#
tokens: 47682/50000 20/146 files (page 3/8)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 8. Use http://codebase.md/cyanheads/atlas-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .clinerules
├── .dockerignore
├── .env.example
├── .github
│   ├── FUNDING.yml
│   └── workflows
│       └── publish.yml
├── .gitignore
├── .ncurc.json
├── .repomixignore
├── automated-tests
│   └── AGENT_TEST_05282025.md
├── CHANGELOG.md
├── CLAUDE.md
├── docker-compose.yml
├── docs
│   └── tree.md
├── examples
│   ├── backup-example
│   │   ├── knowledges.json
│   │   ├── projects.json
│   │   ├── relationships.json
│   │   └── tasks.json
│   ├── deep-research-example
│   │   ├── covington_community_grant_research.md
│   │   └── full-export.json
│   ├── README.md
│   └── webui-example.png
├── LICENSE
├── mcp.json
├── package-lock.json
├── package.json
├── README.md
├── repomix.config.json
├── scripts
│   ├── clean.ts
│   ├── fetch-openapi-spec.ts
│   ├── make-executable.ts
│   └── tree.ts
├── smithery.yaml
├── src
│   ├── config
│   │   └── index.ts
│   ├── index.ts
│   ├── mcp
│   │   ├── resources
│   │   │   ├── index.ts
│   │   │   ├── knowledge
│   │   │   │   └── knowledgeResources.ts
│   │   │   ├── projects
│   │   │   │   └── projectResources.ts
│   │   │   ├── tasks
│   │   │   │   └── taskResources.ts
│   │   │   └── types.ts
│   │   ├── server.ts
│   │   ├── tools
│   │   │   ├── atlas_database_clean
│   │   │   │   ├── cleanDatabase.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_deep_research
│   │   │   │   ├── deepResearch.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_knowledge_add
│   │   │   │   ├── addKnowledge.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_knowledge_delete
│   │   │   │   ├── deleteKnowledge.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_knowledge_list
│   │   │   │   ├── index.ts
│   │   │   │   ├── listKnowledge.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_project_create
│   │   │   │   ├── createProject.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_project_delete
│   │   │   │   ├── deleteProject.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_project_list
│   │   │   │   ├── index.ts
│   │   │   │   ├── listProjects.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_project_update
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   ├── types.ts
│   │   │   │   └── updateProject.ts
│   │   │   ├── atlas_task_create
│   │   │   │   ├── createTask.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_task_delete
│   │   │   │   ├── deleteTask.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_task_list
│   │   │   │   ├── index.ts
│   │   │   │   ├── listTasks.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   └── types.ts
│   │   │   ├── atlas_task_update
│   │   │   │   ├── index.ts
│   │   │   │   ├── responseFormat.ts
│   │   │   │   ├── types.ts
│   │   │   │   └── updateTask.ts
│   │   │   └── atlas_unified_search
│   │   │       ├── index.ts
│   │   │       ├── responseFormat.ts
│   │   │       ├── types.ts
│   │   │       └── unifiedSearch.ts
│   │   └── transports
│   │       ├── authentication
│   │       │   └── authMiddleware.ts
│   │       ├── httpTransport.ts
│   │       └── stdioTransport.ts
│   ├── services
│   │   └── neo4j
│   │       ├── backupRestoreService
│   │       │   ├── backupRestoreTypes.ts
│   │       │   ├── backupUtils.ts
│   │       │   ├── exportLogic.ts
│   │       │   ├── importLogic.ts
│   │       │   ├── index.ts
│   │       │   └── scripts
│   │       │       ├── db-backup.ts
│   │       │       └── db-import.ts
│   │       ├── driver.ts
│   │       ├── events.ts
│   │       ├── helpers.ts
│   │       ├── index.ts
│   │       ├── knowledgeService.ts
│   │       ├── projectService.ts
│   │       ├── searchService
│   │       │   ├── fullTextSearchLogic.ts
│   │       │   ├── index.ts
│   │       │   ├── searchTypes.ts
│   │       │   └── unifiedSearchLogic.ts
│   │       ├── taskService.ts
│   │       ├── types.ts
│   │       └── utils.ts
│   ├── types
│   │   ├── errors.ts
│   │   ├── mcp.ts
│   │   └── tool.ts
│   ├── utils
│   │   ├── index.ts
│   │   ├── internal
│   │   │   ├── errorHandler.ts
│   │   │   ├── index.ts
│   │   │   ├── logger.ts
│   │   │   └── requestContext.ts
│   │   ├── metrics
│   │   │   ├── index.ts
│   │   │   └── tokenCounter.ts
│   │   ├── parsing
│   │   │   ├── dateParser.ts
│   │   │   ├── index.ts
│   │   │   └── jsonParser.ts
│   │   └── security
│   │       ├── idGenerator.ts
│   │       ├── index.ts
│   │       ├── rateLimiter.ts
│   │       └── sanitization.ts
│   └── webui
│       ├── index.html
│       ├── logic
│       │   ├── api-service.js
│       │   ├── app-state.js
│       │   ├── config.js
│       │   ├── dom-elements.js
│       │   ├── main.js
│       │   └── ui-service.js
│       └── styling
│           ├── base.css
│           ├── components.css
│           ├── layout.css
│           └── theme.css
├── tsconfig.json
├── tsconfig.typedoc.json
└── typedoc.json
```

# Files

--------------------------------------------------------------------------------
/src/webui/styling/layout.css:
--------------------------------------------------------------------------------

```css
  1 | /* ==========================================================================
  2 |    Main Application Layout (#app, header, main, footer)
  3 |    ========================================================================== */
  4 | #app {
  5 |   position: relative; /* For theme toggle positioning */
  6 |   max-width: 1000px;
  7 |   margin: var(--spacing-xl) auto;
  8 |   padding: var(--spacing-lg) var(--spacing-xl);
  9 |   background-color: var(--card-bg-color);
 10 |   border-radius: var(--border-radius-lg);
 11 |   box-shadow: 0 8px 24px var(--shadow-color);
 12 |   transition: background-color 0.2s ease-out;
 13 | }
 14 | 
 15 | .app-header {
 16 |   margin-bottom: var(--spacing-xl);
 17 | }
 18 | 
 19 | .app-footer {
 20 |   margin-top: var(--spacing-xl);
 21 |   padding-top: var(--spacing-lg);
 22 |   border-top: 1px solid var(--border-color);
 23 | }
 24 | 
 25 | /* ==========================================================================
 26 |    Controls Section (Project Select, Refresh Button)
 27 |    ========================================================================== */
 28 | .controls-section {
 29 |   margin-bottom: var(--spacing-xl);
 30 | }
 31 | .controls-container {
 32 |   display: flex;
 33 |   align-items: center;
 34 |   gap: var(--spacing-md);
 35 |   flex-wrap: wrap;
 36 | }
 37 | 
 38 | .controls-container label {
 39 |   margin-bottom: 0; /* Align with controls */
 40 |   flex-shrink: 0; /* Prevent label from shrinking */
 41 | }
 42 | 
 43 | /* ==========================================================================
 44 |    Data Sections (Project Details, Tasks, Knowledge)
 45 |    ========================================================================== */
 46 | .data-section {
 47 |   margin-top: var(--spacing-xl);
 48 |   padding: var(--spacing-lg);
 49 |   border: 1px solid var(--border-color);
 50 |   border-radius: var(--border-radius-md);
 51 |   background-color: var(--data-section-bg);
 52 |   transition:
 53 |     background-color 0.2s ease-out,
 54 |     border-color 0.2s ease-out;
 55 | }
 56 | 
 57 | .section-header {
 58 |   display: flex;
 59 |   justify-content: space-between;
 60 |   align-items: center;
 61 |   margin-bottom: var(--spacing-md);
 62 |   flex-wrap: wrap; /* Allow wrapping for view controls */
 63 |   gap: var(--spacing-md);
 64 | }
 65 | 
 66 | .section-header h3 {
 67 |   margin-bottom: 0; /* Remove bottom margin as it's handled by section-header */
 68 | }
 69 | 
 70 | /* Project Details Grid Specifics */
 71 | #details-content.details-grid {
 72 |   display: grid;
 73 |   grid-template-columns: auto 1fr; /* Label and value */
 74 |   gap: var(--spacing-sm) var(--spacing-md);
 75 |   align-items: start; /* Align items to the start of their grid cell */
 76 | }
 77 | 
 78 | #details-content.details-grid > .data-item {
 79 |   display: contents; /* Allow children (strong, div/pre/ul) to participate in the grid */
 80 | }
 81 | 
 82 | #details-content.details-grid > .data-item > strong {
 83 |   /* Label */
 84 |   font-weight: 500;
 85 |   color: var(--secondary-text-color);
 86 |   padding-top: var(--spacing-xs); /* Align with multi-line values better */
 87 |   grid-column: 1;
 88 | }
 89 | #details-content.details-grid > .data-item > div,
 90 | #details-content.details-grid > .data-item > pre,
 91 | #details-content.details-grid > .data-item > ul {
 92 |   /* Value */
 93 |   grid-column: 2;
 94 |   margin-bottom: 0; /* Remove default margin from these elements when in grid */
 95 | }
 96 | #details-content.details-grid > .data-item > ul {
 97 |   list-style-position: outside; /* More standard list appearance */
 98 |   padding-left: var(--spacing-md); /* Indent list items */
 99 |   margin-top: 0;
100 | }
101 | #details-content.details-grid > .data-item > ul li {
102 |   margin-bottom: var(--spacing-xs);
103 | }
104 | 
105 | /* General Data Items (Used for Tasks, Knowledge in non-grid layout) */
106 | .data-item {
107 |   padding-bottom: var(--spacing-md);
108 |   margin-bottom: var(--spacing-md);
109 |   border-bottom: 1px solid var(--data-item-border-color);
110 |   transition: border-color 0.2s ease-out;
111 | }
112 | 
113 | .data-item:last-child {
114 |   border-bottom: none;
115 |   margin-bottom: 0;
116 |   padding-bottom: 0;
117 | }
118 | 
119 | .data-item strong {
120 |   /* Used in task/knowledge titles */
121 |   color: var(--text-color);
122 |   font-weight: 600;
123 |   display: block; /* Make title take full width */
124 |   margin-bottom: var(--spacing-xs);
125 | }
126 | 
127 | .data-item div {
128 |   /* General content div within a data item */
129 |   margin-bottom: var(--spacing-xs);
130 | }
131 | 
132 | /* ==========================================================================
133 |    Mermaid Diagram Container
134 |    ========================================================================== */
135 | .mermaid-container {
136 |   width: 100%;
137 |   min-height: 300px; /* Adjust as needed */
138 |   overflow: auto; /* For larger diagrams */
139 |   margin-top: var(--spacing-md);
140 |   border: 1px solid var(--border-color);
141 |   border-radius: var(--border-radius-md);
142 |   padding: var(--spacing-md);
143 |   background-color: var(
144 |     --card-bg-color
145 |   ); /* Match card background for consistency */
146 |   box-sizing: border-box; /* Ensure padding and border are included in width/height */
147 | }
148 | .mermaid-container svg {
149 |   display: block; /* Remove extra space below SVG */
150 |   margin: auto; /* Center if smaller than container */
151 |   max-width: 100%; /* Ensure SVG scales down if too wide */
152 | }
153 | 
154 | /* ==========================================================================
155 |    Task Board Styles
156 |    ========================================================================== */
157 | .task-board-grid {
158 |   display: flex;
159 |   gap: var(--spacing-md);
160 |   overflow-x: auto; /* Allow horizontal scrolling for columns */
161 |   padding-bottom: var(--spacing-md); /* Space for scrollbar */
162 |   min-height: 300px; /* Ensure columns have some height */
163 | }
164 | 
165 | .task-board-column {
166 |   flex: 0 0 280px; /* Fixed width for columns, adjust as needed */
167 |   max-width: 280px;
168 |   background-color: var(
169 |     --bg-color
170 |   ); /* Slightly different from card for distinction */
171 |   border-radius: var(--border-radius-md);
172 |   padding: var(--spacing-md);
173 |   border: 1px solid var(--border-color);
174 |   display: flex;
175 |   flex-direction: column;
176 |   transition: background-color 0.2s ease-out;
177 | }
178 | 
179 | .task-board-column h4 {
180 |   font-size: 1.1rem;
181 |   font-weight: 600;
182 |   margin-bottom: var(--spacing-md);
183 |   padding-bottom: var(--spacing-sm);
184 |   border-bottom: 1px solid var(--border-color);
185 |   text-align: center;
186 | }
187 | 
188 | .task-board-column-content {
189 |   flex-grow: 1;
190 |   overflow-y: auto; /* Allow scrolling within a column if many tasks */
191 |   display: flex;
192 |   flex-direction: column;
193 |   gap: var(--spacing-sm);
194 | }
195 | 
196 | /* ==========================================================================
197 |    Data Explorer Styles
198 |    ========================================================================== */
199 | #data-explorer-container .controls-container {
200 |   margin-bottom: var(--spacing-lg); /* Space between controls and content */
201 | }
202 | 
203 | .explorer-node-list {
204 |   max-height: 400px; /* Limit height and allow scrolling */
205 |   overflow-y: auto;
206 |   border: 1px solid var(--border-color);
207 |   border-radius: var(--border-radius-md);
208 |   padding: var(--spacing-sm);
209 |   margin-bottom: var(--spacing-lg); /* Space before details section */
210 | }
211 | 
212 | #data-explorer-details {
213 |   /* Uses .details-grid, so existing styles apply. 
214 |      Can add specific overrides if needed */
215 |   margin-top: var(--spacing-lg);
216 |   padding-top: var(--spacing-lg);
217 |   border-top: 1px solid var(--border-color);
218 | }
219 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_update/responseFormat.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ProjectResponse } from "../../../types/mcp.js";
  2 | import { createToolResponse } from "../../../types/mcp.js"; // Import the new response creator
  3 | 
  4 | /**
  5 |  * Defines a generic interface for formatting data into a string.
  6 |  */
  7 | interface ResponseFormatter<T> {
  8 |   format(data: T): string;
  9 | }
 10 | 
 11 | /**
 12 |  * Extends the ProjectResponse to include Neo4j properties structure
 13 |  */
 14 | interface SingleProjectResponse extends ProjectResponse {
 15 |   properties?: any;
 16 |   identity?: number;
 17 |   labels?: string[];
 18 |   elementId?: string;
 19 | }
 20 | 
 21 | /**
 22 |  * Interface for bulk project update response
 23 |  */
 24 | interface BulkProjectResponse {
 25 |   success: boolean;
 26 |   message: string;
 27 |   updated: (ProjectResponse & {
 28 |     properties?: any;
 29 |     identity?: number;
 30 |     labels?: string[];
 31 |     elementId?: string;
 32 |   })[];
 33 |   errors: {
 34 |     index: number;
 35 |     project: {
 36 |       // Original input for the failed update
 37 |       id: string;
 38 |       updates: any;
 39 |     };
 40 |     error: {
 41 |       code: string;
 42 |       message: string;
 43 |       details?: any;
 44 |     };
 45 |   }[];
 46 | }
 47 | 
 48 | /**
 49 |  * Formatter for individual project modification responses
 50 |  */
 51 | export class SingleProjectUpdateFormatter
 52 |   implements ResponseFormatter<SingleProjectResponse>
 53 | {
 54 |   format(data: SingleProjectResponse): string {
 55 |     // Extract project properties from Neo4j structure or direct data
 56 |     const projectData = data.properties || data;
 57 |     const {
 58 |       name,
 59 |       id,
 60 |       status,
 61 |       taskType,
 62 |       updatedAt,
 63 |       description,
 64 |       urls,
 65 |       completionRequirements,
 66 |       outputFormat,
 67 |       createdAt,
 68 |     } = projectData;
 69 | 
 70 |     // Create a structured summary section
 71 |     const summary =
 72 |       `Project Modified Successfully\n\n` +
 73 |       `Project: ${name || "Unnamed Project"}\n` +
 74 |       `ID: ${id || "Unknown ID"}\n` +
 75 |       `Status: ${status || "Unknown Status"}\n` +
 76 |       `Type: ${taskType || "Unknown Type"}\n` +
 77 |       `Updated: ${updatedAt ? new Date(updatedAt).toLocaleString() : "Unknown Date"}\n`;
 78 | 
 79 |     // Create a comprehensive details section
 80 |     let details = `Project Details:\n`;
 81 |     const fieldLabels: Record<keyof SingleProjectResponse, string> = {
 82 |       id: "ID",
 83 |       name: "Name",
 84 |       description: "Description",
 85 |       status: "Status",
 86 |       urls: "URLs",
 87 |       completionRequirements: "Completion Requirements",
 88 |       outputFormat: "Output Format",
 89 |       taskType: "Task Type",
 90 |       createdAt: "Created At",
 91 |       updatedAt: "Updated At",
 92 |       properties: "Raw Properties",
 93 |       identity: "Neo4j Identity",
 94 |       labels: "Neo4j Labels",
 95 |       elementId: "Neo4j Element ID",
 96 |       dependencies: "Dependencies",
 97 |     };
 98 |     const relevantKeys: (keyof SingleProjectResponse)[] = [
 99 |       "id",
100 |       "name",
101 |       "description",
102 |       "status",
103 |       "taskType",
104 |       "completionRequirements",
105 |       "outputFormat",
106 |       "urls",
107 |       "createdAt",
108 |       "updatedAt",
109 |     ];
110 | 
111 |     relevantKeys.forEach((key) => {
112 |       if (projectData[key] !== undefined && projectData[key] !== null) {
113 |         let value = projectData[key];
114 |         if (Array.isArray(value)) {
115 |           value =
116 |             value.length > 0
117 |               ? value
118 |                   .map((item) =>
119 |                     typeof item === "object" ? JSON.stringify(item) : item,
120 |                   )
121 |                   .join(", ")
122 |               : "None";
123 |         } else if (
124 |           typeof value === "string" &&
125 |           (key === "createdAt" || key === "updatedAt")
126 |         ) {
127 |           try {
128 |             value = new Date(value).toLocaleString();
129 |           } catch (e) {
130 |             /* Keep original */
131 |           }
132 |         }
133 |         details += `  ${fieldLabels[key] || key}: ${value}\n`;
134 |       }
135 |     });
136 | 
137 |     return `${summary}\n${details}`;
138 |   }
139 | }
140 | 
141 | /**
142 |  * Formatter for bulk project update responses
143 |  */
144 | export class BulkProjectUpdateFormatter
145 |   implements ResponseFormatter<BulkProjectResponse>
146 | {
147 |   format(data: BulkProjectResponse): string {
148 |     const { success, message, updated, errors } = data;
149 | 
150 |     const summary =
151 |       `${success && errors.length === 0 ? "Projects Updated Successfully" : "Project Updates Completed"}\n\n` +
152 |       `Status: ${success && errors.length === 0 ? "✅ Success" : errors.length > 0 ? "⚠️ Partial Success / Errors" : "✅ Success (No items or no errors)"}\n` +
153 |       `Summary: ${message}\n` +
154 |       `Updated: ${updated.length} project(s)\n` +
155 |       `Errors: ${errors.length} error(s)\n`;
156 | 
157 |     let updatedSection = "";
158 |     if (updated.length > 0) {
159 |       updatedSection = `\n--- Modified Projects (${updated.length}) ---\n\n`;
160 |       updatedSection += updated
161 |         .map((project, index) => {
162 |           const projectData = project.properties || project;
163 |           return (
164 |             `${index + 1}. ${projectData.name || "Unnamed Project"} (ID: ${projectData.id || "N/A"})\n` +
165 |             `   Status: ${projectData.status || "N/A"}\n` +
166 |             `   Updated: ${projectData.updatedAt ? new Date(projectData.updatedAt).toLocaleString() : "N/A"}`
167 |           );
168 |         })
169 |         .join("\n\n");
170 |     }
171 | 
172 |     let errorsSection = "";
173 |     if (errors.length > 0) {
174 |       errorsSection = `\n--- Errors Encountered (${errors.length}) ---\n\n`;
175 |       errorsSection += errors
176 |         .map((errorItem, index) => {
177 |           return (
178 |             `${index + 1}. Error updating Project ID: "${errorItem.project.id}"\n` +
179 |             `   Error Code: ${errorItem.error.code}\n` +
180 |             `   Message: ${errorItem.error.message}` +
181 |             (errorItem.error.details
182 |               ? `\n   Details: ${JSON.stringify(errorItem.error.details)}`
183 |               : "")
184 |           );
185 |         })
186 |         .join("\n\n");
187 |     }
188 | 
189 |     return `${summary}${updatedSection}${errorsSection}`.trim();
190 |   }
191 | }
192 | 
193 | /**
194 |  * Create a formatted, human-readable response for the atlas_project_update tool
195 |  *
196 |  * @param data The raw project modification response (SingleProjectResponse or BulkProjectResponse)
197 |  * @param isError Whether this response represents an error condition (primarily for single responses)
198 |  * @returns Formatted MCP tool response with appropriate structure
199 |  */
200 | export function formatProjectUpdateResponse(data: any, isError = false): any {
201 |   const isBulkResponse =
202 |     data.hasOwnProperty("success") &&
203 |     data.hasOwnProperty("updated") &&
204 |     data.hasOwnProperty("errors");
205 | 
206 |   let formattedText: string;
207 |   let finalIsError: boolean;
208 | 
209 |   if (isBulkResponse) {
210 |     const formatter = new BulkProjectUpdateFormatter();
211 |     const bulkData = data as BulkProjectResponse;
212 |     formattedText = formatter.format(bulkData);
213 |     finalIsError = !bulkData.success || bulkData.errors.length > 0;
214 |   } else {
215 |     const formatter = new SingleProjectUpdateFormatter();
216 |     // For single response, 'data' is the updated project object.
217 |     // 'isError' must be determined by the caller if an error occurred before this point.
218 |     formattedText = formatter.format(data as SingleProjectResponse);
219 |     finalIsError = isError;
220 |   }
221 |   return createToolResponse(formattedText, finalIsError);
222 | }
223 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_task_update/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | import {
  3 |   McpToolResponse,
  4 |   PriorityLevel,
  5 |   ResponseFormat,
  6 |   TaskStatus,
  7 |   TaskType,
  8 |   createPriorityLevelEnum,
  9 |   createResponseFormatEnum,
 10 |   createTaskStatusEnum,
 11 |   createTaskTypeEnum,
 12 | } from "../../../types/mcp.js";
 13 | 
 14 | export const TaskUpdateSchema = z.object({
 15 |   id: z.string().describe("Identifier of the existing task to be modified"),
 16 |   updates: z
 17 |     .object({
 18 |       title: z
 19 |         .string()
 20 |         .min(5)
 21 |         .max(150)
 22 |         .optional()
 23 |         .describe("Modified task title (5-150 characters)"),
 24 |       description: z
 25 |         .string()
 26 |         .optional()
 27 |         .describe("Revised task description and requirements"),
 28 |       priority: createPriorityLevelEnum()
 29 |         .optional()
 30 |         .describe("Updated priority level reflecting current importance"),
 31 |       status: createTaskStatusEnum()
 32 |         .optional()
 33 |         .describe("Updated task status reflecting current progress"),
 34 |       assignedTo: z.string().nullable().optional().describe(
 35 |         // Allow null for unassignment
 36 |         "Updated assignee ID for task responsibility (null to unassign)",
 37 |       ),
 38 |       urls: z
 39 |         .array(
 40 |           z.object({
 41 |             title: z.string(),
 42 |             url: z.string(),
 43 |           }),
 44 |         )
 45 |         .optional()
 46 |         .describe("Modified reference materials and documentation links"),
 47 |       tags: z
 48 |         .array(z.string())
 49 |         .optional()
 50 |         .describe("Updated categorical labels for task organization"),
 51 |       completionRequirements: z
 52 |         .string()
 53 |         .optional()
 54 |         .describe("Revised success criteria for task completion"),
 55 |       outputFormat: z
 56 |         .string()
 57 |         .optional()
 58 |         .describe("Modified deliverable specification for task output"),
 59 |       taskType: createTaskTypeEnum()
 60 |         .optional()
 61 |         .describe("Revised classification for task categorization"),
 62 |     })
 63 |     .describe(
 64 |       "Partial update object containing only fields that need modification",
 65 |     ),
 66 | });
 67 | 
 68 | const SingleTaskUpdateSchema = z
 69 |   .object({
 70 |     mode: z.literal("single"),
 71 |     id: z.string(),
 72 |     updates: z.object({
 73 |       title: z.string().min(5).max(150).optional(),
 74 |       description: z.string().optional(),
 75 |       priority: createPriorityLevelEnum().optional(),
 76 |       status: createTaskStatusEnum().optional(),
 77 |       assignedTo: z.string().nullable().optional(), // Allow null
 78 |       urls: z
 79 |         .array(
 80 |           z.object({
 81 |             title: z.string(),
 82 |             url: z.string(),
 83 |           }),
 84 |         )
 85 |         .optional(),
 86 |       tags: z.array(z.string()).optional(),
 87 |       completionRequirements: z.string().optional(),
 88 |       outputFormat: z.string().optional(),
 89 |       taskType: createTaskTypeEnum().optional(),
 90 |     }),
 91 |     responseFormat: createResponseFormatEnum()
 92 |       .optional()
 93 |       .default(ResponseFormat.FORMATTED)
 94 |       .describe(
 95 |         "Desired response format: 'formatted' (default string) or 'json' (raw object)",
 96 |       ),
 97 |   })
 98 |   .describe("Update an individual task with selective field modifications");
 99 | 
100 | const BulkTaskUpdateSchema = z
101 |   .object({
102 |     mode: z.literal("bulk"),
103 |     tasks: z
104 |       .array(
105 |         z.object({
106 |           id: z.string().describe("Identifier of the task to update"),
107 |           updates: z.object({
108 |             title: z.string().min(5).max(150).optional(),
109 |             description: z.string().optional(),
110 |             priority: createPriorityLevelEnum().optional(),
111 |             status: createTaskStatusEnum().optional(),
112 |             assignedTo: z.string().nullable().optional(), // Allow null
113 |             urls: z
114 |               .array(
115 |                 z.object({
116 |                   title: z.string(),
117 |                   url: z.string(),
118 |                 }),
119 |               )
120 |               .optional(),
121 |             tags: z.array(z.string()).optional(),
122 |             completionRequirements: z.string().optional(),
123 |             outputFormat: z.string().optional(),
124 |             taskType: createTaskTypeEnum().optional(),
125 |           }),
126 |         }),
127 |       )
128 |       .min(1)
129 |       .max(100)
130 |       .describe(
131 |         "Collection of task updates to be applied in a single transaction",
132 |       ),
133 |     responseFormat: createResponseFormatEnum()
134 |       .optional()
135 |       .default(ResponseFormat.FORMATTED)
136 |       .describe(
137 |         "Desired response format: 'formatted' (default string) or 'json' (raw object)",
138 |       ),
139 |   })
140 |   .describe("Update multiple related tasks in a single efficient transaction");
141 | 
142 | // Schema shapes for tool registration
143 | export const AtlasTaskUpdateSchemaShape = {
144 |   mode: z
145 |     .enum(["single", "bulk"])
146 |     .describe(
147 |       "Operation mode - 'single' for one task, 'bulk' for multiple tasks",
148 |     ),
149 |   id: z
150 |     .string()
151 |     .optional()
152 |     .describe("Existing task ID to update (required for mode='single')"),
153 |   updates: z
154 |     .object({
155 |       title: z.string().min(5).max(150).optional(),
156 |       description: z.string().optional(),
157 |       priority: createPriorityLevelEnum().optional(),
158 |       status: createTaskStatusEnum().optional(),
159 |       assignedTo: z.string().nullable().optional(), // Allow null
160 |       urls: z
161 |         .array(
162 |           z.object({
163 |             title: z.string(),
164 |             url: z.string(),
165 |           }),
166 |         )
167 |         .optional(),
168 |       tags: z.array(z.string()).optional(),
169 |       completionRequirements: z.string().optional(),
170 |       outputFormat: z.string().optional(),
171 |       taskType: createTaskTypeEnum().optional(),
172 |     })
173 |     .optional()
174 |     .describe(
175 |       "Object containing fields to modify (only specified fields will be updated) (required for mode='single')",
176 |     ),
177 |   tasks: z
178 |     .array(
179 |       z.object({
180 |         id: z.string(),
181 |         updates: z.object({
182 |           title: z.string().min(5).max(150).optional(),
183 |           description: z.string().optional(),
184 |           priority: createPriorityLevelEnum().optional(),
185 |           status: createTaskStatusEnum().optional(),
186 |           assignedTo: z.string().nullable().optional(), // Allow null
187 |           urls: z
188 |             .array(
189 |               z.object({
190 |                 title: z.string(),
191 |                 url: z.string(),
192 |               }),
193 |             )
194 |             .optional(),
195 |           tags: z.array(z.string()).optional(),
196 |           completionRequirements: z.string().optional(),
197 |           outputFormat: z.string().optional(),
198 |           taskType: createTaskTypeEnum().optional(),
199 |         }),
200 |       }),
201 |     )
202 |     .optional()
203 |     .describe(
204 |       "Array of task updates, each containing an ID and updates object (required for mode='bulk')",
205 |     ),
206 |   responseFormat: createResponseFormatEnum()
207 |     .optional()
208 |     .describe(
209 |       "Desired response format: 'formatted' (default string) or 'json' (raw object)",
210 |     ),
211 | } as const;
212 | 
213 | // Schema for validation
214 | export const AtlasTaskUpdateSchema = z.discriminatedUnion("mode", [
215 |   SingleTaskUpdateSchema,
216 |   BulkTaskUpdateSchema,
217 | ]);
218 | 
219 | export type AtlasTaskUpdateInput = z.infer<typeof AtlasTaskUpdateSchema>;
220 | export type TaskUpdateInput = z.infer<typeof TaskUpdateSchema>;
221 | export type AtlasTaskUpdateResponse = McpToolResponse;
222 | 
```

--------------------------------------------------------------------------------
/src/utils/security/rateLimiter.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Provides a generic `RateLimiter` class for implementing rate limiting logic.
  3 |  * It supports configurable time windows, request limits, and automatic cleanup of expired entries.
  4 |  * @module src/utils/security/rateLimiter
  5 |  */
  6 | import { environment } from "../../config/index.js";
  7 | import { BaseErrorCode, McpError } from "../../types/errors.js";
  8 | import { logger, RequestContext, requestContextService } from "../index.js";
  9 | 
 10 | /**
 11 |  * Defines configuration options for the {@link RateLimiter}.
 12 |  */
 13 | export interface RateLimitConfig {
 14 |   /** Time window in milliseconds. */
 15 |   windowMs: number;
 16 |   /** Maximum number of requests allowed in the window. */
 17 |   maxRequests: number;
 18 |   /** Custom error message template. Can include `{waitTime}` placeholder. */
 19 |   errorMessage?: string;
 20 |   /** If true, skip rate limiting in development. */
 21 |   skipInDevelopment?: boolean;
 22 |   /** Optional function to generate a custom key for rate limiting. */
 23 |   keyGenerator?: (identifier: string, context?: RequestContext) => string;
 24 |   /** How often, in milliseconds, to clean up expired entries. */
 25 |   cleanupInterval?: number;
 26 | }
 27 | 
 28 | /**
 29 |  * Represents an individual entry for tracking requests against a rate limit key.
 30 |  */
 31 | export interface RateLimitEntry {
 32 |   /** Current request count. */
 33 |   count: number;
 34 |   /** When the window resets (timestamp in milliseconds). */
 35 |   resetTime: number;
 36 | }
 37 | 
 38 | /**
 39 |  * A generic rate limiter class using an in-memory store.
 40 |  * Controls frequency of operations based on unique keys.
 41 |  */
 42 | export class RateLimiter {
 43 |   /**
 44 |    * Stores current request counts and reset times for each key.
 45 |    * @private
 46 |    */
 47 |   private limits: Map<string, RateLimitEntry>;
 48 |   /**
 49 |    * Timer ID for periodic cleanup.
 50 |    * @private
 51 |    */
 52 |   private cleanupTimer: NodeJS.Timeout | null = null;
 53 | 
 54 |   /**
 55 |    * Default configuration values.
 56 |    * @private
 57 |    */
 58 |   private static DEFAULT_CONFIG: RateLimitConfig = {
 59 |     windowMs: 15 * 60 * 1000, // 15 minutes
 60 |     maxRequests: 100,
 61 |     errorMessage:
 62 |       "Rate limit exceeded. Please try again in {waitTime} seconds.",
 63 |     skipInDevelopment: false,
 64 |     cleanupInterval: 5 * 60 * 1000, // 5 minutes
 65 |   };
 66 | 
 67 |   /**
 68 |    * Creates a new `RateLimiter` instance.
 69 |    * @param config - Configuration options, merged with defaults.
 70 |    */
 71 |   constructor(private config: RateLimitConfig) {
 72 |     this.config = { ...RateLimiter.DEFAULT_CONFIG, ...config };
 73 |     this.limits = new Map();
 74 |     this.startCleanupTimer();
 75 |   }
 76 | 
 77 |   /**
 78 |    * Starts the periodic timer to clean up expired rate limit entries.
 79 |    * @private
 80 |    */
 81 |   private startCleanupTimer(): void {
 82 |     if (this.cleanupTimer) {
 83 |       clearInterval(this.cleanupTimer);
 84 |     }
 85 | 
 86 |     const interval =
 87 |       this.config.cleanupInterval ?? RateLimiter.DEFAULT_CONFIG.cleanupInterval;
 88 | 
 89 |     if (interval && interval > 0) {
 90 |       this.cleanupTimer = setInterval(() => {
 91 |         this.cleanupExpiredEntries();
 92 |       }, interval);
 93 | 
 94 |       if (this.cleanupTimer.unref) {
 95 |         this.cleanupTimer.unref(); // Allow Node.js process to exit if only timer active
 96 |       }
 97 |     }
 98 |   }
 99 | 
100 |   /**
101 |    * Removes expired rate limit entries from the store.
102 |    * @private
103 |    */
104 |   private cleanupExpiredEntries(): void {
105 |     const now = Date.now();
106 |     let expiredCount = 0;
107 | 
108 |     for (const [key, entry] of this.limits.entries()) {
109 |       if (now >= entry.resetTime) {
110 |         this.limits.delete(key);
111 |         expiredCount++;
112 |       }
113 |     }
114 | 
115 |     if (expiredCount > 0) {
116 |       const logContext = requestContextService.createRequestContext({
117 |         operation: "RateLimiter.cleanupExpiredEntries",
118 |         cleanedCount: expiredCount,
119 |         totalRemainingAfterClean: this.limits.size,
120 |       });
121 |       logger.debug(
122 |         `Cleaned up ${expiredCount} expired rate limit entries`,
123 |         logContext,
124 |       );
125 |     }
126 |   }
127 | 
128 |   /**
129 |    * Updates the configuration of the rate limiter instance.
130 |    * @param config - New configuration options to merge.
131 |    */
132 |   public configure(config: Partial<RateLimitConfig>): void {
133 |     this.config = { ...this.config, ...config };
134 |     if (config.cleanupInterval !== undefined) {
135 |       this.startCleanupTimer();
136 |     }
137 |   }
138 | 
139 |   /**
140 |    * Retrieves a copy of the current rate limiter configuration.
141 |    * @returns The current configuration.
142 |    */
143 |   public getConfig(): RateLimitConfig {
144 |     return { ...this.config };
145 |   }
146 | 
147 |   /**
148 |    * Resets all rate limits by clearing the internal store.
149 |    */
150 |   public reset(): void {
151 |     this.limits.clear();
152 |     const logContext = requestContextService.createRequestContext({
153 |       operation: "RateLimiter.reset",
154 |     });
155 |     logger.debug("Rate limiter reset, all limits cleared", logContext);
156 |   }
157 | 
158 |   /**
159 |    * Checks if a request exceeds the configured rate limit.
160 |    * Throws an `McpError` if the limit is exceeded.
161 |    *
162 |    * @param key - A unique identifier for the request source.
163 |    * @param context - Optional request context for custom key generation.
164 |    * @throws {McpError} If the rate limit is exceeded.
165 |    */
166 |   public check(key: string, context?: RequestContext): void {
167 |     if (this.config.skipInDevelopment && environment === "development") {
168 |       return;
169 |     }
170 | 
171 |     const limitKey = this.config.keyGenerator
172 |       ? this.config.keyGenerator(key, context)
173 |       : key;
174 | 
175 |     const now = Date.now();
176 |     const entry = this.limits.get(limitKey);
177 | 
178 |     if (!entry || now >= entry.resetTime) {
179 |       this.limits.set(limitKey, {
180 |         count: 1,
181 |         resetTime: now + this.config.windowMs,
182 |       });
183 |       return;
184 |     }
185 | 
186 |     if (entry.count >= this.config.maxRequests) {
187 |       const waitTime = Math.ceil((entry.resetTime - now) / 1000);
188 |       const errorMessage = (
189 |         this.config.errorMessage || RateLimiter.DEFAULT_CONFIG.errorMessage!
190 |       ).replace("{waitTime}", waitTime.toString());
191 | 
192 |       throw new McpError(BaseErrorCode.RATE_LIMITED, errorMessage, {
193 |         waitTimeSeconds: waitTime,
194 |         key: limitKey,
195 |         limit: this.config.maxRequests,
196 |         windowMs: this.config.windowMs,
197 |       });
198 |     }
199 | 
200 |     entry.count++;
201 |   }
202 | 
203 |   /**
204 |    * Retrieves the current rate limit status for a specific key.
205 |    * @param key - The rate limit key.
206 |    * @returns Status object or `null` if no entry exists.
207 |    */
208 |   public getStatus(key: string): {
209 |     current: number;
210 |     limit: number;
211 |     remaining: number;
212 |     resetTime: number;
213 |   } | null {
214 |     const entry = this.limits.get(key);
215 |     if (!entry) {
216 |       return null;
217 |     }
218 |     return {
219 |       current: entry.count,
220 |       limit: this.config.maxRequests,
221 |       remaining: Math.max(0, this.config.maxRequests - entry.count),
222 |       resetTime: entry.resetTime,
223 |     };
224 |   }
225 | 
226 |   /**
227 |    * Stops the cleanup timer and clears all rate limit entries.
228 |    * Call when the rate limiter is no longer needed.
229 |    */
230 |   public dispose(): void {
231 |     if (this.cleanupTimer) {
232 |       clearInterval(this.cleanupTimer);
233 |       this.cleanupTimer = null;
234 |     }
235 |     this.limits.clear();
236 |   }
237 | }
238 | 
239 | /**
240 |  * Default singleton instance of the `RateLimiter`.
241 |  * Initialized with default configuration. Use `rateLimiter.configure({})` to customize.
242 |  */
243 | export const rateLimiter = new RateLimiter({
244 |   windowMs: 15 * 60 * 1000, // Default: 15 minutes
245 |   maxRequests: 100, // Default: 100 requests per window
246 | });
247 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_knowledge_add/responseFormat.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createToolResponse } from "../../../types/mcp.js"; // Import the new response creator
  2 | 
  3 | /**
  4 |  * Defines a generic interface for formatting data into a string.
  5 |  */
  6 | interface ResponseFormatter<T> {
  7 |   format(data: T): string;
  8 | }
  9 | 
 10 | /**
 11 |  * Interface for a single knowledge item response
 12 |  */
 13 | interface SingleKnowledgeResponse {
 14 |   id: string;
 15 |   projectId: string;
 16 |   text: string;
 17 |   tags?: string[];
 18 |   domain: string;
 19 |   citations?: string[];
 20 |   createdAt: string;
 21 |   updatedAt: string;
 22 |   properties?: any; // Neo4j properties if not fully mapped
 23 |   identity?: number; // Neo4j internal ID
 24 |   labels?: string[]; // Neo4j labels
 25 |   elementId?: string; // Neo4j element ID
 26 | }
 27 | 
 28 | /**
 29 |  * Interface for bulk knowledge addition response
 30 |  */
 31 | interface BulkKnowledgeResponse {
 32 |   success: boolean;
 33 |   message: string;
 34 |   created: (SingleKnowledgeResponse & {
 35 |     properties?: any;
 36 |     identity?: number;
 37 |     labels?: string[];
 38 |     elementId?: string;
 39 |   })[];
 40 |   errors: {
 41 |     index: number;
 42 |     knowledge: any; // Original input for the failed item
 43 |     error: {
 44 |       code: string;
 45 |       message: string;
 46 |       details?: any;
 47 |     };
 48 |   }[];
 49 | }
 50 | 
 51 | /**
 52 |  * Formatter for single knowledge item addition responses
 53 |  */
 54 | export class SingleKnowledgeFormatter
 55 |   implements ResponseFormatter<SingleKnowledgeResponse>
 56 | {
 57 |   format(data: SingleKnowledgeResponse): string {
 58 |     // Extract knowledge properties from Neo4j structure or direct data
 59 |     const knowledgeData = data.properties || data;
 60 |     const {
 61 |       id,
 62 |       projectId,
 63 |       domain,
 64 |       createdAt,
 65 |       text,
 66 |       tags,
 67 |       citations,
 68 |       updatedAt,
 69 |     } = knowledgeData;
 70 | 
 71 |     // Create a summary section
 72 |     const summary =
 73 |       `Knowledge Item Added Successfully\n\n` +
 74 |       `ID: ${id || "Unknown ID"}\n` +
 75 |       `Project ID: ${projectId || "Unknown Project"}\n` +
 76 |       `Domain: ${domain || "Uncategorized"}\n` +
 77 |       `Created: ${createdAt ? new Date(createdAt).toLocaleString() : "Unknown Date"}\n`;
 78 | 
 79 |     // Create a comprehensive details section
 80 |     const fieldLabels: Record<keyof SingleKnowledgeResponse, string> = {
 81 |       id: "ID",
 82 |       projectId: "Project ID",
 83 |       text: "Content",
 84 |       tags: "Tags",
 85 |       domain: "Domain",
 86 |       citations: "Citations",
 87 |       createdAt: "Created At",
 88 |       updatedAt: "Updated At",
 89 |       // Neo4j specific fields are generally not for direct user display unless needed
 90 |       properties: "Raw Properties",
 91 |       identity: "Neo4j Identity",
 92 |       labels: "Neo4j Labels",
 93 |       elementId: "Neo4j Element ID",
 94 |     };
 95 | 
 96 |     let details = `Knowledge Item Details\n\n`;
 97 | 
 98 |     // Build details as key-value pairs for relevant fields
 99 |     (Object.keys(fieldLabels) as Array<keyof SingleKnowledgeResponse>).forEach(
100 |       (key) => {
101 |         if (
102 |           knowledgeData[key] !== undefined &&
103 |           ["properties", "identity", "labels", "elementId"].indexOf(
104 |             key as string,
105 |           ) === -1
106 |         ) {
107 |           // Exclude raw Neo4j fields from default display
108 |           let value = knowledgeData[key];
109 | 
110 |           if (Array.isArray(value)) {
111 |             value = value.length > 0 ? value.join(", ") : "None";
112 |           } else if (
113 |             typeof value === "string" &&
114 |             (key === "createdAt" || key === "updatedAt")
115 |           ) {
116 |             try {
117 |               value = new Date(value).toLocaleString();
118 |             } catch (e) {
119 |               /* Keep original if parsing fails */
120 |             }
121 |           }
122 | 
123 |           if (
124 |             key === "text" &&
125 |             typeof value === "string" &&
126 |             value.length > 100
127 |           ) {
128 |             value = value.substring(0, 100) + "... (truncated)";
129 |           }
130 | 
131 |           details += `${fieldLabels[key]}: ${value}\n`;
132 |         }
133 |       },
134 |     );
135 | 
136 |     return `${summary}\n${details}`;
137 |   }
138 | }
139 | 
140 | /**
141 |  * Formatter for bulk knowledge addition responses
142 |  */
143 | export class BulkKnowledgeFormatter
144 |   implements ResponseFormatter<BulkKnowledgeResponse>
145 | {
146 |   format(data: BulkKnowledgeResponse): string {
147 |     const { success, message, created, errors } = data;
148 | 
149 |     const summary =
150 |       `${success && errors.length === 0 ? "Knowledge Items Added Successfully" : "Knowledge Addition Completed"}\n\n` +
151 |       `Status: ${success && errors.length === 0 ? "✅ Success" : errors.length > 0 ? "⚠️ Partial Success / Errors" : "✅ Success (No items or no errors)"}\n` +
152 |       `Summary: ${message}\n` +
153 |       `Added: ${created.length} item(s)\n` +
154 |       `Errors: ${errors.length} error(s)\n`;
155 | 
156 |     let createdSection = "";
157 |     if (created.length > 0) {
158 |       createdSection = `\n--- Added Knowledge Items (${created.length}) ---\n\n`;
159 |       createdSection += created
160 |         .map((item, index) => {
161 |           const itemData = item.properties || item;
162 |           return (
163 |             `${index + 1}. ID: ${itemData.id || "N/A"}\n` +
164 |             `   Project ID: ${itemData.projectId || "N/A"}\n` +
165 |             `   Domain: ${itemData.domain || "N/A"}\n` +
166 |             `   Tags: ${itemData.tags ? itemData.tags.join(", ") : "None"}\n` +
167 |             `   Created: ${itemData.createdAt ? new Date(itemData.createdAt).toLocaleString() : "N/A"}`
168 |           );
169 |         })
170 |         .join("\n\n");
171 |     }
172 | 
173 |     let errorsSection = "";
174 |     if (errors.length > 0) {
175 |       errorsSection = `\n--- Errors Encountered (${errors.length}) ---\n\n`;
176 |       errorsSection += errors
177 |         .map((errorDetail, index) => {
178 |           const itemInput = errorDetail.knowledge;
179 |           return (
180 |             `${index + 1}. Error for item (Index: ${errorDetail.index})\n` +
181 |             `   Input Project ID: ${itemInput?.projectId || "N/A"}\n` +
182 |             `   Input Domain: ${itemInput?.domain || "N/A"}\n` +
183 |             `   Error Code: ${errorDetail.error.code}\n` +
184 |             `   Message: ${errorDetail.error.message}` +
185 |             (errorDetail.error.details
186 |               ? `\n   Details: ${JSON.stringify(errorDetail.error.details)}`
187 |               : "")
188 |           );
189 |         })
190 |         .join("\n\n");
191 |     }
192 | 
193 |     return `${summary}${createdSection}${errorsSection}`.trim();
194 |   }
195 | }
196 | 
197 | /**
198 |  * Create a formatted, human-readable response for the atlas_knowledge_add tool
199 |  *
200 |  * @param data The raw knowledge addition response data (can be SingleKnowledgeResponse or BulkKnowledgeResponse)
201 |  * @param isError Whether this response represents an error condition (primarily for single responses if not inherent in data)
202 |  * @returns Formatted MCP tool response with appropriate structure
203 |  */
204 | export function formatKnowledgeAddResponse(data: any, isError = false): any {
205 |   const isBulkResponse =
206 |     data.hasOwnProperty("success") &&
207 |     data.hasOwnProperty("created") &&
208 |     data.hasOwnProperty("errors");
209 | 
210 |   let formattedText: string;
211 |   let finalIsError: boolean;
212 | 
213 |   if (isBulkResponse) {
214 |     const formatter = new BulkKnowledgeFormatter();
215 |     formattedText = formatter.format(data as BulkKnowledgeResponse);
216 |     finalIsError = !data.success || data.errors.length > 0;
217 |   } else {
218 |     const formatter = new SingleKnowledgeFormatter();
219 |     formattedText = formatter.format(data as SingleKnowledgeResponse);
220 |     finalIsError = isError; // For single responses, rely on the passed isError or enhance if data has success field
221 |   }
222 |   return createToolResponse(formattedText, finalIsError);
223 | }
224 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_task_create/responseFormat.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { TaskResponse } from "../../../types/mcp.js";
  2 | import { createToolResponse } from "../../../types/mcp.js"; // Import the new response creator
  3 | 
  4 | /**
  5 |  * Defines a generic interface for formatting data into a string.
  6 |  */
  7 | interface ResponseFormatter<T> {
  8 |   format(data: T): string;
  9 | }
 10 | 
 11 | /**
 12 |  * Extends the TaskResponse to include Neo4j properties structure
 13 |  */
 14 | interface SingleTaskResponse extends TaskResponse {
 15 |   properties?: any;
 16 |   identity?: number;
 17 |   labels?: string[];
 18 |   elementId?: string;
 19 | }
 20 | 
 21 | /**
 22 |  * Interface for bulk task creation response
 23 |  */
 24 | interface BulkTaskResponse {
 25 |   success: boolean;
 26 |   message: string;
 27 |   created: (TaskResponse & {
 28 |     properties?: any;
 29 |     identity?: number;
 30 |     labels?: string[];
 31 |     elementId?: string;
 32 |   })[];
 33 |   errors: {
 34 |     index: number;
 35 |     task: any; // Original input for the failed task
 36 |     error: {
 37 |       code: string;
 38 |       message: string;
 39 |       details?: any;
 40 |     };
 41 |   }[];
 42 | }
 43 | 
 44 | /**
 45 |  * Formatter for single task creation responses
 46 |  */
 47 | export class SingleTaskFormatter
 48 |   implements ResponseFormatter<SingleTaskResponse>
 49 | {
 50 |   format(data: SingleTaskResponse): string {
 51 |     // Extract task properties from Neo4j structure or direct data
 52 |     const taskData = data.properties || data;
 53 |     const {
 54 |       title,
 55 |       id,
 56 |       projectId,
 57 |       status,
 58 |       priority,
 59 |       taskType,
 60 |       createdAt,
 61 |       description,
 62 |       assignedTo,
 63 |       urls,
 64 |       tags,
 65 |       completionRequirements,
 66 |       dependencies,
 67 |       outputFormat,
 68 |       updatedAt,
 69 |     } = taskData;
 70 | 
 71 |     // Create a summary section
 72 |     const summary =
 73 |       `Task Created Successfully\n\n` +
 74 |       `Task: ${title || "Unnamed Task"}\n` +
 75 |       `ID: ${id || "Unknown ID"}\n` +
 76 |       `Project ID: ${projectId || "Unknown Project"}\n` +
 77 |       `Status: ${status || "Unknown Status"}\n` +
 78 |       `Priority: ${priority || "Unknown Priority"}\n` +
 79 |       `Type: ${taskType || "Unknown Type"}\n` +
 80 |       `Created: ${createdAt ? new Date(createdAt).toLocaleString() : "Unknown Date"}\n`;
 81 | 
 82 |     // Create a comprehensive details section
 83 |     let details = `Task Details:\n`;
 84 |     const fieldLabels: Record<keyof SingleTaskResponse, string> = {
 85 |       id: "ID",
 86 |       projectId: "Project ID",
 87 |       title: "Title",
 88 |       description: "Description",
 89 |       priority: "Priority",
 90 |       status: "Status",
 91 |       assignedTo: "Assigned To",
 92 |       urls: "URLs",
 93 |       tags: "Tags",
 94 |       completionRequirements: "Completion Requirements",
 95 |       dependencies: "Dependencies",
 96 |       outputFormat: "Output Format",
 97 |       taskType: "Task Type",
 98 |       createdAt: "Created At",
 99 |       updatedAt: "Updated At",
100 |       properties: "Raw Properties",
101 |       identity: "Neo4j Identity",
102 |       labels: "Neo4j Labels",
103 |       elementId: "Neo4j Element ID",
104 |     };
105 |     const relevantKeys: (keyof SingleTaskResponse)[] = [
106 |       "id",
107 |       "projectId",
108 |       "title",
109 |       "description",
110 |       "priority",
111 |       "status",
112 |       "assignedTo",
113 |       "urls",
114 |       "tags",
115 |       "completionRequirements",
116 |       "dependencies",
117 |       "outputFormat",
118 |       "taskType",
119 |       "createdAt",
120 |       "updatedAt",
121 |     ];
122 | 
123 |     relevantKeys.forEach((key) => {
124 |       if (taskData[key] !== undefined && taskData[key] !== null) {
125 |         let value = taskData[key];
126 |         if (Array.isArray(value)) {
127 |           value =
128 |             value.length > 0
129 |               ? value
130 |                   .map((item) =>
131 |                     typeof item === "object" ? JSON.stringify(item) : item,
132 |                   )
133 |                   .join(", ")
134 |               : "None";
135 |         } else if (
136 |           typeof value === "string" &&
137 |           (key === "createdAt" || key === "updatedAt")
138 |         ) {
139 |           try {
140 |             value = new Date(value).toLocaleString();
141 |           } catch (e) {
142 |             /* Keep original */
143 |           }
144 |         }
145 |         details += `  ${fieldLabels[key] || key}: ${value}\n`;
146 |       }
147 |     });
148 | 
149 |     return `${summary}\n${details}`;
150 |   }
151 | }
152 | 
153 | /**
154 |  * Formatter for bulk task creation responses
155 |  */
156 | export class BulkTaskFormatter implements ResponseFormatter<BulkTaskResponse> {
157 |   format(data: BulkTaskResponse): string {
158 |     const { success, message, created, errors } = data;
159 | 
160 |     const summary =
161 |       `${success && errors.length === 0 ? "Tasks Created Successfully" : "Task Creation Completed"}\n\n` +
162 |       `Status: ${success && errors.length === 0 ? "✅ Success" : errors.length > 0 ? "⚠️ Partial Success / Errors" : "✅ Success (No items or no errors)"}\n` +
163 |       `Summary: ${message}\n` +
164 |       `Created: ${created.length} task(s)\n` +
165 |       `Errors: ${errors.length} error(s)\n`;
166 | 
167 |     let createdSection = "";
168 |     if (created.length > 0) {
169 |       createdSection = `\n--- Created Tasks (${created.length}) ---\n\n`;
170 |       createdSection += created
171 |         .map((task, index) => {
172 |           const taskData = task.properties || task;
173 |           return (
174 |             `${index + 1}. ${taskData.title || "Unnamed Task"} (ID: ${taskData.id || "N/A"})\n` +
175 |             `   Project ID: ${taskData.projectId || "N/A"}\n` +
176 |             `   Priority: ${taskData.priority || "N/A"}\n` +
177 |             `   Status: ${taskData.status || "N/A"}\n` +
178 |             `   Created: ${taskData.createdAt ? new Date(taskData.createdAt).toLocaleString() : "N/A"}`
179 |           );
180 |         })
181 |         .join("\n\n");
182 |     }
183 | 
184 |     let errorsSection = "";
185 |     if (errors.length > 0) {
186 |       errorsSection = `\n--- Errors Encountered (${errors.length}) ---\n\n`;
187 |       errorsSection += errors
188 |         .map((errorItem, index) => {
189 |           const taskTitle =
190 |             errorItem.task?.title || `Input task at index ${errorItem.index}`;
191 |           return (
192 |             `${index + 1}. Error for task: "${taskTitle}" (Project ID: ${errorItem.task?.projectId || "N/A"})\n` +
193 |             `   Error Code: ${errorItem.error.code}\n` +
194 |             `   Message: ${errorItem.error.message}` +
195 |             (errorItem.error.details
196 |               ? `\n   Details: ${JSON.stringify(errorItem.error.details)}`
197 |               : "")
198 |           );
199 |         })
200 |         .join("\n\n");
201 |     }
202 | 
203 |     return `${summary}${createdSection}${errorsSection}`.trim();
204 |   }
205 | }
206 | 
207 | /**
208 |  * Create a formatted, human-readable response for the atlas_task_create tool
209 |  *
210 |  * @param data The raw task creation response data (SingleTaskResponse or BulkTaskResponse)
211 |  * @param isError Whether this response represents an error condition (primarily for single responses)
212 |  * @returns Formatted MCP tool response with appropriate structure
213 |  */
214 | export function formatTaskCreateResponse(data: any, isError = false): any {
215 |   const isBulkResponse =
216 |     data.hasOwnProperty("success") &&
217 |     data.hasOwnProperty("created") &&
218 |     data.hasOwnProperty("errors");
219 | 
220 |   let formattedText: string;
221 |   let finalIsError: boolean;
222 | 
223 |   if (isBulkResponse) {
224 |     const formatter = new BulkTaskFormatter();
225 |     const bulkData = data as BulkTaskResponse;
226 |     formattedText = formatter.format(bulkData);
227 |     finalIsError = !bulkData.success || bulkData.errors.length > 0;
228 |   } else {
229 |     const formatter = new SingleTaskFormatter();
230 |     // For single response, 'data' is the created task object.
231 |     // 'isError' must be determined by the caller if an error occurred before this point.
232 |     formattedText = formatter.format(data as SingleTaskResponse);
233 |     finalIsError = isError;
234 |   }
235 |   return createToolResponse(formattedText, finalIsError);
236 | }
237 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_create/responseFormat.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ProjectResponse } from "../../../types/mcp.js";
  2 | import { createToolResponse } from "../../../types/mcp.js"; // Import the new response creator
  3 | 
  4 | /**
  5 |  * Defines a generic interface for formatting data into a string.
  6 |  */
  7 | interface ResponseFormatter<T> {
  8 |   format(data: T): string;
  9 | }
 10 | 
 11 | /**
 12 |  * Extends the ProjectResponse to include Neo4j properties structure
 13 |  */
 14 | interface SingleProjectResponse extends ProjectResponse {
 15 |   properties?: any;
 16 |   identity?: number;
 17 |   labels?: string[];
 18 |   elementId?: string;
 19 | }
 20 | 
 21 | /**
 22 |  * Interface for bulk project creation response
 23 |  */
 24 | interface BulkProjectResponse {
 25 |   success: boolean;
 26 |   message: string;
 27 |   created: (ProjectResponse & {
 28 |     properties?: any;
 29 |     identity?: number;
 30 |     labels?: string[];
 31 |     elementId?: string;
 32 |   })[];
 33 |   errors: {
 34 |     index: number;
 35 |     project: any; // Original input for the failed project
 36 |     error: {
 37 |       code: string;
 38 |       message: string;
 39 |       details?: any;
 40 |     };
 41 |   }[];
 42 | }
 43 | 
 44 | /**
 45 |  * Formatter for single project creation responses
 46 |  */
 47 | export class SingleProjectFormatter
 48 |   implements ResponseFormatter<SingleProjectResponse>
 49 | {
 50 |   format(data: SingleProjectResponse): string {
 51 |     // Extract project properties from Neo4j structure or direct data
 52 |     const projectData = data.properties || data;
 53 |     const {
 54 |       name,
 55 |       id,
 56 |       status,
 57 |       taskType,
 58 |       createdAt,
 59 |       description,
 60 |       urls,
 61 |       completionRequirements,
 62 |       outputFormat,
 63 |       updatedAt,
 64 |     } = projectData;
 65 | 
 66 |     // Create a summary section
 67 |     const summary =
 68 |       `Project Created Successfully\n\n` +
 69 |       `Project: ${name || "Unnamed Project"}\n` +
 70 |       `ID: ${id || "Unknown ID"}\n` +
 71 |       `Status: ${status || "Unknown Status"}\n` +
 72 |       `Type: ${taskType || "Unknown Type"}\n` +
 73 |       `Created: ${createdAt ? new Date(createdAt).toLocaleString() : "Unknown Date"}\n`;
 74 | 
 75 |     // Create a comprehensive details section
 76 |     const fieldLabels: Record<keyof SingleProjectResponse, string> = {
 77 |       id: "ID",
 78 |       name: "Name",
 79 |       description: "Description",
 80 |       status: "Status",
 81 |       urls: "URLs",
 82 |       completionRequirements: "Completion Requirements",
 83 |       outputFormat: "Output Format",
 84 |       taskType: "Task Type",
 85 |       createdAt: "Created At",
 86 |       updatedAt: "Updated At",
 87 |       // Neo4j specific fields
 88 |       properties: "Raw Properties",
 89 |       identity: "Neo4j Identity",
 90 |       labels: "Neo4j Labels",
 91 |       elementId: "Neo4j Element ID",
 92 |       // Fields from ProjectResponse that might not be in projectData directly if it's just properties
 93 |       dependencies: "Dependencies", // Assuming ProjectResponse might have this
 94 |     };
 95 | 
 96 |     let details = `Project Details:\n`;
 97 | 
 98 |     // Build details as key-value pairs for relevant fields
 99 |     const relevantKeys: (keyof SingleProjectResponse)[] = [
100 |       "id",
101 |       "name",
102 |       "description",
103 |       "status",
104 |       "taskType",
105 |       "completionRequirements",
106 |       "outputFormat",
107 |       "urls",
108 |       "createdAt",
109 |       "updatedAt",
110 |     ];
111 | 
112 |     relevantKeys.forEach((key) => {
113 |       if (projectData[key] !== undefined && projectData[key] !== null) {
114 |         let value = projectData[key];
115 | 
116 |         if (Array.isArray(value)) {
117 |           value =
118 |             value.length > 0
119 |               ? value
120 |                   .map((item) =>
121 |                     typeof item === "object" ? JSON.stringify(item) : item,
122 |                   )
123 |                   .join(", ")
124 |               : "None";
125 |         } else if (
126 |           typeof value === "string" &&
127 |           (key === "createdAt" || key === "updatedAt")
128 |         ) {
129 |           try {
130 |             value = new Date(value).toLocaleString();
131 |           } catch (e) {
132 |             /* Keep original if parsing fails */
133 |           }
134 |         }
135 | 
136 |         details += `  ${fieldLabels[key] || key}: ${value}\n`;
137 |       }
138 |     });
139 | 
140 |     return `${summary}\n${details}`;
141 |   }
142 | }
143 | 
144 | /**
145 |  * Formatter for bulk project creation responses
146 |  */
147 | export class BulkProjectFormatter
148 |   implements ResponseFormatter<BulkProjectResponse>
149 | {
150 |   format(data: BulkProjectResponse): string {
151 |     const { success, message, created, errors } = data;
152 | 
153 |     const summary =
154 |       `${success && errors.length === 0 ? "Projects Created Successfully" : "Project Creation Completed"}\n\n` +
155 |       `Status: ${success && errors.length === 0 ? "✅ Success" : errors.length > 0 ? "⚠️ Partial Success / Errors" : "✅ Success (No items or no errors)"}\n` +
156 |       `Summary: ${message}\n` +
157 |       `Created: ${created.length} project(s)\n` +
158 |       `Errors: ${errors.length} error(s)\n`;
159 | 
160 |     let createdSection = "";
161 |     if (created.length > 0) {
162 |       createdSection = `\n--- Created Projects (${created.length}) ---\n\n`;
163 |       createdSection += created
164 |         .map((project, index) => {
165 |           const projectData = project.properties || project;
166 |           return (
167 |             `${index + 1}. ${projectData.name || "Unnamed Project"} (ID: ${projectData.id || "N/A"})\n` +
168 |             `   Type: ${projectData.taskType || "N/A"}\n` +
169 |             `   Status: ${projectData.status || "N/A"}\n` +
170 |             `   Created: ${projectData.createdAt ? new Date(projectData.createdAt).toLocaleString() : "N/A"}`
171 |           );
172 |         })
173 |         .join("\n\n");
174 |     }
175 | 
176 |     let errorsSection = "";
177 |     if (errors.length > 0) {
178 |       errorsSection = `\n--- Errors Encountered (${errors.length}) ---\n\n`;
179 |       errorsSection += errors
180 |         .map((errorItem, index) => {
181 |           const projectName =
182 |             errorItem.project?.name ||
183 |             `Input project at index ${errorItem.index}`;
184 |           return (
185 |             `${index + 1}. Error for project: "${projectName}"\n` +
186 |             `   Error Code: ${errorItem.error.code}\n` +
187 |             `   Message: ${errorItem.error.message}` +
188 |             (errorItem.error.details
189 |               ? `\n   Details: ${JSON.stringify(errorItem.error.details)}`
190 |               : "")
191 |           );
192 |         })
193 |         .join("\n\n");
194 |     }
195 | 
196 |     return `${summary}${createdSection}${errorsSection}`.trim();
197 |   }
198 | }
199 | 
200 | /**
201 |  * Create a formatted, human-readable response for the atlas_project_create tool
202 |  *
203 |  * @param data The raw project creation response data (SingleProjectResponse or BulkProjectResponse)
204 |  * @param isError Whether this response represents an error condition (primarily for single responses)
205 |  * @returns Formatted MCP tool response with appropriate structure
206 |  */
207 | export function formatProjectCreateResponse(data: any, isError = false): any {
208 |   const isBulkResponse =
209 |     data.hasOwnProperty("success") &&
210 |     data.hasOwnProperty("created") &&
211 |     data.hasOwnProperty("errors");
212 | 
213 |   let formattedText: string;
214 |   let finalIsError: boolean;
215 | 
216 |   if (isBulkResponse) {
217 |     const formatter = new BulkProjectFormatter();
218 |     const bulkData = data as BulkProjectResponse;
219 |     formattedText = formatter.format(bulkData);
220 |     finalIsError = !bulkData.success || bulkData.errors.length > 0;
221 |   } else {
222 |     const formatter = new SingleProjectFormatter();
223 |     // For single response, the 'data' is the project object itself.
224 |     // 'isError' must be determined by the caller if an error occurred before this point.
225 |     // If 'data' represents a successfully created project, isError should be false.
226 |     formattedText = formatter.format(data as SingleProjectResponse);
227 |     finalIsError = isError;
228 |   }
229 |   return createToolResponse(formattedText, finalIsError);
230 | }
231 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_task_create/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | import {
  3 |   McpToolResponse,
  4 |   PriorityLevel,
  5 |   ResponseFormat,
  6 |   TaskStatus,
  7 |   TaskType,
  8 |   createPriorityLevelEnum,
  9 |   createResponseFormatEnum,
 10 |   createTaskStatusEnum,
 11 |   createTaskTypeEnum,
 12 | } from "../../../types/mcp.js";
 13 | 
 14 | export const TaskSchema = z.object({
 15 |   id: z.string().optional().describe("Optional client-generated task ID"),
 16 |   projectId: z
 17 |     .string()
 18 |     .describe("ID of the parent project this task belongs to"),
 19 |   title: z
 20 |     .string()
 21 |     .min(5)
 22 |     .max(150)
 23 |     .describe(
 24 |       "Concise task title clearly describing the objective (5-150 characters)",
 25 |     ),
 26 |   description: z
 27 |     .string()
 28 |     .describe("Detailed explanation of the task requirements and context"),
 29 |   priority: createPriorityLevelEnum()
 30 |     .default(PriorityLevel.MEDIUM)
 31 |     .describe("Importance level"),
 32 |   status: createTaskStatusEnum()
 33 |     .default(TaskStatus.TODO)
 34 |     .describe("Current task state"),
 35 |   assignedTo: z
 36 |     .string()
 37 |     .optional()
 38 |     .describe("ID of entity responsible for task completion"),
 39 |   urls: z
 40 |     .array(
 41 |       z.object({
 42 |         title: z.string(),
 43 |         url: z.string(),
 44 |       }),
 45 |     )
 46 |     .optional()
 47 |     .describe("Relevant URLs with descriptive titles for reference materials"),
 48 |   tags: z
 49 |     .array(z.string())
 50 |     .optional()
 51 |     .describe("Categorical labels for organization and filtering"),
 52 |   completionRequirements: z
 53 |     .string()
 54 |     .describe("Specific, measurable criteria that indicate task completion"),
 55 |   dependencies: z
 56 |     .array(z.string())
 57 |     .optional()
 58 |     .describe(
 59 |       "Array of existing task IDs that must be completed before this task can begin",
 60 |     ),
 61 |   outputFormat: z
 62 |     .string()
 63 |     .describe("Required format specification for task deliverables"),
 64 |   taskType: createTaskTypeEnum()
 65 |     .or(z.string())
 66 |     .describe("Classification of task purpose"),
 67 | });
 68 | 
 69 | const SingleTaskSchema = z
 70 |   .object({
 71 |     mode: z.literal("single"),
 72 |     id: z.string().optional(),
 73 |     projectId: z.string(),
 74 |     title: z.string().min(5).max(150),
 75 |     description: z.string(),
 76 |     priority: createPriorityLevelEnum()
 77 |       .optional()
 78 |       .default(PriorityLevel.MEDIUM),
 79 |     status: createTaskStatusEnum().optional().default(TaskStatus.TODO),
 80 |     assignedTo: z.string().optional(),
 81 |     urls: z
 82 |       .array(
 83 |         z.object({
 84 |           title: z.string(),
 85 |           url: z.string(),
 86 |         }),
 87 |       )
 88 |       .optional(),
 89 |     tags: z.array(z.string()).optional(),
 90 |     completionRequirements: z.string(),
 91 |     dependencies: z.array(z.string()).optional(),
 92 |     outputFormat: z.string(),
 93 |     taskType: createTaskTypeEnum().or(z.string()),
 94 |     responseFormat: createResponseFormatEnum()
 95 |       .optional()
 96 |       .default(ResponseFormat.FORMATTED)
 97 |       .describe(
 98 |         "Desired response format: 'formatted' (default string) or 'json' (raw object)",
 99 |       ),
100 |   })
101 |   .describe("Creates a single task with comprehensive details and metadata");
102 | 
103 | const BulkTaskSchema = z
104 |   .object({
105 |     mode: z.literal("bulk"),
106 |     tasks: z
107 |       .array(TaskSchema)
108 |       .min(1)
109 |       .max(100)
110 |       .describe(
111 |         "Collection of task definitions to create in a single operation. Each object must include all fields required for single task creation (projectId, title, description, completionRequirements, outputFormat, taskType).",
112 |       ),
113 |     responseFormat: createResponseFormatEnum()
114 |       .optional()
115 |       .default(ResponseFormat.FORMATTED)
116 |       .describe(
117 |         "Desired response format: 'formatted' (default string) or 'json' (raw object)",
118 |       ),
119 |   })
120 |   .describe("Create multiple related tasks in a single efficient transaction");
121 | 
122 | // Schema shapes for tool registration
123 | export const AtlasTaskCreateSchemaShape = {
124 |   mode: z
125 |     .enum(["single", "bulk"])
126 |     .describe(
127 |       "Operation mode - 'single' for creating one detailed task with full specifications, 'bulk' for efficiently creating multiple related tasks in a single transaction",
128 |     ),
129 |   id: z
130 |     .string()
131 |     .optional()
132 |     .describe(
133 |       "Optional client-generated task ID for consistent cross-referencing",
134 |     ),
135 |   projectId: z
136 |     .string()
137 |     .optional()
138 |     .describe(
139 |       "ID of the parent project this task belongs to, establishing the project-task relationship hierarchy (required for mode='single')",
140 |     ),
141 |   title: z
142 |     .string()
143 |     .min(5)
144 |     .max(150)
145 |     .optional()
146 |     .describe(
147 |       "Concise task title clearly describing the objective (5-150 characters) for display and identification (required for mode='single')",
148 |     ),
149 |   description: z
150 |     .string()
151 |     .optional()
152 |     .describe(
153 |       "Detailed explanation of the task requirements, context, approach, and implementation details (required for mode='single')",
154 |     ),
155 |   priority: createPriorityLevelEnum()
156 |     .optional()
157 |     .describe(
158 |       "Importance level for task prioritization and resource allocation (Default: medium)",
159 |     ),
160 |   status: createTaskStatusEnum()
161 |     .optional()
162 |     .describe(
163 |       "Current task workflow state for tracking task lifecycle and progress (Default: todo)",
164 |     ),
165 |   assignedTo: z
166 |     .string()
167 |     .optional()
168 |     .describe(
169 |       "ID of entity responsible for task completion and accountability tracking",
170 |     ),
171 |   urls: z
172 |     .array(
173 |       z.object({
174 |         title: z.string(),
175 |         url: z.string(),
176 |       }),
177 |     )
178 |     .optional()
179 |     .describe(
180 |       "Array of relevant URLs with descriptive titles for reference materials",
181 |     ),
182 |   tags: z
183 |     .array(z.string())
184 |     .optional()
185 |     .describe(
186 |       "Array of categorical labels for task organization, filtering, and thematic grouping",
187 |     ),
188 |   completionRequirements: z
189 |     .string()
190 |     .optional()
191 |     .describe(
192 |       "Specific, measurable criteria that define when the task is considered complete and ready for verification (required for mode='single')",
193 |     ),
194 |   dependencies: z
195 |     .array(z.string())
196 |     .optional()
197 |     .describe(
198 |       "Array of existing task IDs that must be completed before this task can begin, creating sequential workflow paths and prerequisites",
199 |     ),
200 |   outputFormat: z
201 |     .string()
202 |     .optional()
203 |     .describe(
204 |       "Required format and structure specification for the task's deliverables, artifacts, and documentation (required for mode='single')",
205 |     ),
206 |   taskType: createTaskTypeEnum()
207 |     .or(z.string())
208 |     .optional()
209 |     .describe(
210 |       "Classification of task purpose for workflow organization, filtering, and reporting (required for mode='single')",
211 |     ),
212 |   tasks: z
213 |     .array(TaskSchema)
214 |     .min(1)
215 |     .max(100)
216 |     .optional()
217 |     .describe(
218 |       "Array of complete task definition objects to create in a single transaction (supports 1-100 tasks, required for mode='bulk'). Each object must include all fields required for single task creation (projectId, title, description, completionRequirements, outputFormat, taskType).",
219 |     ),
220 |   responseFormat: createResponseFormatEnum()
221 |     .optional()
222 |     .describe(
223 |       "Desired response format: 'formatted' (default string) or 'json' (raw object)",
224 |     ),
225 | } as const;
226 | 
227 | // Schema for validation
228 | export const AtlasTaskCreateSchema = z.discriminatedUnion("mode", [
229 |   SingleTaskSchema,
230 |   BulkTaskSchema,
231 | ]);
232 | 
233 | export type AtlasTaskCreateInput = z.infer<typeof AtlasTaskCreateSchema>;
234 | export type TaskInput = z.infer<typeof TaskSchema>;
235 | export type AtlasTaskCreateResponse = McpToolResponse;
236 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_list/responseFormat.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createToolResponse } from "../../../types/mcp.js"; // Import the new response creator
  2 | import { Project, ProjectListResponse } from "./types.js";
  3 | 
  4 | /**
  5 |  * Defines a generic interface for formatting data into a string.
  6 |  */
  7 | interface ResponseFormatter<T> {
  8 |   format(data: T): string;
  9 | }
 10 | 
 11 | /**
 12 |  * Formatter for structured project query responses
 13 |  */
 14 | export class ProjectListFormatter
 15 |   implements ResponseFormatter<ProjectListResponse>
 16 | {
 17 |   /**
 18 |    * Get an emoji indicator for the task status
 19 |    */
 20 |   private getStatusEmoji(status: string): string {
 21 |     switch (status) {
 22 |       case "backlog":
 23 |         return "📋";
 24 |       case "todo":
 25 |         return "📝";
 26 |       case "in_progress":
 27 |         return "🔄";
 28 |       case "completed":
 29 |         return "✅";
 30 |       default:
 31 |         return "❓";
 32 |     }
 33 |   }
 34 | 
 35 |   /**
 36 |    * Get a visual indicator for the priority level
 37 |    */
 38 |   private getPriorityIndicator(priority: string): string {
 39 |     switch (priority) {
 40 |       case "critical":
 41 |         return "[!!!]";
 42 |       case "high":
 43 |         return "[!!]";
 44 |       case "medium":
 45 |         return "[!]";
 46 |       case "low":
 47 |         return "[-]";
 48 |       default:
 49 |         return "[?]";
 50 |     }
 51 |   }
 52 |   format(data: ProjectListResponse): string {
 53 |     const { projects, total, page, limit, totalPages } = data;
 54 | 
 55 |     // Generate result summary section
 56 |     const summary =
 57 |       `Project Portfolio\n\n` +
 58 |       `Total Entities: ${total}\n` +
 59 |       `Page: ${page} of ${totalPages}\n` +
 60 |       `Displaying: ${Math.min(limit, projects.length)} project(s) per page\n`;
 61 | 
 62 |     if (projects.length === 0) {
 63 |       return `${summary}\n\nNo project entities matched the specified criteria`;
 64 |     }
 65 | 
 66 |     // Format each project
 67 |     const projectsSections = projects
 68 |       .map((project, index) => {
 69 |         // Access properties directly from the project object
 70 |         const { name, id, status, taskType, createdAt } = project;
 71 | 
 72 |         let projectSection =
 73 |           `${index + 1}. ${name || "Unnamed Project"}\n\n` +
 74 |           `ID: ${id || "Unknown ID"}\n` +
 75 |           `Status: ${status || "Unknown Status"}\n` +
 76 |           `Type: ${taskType || "Unknown Type"}\n` +
 77 |           `Created: ${createdAt ? new Date(createdAt).toLocaleString() : "Unknown Date"}\n`;
 78 | 
 79 |         // Add project details in plain text format
 80 |         projectSection += `\nProject Details\n\n`;
 81 | 
 82 |         // Add each property with proper formatting, accessing directly from 'project'
 83 |         if (project.id) projectSection += `ID: ${project.id}\n`;
 84 |         if (project.name) projectSection += `Name: ${project.name}\n`;
 85 |         if (project.description)
 86 |           projectSection += `Description: ${project.description}\n`;
 87 |         if (project.status) projectSection += `Status: ${project.status}\n`;
 88 | 
 89 |         // Format URLs array
 90 |         if (project.urls) {
 91 |           const urlsValue =
 92 |             Array.isArray(project.urls) && project.urls.length > 0
 93 |               ? project.urls
 94 |                   .map((u) => `${u.title}: ${u.url}`)
 95 |                   .join("\n           ") // Improved formatting for URLs
 96 |               : "None";
 97 |           projectSection += `URLs: ${urlsValue}\n`;
 98 |         }
 99 | 
100 |         if (project.completionRequirements)
101 |           projectSection += `Completion Requirements: ${project.completionRequirements}\n`;
102 |         if (project.outputFormat)
103 |           projectSection += `Output Format: ${project.outputFormat}\n`;
104 |         if (project.taskType)
105 |           projectSection += `Task Type: ${project.taskType}\n`;
106 | 
107 |         // Format dates
108 |         if (project.createdAt) {
109 |           const createdDate =
110 |             typeof project.createdAt === "string" &&
111 |             /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(project.createdAt)
112 |               ? new Date(project.createdAt).toLocaleString()
113 |               : project.createdAt;
114 |           projectSection += `Created At: ${createdDate}\n`;
115 |         }
116 | 
117 |         if (project.updatedAt) {
118 |           const updatedDate =
119 |             typeof project.updatedAt === "string" &&
120 |             /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(project.updatedAt)
121 |               ? new Date(project.updatedAt).toLocaleString()
122 |               : project.updatedAt;
123 |           projectSection += `Updated At: ${updatedDate}\n`;
124 |         }
125 | 
126 |         // Add tasks if included
127 |         if (project.tasks && project.tasks.length > 0) {
128 |           projectSection += `\nTasks (${project.tasks.length}):\n`;
129 | 
130 |           projectSection += project.tasks
131 |             .map((task, taskIndex) => {
132 |               const taskTitle = task.title || "Unnamed Task";
133 |               const taskId = task.id || "Unknown ID";
134 |               const taskStatus = task.status || "Unknown Status";
135 |               const taskPriority = task.priority || "Unknown Priority";
136 |               const taskCreatedAt = task.createdAt
137 |                 ? new Date(task.createdAt).toLocaleString()
138 |                 : "Unknown Date";
139 | 
140 |               const statusEmoji = this.getStatusEmoji(taskStatus);
141 |               const priorityIndicator = this.getPriorityIndicator(taskPriority);
142 | 
143 |               return (
144 |                 `  ${taskIndex + 1}. ${statusEmoji} ${priorityIndicator} ${taskTitle}\n` +
145 |                 `     ID: ${taskId}\n` +
146 |                 `     Status: ${taskStatus}\n` +
147 |                 `     Priority: ${taskPriority}\n` +
148 |                 `     Created: ${taskCreatedAt}`
149 |               );
150 |             })
151 |             .join("\n\n");
152 |           projectSection += "\n";
153 |         }
154 | 
155 |         // Add knowledge if included
156 |         if (project.knowledge && project.knowledge.length > 0) {
157 |           projectSection += `\nKnowledge Items (${project.knowledge.length}):\n`;
158 | 
159 |           projectSection += project.knowledge
160 |             .map((item, itemIndex) => {
161 |               return (
162 |                 `  ${itemIndex + 1}. ${item.domain || "Uncategorized"} (ID: ${item.id || "N/A"})\n` +
163 |                 `     Tags: ${item.tags && item.tags.length > 0 ? item.tags.join(", ") : "None"}\n` +
164 |                 `     Created: ${item.createdAt ? new Date(item.createdAt).toLocaleString() : "N/A"}\n` +
165 |                 `     Content Preview: ${item.text || "No content available"}`
166 |               ); // Preview already truncated if needed
167 |             })
168 |             .join("\n\n");
169 |           projectSection += "\n";
170 |         }
171 | 
172 |         return projectSection;
173 |       })
174 |       .join("\n\n----------\n\n");
175 | 
176 |     // Append pagination metadata for multi-page results
177 |     let paginationInfo = "";
178 |     if (totalPages > 1) {
179 |       paginationInfo =
180 |         `\n\nPagination Controls:\n` + // Added colon for clarity
181 |         `Viewing page ${page} of ${totalPages}.\n` +
182 |         `${page < totalPages ? "Use 'page' parameter to navigate to additional results." : "You are on the last page."}`;
183 |     }
184 | 
185 |     return `${summary}\n\n${projectsSections}${paginationInfo}`;
186 |   }
187 | }
188 | 
189 | /**
190 |  * Create a human-readable formatted response for the atlas_project_list tool
191 |  *
192 |  * @param data The structured project query response data
193 |  * @param isError Whether this response represents an error condition
194 |  * @returns Formatted MCP tool response with appropriate structure
195 |  */
196 | export function formatProjectListResponse(data: any, isError = false): any {
197 |   const formatter = new ProjectListFormatter();
198 |   const formattedText = formatter.format(data as ProjectListResponse); // Assuming data is ProjectListResponse
199 |   return createToolResponse(formattedText, isError);
200 | }
201 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_list/listProjects.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   KnowledgeService,
  3 |   ProjectService,
  4 |   TaskService,
  5 | } from "../../../services/neo4j/index.js";
  6 | import { BaseErrorCode, McpError } from "../../../types/errors.js";
  7 | import { logger, requestContextService } from "../../../utils/index.js"; // Import requestContextService
  8 | import {
  9 |   Project,
 10 |   ProjectListRequest,
 11 |   ProjectListResponse,
 12 |   Knowledge,
 13 |   Task,
 14 | } from "./types.js"; // Import Knowledge and Task
 15 | 
 16 | /**
 17 |  * Retrieve and filter project entities based on specified criteria
 18 |  * Provides two query modes: detailed entity retrieval or paginated collection listing
 19 |  *
 20 |  * @param request The project query parameters including filters and pagination controls
 21 |  * @returns Promise resolving to structured project entities with optional related resources
 22 |  */
 23 | export async function listProjects(
 24 |   request: ProjectListRequest,
 25 | ): Promise<ProjectListResponse> {
 26 |   const reqContext = requestContextService.createRequestContext({
 27 |     toolName: "listProjects",
 28 |     requestMode: request.mode,
 29 |   });
 30 |   try {
 31 |     const {
 32 |       mode = "all",
 33 |       id,
 34 |       page = 1,
 35 |       limit = 20,
 36 |       includeKnowledge = false,
 37 |       includeTasks = false,
 38 |       taskType,
 39 |       status,
 40 |     } = request;
 41 | 
 42 |     // Parameter validation
 43 |     if (mode === "details" && !id) {
 44 |       throw new McpError(
 45 |         BaseErrorCode.VALIDATION_ERROR,
 46 |         'Project identifier is required when using mode="details"',
 47 |       );
 48 |     }
 49 | 
 50 |     // Sanitize pagination parameters
 51 |     const validatedPage = Math.max(1, page);
 52 |     const validatedLimit = Math.min(Math.max(1, limit), 100);
 53 | 
 54 |     let projects: Project[] = [];
 55 |     let total = 0;
 56 |     let totalPages = 0;
 57 | 
 58 |     if (mode === "details") {
 59 |       // Retrieve specific project entity by identifier
 60 |       const projectResult = await ProjectService.getProjectById(id!);
 61 | 
 62 |       if (!projectResult) {
 63 |         throw new McpError(
 64 |           BaseErrorCode.NOT_FOUND,
 65 |           `Project with identifier ${id} not found`,
 66 |         );
 67 |       }
 68 | 
 69 |       // Cast to the tool's Project type
 70 |       projects = [projectResult as Project];
 71 |       total = 1;
 72 |       totalPages = 1;
 73 |     } else {
 74 |       // Get paginated list of projects with filters
 75 |       const projectsResult = await ProjectService.getProjects({
 76 |         status,
 77 |         taskType,
 78 |         page: validatedPage,
 79 |         limit: validatedLimit,
 80 |       });
 81 | 
 82 |       // Cast each project to the tool's Project type
 83 |       projects = projectsResult.data.map((p) => p as Project);
 84 |       total = projectsResult.total;
 85 |       totalPages = projectsResult.totalPages;
 86 |     }
 87 | 
 88 |     // Process knowledge resource associations if requested
 89 |     if (includeKnowledge && projects.length > 0) {
 90 |       for (const project of projects) {
 91 |         if (mode === "details") {
 92 |           // For detailed view, retrieve comprehensive knowledge resources
 93 |           const knowledgeResult = await KnowledgeService.getKnowledge({
 94 |             projectId: project.id, // Access directly
 95 |             page: 1,
 96 |             limit: 100, // Reasonable threshold for associated resources
 97 |           });
 98 | 
 99 |           // Add debug logging
100 |           logger.info("Knowledge items retrieved", {
101 |             ...reqContext,
102 |             projectId: project.id, // Access directly
103 |             count: knowledgeResult.data.length,
104 |             firstItem: knowledgeResult.data[0]
105 |               ? JSON.stringify(knowledgeResult.data[0])
106 |               : "none",
107 |           });
108 | 
109 |           // Map directly, assuming KnowledgeService returns Neo4jKnowledge objects
110 |           project.knowledge = knowledgeResult.data.map((item) => {
111 |             // More explicit mapping with debug info
112 |             logger.debug("Processing knowledge item", {
113 |               ...reqContext,
114 |               id: item.id,
115 |               domain: item.domain,
116 |               textLength: item.text ? item.text.length : 0,
117 |             });
118 | 
119 |             // Cast to the tool's Knowledge type
120 |             return item as Knowledge;
121 |           });
122 |         } else {
123 |           // For list mode, get abbreviated knowledge items
124 |           const knowledgeResult = await KnowledgeService.getKnowledge({
125 |             projectId: project.id, // Access directly
126 |             page: 1,
127 |             limit: 5, // Just a few for summary view
128 |           });
129 | 
130 |           // Map directly, assuming KnowledgeService returns Neo4jKnowledge objects
131 |           project.knowledge = knowledgeResult.data.map((item) => {
132 |             // Cast to the tool's Knowledge type, potentially truncating text
133 |             const knowledgeItem = item as Knowledge;
134 |             return {
135 |               ...knowledgeItem,
136 |               // Show a preview of the text - increased to 200 characters
137 |               text:
138 |                 item.text && item.text.length > 200
139 |                   ? item.text.substring(0, 200) + "... (truncated)"
140 |                   : item.text,
141 |             };
142 |           });
143 |         }
144 |       }
145 |     }
146 | 
147 |     // Process task entity associations if requested
148 |     if (includeTasks && projects.length > 0) {
149 |       for (const project of projects) {
150 |         if (mode === "details") {
151 |           // For detailed view, retrieve prioritized task entities
152 |           const tasksResult = await TaskService.getTasks({
153 |             projectId: project.id, // Access directly
154 |             page: 1,
155 |             limit: 100, // Reasonable threshold for associated entities
156 |             sortBy: "priority",
157 |             sortDirection: "desc",
158 |           });
159 | 
160 |           // Add debug logging
161 |           logger.info("Tasks retrieved for project", {
162 |             ...reqContext,
163 |             projectId: project.id, // Access directly
164 |             count: tasksResult.data.length,
165 |             firstItem: tasksResult.data[0]
166 |               ? JSON.stringify(tasksResult.data[0])
167 |               : "none",
168 |           });
169 | 
170 |           // Map directly, assuming TaskService returns Neo4jTask objects
171 |           project.tasks = tasksResult.data.map((item) => {
172 |             // Debug info
173 |             logger.debug("Processing task item", {
174 |               ...reqContext,
175 |               id: item.id,
176 |               title: item.title,
177 |               status: item.status,
178 |               priority: item.priority,
179 |             });
180 | 
181 |             // Cast to the tool's Task type
182 |             return item as Task;
183 |           });
184 |         } else {
185 |           // For list mode, get abbreviated task items
186 |           const tasksResult = await TaskService.getTasks({
187 |             projectId: project.id, // Access directly
188 |             page: 1,
189 |             limit: 5, // Just a few for summary view
190 |             sortBy: "priority",
191 |             sortDirection: "desc",
192 |           });
193 | 
194 |           // Map directly, assuming TaskService returns Neo4jTask objects
195 |           project.tasks = tasksResult.data.map((item) => {
196 |             // Cast to the tool's Task type
197 |             return item as Task;
198 |           });
199 |         }
200 |       }
201 |     }
202 | 
203 |     // Construct the response
204 |     const response: ProjectListResponse = {
205 |       projects,
206 |       total,
207 |       page: validatedPage,
208 |       limit: validatedLimit,
209 |       totalPages,
210 |     };
211 | 
212 |     logger.info("Project query executed successfully", {
213 |       ...reqContext,
214 |       mode,
215 |       count: projects.length,
216 |       total,
217 |       includeKnowledge,
218 |       includeTasks,
219 |     });
220 | 
221 |     return response;
222 |   } catch (error) {
223 |     logger.error("Project query execution failed", error as Error, reqContext);
224 | 
225 |     if (error instanceof McpError) {
226 |       throw error;
227 |     }
228 | 
229 |     throw new McpError(
230 |       BaseErrorCode.INTERNAL_ERROR,
231 |       `Failed to retrieve project entities: ${error instanceof Error ? error.message : String(error)}`,
232 |     );
233 |   }
234 | }
235 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_knowledge_add/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import {
  4 |   createToolExample,
  5 |   createToolMetadata,
  6 |   registerTool,
  7 | } from "../../../types/tool.js";
  8 | import { atlasAddKnowledge } from "./addKnowledge.js";
  9 | import { AtlasKnowledgeAddSchemaShape } from "./types.js";
 10 | 
 11 | export const registerAtlasKnowledgeAddTool = (server: McpServer) => {
 12 |   registerTool(
 13 |     server,
 14 |     "atlas_knowledge_add",
 15 |     "Adds a new knowledge item or multiple items to the system with domain categorization, tagging, and citation support",
 16 |     AtlasKnowledgeAddSchemaShape,
 17 |     atlasAddKnowledge,
 18 |     createToolMetadata({
 19 |       examples: [
 20 |         createToolExample(
 21 |           {
 22 |             mode: "single",
 23 |             projectId: "proj_ms_migration",
 24 |             text: "GraphQL provides significant performance benefits over REST when clients need to request multiple related resources. By allowing clients to specify exactly what data they need in a single request, GraphQL eliminates over-fetching and under-fetching problems common in REST APIs.",
 25 |             domain: "technical",
 26 |             tags: ["graphql", "api", "performance", "rest"],
 27 |             citations: [
 28 |               "https://graphql.org/learn/best-practices/",
 29 |               "https://www.apollographql.com/blog/graphql/basics/graphql-vs-rest/",
 30 |             ],
 31 |           },
 32 |           `{
 33 |             "id": "know_graphql_benefits",
 34 |             "projectId": "proj_ms_migration",
 35 |             "text": "GraphQL provides significant performance benefits over REST when clients need to request multiple related resources. By allowing clients to specify exactly what data they need in a single request, GraphQL eliminates over-fetching and under-fetching problems common in REST APIs.",
 36 |             "tags": ["graphql", "api", "performance", "rest"],
 37 |             "domain": "technical",
 38 |             "citations": ["https://graphql.org/learn/best-practices/", "https://www.apollographql.com/blog/graphql/basics/graphql-vs-rest/"],
 39 |             "createdAt": "2025-03-23T10:11:24.123Z",
 40 |             "updatedAt": "2025-03-23T10:11:24.123Z"
 41 |           }`,
 42 |           "Add technical knowledge about GraphQL benefits with citations and tags",
 43 |         ),
 44 |         createToolExample(
 45 |           {
 46 |             mode: "bulk",
 47 |             knowledge: [
 48 |               {
 49 |                 projectId: "proj_ui_redesign",
 50 |                 text: "User interviews revealed that 78% of our customers struggle with the current checkout flow, particularly the address entry form which was described as 'confusing' and 'overly complex'.",
 51 |                 domain: "business",
 52 |                 tags: ["user-research", "checkout", "ux-issues"],
 53 |               },
 54 |               {
 55 |                 projectId: "proj_ui_redesign",
 56 |                 text: "Industry research shows that automatically formatting phone numbers and credit card fields as users type reduces error rates by approximately 25%. Implementing real-time validation with clear error messages has been shown to decrease form abandonment rates by up to 40%.",
 57 |                 domain: "technical",
 58 |                 tags: ["form-design", "validation", "ux-patterns"],
 59 |                 citations: [
 60 |                   "https://baymard.com/blog/input-mask-form-fields",
 61 |                   "https://www.smashingmagazine.com/2020/03/form-validation-ux-design/",
 62 |                 ],
 63 |               },
 64 |             ],
 65 |           },
 66 |           `{
 67 |             "success": true,
 68 |             "message": "Successfully added 2 knowledge items",
 69 |             "created": [
 70 |               {
 71 |                 "id": "know_checkout_research",
 72 |                 "projectId": "proj_ui_redesign",
 73 |                 "text": "User interviews revealed that 78% of our customers struggle with the current checkout flow, particularly the address entry form which was described as 'confusing' and 'overly complex'.",
 74 |                 "tags": ["user-research", "checkout", "ux-issues"],
 75 |                 "domain": "business",
 76 |                 "citations": [],
 77 |                 "createdAt": "2025-03-23T10:11:24.123Z",
 78 |                 "updatedAt": "2025-03-23T10:11:24.123Z"
 79 |               },
 80 |               {
 81 |                 "id": "know_form_validation",
 82 |                 "projectId": "proj_ui_redesign",
 83 |                 "text": "Industry research shows that automatically formatting phone numbers and credit card fields as users type reduces error rates by approximately 25%. Implementing real-time validation with clear error messages has been shown to decrease form abandonment rates by up to 40%.",
 84 |                 "tags": ["form-design", "validation", "ux-patterns"],
 85 |                 "domain": "technical",
 86 |                 "citations": ["https://baymard.com/blog/input-mask-form-fields", "https://www.smashingmagazine.com/2020/03/form-validation-ux-design/"],
 87 |                 "createdAt": "2025-03-23T10:11:24.456Z",
 88 |                 "updatedAt": "2025-03-23T10:11:24.456Z"
 89 |               }
 90 |             ],
 91 |             "errors": []
 92 |           }`,
 93 |           "Add multiple knowledge items with mixed domains and research findings",
 94 |         ),
 95 |       ],
 96 |       requiredPermission: "knowledge:create",
 97 |       returnSchema: z.union([
 98 |         // Single knowledge response
 99 |         z.object({
100 |           id: z.string().describe("Knowledge ID"),
101 |           projectId: z.string().describe("Project ID"),
102 |           text: z.string().describe("Knowledge content"),
103 |           tags: z.array(z.string()).describe("Categorical labels"),
104 |           domain: z.string().describe("Knowledge domain"),
105 |           citations: z.array(z.string()).describe("Reference sources"),
106 |           createdAt: z.string().describe("Creation timestamp"),
107 |           updatedAt: z.string().describe("Last update timestamp"),
108 |         }),
109 |         // Bulk creation response
110 |         z.object({
111 |           success: z.boolean().describe("Operation success status"),
112 |           message: z.string().describe("Result message"),
113 |           created: z
114 |             .array(
115 |               z.object({
116 |                 id: z.string().describe("Knowledge ID"),
117 |                 projectId: z.string().describe("Project ID"),
118 |                 text: z.string().describe("Knowledge content"),
119 |                 tags: z.array(z.string()).describe("Categorical labels"),
120 |                 domain: z.string().describe("Knowledge domain"),
121 |                 citations: z.array(z.string()).describe("Reference sources"),
122 |                 createdAt: z.string().describe("Creation timestamp"),
123 |                 updatedAt: z.string().describe("Last update timestamp"),
124 |               }),
125 |             )
126 |             .describe("Created knowledge items"),
127 |           errors: z
128 |             .array(
129 |               z.object({
130 |                 index: z.number().describe("Index in the knowledge array"),
131 |                 knowledge: z.any().describe("Original knowledge data"),
132 |                 error: z
133 |                   .object({
134 |                     code: z.string().describe("Error code"),
135 |                     message: z.string().describe("Error message"),
136 |                     details: z
137 |                       .any()
138 |                       .optional()
139 |                       .describe("Additional error details"),
140 |                   })
141 |                   .describe("Error information"),
142 |               }),
143 |             )
144 |             .describe("Creation errors"),
145 |         }),
146 |       ]),
147 |       rateLimit: {
148 |         windowMs: 60 * 1000, // 1 minute
149 |         maxRequests: 20, // 20 requests per minute (higher than project creation as knowledge items are typically smaller)
150 |       },
151 |     }),
152 |   );
153 | };
154 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_knowledge_list/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import {
  4 |   ResponseFormat,
  5 |   createResponseFormatEnum,
  6 |   createToolResponse,
  7 | } from "../../../types/mcp.js";
  8 | import {
  9 |   createToolExample,
 10 |   createToolMetadata,
 11 |   registerTool,
 12 | } from "../../../types/tool.js";
 13 | import { listKnowledge } from "./listKnowledge.js";
 14 | import { formatKnowledgeListResponse } from "./responseFormat.js";
 15 | import { KnowledgeListRequest } from "./types.js"; // Corrected import name
 16 | 
 17 | /**
 18 |  * Registers the atlas_knowledge_list tool with the MCP server
 19 |  *
 20 |  * @param server The MCP server instance
 21 |  */
 22 | export function registerAtlasKnowledgeListTool(server: McpServer): void {
 23 |   registerTool(
 24 |     server,
 25 |     "atlas_knowledge_list",
 26 |     "Lists knowledge items according to specified filters with tag-based categorization, domain filtering, and full-text search capabilities",
 27 |     {
 28 |       projectId: z
 29 |         .string()
 30 |         .describe("ID of the project to list knowledge items for (required)"),
 31 |       tags: z
 32 |         .array(z.string())
 33 |         .optional()
 34 |         .describe(
 35 |           "Array of tags to filter by (items matching any tag will be included)",
 36 |         ),
 37 |       domain: z
 38 |         .string()
 39 |         .optional()
 40 |         .describe("Filter by knowledge domain/category"),
 41 |       search: z
 42 |         .string()
 43 |         .optional()
 44 |         .describe("Text search query to filter results by content relevance"),
 45 |       page: z
 46 |         .number()
 47 |         .min(1)
 48 |         .optional()
 49 |         .default(1)
 50 |         .describe("Page number for paginated results (Default: 1)"),
 51 |       limit: z
 52 |         .number()
 53 |         .min(1)
 54 |         .max(100)
 55 |         .optional()
 56 |         .default(20)
 57 |         .describe("Number of results per page, maximum 100 (Default: 20)"),
 58 |       responseFormat: createResponseFormatEnum()
 59 |         .optional()
 60 |         .default(ResponseFormat.FORMATTED)
 61 |         .describe(
 62 |           "Desired response format: 'formatted' (default string) or 'json' (raw object)",
 63 |         ),
 64 |     },
 65 |     async (input, context) => {
 66 |       // Process knowledge list request
 67 |       const validatedInput = input as unknown as KnowledgeListRequest & {
 68 |         responseFormat?: ResponseFormat;
 69 |       }; // Corrected type cast
 70 |       const result = await listKnowledge(validatedInput);
 71 | 
 72 |       // Conditionally format response
 73 |       if (validatedInput.responseFormat === ResponseFormat.JSON) {
 74 |         return createToolResponse(JSON.stringify(result, null, 2));
 75 |       } else {
 76 |         // Return the result using the formatter for rich display
 77 |         return formatKnowledgeListResponse(result, false);
 78 |       }
 79 |     },
 80 |     createToolMetadata({
 81 |       examples: [
 82 |         createToolExample(
 83 |           {
 84 |             projectId: "proj_ms_migration",
 85 |             limit: 5,
 86 |           },
 87 |           `{
 88 |             "knowledge": [
 89 |               {
 90 |                 "id": "know_saga_pattern",
 91 |                 "projectId": "proj_ms_migration",
 92 |                 "projectName": "Microservice Architecture Migration",
 93 |                 "text": "Distributed transactions must use Saga pattern with compensating actions to maintain data integrity across services",
 94 |                 "tags": ["architecture", "data-integrity", "patterns"],
 95 |                 "domain": "technical",
 96 |                 "citations": ["https://microservices.io/patterns/data/saga.html"],
 97 |                 "createdAt": "2025-03-23T11:22:14.789Z",
 98 |                 "updatedAt": "2025-03-23T11:22:14.789Z"
 99 |               },
100 |               {
101 |                 "id": "know_rate_limiting",
102 |                 "projectId": "proj_ms_migration",
103 |                 "projectName": "Microservice Architecture Migration",
104 |                 "text": "Rate limiting should be implemented at the API Gateway level using Redis-based token bucket algorithm",
105 |                 "tags": ["api-gateway", "performance", "security"],
106 |                 "domain": "technical",
107 |                 "citations": ["https://www.nginx.com/blog/rate-limiting-nginx/"],
108 |                 "createdAt": "2025-03-23T12:34:27.456Z",
109 |                 "updatedAt": "2025-03-23T12:34:27.456Z"
110 |               }
111 |             ],
112 |             "total": 2,
113 |             "page": 1,
114 |             "limit": 5,
115 |             "totalPages": 1
116 |           }`,
117 |           "Retrieve all knowledge items for a specific project",
118 |         ),
119 |         createToolExample(
120 |           {
121 |             projectId: "proj_ms_migration",
122 |             domain: "technical",
123 |             tags: ["security"],
124 |           },
125 |           `{
126 |             "knowledge": [
127 |               {
128 |                 "id": "know_rate_limiting",
129 |                 "projectId": "proj_ms_migration",
130 |                 "projectName": "Microservice Architecture Migration",
131 |                 "text": "Rate limiting should be implemented at the API Gateway level using Redis-based token bucket algorithm",
132 |                 "tags": ["api-gateway", "performance", "security"],
133 |                 "domain": "technical",
134 |                 "citations": ["https://www.nginx.com/blog/rate-limiting-nginx/"],
135 |                 "createdAt": "2025-03-23T12:34:27.456Z",
136 |                 "updatedAt": "2025-03-23T12:34:27.456Z"
137 |               }
138 |             ],
139 |             "total": 1,
140 |             "page": 1,
141 |             "limit": 20,
142 |             "totalPages": 1
143 |           }`,
144 |           "Filter knowledge items by domain and tags",
145 |         ),
146 |         createToolExample(
147 |           {
148 |             projectId: "proj_ms_migration",
149 |             search: "data integrity",
150 |           },
151 |           `{
152 |             "knowledge": [
153 |               {
154 |                 "id": "know_saga_pattern",
155 |                 "projectId": "proj_ms_migration",
156 |                 "projectName": "Microservice Architecture Migration",
157 |                 "text": "Distributed transactions must use Saga pattern with compensating actions to maintain data integrity across services",
158 |                 "tags": ["architecture", "data-integrity", "patterns"],
159 |                 "domain": "technical",
160 |                 "citations": ["https://microservices.io/patterns/data/saga.html"],
161 |                 "createdAt": "2025-03-23T11:22:14.789Z",
162 |                 "updatedAt": "2025-03-23T11:22:14.789Z"
163 |               }
164 |             ],
165 |             "total": 1,
166 |             "page": 1,
167 |             "limit": 20,
168 |             "totalPages": 1
169 |           }`,
170 |           "Search knowledge items for specific text content",
171 |         ),
172 |       ],
173 |       requiredPermission: "knowledge:read",
174 |       entityType: "knowledge",
175 |       returnSchema: z.object({
176 |         knowledge: z.array(
177 |           z.object({
178 |             id: z.string().describe("Knowledge ID"),
179 |             projectId: z.string().describe("Project ID"),
180 |             projectName: z.string().optional().describe("Project name"),
181 |             text: z.string().describe("Knowledge content"),
182 |             tags: z.array(z.string()).optional().describe("Categorical labels"),
183 |             domain: z.string().describe("Knowledge domain/category"),
184 |             citations: z
185 |               .array(z.string())
186 |               .optional()
187 |               .describe("Reference sources"),
188 |             createdAt: z.string().describe("Creation timestamp"),
189 |             updatedAt: z.string().describe("Last update timestamp"),
190 |           }),
191 |         ),
192 |         total: z
193 |           .number()
194 |           .describe("Total number of knowledge items matching criteria"),
195 |         page: z.number().describe("Current page number"),
196 |         limit: z.number().describe("Number of items per page"),
197 |         totalPages: z.number().describe("Total number of pages"),
198 |       }),
199 |       rateLimit: {
200 |         windowMs: 60 * 1000, // 1 minute
201 |         maxRequests: 30, // 30 requests per minute
202 |       },
203 |     }),
204 |   );
205 | }
206 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_unified_search/unifiedSearch.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   SearchResultItem,
  3 |   SearchService,
  4 | } from "../../../services/neo4j/index.js";
  5 | import { PaginatedResult } from "../../../services/neo4j/types.js";
  6 | import { BaseErrorCode, McpError } from "../../../types/errors.js";
  7 | import { ResponseFormat } from "../../../types/mcp.js";
  8 | import { ToolContext } from "../../../types/tool.js";
  9 | import { logger, requestContextService } from "../../../utils/index.js";
 10 | import { formatUnifiedSearchResponse } from "./responseFormat.js";
 11 | import {
 12 |   UnifiedSearchRequestInput,
 13 |   UnifiedSearchRequestSchema,
 14 |   UnifiedSearchResponse,
 15 | } from "./types.js";
 16 | 
 17 | export const atlasUnifiedSearch = async (
 18 |   input: unknown,
 19 |   context: ToolContext,
 20 | ): Promise<any> => {
 21 |   const reqContext =
 22 |     context.requestContext ??
 23 |     requestContextService.createRequestContext({
 24 |       toolName: "atlasUnifiedSearch",
 25 |     });
 26 |   try {
 27 |     const validatedInput = UnifiedSearchRequestSchema.parse(
 28 |       input,
 29 |     ) as UnifiedSearchRequestInput & { responseFormat?: ResponseFormat };
 30 | 
 31 |     logger.info("Performing unified search", {
 32 |       ...reqContext,
 33 |       input: validatedInput,
 34 |     });
 35 | 
 36 |     if (!validatedInput.value || validatedInput.value.trim() === "") {
 37 |       throw new McpError(
 38 |         BaseErrorCode.VALIDATION_ERROR,
 39 |         "Search value cannot be empty",
 40 |         { param: "value" },
 41 |       );
 42 |     }
 43 | 
 44 |     let searchResults: PaginatedResult<SearchResultItem>;
 45 |     const propertyForSearch = validatedInput.property?.trim();
 46 |     const entityTypesForSearch = validatedInput.entityTypes || [
 47 |       "project",
 48 |       "task",
 49 |       "knowledge",
 50 |     ]; // Default if not provided
 51 | 
 52 |     // Determine if we should use full-text for the given property and entity type
 53 |     let shouldUseFullText = false;
 54 |     if (propertyForSearch) {
 55 |       const lowerProp = propertyForSearch.toLowerCase();
 56 |       // Check for specific entityType + property combinations that have dedicated full-text indexes
 57 |       if (entityTypesForSearch.includes("knowledge") && lowerProp === "text") {
 58 |         shouldUseFullText = true;
 59 |       } else if (
 60 |         entityTypesForSearch.includes("project") &&
 61 |         (lowerProp === "name" || lowerProp === "description")
 62 |       ) {
 63 |         shouldUseFullText = true;
 64 |       } else if (
 65 |         entityTypesForSearch.includes("task") &&
 66 |         (lowerProp === "title" || lowerProp === "description")
 67 |       ) {
 68 |         shouldUseFullText = true;
 69 |       }
 70 |       // Add other specific full-text indexed fields here if any
 71 |     } else {
 72 |       // No specific property, so general full-text search is appropriate across default indexed fields
 73 |       shouldUseFullText = true;
 74 |     }
 75 | 
 76 |     if (shouldUseFullText) {
 77 |       logger.info(
 78 |         `Using full-text search. Property: '${propertyForSearch || "default fields"}'`,
 79 |         {
 80 |           ...reqContext,
 81 |           property: propertyForSearch,
 82 |           targetEntityTypes: entityTypesForSearch,
 83 |           effectiveFuzzy: validatedInput.fuzzy === true,
 84 |         },
 85 |       );
 86 | 
 87 |       const escapeLucene = (str: string) =>
 88 |         str.replace(/([+\-!(){}\[\]^"~*?:\\\/"])/g, "\\$1");
 89 |       let luceneQueryValue = escapeLucene(validatedInput.value);
 90 | 
 91 |       // If fuzzy is requested for the tool, apply it to the Lucene query
 92 |       if (validatedInput.fuzzy === true) {
 93 |         luceneQueryValue = `${luceneQueryValue}~1`;
 94 |       }
 95 |       // Note: If propertyForSearch is set (e.g., "text" for "knowledge"),
 96 |       // SearchService.fullTextSearch will use the appropriate index (e.g., "knowledge_fulltext").
 97 |       // Lucene itself can handle field-specific queries like "fieldName:term",
 98 |       // but our SearchService.fullTextSearch is already structured to call specific indexes.
 99 |       // So, just passing the term (and fuzzy if needed) is correct here.
100 | 
101 |       logger.debug("Constructed Lucene query value for full-text search", {
102 |         ...reqContext,
103 |         luceneQueryValue,
104 |       });
105 | 
106 |       searchResults = await SearchService.fullTextSearch(luceneQueryValue, {
107 |         entityTypes: entityTypesForSearch,
108 |         taskType: validatedInput.taskType,
109 |         page: validatedInput.page,
110 |         limit: validatedInput.limit,
111 |       });
112 |     } else {
113 |       // propertyForSearch is specified, and it's not one we've decided to use full-text for
114 |       // This path implies a regex-based search on a specific, non-full-text-optimized property.
115 |       // We want "contains" (fuzzy: true for SearchService.search) by default for this path,
116 |       // unless the user explicitly passed fuzzy: false in the tool input.
117 |       let finalFuzzyForRegexPath: boolean;
118 |       if ((input as any)?.fuzzy === false) {
119 |         // User explicitly requested an exact match for the regex search
120 |         finalFuzzyForRegexPath = false;
121 |       } else {
122 |         // User either passed fuzzy: true, or didn't pass fuzzy (in which case Zod default is true,
123 |         // and we also want "contains" as the intelligent default for this path).
124 |         finalFuzzyForRegexPath = true;
125 |       }
126 | 
127 |       logger.info(
128 |         `Using regex-based search for specific property: '${propertyForSearch}'. Effective fuzzy for SearchService.search (true means contains): ${finalFuzzyForRegexPath}`,
129 |         {
130 |           ...reqContext,
131 |           property: propertyForSearch,
132 |           targetEntityTypes: entityTypesForSearch,
133 |           userInputFuzzy: (input as any)?.fuzzy, // Log what user actually passed, if anything
134 |           zodParsedFuzzy: validatedInput.fuzzy, // Log what Zod parsed (with default)
135 |           finalFuzzyForRegexPath,
136 |         },
137 |       );
138 | 
139 |       searchResults = await SearchService.search({
140 |         property: propertyForSearch, // Already trimmed
141 |         value: validatedInput.value,
142 |         entityTypes: entityTypesForSearch,
143 |         caseInsensitive: validatedInput.caseInsensitive, // Pass through
144 |         fuzzy: finalFuzzyForRegexPath, // This now correctly defaults to 'true' for "contains"
145 |         taskType: validatedInput.taskType,
146 |         assignedToUserId: validatedInput.assignedToUserId, // Pass through
147 |         page: validatedInput.page,
148 |         limit: validatedInput.limit,
149 |       });
150 |     }
151 | 
152 |     if (!searchResults || !Array.isArray(searchResults.data)) {
153 |       logger.error(
154 |         "Search service returned invalid data structure.",
155 |         new Error("Invalid search results structure"),
156 |         { ...reqContext, searchResultsReceived: searchResults },
157 |       );
158 |       throw new McpError(
159 |         BaseErrorCode.INTERNAL_ERROR,
160 |         "Received invalid data structure from search service.",
161 |       );
162 |     }
163 | 
164 |     logger.info("Unified search completed successfully", {
165 |       ...reqContext,
166 |       resultCount: searchResults.data.length,
167 |       totalResults: searchResults.total,
168 |     });
169 | 
170 |     const responseData: UnifiedSearchResponse = {
171 |       results: searchResults.data,
172 |       total: searchResults.total,
173 |       page: searchResults.page,
174 |       limit: searchResults.limit,
175 |       totalPages: searchResults.totalPages,
176 |     };
177 | 
178 |     if (validatedInput.responseFormat === ResponseFormat.JSON) {
179 |       return responseData;
180 |     } else {
181 |       return formatUnifiedSearchResponse(responseData);
182 |     }
183 |   } catch (error) {
184 |     const errorMessage =
185 |       error instanceof Error ? error.message : "Unknown error";
186 |     // const errorStack = error instanceof Error ? error.stack : undefined; // Already captured by logger
187 |     logger.error("Failed to perform unified search", error as Error, {
188 |       ...reqContext,
189 |       // errorMessage and errorStack are part of the Error object passed to logger
190 |       inputReceived: input,
191 |     });
192 | 
193 |     if (error instanceof McpError) {
194 |       throw error;
195 |     } else {
196 |       throw new McpError(
197 |         BaseErrorCode.INTERNAL_ERROR,
198 |         `Error performing unified search: ${errorMessage}`,
199 |       );
200 |     }
201 |   }
202 | };
203 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | // Imports MUST be at the top level
  4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  5 | import http from "http";
  6 | import { config, environment } from "./config/index.js"; // This loads .env via dotenv.config()
  7 | import { initializeAndStartServer } from "./mcp/server.js";
  8 | import { closeNeo4jConnection } from "./services/neo4j/index.js";
  9 | import { logger, McpLogLevel, requestContextService } from "./utils/index.js";
 10 | 
 11 | /**
 12 |  * The main MCP server instance, stored if transport is stdio for shutdown.
 13 |  * Or the HTTP server instance if transport is http.
 14 |  * @type {McpServer | http.Server | undefined}
 15 |  */
 16 | let serverInstance: McpServer | http.Server | undefined;
 17 | 
 18 | /**
 19 |  * Gracefully shuts down the main MCP server and related services.
 20 |  * Handles process termination signals (SIGTERM, SIGINT) and critical errors.
 21 |  *
 22 |  * @param signal - The signal or event name that triggered the shutdown (e.g., "SIGTERM", "uncaughtException").
 23 |  */
 24 | const shutdown = async (signal: string) => {
 25 |   // Create a proper RequestContext for shutdown operations
 26 |   const shutdownContext = requestContextService.createRequestContext({
 27 |     operation: "Shutdown",
 28 |     signal,
 29 |     appName: config.mcpServerName,
 30 |   });
 31 | 
 32 |   logger.info(
 33 |     `Received ${signal}. Starting graceful shutdown for ${config.mcpServerName}...`,
 34 |     shutdownContext,
 35 |   );
 36 | 
 37 |   try {
 38 |     if (serverInstance) {
 39 |       if (serverInstance instanceof McpServer) {
 40 |         logger.info("Closing main MCP server (stdio) instance...", shutdownContext);
 41 |         await serverInstance.close();
 42 |         logger.info(
 43 |           "Main MCP server (stdio) instance closed successfully.",
 44 |           shutdownContext,
 45 |         );
 46 |       } else if (serverInstance instanceof http.Server) {
 47 |         logger.info("Closing HTTP server instance...", shutdownContext);
 48 |         const currentHttpServer = serverInstance;
 49 |         await new Promise<void>((resolve, reject) => {
 50 |           currentHttpServer.close((err?: Error) => {
 51 |             if (err) {
 52 |               logger.error("Error closing HTTP server", err, shutdownContext);
 53 |               return reject(err);
 54 |             }
 55 |             logger.info("HTTP server instance closed successfully.", shutdownContext);
 56 |             resolve();
 57 |           });
 58 |         });
 59 |       }
 60 |     } else {
 61 |       logger.info(
 62 |         "No global MCP server instance to close (expected for HTTP transport or if not yet initialized).",
 63 |         shutdownContext,
 64 |       );
 65 |     }
 66 | 
 67 |     logger.info("Closing Neo4j driver connection...", shutdownContext);
 68 |     await closeNeo4jConnection();
 69 |     logger.info(
 70 |       "Neo4j driver connection closed successfully.",
 71 |       shutdownContext,
 72 |     );
 73 | 
 74 |     logger.info(
 75 |       `Graceful shutdown for ${config.mcpServerName} completed successfully. Exiting.`,
 76 |       shutdownContext,
 77 |     );
 78 |     process.exit(0);
 79 |   } catch (error) {
 80 |     // Pass the error object directly as the second argument to logger.error
 81 |     logger.error("Critical error during shutdown", error as Error, {
 82 |       ...shutdownContext,
 83 |       // error message and stack are now part of the Error object passed to logger
 84 |     });
 85 |     process.exit(1);
 86 |   }
 87 | };
 88 | 
 89 | /**
 90 |  * Initializes and starts the main MCP server.
 91 |  * Sets up logging, request context, initializes the server instance, starts the transport,
 92 |  * and registers signal handlers for graceful shutdown and error handling.
 93 |  */
 94 | const start = async () => {
 95 |   // --- Logger Initialization ---
 96 |   const validMcpLogLevels: McpLogLevel[] = [
 97 |     "debug",
 98 |     "info",
 99 |     "notice",
100 |     "warning",
101 |     "error",
102 |     "crit",
103 |     "alert",
104 |     "emerg",
105 |   ];
106 |   const initialLogLevelConfig = config.logLevel;
107 |   let validatedMcpLogLevel: McpLogLevel = "info"; // Default
108 | 
109 |   if (validMcpLogLevels.includes(initialLogLevelConfig as McpLogLevel)) {
110 |     validatedMcpLogLevel = initialLogLevelConfig as McpLogLevel;
111 |   } else {
112 |     // Use console.warn here as logger isn't fully initialized yet, only if TTY
113 |     if (process.stdout.isTTY) {
114 |       console.warn(
115 |         `Invalid MCP_LOG_LEVEL "${initialLogLevelConfig}" provided via config/env. Defaulting to "info".`,
116 |       );
117 |     }
118 |   }
119 |   // Initialize the logger with the validated MCP level and wait for it to complete.
120 |   await logger.initialize(validatedMcpLogLevel);
121 |   // The logger.initialize() method itself logs its status, so no redundant log here.
122 |   // --- End Logger Initialization ---
123 | 
124 |   const configLoadContext = requestContextService.createRequestContext({
125 |     operation: "ConfigLoad",
126 |   });
127 |   logger.debug("Configuration loaded successfully", {
128 |     ...configLoadContext,
129 |     configValues: config,
130 |   }); // Spread context and add specific data
131 | 
132 |   const transportType = config.mcpTransportType;
133 |   const startupContext = requestContextService.createRequestContext({
134 |     operation: `AtlasServerStartup_${transportType}`,
135 |     appName: config.mcpServerName,
136 |     appVersion: config.mcpServerVersion,
137 |     environment: environment,
138 |   });
139 | 
140 |   logger.info(
141 |     `Starting ${config.mcpServerName} v${config.mcpServerVersion} (Transport: ${transportType})...`,
142 |     startupContext,
143 |   );
144 | 
145 |   try {
146 |     logger.debug(
147 |       "Initializing and starting MCP server transport...",
148 |       startupContext,
149 |     );
150 | 
151 |     const potentialServer = await initializeAndStartServer();
152 | 
153 |     if (transportType === "stdio" && potentialServer instanceof McpServer) {
154 |       serverInstance = potentialServer;
155 |       logger.debug(
156 |         "Stored McpServer instance for stdio transport.",
157 |         startupContext,
158 |       );
159 |     } else if (transportType === "http" && potentialServer instanceof http.Server) {
160 |       serverInstance = potentialServer;
161 |       logger.debug(
162 |         "Stored HTTP server instance. MCP sessions are per-request.",
163 |         startupContext,
164 |       );
165 |     } else if (transportType === "http" && !potentialServer) {
166 |         logger.debug(
167 |             "HTTP transport started. Server instance not returned by initializeAndStartServer. MCP sessions are per-request.",
168 |             startupContext,
169 |         );
170 |     }
171 | 
172 | 
173 |     logger.info(
174 |       `${config.mcpServerName} is running with ${transportType} transport.`,
175 |       {
176 |         ...startupContext,
177 |         startTime: new Date().toISOString(),
178 |       },
179 |     );
180 | 
181 |     // --- Signal and Error Handling Setup ---
182 |     process.on("SIGTERM", () => shutdown("SIGTERM"));
183 |     process.on("SIGINT", () => shutdown("SIGINT"));
184 | 
185 |     process.on("uncaughtException", async (error) => {
186 |       const errorContext = {
187 |         ...startupContext,
188 |         event: "uncaughtException",
189 |         error: error instanceof Error ? error.message : String(error),
190 |         stack: error instanceof Error ? error.stack : undefined,
191 |       };
192 |       logger.error(
193 |         "Uncaught exception detected. Initiating shutdown...",
194 |         errorContext,
195 |       );
196 |       await shutdown("uncaughtException");
197 |     });
198 | 
199 |     process.on("unhandledRejection", async (reason: unknown) => {
200 |       const rejectionContext = {
201 |         ...startupContext,
202 |         event: "unhandledRejection",
203 |         reason: reason instanceof Error ? reason.message : String(reason),
204 |         stack: reason instanceof Error ? reason.stack : undefined,
205 |       };
206 |       logger.error(
207 |         "Unhandled promise rejection detected. Initiating shutdown...",
208 |         rejectionContext,
209 |       );
210 |       await shutdown("unhandledRejection");
211 |     });
212 |   } catch (error) {
213 |     logger.error("Critical error during ATLAS MCP Server startup, exiting.", {
214 |       ...startupContext,
215 |       finalErrorContext: "Startup Failure",
216 |       error: error instanceof Error ? error.message : String(error),
217 |       stack: error instanceof Error ? error.stack : undefined,
218 |     });
219 |     process.exit(1);
220 |   }
221 | };
222 | 
223 | // --- Async IIFE to allow top-level await ---
224 | (async () => {
225 |   await start();
226 | })();
227 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_create/createProject.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ProjectService } from "../../../services/neo4j/projectService.js";
  2 | import { ProjectDependencyType } from "../../../services/neo4j/types.js"; // Import the enum
  3 | import {
  4 |   BaseErrorCode,
  5 |   McpError,
  6 |   ProjectErrorCode,
  7 | } from "../../../types/errors.js";
  8 | import { ResponseFormat, createToolResponse } from "../../../types/mcp.js";
  9 | import { logger, requestContextService } from "../../../utils/index.js"; // Import requestContextService
 10 | import { ToolContext } from "../../../types/tool.js";
 11 | import { AtlasProjectCreateInput, AtlasProjectCreateSchema } from "./types.js";
 12 | import { formatProjectCreateResponse } from "./responseFormat.js";
 13 | 
 14 | export const atlasCreateProject = async (
 15 |   input: unknown,
 16 |   context: ToolContext,
 17 | ) => {
 18 |   let validatedInput: AtlasProjectCreateInput | undefined;
 19 |   const reqContext =
 20 |     context.requestContext ??
 21 |     requestContextService.createRequestContext({
 22 |       toolName: "atlasCreateProject",
 23 |     });
 24 | 
 25 |   try {
 26 |     // Parse and validate input against schema
 27 |     validatedInput = AtlasProjectCreateSchema.parse(input);
 28 | 
 29 |     // Handle single vs bulk project creation based on mode
 30 |     if (validatedInput.mode === "bulk") {
 31 |       // Execute bulk creation operation
 32 |       logger.info("Initializing multiple projects", {
 33 |         ...reqContext,
 34 |         count: validatedInput.projects.length,
 35 |       });
 36 | 
 37 |       const results = {
 38 |         success: true,
 39 |         message: `Successfully created ${validatedInput.projects.length} projects`,
 40 |         created: [] as any[],
 41 |         errors: [] as any[],
 42 |       };
 43 | 
 44 |       // Process each project sequentially to maintain consistency
 45 |       for (let i = 0; i < validatedInput.projects.length; i++) {
 46 |         const projectData = validatedInput.projects[i];
 47 |         try {
 48 |           const createdProject = await ProjectService.createProject({
 49 |             name: projectData.name,
 50 |             description: projectData.description,
 51 |             status: projectData.status || "active",
 52 |             urls: projectData.urls || [],
 53 |             completionRequirements: projectData.completionRequirements,
 54 |             outputFormat: projectData.outputFormat,
 55 |             taskType: projectData.taskType,
 56 |             id: projectData.id, // Use client-provided ID if available
 57 |           });
 58 | 
 59 |           results.created.push(createdProject);
 60 | 
 61 |           // Create dependency relationships if specified
 62 |           if (projectData.dependencies && projectData.dependencies.length > 0) {
 63 |             for (const dependencyId of projectData.dependencies) {
 64 |               try {
 65 |                 await ProjectService.addProjectDependency(
 66 |                   createdProject.id,
 67 |                   dependencyId,
 68 |                   ProjectDependencyType.REQUIRES, // Use enum member
 69 |                   "Dependency created during project creation",
 70 |                 );
 71 |               } catch (error) {
 72 |                 const depErrorContext =
 73 |                   requestContextService.createRequestContext({
 74 |                     ...reqContext,
 75 |                     originalErrorMessage:
 76 |                       error instanceof Error ? error.message : String(error),
 77 |                     originalErrorStack:
 78 |                       error instanceof Error ? error.stack : undefined,
 79 |                     projectId: createdProject.id,
 80 |                     dependencyIdAttempted: dependencyId,
 81 |                   });
 82 |                 logger.warning(
 83 |                   `Failed to create dependency for project ${createdProject.id} to ${dependencyId}`,
 84 |                   depErrorContext,
 85 |                 );
 86 |               }
 87 |             }
 88 |           }
 89 |         } catch (error) {
 90 |           results.success = false;
 91 |           results.errors.push({
 92 |             index: i,
 93 |             project: projectData,
 94 |             error: {
 95 |               code:
 96 |                 error instanceof McpError
 97 |                   ? error.code
 98 |                   : BaseErrorCode.INTERNAL_ERROR,
 99 |               message: error instanceof Error ? error.message : "Unknown error",
100 |               details: error instanceof McpError ? error.details : undefined,
101 |             },
102 |           });
103 |         }
104 |       }
105 | 
106 |       if (results.errors.length > 0) {
107 |         results.message = `Created ${results.created.length} of ${validatedInput.projects.length} projects with ${results.errors.length} errors`;
108 |       }
109 | 
110 |       logger.info("Bulk project initialization completed", {
111 |         ...reqContext,
112 |         successCount: results.created.length,
113 |         errorCount: results.errors.length,
114 |         projectIds: results.created.map((p) => p.id),
115 |       });
116 | 
117 |       // Conditionally format response
118 |       if (validatedInput.responseFormat === ResponseFormat.JSON) {
119 |         return createToolResponse(JSON.stringify(results, null, 2));
120 |       } else {
121 |         return formatProjectCreateResponse(results);
122 |       }
123 |     } else {
124 |       // Process single project creation
125 |       const {
126 |         mode,
127 |         id,
128 |         name,
129 |         description,
130 |         status,
131 |         urls,
132 |         completionRequirements,
133 |         dependencies,
134 |         outputFormat,
135 |         taskType,
136 |       } = validatedInput;
137 | 
138 |       logger.info("Initializing new project", {
139 |         ...reqContext,
140 |         name,
141 |         status,
142 |       });
143 | 
144 |       const project = await ProjectService.createProject({
145 |         id, // Use client-provided ID if available
146 |         name,
147 |         description,
148 |         status: status || "active",
149 |         urls: urls || [],
150 |         completionRequirements,
151 |         outputFormat,
152 |         taskType,
153 |       });
154 | 
155 |       // Create dependency relationships if specified
156 |       if (dependencies && dependencies.length > 0) {
157 |         for (const dependencyId of dependencies) {
158 |           try {
159 |             await ProjectService.addProjectDependency(
160 |               project.id,
161 |               dependencyId,
162 |               ProjectDependencyType.REQUIRES, // Use enum member
163 |               "Dependency created during project creation",
164 |             );
165 |           } catch (error) {
166 |             const depErrorContext = requestContextService.createRequestContext({
167 |               ...reqContext,
168 |               originalErrorMessage:
169 |                 error instanceof Error ? error.message : String(error),
170 |               originalErrorStack:
171 |                 error instanceof Error ? error.stack : undefined,
172 |               projectId: project.id,
173 |               dependencyIdAttempted: dependencyId,
174 |             });
175 |             logger.warning(
176 |               `Failed to create dependency for project ${project.id} to ${dependencyId}`,
177 |               depErrorContext,
178 |             );
179 |           }
180 |         }
181 |       }
182 | 
183 |       logger.info("Project initialized successfully", {
184 |         ...reqContext,
185 |         projectId: project.id,
186 |       });
187 | 
188 |       // Conditionally format response
189 |       if (validatedInput.responseFormat === ResponseFormat.JSON) {
190 |         return createToolResponse(JSON.stringify(project, null, 2));
191 |       } else {
192 |         return formatProjectCreateResponse(project);
193 |       }
194 |     }
195 |   } catch (error) {
196 |     // Handle specific error cases
197 |     if (error instanceof McpError) {
198 |       throw error;
199 |     }
200 | 
201 |     logger.error("Failed to initialize project(s)", error as Error, {
202 |       ...reqContext,
203 |       inputReceived: validatedInput ?? input, // Log validated or raw input
204 |     });
205 | 
206 |     // Handle duplicate name error specifically
207 |     if (error instanceof Error && error.message.includes("duplicate")) {
208 |       throw new McpError(
209 |         ProjectErrorCode.DUPLICATE_NAME,
210 |         `A project with this name already exists`,
211 |         {
212 |           name:
213 |             validatedInput?.mode === "single"
214 |               ? validatedInput?.name
215 |               : validatedInput?.projects?.[0]?.name,
216 |         },
217 |       );
218 |     }
219 | 
220 |     // Convert other errors to McpError
221 |     throw new McpError(
222 |       BaseErrorCode.INTERNAL_ERROR,
223 |       `Error creating project(s): ${error instanceof Error ? error.message : "Unknown error"}`,
224 |     );
225 |   }
226 | };
227 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_update/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | import {
  3 |   McpToolResponse,
  4 |   ProjectStatus,
  5 |   ResponseFormat,
  6 |   TaskType,
  7 |   createResponseFormatEnum,
  8 | } from "../../../types/mcp.js";
  9 | 
 10 | export const ProjectUpdateSchema = z.object({
 11 |   id: z.string().describe("Identifier of the existing project to be modified"),
 12 |   updates: z
 13 |     .object({
 14 |       name: z
 15 |         .string()
 16 |         .min(1)
 17 |         .max(100)
 18 |         .optional()
 19 |         .describe(
 20 |           "Modified project name following naming conventions (1-100 characters)",
 21 |         ),
 22 |       description: z
 23 |         .string()
 24 |         .optional()
 25 |         .describe("Revised project scope, goals, and implementation details"),
 26 |       status: z
 27 |         .enum([
 28 |           ProjectStatus.ACTIVE,
 29 |           ProjectStatus.PENDING,
 30 |           ProjectStatus.IN_PROGRESS,
 31 |           ProjectStatus.COMPLETED,
 32 |           ProjectStatus.ARCHIVED,
 33 |         ])
 34 |         .optional()
 35 |         .describe(
 36 |           "Updated lifecycle state reflecting current project progress",
 37 |         ),
 38 |       urls: z
 39 |         .array(
 40 |           z.object({
 41 |             title: z.string(),
 42 |             url: z.string(),
 43 |           }),
 44 |         )
 45 |         .optional()
 46 |         .describe(
 47 |           "Modified documentation links, specifications, and technical resources",
 48 |         ),
 49 |       completionRequirements: z
 50 |         .string()
 51 |         .optional()
 52 |         .describe(
 53 |           "Revised definition of done with updated success criteria and metrics",
 54 |         ),
 55 |       outputFormat: z
 56 |         .string()
 57 |         .optional()
 58 |         .describe("Modified deliverable specification for project artifacts"),
 59 |       taskType: z
 60 |         .enum([
 61 |           TaskType.RESEARCH,
 62 |           TaskType.GENERATION,
 63 |           TaskType.ANALYSIS,
 64 |           TaskType.INTEGRATION,
 65 |         ])
 66 |         .optional()
 67 |         .describe(
 68 |           "Revised classification for project categorization and workflow",
 69 |         ),
 70 |     })
 71 |     .describe(
 72 |       "Partial update object containing only fields that need modification",
 73 |     ),
 74 | });
 75 | 
 76 | const SingleProjectUpdateSchema = z
 77 |   .object({
 78 |     mode: z.literal("single"),
 79 |     id: z.string(),
 80 |     updates: z.object({
 81 |       name: z.string().min(1).max(100).optional(),
 82 |       description: z.string().optional(),
 83 |       status: z
 84 |         .enum([
 85 |           ProjectStatus.ACTIVE,
 86 |           ProjectStatus.PENDING,
 87 |           ProjectStatus.IN_PROGRESS,
 88 |           ProjectStatus.COMPLETED,
 89 |           ProjectStatus.ARCHIVED,
 90 |         ])
 91 |         .optional(),
 92 |       urls: z
 93 |         .array(
 94 |           z.object({
 95 |             title: z.string(),
 96 |             url: z.string(),
 97 |           }),
 98 |         )
 99 |         .optional(),
100 |       completionRequirements: z.string().optional(),
101 |       outputFormat: z.string().optional(),
102 |       taskType: z
103 |         .enum([
104 |           TaskType.RESEARCH,
105 |           TaskType.GENERATION,
106 |           TaskType.ANALYSIS,
107 |           TaskType.INTEGRATION,
108 |         ])
109 |         .optional(),
110 |     }),
111 |     responseFormat: createResponseFormatEnum()
112 |       .optional()
113 |       .default(ResponseFormat.FORMATTED)
114 |       .describe(
115 |         "Desired response format: 'formatted' (default string) or 'json' (raw object)",
116 |       ),
117 |   })
118 |   .describe(
119 |     "Atomically update a single project with selective field modifications",
120 |   );
121 | 
122 | const BulkProjectUpdateSchema = z
123 |   .object({
124 |     mode: z.literal("bulk"),
125 |     projects: z
126 |       .array(
127 |         z.object({
128 |           id: z.string().describe("Identifier of the project to update"),
129 |           updates: z.object({
130 |             name: z.string().min(1).max(100).optional(),
131 |             description: z.string().optional(),
132 |             status: z
133 |               .enum([
134 |                 ProjectStatus.ACTIVE,
135 |                 ProjectStatus.PENDING,
136 |                 ProjectStatus.IN_PROGRESS,
137 |                 ProjectStatus.COMPLETED,
138 |                 ProjectStatus.ARCHIVED,
139 |               ])
140 |               .optional(),
141 |             urls: z
142 |               .array(
143 |                 z.object({
144 |                   title: z.string(),
145 |                   url: z.string(),
146 |                 }),
147 |               )
148 |               .optional(),
149 |             completionRequirements: z.string().optional(),
150 |             outputFormat: z.string().optional(),
151 |             taskType: z
152 |               .enum([
153 |                 TaskType.RESEARCH,
154 |                 TaskType.GENERATION,
155 |                 TaskType.ANALYSIS,
156 |                 TaskType.INTEGRATION,
157 |               ])
158 |               .optional(),
159 |           }),
160 |         }),
161 |       )
162 |       .min(1)
163 |       .max(100)
164 |       .describe(
165 |         "Collection of project updates to be applied in a single transaction",
166 |       ),
167 |     responseFormat: createResponseFormatEnum()
168 |       .optional()
169 |       .default(ResponseFormat.FORMATTED)
170 |       .describe(
171 |         "Desired response format: 'formatted' (default string) or 'json' (raw object)",
172 |       ),
173 |   })
174 |   .describe(
175 |     "Update multiple related projects in a single efficient transaction",
176 |   );
177 | 
178 | // Schema shapes for tool registration
179 | export const AtlasProjectUpdateSchemaShape = {
180 |   mode: z
181 |     .enum(["single", "bulk"])
182 |     .describe(
183 |       "Operation mode - 'single' for individual update, 'bulk' for updating multiple projects",
184 |     ),
185 |   id: z
186 |     .string()
187 |     .optional()
188 |     .describe(
189 |       "Project identifier for the update operation (required for mode='single')",
190 |     ),
191 |   updates: z
192 |     .object({
193 |       name: z.string().min(1).max(100).optional(),
194 |       description: z.string().optional(),
195 |       status: z
196 |         .enum([
197 |           ProjectStatus.ACTIVE,
198 |           ProjectStatus.PENDING,
199 |           ProjectStatus.IN_PROGRESS,
200 |           ProjectStatus.COMPLETED,
201 |           ProjectStatus.ARCHIVED,
202 |         ])
203 |         .optional(),
204 |       urls: z
205 |         .array(
206 |           z.object({
207 |             title: z.string(),
208 |             url: z.string(),
209 |           }),
210 |         )
211 |         .optional(),
212 |       completionRequirements: z.string().optional(),
213 |       outputFormat: z.string().optional(),
214 |       taskType: z
215 |         .enum([
216 |           TaskType.RESEARCH,
217 |           TaskType.GENERATION,
218 |           TaskType.ANALYSIS,
219 |           TaskType.INTEGRATION,
220 |         ])
221 |         .optional(),
222 |     })
223 |     .optional()
224 |     .describe(
225 |       "Partial update specifying only the fields to be modified (required for mode='single')",
226 |     ),
227 |   projects: z
228 |     .array(
229 |       z.object({
230 |         id: z.string(),
231 |         updates: z.object({
232 |           name: z.string().min(1).max(100).optional(),
233 |           description: z.string().optional(),
234 |           status: z
235 |             .enum([
236 |               ProjectStatus.ACTIVE,
237 |               ProjectStatus.PENDING,
238 |               ProjectStatus.IN_PROGRESS,
239 |               ProjectStatus.COMPLETED,
240 |               ProjectStatus.ARCHIVED,
241 |             ])
242 |             .optional(),
243 |           urls: z
244 |             .array(
245 |               z.object({
246 |                 title: z.string(),
247 |                 url: z.string(),
248 |               }),
249 |             )
250 |             .optional(),
251 |           completionRequirements: z.string().optional(),
252 |           outputFormat: z.string().optional(),
253 |           taskType: z
254 |             .enum([
255 |               TaskType.RESEARCH,
256 |               TaskType.GENERATION,
257 |               TaskType.ANALYSIS,
258 |               TaskType.INTEGRATION,
259 |             ])
260 |             .optional(),
261 |         }),
262 |       }),
263 |     )
264 |     .optional()
265 |     .describe(
266 |       "Collection of project modifications to apply in a single transaction (required for mode='bulk')",
267 |     ),
268 |   responseFormat: createResponseFormatEnum()
269 |     .optional()
270 |     .describe(
271 |       "Desired response format: 'formatted' (default string) or 'json' (raw object)",
272 |     ),
273 | } as const;
274 | 
275 | // Schema for validation
276 | export const AtlasProjectUpdateSchema = z.discriminatedUnion("mode", [
277 |   SingleProjectUpdateSchema,
278 |   BulkProjectUpdateSchema,
279 | ]);
280 | 
281 | export type AtlasProjectUpdateInput = z.infer<typeof AtlasProjectUpdateSchema>;
282 | export type ProjectUpdateInput = z.infer<typeof ProjectUpdateSchema>;
283 | export type AtlasProjectUpdateResponse = McpToolResponse;
284 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_task_list/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import {
  4 |   PriorityLevel,
  5 |   ResponseFormat,
  6 |   TaskStatus,
  7 |   createResponseFormatEnum,
  8 |   createToolResponse,
  9 | } from "../../../types/mcp.js";
 10 | import {
 11 |   createToolExample,
 12 |   createToolMetadata,
 13 |   registerTool,
 14 | } from "../../../types/tool.js";
 15 | import { atlasListTasks } from "./listTasks.js";
 16 | import { formatTaskListResponse } from "./responseFormat.js";
 17 | import { TaskListRequestInput } from "./types.js"; // Corrected import
 18 | 
 19 | // Schema shapes for tool registration
 20 | const TaskListRequestSchemaShape = {
 21 |   projectId: z
 22 |     .string()
 23 |     .describe("ID of the project to list tasks for (required)"),
 24 |   status: z
 25 |     .union([
 26 |       z.enum([
 27 |         TaskStatus.BACKLOG,
 28 |         TaskStatus.TODO,
 29 |         TaskStatus.IN_PROGRESS,
 30 |         TaskStatus.COMPLETED,
 31 |       ]),
 32 |       z.array(
 33 |         z.enum([
 34 |           TaskStatus.BACKLOG,
 35 |           TaskStatus.TODO,
 36 |           TaskStatus.IN_PROGRESS,
 37 |           TaskStatus.COMPLETED,
 38 |         ]),
 39 |       ),
 40 |     ])
 41 |     .optional()
 42 |     .describe("Filter by task status or array of statuses"),
 43 |   assignedTo: z.string().optional().describe("Filter by assignment ID"),
 44 |   priority: z
 45 |     .union([
 46 |       z.enum([
 47 |         PriorityLevel.LOW,
 48 |         PriorityLevel.MEDIUM,
 49 |         PriorityLevel.HIGH,
 50 |         PriorityLevel.CRITICAL,
 51 |       ]),
 52 |       z.array(
 53 |         z.enum([
 54 |           PriorityLevel.LOW,
 55 |           PriorityLevel.MEDIUM,
 56 |           PriorityLevel.HIGH,
 57 |           PriorityLevel.CRITICAL,
 58 |         ]),
 59 |       ),
 60 |     ])
 61 |     .optional()
 62 |     .describe("Filter by priority level or array of priorities"),
 63 |   tags: z
 64 |     .array(z.string())
 65 |     .optional()
 66 |     .describe(
 67 |       "Array of tags to filter by (tasks matching any tag will be included)",
 68 |     ),
 69 |   taskType: z.string().optional().describe("Filter by task classification"),
 70 |   sortBy: z
 71 |     .enum(["priority", "createdAt", "status"])
 72 |     .optional()
 73 |     .describe("Field to sort results by (Default: createdAt)"),
 74 |   sortDirection: z
 75 |     .enum(["asc", "desc"])
 76 |     .optional()
 77 |     .describe("Sort order (Default: desc)"),
 78 |   page: z
 79 |     .number()
 80 |     .optional()
 81 |     .describe("Page number for paginated results (Default: 1)"),
 82 |   limit: z
 83 |     .number()
 84 |     .optional()
 85 |     .describe("Number of results per page, maximum 100 (Default: 20)"),
 86 |   responseFormat: createResponseFormatEnum()
 87 |     .optional()
 88 |     .default(ResponseFormat.FORMATTED)
 89 |     .describe(
 90 |       "Desired response format: 'formatted' (default string) or 'json' (raw object)",
 91 |     ),
 92 | };
 93 | 
 94 | export const registerAtlasTaskListTool = (server: McpServer) => {
 95 |   registerTool(
 96 |     server,
 97 |     "atlas_task_list",
 98 |     "Lists tasks according to specified filters with advanced filtering, sorting, and pagination capabilities",
 99 |     TaskListRequestSchemaShape,
100 |     async (input, context) => {
101 |       // Parse and process input (assuming validation happens implicitly via registerTool)
102 |       const validatedInput = input as unknown as TaskListRequestInput & {
103 |         responseFormat?: ResponseFormat;
104 |       }; // Corrected type cast
105 |       const result = await atlasListTasks(validatedInput, context); // Added context argument
106 | 
107 |       // Conditionally format response
108 |       if (validatedInput.responseFormat === ResponseFormat.JSON) {
109 |         // Stringify the result and wrap it in a standard text response
110 |         // The client will need to parse this stringified JSON
111 |         return createToolResponse(JSON.stringify(result, null, 2));
112 |       } else {
113 |         // Return the formatted string using the formatter for rich display
114 |         return formatTaskListResponse(result, false); // Added second argument
115 |       }
116 |     },
117 |     createToolMetadata({
118 |       examples: [
119 |         createToolExample(
120 |           {
121 |             projectId: "proj_example123",
122 |             status: "in_progress",
123 |             limit: 10,
124 |           },
125 |           `{
126 |             "tasks": [
127 |               {
128 |                 "id": "task_abcd1234",
129 |                 "projectId": "proj_example123",
130 |                 "title": "Implement User Authentication",
131 |                 "description": "Create secure user authentication system with JWT and refresh tokens",
132 |                 "priority": "high",
133 |                 "status": "in_progress",
134 |                 "assignedTo": "user_5678",
135 |                 "tags": ["security", "backend"],
136 |                 "completionRequirements": "Authentication endpoints working with proper error handling and tests",
137 |                 "outputFormat": "Documented API with test coverage",
138 |                 "taskType": "implementation",
139 |                 "createdAt": "2025-03-20T14:24:35.123Z",
140 |                 "updatedAt": "2025-03-22T09:15:22.456Z"
141 |               }
142 |             ],
143 |             "total": 5,
144 |             "page": 1,
145 |             "limit": 10,
146 |             "totalPages": 1
147 |           }`,
148 |           "List in-progress tasks for a specific project",
149 |         ),
150 |         createToolExample(
151 |           {
152 |             projectId: "proj_frontend42",
153 |             priority: ["high", "critical"],
154 |             tags: ["bug", "urgent"],
155 |             sortBy: "priority",
156 |             sortDirection: "desc",
157 |           },
158 |           `{
159 |             "tasks": [
160 |               {
161 |                 "id": "task_ef5678",
162 |                 "projectId": "proj_frontend42",
163 |                 "title": "Fix Critical UI Rendering Bug",
164 |                 "description": "Address the UI rendering issue causing layout problems on mobile devices",
165 |                 "priority": "critical",
166 |                 "status": "todo",
167 |                 "tags": ["bug", "ui", "urgent"],
168 |                 "completionRequirements": "UI displays correctly on all supported mobile devices",
169 |                 "outputFormat": "Fixed code with browser compatibility tests",
170 |                 "taskType": "bugfix",
171 |                 "createdAt": "2025-03-21T10:30:15.789Z",
172 |                 "updatedAt": "2025-03-21T10:30:15.789Z"
173 |               },
174 |               {
175 |                 "id": "task_gh9012",
176 |                 "projectId": "proj_frontend42",
177 |                 "title": "Optimize Image Loading Performance",
178 |                 "description": "Implement lazy loading and optimize image assets to improve page load time",
179 |                 "priority": "high",
180 |                 "status": "backlog",
181 |                 "tags": ["performance", "urgent"],
182 |                 "completionRequirements": "Page load time reduced by 40% with Lighthouse score above 90",
183 |                 "outputFormat": "Optimized code with performance benchmarks",
184 |                 "taskType": "optimization",
185 |                 "createdAt": "2025-03-19T16:45:22.123Z",
186 |                 "updatedAt": "2025-03-19T16:45:22.123Z"
187 |               }
188 |             ],
189 |             "total": 2,
190 |             "page": 1,
191 |             "limit": 20,
192 |             "totalPages": 1
193 |           }`,
194 |           "List high priority and critical tasks with specific tags, sorted by priority",
195 |         ),
196 |       ],
197 |       requiredPermission: "project:read",
198 |       returnSchema: z.object({
199 |         tasks: z.array(
200 |           z.object({
201 |             id: z.string(),
202 |             projectId: z.string(),
203 |             title: z.string(),
204 |             description: z.string(),
205 |             priority: z.enum([
206 |               PriorityLevel.LOW,
207 |               PriorityLevel.MEDIUM,
208 |               PriorityLevel.HIGH,
209 |               PriorityLevel.CRITICAL,
210 |             ]),
211 |             status: z.enum([
212 |               TaskStatus.BACKLOG,
213 |               TaskStatus.TODO,
214 |               TaskStatus.IN_PROGRESS,
215 |               TaskStatus.COMPLETED,
216 |             ]),
217 |             assignedTo: z.string().optional(),
218 |             urls: z
219 |               .array(
220 |                 z.object({
221 |                   title: z.string(),
222 |                   url: z.string(),
223 |                 }),
224 |               )
225 |               .optional(),
226 |             tags: z.array(z.string()).optional(),
227 |             completionRequirements: z.string(),
228 |             outputFormat: z.string(),
229 |             taskType: z.string(),
230 |             createdAt: z.string(),
231 |             updatedAt: z.string(),
232 |           }),
233 |         ),
234 |         total: z.number().int(),
235 |         page: z.number().int(),
236 |         limit: z.number().int(),
237 |         totalPages: z.number().int(),
238 |       }),
239 |       rateLimit: {
240 |         windowMs: 60 * 1000, // 1 minute
241 |         maxRequests: 30, // 30 requests per minute
242 |       },
243 |     }),
244 |   );
245 | };
246 | 
```

--------------------------------------------------------------------------------
/scripts/fetch-openapi-spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * @fileoverview Fetches an OpenAPI specification (YAML/JSON) from a URL,
  5 |  * parses it, and saves it locally in both YAML and JSON formats.
  6 |  * @module scripts/fetch-openapi-spec
  7 |  *   Includes fallback logic for common OpenAPI file names (openapi.yaml, openapi.json).
  8 |  *   Ensures output paths are within the project directory for security.
  9 |  *
 10 |  * @example
 11 |  * // Fetch spec and save to docs/api/my_api.yaml and docs/api/my_api.json
 12 |  * // ts-node --esm scripts/fetch-openapi-spec.ts https://api.example.com/v1 docs/api/my_api
 13 |  *
 14 |  * @example
 15 |  * // Fetch spec from a direct file URL
 16 |  * // ts-node --esm scripts/fetch-openapi-spec.ts https://petstore3.swagger.io/api/v3/openapi.json docs/api/petstore_v3
 17 |  */
 18 | 
 19 | import axios, { AxiosError } from "axios";
 20 | import fs from "fs/promises";
 21 | import yaml from "js-yaml";
 22 | import path from "path";
 23 | 
 24 | const projectRoot = process.cwd();
 25 | 
 26 | const args = process.argv.slice(2);
 27 | const helpFlag = args.includes("--help");
 28 | const urlArg = args[0];
 29 | const outputBaseArg = args[1];
 30 | 
 31 | if (helpFlag || !urlArg || !outputBaseArg) {
 32 |   console.log(`
 33 | Fetch OpenAPI Specification Script
 34 | 
 35 | Usage:
 36 |   ts-node --esm scripts/fetch-openapi-spec.ts <url> <output-base-path> [--help]
 37 | 
 38 | Arguments:
 39 |   <url>                Base URL or direct URL to the OpenAPI spec (YAML/JSON).
 40 |   <output-base-path>   Base path for output files (relative to project root),
 41 |                        e.g., 'docs/api/my_api'. Will generate .yaml and .json.
 42 |   --help               Show this help message.
 43 | 
 44 | Example:
 45 |   ts-node --esm scripts/fetch-openapi-spec.ts https://petstore3.swagger.io/api/v3 docs/api/petstore_v3
 46 | `);
 47 |   process.exit(helpFlag ? 0 : 1);
 48 | }
 49 | 
 50 | const outputBasePathAbsolute = path.resolve(projectRoot, outputBaseArg);
 51 | const yamlOutputPath = `${outputBasePathAbsolute}.yaml`;
 52 | const jsonOutputPath = `${outputBasePathAbsolute}.json`;
 53 | const outputDirAbsolute = path.dirname(outputBasePathAbsolute);
 54 | 
 55 | // Security Check: Ensure output paths are within project root
 56 | if (
 57 |   !outputDirAbsolute.startsWith(projectRoot + path.sep) ||
 58 |   !yamlOutputPath.startsWith(projectRoot + path.sep) ||
 59 |   !jsonOutputPath.startsWith(projectRoot + path.sep)
 60 | ) {
 61 |   console.error(
 62 |     `Error: Output path "${outputBaseArg}" resolves outside the project directory. Aborting.`,
 63 |   );
 64 |   process.exit(1);
 65 | }
 66 | 
 67 | /**
 68 |  * Attempts to fetch content from a given URL.
 69 |  * @param url - The URL to fetch data from.
 70 |  * @returns A promise resolving to an object with data and content type, or null if fetch fails.
 71 |  */
 72 | async function tryFetch(
 73 |   url: string,
 74 | ): Promise<{ data: string; contentType: string | null } | null> {
 75 |   try {
 76 |     console.log(`Attempting to fetch from: ${url}`);
 77 |     const response = await axios.get(url, {
 78 |       responseType: "text",
 79 |       validateStatus: (status) => status >= 200 && status < 300,
 80 |     });
 81 |     const contentType = response.headers["content-type"] || null;
 82 |     console.log(
 83 |       `Successfully fetched (Status: ${response.status}, Content-Type: ${contentType || "N/A"})`,
 84 |     );
 85 |     return { data: response.data, contentType };
 86 |   } catch (error) {
 87 |     let status = "Unknown";
 88 |     if (axios.isAxiosError(error)) {
 89 |       const axiosError = error as AxiosError;
 90 |       status = axiosError.response
 91 |         ? String(axiosError.response.status)
 92 |         : "Network Error";
 93 |     }
 94 |     console.warn(`Failed to fetch from ${url} (Status: ${status})`);
 95 |     return null;
 96 |   }
 97 | }
 98 | 
 99 | /**
100 |  * Parses fetched data as YAML or JSON, attempting to infer from content type or by trying both.
101 |  * @param data - The raw string data fetched from the URL.
102 |  * @param contentType - The content type header from the HTTP response, if available.
103 |  * @returns The parsed OpenAPI specification as an object, or null if parsing fails.
104 |  */
105 | function parseSpec(data: string, contentType: string | null): object | null {
106 |   try {
107 |     const lowerContentType = contentType?.toLowerCase();
108 |     if (
109 |       lowerContentType?.includes("yaml") ||
110 |       lowerContentType?.includes("yml")
111 |     ) {
112 |       console.log("Parsing content as YAML based on Content-Type...");
113 |       return yaml.load(data) as object;
114 |     } else if (lowerContentType?.includes("json")) {
115 |       console.log("Parsing content as JSON based on Content-Type...");
116 |       return JSON.parse(data);
117 |     } else {
118 |       console.log(
119 |         "Content-Type is ambiguous or missing. Attempting to parse as YAML first...",
120 |       );
121 |       try {
122 |         const parsedYaml = yaml.load(data) as object;
123 |         // Basic validation: check if it's a non-null object.
124 |         if (parsedYaml && typeof parsedYaml === "object") {
125 |           console.log("Successfully parsed as YAML.");
126 |           return parsedYaml;
127 |         }
128 |       } catch (yamlError) {
129 |         console.log("YAML parsing failed. Attempting to parse as JSON...");
130 |         try {
131 |           const parsedJson = JSON.parse(data);
132 |           if (parsedJson && typeof parsedJson === "object") {
133 |             console.log("Successfully parsed as JSON.");
134 |             return parsedJson;
135 |           }
136 |         } catch (jsonError) {
137 |           console.warn(
138 |             "Could not parse content as YAML or JSON after attempting both.",
139 |           );
140 |           return null;
141 |         }
142 |       }
143 |       // If YAML parsing resulted in a non-object (e.g. string, number) but didn't throw
144 |       console.warn(
145 |         "Content parsed as YAML but was not a valid object structure. Trying JSON.",
146 |       );
147 |       try {
148 |         const parsedJson = JSON.parse(data);
149 |         if (parsedJson && typeof parsedJson === "object") {
150 |           console.log(
151 |             "Successfully parsed as JSON on second attempt for non-object YAML.",
152 |           );
153 |           return parsedJson;
154 |         }
155 |       } catch (jsonError) {
156 |         console.warn(
157 |           "Could not parse content as YAML or JSON after attempting both.",
158 |         );
159 |         return null;
160 |       }
161 |     }
162 |   } catch (parseError) {
163 |     console.error(
164 |       `Error parsing specification: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
165 |     );
166 |   }
167 |   return null;
168 | }
169 | 
170 | /**
171 |  * Main orchestrator function. Fetches the OpenAPI spec from the provided URL (with fallbacks),
172 |  * parses it, and saves it to the specified output paths in both YAML and JSON formats.
173 |  */
174 | async function fetchAndProcessSpec(): Promise<void> {
175 |   let fetchedResult: { data: string; contentType: string | null } | null = null;
176 |   const potentialUrls: string[] = [urlArg];
177 | 
178 |   if (
179 |     !urlArg.endsWith(".yaml") &&
180 |     !urlArg.endsWith(".yml") &&
181 |     !urlArg.endsWith(".json")
182 |   ) {
183 |     const urlWithoutTrailingSlash = urlArg.endsWith("/")
184 |       ? urlArg.slice(0, -1)
185 |       : urlArg;
186 |     potentialUrls.push(`${urlWithoutTrailingSlash}/openapi.yaml`);
187 |     potentialUrls.push(`${urlWithoutTrailingSlash}/openapi.json`);
188 |   }
189 | 
190 |   for (const url of potentialUrls) {
191 |     fetchedResult = await tryFetch(url);
192 |     if (fetchedResult) break;
193 |   }
194 | 
195 |   if (!fetchedResult) {
196 |     console.error(
197 |       `Error: Failed to fetch specification from all attempted URLs: ${potentialUrls.join(", ")}. Aborting.`,
198 |     );
199 |     process.exit(1);
200 |   }
201 | 
202 |   const openapiSpec = parseSpec(fetchedResult.data, fetchedResult.contentType);
203 | 
204 |   if (!openapiSpec || typeof openapiSpec !== "object") {
205 |     console.error(
206 |       "Error: Failed to parse specification content or content is not a valid object. Aborting.",
207 |     );
208 |     process.exit(1);
209 |   }
210 | 
211 |   try {
212 |     await fs.access(outputDirAbsolute);
213 |   } catch (error: any) {
214 |     if (error.code === "ENOENT") {
215 |       console.log(`Output directory not found. Creating: ${outputDirAbsolute}`);
216 |       await fs.mkdir(outputDirAbsolute, { recursive: true });
217 |     } else {
218 |       console.error(
219 |         `Error accessing output directory ${outputDirAbsolute}: ${error.message}. Aborting.`,
220 |       );
221 |       process.exit(1);
222 |     }
223 |   }
224 | 
225 |   try {
226 |     console.log(`Saving YAML specification to: ${yamlOutputPath}`);
227 |     await fs.writeFile(yamlOutputPath, yaml.dump(openapiSpec), "utf8");
228 |     console.log(`Successfully saved YAML specification.`);
229 |   } catch (error) {
230 |     console.error(
231 |       `Error saving YAML to ${yamlOutputPath}: ${error instanceof Error ? error.message : String(error)}. Aborting.`,
232 |     );
233 |     process.exit(1);
234 |   }
235 | 
236 |   try {
237 |     console.log(`Saving JSON specification to: ${jsonOutputPath}`);
238 |     await fs.writeFile(
239 |       jsonOutputPath,
240 |       JSON.stringify(openapiSpec, null, 2),
241 |       "utf8",
242 |     );
243 |     console.log(`Successfully saved JSON specification.`);
244 |   } catch (error) {
245 |     console.error(
246 |       `Error saving JSON to ${jsonOutputPath}: ${error instanceof Error ? error.message : String(error)}. Aborting.`,
247 |     );
248 |     process.exit(1);
249 |   }
250 | 
251 |   console.log("OpenAPI specification processed and saved successfully.");
252 | }
253 | 
254 | fetchAndProcessSpec();
255 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_project_update/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { ProjectStatus, createProjectStatusEnum } from "../../../types/mcp.js";
  4 | import {
  5 |   createToolExample,
  6 |   createToolMetadata,
  7 |   registerTool,
  8 | } from "../../../types/tool.js";
  9 | import { atlasUpdateProject } from "./updateProject.js";
 10 | import { AtlasProjectUpdateSchemaShape } from "./types.js";
 11 | 
 12 | export const registerAtlasProjectUpdateTool = (server: McpServer) => {
 13 |   registerTool(
 14 |     server,
 15 |     "atlas_project_update",
 16 |     "Modifies attributes of existing project entities within the system with support for both targeted single updates and efficient bulk modifications",
 17 |     AtlasProjectUpdateSchemaShape,
 18 |     atlasUpdateProject,
 19 |     createToolMetadata({
 20 |       examples: [
 21 |         createToolExample(
 22 |           {
 23 |             mode: "single",
 24 |             id: "proj_ms_migration",
 25 |             updates: {
 26 |               name: "Microservice Architecture Migration - Phase 2",
 27 |               description:
 28 |                 "Extended refactoring to include data migration layer and enhanced service discovery through etcd integration",
 29 |               status: "in-progress",
 30 |             },
 31 |           },
 32 |           `{
 33 |             "id": "proj_ms_migration",
 34 |             "name": "Microservice Architecture Migration - Phase 2",
 35 |             "description": "Extended refactoring to include data migration layer and enhanced service discovery through etcd integration",
 36 |             "status": "in-progress",
 37 |             "urls": [
 38 |               {"title": "MCP Server Repository", "url": "https://github.com/cyanheads/atlas-mcp-server.git"},
 39 |               {"title": "Technical Spec", "url": "file:///Users/username/project_name/docs/atlas-reference.md"},
 40 |               {"title": "MCP Docs", "url": "https://modelcontextprotocol.io/"}
 41 |             ],
 42 |             "completionRequirements": "All critical services migrated with 100% test coverage, performance metrics meeting SLAs, and zero regressions in core functionality",
 43 |             "outputFormat": "Containerized services with CI/CD pipelines, comprehensive API documentation, and migration runbook",
 44 |             "taskType": "integration",
 45 |             "createdAt": "2025-03-23T10:11:24.123Z",
 46 |             "updatedAt": "2025-03-23T10:12:34.456Z"
 47 |           }`,
 48 |           "Update project scope and phase for an ongoing engineering initiative",
 49 |         ),
 50 |         createToolExample(
 51 |           {
 52 |             mode: "bulk",
 53 |             projects: [
 54 |               {
 55 |                 id: "proj_graphql",
 56 |                 updates: {
 57 |                   status: "completed",
 58 |                   completionRequirements:
 59 |                     "API supports all current use cases with n+1 query optimization, proper error handling, and 95% test coverage with performance benchmarks showing 30% reduction in API request times",
 60 |                 },
 61 |               },
 62 |               {
 63 |                 id: "proj_perf",
 64 |                 updates: {
 65 |                   status: "in-progress",
 66 |                   description:
 67 |                     "Extended performance analysis to include bundle size optimization, lazy-loading routes, and server-side rendering for critical pages",
 68 |                   urls: [
 69 |                     {
 70 |                       title: "Lighthouse CI Results",
 71 |                       url: "https://lighthouse-ci.app/dashboard?project=frontend-perf",
 72 |                     },
 73 |                     {
 74 |                       title: "Web Vitals Tracking",
 75 |                       url: "https://analytics.google.com/web-vitals",
 76 |                     },
 77 |                   ],
 78 |                 },
 79 |               },
 80 |             ],
 81 |           },
 82 |           `{
 83 |             "success": true,
 84 |             "message": "Successfully updated 2 projects",
 85 |             "updated": [
 86 |               {
 87 |                 "id": "proj_graphql",
 88 |                 "name": "GraphQL API Implementation",
 89 |                 "description": "Design and implement GraphQL API layer to replace existing REST endpoints with optimized query capabilities",
 90 |                 "status": "completed",
 91 |                 "urls": [],
 92 |                 "completionRequirements": "API supports all current use cases with n+1 query optimization, proper error handling, and 95% test coverage with performance benchmarks showing 30% reduction in API request times",
 93 |                 "outputFormat": "TypeScript-based GraphQL schema with resolvers, documentation, and integration tests",
 94 |                 "taskType": "generation",
 95 |                 "createdAt": "2025-03-23T10:11:24.123Z",
 96 |                 "updatedAt": "2025-03-23T10:12:34.456Z"
 97 |               },
 98 |               {
 99 |                 "id": "proj_perf",
100 |                 "name": "Performance Optimization Suite",
101 |                 "description": "Extended performance analysis to include bundle size optimization, lazy-loading routes, and server-side rendering for critical pages",
102 |                 "status": "in-progress",
103 |                 "urls": [
104 |                   {"title": "Lighthouse CI Results", "url": "https://lighthouse-ci.app/dashboard?project=frontend-perf"},
105 |                   {"title": "Web Vitals Tracking", "url": "https://analytics.google.com/web-vitals"}
106 |                 ],
107 |                 "completionRequirements": "Core React components meet Web Vitals thresholds with 50% reduction in LCP and TTI metrics",
108 |                 "outputFormat": "Optimized component library, performance test suite, and technical recommendation document",
109 |                 "taskType": "analysis",
110 |                 "createdAt": "2025-03-23T10:11:24.456Z",
111 |                 "updatedAt": "2025-03-23T10:12:34.789Z"
112 |               }
113 |             ],
114 |             "errors": []
115 |           }`,
116 |           "Synchronize project statuses across dependent engineering initiatives",
117 |         ),
118 |       ],
119 |       requiredPermission: "project:update",
120 |       returnSchema: z.union([
121 |         // Single project response
122 |         z.object({
123 |           id: z.string().describe("Project ID"),
124 |           name: z.string().describe("Project name"),
125 |           description: z.string().describe("Project description"),
126 |           status: createProjectStatusEnum().describe("Project status"),
127 |           urls: z
128 |             .array(
129 |               z.object({
130 |                 title: z.string(),
131 |                 url: z.string(),
132 |               }),
133 |             )
134 |             .describe("Reference materials"),
135 |           completionRequirements: z.string().describe("Completion criteria"),
136 |           outputFormat: z.string().describe("Deliverable format"),
137 |           taskType: z.string().describe("Project classification"),
138 |           createdAt: z.string().describe("Creation timestamp"),
139 |           updatedAt: z.string().describe("Last update timestamp"),
140 |         }),
141 |         // Bulk update response
142 |         z.object({
143 |           success: z.boolean().describe("Operation success status"),
144 |           message: z.string().describe("Result message"),
145 |           updated: z
146 |             .array(
147 |               z.object({
148 |                 id: z.string().describe("Project ID"),
149 |                 name: z.string().describe("Project name"),
150 |                 description: z.string().describe("Project description"),
151 |                 status: createProjectStatusEnum().describe("Project status"),
152 |                 urls: z
153 |                   .array(
154 |                     z.object({
155 |                       title: z.string(),
156 |                       url: z.string(),
157 |                     }),
158 |                   )
159 |                   .describe("Reference materials"),
160 |                 completionRequirements: z
161 |                   .string()
162 |                   .describe("Completion criteria"),
163 |                 outputFormat: z.string().describe("Deliverable format"),
164 |                 taskType: z.string().describe("Project classification"),
165 |                 createdAt: z.string().describe("Creation timestamp"),
166 |                 updatedAt: z.string().describe("Last update timestamp"),
167 |               }),
168 |             )
169 |             .describe("Updated projects"),
170 |           errors: z
171 |             .array(
172 |               z.object({
173 |                 index: z.number().describe("Index in the projects array"),
174 |                 project: z.any().describe("Original project update data"),
175 |                 error: z
176 |                   .object({
177 |                     code: z.string().describe("Error code"),
178 |                     message: z.string().describe("Error message"),
179 |                     details: z
180 |                       .any()
181 |                       .optional()
182 |                       .describe("Additional error details"),
183 |                   })
184 |                   .describe("Error information"),
185 |               }),
186 |             )
187 |             .describe("Update errors"),
188 |         }),
189 |       ]),
190 |       rateLimit: {
191 |         windowMs: 60 * 1000, // 1 minute
192 |         maxRequests: 15, // 15 requests per minute (either single or bulk)
193 |       },
194 |     }),
195 |   );
196 | };
197 | 
```

--------------------------------------------------------------------------------
/src/webui/styling/components.css:
--------------------------------------------------------------------------------

```css
  1 | /* ==========================================================================
  2 |    Component Styles: Buttons, Selects, Cards, Toggles etc.
  3 |    ========================================================================== */
  4 | select,
  5 | button {
  6 |   padding: var(--spacing-sm) var(--spacing-md);
  7 |   border-radius: var(--border-radius-md);
  8 |   border: 1px solid var(--border-color);
  9 |   font-size: 1rem;
 10 |   background-color: var(--card-bg-color); /* Match card for consistency */
 11 |   color: var(--text-color);
 12 |   transition:
 13 |     background-color 0.15s ease-out,
 14 |     border-color 0.15s ease-out,
 15 |     box-shadow 0.15s ease-out;
 16 | }
 17 | 
 18 | select:focus,
 19 | button:focus {
 20 |   outline: none;
 21 |   border-color: var(--primary-color);
 22 |   box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); /* Standard Bootstrap-like focus ring */
 23 | }
 24 | /* For dark mode, ensure focus ring is visible */
 25 | .dark-mode select:focus,
 26 | .dark-mode button:focus {
 27 |   box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.35);
 28 | }
 29 | 
 30 | select {
 31 |   flex-grow: 1;
 32 |   min-width: 200px; /* Ensure select has a minimum width */
 33 | }
 34 | 
 35 | button {
 36 |   background-color: var(--primary-color);
 37 |   color: var(--button-text-color);
 38 |   cursor: pointer;
 39 |   font-weight: 500;
 40 |   border-color: var(--primary-color);
 41 | }
 42 | 
 43 | button:hover {
 44 |   background-color: var(--primary-hover-color);
 45 |   border-color: var(--primary-hover-color);
 46 | }
 47 | 
 48 | button#refresh-button {
 49 |   background-color: var(--button-secondary-bg);
 50 |   border-color: var(--button-secondary-bg);
 51 |   color: var(--button-text-color);
 52 | }
 53 | button#refresh-button:hover {
 54 |   background-color: var(--button-secondary-hover-bg);
 55 |   border-color: var(--button-secondary-hover-bg);
 56 | }
 57 | 
 58 | .view-controls {
 59 |   display: flex;
 60 |   gap: var(--spacing-sm);
 61 | }
 62 | 
 63 | .view-controls .view-toggle-button {
 64 |   padding: var(--spacing-xs) var(--spacing-sm);
 65 |   font-size: 0.85rem;
 66 |   background-color: transparent;
 67 |   color: var(--primary-color);
 68 |   border: 1px solid var(--primary-color);
 69 | }
 70 | .view-controls .view-toggle-button:hover,
 71 | .view-controls .view-toggle-button:focus {
 72 |   background-color: rgba(
 73 |     0,
 74 |     123,
 75 |     255,
 76 |     0.1
 77 |   ); /* Use primary color with alpha for hover */
 78 | }
 79 | .dark-mode .view-controls .view-toggle-button:hover,
 80 | .dark-mode .view-controls .view-toggle-button:focus {
 81 |   background-color: rgba(
 82 |     0,
 83 |     123,
 84 |     255,
 85 |     0.2
 86 |   ); /* Slightly more opaque for dark mode */
 87 | }
 88 | 
 89 | /* Compact View for Tasks/Knowledge */
 90 | .data-item.compact {
 91 |   padding: var(--spacing-sm) 0;
 92 |   display: flex;
 93 |   justify-content: space-between;
 94 |   align-items: center;
 95 | }
 96 | .data-item.compact strong {
 97 |   margin-bottom: 0;
 98 |   font-weight: 500;
 99 | }
100 | .data-item.compact .item-status {
101 |   font-size: 0.85rem;
102 |   color: var(--secondary-text-color);
103 |   background-color: var(--code-bg-color);
104 |   padding: 2px 6px;
105 |   border-radius: var(--border-radius-sm);
106 | }
107 | /* Hide detailed elements in compact mode */
108 | .data-item.compact pre, 
109 | .data-item.compact div:not(:first-child), /* Hides divs other than the one containing the title/status */
110 | .data-item.compact ul {
111 |   display: none;
112 | }
113 | 
114 | .task-card {
115 |   background-color: var(--card-bg-color);
116 |   padding: var(--spacing-sm);
117 |   border-radius: var(--border-radius-sm);
118 |   border: 1px solid var(--data-item-border-color);
119 |   box-shadow: 0 2px 4px var(--shadow-color);
120 |   cursor: pointer; /* If tasks are clickable for details */
121 |   transition:
122 |     box-shadow 0.15s ease-out,
123 |     transform 0.15s ease-out;
124 | }
125 | 
126 | .task-card:hover {
127 |   box-shadow: 0 4px 8px var(--shadow-color);
128 |   transform: translateY(-2px);
129 | }
130 | 
131 | .task-card-title {
132 |   font-weight: 600;
133 |   margin-bottom: var(--spacing-xs);
134 |   color: var(--text-color);
135 | }
136 | 
137 | .task-card-priority,
138 | .task-card-assignee {
139 |   font-size: 0.8rem;
140 |   color: var(--secondary-text-color);
141 |   margin-top: var(--spacing-xs);
142 | }
143 | 
144 | .empty-column {
145 |   text-align: center;
146 |   color: var(--secondary-text-color);
147 |   font-style: italic;
148 |   padding: var(--spacing-md) 0;
149 | }
150 | 
151 | .explorer-node-item {
152 |   padding: var(--spacing-sm);
153 |   border-bottom: 1px solid var(--data-item-border-color);
154 |   cursor: pointer;
155 |   transition: background-color 0.15s ease-out;
156 | }
157 | 
158 | .explorer-node-item:last-child {
159 |   border-bottom: none;
160 | }
161 | 
162 | .explorer-node-item:hover {
163 |   background-color: var(--bg-color); /* Subtle hover effect */
164 | }
165 | .dark-mode .explorer-node-item:hover {
166 |   background-color: var(--border-color); /* Darker hover for dark mode */
167 | }
168 | 
169 | /* ==========================================================================
170 |    Status, Error, Loading Messages
171 |    ========================================================================== */
172 | .error {
173 |   /* For #error-message div */
174 |   color: var(--error-color);
175 |   font-weight: 500;
176 |   padding: var(--spacing-sm) var(--spacing-md);
177 |   background-color: var(--error-bg-color);
178 |   border: 1px solid var(--error-border-color);
179 |   border-radius: var(--border-radius-sm);
180 |   margin-top: var(--spacing-md);
181 |   transition:
182 |     background-color 0.2s ease-out,
183 |     border-color 0.2s ease-out,
184 |     color 0.2s ease-out;
185 | }
186 | 
187 | .loading {
188 |   /* For loading text within containers */
189 |   font-style: italic;
190 |   color: var(--secondary-text-color);
191 |   padding: var(--spacing-md) 0;
192 |   text-align: center;
193 | }
194 | 
195 | #connection-status {
196 |   margin-top: var(
197 |     --spacing-lg
198 |   ); /* Ensure it's below error message if both visible */
199 |   padding: var(--spacing-sm) var(--spacing-md);
200 |   background-color: var(--connection-status-bg);
201 |   border-radius: var(--border-radius-sm);
202 |   text-align: center;
203 |   font-weight: 500;
204 |   color: var(--text-color);
205 |   transition:
206 |     background-color 0.2s ease-out,
207 |     color 0.2s ease-out;
208 | }
209 | #connection-status span {
210 |   /* The actual status text, e.g., "Connected" */
211 |   font-weight: 600;
212 | }
213 | 
214 | /* ==========================================================================
215 |    Utility Classes
216 |    ========================================================================== */
217 | .hidden {
218 |   display: none !important;
219 | }
220 | 
221 | /* ==========================================================================
222 |    Theme Toggle Switch
223 |    ========================================================================== */
224 | .theme-switch-wrapper {
225 |   display: flex;
226 |   align-items: center;
227 |   position: absolute;
228 |   top: var(--spacing-md);
229 |   right: var(--spacing-md);
230 |   gap: var(--spacing-sm);
231 | }
232 | .theme-label {
233 |   font-size: 0.8rem;
234 |   color: var(--secondary-text-color);
235 |   cursor: pointer;
236 | }
237 | 
238 | .theme-switch {
239 |   /* The label acting as a container */
240 |   display: inline-block;
241 |   height: 20px; /* Smaller toggle */
242 |   position: relative;
243 |   width: 40px; /* Smaller toggle */
244 | }
245 | .theme-switch input {
246 |   display: none;
247 | } /* Hide actual checkbox */
248 | 
249 | .slider {
250 |   /* The visual track of the switch */
251 |   background-color: #ccc; /* Default off state */
252 |   position: absolute;
253 |   cursor: pointer;
254 |   top: 0;
255 |   left: 0;
256 |   right: 0;
257 |   bottom: 0;
258 |   transition: 0.3s;
259 |   border-radius: 20px; /* Fully rounded ends */
260 | }
261 | .slider:before {
262 |   /* The circular handle */
263 |   background-color: #fff;
264 |   position: absolute;
265 |   content: "";
266 |   height: 14px; /* Smaller handle */
267 |   width: 14px; /* Smaller handle */
268 |   left: 3px; /* Padding from left edge */
269 |   bottom: 3px; /* Padding from bottom edge */
270 |   transition: 0.3s;
271 |   border-radius: 50%; /* Circular */
272 |   box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
273 | }
274 | 
275 | input:checked + .slider {
276 |   background-color: var(--primary-color);
277 | } /* "On" state color */
278 | input:checked + .slider:before {
279 |   transform: translateX(20px);
280 | } /* Move handle to the right */
281 | 
282 | /* ==========================================================================
283 |    Responsive Adjustments
284 |    ========================================================================== */
285 | @media (max-width: 768px) {
286 |   body {
287 |     font-size: 15px;
288 |   }
289 |   #app {
290 |     margin: var(--spacing-md);
291 |     padding: var(--spacing-md);
292 |   }
293 |   h1 {
294 |     font-size: 1.8rem;
295 |   }
296 |   h2 {
297 |     font-size: 1.5rem;
298 |   }
299 |   h3 {
300 |     font-size: 1.2rem;
301 |   }
302 | 
303 |   .controls-container {
304 |     flex-direction: column;
305 |     align-items: stretch; /* Make controls full width */
306 |   }
307 |   .controls-container select,
308 |   .controls-container button {
309 |     width: 100%;
310 |     margin-bottom: var(--spacing-sm); /* Consistent spacing when stacked */
311 |   }
312 |   .controls-container button:last-child {
313 |     margin-bottom: 0;
314 |   }
315 | 
316 |   .section-header {
317 |     flex-direction: column;
318 |     align-items: flex-start; /* Align header content to the start */
319 |   }
320 |   .view-controls {
321 |     width: 100%;
322 |     justify-content: flex-start; /* Align toggles to start */
323 |   }
324 | 
325 |   .theme-switch-wrapper {
326 |     top: var(--spacing-sm);
327 |     right: var(--spacing-sm);
328 |   }
329 |   .theme-label {
330 |     display: none; /* Hide "Toggle Theme" text on small screens to save space */
331 |   }
332 | 
333 |   #details-content.details-grid {
334 |     grid-template-columns: 1fr; /* Stack labels and values */
335 |   }
336 |   #details-content.details-grid > .data-item > strong {
337 |     /* Label */
338 |     margin-bottom: var(
339 |       --spacing-xs
340 |     ); /* Space between label and value when stacked */
341 |     grid-column: 1; /* Ensure it stays in the first column */
342 |   }
343 |   #details-content.details-grid > .data-item > div,
344 |   #details-content.details-grid > .data-item > pre,
345 |   #details-content.details-grid > .data-item > ul {
346 |     /* Value */
347 |     grid-column: 1; /* Ensure it stays in the first column */
348 |   }
349 | }
350 | 
```
Page 3/8FirstPrevNextLast