This is page 5 of 8. Use http://codebase.md/cyanheads/atlas-mcp-server?format=md&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

--------------------------------------------------------------------------------
/examples/deep-research-example/covington_community_grant_research.md:
--------------------------------------------------------------------------------

```markdown
  1 | Prompt:
  2 | 
  3 | # Grant Research Task: Covington, KY Community Projects
  4 | 
  5 | Conduct comprehensive research on publicly available grants for solo-owned community projects in Covington, Kentucky using atlas_deep_research to gather information. Focus on funding opportunities specifically suitable for individual owners of community gardens and similar local initiatives. Identify grant programs from government sources (federal, state, and local), private foundations, and community organizations that support urban agriculture and neighborhood improvement projects.
  6 | 
  7 | Research should prioritize:
  8 | 
  9 | - Current open application periods and upcoming deadlines
 10 | - Grant amounts and funding cycles
 11 | - Eligibility requirements specific to individual applicants rather than organizations
 12 | - Any special programs targeting the Covington area or Northern Kentucky region
 13 | - Application processes and documentation requirements
 14 | - Past successful community garden projects in the area
 15 | 
 16 | Include information on matching grant opportunities and any technical assistance programs available to support the application process. Perform extensive research and provide a detailed report on your findings in /Users/casey/Developer/github/atlas-mcp-agent/workspace.
 17 | 
 18 | Use the perplexity search tool for LLM augmented searching (search-augmented-generation); use the firecrawl tools for web scraping when necessary. Keep the atlas db completely up to date with all details.
 19 | 
 20 | Perform extensive research. Include specific details about the grants. Include details on how to apply for the grants.
 21 | 
 22 | Start by clearing the db.
 23 | 
 24 | ---
 25 | 
 26 | # Grant Research Report: Solo-Owned Community Projects in Covington, KY
 27 | 
 28 | **Date:** April 1, 2025
 29 | **Project:** Grant Research: Covington, KY Community Projects (ID: `proj_a67039840514408386ec549c53668a8b`)
 30 | 
 31 | ## Executive Summary
 32 | 
 33 | This report details findings on grant opportunities for individuals seeking to start or manage community projects, such as community gardens, in Covington, Kentucky. Research indicates that **direct grant funding for solo individuals is limited**. Most federal, state, and foundation grants require applicants to be registered non-profits, government entities, schools, or tribes.
 34 | 
 35 | However, several viable pathways exist, primarily through **collaboration and partnership**:
 36 | 
 37 | 1.  **Forming Informal Groups:** Applying as a small, resident-led group (even informal ones) is often accepted, particularly for local programs like the Covington Neighborhood Grant.
 38 | 2.  **Partnering with Existing Organizations:** Collaborating with established non-profits (like The Center for Great Neighborhoods) or schools can provide access to grants requiring organizational status.
 39 | 3.  **Fiscal Sponsorship:** Utilizing a fiscal sponsor allows individuals to apply for grants under the umbrella of a registered non-profit.
 40 | 
 41 | The most promising local opportunity is the **Covington Neighborhood Grant Program**, facilitated by The Center for Great Neighborhoods, which supports resident-led group projects. Technical assistance for grant writing is available through various state and local organizations.
 42 | 
 43 | ## Key Grant Opportunities & Programs
 44 | 
 45 | ### 1. Covington Neighborhood Grant Program
 46 | 
 47 | - **Funding:** $1,400 - $5,000 per project.
 48 | - **Eligibility:** Resident-led groups (formal associations, emerging groups, or informal neighbor collaborations). Individuals must partner or form a group.
 49 | - **Focus:** Neighborhood beautification, blight removal, community events, public space improvements (including gardens, landscaping).
 50 | - **Application:** Process involves meeting with The Center for Great Neighborhoods (CfGN) for guidance, submitting a detailed application/budget. CfGN assists applicants.
 51 | - **Deadline:** FY25 cycle closed Sept 30, 2024. Future cycles TBA.
 52 | - **Source/Contact:**
 53 |   - The Center for Great Neighborhoods (CfGN): [https://www.greatneighborhoods.org](https://www.greatneighborhoods.org)
 54 |   - Program Info: Often via City of Covington documents or direct contact with CfGN.
 55 |   - CfGN Contact: Cate Douglas, cate@greatneighborhoods.org, (859) 547-5550.
 56 | - _(Atlas Sources: know_a62b36b9..., know_46e966ad..., know_194abba6..., know_ddae3b81f..., know_1d2b459a..., know_8a77ae38..., know_cdaf9b90...)_
 57 | 
 58 | ### 2. Kentucky State University Small-Scale Farm Grant Program
 59 | 
 60 | - **Funding:** Up to $5,000 per household.
 61 | - **Eligibility:** Small-scale farms (<$250k annual sales), potentially including urban farms. Individual farmers may be eligible. Priority for limited-resource, women, minority farmers.
 62 | - **Focus:** Improving farm operations, value-added enterprises, organic/specialty crops, agroforestry.
 63 | - **Application:** Submit detailed farm improvement plan & budget via email/mail.
 64 | - **Deadline:** Previous cycle closed Dec 1, 2024. Future cycles TBA.
 65 | - **Source/Contact:**
 66 |   - Official Program Page: [https://www.kysu.edu/academics/college-ahnr/school-of-anr/co-op/small-scale-farm-grant-program.php](https://www.kysu.edu/academics/college-ahnr/school-of-anr/co-op/small-scale-farm-grant-program.php)
 67 |   - Contact/Submission: M. Allison Noel, allison.noel@kysu.edu, Kentucky State University, 400 East Main Street, CEB-218, Frankfort, KY 40601.
 68 | - _(Atlas Sources: know_46e966ad..., know_3005a083..., know_0dc8d825...)_
 69 | 
 70 | ### 3. GrantWatch Listings (Potential Direct Individual Grants)
 71 | 
 72 | - **GrantWatch Website:** [https://www.grantwatch.com](https://www.grantwatch.com)
 73 | - **Urban Gardeners Grant:** Up to $5k potentially for individuals/orgs (for-profit focus). Verification needed via GrantWatch.
 74 | - **KY Agricultural Producer Grant:** Up to $5k potentially for specific producer categories (BIPOC, female, etc.). Verification needed via GrantWatch.
 75 | - **Application/Deadline:** Recurring/unspecified. Check GrantWatch.com for current status and specific application details.
 76 | - _(Atlas Sources: know_8f2cb3e98..., know_10e0f081..., know_4c3d6e43...)_
 77 | 
 78 | ### 4. USDA Repair Loans & Grants (Indirect Support)
 79 | 
 80 | - **Funding:** Loans up to $40k; Grants up to $10k (for elderly 62+ removing hazards).
 81 | - **Eligibility:** Low-income homeowners (individuals).
 82 | - **Focus:** Home repairs/modernization, hazard removal. Not garden-specific but could fund related property repairs. Check USDA local office for details.
 83 | - _(Atlas Sources: know_194abba6...)_
 84 | 
 85 | ### 5. USDA Farm Service Agency (FSA) Loans (Not Grants)
 86 | 
 87 | - **Funding:** Direct Operating Loans & Microloans (up to $50k).
 88 | - **Eligibility:** Individual small-scale urban farmers/gardeners.
 89 | - **Focus:** Startup costs (equipment, seeds, etc.). Requires repayment. Check USDA FSA local office for details.
 90 | - _(Atlas Sources: know_a62b36b9...)_
 91 | 
 92 | ### 6. Pure Farmland Grants / Alternatives
 93 | 
 94 | - **Pure Farmland:** Specific grant not found in recent searches; may be inactive or misnamed. Past grants ($1k+) supported Covington gardens (e.g., Redden Gardens). Check for recurrence.
 95 | - **Alternatives:**
 96 |   - GroMoreGood Grassroots Grant (Scotts): $1.5k, youth focus, Jan 30 deadline. Check Scotts Miracle-Gro Foundation website.
 97 |   - Columbus Foundation Grants: $1.5k, Central OH focus, Mar 21 deadline. Check geographic eligibility.
 98 | - _(Atlas Sources: know_1d2b459a..., know_ed478734...)_
 99 | 
100 | ### 7. Other Federal/State/Foundation Grants (Partnership Required)
101 | 
102 | - **Federal (USDA Urban Ag, People's Garden, Community Facilities):** Primarily target organizations. Individuals need to partner. Check specific USDA program websites.
103 | - **State (KY Urban Forestry):** Requires partnership with non-profits/local govt. Check KY Division of Forestry.
104 | - **Foundations (Horizon Community Funds, Greater Cincinnati Foundation, National Garden Grants):** Fund non-profits or schools. Individuals need to partner or use fiscal sponsors. Check foundation websites.
105 | - _(Atlas Sources: know_33bca161..., know_490fcce4..., know_ddae3b81f..., know_214bee7f...)_
106 | 
107 | ## Matching Grant Opportunities
108 | 
109 | - Direct matching grants for individuals are unlikely.
110 | - The **Kentucky G.R.A.N.T. Program** offers state matching funds for federal grants but requires county, city, or 501(c)(3) status. Partnership is necessary for individuals/groups. Check KY Cabinet for Economic Development website.
111 | - Other programs (e.g., USDA People's Gardens, some state environmental grants) may require local matches and partnerships.
112 | - **Key Strategy:** Partnering with eligible entities (esp. CfGN) is the primary way to access matching funds.
113 | - _(Atlas Source: know_7ad86d22...)_
114 | 
115 | ## Technical Assistance & Grant Writing Support
116 | 
117 | Several resources can assist individuals/groups:
118 | 
119 | - **The Center for Great Neighborhoods (Covington):** Direct assistance for the Neighborhood Grant Program. ([https://www.greatneighborhoods.org](https://www.greatneighborhoods.org))
120 | - **Kentucky Nonprofit Network (KNN):** Grant writing workshops in Covington. (URL not found in search)
121 | - **Grant Ready Kentucky (GRKY):** Statewide direct assistance for federal grants. ([https://grantreadyky.org](https://grantreadyky.org))
122 | - **Kentucky SBDC (Covington):** Free coaching for nonprofits/entrepreneurs. (Official URL not found; related SBA KY page: [https://www.sba.gov/district/kentucky](https://www.sba.gov/district/kentucky))
123 | - **Appalachian Community Fund (ACF):** TA grants for training in Appalachian KY counties. (Official URL not found)
124 | - **USDA Rural Development:** Grants focused on cooperative development and capacity building. Check USDA RD website.
125 | - **KY Farm to School Network:** TA specifically for school gardens. ([https://www.kyfarmtoschool.com](https://www.kyfarmtoschool.com))
126 | - _(Atlas Sources: know_114ca14f..., know_4c3d6e43...)_
127 | 
128 | ## Examples of Funded Covington Projects
129 | 
130 | Successful local projects often involve resident groups and partnerships:
131 | 
132 | - **Redden Gardens (Eastside):** Allotment garden with CfGN partnership.
133 | - **Orchard Park (Westside):** Urban farm/community space created by 'Grow the Cov' group with CfGN/city support.
134 | - **Riddle-Yates Community Garden:** Covington's oldest, managed by CfGN.
135 | - **Make Goebel Great:** Resident-led park improvement project with city collaboration.
136 | - _(Atlas Source: know_bc417d3a...)_
137 | 
138 | ## Conclusion & Recommendation
139 | 
140 | Funding a solo-led community project in Covington requires strategic planning. While direct individual grants are scarce, opportunities significantly increase through collaboration. **The most recommended approach for an individual is to partner with neighbors to form a small group or work directly with The Center for Great Neighborhoods ([https://www.greatneighborhoods.org](https://www.greatneighborhoods.org)).** CfGN can provide guidance, assist with applications for the Neighborhood Grant Program, and potentially act as a fiscal sponsor or connector for other funding sources requiring organizational status. Exploring the KYSU Small-Scale Farm Grant ([https://www.kysu.edu/academics/college-ahnr/school-of-anr/co-op/small-scale-farm-grant-program.php](https://www.kysu.edu/academics/college-ahnr/school-of-anr/co-op/small-scale-farm-grant-program.php)) or specific GrantWatch listings ([https://www.grantwatch.com](https://www.grantwatch.com)) are secondary options if eligibility criteria are met. Leveraging available technical assistance resources (like GRKY: [https://grantreadyky.org](https://grantreadyky.org)) is also highly advised.
141 | 
142 | ---
143 | 
144 | ## Atlas Database Export
145 | 
146 | For a complete export of the research collected in the Atlas database, including detailed information about each grant opportunity, eligibility criteria, application processes, and contact information, please refer to the file:
147 | 
148 | [./full-export.json](./full-export.json)
149 | 
150 | This export contains all structured knowledge nodes created during the research process, including relationships between different funding sources and programs. The database can be imported into other Atlas instances or used for reference when developing grant applications.
151 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools/atlas_unified_search/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 { formatUnifiedSearchResponse } from "./responseFormat.js";
 14 | import { UnifiedSearchRequestInput } from "./types.js"; // Corrected type import
 15 | import { atlasUnifiedSearch } from "./unifiedSearch.js";
 16 | 
 17 | export const registerAtlasUnifiedSearchTool = (server: McpServer) => {
 18 |   registerTool(
 19 |     server,
 20 |     "atlas_unified_search",
 21 |     "Performs a unified search across specified entity types (node labels) with relevance scoring and flexible filtering options", // Updated description
 22 |     {
 23 |       property: z
 24 |         .string()
 25 |         .optional()
 26 |         .describe(
 27 |           "Specific property to search within (e.g., name, description, text)",
 28 |         ),
 29 |       value: z
 30 |         .string()
 31 |         .describe(
 32 |           "Search term or phrase to find across the knowledge base (required)",
 33 |         ),
 34 |       entityTypes: z
 35 |         .array(
 36 |           z.string(), // Allow any string label
 37 |         )
 38 |         .optional()
 39 |         .describe(
 40 |           "Array of entity types (node labels) to include in search (Default: project, task, knowledge if omitted)", // Updated description
 41 |         ),
 42 |       caseInsensitive: z
 43 |         .boolean()
 44 |         .optional()
 45 |         .default(true)
 46 |         .describe(
 47 |           "Boolean flag to ignore case sensitivity when searching for better recall (Default: true)",
 48 |         ),
 49 |       fuzzy: z
 50 |         .boolean()
 51 |         .optional()
 52 |         .default(false)
 53 |         .describe(
 54 |           "Boolean flag to enable approximate matching for typos, spelling variations, and similar terms (Default: false)",
 55 |         ),
 56 |       taskType: z.string().optional().describe(
 57 |         "Optional filter by project/task classification type for more targeted results (applies only if searching Project or Task types)", // Clarified description
 58 |       ),
 59 |       assignedToUserId: z
 60 |         .string()
 61 |         .optional()
 62 |         .describe(
 63 |           "Optional: Filter tasks by the ID of the assigned user. Only applicable when 'property' is specified (regex search) and 'entityTypes' includes 'task'.",
 64 |         ),
 65 |       page: z
 66 |         .number()
 67 |         .optional()
 68 |         .describe("Page number for paginated results (Default: 1)"),
 69 |       limit: z
 70 |         .number()
 71 |         .optional()
 72 |         .describe("Number of results per page, maximum 100 (Default: 20)"),
 73 |       responseFormat: createResponseFormatEnum()
 74 |         .optional()
 75 |         .default(ResponseFormat.FORMATTED)
 76 |         .describe(
 77 |           "Desired response format: 'formatted' (default string) or 'json' (raw object)",
 78 |         ),
 79 |     },
 80 |     async (input, context) => {
 81 |       // Process unified search request
 82 |       const validatedInput = input as unknown as UnifiedSearchRequestInput & {
 83 |         responseFormat?: ResponseFormat;
 84 |       };
 85 | 
 86 |       // Provide default entityTypes if not specified
 87 |       const searchInputWithDefaults = {
 88 |         ...validatedInput,
 89 |         entityTypes:
 90 |           validatedInput.entityTypes && validatedInput.entityTypes.length > 0
 91 |             ? validatedInput.entityTypes
 92 |             : ["project", "task", "knowledge"], // Default if empty or undefined
 93 |       };
 94 | 
 95 |       const result = await atlasUnifiedSearch(searchInputWithDefaults, context);
 96 | 
 97 |       // Conditionally format response
 98 |       if (validatedInput.responseFormat === ResponseFormat.JSON) {
 99 |         return createToolResponse(JSON.stringify(result, null, 2));
100 |       } else {
101 |         // Return the result using the formatter for rich display
102 |         return formatUnifiedSearchResponse(result, false);
103 |       }
104 |     },
105 |     createToolMetadata({
106 |       examples: [
107 |         createToolExample(
108 |           {
109 |             value: "authentication",
110 |             entityTypes: ["project", "task"], // Example still uses specific types
111 |             fuzzy: true,
112 |           },
113 |           `{
114 |             "results": [
115 |               {
116 |                 "id": "task_auth123",
117 |                 "type": "task", // Example still uses specific types
118 |                 "entityType": "implementation",
119 |                 "title": "Implement OAuth Authentication",
120 |                 "description": "Create secure authentication system using OAuth 2.0 protocol",
121 |                 "matchedProperty": "title",
122 |                 "matchedValue": "Implement OAuth Authentication",
123 |                 "createdAt": "2025-03-15T10:22:44.123Z",
124 |                 "updatedAt": "2025-03-15T10:22:44.123Z",
125 |                 "projectId": "proj_backend42",
126 |                 "projectName": "API Platform Modernization",
127 |                 "score": 9.5
128 |               },
129 |               {
130 |                 "id": "proj_auth456",
131 |                 "type": "project", // Example still uses specific types
132 |                 "entityType": "security",
133 |                 "title": "Authentication Microservice",
134 |                 "description": "Build standalone authentication microservice with JWT, refresh tokens and multi-factor support",
135 |                 "matchedProperty": "name",
136 |                 "matchedValue": "Authentication Microservice",
137 |                 "createdAt": "2025-03-10T08:30:12.456Z",
138 |                 "updatedAt": "2025-03-10T08:30:12.456Z",
139 |                 "score": 10
140 |               }
141 |               // ... potentially other types if they match
142 |             ],
143 |             "total": 2, // Example total might change if other types match
144 |             "page": 1,
145 |             "limit": 20,
146 |             "totalPages": 1
147 |           }`,
148 |           "Search for authentication-related projects and tasks with fuzzy matching",
149 |         ),
150 |         createToolExample(
151 |           {
152 |             value: "performance",
153 |             property: "description",
154 |             entityTypes: ["knowledge"], // Example still uses specific types
155 |           },
156 |           `{
157 |             "results": [
158 |               {
159 |                 "id": "know_perf123",
160 |                 "type": "knowledge", // Example still uses specific types
161 |                 "entityType": "technical",
162 |                 "title": "React Performance Optimiz...",
163 |                 "description": "Techniques for optimizing React component performance including memoization, virtualization, and code splitting",
164 |                 "matchedProperty": "text",
165 |                 "matchedValue": "Techniques for optimizing React component performance including memoization, virtualization, and code splitting",
166 |                 "createdAt": "2025-03-18T14:05:33.789Z",
167 |                 "updatedAt": "2025-03-18T14:05:33.789Z",
168 |                 "projectId": "proj_frontend42",
169 |                 "projectName": "Frontend Modernization",
170 |                 "score": 8.2
171 |               }
172 |               // ... potentially other types if they match
173 |             ],
174 |             "total": 1, // Example total might change if other types match
175 |             "page": 1,
176 |             "limit": 20,
177 |             "totalPages": 1
178 |           }`,
179 |           "Search knowledge items containing 'performance' in the description",
180 |         ),
181 |         createToolExample(
182 |           {
183 |             value: "api",
184 |             // No entityTypes specified, defaults to project, task, knowledge
185 |           },
186 |           `{
187 |             "results": [
188 |               {
189 |                 "id": "proj_api789",
190 |                 "type": "project", // Example still uses specific types
191 |                 "entityType": "integration",
192 |                 "title": "API Gateway Implementation",
193 |                 "description": "Create centralized API gateway for service integration with rate limiting, monitoring and authentication",
194 |                 "matchedProperty": "name",
195 |                 "matchedValue": "API Gateway Implementation",
196 |                 "createdAt": "2025-03-01T09:45:22.321Z",
197 |                 "updatedAt": "2025-03-05T15:12:44.456Z",
198 |                 "score": 10
199 |               },
200 |               {
201 |                 "id": "task_api456",
202 |                 "type": "task", // Example still uses specific types
203 |                 "entityType": "development",
204 |                 "title": "Document REST API Endpoints",
205 |                 "description": "Create comprehensive documentation for all REST API endpoints using OpenAPI specification",
206 |                 "matchedProperty": "title",
207 |                 "matchedValue": "Document REST API Endpoints",
208 |                 "createdAt": "2025-03-08T11:20:15.654Z",
209 |                 "updatedAt": "2025-03-08T11:20:15.654Z",
210 |                 "projectId": "proj_api789",
211 |                 "projectName": "API Gateway Implementation",
212 |                 "score": 9.8
213 |               },
214 |               {
215 |                 "id": "know_api321",
216 |                 "type": "knowledge", // Example still uses specific types
217 |                 "entityType": "technical",
218 |                 "title": "API Design Best Practices...",
219 |                 "description": "Best practices for RESTful API design including versioning, error handling, and resource naming conventions",
220 |                 "matchedProperty": "text",
221 |                 "matchedValue": "Best practices for RESTful API design including versioning, error handling, and resource naming conventions",
222 |                 "createdAt": "2025-03-12T16:30:45.987Z",
223 |                 "updatedAt": "2025-03-12T16:30:45.987Z",
224 |                 "projectId": "proj_api789",
225 |                 "projectName": "API Gateway Implementation",
226 |                 "score": 8.5
227 |               }
228 |               // ... potentially other types if they match
229 |             ],
230 |             "total": 8, // Example total might change if other types match
231 |             "page": 1,
232 |             "limit": 10,
233 |             "totalPages": 1
234 |           }`,
235 |           "Search for 'api' across default entity types (project, task, knowledge) with pagination",
236 |         ),
237 |       ],
238 |       requiredPermission: "search:read",
239 |       returnSchema: z.object({
240 |         results: z.array(
241 |           z.object({
242 |             id: z.string().describe("Unique identifier"),
243 |             type: z.string().describe("Entity type (node label)"), // Allow any string
244 |             entityType: z
245 |               .string()
246 |               .optional()
247 |               .describe(
248 |                 "Specific classification of the entity (e.g., taskType, domain)",
249 |               ), // Made optional as it might not apply to all types
250 |             title: z
251 |               .string()
252 |               .describe(
253 |                 "Entity title or name (might be generated for some types)",
254 |               ),
255 |             description: z
256 |               .string()
257 |               .optional()
258 |               .describe(
259 |                 "Entity description text (might be primary text for some types)",
260 |               ), // Made optional
261 |             matchedProperty: z
262 |               .string()
263 |               .describe("Property where the match was found"),
264 |             matchedValue: z
265 |               .string()
266 |               .describe("Value containing the match (potentially truncated)"),
267 |             createdAt: z
268 |               .string()
269 |               .optional()
270 |               .describe("Creation timestamp (if available)"), // Made optional
271 |             updatedAt: z
272 |               .string()
273 |               .optional()
274 |               .describe("Last update timestamp (if available)"), // Made optional
275 |             projectId: z
276 |               .string()
277 |               .optional()
278 |               .describe("Associated Project ID (if applicable)"),
279 |             projectName: z
280 |               .string()
281 |               .optional()
282 |               .describe("Associated Project name (if applicable)"),
283 |             score: z.number().describe("Relevance score"),
284 |           }),
285 |         ),
286 |         total: z.number().int().describe("Total number of matching results"),
287 |         page: z.number().int().describe("Current page number"),
288 |         limit: z.number().int().describe("Results per page"),
289 |         totalPages: z.number().int().describe("Total number of pages"),
290 |       }),
291 |       rateLimit: {
292 |         windowMs: 60 * 1000, // 1 minute
293 |         maxRequests: 20, // 20 requests per minute
294 |       },
295 |     }),
296 |   );
297 | };
298 | 
```

--------------------------------------------------------------------------------
/src/services/neo4j/backupRestoreService/importLogic.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Implements the database import logic for Neo4j.
  3 |  * @module src/services/neo4j/backupRestoreService/importLogic
  4 |  */
  5 | 
  6 | import { existsSync, readdirSync, readFileSync } from "fs";
  7 | import { stat } from "fs/promises";
  8 | import { Session } from "neo4j-driver";
  9 | import path from "path";
 10 | import { logger, requestContextService } from "../../../utils/index.js";
 11 | import { neo4jDriver } from "../driver.js";
 12 | import { escapeRelationshipType } from "../helpers.js";
 13 | import { FullExport } from "./backupRestoreTypes.js";
 14 | import { secureResolve, validatedBackupRoot } from "./backupUtils.js";
 15 | 
 16 | /**
 17 |  * Imports data from JSON files, overwriting the existing database.
 18 |  * Can import from either full-export.json (if it exists) or individual entity files.
 19 |  * @param backupDirInput The path to the directory containing the backup JSON files.
 20 |  * @throws Error if any step fails or if the backup directory is invalid.
 21 |  */
 22 | export const _importDatabase = async (
 23 |   backupDirInput: string,
 24 | ): Promise<void> => {
 25 |   const backupDir = secureResolve(
 26 |     validatedBackupRoot,
 27 |     path.relative(validatedBackupRoot, path.resolve(backupDirInput)),
 28 |   );
 29 |   if (!backupDir) {
 30 |     throw new Error(
 31 |       `Invalid backup directory provided: "${backupDirInput}". It must be within "${validatedBackupRoot}".`,
 32 |     );
 33 |   }
 34 |   try {
 35 |     const stats = await stat(backupDir);
 36 |     if (!stats.isDirectory()) {
 37 |       throw new Error(
 38 |         `Backup path "${backupDir}" exists but is not a directory.`,
 39 |       );
 40 |     }
 41 |   } catch (error: any) {
 42 |     if (error.code === "ENOENT") {
 43 |       throw new Error(`Backup directory "${backupDir}" does not exist.`);
 44 |     }
 45 |     throw new Error(
 46 |       `Failed to access backup directory "${backupDir}": ${error.message}`,
 47 |     );
 48 |   }
 49 | 
 50 |   const operationName = "_importDatabase"; // Renamed
 51 |   const baseContext = requestContextService.createRequestContext({
 52 |     operation: operationName,
 53 |     importDir: backupDir,
 54 |   });
 55 | 
 56 |   let session: Session | null = null;
 57 |   logger.warning(
 58 |     `Starting database import from validated directory ${backupDir}. THIS WILL OVERWRITE ALL EXISTING DATA.`,
 59 |     baseContext,
 60 |   );
 61 | 
 62 |   try {
 63 |     session = await neo4jDriver.getSession();
 64 |     logger.info("Clearing existing database...", baseContext);
 65 |     await session.executeWrite(async (tx) => {
 66 |       logger.debug("Executing clear database transaction...", baseContext);
 67 |       await tx.run("MATCH (n) DETACH DELETE n");
 68 |       logger.debug("Clear database transaction executed.", baseContext);
 69 |     });
 70 |     logger.info("Existing database cleared.", baseContext);
 71 | 
 72 |     let relationships: Array<{
 73 |       startNodeId: string;
 74 |       endNodeId: string;
 75 |       type: string;
 76 |       properties: Record<string, any>;
 77 |     }> = [];
 78 | 
 79 |     const fullExportPath = secureResolve(backupDir, "full-export.json");
 80 | 
 81 |     if (fullExportPath && existsSync(fullExportPath)) {
 82 |       logger.info(
 83 |         `Found full-export.json at ${fullExportPath}. Using consolidated import.`,
 84 |         { ...baseContext, filePath: fullExportPath },
 85 |       );
 86 |       const fullExportContent = readFileSync(fullExportPath, "utf-8");
 87 |       const fullExport: FullExport = JSON.parse(fullExportContent);
 88 | 
 89 |       for (const label in fullExport.nodes) {
 90 |         if (Object.prototype.hasOwnProperty.call(fullExport.nodes, label)) {
 91 |           const nodesToImport = fullExport.nodes[label];
 92 |           if (!nodesToImport || nodesToImport.length === 0) {
 93 |             logger.info(`No ${label} nodes to import from full-export.json.`, {
 94 |               ...baseContext,
 95 |               label,
 96 |             });
 97 |             continue;
 98 |           }
 99 |           logger.debug(
100 |             `Importing ${nodesToImport.length} ${label} nodes from full-export.json`,
101 |             { ...baseContext, label, count: nodesToImport.length },
102 |           );
103 |           const escapedLabel = `\`${label.replace(/`/g, "``")}\``;
104 |           const query = `UNWIND $nodes as nodeProps CREATE (n:${escapedLabel}) SET n = nodeProps`;
105 |           await session.executeWrite(async (tx) => {
106 |             logger.debug(
107 |               `Executing node creation transaction for label ${label} (full-export)...`,
108 |               { ...baseContext, label },
109 |             );
110 |             await tx.run(query, { nodes: nodesToImport });
111 |             logger.debug(
112 |               `Node creation transaction for label ${label} (full-export) executed.`,
113 |               { ...baseContext, label },
114 |             );
115 |           });
116 |           logger.info(
117 |             `Successfully imported ${nodesToImport.length} ${label} nodes from full-export.json`,
118 |             { ...baseContext, label, count: nodesToImport.length },
119 |           );
120 |         }
121 |       }
122 |       if (fullExport.relationships && fullExport.relationships.length > 0) {
123 |         logger.info(
124 |           `Found ${fullExport.relationships.length} relationships in full-export.json.`,
125 |           { ...baseContext, count: fullExport.relationships.length },
126 |         );
127 |         relationships = fullExport.relationships;
128 |       } else {
129 |         logger.info(`No relationships found in full-export.json.`, baseContext);
130 |       }
131 |     } else {
132 |       logger.info(
133 |         `No full-export.json found or path invalid. Using individual entity files from ${backupDir}.`,
134 |         baseContext,
135 |       );
136 |       const filesInBackupDir = readdirSync(backupDir);
137 |       const nodeFiles = filesInBackupDir.filter(
138 |         (file) =>
139 |           file.toLowerCase().endsWith(".json") &&
140 |           file !== "relationships.json" &&
141 |           file !== "full-export.json",
142 |       );
143 | 
144 |       for (const nodeFile of nodeFiles) {
145 |         const filePath = secureResolve(backupDir, nodeFile);
146 |         if (!filePath) {
147 |           logger.warning(
148 |             `Skipping potentially insecure node file path: ${nodeFile} in ${backupDir}`,
149 |             { ...baseContext, nodeFile },
150 |           );
151 |           continue;
152 |         }
153 |         const inferredLabelFromFile = path.basename(nodeFile, ".json");
154 |         const label = inferredLabelFromFile.endsWith("s")
155 |           ? inferredLabelFromFile.charAt(0).toUpperCase() +
156 |             inferredLabelFromFile.slice(1, -1)
157 |           : inferredLabelFromFile.charAt(0).toUpperCase() +
158 |             inferredLabelFromFile.slice(1);
159 | 
160 |         if (!existsSync(filePath)) {
161 |           logger.warning(
162 |             `Node file ${nodeFile} (inferred label ${label}) not found at ${filePath}. Skipping.`,
163 |             { ...baseContext, nodeFile, label, filePath },
164 |           );
165 |           continue;
166 |         }
167 |         logger.debug(
168 |           `Importing nodes with inferred label: ${label} from ${filePath}`,
169 |           { ...baseContext, label, filePath },
170 |         );
171 |         const fileContent = readFileSync(filePath, "utf-8");
172 |         const nodesToImport: Record<string, any>[] = JSON.parse(fileContent);
173 | 
174 |         if (nodesToImport.length === 0) {
175 |           logger.info(`No ${label} nodes to import from ${filePath}.`, {
176 |             ...baseContext,
177 |             label,
178 |             filePath,
179 |           });
180 |           continue;
181 |         }
182 |         const escapedLabel = `\`${label.replace(/`/g, "``")}\``;
183 |         const query = `UNWIND $nodes as nodeProps CREATE (n:${escapedLabel}) SET n = nodeProps`;
184 |         await session.executeWrite(async (tx) => {
185 |           logger.debug(
186 |             `Executing node creation transaction for label ${label} (individual file)...`,
187 |             { ...baseContext, label },
188 |           );
189 |           await tx.run(query, { nodes: nodesToImport });
190 |           logger.debug(
191 |             `Node creation transaction for label ${label} (individual file) executed.`,
192 |             { ...baseContext, label },
193 |           );
194 |         });
195 |         logger.info(
196 |           `Successfully imported ${nodesToImport.length} ${label} nodes from ${filePath}`,
197 |           { ...baseContext, label, count: nodesToImport.length, filePath },
198 |         );
199 |       }
200 |       const relFilePath = secureResolve(backupDir, "relationships.json");
201 |       if (relFilePath && existsSync(relFilePath)) {
202 |         logger.info(`Importing relationships from ${relFilePath}...`, {
203 |           ...baseContext,
204 |           filePath: relFilePath,
205 |         });
206 |         const relFileContent = readFileSync(relFilePath, "utf-8");
207 |         relationships = JSON.parse(relFileContent);
208 |         if (relationships.length === 0) {
209 |           logger.info(`No relationships found to import in ${relFilePath}.`, {
210 |             ...baseContext,
211 |             filePath: relFilePath,
212 |           });
213 |         }
214 |       } else {
215 |         logger.warning(
216 |           `Relationships file not found or path invalid: ${relFilePath}. Skipping relationship import.`,
217 |           { ...baseContext, filePath: relFilePath },
218 |         );
219 |       }
220 |     }
221 | 
222 |     if (relationships.length > 0) {
223 |       logger.info(
224 |         `Attempting to import ${relationships.length} relationships...`,
225 |         { ...baseContext, totalRelationships: relationships.length },
226 |       );
227 |       let importedCount = 0;
228 |       let failedCount = 0;
229 |       const relationshipsByType: Record<
230 |         string,
231 |         Array<{
232 |           startNodeId: string;
233 |           endNodeId: string;
234 |           properties: Record<string, any>;
235 |         }>
236 |       > = {};
237 | 
238 |       for (const rel of relationships) {
239 |         if (!rel.startNodeId || !rel.endNodeId || !rel.type) {
240 |           logger.warning(
241 |             `Skipping relationship due to missing critical data (startNodeId, endNodeId, or type): ${JSON.stringify(rel)}`,
242 |             { ...baseContext, relationshipData: rel },
243 |           );
244 |           failedCount++;
245 |           continue;
246 |         }
247 |         if (!relationshipsByType[rel.type]) {
248 |           relationshipsByType[rel.type] = [];
249 |         }
250 |         relationshipsByType[rel.type].push({
251 |           startNodeId: rel.startNodeId,
252 |           endNodeId: rel.endNodeId,
253 |           properties: rel.properties || {},
254 |         });
255 |       }
256 | 
257 |       const batchSize = 500;
258 |       for (const relType in relationshipsByType) {
259 |         if (
260 |           Object.prototype.hasOwnProperty.call(relationshipsByType, relType)
261 |         ) {
262 |           const relsOfType = relationshipsByType[relType];
263 |           const escapedType = escapeRelationshipType(relType);
264 |           logger.debug(
265 |             `Processing ${relsOfType.length} relationships of type ${relType} (escaped: ${escapedType})`,
266 |             { ...baseContext, relType, count: relsOfType.length },
267 |           );
268 |           for (let i = 0; i < relsOfType.length; i += batchSize) {
269 |             const batch = relsOfType.slice(i, i + batchSize);
270 |             const batchNumber = i / batchSize + 1;
271 |             logger.debug(
272 |               `Processing batch ${batchNumber} for type ${relType} (size: ${batch.length})`,
273 |               { ...baseContext, relType, batchNumber, batchSize: batch.length },
274 |             );
275 |             const relQuery = `
276 |               UNWIND $rels AS relData
277 |               MATCH (start {id: relData.startNodeId})
278 |               MATCH (end {id: relData.endNodeId})
279 |               CREATE (start)-[r:${escapedType}]->(end)
280 |               SET r = relData.properties
281 |               RETURN count(r) as createdCount
282 |             `;
283 |             try {
284 |               const result = await session.executeWrite(async (tx) => {
285 |                 logger.debug(
286 |                   `Executing UNWIND transaction for type ${relType}, batch ${batchNumber}`,
287 |                   { ...baseContext, relType, batchNumber },
288 |                 );
289 |                 const txResult = await tx.run(relQuery, { rels: batch });
290 |                 logger.debug(
291 |                   `UNWIND transaction executed for type ${relType}, batch ${batchNumber}`,
292 |                   { ...baseContext, relType, batchNumber },
293 |                 );
294 |                 return txResult.records[0]?.get("createdCount").toNumber() || 0;
295 |               });
296 |               importedCount += result;
297 |               logger.debug(
298 |                 `Successfully created ${result} relationships of type ${relType} in batch ${batchNumber}`,
299 |                 { ...baseContext, relType, batchNumber, count: result },
300 |               );
301 |             } catch (error) {
302 |               const errorMsg =
303 |                 error instanceof Error ? error.message : String(error);
304 |               logger.error(
305 |                 `Failed to create relationships of type ${relType} in batch ${batchNumber}: ${errorMsg}`,
306 |                 error as Error,
307 |                 {
308 |                   ...baseContext,
309 |                   relType,
310 |                   batchNumber,
311 |                   batchDataSample: batch.slice(0, 5),
312 |                 },
313 |               );
314 |               failedCount += batch.length;
315 |             }
316 |           }
317 |         }
318 |       }
319 |       logger.info(
320 |         `Relationship import summary: Attempted=${relationships.length}, Succeeded=${importedCount}, Failed=${failedCount}`,
321 |         {
322 |           ...baseContext,
323 |           attempted: relationships.length,
324 |           succeeded: importedCount,
325 |           failed: failedCount,
326 |         },
327 |       );
328 |     } else {
329 |       logger.info("No relationships to import.", baseContext);
330 |     }
331 |     logger.info("Database import completed successfully.", baseContext);
332 |   } catch (error) {
333 |     const errorMessage = error instanceof Error ? error.message : String(error);
334 |     logger.error(
335 |       `Database import failed: ${errorMessage}`,
336 |       error as Error,
337 |       baseContext,
338 |     );
339 |     throw new Error(`Database import failed: ${errorMessage}`);
340 |   } finally {
341 |     if (session) {
342 |       await session.close();
343 |     }
344 |   }
345 | };
346 | 
```

--------------------------------------------------------------------------------
/src/types/tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { OperationContext } from "../utils/internal/requestContext.js";
  4 | // Use OperationContext as ToolContext
  5 | export type ToolContext = OperationContext;
  6 | // import { createToolMiddleware } from "../utils/security/index.js"; // Assuming this was from a missing file
  7 | // import { checkPermission } from "../utils/security/index.js"; // Assuming this was from a missing file
  8 | import { McpError } from "./errors.js";
  9 | import { createToolResponse, McpToolResponse } from "./mcp.js";
 10 | 
 11 | // Tool example definition
 12 | export interface ToolExample {
 13 |   input: Record<string, unknown>;
 14 |   output: string;
 15 |   description?: string;
 16 | }
 17 | 
 18 | // Entity types for Atlas Platform
 19 | export type EntityType = "project" | "task" | "knowledge";
 20 | 
 21 | // Task types supported in Atlas Platform
 22 | export type TaskType =
 23 |   | "research"
 24 |   | "generation"
 25 |   | "analysis"
 26 |   | "integration"
 27 |   | string;
 28 | 
 29 | // Project status states
 30 | export type ProjectStatus =
 31 |   | "active"
 32 |   | "pending"
 33 |   | "in-progress"
 34 |   | "completed"
 35 |   | "archived";
 36 | 
 37 | // Task status states
 38 | export type TaskStatus = "backlog" | "todo" | "in-progress" | "completed";
 39 | 
 40 | // Priority levels for tasks
 41 | export type PriorityLevel = "low" | "medium" | "high" | "critical";
 42 | 
 43 | // Domain types for knowledge categorization
 44 | export type KnowledgeDomain = "technical" | "business" | "scientific" | string;
 45 | 
 46 | // Tool metadata
 47 | export interface ToolMetadata {
 48 |   examples?: ToolExample[];
 49 |   returnSchema?: z.ZodType<any>;
 50 |   requiredPermission?: string;
 51 |   entityType?: EntityType | EntityType[]; // Associates tool with specific entity types
 52 |   rateLimit?: {
 53 |     windowMs: number;
 54 |     maxRequests: number;
 55 |   };
 56 |   supportsBulkOperations?: boolean; // Indicates whether tool supports bulk mode
 57 | }
 58 | 
 59 | // Base handler type that matches SDK expectations
 60 | type BaseToolHandler = (
 61 |   input: unknown,
 62 |   context: ToolContext,
 63 | ) => Promise<McpToolResponse>;
 64 | 
 65 | // Enhanced tool registration function
 66 | export const registerTool = (
 67 |   server: McpServer,
 68 |   name: string,
 69 |   description: string,
 70 |   schema: z.ZodRawShape,
 71 |   handler: BaseToolHandler,
 72 |   metadata?: ToolMetadata,
 73 | ) => {
 74 |   const wrappedHandler = async (
 75 |     args: Record<string, unknown>,
 76 |     extra: Record<string, unknown>,
 77 |   ): Promise<McpToolResponse> => {
 78 |     try {
 79 |       // Check permissions if required
 80 |       // if (metadata?.requiredPermission) {
 81 |       //   // const { checkPermission } = await import("../utils/security/index.js"); // Placeholder for missing checkPermission
 82 |       //   // checkPermission(extra as ToolContext, metadata.requiredPermission);
 83 |       //   console.warn(`Permission check for '${metadata.requiredPermission}' skipped due to missing checkPermission function.`);
 84 |       // }
 85 | 
 86 |       // Validate input
 87 |       const zodSchema = z.object(schema);
 88 |       const validatedInput = zodSchema.parse(args);
 89 | 
 90 |       // Create middleware with custom rate limit if specified
 91 |       // const middleware = createToolMiddleware(name); // Placeholder for missing createToolMiddleware
 92 |       // const result = await middleware(handler, validatedInput, extra as ToolContext);
 93 | 
 94 |       // Directly call handler if middleware is missing
 95 |       const result = await handler(validatedInput, extra as ToolContext);
 96 | 
 97 |       // Ensure result matches expected format
 98 |       if (
 99 |         typeof result === "object" &&
100 |         result !== null &&
101 |         "content" in result
102 |       ) {
103 |         return result as McpToolResponse;
104 |       }
105 | 
106 |       // Convert unexpected result format to standard response
107 |       return createToolResponse(JSON.stringify(result));
108 |     } catch (error) {
109 |       if (error instanceof McpError) {
110 |         return error.toResponse();
111 |       }
112 |       if (error instanceof z.ZodError) {
113 |         return createToolResponse(
114 |           `Validation error: ${error.errors.map((e) => e.message).join(", ")}`,
115 |           true,
116 |         );
117 |       }
118 |       return createToolResponse(
119 |         `Error: ${error instanceof Error ? error.message : "An unknown error occurred"}`,
120 |         true,
121 |       );
122 |     }
123 |   };
124 | 
125 |   // Keep description concise and focused on tool purpose only
126 |   const fullDescription = description;
127 | 
128 |   // Register tool with server
129 |   // Examples are handled separately through the metadata but not passed directly to server.tool
130 |   server.tool(name, fullDescription, schema, wrappedHandler);
131 | };
132 | 
133 | // Helper to create tool examples
134 | export const createToolExample = (
135 |   input: Record<string, unknown>,
136 |   output: string,
137 |   description?: string,
138 | ): ToolExample => ({
139 |   input,
140 |   output,
141 |   description,
142 | });
143 | 
144 | // Helper to create tool metadata
145 | export const createToolMetadata = (metadata: ToolMetadata): ToolMetadata =>
146 |   metadata;
147 | 
148 | /**
149 |  * Atlas Platform specific interfaces to represent the core data model
150 |  * These interfaces match the database objects described in the Atlas Platform Reference Guide
151 |  */
152 | 
153 | export interface Project {
154 |   /** Optional client-generated ID; system will generate if not provided */
155 |   id?: string;
156 | 
157 |   /** Descriptive project name (1-100 characters) */
158 |   name: string;
159 | 
160 |   /** Comprehensive project overview explaining purpose and scope */
161 |   description: string;
162 | 
163 |   /** Current project state */
164 |   status: ProjectStatus;
165 | 
166 |   /** Relevant URLs with descriptive titles for reference materials */
167 |   urls?: Array<{ title: string; url: string }>;
168 | 
169 |   /** Specific, measurable criteria that indicate project completion */
170 |   completionRequirements: string;
171 | 
172 |   /** Array of existing project IDs that must be completed before this project can begin */
173 |   dependencies?: string[];
174 | 
175 |   /** Required format specification for final project deliverables */
176 |   outputFormat: string;
177 | 
178 |   /** Classification of project purpose */
179 |   taskType: TaskType;
180 | 
181 |   /** Timestamp when the project was created */
182 |   createdAt: string;
183 | 
184 |   /** Timestamp when the project was last updated */
185 |   updatedAt: string;
186 | }
187 | 
188 | export interface Task {
189 |   /** Optional client-generated ID; system will generate if not provided */
190 |   id?: string;
191 | 
192 |   /** ID of the parent project this task belongs to */
193 |   projectId: string;
194 | 
195 |   /** Concise task title clearly describing the objective (5-150 characters) */
196 |   title: string;
197 | 
198 |   /** Detailed explanation of the task requirements and context */
199 |   description: string;
200 | 
201 |   /** Importance level */
202 |   priority: PriorityLevel;
203 | 
204 |   /** Current task state */
205 |   status: TaskStatus;
206 | 
207 |   /** ID of entity responsible for task completion */
208 |   assignedTo?: string;
209 | 
210 |   /** Relevant URLs with descriptive titles for reference materials */
211 |   urls?: Array<{ title: string; url: string }>;
212 | 
213 |   /** Categorical labels for organization and filtering */
214 |   tags?: string[];
215 | 
216 |   /** Specific, measurable criteria that indicate task completion */
217 |   completionRequirements: string;
218 | 
219 |   /** Array of existing task IDs that must be completed before this task can begin */
220 |   dependencies?: string[];
221 | 
222 |   /** Required format specification for task deliverables */
223 |   outputFormat: string;
224 | 
225 |   /** Classification of task purpose */
226 |   taskType: TaskType;
227 | 
228 |   /** Timestamp when the task was created */
229 |   createdAt: string;
230 | 
231 |   /** Timestamp when the task was last updated */
232 |   updatedAt: string;
233 | }
234 | 
235 | export interface Knowledge {
236 |   /** Optional client-generated ID; system will generate if not provided */
237 |   id?: string;
238 | 
239 |   /** ID of the parent project this knowledge belongs to */
240 |   projectId: string;
241 | 
242 |   /** Main content of the knowledge item (can be structured or unstructured) */
243 |   text: string;
244 | 
245 |   /** Categorical labels for organization and filtering */
246 |   tags?: string[];
247 | 
248 |   /** Primary knowledge area or discipline */
249 |   domain: KnowledgeDomain;
250 | 
251 |   /** Array of reference sources supporting this knowledge (URLs, DOIs, etc.) */
252 |   citations?: string[];
253 | 
254 |   /** Timestamp when the knowledge item was created */
255 |   createdAt: string;
256 | 
257 |   /** Timestamp when the knowledge item was last updated */
258 |   updatedAt: string;
259 | }
260 | 
261 | /**
262 |  * Operation request interfaces based on the API Reference
263 |  * These interfaces can be used as a foundation for building tool input schemas
264 |  */
265 | 
266 | export interface ProjectCreateRequest {
267 |   /** Operation mode - 'single' for one project, 'bulk' for multiple projects */
268 |   mode?: "single" | "bulk";
269 | 
270 |   /** Optional client-generated project ID (required for mode='single') */
271 |   id?: string;
272 | 
273 |   /** Descriptive project name (1-100 characters) (required for mode='single') */
274 |   name?: string;
275 | 
276 |   /** Comprehensive project overview explaining purpose and scope (required for mode='single') */
277 |   description?: string;
278 | 
279 |   /** Current project state (Default: active) */
280 |   status?: ProjectStatus;
281 | 
282 |   /** Array of relevant URLs with descriptive titles for reference materials */
283 |   urls?: Array<{ title: string; url: string }>;
284 | 
285 |   /** Specific, measurable criteria that indicate project completion (required for mode='single') */
286 |   completionRequirements?: string;
287 | 
288 |   /** Array of existing project IDs that must be completed before this project can begin */
289 |   dependencies?: string[];
290 | 
291 |   /** Required format specification for final project deliverables (required for mode='single') */
292 |   outputFormat?: string;
293 | 
294 |   /** Classification of project purpose (required for mode='single') */
295 |   taskType?: TaskType;
296 | 
297 |   /** Array of project objects with the above fields (required for mode='bulk') */
298 |   projects?: Partial<Project>[];
299 | }
300 | 
301 | export interface TaskCreateRequest {
302 |   /** Operation mode - 'single' for one task, 'bulk' for multiple tasks */
303 |   mode?: "single" | "bulk";
304 | 
305 |   /** Optional client-generated task ID */
306 |   id?: string;
307 | 
308 |   /** ID of the parent project this task belongs to (required for mode='single') */
309 |   projectId?: string;
310 | 
311 |   /** Concise task title clearly describing the objective (5-150 characters) (required for mode='single') */
312 |   title?: string;
313 | 
314 |   /** Detailed explanation of the task requirements and context (required for mode='single') */
315 |   description?: string;
316 | 
317 |   /** Importance level (Default: medium) */
318 |   priority?: PriorityLevel;
319 | 
320 |   /** Current task state (Default: todo) */
321 |   status?: TaskStatus;
322 | 
323 |   /** ID of entity responsible for task completion */
324 |   assignedTo?: string;
325 | 
326 |   /** Array of relevant URLs with descriptive titles for reference materials */
327 |   urls?: Array<{ title: string; url: string }>;
328 | 
329 |   /** Array of categorical labels for organization and filtering */
330 |   tags?: string[];
331 | 
332 |   /** Specific, measurable criteria that indicate task completion (required for mode='single') */
333 |   completionRequirements?: string;
334 | 
335 |   /** Array of existing task IDs that must be completed before this task can begin */
336 |   dependencies?: string[];
337 | 
338 |   /** Required format specification for task deliverables (required for mode='single') */
339 |   outputFormat?: string;
340 | 
341 |   /** Classification of task purpose (required for mode='single') */
342 |   taskType?: TaskType;
343 | 
344 |   /** Array of task objects with the above fields (required for mode='bulk') */
345 |   tasks?: Partial<Task>[];
346 | }
347 | 
348 | export interface KnowledgeAddRequest {
349 |   /** Operation mode - 'single' for one knowledge item, 'bulk' for multiple items */
350 |   mode?: "single" | "bulk";
351 | 
352 |   /** Optional client-generated knowledge ID */
353 |   id?: string;
354 | 
355 |   /** ID of the parent project this knowledge belongs to (required for mode='single') */
356 |   projectId?: string;
357 | 
358 |   /** Main content of the knowledge item (can be structured or unstructured) (required for mode='single') */
359 |   text?: string;
360 | 
361 |   /** Array of categorical labels for organization and filtering */
362 |   tags?: string[];
363 | 
364 |   /** Primary knowledge area or discipline (required for mode='single') */
365 |   domain?: KnowledgeDomain;
366 | 
367 |   /** Array of reference sources supporting this knowledge (URLs, DOIs, etc.) */
368 |   citations?: string[];
369 | 
370 |   /** Array of knowledge objects with the above fields (required for mode='bulk') */
371 |   knowledge?: Partial<Knowledge>[];
372 | }
373 | 
374 | // Example usage - Updated for Atlas Platform:
375 | /*
376 | registerTool(
377 |   server,
378 |   "atlas_project_create",
379 |   "Creates a new project or multiple projects in the system",
380 |   {
381 |     mode: z.enum(['single', 'bulk']).optional().default('single'),
382 |     id: z.string().optional(),
383 |     name: z.string().min(1).max(100).optional(),
384 |     description: z.string().optional(),
385 |     status: z.enum(['active', 'pending', 'completed', 'archived']).optional().default('active'),
386 |     completionRequirements: z.string().optional(),
387 |     dependencies: z.array(z.string()).optional(),
388 |     outputFormat: z.string().optional(),
389 |     taskType: z.union([
390 |       z.literal('research'),
391 |       z.literal('generation'),
392 |       z.literal('analysis'),
393 |       z.literal('integration'),
394 |       z.string()
395 |     ]).optional(),
396 |     projects: z.array(z.object({}).passthrough()).optional()
397 |   },
398 |   async (input, context) => {
399 |     // Implementation would validate and process the input
400 |     return createToolResponse(JSON.stringify(result, null, 2));
401 |   },
402 |   createToolMetadata({
403 |     examples: [
404 |       createToolExample(
405 |         { 
406 |           mode: "single",
407 |           name: "Atlas Migration Project", 
408 |           description: "Migrate existing project data to the Atlas Platform", 
409 |           completionRequirements: "All data migrated with validation",
410 |           outputFormat: "Functional system with documentation",
411 |           taskType: "integration"
412 |         },
413 |         "Project created successfully with ID: proj_xyz123",
414 |         "Create a single integration project"
415 |       )
416 |     ],
417 |     requiredPermission: "project:create",
418 |     entityType: 'project',
419 |     supportsBulkOperations: true
420 |   })
421 | );
422 | */
423 | 
```

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

```typescript
  1 | import dotenv from "dotenv";
  2 | import { existsSync, mkdirSync, readFileSync, statSync } from "fs";
  3 | import path, { dirname, join } from "path";
  4 | import { fileURLToPath } from "url";
  5 | import { z } from "zod";
  6 | 
  7 | dotenv.config(); // Load environment variables from .env file
  8 | 
  9 | // --- Determine Project Root ---
 10 | /**
 11 |  * Finds the project root directory by searching upwards for package.json.
 12 |  * @param startDir The directory to start searching from.
 13 |  * @returns The absolute path to the project root, or throws an error if not found.
 14 |  */
 15 | const findProjectRoot = (startDir: string): string => {
 16 |   let currentDir = startDir;
 17 |   while (true) {
 18 |     const packageJsonPath = join(currentDir, "package.json");
 19 |     if (existsSync(packageJsonPath)) {
 20 |       return currentDir;
 21 |     }
 22 |     const parentDir = dirname(currentDir);
 23 |     if (parentDir === currentDir) {
 24 |       throw new Error(
 25 |         `Could not find project root (package.json) starting from ${startDir}`,
 26 |       );
 27 |     }
 28 |     currentDir = parentDir;
 29 |   }
 30 | };
 31 | 
 32 | let projectRoot: string;
 33 | try {
 34 |   const currentModuleDir = dirname(fileURLToPath(import.meta.url));
 35 |   projectRoot = findProjectRoot(currentModuleDir);
 36 | } catch (error: any) {
 37 |   console.error(`FATAL: Error determining project root: ${error.message}`);
 38 |   projectRoot = process.cwd();
 39 |   console.warn(
 40 |     `Warning: Using process.cwd() (${projectRoot}) as fallback project root.`,
 41 |   );
 42 | }
 43 | // --- End Determine Project Root ---
 44 | 
 45 | // --- Reading package.json ---
 46 | const packageJsonPath = path.resolve(projectRoot, "package.json");
 47 | let pkg: { name: string; version: string } = {
 48 |   name: "atlas-mcp-server",
 49 |   version: "0.0.0",
 50 | }; // Default
 51 | 
 52 | try {
 53 |   // Basic check to ensure resolved path is within the determined project root
 54 |   if (
 55 |     !packageJsonPath.startsWith(projectRoot + path.sep) &&
 56 |     packageJsonPath !== projectRoot
 57 |   ) {
 58 |     // This check might be too simplistic if symlinks are involved, but good for basic safety.
 59 |     // A more robust check would normalize both paths before comparison.
 60 |   }
 61 |   pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
 62 | } catch (error: any) {
 63 |   if (process.stdout.isTTY) {
 64 |     console.error(
 65 |       `Warning: Could not read package.json at ${packageJsonPath} for default config values. Using hardcoded defaults. Error: ${error.message}`,
 66 |     );
 67 |   }
 68 | }
 69 | // --- End Reading package.json ---
 70 | 
 71 | /**
 72 |  * Zod schema for validating environment variables.
 73 |  * Provides type safety, validation, defaults, and clear error messages.
 74 |  * @private
 75 |  */
 76 | const EnvSchema = z.object({
 77 |   /** Optional. The desired name for the MCP server. Defaults to `package.json` name. */
 78 |   MCP_SERVER_NAME: z.string().optional(),
 79 |   /** Optional. The version of the MCP server. Defaults to `package.json` version. */
 80 |   MCP_SERVER_VERSION: z.string().optional(),
 81 |   /** Minimum logging level. See `McpLogLevel` in logger utility. Default: "debug". */
 82 |   MCP_LOG_LEVEL: z.string().default("debug"),
 83 |   /** Runtime environment (e.g., "development", "production"). Default: "development". */
 84 |   NODE_ENV: z.string().default("development"),
 85 |   /** MCP communication transport ("stdio" or "http"). Default: "stdio". */
 86 |   MCP_TRANSPORT_TYPE: z.enum(["stdio", "http"]).default("stdio"),
 87 |   /** HTTP server port (if MCP_TRANSPORT_TYPE is "http"). Default: 3010. */
 88 |   MCP_HTTP_PORT: z.coerce.number().int().positive().default(3010),
 89 |   /** HTTP server host (if MCP_TRANSPORT_TYPE is "http"). Default: "127.0.0.1". */
 90 |   MCP_HTTP_HOST: z.string().default("127.0.0.1"),
 91 |   /** Optional. Comma-separated allowed origins for CORS (HTTP transport). */
 92 |   MCP_ALLOWED_ORIGINS: z.string().optional(),
 93 |   /** Optional. Secret key (min 32 chars) for auth tokens (HTTP transport). CRITICAL for production. */
 94 |   MCP_AUTH_SECRET_KEY: z
 95 |     .string()
 96 |     .min(
 97 |       32,
 98 |       "MCP_AUTH_SECRET_KEY must be at least 32 characters long for security reasons.",
 99 |     )
100 |     .optional(),
101 |   MCP_RATE_LIMIT_WINDOW_MS: z.coerce.number().int().positive().default(60000), // 1 minute
102 |   MCP_RATE_LIMIT_MAX_REQUESTS: z.coerce.number().int().positive().default(100),
103 | 
104 |   NEO4J_URI: z.string().default("bolt://localhost:7687"),
105 |   NEO4J_USER: z.string().default("neo4j"),
106 |   NEO4J_PASSWORD: z.string().default("password"),
107 | 
108 |   BACKUP_FILE_DIR: z.string().default(path.join(projectRoot, "atlas-backups")),
109 |   BACKUP_MAX_COUNT: z.coerce.number().int().min(0).default(10),
110 | 
111 |   /** Directory for log files. Defaults to "logs" in project root. */
112 |   LOGS_DIR: z.string().default(path.join(projectRoot, "logs")),
113 | 
114 |   /** Optional. OAuth provider authorization endpoint URL. */
115 |   OAUTH_PROXY_AUTHORIZATION_URL: z
116 |     .string()
117 |     .url("OAUTH_PROXY_AUTHORIZATION_URL must be a valid URL.")
118 |     .optional(),
119 |   /** Optional. OAuth provider token endpoint URL. */
120 |   OAUTH_PROXY_TOKEN_URL: z
121 |     .string()
122 |     .url("OAUTH_PROXY_TOKEN_URL must be a valid URL.")
123 |     .optional(),
124 |   /** Optional. OAuth provider revocation endpoint URL. */
125 |   OAUTH_PROXY_REVOCATION_URL: z
126 |     .string()
127 |     .url("OAUTH_PROXY_REVOCATION_URL must be a valid URL.")
128 |     .optional(),
129 |   /** Optional. OAuth provider issuer URL. */
130 |   OAUTH_PROXY_ISSUER_URL: z
131 |     .string()
132 |     .url("OAUTH_PROXY_ISSUER_URL must be a valid URL.")
133 |     .optional(),
134 |   /** Optional. OAuth service documentation URL. */
135 |   OAUTH_PROXY_SERVICE_DOCUMENTATION_URL: z
136 |     .string()
137 |     .url("OAUTH_PROXY_SERVICE_DOCUMENTATION_URL must be a valid URL.")
138 |     .optional(),
139 |   /** Optional. Comma-separated default OAuth client redirect URIs. */
140 |   OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URIS: z.string().optional(),
141 | });
142 | 
143 | // Parse and validate environment variables
144 | const parsedEnv = EnvSchema.safeParse(process.env);
145 | 
146 | if (!parsedEnv.success) {
147 |   if (process.stdout.isTTY) {
148 |     console.error(
149 |       "❌ Invalid environment variables found:",
150 |       parsedEnv.error.flatten().fieldErrors,
151 |     );
152 |   }
153 |   // Consider throwing an error in production for critical misconfigurations.
154 | }
155 | 
156 | const env = parsedEnv.success ? parsedEnv.data : EnvSchema.parse({}); // Use defaults on failure
157 | 
158 | // --- Directory Ensurance Function ---
159 | /**
160 |  * Ensures a directory exists and is within the project root.
161 |  * @param dirPath The desired path for the directory (can be relative or absolute).
162 |  * @param rootDir The root directory of the project to contain the directory.
163 |  * @param dirName The name of the directory type for logging (e.g., "backup", "logs").
164 |  * @returns The validated, absolute path to the directory, or null if invalid.
165 |  */
166 | const ensureDirectory = (
167 |   dirPath: string,
168 |   rootDir: string,
169 |   dirName: string,
170 | ): string | null => {
171 |   const resolvedDirPath = path.isAbsolute(dirPath)
172 |     ? dirPath
173 |     : path.resolve(rootDir, dirPath);
174 | 
175 |   // Ensure the resolved path is within the project root boundary
176 |   if (
177 |     !resolvedDirPath.startsWith(rootDir + path.sep) &&
178 |     resolvedDirPath !== rootDir
179 |   ) {
180 |     if (process.stdout.isTTY) {
181 |       console.error(
182 |         `Error: ${dirName} path "${dirPath}" resolves to "${resolvedDirPath}", which is outside the project boundary "${rootDir}".`,
183 |       );
184 |     }
185 |     return null;
186 |   }
187 | 
188 |   if (!existsSync(resolvedDirPath)) {
189 |     try {
190 |       mkdirSync(resolvedDirPath, { recursive: true });
191 |       if (process.stdout.isTTY) {
192 |         console.log(`Created ${dirName} directory: ${resolvedDirPath}`);
193 |       }
194 |     } catch (err: unknown) {
195 |       const errorMessage = err instanceof Error ? err.message : String(err);
196 |       if (process.stdout.isTTY) {
197 |         console.error(
198 |           `Error creating ${dirName} directory at ${resolvedDirPath}: ${errorMessage}`,
199 |         );
200 |       }
201 |       return null;
202 |     }
203 |   } else {
204 |     try {
205 |       const stats = statSync(resolvedDirPath);
206 |       if (!stats.isDirectory()) {
207 |         if (process.stdout.isTTY) {
208 |           console.error(
209 |             `Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`,
210 |           );
211 |         }
212 |         return null;
213 |       }
214 |     } catch (statError: any) {
215 |       if (process.stdout.isTTY) {
216 |         console.error(
217 |           `Error accessing ${dirName} path ${resolvedDirPath}: ${statError.message}`,
218 |         );
219 |       }
220 |       return null;
221 |     }
222 |   }
223 |   return resolvedDirPath;
224 | };
225 | // --- End Directory Ensurance Function ---
226 | 
227 | // --- Backup Directory Handling ---
228 | /**
229 |  * Ensures the backup directory exists and is within the project root.
230 |  * @param backupPath The desired path for the backup directory (can be relative or absolute).
231 |  * @param rootDir The root directory of the project to contain the backups.
232 |  * @returns The validated, absolute path to the backup directory, or null if invalid.
233 |  */
234 | const ensureBackupDir = (
235 |   backupPath: string,
236 |   rootDir: string,
237 | ): string | null => {
238 |   return ensureDirectory(backupPath, rootDir, "backup");
239 | };
240 | 
241 | const validatedBackupPath = ensureBackupDir(env.BACKUP_FILE_DIR, projectRoot);
242 | 
243 | if (!validatedBackupPath) {
244 |   if (process.stdout.isTTY) {
245 |     console.error(
246 |       "FATAL: Backup directory configuration is invalid or could not be created. Please check permissions and path. Exiting.",
247 |     );
248 |   }
249 |   process.exit(1);
250 | }
251 | // --- End Backup Directory Handling ---
252 | 
253 | // --- Logs Directory Handling ---
254 | /**
255 |  * Ensures the logs directory exists and is within the project root.
256 |  * @param logsPath The desired path for the logs directory (can be relative or absolute).
257 |  * @param rootDir The root directory of the project to contain the logs.
258 |  * @returns The validated, absolute path to the logs directory, or null if invalid.
259 |  */
260 | const ensureLogsDir = (logsPath: string, rootDir: string): string | null => {
261 |   return ensureDirectory(logsPath, rootDir, "logs");
262 | };
263 | 
264 | const validatedLogsPath = ensureLogsDir(env.LOGS_DIR, projectRoot);
265 | 
266 | if (!validatedLogsPath) {
267 |   if (process.stdout.isTTY) {
268 |     console.error(
269 |       "FATAL: Logs directory configuration is invalid or could not be created. Please check permissions and path. Exiting.",
270 |     );
271 |   }
272 |   process.exit(1);
273 | }
274 | // --- End Logs Directory Handling ---
275 | 
276 | /**
277 |  * Main application configuration object.
278 |  * Aggregates settings from validated environment variables and `package.json`.
279 |  */
280 | export const config = {
281 |   /** MCP server name. Env `MCP_SERVER_NAME` > `package.json` name > "atlas-mcp-server". */
282 |   mcpServerName: env.MCP_SERVER_NAME || pkg.name,
283 |   /** MCP server version. Env `MCP_SERVER_VERSION` > `package.json` version > "0.0.0". */
284 |   mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
285 |   /** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
286 |   logLevel: env.MCP_LOG_LEVEL,
287 |   /** Absolute path to the logs directory. From `LOGS_DIR` env var. */
288 |   logsPath: validatedLogsPath,
289 |   /** Runtime environment. From `NODE_ENV` env var. Default: "development". */
290 |   environment: env.NODE_ENV,
291 | 
292 |   /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
293 |   mcpTransportType: env.MCP_TRANSPORT_TYPE,
294 |   /** HTTP server port (if http transport). From `MCP_HTTP_PORT` env var. Default: 3010. */
295 |   mcpHttpPort: env.MCP_HTTP_PORT,
296 |   /** HTTP server host (if http transport). From `MCP_HTTP_HOST` env var. Default: "127.0.0.1". */
297 |   mcpHttpHost: env.MCP_HTTP_HOST,
298 |   /** Array of allowed CORS origins (http transport). From `MCP_ALLOWED_ORIGINS` (comma-separated). */
299 |   mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",")
300 |     .map((origin) => origin.trim())
301 |     .filter(Boolean),
302 |   /** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
303 |   mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY,
304 | 
305 |   /** Neo4j connection URI. From `NEO4J_URI`. */
306 |   neo4jUri: env.NEO4J_URI,
307 |   /** Neo4j username. From `NEO4J_USER`. */
308 |   neo4jUser: env.NEO4J_USER,
309 |   /** Neo4j password. From `NEO4J_PASSWORD`. */
310 |   neo4jPassword: env.NEO4J_PASSWORD,
311 | 
312 |   /** Backup configuration. */
313 |   backup: {
314 |     /** Maximum number of backups to keep. From `BACKUP_MAX_COUNT`. */
315 |     maxBackups: env.BACKUP_MAX_COUNT,
316 |     /** Absolute path to the backup directory. From `BACKUP_FILE_DIR`. */
317 |     backupPath: validatedBackupPath,
318 |   },
319 | 
320 |   /** Security-related configurations. */
321 |   security: {
322 |     /** Indicates if authentication is required. True if `MCP_AUTH_SECRET_KEY` is set. */
323 |     authRequired: !!env.MCP_AUTH_SECRET_KEY,
324 |     /** Rate limiting window in milliseconds. From `MCP_RATE_LIMIT_WINDOW_MS`. */
325 |     rateLimitWindowMs: env.MCP_RATE_LIMIT_WINDOW_MS,
326 |     /** Maximum number of requests allowed per window. From `MCP_RATE_LIMIT_MAX_REQUESTS`. */
327 |     rateLimitMaxRequests: env.MCP_RATE_LIMIT_MAX_REQUESTS,
328 |   },
329 | 
330 |   /** OAuth Proxy configurations. Undefined if no related env vars are set. */
331 |   oauthProxy:
332 |     env.OAUTH_PROXY_AUTHORIZATION_URL ||
333 |     env.OAUTH_PROXY_TOKEN_URL ||
334 |     env.OAUTH_PROXY_REVOCATION_URL ||
335 |     env.OAUTH_PROXY_ISSUER_URL ||
336 |     env.OAUTH_PROXY_SERVICE_DOCUMENTATION_URL ||
337 |     env.OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URIS
338 |       ? {
339 |           authorizationUrl: env.OAUTH_PROXY_AUTHORIZATION_URL,
340 |           tokenUrl: env.OAUTH_PROXY_TOKEN_URL,
341 |           revocationUrl: env.OAUTH_PROXY_REVOCATION_URL,
342 |           issuerUrl: env.OAUTH_PROXY_ISSUER_URL,
343 |           serviceDocumentationUrl: env.OAUTH_PROXY_SERVICE_DOCUMENTATION_URL,
344 |           defaultClientRedirectUris:
345 |             env.OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URIS?.split(",")
346 |               .map((uri) => uri.trim())
347 |               .filter(Boolean),
348 |         }
349 |       : undefined,
350 | };
351 | 
352 | /**
353 |  * Configured logging level for the application.
354 |  * Exported for convenience.
355 |  */
356 | export const logLevel: string = config.logLevel;
357 | 
358 | /**
359 |  * Configured runtime environment ("development", "production", etc.).
360 |  * Exported for convenience.
361 |  */
362 | export const environment: string = config.environment;
363 | 
```

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

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { BaseErrorCode, McpError } from "../../../types/errors.js";
  3 | import { McpToolResponse, ResponseFormat } from "../../../types/mcp.js";
  4 | import {
  5 |   createToolExample,
  6 |   createToolMetadata,
  7 |   registerTool,
  8 |   ToolContext,
  9 | } from "../../../types/tool.js";
 10 | import { logger, requestContextService } from "../../../utils/index.js"; // Import requestContextService
 11 | // ToolContext is now imported from ../../../types/tool.js
 12 | import { deepResearch } from "./deepResearch.js";
 13 | import { formatDeepResearchResponse } from "./responseFormat.js";
 14 | import {
 15 |   AtlasDeepResearchInput,
 16 |   AtlasDeepResearchInputSchema,
 17 |   AtlasDeepResearchOutputSchema,
 18 |   AtlasDeepResearchSchemaShape,
 19 | } from "./types.js";
 20 | 
 21 | /**
 22 |  * Main handler function for the `atlas_deep_research` MCP tool.
 23 |  * This function orchestrates the tool's execution:
 24 |  * 1. Validates the incoming parameters against the `AtlasDeepResearchInputSchema`.
 25 |  * 2. Calls the core `deepResearch` function to perform the business logic.
 26 |  * 3. Formats the result into the appropriate `McpToolResponse` based on the requested `responseFormat`.
 27 |  * 4. Handles errors gracefully, logging them and returning appropriate `McpError` responses.
 28 |  *
 29 |  * @param params - The raw, unvalidated input parameters received from the MCP client.
 30 |  * @param context - Optional context object containing request-specific information (e.g., request ID).
 31 |  * @returns A promise resolving to an `McpToolResponse` object.
 32 |  * @throws {McpError} If input validation fails or an unhandled error occurs during execution.
 33 |  */
 34 | async function handler(
 35 |   params: unknown,
 36 |   context?: ToolContext,
 37 | ): Promise<McpToolResponse> {
 38 |   const reqContext =
 39 |     context?.requestContext ??
 40 |     requestContextService.createRequestContext({
 41 |       toolName: "atlas_deep_research_handler",
 42 |     });
 43 |   logger.debug("Received atlas_deep_research request", {
 44 |     ...reqContext,
 45 |     params,
 46 |   });
 47 | 
 48 |   // 1. Validate Input
 49 |   const validationResult = AtlasDeepResearchInputSchema.safeParse(params);
 50 |   if (!validationResult.success) {
 51 |     logger.error(
 52 |       "Invalid input for atlas_deep_research",
 53 |       validationResult.error,
 54 |       {
 55 |         // Pass error object
 56 |         ...reqContext,
 57 |         // errors: validationResult.error.errors, // This is now part of the error object
 58 |       },
 59 |     );
 60 |     throw new McpError(
 61 |       BaseErrorCode.VALIDATION_ERROR,
 62 |       "Invalid input parameters for atlas_deep_research",
 63 |       validationResult.error.format(), // Provides detailed validation errors
 64 |     );
 65 |   }
 66 |   const input: AtlasDeepResearchInput = validationResult.data;
 67 | 
 68 |   // Optional: Implement permission checks here if necessary
 69 |   // e.g., checkPermission(context, 'knowledge:create');
 70 | 
 71 |   try {
 72 |     // 2. Call Core Logic
 73 |     logger.info(
 74 |       `Calling deepResearch core logic for request ID: ${reqContext.requestId}`,
 75 |       reqContext,
 76 |     );
 77 |     const result = await deepResearch(input);
 78 | 
 79 |     // 3. Format Response
 80 |     logger.debug(
 81 |       `Formatting atlas_deep_research response for request ID: ${reqContext.requestId}`,
 82 |       reqContext,
 83 |     );
 84 |     if (input.responseFormat === ResponseFormat.JSON) {
 85 |       // Return raw JSON output if requested
 86 |       return {
 87 |         content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
 88 |         isError: !result.success, // Reflect the success status in the MCP response
 89 |       };
 90 |     } else {
 91 |       // Use the dedicated formatter for 'formatted' output
 92 |       return formatDeepResearchResponse(result, input);
 93 |     }
 94 |   } catch (error) {
 95 |     logger.error("Error executing atlas_deep_research", error as Error, {
 96 |       ...reqContext,
 97 |       // errorMessage and stack are now part of the error object
 98 |     });
 99 |     // Re-throw errors that are already McpError instances
100 |     if (error instanceof McpError) {
101 |       throw error;
102 |     }
103 |     // Wrap unexpected errors in a standard internal error response, including requestId if available
104 |     const errMessage = `Atlas deep research tool execution failed (Request ID: ${reqContext.requestId ?? "N/A"}): ${
105 |       error instanceof Error ? error.message : String(error)
106 |     }`;
107 |     throw new McpError(BaseErrorCode.INTERNAL_ERROR, errMessage);
108 |   }
109 | }
110 | 
111 | // Define Tool Examples
112 | const examples = [
113 |   createToolExample(
114 |     // Example 1: Structured technical research with comprehensive subtasks
115 |     {
116 |       projectId: "proj_123abc",
117 |       researchTopic: "Quantum-Resistant Encryption Algorithms",
118 |       researchGoal:
119 |         "Systematically identify and critically evaluate leading PQC algorithms, analyzing their technical strengths/limitations and projected adoption timelines.",
120 |       scopeDefinition:
121 |         "Focus on NIST PQC finalists and standardized algorithms with practical implementation potential. Exclude purely theoretical approaches without near-term implementation viability.",
122 |       subTopics: [
123 |         {
124 |           question:
125 |             "What are the fundamental taxonomic categories of post-quantum cryptography (PQC) and their underlying mathematical foundations?",
126 |           initialSearchQueries: [
127 |             "PQC taxonomic classification",
128 |             "lattice-based cryptography NIST",
129 |             "hash-based signature schemes",
130 |             "code-based encryption methods",
131 |             "multivariate cryptographic systems",
132 |           ],
133 |           nodeId: "client_sub_001", // Example client-provided ID
134 |           priority: "high", // Strategic priority assignment
135 |           initialStatus: "todo",
136 |         },
137 |         {
138 |           question:
139 |             "Which specific algorithms have achieved NIST PQC standardization status or finalist positions?",
140 |           initialSearchQueries: [
141 |             "NIST PQC Round 3 finalists",
142 |             "CRYSTALS-Kyber specification",
143 |             "CRYSTALS-Dilithium implementation",
144 |             "Falcon signature scheme",
145 |             "SPHINCS+ hash-based signatures",
146 |           ],
147 |           assignedTo: "user_alice", // Clear accountability assignment
148 |         },
149 |         {
150 |           question:
151 |             "What are the quantifiable performance characteristics and resource requirements (computational overhead, key/signature sizes) for leading PQC algorithms?",
152 |           initialSearchQueries: [
153 |             "PQC comparative performance metrics",
154 |             "Kyber key size benchmarks",
155 |             "Dilithium signature size optimization",
156 |           ],
157 |           priority: "medium",
158 |         },
159 |         {
160 |           question:
161 |             "What practical implementation challenges and realistic adoption timelines exist for PQC deployment across critical infrastructure?",
162 |           initialSearchQueries: [
163 |             "PQC integration challenges enterprise systems",
164 |             "quantum-resistant migration strategy financial sector",
165 |             "realistic quantum threat timeline infrastructure",
166 |           ],
167 |         },
168 |       ],
169 |       researchDomain: "technical",
170 |       initialTags: ["#cryptography", "#pqc", "#cybersecurity"],
171 |       planNodeId: "client_plan_001", // Example client-provided ID
172 |       createTasks: true, // Enable operational workflow integration
173 |       responseFormat: "formatted",
174 |     },
175 |     // Expected formatted output (conceptual, actual output depends on formatter)
176 |     `## Structured Deep Research Plan Initiated\n**Topic:** Quantum-Resistant Encryption Algorithms\n**Goal:** Systematically identify and critically evaluate leading PQC algorithms...\n**Plan Node ID:** plan_...\n**Sub-Topics Created:** 4 (with Operational Tasks)\n- **Question:** What are the fundamental taxonomic categories...?\n  - **Knowledge Node ID:** \`sub_...\`\n  - **Task ID:** \`task_...\`\n  - **Strategic Priority:** high\n  - **Workflow Status:** todo\n  - **Precision Search Queries:** ...\n... (additional focused sub-topics)`,
177 |     "Initiate a comprehensive technical deep research plan on post-quantum cryptography, creating structured knowledge nodes with corresponding prioritized workflow tasks and precise search queries for systematic investigation.",
178 |   ),
179 |   createToolExample(
180 |     // Example 2: Strategic market analysis with focused inquiries
181 |     {
182 |       projectId: "proj_456def",
183 |       researchTopic:
184 |         "Strategic Market Analysis for AI-Powered Code Review Tools",
185 |       researchGoal:
186 |         "Identify key market participants, quantify addressable market size, and identify emerging technology and adoption trends.",
187 |       subTopics: [
188 |         {
189 |           question:
190 |             "Who are the established and emerging competitors within the AI code review space?",
191 |           initialSearchQueries: [
192 |             "leading AI code review platforms",
193 |             "GitHub Copilot market position",
194 |             "emerging static analysis AI tools",
195 |           ],
196 |         },
197 |         {
198 |           question:
199 |             "What is the current market valuation and projected compound annual growth rate (CAGR) for developer tools with AI integration?",
200 |           initialSearchQueries: [
201 |             "developer tools market size analysis 2025",
202 |             "AI code review CAGR forecast",
203 |             "static analysis tools market growth",
204 |           ],
205 |         },
206 |         {
207 |           question:
208 |             "What differentiated pricing models and monetization strategies are proving most effective in this market segment?",
209 |           initialSearchQueries: [
210 |             "AI code review pricing models comparison",
211 |             "developer tools subscription economics",
212 |             "open-core vs SaaS code analysis tools",
213 |           ],
214 |         },
215 |       ],
216 |       createTasks: false, // Focus on knowledge capture without operational workflow items
217 |       responseFormat: "json", // Request machine-processable structured output
218 |     },
219 |     // Expected JSON output (structure matches AtlasDeepResearchOutput)
220 |     `{
221 |       "success": true,
222 |       "message": "Successfully created comprehensive research plan \\"Strategic Market Analysis for AI-Powered Code Review Tools\\" with primary knowledge node plan_... and 3 specialized sub-topic nodes.",
223 |       "planNodeId": "plan_...",
224 |       "initialTags": [],
225 |       "subTopicNodes": [
226 |         { 
227 |           "question": "Who are the established and emerging competitors within the AI code review space?", 
228 |           "nodeId": "sub_...", 
229 |           "initialSearchQueries": ["leading AI code review platforms", "GitHub Copilot market position", "emerging static analysis AI tools"]
230 |         },
231 |         { 
232 |           "question": "What is the current market valuation and projected compound annual growth rate (CAGR) for developer tools with AI integration?", 
233 |           "nodeId": "sub_...", 
234 |           "initialSearchQueries": ["developer tools market size analysis 2025", "AI code review CAGR forecast", "static analysis tools market growth"]
235 |         },
236 |         { 
237 |           "question": "What differentiated pricing models and monetization strategies are proving most effective in this market segment?", 
238 |           "nodeId": "sub_...", 
239 |           "initialSearchQueries": ["AI code review pricing models comparison", "developer tools subscription economics", "open-core vs SaaS code analysis tools"]
240 |         }
241 |       ],
242 |       "tasksCreated": false
243 |     }`,
244 |     "Conduct targeted market intelligence gathering on AI code review tools ecosystem, focusing on competitive landscape analysis, market sizing, and business model evaluation, with precise search parameters for each inquiry area.",
245 |   ),
246 | ];
247 | 
248 | /**
249 |  * Registers the `atlas_deep_research` tool, including its metadata, schema,
250 |  * handler function, and examples, with the provided MCP server instance.
251 |  *
252 |  * @param server - The `McpServer` instance to register the tool with.
253 |  */
254 | export function registerAtlasDeepResearchTool(server: McpServer): void {
255 |   registerTool(
256 |     server,
257 |     "atlas_deep_research", // Tool name
258 |     "Initiates a strategically structured deep research process by creating a hierarchical knowledge plan within the Atlas system, optionally generating linked operational tasks for systematic investigation. Facilitates methodical research workflows by emphasizing initial collection of high-specificity factual details (proper nouns, specific terminology, precise identifiers) relevant to the inquiry domain, followed by targeted recursive investigation to build comprehensive knowledge graphs. This tool operationalizes research by decomposing complex topics into discrete, manageable components with clear investigative parameters, optimizing for both depth and efficiency in knowledge acquisition. Use it to orchestrate comprehensive research initiatives, construct semantic knowledge networks with well-defined relationships, and ensure continuous knowledge base enrichment with high-precision, factually-verified information.", // Enhanced tool description
259 |     AtlasDeepResearchSchemaShape, // Input schema shape (used to generate full schema)
260 |     handler, // The handler function defined above
261 |     createToolMetadata({
262 |       examples: examples, // Tool usage examples
263 |       // Required permissions might need adjustment if task creation is always enabled or based on input
264 |       requiredPermission: "knowledge:create task:create", // Combined into single string
265 |       returnSchema: AtlasDeepResearchOutputSchema, // Schema for the structured output
266 |       // Optional: Define rate limits if needed
267 |       // rateLimit: { windowMs: 60 * 1000, maxRequests: 10 }
268 |     }),
269 |   );
270 |   const registerContext = requestContextService.createRequestContext({
271 |     operation: "registerAtlasDeepResearchTool",
272 |   });
273 |   logger.info("Registered atlas_deep_research tool.", registerContext);
274 | }
275 | 
```

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

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import {
  4 |   ProjectStatus,
  5 |   ResponseFormat,
  6 |   createProjectStatusEnum, // Import the enum helper
  7 |   createResponseFormatEnum,
  8 |   createToolResponse,
  9 | } from "../../../types/mcp.js";
 10 | import {
 11 |   createToolExample,
 12 |   createToolMetadata,
 13 |   registerTool,
 14 | } from "../../../types/tool.js";
 15 | import { listProjects } from "./listProjects.js";
 16 | import { ProjectListRequest } from "./types.js";
 17 | import { formatProjectListResponse } from "./responseFormat.js";
 18 | 
 19 | /**
 20 |  * Registers the atlas_project_list tool with the MCP server
 21 |  *
 22 |  * @param server The MCP server instance
 23 |  */
 24 | export function registerAtlasProjectListTool(server: McpServer): void {
 25 |   registerTool(
 26 |     server,
 27 |     "atlas_project_list",
 28 |     "Retrieves and filters project entities based on specified criteria with pagination support and relationship expansion capabilities",
 29 |     {
 30 |       mode: z
 31 |         .enum(["all", "details"])
 32 |         .optional()
 33 |         .default("all")
 34 |         .describe(
 35 |           'Listing mode - "all" for paginated list of projects, "details" for comprehensive single project information',
 36 |         ),
 37 |       id: z
 38 |         .string()
 39 |         .optional()
 40 |         .describe(
 41 |           'Project ID to retrieve complete details for, including relationships (required for mode="details")',
 42 |         ),
 43 |       page: z
 44 |         .number()
 45 |         .min(1)
 46 |         .optional()
 47 |         .default(1)
 48 |         .describe(
 49 |           'Page number for paginated results when using mode="all" (Default: 1)',
 50 |         ),
 51 |       limit: z
 52 |         .number()
 53 |         .min(1)
 54 |         .max(100)
 55 |         .optional()
 56 |         .default(20)
 57 |         .describe(
 58 |           "Number of results per page, minimum 1, maximum 100 (Default: 20)",
 59 |         ),
 60 |       includeKnowledge: z
 61 |         .boolean()
 62 |         .optional()
 63 |         .default(false)
 64 |         .describe(
 65 |           "Boolean flag to include associated knowledge items with the project results (Default: false)",
 66 |         ),
 67 |       includeTasks: z
 68 |         .boolean()
 69 |         .optional()
 70 |         .default(false)
 71 |         .describe(
 72 |           "Boolean flag to include associated tasks in the response (Default: false)",
 73 |         ),
 74 |       taskType: z
 75 |         .string()
 76 |         .optional()
 77 |         .describe("Filter results by project classification or category type"),
 78 |       status: z
 79 |         .union([
 80 |           createProjectStatusEnum(), // Use the enum helper
 81 |           z.array(createProjectStatusEnum()), // Use the enum helper for arrays too
 82 |         ])
 83 |         .optional()
 84 |         .describe("Filter results by project status or multiple statuses"),
 85 |       responseFormat: createResponseFormatEnum()
 86 |         .optional()
 87 |         .default(ResponseFormat.FORMATTED)
 88 |         .describe(
 89 |           "Desired response format: 'formatted' (default string) or 'json' (raw object)",
 90 |         ),
 91 |     },
 92 |     async (input, context) => {
 93 |       // Parse and process input (assuming validation happens implicitly via registerTool)
 94 |       const validatedInput = input as unknown as ProjectListRequest & {
 95 |         responseFormat?: ResponseFormat;
 96 |       };
 97 |       const result = await listProjects(validatedInput);
 98 | 
 99 |       // Conditionally format response
100 |       if (validatedInput.responseFormat === ResponseFormat.JSON) {
101 |         return createToolResponse(JSON.stringify(result, null, 2));
102 |       } else {
103 |         // Return the result using the formatter for rich display
104 |         return formatProjectListResponse(result);
105 |       }
106 |     },
107 |     createToolMetadata({
108 |       examples: [
109 |         createToolExample(
110 |           {
111 |             mode: "all",
112 |             limit: 5,
113 |           },
114 |           `{
115 |             "projects": [
116 |               {
117 |                 "id": "proj_ms_migration",
118 |                 "name": "Microservice Architecture Migration",
119 |                 "description": "Refactor monolithic application into scalable microservices architecture with distributed data stores and API gateway",
120 |                 "status": "active",
121 |                 "urls": [
122 |                   {"title": "MCP Server Repository", "url": "https://github.com/cyanheads/atlas-mcp-server.git"}, 
123 |                   {"title": "Technical Spec", "url": "file:///Users/username/project_name/docs/atlas-reference.md"}
124 |                 ],
125 |                 "completionRequirements": "All critical services migrated with 100% test coverage, performance metrics meeting SLAs, and zero regressions in core functionality",
126 |                 "outputFormat": "Containerized services with CI/CD pipelines, comprehensive API documentation, and migration runbook",
127 |                 "taskType": "integration",
128 |                 "createdAt": "2025-03-23T10:11:24.123Z",
129 |                 "updatedAt": "2025-03-23T10:11:24.123Z"
130 |               },
131 |               {
132 |                 "id": "proj_graphql",
133 |                 "name": "GraphQL API Implementation",
134 |                 "description": "Design and implement GraphQL API layer to replace existing REST endpoints with optimized query capabilities",
135 |                 "status": "in-progress",
136 |                 "urls": [
137 |                   {"title": "MCP Types Definition", "url": "https://github.com/cyanheads/atlas-mcp-server.git/blob/main/src/types/mcp.ts"},
138 |                   {"title": "Neo4j Schema", "url": "file:///Users/username/project_name/docs/neo4j-schema.md"}
139 |                 ],
140 |                 "completionRequirements": "API supports all current use cases with n+1 query optimization, proper error handling, and 95% test coverage",
141 |                 "outputFormat": "TypeScript-based GraphQL schema with resolvers, documentation, and integration tests",
142 |                 "taskType": "generation",
143 |                 "createdAt": "2025-03-23T10:11:24.456Z",
144 |                 "updatedAt": "2025-03-23T10:11:24.456Z"
145 |               }
146 |             ],
147 |             "total": 2,
148 |             "page": 1,
149 |             "limit": 5,
150 |             "totalPages": 1
151 |           }`,
152 |           "Retrieve project portfolio with pagination controls",
153 |         ),
154 |         createToolExample(
155 |           {
156 |             mode: "details",
157 |             id: "proj_ms_migration",
158 |             includeTasks: true,
159 |             includeKnowledge: true,
160 |           },
161 |           `{
162 |             "projects": [
163 |               {
164 |                 "id": "proj_ms_migration",
165 |                 "name": "Microservice Architecture Migration",
166 |                 "description": "Refactor monolithic application into scalable microservices architecture with distributed data stores and API gateway",
167 |                 "status": "active",
168 |                 "urls": [
169 |                   {"title": "MCP Server Repository", "url": "https://github.com/cyanheads/atlas-mcp-server.git"}, 
170 |                   {"title": "Technical Spec", "url": "file:///Users/username/project_name/docs/atlas-reference.md"},
171 |                   {"title": "MCP Docs", "url": "https://modelcontextprotocol.io/"}
172 |                 ],
173 |                 "completionRequirements": "All critical services migrated with 100% test coverage, performance metrics meeting SLAs, and zero regressions in core functionality",
174 |                 "outputFormat": "Containerized services with CI/CD pipelines, comprehensive API documentation, and migration runbook",
175 |                 "taskType": "integration",
176 |                 "createdAt": "2025-03-23T10:11:24.123Z",
177 |                 "updatedAt": "2025-03-23T10:11:24.123Z",
178 |                 "tasks": [
179 |                   {
180 |                     "id": "task_auth_svc",
181 |                     "title": "Authentication Service Extraction",
182 |                     "status": "in_progress",
183 |                     "priority": "critical",
184 |                     "createdAt": "2025-03-23T10:15:32.123Z"
185 |                   },
186 |                   {
187 |                     "id": "task_api_gateway",
188 |                     "title": "API Gateway Implementation with Kong",
189 |                     "status": "todo",
190 |                     "priority": "high",
191 |                     "createdAt": "2025-03-23T10:17:45.123Z"
192 |                   }
193 |                 ],
194 |                 "knowledge": [
195 |                   {
196 |                     "id": "know_saga_pattern",
197 |                     "text": "Distributed transactions must use Saga pattern with compensating actions to maintain data integrity across services",
198 |                     "tags": ["architecture", "data-integrity", "patterns"],
199 |                     "domain": "technical",
200 |                     "createdAt": "2025-03-23T11:22:14.789Z"
201 |                   },
202 |                   {
203 |                     "id": "know_rate_limiting",
204 |                     "text": "Rate limiting should be implemented at the API Gateway level using Redis-based token bucket algorithm",
205 |                     "tags": ["api-gateway", "performance", "security"],
206 |                     "domain": "technical",
207 |                     "createdAt": "2025-03-23T12:34:27.456Z"
208 |                   }
209 |                 ]
210 |               }
211 |             ],
212 |             "total": 1,
213 |             "page": 1,
214 |             "limit": 20,
215 |             "totalPages": 1
216 |           }`,
217 |           "Retrieve comprehensive project details with associated tasks and technical knowledge",
218 |         ),
219 |         createToolExample(
220 |           {
221 |             mode: "all",
222 |             status: ["active", "in-progress"],
223 |             taskType: "analysis",
224 |           },
225 |           `{
226 |             "projects": [
227 |               {
228 |                 "id": "proj_perf",
229 |                 "name": "Performance Optimization Suite",
230 |                 "description": "Identify and resolve frontend rendering bottlenecks in React application through profiling and optimization techniques",
231 |                 "status": "active",
232 |                 "urls": [
233 |                   {"title": "Lighthouse CI Results", "url": "https://lighthouse-ci.app/dashboard?project=frontend-perf"},
234 |                   {"title": "Web Vitals Tracking", "url": "https://analytics.google.com/web-vitals"}
235 |                 ],
236 |                 "completionRequirements": "Core React components meet Web Vitals thresholds with 50% reduction in LCP and TTI metrics",
237 |                 "outputFormat": "Optimized component library, performance test suite, and technical recommendation document",
238 |                 "taskType": "analysis",
239 |                 "createdAt": "2025-03-23T10:11:24.123Z",
240 |                 "updatedAt": "2025-03-23T10:11:24.123Z"
241 |               },
242 |               {
243 |                 "id": "proj_security",
244 |                 "name": "Security Vulnerability Assessment",
245 |                 "description": "Comprehensive security analysis of authentication flow and data storage with OWASP compliance verification",
246 |                 "status": "in-progress",
247 |                 "urls": [
248 |                   {"title": "OWASP Top 10", "url": "https://owasp.org/Top10/"},
249 |                   {"title": "Security Checklist", "url": "file:///Users/username/project_name/security/assessment-checklist.md"}
250 |                 ],
251 |                 "completionRequirements": "All high and critical vulnerabilities resolved, compliance with OWASP Top 10, and security test coverage exceeding 90%",
252 |                 "outputFormat": "Security report with remediation steps, updated authentication flow, and automated security test suite",
253 |                 "taskType": "analysis",
254 |                 "createdAt": "2025-03-24T09:34:12.789Z",
255 |                 "updatedAt": "2025-03-24T09:34:12.789Z"
256 |               }
257 |             ],
258 |             "total": 2,
259 |             "page": 1,
260 |             "limit": 20,
261 |             "totalPages": 1
262 |           }`,
263 |           "Query projects by lifecycle state and classification type",
264 |         ),
265 |       ],
266 |       requiredPermission: "project:read",
267 |       entityType: "project",
268 |       returnSchema: z.object({
269 |         projects: z.array(
270 |           z.object({
271 |             id: z.string().describe("Project ID"),
272 |             name: z.string().describe("Project name"),
273 |             description: z.string().describe("Project description"),
274 |             status: z.string().describe("Project status"),
275 |             urls: z
276 |               .array(
277 |                 z.object({
278 |                   title: z.string(),
279 |                   url: z.string(),
280 |                 }),
281 |               )
282 |               .describe("Reference materials"),
283 |             completionRequirements: z.string().describe("Completion criteria"),
284 |             outputFormat: z.string().describe("Deliverable format"),
285 |             taskType: z.string().describe("Project classification"),
286 |             createdAt: z.string().describe("Creation timestamp"),
287 |             updatedAt: z.string().describe("Last update timestamp"),
288 |             knowledge: z
289 |               .array(
290 |                 z.object({
291 |                   id: z.string(),
292 |                   text: z.string(),
293 |                   tags: z.array(z.string()).optional(),
294 |                   domain: z.string(),
295 |                   createdAt: z.string(),
296 |                 }),
297 |               )
298 |               .optional()
299 |               .describe("Associated knowledge items (if requested)"),
300 |             tasks: z
301 |               .array(
302 |                 z.object({
303 |                   id: z.string(),
304 |                   title: z.string(),
305 |                   status: z.string(),
306 |                   priority: z.string(),
307 |                   createdAt: z.string(),
308 |                 }),
309 |               )
310 |               .optional()
311 |               .describe("Associated tasks (if requested)"),
312 |           }),
313 |         ),
314 |         total: z
315 |           .number()
316 |           .describe("Total number of projects matching criteria"),
317 |         page: z.number().describe("Current page number"),
318 |         limit: z.number().describe("Number of items per page"),
319 |         totalPages: z.number().describe("Total number of pages"),
320 |       }),
321 |       rateLimit: {
322 |         windowMs: 60 * 1000, // 1 minute
323 |         maxRequests: 30, // 30 requests per minute
324 |       },
325 |     }),
326 |   );
327 | }
328 | 
```

--------------------------------------------------------------------------------
/src/services/neo4j/utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { logger, requestContextService } from "../../utils/index.js"; // Updated import path
  2 | import { neo4jDriver } from "./driver.js";
  3 | import {
  4 |   NodeLabels,
  5 |   PaginatedResult,
  6 |   PaginationOptions,
  7 |   RelationshipTypes,
  8 | } from "./types.js";
  9 | import { Record as Neo4jRecord } from "neo4j-driver"; // Import Record type
 10 | 
 11 | /**
 12 |  * Database utility functions for Neo4j
 13 |  */
 14 | export class Neo4jUtils {
 15 |   /**
 16 |    * Initialize the Neo4j database schema with constraints and indexes
 17 |    * Should be called at application startup
 18 |    */
 19 |   static async initializeSchema(): Promise<void> {
 20 |     const session = await neo4jDriver.getSession();
 21 |     const reqContext = requestContextService.createRequestContext({
 22 |       operation: "Neo4jUtils.initializeSchema",
 23 |     });
 24 |     try {
 25 |       logger.info("Initializing Neo4j database schema", reqContext);
 26 | 
 27 |       const constraints = [
 28 |         `CREATE CONSTRAINT project_id_unique IF NOT EXISTS FOR (p:${NodeLabels.Project}) REQUIRE p.id IS UNIQUE`,
 29 |         `CREATE CONSTRAINT task_id_unique IF NOT EXISTS FOR (t:${NodeLabels.Task}) REQUIRE t.id IS UNIQUE`,
 30 |         `CREATE CONSTRAINT knowledge_id_unique IF NOT EXISTS FOR (k:${NodeLabels.Knowledge}) REQUIRE k.id IS UNIQUE`,
 31 |         `CREATE CONSTRAINT user_id_unique IF NOT EXISTS FOR (u:${NodeLabels.User}) REQUIRE u.id IS UNIQUE`,
 32 |         `CREATE CONSTRAINT citation_id_unique IF NOT EXISTS FOR (c:${NodeLabels.Citation}) REQUIRE c.id IS UNIQUE`,
 33 |         `CREATE CONSTRAINT tasktype_name_unique IF NOT EXISTS FOR (t:${NodeLabels.TaskType}) REQUIRE t.name IS UNIQUE`,
 34 |         `CREATE CONSTRAINT domain_name_unique IF NOT EXISTS FOR (d:${NodeLabels.Domain}) REQUIRE d.name IS UNIQUE`,
 35 |       ];
 36 | 
 37 |       const indexes = [
 38 |         `CREATE INDEX project_status IF NOT EXISTS FOR (p:${NodeLabels.Project}) ON (p.status)`,
 39 |         `CREATE INDEX project_taskType IF NOT EXISTS FOR (p:${NodeLabels.Project}) ON (p.taskType)`,
 40 |         `CREATE INDEX task_status IF NOT EXISTS FOR (t:${NodeLabels.Task}) ON (t.status)`,
 41 |         `CREATE INDEX task_priority IF NOT EXISTS FOR (t:${NodeLabels.Task}) ON (t.priority)`,
 42 |         `CREATE INDEX task_projectId IF NOT EXISTS FOR (t:${NodeLabels.Task}) ON (t.projectId)`,
 43 |         `CREATE INDEX knowledge_projectId IF NOT EXISTS FOR (k:${NodeLabels.Knowledge}) ON (k.projectId)`,
 44 |       ];
 45 | 
 46 |       // Full-text indexes (check compatibility with Community Edition version)
 47 |       // These might require specific configuration or versions. Wrap in try-catch if needed.
 48 |       const fullTextIndexes = [
 49 |         `CREATE FULLTEXT INDEX project_fulltext IF NOT EXISTS FOR (p:${NodeLabels.Project}) ON EACH [p.name, p.description]`,
 50 |         `CREATE FULLTEXT INDEX task_fulltext IF NOT EXISTS FOR (t:${NodeLabels.Task}) ON EACH [t.title, t.description]`,
 51 |         `CREATE FULLTEXT INDEX knowledge_fulltext IF NOT EXISTS FOR (k:${NodeLabels.Knowledge}) ON EACH [k.text]`,
 52 |       ];
 53 | 
 54 |       // Execute schema creation queries within a transaction
 55 |       await session.executeWrite(async (tx) => {
 56 |         for (const query of [...constraints, ...indexes, ...fullTextIndexes]) {
 57 |           try {
 58 |             await tx.run(query);
 59 |           } catch (error) {
 60 |             // Log index creation errors but don't necessarily fail initialization
 61 |             // Especially full-text might not be supported/enabled
 62 |             const errorMessage =
 63 |               error instanceof Error ? error.message : String(error);
 64 |             if (query.includes("FULLTEXT")) {
 65 |               logger.warning(
 66 |                 `Could not create full-text index (potentially unsupported): ${errorMessage}. Query: ${query}`,
 67 |                 { ...reqContext, queryFailed: query },
 68 |               );
 69 |             } else {
 70 |               logger.error(
 71 |                 `Failed to execute schema query: ${errorMessage}. Query: ${query}`,
 72 |                 error as Error,
 73 |                 { ...reqContext, queryFailed: query },
 74 |               );
 75 |               // Rethrow for critical constraints/indexes
 76 |               if (!query.includes("FULLTEXT")) throw error;
 77 |             }
 78 |           }
 79 |         }
 80 |       });
 81 | 
 82 |       logger.info("Neo4j database schema initialization attempted", reqContext);
 83 |     } catch (error) {
 84 |       const errorMessage =
 85 |         error instanceof Error ? error.message : String(error);
 86 |       logger.error(
 87 |         "Failed to initialize Neo4j database schema",
 88 |         error as Error,
 89 |         { ...reqContext, detail: errorMessage },
 90 |       );
 91 |       throw error;
 92 |     } finally {
 93 |       await session.close();
 94 |     }
 95 |   }
 96 | 
 97 |   /**
 98 |    * Clear all data from the database and reinitialize the schema
 99 |    * WARNING: This permanently deletes all data
100 |    */
101 |   static async clearDatabase(): Promise<void> {
102 |     const session = await neo4jDriver.getSession();
103 |     const reqContext = requestContextService.createRequestContext({
104 |       operation: "Neo4jUtils.clearDatabase",
105 |     });
106 |     try {
107 |       logger.warning("Clearing all data from Neo4j database", reqContext);
108 | 
109 |       // Delete all nodes and relationships
110 |       await session.executeWrite(async (tx) => {
111 |         await tx.run("MATCH (n) DETACH DELETE n");
112 |       });
113 | 
114 |       // Recreate schema
115 |       await this.initializeSchema();
116 | 
117 |       logger.info("Neo4j database cleared successfully", reqContext);
118 |     } catch (error) {
119 |       const errorMessage =
120 |         error instanceof Error ? error.message : String(error);
121 |       logger.error("Failed to clear Neo4j database", error as Error, {
122 |         ...reqContext,
123 |         detail: errorMessage,
124 |       });
125 |       throw error;
126 |     } finally {
127 |       await session.close();
128 |     }
129 |   }
130 | 
131 |   /**
132 |    * Apply pagination to query results
133 |    * @param data Array of data to paginate
134 |    * @param options Pagination options
135 |    * @returns Paginated result object
136 |    */
137 |   static paginateResults<T>(
138 |     data: T[],
139 |     options: PaginationOptions = {},
140 |   ): PaginatedResult<T> {
141 |     const page = Math.max(options.page || 1, 1);
142 |     const limit = Math.min(Math.max(options.limit || 20, 1), 100); // Ensure limit is between 1 and 100
143 | 
144 |     const total = data.length;
145 |     const totalPages = Math.ceil(total / limit);
146 |     const startIndex = (page - 1) * limit;
147 |     const endIndex = Math.min(startIndex + limit, total); // Ensure endIndex doesn't exceed total
148 | 
149 |     const paginatedData = data.slice(startIndex, endIndex);
150 | 
151 |     return {
152 |       data: paginatedData,
153 |       total: total,
154 |       page: page,
155 |       limit: limit,
156 |       totalPages: totalPages,
157 |     };
158 |   }
159 | 
160 |   /**
161 |    * Generate a Cypher fragment for array parameters (e.g., for IN checks)
162 |    * @param nodeAlias Alias of the node in the query (e.g., 't' for task)
163 |    * @param propertyName Name of the property on the node (e.g., 'tags')
164 |    * @param paramName Name for the Cypher parameter (e.g., 'tagsList')
165 |    * @param arrayParam Array parameter value
166 |    * @param matchAll If true, use ALL items must be in the node's list. If false (default), use ANY item must be in the node's list.
167 |    * @returns Object with cypher fragment and params
168 |    */
169 |   static generateArrayInListQuery(
170 |     nodeAlias: string,
171 |     propertyName: string,
172 |     paramName: string,
173 |     arrayParam?: string[] | string,
174 |     matchAll: boolean = false,
175 |   ): { cypher: string; params: Record<string, any> } {
176 |     if (!arrayParam || (Array.isArray(arrayParam) && arrayParam.length === 0)) {
177 |       return { cypher: "", params: {} };
178 |     }
179 | 
180 |     const params: Record<string, any> = {};
181 |     const listParam = Array.isArray(arrayParam) ? arrayParam : [arrayParam];
182 |     params[paramName] = listParam;
183 | 
184 |     const operator = matchAll ? "ALL" : "ANY";
185 |     // Cypher syntax for checking if items from a parameter list are in a node's list property
186 |     const cypher = `${operator}(item IN $${paramName} WHERE item IN ${nodeAlias}.${propertyName})`;
187 | 
188 |     return { cypher, params };
189 |   }
190 | 
191 |   /**
192 |    * Validate that a node exists in the database
193 |    * @param label Node label
194 |    * @param property Property to check
195 |    * @param value Value to check
196 |    * @returns True if the node exists, false otherwise
197 |    */
198 |   static async nodeExists(
199 |     label: NodeLabels,
200 |     property: string,
201 |     value: string | number, // Allow number for potential future use
202 |   ): Promise<boolean> {
203 |     const session = await neo4jDriver.getSession();
204 |     try {
205 |       // Use EXISTS for potentially better performance than COUNT
206 |       const query = `
207 |         MATCH (n:${label} {${property}: $value})
208 |         RETURN EXISTS { (n) } AS nodeExists
209 |       `;
210 | 
211 |       const result = await session.executeRead(async (tx) => {
212 |         const res = await tx.run(query, { value });
213 |         return res.records[0]?.get("nodeExists");
214 |       });
215 | 
216 |       return result === true;
217 |     } catch (error) {
218 |       const errorMessage =
219 |         error instanceof Error ? error.message : String(error);
220 |       const errorContext = requestContextService.createRequestContext({
221 |         operation: "Neo4jUtils.nodeExists",
222 |         label,
223 |         property,
224 |         value,
225 |       });
226 |       logger.error(
227 |         `Error checking node existence for ${label} {${property}: ${value}}`,
228 |         error as Error,
229 |         { ...errorContext, detail: errorMessage },
230 |       );
231 |       throw error; // Re-throw error after logging
232 |     } finally {
233 |       await session.close();
234 |     }
235 |   }
236 | 
237 |   /**
238 |    * Validate relationships between nodes
239 |    * @param startLabel Label of the start node
240 |    * @param startProperty Property of the start node to check
241 |    * @param startValue Value of the start node property
242 |    * @param endLabel Label of the end node
243 |    * @param endProperty Property of the end node to check
244 |    * @param endValue Value of the end node property
245 |    * @param relationshipType Type of relationship to check
246 |    * @returns True if the relationship exists, false otherwise
247 |    */
248 |   static async relationshipExists(
249 |     startLabel: NodeLabels,
250 |     startProperty: string,
251 |     startValue: string | number,
252 |     endLabel: NodeLabels,
253 |     endProperty: string,
254 |     endValue: string | number,
255 |     relationshipType: RelationshipTypes,
256 |   ): Promise<boolean> {
257 |     const session = await neo4jDriver.getSession();
258 |     try {
259 |       // Use EXISTS for potentially better performance
260 |       const query = `
261 |         MATCH (a:${startLabel} {${startProperty}: $startValue})
262 |         MATCH (b:${endLabel} {${endProperty}: $endValue})
263 |         RETURN EXISTS { (a)-[:${relationshipType}]->(b) } AS relExists
264 |       `;
265 | 
266 |       const result = await session.executeRead(async (tx) => {
267 |         const res = await tx.run(query, { startValue, endValue });
268 |         return res.records[0]?.get("relExists");
269 |       });
270 | 
271 |       return result === true;
272 |     } catch (error) {
273 |       const errorMessage =
274 |         error instanceof Error ? error.message : String(error);
275 |       const errorContext = requestContextService.createRequestContext({
276 |         operation: "Neo4jUtils.relationshipExists",
277 |         startLabel,
278 |         startProperty,
279 |         startValue,
280 |         endLabel,
281 |         endProperty,
282 |         endValue,
283 |         relationshipType,
284 |       });
285 |       logger.error(
286 |         `Error checking relationship existence: (${startLabel})-[:${relationshipType}]->(${endLabel})`,
287 |         error as Error,
288 |         { ...errorContext, detail: errorMessage },
289 |       );
290 |       throw error;
291 |     } finally {
292 |       await session.close();
293 |     }
294 |   }
295 | 
296 |   /**
297 |    * Generate a timestamp string in ISO format for database operations
298 |    * @returns Current timestamp as ISO string
299 |    */
300 |   static getCurrentTimestamp(): string {
301 |     return new Date().toISOString();
302 |   }
303 | 
304 |   /**
305 |    * Process Neo4j result records into plain JavaScript objects.
306 |    * Assumes the record contains the node or properties under the specified key.
307 |    * @param records Neo4j result records array (RecordShape from neo4j-driver).
308 |    * @param primaryKey The key in the record containing the node or properties map (default: 'n').
309 |    * @returns Processed records as an array of plain objects.
310 |    */
311 |   static processRecords<T>(
312 |     records: Neo4jRecord[],
313 |     primaryKey: string = "n",
314 |   ): T[] {
315 |     if (!records || records.length === 0) {
316 |       return [];
317 |     }
318 | 
319 |     return records
320 |       .map((record) => {
321 |         // Use .toObject() which handles conversion from Neo4j types
322 |         const obj = record.toObject();
323 |         // If the query returns the node directly (e.g., RETURN n), access its properties
324 |         // If the query returns properties directly (e.g., RETURN n.id as id), obj already has them.
325 |         const data = obj[primaryKey]?.properties
326 |           ? obj[primaryKey].properties
327 |           : obj;
328 | 
329 |         // Ensure 'urls' is an array if it exists (handles potential null/undefined from DB)
330 |         if (data && "urls" in data) {
331 |           data.urls = data.urls || [];
332 |         }
333 |         // Ensure 'tags' is an array if it exists
334 |         if (data && "tags" in data) {
335 |           data.tags = data.tags || [];
336 |         }
337 |         // Ensure 'citations' is an array if it exists
338 |         if (data && "citations" in data) {
339 |           data.citations = data.citations || [];
340 |         }
341 | 
342 |         return data as T;
343 |       })
344 |       .filter((item): item is T => item !== null && item !== undefined);
345 |   }
346 | 
347 |   /**
348 |    * Check if the database is empty (no nodes exist)
349 |    * @returns Promise<boolean> - true if database is empty, false otherwise
350 |    */
351 |   static async isDatabaseEmpty(): Promise<boolean> {
352 |     const session = await neo4jDriver.getSession();
353 |     try {
354 |       const query = `
355 |         MATCH (n)
356 |         RETURN count(n) = 0 AS isEmpty
357 |         LIMIT 1
358 |       `;
359 | 
360 |       const result = await session.executeRead(async (tx) => {
361 |         const res = await tx.run(query);
362 |         // If no records are returned (e.g., DB error), assume not empty for safety
363 |         return res.records[0]?.get("isEmpty") ?? false;
364 |       });
365 | 
366 |       return result;
367 |     } catch (error) {
368 |       const errorMessage =
369 |         error instanceof Error ? error.message : String(error);
370 |       const errorContext = requestContextService.createRequestContext({
371 |         operation: "Neo4jUtils.isDatabaseEmpty.error",
372 |       });
373 |       logger.error("Error checking if database is empty", error as Error, {
374 |         ...errorContext,
375 |         detail: errorMessage,
376 |       });
377 |       // If we can't check, assume it's not empty to be safe
378 |       return false;
379 |     } finally {
380 |       await session.close();
381 |     }
382 |   }
383 | }
384 | 
```

--------------------------------------------------------------------------------
/src/services/neo4j/searchService/unifiedSearchLogic.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * @fileoverview Implements the unified search logic for Neo4j entities.
  3 |  * @module src/services/neo4j/searchService/unifiedSearchLogic
  4 |  */
  5 | 
  6 | import { Session, int } from "neo4j-driver";
  7 | import { logger, requestContextService } from "../../../utils/index.js";
  8 | import { neo4jDriver } from "../driver.js";
  9 | import {
 10 |   NodeLabels,
 11 |   PaginatedResult,
 12 |   RelationshipTypes,
 13 |   SearchOptions,
 14 | } from "../types.js";
 15 | import { Neo4jUtils } from "../utils.js";
 16 | import { SearchResultItem } from "./searchTypes.js";
 17 | 
 18 | /**
 19 |  * Helper to search within a single node label with sorting and limit.
 20 |  * Acquires and closes its own session.
 21 |  * @private
 22 |  */
 23 | async function _searchSingleLabel(
 24 |   labelInput: string,
 25 |   cypherSearchValue: string,
 26 |   originalPropertyName: string, // Used for Cypher query (case-sensitive)
 27 |   normalizedLogicProperty: string, // Used for internal logic (lowercase)
 28 |   taskTypeFilter?: string,
 29 |   limit: number = 50,
 30 |   assignedToUserIdFilter?: string,
 31 | ): Promise<SearchResultItem[]> {
 32 |   let session: Session | null = null;
 33 |   const reqContext_single = requestContextService.createRequestContext({
 34 |     operation: "SearchService._searchSingleLabel", // Updated operation name
 35 |     labelInput,
 36 |     cypherSearchValue,
 37 |     originalPropertyName,
 38 |     normalizedLogicProperty,
 39 |     taskTypeFilter,
 40 |     assignedToUserIdFilter,
 41 |     limit,
 42 |   });
 43 |   try {
 44 |     session = await neo4jDriver.getSession();
 45 | 
 46 |     let actualLabel: NodeLabels | undefined;
 47 |     switch (labelInput.toLowerCase()) {
 48 |       case "project":
 49 |         actualLabel = NodeLabels.Project;
 50 |         break;
 51 |       case "task":
 52 |         actualLabel = NodeLabels.Task;
 53 |         break;
 54 |       case "knowledge":
 55 |         actualLabel = NodeLabels.Knowledge;
 56 |         break;
 57 |       default:
 58 |         logger.warning(
 59 |           `Unsupported label provided to _searchSingleLabel: ${labelInput}`,
 60 |           reqContext_single,
 61 |         );
 62 |         return [];
 63 |     }
 64 | 
 65 |     const correctlyEscapedLabel = `\`${actualLabel}\``;
 66 | 
 67 |     const params: Record<string, any> = {
 68 |       searchValue: cypherSearchValue,
 69 |       label: actualLabel,
 70 |       limit: int(limit),
 71 |     };
 72 | 
 73 |     const matchClauses: string[] = [`MATCH (n:${correctlyEscapedLabel})`];
 74 |     let whereConditions: string[] = [];
 75 | 
 76 |     if (taskTypeFilter) {
 77 |       whereConditions.push("n.taskType = $taskTypeFilter");
 78 |       params.taskTypeFilter = taskTypeFilter;
 79 |     }
 80 | 
 81 |     if (actualLabel === NodeLabels.Task && assignedToUserIdFilter) {
 82 |       matchClauses.push(
 83 |         `MATCH (n)-[:${RelationshipTypes.ASSIGNED_TO}]->(assignee:${NodeLabels.User} {id: $assignedToUserIdFilter})`,
 84 |       );
 85 |       params.assignedToUserIdFilter = assignedToUserIdFilter;
 86 |     }
 87 | 
 88 |     let propertyForCypher: string; // This will be original case, or default
 89 |     let propertyForLogic: string; // This will be lowercase, or default
 90 | 
 91 |     if (originalPropertyName) {
 92 |       propertyForCypher = originalPropertyName;
 93 |       propertyForLogic = normalizedLogicProperty; // Already lowercase from _searchUnified
 94 |     } else {
 95 |       // Default property based on label if none specified
 96 |       switch (actualLabel) {
 97 |         case NodeLabels.Project:
 98 |           propertyForCypher = "name"; // Default is original case
 99 |           propertyForLogic = "name";
100 |           break;
101 |         case NodeLabels.Task:
102 |           propertyForCypher = "title";
103 |           propertyForLogic = "title";
104 |           break;
105 |         case NodeLabels.Knowledge:
106 |           propertyForCypher = "text";
107 |           propertyForLogic = "text";
108 |           break;
109 |         default: // Should not happen due to earlier check
110 |           logger.error(
111 |             "Unreachable code: default property determination failed.",
112 |             reqContext_single,
113 |           );
114 |           return [];
115 |       }
116 |     }
117 | 
118 |     propertyForLogic = propertyForLogic.toLowerCase();
119 | 
120 |     if (!propertyForCypher) {
121 |       logger.warning(
122 |         `Could not determine a search property for Cypher for label ${actualLabel}. Returning empty results.`,
123 |         { ...reqContext_single, actualLabel },
124 |       );
125 |       return [];
126 |     }
127 | 
128 |     const propExistsCheck = `n.\`${propertyForCypher}\` IS NOT NULL`;
129 |     let searchPart: string;
130 | 
131 |     if (propertyForLogic === "tags") {
132 |       let tagSearchTerm = params.searchValue;
133 |       if (tagSearchTerm.startsWith("(?i)")) {
134 |         tagSearchTerm = tagSearchTerm.substring(4);
135 |       }
136 |       let coreValue = tagSearchTerm;
137 |       if (tagSearchTerm.startsWith("^") && tagSearchTerm.endsWith("$")) {
138 |         coreValue = tagSearchTerm.substring(1, tagSearchTerm.length - 1);
139 |       } else if (
140 |         tagSearchTerm.startsWith(".*") &&
141 |         tagSearchTerm.endsWith(".*")
142 |       ) {
143 |         coreValue = tagSearchTerm.substring(2, tagSearchTerm.length - 2);
144 |       }
145 |       params.exactTagValueLower = coreValue.toLowerCase();
146 |       searchPart = `ANY(tag IN n.\`${propertyForCypher}\` WHERE toLower(tag) = $exactTagValueLower)`;
147 |     } else if (
148 |       propertyForLogic === "urls" &&
149 |       (actualLabel === NodeLabels.Project || actualLabel === NodeLabels.Task)
150 |     ) {
151 |       searchPart = `toString(n.\`${propertyForCypher}\`) =~ $searchValue`;
152 |     } else {
153 |       searchPart = `n.\`${propertyForCypher}\` =~ $searchValue`;
154 |     }
155 | 
156 |     whereConditions.push(`(${propExistsCheck} AND ${searchPart})`);
157 |     const whereClause =
158 |       whereConditions.length > 0
159 |         ? `WHERE ${whereConditions.join(" AND ")}`
160 |         : "";
161 | 
162 |     const scoreValueParam = "$searchValue";
163 |     const scoreExactTagValueLowerParam = "$exactTagValueLower";
164 |     const scoringLogic = `
165 |       CASE
166 |         WHEN n.\`${propertyForCypher}\` IS NOT NULL THEN
167 |           CASE
168 |             WHEN '${propertyForLogic}' = 'tags' AND ANY(tag IN n.\`${propertyForCypher}\` WHERE toLower(tag) = ${scoreExactTagValueLowerParam}) THEN 8
169 |             WHEN n.\`${propertyForCypher}\` =~ ${scoreValueParam} THEN 8
170 |             ELSE 5
171 |           END
172 |         ELSE 5
173 |       END AS score
174 |     `;
175 | 
176 |     const valueParam = "$searchValue";
177 |     const returnClause = `
178 |       RETURN
179 |         n.id AS id,
180 |         $label AS type,
181 |         CASE $label
182 |           WHEN '${NodeLabels.Knowledge}' THEN (CASE WHEN d IS NOT NULL THEN d.name ELSE null END)
183 |           ELSE n.taskType 
184 |         END AS entityType,
185 |         COALESCE(n.name, n.title, CASE WHEN n.text IS NOT NULL AND size(toString(n.text)) > 50 THEN left(toString(n.text), 50) + '...' ELSE toString(n.text) END, n.id) AS title,
186 |         COALESCE(n.description, n.text, CASE WHEN '${propertyForLogic}' = 'urls' THEN toString(n.urls) ELSE NULL END) AS description,
187 |         '${propertyForCypher}' AS matchedProperty,
188 |         CASE
189 |           WHEN n.\`${propertyForCypher}\` IS NOT NULL THEN
190 |             CASE
191 |               WHEN '${propertyForLogic}' = 'tags' AND ANY(t IN n.\`${propertyForCypher}\` WHERE toLower(t) = ${scoreExactTagValueLowerParam}) THEN
192 |                 HEAD([tag IN n.\`${propertyForCypher}\` WHERE toLower(tag) = ${scoreExactTagValueLowerParam}])
193 |               WHEN '${propertyForLogic}' = 'urls' AND toString(n.\`${propertyForCypher}\`) =~ ${valueParam} THEN
194 |                 CASE
195 |                   WHEN size(toString(n.\`${propertyForCypher}\`)) > 100 THEN left(toString(n.\`${propertyForCypher}\`), 100) + '...'
196 |                   ELSE toString(n.\`${propertyForCypher}\`)
197 |                 END
198 |               WHEN n.\`${propertyForCypher}\` =~ ${valueParam} THEN
199 |                 CASE
200 |                   WHEN size(toString(n.\`${propertyForCypher}\`)) > 100 THEN left(toString(n.\`${propertyForCypher}\`), 100) + '...'
201 |                   ELSE toString(n.\`${propertyForCypher}\`)
202 |                 END
203 |               ELSE ''
204 |             END
205 |           ELSE ''
206 |         END AS matchedValue,
207 |         n.createdAt AS createdAt,
208 |         n.updatedAt AS updatedAt,
209 |         CASE $label
210 |           WHEN '${NodeLabels.Project}' THEN n.id
211 |           ELSE n.projectId
212 |         END AS projectId,
213 |         CASE $label
214 |           WHEN '${NodeLabels.Project}' THEN n.name
215 |           WHEN '${NodeLabels.Task}' THEN (CASE WHEN p IS NOT NULL THEN p.name ELSE null END)
216 |           WHEN '${NodeLabels.Knowledge}' THEN (CASE WHEN k_proj IS NOT NULL THEN k_proj.name ELSE null END)
217 |           ELSE null
218 |         END AS projectName,
219 |         ${scoringLogic}
220 |     `;
221 | 
222 |     let optionalMatches = "";
223 |     if (actualLabel === NodeLabels.Task) {
224 |       optionalMatches = `OPTIONAL MATCH (p:${NodeLabels.Project} {id: n.projectId})`;
225 |     } else if (actualLabel === NodeLabels.Knowledge) {
226 |       optionalMatches = `
227 |         OPTIONAL MATCH (k_proj:${NodeLabels.Project} {id: n.projectId})
228 |         OPTIONAL MATCH (n)-[:${RelationshipTypes.BELONGS_TO_DOMAIN}]->(d:${NodeLabels.Domain})
229 |       `;
230 |     }
231 | 
232 |     const finalMatchQueryPart = matchClauses.join("\n        ");
233 |     let baseWithVariables = ["n"];
234 |     if (actualLabel === NodeLabels.Task && assignedToUserIdFilter) {
235 |       baseWithVariables.push("assignee");
236 |     }
237 |     baseWithVariables = [...new Set(baseWithVariables)];
238 | 
239 |     const query = `
240 |       ${finalMatchQueryPart}
241 |       ${whereClause}
242 |       WITH ${baseWithVariables.join(", ")}
243 |       ${optionalMatches}
244 |       ${returnClause}
245 |       ORDER BY score DESC, COALESCE(n.updatedAt, n.createdAt) DESC
246 |       LIMIT $limit
247 |     `;
248 | 
249 |     logger.debug(
250 |       `Executing search query for label ${actualLabel}. Property for Cypher: '${propertyForCypher}', Property for Logic: '${propertyForLogic}', SearchValue (Regex): '${params.searchValue}'`,
251 |       {
252 |         ...reqContext_single,
253 |         actualLabel,
254 |         propertyForCypher,
255 |         propertyForLogic,
256 |         rawSearchValueParam: params.searchValue,
257 |         query,
258 |         params,
259 |       },
260 |     );
261 |     const result = await session.executeRead(
262 |       async (tx: any) => (await tx.run(query, params)).records,
263 |     );
264 | 
265 |     return result.map((record: any) => {
266 |       const data = record.toObject();
267 |       const scoreValue = data.score;
268 |       const score =
269 |         typeof scoreValue === "number"
270 |           ? scoreValue
271 |           : scoreValue && typeof scoreValue.toNumber === "function"
272 |             ? scoreValue.toNumber()
273 |             : 5;
274 |       const description =
275 |         typeof data.description === "string" ? data.description : undefined;
276 |       return {
277 |         ...data,
278 |         score,
279 |         description,
280 |         entityType: data.entityType || undefined,
281 |         createdAt: data.createdAt || undefined,
282 |         updatedAt: data.updatedAt || undefined,
283 |         projectId: data.projectId || undefined,
284 |         projectName: data.projectName || undefined,
285 |       } as SearchResultItem;
286 |     });
287 |   } catch (error) {
288 |     const errorMessage = error instanceof Error ? error.message : String(error);
289 |     logger.error(`Error searching label ${labelInput}`, error as Error, {
290 |       ...reqContext_single,
291 |       detail: errorMessage,
292 |     });
293 |     return [];
294 |   } finally {
295 |     if (session) {
296 |       await session.close();
297 |     }
298 |   }
299 | }
300 | 
301 | /**
302 |  * Perform a unified search across multiple entity types (node labels).
303 |  * Searches common properties like name, title, description, text.
304 |  * Applies pagination after combining and sorting results from individual label searches.
305 |  * @param options Search options
306 |  * @returns Paginated search results
307 |  */
308 | export async function _searchUnified(
309 |   options: SearchOptions,
310 | ): Promise<PaginatedResult<SearchResultItem>> {
311 |   const reqContext = requestContextService.createRequestContext({
312 |     operation: "SearchService._searchUnified", // Updated operation name
313 |     searchOptions: options,
314 |   });
315 |   try {
316 |     const {
317 |       property = "",
318 |       value,
319 |       entityTypes = ["project", "task", "knowledge"],
320 |       caseInsensitive = true,
321 |       fuzzy = false,
322 |       taskType,
323 |       assignedToUserId,
324 |       page = 1,
325 |       limit = 20,
326 |     } = options;
327 | 
328 |     if (!value || value.trim() === "") {
329 |       throw new Error("Search value cannot be empty");
330 |     }
331 | 
332 |     const targetLabels = Array.isArray(entityTypes)
333 |       ? entityTypes
334 |       : [entityTypes];
335 |     if (targetLabels.length === 0) {
336 |       logger.warning(
337 |         "Unified search called with empty entityTypes array. Returning empty results.",
338 |         reqContext,
339 |       );
340 |       return Neo4jUtils.paginateResults([], { page, limit });
341 |     }
342 | 
343 |     const normalizedProperty = property ? property.toLowerCase() : "";
344 |     const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
345 |     const caseFlag = caseInsensitive ? "(?i)" : "";
346 | 
347 |     const cypherSearchValue = fuzzy
348 |       ? `${caseFlag}.*${escapedValue}.*`
349 |       : `${caseFlag}^${escapedValue}$`;
350 | 
351 |     const allResults: SearchResultItem[] = [];
352 |     const searchPromises: Promise<SearchResultItem[]>[] = [];
353 |     const perLabelLimit = Math.max(limit * 2, 50);
354 | 
355 |     for (const label of targetLabels) {
356 |       if (!label || typeof label !== "string") {
357 |         logger.warning(`Skipping invalid label in entityTypes: ${label}`, {
358 |           ...reqContext,
359 |           invalidLabel: label,
360 |         });
361 |         continue;
362 |       }
363 | 
364 |       searchPromises.push(
365 |         _searchSingleLabel(
366 |           // Call the local helper
367 |           label,
368 |           cypherSearchValue,
369 |           property,
370 |           normalizedProperty,
371 |           label.toLowerCase() === "project" || label.toLowerCase() === "task"
372 |             ? taskType
373 |             : undefined,
374 |           perLabelLimit,
375 |           label.toLowerCase() === "task" ? assignedToUserId : undefined,
376 |         ),
377 |       );
378 |     }
379 | 
380 |     const settledResults = await Promise.allSettled(searchPromises);
381 | 
382 |     settledResults.forEach((result, index) => {
383 |       const label = targetLabels[index];
384 |       if (
385 |         result.status === "fulfilled" &&
386 |         result.value &&
387 |         Array.isArray(result.value)
388 |       ) {
389 |         allResults.push(...result.value);
390 |       } else if (result.status === "rejected") {
391 |         logger.error(
392 |           `Search promise rejected for label "${label}":`,
393 |           new Error(String(result.reason)),
394 |           { ...reqContext, label, rejectionReason: result.reason },
395 |         );
396 |       } else if (result.status === "fulfilled") {
397 |         logger.warning(
398 |           `Search promise fulfilled with non-array value for label "${label}":`,
399 |           { ...reqContext, label, fulfilledValue: result.value },
400 |         );
401 |       }
402 |     });
403 | 
404 |     allResults.sort((a, b) => {
405 |       if (b.score !== a.score) return b.score - a.score;
406 |       const dateA = a.updatedAt || a.createdAt || "1970-01-01T00:00:00.000Z";
407 |       const dateB = b.updatedAt || b.createdAt || "1970-01-01T00:00:00.000Z";
408 |       return new Date(dateB).getTime() - new Date(dateA).getTime();
409 |     });
410 | 
411 |     return Neo4jUtils.paginateResults(allResults, { page, limit });
412 |   } catch (error) {
413 |     const errorMessage = error instanceof Error ? error.message : String(error);
414 |     logger.error("Error performing unified search", error as Error, {
415 |       ...reqContext,
416 |       detail: errorMessage,
417 |       originalOptions: options,
418 |     });
419 |     throw error;
420 |   }
421 | }
422 | 
```

--------------------------------------------------------------------------------
/src/webui/logic/ui-service.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * @fileoverview Handles UI logic, theme management, and dynamic content rendering.
  3 |  * @module src/webui/logic/ui-service
  4 |  */
  5 | 
  6 | import { config } from "./config.js";
  7 | import { dom } from "./dom-elements.js";
  8 | import { utils, state } from "./app-state.js";
  9 | 
 10 | /**
 11 |  * Manages UI interactions and visual states.
 12 |  * @type {object}
 13 |  */
 14 | export const uiHelpers = {
 15 |   /**
 16 |    * Applies the specified theme to the document.
 17 |    * @param {string} theme - The theme to apply ('light' or 'dark').
 18 |    */
 19 |   applyTheme: (theme) => {
 20 |     document.documentElement.classList.toggle("dark-mode", theme === "dark");
 21 |     if (dom.themeCheckbox) {
 22 |       // Ensure element exists
 23 |       dom.themeCheckbox.checked = theme === "dark";
 24 |     }
 25 |     if (typeof mermaid !== "undefined") {
 26 |       try {
 27 |         mermaid.initialize({
 28 |           startOnLoad: false,
 29 |           theme:
 30 |             theme === "dark"
 31 |               ? config.MERMAID_THEME_DARK
 32 |               : config.MERMAID_THEME_LIGHT,
 33 |           gantt: { axisFormatter: [["%Y-%m-%d", (d) => d.getDay() === 1]] }, // Original gantt config
 34 |           flowchart: { htmlLabels: true }, // Original flowchart config
 35 |         });
 36 |       } catch (e) {
 37 |         console.error("Mermaid initialization error during theme apply:", e);
 38 |         // Potentially show a non-critical error to the user if Mermaid fails to init
 39 |       }
 40 |     }
 41 |   },
 42 | 
 43 |   /**
 44 |    * Toggles the current theme between light and dark.
 45 |    * Saves the new theme to localStorage.
 46 |    * Re-renders task flow if it's visible.
 47 |    */
 48 |   toggleTheme: () => {
 49 |     const currentThemeIsDark =
 50 |       document.documentElement.classList.contains("dark-mode");
 51 |     const newTheme = currentThemeIsDark ? "light" : "dark";
 52 |     uiHelpers.applyTheme(newTheme);
 53 |     localStorage.setItem("atlasTheme", newTheme);
 54 |     if (state.showingTaskFlow && dom.taskFlowContainer) {
 55 |       // Check if taskFlowContainer exists
 56 |       renderHelpers.taskFlow(state.currentTasks, dom.taskFlowContainer);
 57 |     }
 58 |   },
 59 | 
 60 |   /**
 61 |    * Loads the theme from localStorage or defaults.
 62 |    */
 63 |   loadTheme: () => {
 64 |     const savedTheme =
 65 |       localStorage.getItem("atlasTheme") || config.DEFAULT_THEME;
 66 |     uiHelpers.applyTheme(savedTheme);
 67 |   },
 68 | 
 69 |   /**
 70 |    * Sets the display style of an element (show/hide).
 71 |    * @param {HTMLElement} element - The DOM element.
 72 |    * @param {boolean} show - True to show, false to hide.
 73 |    */
 74 |   setDisplay: (element, show) => {
 75 |     if (!element) return;
 76 |     element.classList.toggle("hidden", !show);
 77 |   },
 78 | 
 79 |   /**
 80 |    * Shows a loading message in the specified element.
 81 |    * @param {HTMLElement} element - The DOM element to display loading message in.
 82 |    * @param {string} [message="Loading..."] - The loading message.
 83 |    */
 84 |   showLoading: (element, message = "Loading...") => {
 85 |     if (!element) return;
 86 |     element.innerHTML = `<p class="loading">${utils.escapeHtml(message)}</p>`;
 87 |   },
 88 | 
 89 |   /**
 90 |    * Displays an error message.
 91 |    * @param {string} message - The error message to display.
 92 |    * @param {boolean} [isCritical=false] - If true, updates Neo4j status to error.
 93 |    */
 94 |   showError: (message, isCritical = false) => {
 95 |     if (dom.errorMessageDiv) {
 96 |       // Ensure element exists
 97 |       dom.errorMessageDiv.textContent = message;
 98 |       uiHelpers.setDisplay(dom.errorMessageDiv, true);
 99 |     }
100 |     if (isCritical) {
101 |       uiHelpers.updateNeo4jStatus("Error", "var(--error-color)");
102 |     }
103 |   },
104 | 
105 |   /**
106 |    * Clears any displayed error message.
107 |    */
108 |   clearError: () => {
109 |     if (dom.errorMessageDiv) {
110 |       // Ensure element exists
111 |       dom.errorMessageDiv.textContent = "";
112 |       uiHelpers.setDisplay(dom.errorMessageDiv, false);
113 |     }
114 |   },
115 | 
116 |   /**
117 |    * Updates the Neo4j connection status display.
118 |    * @param {string} text - The status text.
119 |    * @param {string} color - The CSS color for the status text.
120 |    */
121 |   updateNeo4jStatus: (text, color) => {
122 |     if (dom.neo4jStatusSpan) {
123 |       // Ensure element exists
124 |       dom.neo4jStatusSpan.textContent = text;
125 |       dom.neo4jStatusSpan.style.color = color;
126 |     }
127 |   },
128 | 
129 |   /**
130 |    * Updates the text and ARIA state of a toggle button.
131 |    * @param {HTMLButtonElement} button - The button element.
132 |    * @param {boolean} isActive - Whether the button's active state is true.
133 |    * @param {string} activeText - Text to display when active.
134 |    * @param {string} inactiveText - Text to display when inactive.
135 |    */
136 |   updateToggleButton: (button, isActive, activeText, inactiveText) => {
137 |     if (!button) return;
138 |     button.textContent = isActive ? activeText : inactiveText;
139 |     button.setAttribute("aria-pressed", String(isActive));
140 |   },
141 | };
142 | 
143 | /**
144 |  * Handles rendering of dynamic content.
145 |  * @type {object}
146 |  */
147 | export const renderHelpers = {
148 |   /**
149 |    * Renders project details into the specified element.
150 |    * @param {object|null} project - The project object.
151 |    * @param {HTMLElement} element - The DOM element to render into.
152 |    */
153 |   projectDetails: (project, element) => {
154 |     if (!element) return;
155 |     if (!project) {
156 |       element.innerHTML = "<p>Project not found or no data.</p>";
157 |       return;
158 |     }
159 |     const urlsToRender = utils.parseJsonSafe(project.urls);
160 |     const urlsHtml =
161 |       urlsToRender.length > 0
162 |         ? `<ul>${urlsToRender.map((url) => (url && url.url && url.title ? `<li><a href="${utils.escapeHtml(url.url)}" target="_blank" rel="noopener noreferrer">${utils.escapeHtml(url.title)}</a></li>` : "<li>Invalid URL entry</li>")).join("")}</ul>`
163 |         : "N/A";
164 | 
165 |     let dependenciesText = "N/A";
166 |     if (
167 |       project.dependencies &&
168 |       Array.isArray(project.dependencies) &&
169 |       project.dependencies.length > 0
170 |     ) {
171 |       dependenciesText = project.dependencies
172 |         .map((dep) => utils.escapeHtml(dep))
173 |         .join(", ");
174 |     } else if (
175 |       typeof project.dependencies === "string" &&
176 |       project.dependencies.trim() !== ""
177 |     ) {
178 |       dependenciesText = utils.escapeHtml(project.dependencies);
179 |     }
180 | 
181 |     element.innerHTML = `
182 |               <div class="data-item"><strong>ID:</strong> <div>${utils.escapeHtml(project.id)}</div></div>
183 |               <div class="data-item"><strong>Name:</strong> <div>${utils.escapeHtml(project.name)}</div></div>
184 |               <div class="data-item"><strong>Description:</strong> <pre>${utils.escapeHtml(project.description)}</pre></div>
185 |               <div class="data-item"><strong>Status:</strong> <div>${utils.escapeHtml(project.status)}</div></div>
186 |               <div class="data-item"><strong>Task Type:</strong> <div>${utils.escapeHtml(project.taskType)}</div></div>
187 |               <div class="data-item"><strong>Completion Requirements:</strong> <pre>${utils.escapeHtml(project.completionRequirements)}</pre></div>
188 |               <div class="data-item"><strong>Output Format:</strong> <pre>${utils.escapeHtml(project.outputFormat)}</pre></div>
189 |               <div class="data-item"><strong>URLs:</strong> ${urlsHtml}</div>
190 |               <div class="data-item"><strong>Dependencies:</strong> <div>${dependenciesText}</div></div>
191 |               <div class="data-item"><strong>Created At:</strong> <div>${utils.formatDate(project.createdAt)}</div></div>
192 |               <div class="data-item"><strong>Updated At:</strong> <div>${utils.formatDate(project.updatedAt)}</div></div>
193 |           `;
194 |   },
195 | 
196 |   /**
197 |    * Renders tasks into the specified element.
198 |    * @param {Array<object>} tasks - Array of task objects.
199 |    * @param {HTMLElement} element - The DOM element to render into.
200 |    * @param {string} viewMode - 'detailed' or 'compact'.
201 |    */
202 |   tasks: (tasks, element, viewMode) => {
203 |     if (!element) return;
204 |     if (!tasks || tasks.length === 0) {
205 |       element.innerHTML = "<p>No tasks for this project.</p>";
206 |       return;
207 |     }
208 |     element.innerHTML = tasks
209 |       .map((task) => {
210 |         if (!task || typeof task !== "object") {
211 |           console.error("Invalid task object encountered:", task);
212 |           return `<div class="data-item error">Error rendering an invalid task object.</div>`;
213 |         }
214 |         if (viewMode === "compact") {
215 |           return `
216 |                       <div class="data-item compact">
217 |                           <strong>${utils.escapeHtml(task.title)} (ID: ${utils.escapeHtml(task.id)})</strong>
218 |                           <span class="item-status">${utils.escapeHtml(task.status)}</span>
219 |                       </div>`;
220 |         }
221 |         // Detailed view
222 |         try {
223 |           const taskUrlsToRender = utils.parseJsonSafe(task.urls);
224 |           const urlsHtml =
225 |             taskUrlsToRender.length > 0
226 |               ? `URLs: ${taskUrlsToRender.map((u) => (u && u.url && u.title ? `<a href="${utils.escapeHtml(u.url)}" target="_blank" rel="noopener noreferrer">${utils.escapeHtml(u.title)}</a>` : "Invalid URL entry")).join(", ")}<br>`
227 |               : "";
228 | 
229 |           const tagsHtml =
230 |             task.tags && Array.isArray(task.tags) && task.tags.length > 0
231 |               ? `Tags: ${task.tags.map((t) => utils.escapeHtml(t)).join(", ")}<br>`
232 |               : "";
233 | 
234 |           return `
235 |                       <div class="data-item">
236 |                           <strong>${utils.escapeHtml(task.title)} (ID: ${utils.escapeHtml(task.id)})</strong>
237 |                           <div>Status: ${utils.escapeHtml(task.status)} - Priority: ${utils.escapeHtml(task.priority)}</div>
238 |                           <div>Description: <pre>${utils.escapeHtml(task.description)}</pre></div>
239 |                           <div>Type: ${utils.escapeHtml(task.taskType)}</div>
240 |                           <div>Completion: <pre>${utils.escapeHtml(task.completionRequirements)}</pre></div>
241 |                           <div>Output: <pre>${utils.escapeHtml(task.outputFormat)}</pre></div>
242 |                           ${task.assignedTo ? `<div>Assigned To: ${utils.escapeHtml(task.assignedTo)}</div>` : ""}
243 |                           ${tagsHtml ? `<div>${tagsHtml}</div>` : ""}
244 |                           ${urlsHtml ? `<div>${urlsHtml}</div>` : ""}
245 |                           <div>Created: ${utils.formatDate(task.createdAt)} | Updated: ${utils.formatDate(task.updatedAt)}</div>
246 |                       </div>`;
247 |         } catch (renderError) {
248 |           console.error(
249 |             `Error rendering task ${task.id || "unknown"}:`,
250 |             renderError,
251 |             task,
252 |           );
253 |           return `<div class="data-item error">Error rendering task ID ${utils.escapeHtml(task.id || "unknown")}.</div>`;
254 |         }
255 |       })
256 |       .join("");
257 |   },
258 | 
259 |   /**
260 |    * Renders knowledge items into the specified element.
261 |    * @param {Array<object>} items - Array of knowledge item objects.
262 |    * @param {HTMLElement} element - The DOM element to render into.
263 |    * @param {string} viewMode - 'detailed' or 'compact'.
264 |    */
265 |   knowledgeItems: (items, element, viewMode) => {
266 |     if (!element) return;
267 |     if (!items || items.length === 0) {
268 |       element.innerHTML = "<p>No knowledge items for this project.</p>";
269 |       return;
270 |     }
271 |     element.innerHTML = items
272 |       .map((item) => {
273 |         if (!item || typeof item !== "object") {
274 |           console.error("Invalid knowledge object encountered:", item);
275 |           return `<div class="data-item error">Error rendering an invalid knowledge object.</div>`;
276 |         }
277 |         if (viewMode === "compact") {
278 |           return `
279 |                       <div class="data-item compact">
280 |                           <strong>Knowledge ID: ${utils.escapeHtml(item.id)}</strong>
281 |                           <span class="item-status">${utils.escapeHtml(item.domain || "N/A")}</span>
282 |                       </div>`;
283 |         }
284 |         // Detailed view
285 |         try {
286 |           const tagsHtml =
287 |             item.tags && Array.isArray(item.tags) && item.tags.length > 0
288 |               ? `Tags: ${item.tags.map((t) => utils.escapeHtml(t)).join(", ")}<br>`
289 |               : "";
290 |           const citationsHtml =
291 |             item.citations &&
292 |             Array.isArray(item.citations) &&
293 |             item.citations.length > 0
294 |               ? `Citations: <ul>${item.citations.map((c) => `<li>${utils.escapeHtml(c)}</li>`).join("")}</ul>`
295 |               : "";
296 | 
297 |           return `
298 |                       <div class="data-item">
299 |                           <strong>ID: ${utils.escapeHtml(item.id)}</strong>
300 |                           <div>Domain: ${utils.escapeHtml(item.domain)}</div>
301 |                           <div>Text: <pre>${utils.escapeHtml(item.text)}</pre></div>
302 |                           ${tagsHtml ? `<div>${tagsHtml}</div>` : ""}
303 |                           ${citationsHtml ? `<div>${citationsHtml}</div>` : ""}
304 |                           <div>Created: ${utils.formatDate(item.createdAt)} | Updated: ${utils.formatDate(item.updatedAt)}</div>
305 |                       </div>`;
306 |         } catch (renderError) {
307 |           console.error(
308 |             `Error rendering knowledge item ${item.id || "unknown"}:`,
309 |             renderError,
310 |             item,
311 |           );
312 |           return `<div class="data-item error">Error rendering knowledge item ID ${utils.escapeHtml(item.id || "unknown")}.</div>`;
313 |         }
314 |       })
315 |       .join("");
316 |   },
317 | 
318 |   /**
319 |    * Renders a task flow diagram using Mermaid.
320 |    * @param {Array<object>} tasks - Array of task objects.
321 |    * @param {HTMLElement} element - The DOM element to render the diagram into.
322 |    */
323 |   taskFlow: async (tasks, element) => {
324 |     if (!element) return;
325 |     if (!tasks || tasks.length === 0) {
326 |       element.innerHTML = "<p>No tasks to display in flow chart.</p>";
327 |       return;
328 |     }
329 |     if (typeof mermaid === "undefined") {
330 |       element.innerHTML = '<p class="error">Mermaid JS library not loaded.</p>';
331 |       return;
332 |     }
333 |     uiHelpers.showLoading(element, "Generating task flow...");
334 | 
335 |     let flowDefinition = "graph TD;\n";
336 |     tasks.forEach((task) => {
337 |       const taskId = (task.id || "unknown_task").replace(/"/g, "#quot;");
338 |       const taskTitle = utils
339 |         .escapeHtml(task.title || "Untitled Task")
340 |         .replace(/"/g, "#quot;");
341 |       flowDefinition += `    ${taskId}["${taskTitle} (ID: ${taskId})"];\n`;
342 |       if (task.dependencyIds && task.dependencyIds.length > 0) {
343 |         task.dependencyIds.forEach((depId) => {
344 |           const dependencyId = (depId || "unknown_dependency").replace(
345 |             /"/g,
346 |             "#quot;",
347 |           );
348 |           flowDefinition += `    ${dependencyId} --> ${taskId};\n`;
349 |         });
350 |       }
351 |     });
352 | 
353 |     try {
354 |       const currentThemeSetting = document.documentElement.classList.contains(
355 |         "dark-mode",
356 |       )
357 |         ? config.MERMAID_THEME_DARK
358 |         : config.MERMAID_THEME_LIGHT;
359 |       // Re-initialize mermaid with current theme for this rendering
360 |       mermaid.initialize({
361 |         startOnLoad: false,
362 |         theme: currentThemeSetting,
363 |         flowchart: { htmlLabels: true },
364 |       });
365 |       const { svg } = await mermaid.render("taskFlowSvg", flowDefinition);
366 |       element.innerHTML = svg;
367 |     } catch (e) {
368 |       console.error("Mermaid rendering error:", e);
369 |       element.innerHTML = `<p class="error">Error rendering task flow: ${e.message}</p>`;
370 |       uiHelpers.showError(`Mermaid rendering error: ${e.message}`);
371 |     }
372 |   },
373 | };
374 | 
```

--------------------------------------------------------------------------------
/src/mcp/resources/tasks/taskResources.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { ProjectService } from "../../../services/neo4j/projectService.js";
  3 | import { TaskService } from "../../../services/neo4j/taskService.js";
  4 | import { TaskFilterOptions } from "../../../services/neo4j/types.js";
  5 | import {
  6 |   BaseErrorCode,
  7 |   McpError,
  8 |   ProjectErrorCode,
  9 |   TaskErrorCode,
 10 | } from "../../../types/errors.js";
 11 | import { PriorityLevel, TaskStatus } from "../../../types/mcp.js";
 12 | import { logger, requestContextService } from "../../../utils/index.js"; // Import requestContextService
 13 | import { ResourceTemplates, ResourceURIs, toTaskResource } from "../types.js";
 14 | 
 15 | /**
 16 |  * Register Task Resources
 17 |  *
 18 |  * This function registers resource endpoints for the Tasks entity
 19 |  * - GET atlas://tasks - List all tasks
 20 |  * - GET atlas://tasks/{taskId} - Get specific task by ID
 21 |  * - GET atlas://projects/{projectId}/tasks - List tasks for a specific project
 22 |  *
 23 |  * @param server The MCP server instance
 24 |  */
 25 | export function registerTaskResources(server: McpServer) {
 26 |   // List all tasks
 27 |   server.resource(
 28 |     "tasks-list",
 29 |     ResourceURIs.TASKS,
 30 |     {
 31 |       name: "All Tasks",
 32 |       description:
 33 |         "List of all tasks in the Atlas platform with pagination and filtering support",
 34 |       mimeType: "application/json",
 35 |     },
 36 |     async (uri) => {
 37 |       const reqContext = requestContextService.createRequestContext({
 38 |         operation: "listAllTasks",
 39 |         resourceUri: uri.href,
 40 |       });
 41 |       try {
 42 |         logger.info("Listing all tasks", { ...reqContext, uri: uri.href });
 43 | 
 44 |         // Parse query parameters
 45 |         const queryParams = new URLSearchParams(uri.search);
 46 |         // Default project ID required by task service
 47 |         const projectId = queryParams.get("projectId") || "*";
 48 | 
 49 |         const filters: TaskFilterOptions = {
 50 |           projectId,
 51 |         };
 52 | 
 53 |         // Parse status parameter using TaskStatus enum
 54 |         const status = queryParams.get("status");
 55 |         if (status) {
 56 |           switch (status) {
 57 |             case TaskStatus.BACKLOG:
 58 |               filters.status = "backlog";
 59 |               break;
 60 |             case TaskStatus.TODO:
 61 |               filters.status = "todo";
 62 |               break;
 63 |             case TaskStatus.IN_PROGRESS:
 64 |               filters.status = "in-progress";
 65 |               break;
 66 |             case TaskStatus.COMPLETED:
 67 |               filters.status = "completed";
 68 |               break;
 69 |             default:
 70 |               logger.warning(
 71 |                 `Invalid status value: ${status}, ignoring filter`,
 72 |                 { ...reqContext, invalidStatus: status },
 73 |               );
 74 |           }
 75 |         }
 76 | 
 77 |         // Parse priority parameter using PriorityLevel enum
 78 |         const priority = queryParams.get("priority");
 79 |         if (priority) {
 80 |           switch (priority) {
 81 |             case PriorityLevel.LOW:
 82 |               filters.priority = "low";
 83 |               break;
 84 |             case PriorityLevel.MEDIUM:
 85 |               filters.priority = "medium";
 86 |               break;
 87 |             case PriorityLevel.HIGH:
 88 |               filters.priority = "high";
 89 |               break;
 90 |             case PriorityLevel.CRITICAL:
 91 |               filters.priority = "critical";
 92 |               break;
 93 |             default:
 94 |               logger.warning(
 95 |                 `Invalid priority value: ${priority}, ignoring filter`,
 96 |                 { ...reqContext, invalidPriority: priority },
 97 |               );
 98 |           }
 99 |         }
100 | 
101 |         // Parse assignedTo parameter
102 |         const assignedTo = queryParams.get("assignedTo");
103 |         if (assignedTo) {
104 |           filters.assignedTo = String(assignedTo);
105 |         }
106 | 
107 |         // Parse taskType parameter
108 |         const taskType = queryParams.get("taskType");
109 |         if (taskType) {
110 |           filters.taskType = String(taskType);
111 |         }
112 | 
113 |         // Parse tags parameter
114 |         const tags = queryParams.get("tags");
115 |         if (tags) {
116 |           // Split comma-separated tags
117 |           filters.tags = String(tags)
118 |             .split(",")
119 |             .map((tag) => tag.trim());
120 |         }
121 | 
122 |         // Parse sort parameters
123 |         const sortBy = queryParams.get("sortBy");
124 |         if (sortBy) {
125 |           // Validate sortBy value
126 |           const validSortByValues = ["priority", "createdAt", "status"];
127 |           if (validSortByValues.includes(sortBy)) {
128 |             filters.sortBy = sortBy as "priority" | "createdAt" | "status";
129 |           } else {
130 |             logger.warning(
131 |               `Invalid sortBy value: ${sortBy}, using default sorting`,
132 |               { ...reqContext, invalidSortBy: sortBy },
133 |             );
134 |           }
135 |         }
136 | 
137 |         const sortDirection = queryParams.get("sortDirection");
138 |         if (sortDirection) {
139 |           // Validate sortDirection value
140 |           const validDirections = ["asc", "desc"];
141 |           if (validDirections.includes(sortDirection)) {
142 |             filters.sortDirection = sortDirection as "asc" | "desc";
143 |           } else {
144 |             logger.warning(
145 |               `Invalid sortDirection value: ${sortDirection}, using default direction`,
146 |               { ...reqContext, invalidSortDirection: sortDirection },
147 |             );
148 |           }
149 |         }
150 | 
151 |         // Parse pagination parameters
152 |         const page = queryParams.has("page")
153 |           ? parseInt(queryParams.get("page") || "1", 10)
154 |           : 1;
155 | 
156 |         const limit = queryParams.has("limit")
157 |           ? parseInt(queryParams.get("limit") || "20", 10)
158 |           : 20;
159 | 
160 |         // Add pagination to filters
161 |         filters.page = page;
162 |         filters.limit = limit;
163 | 
164 |         // Query the database
165 |         const result = await TaskService.getTasks(filters);
166 | 
167 |         // Map Neo4j tasks to resource objects
168 |         const taskResources = result.data.map((task) => toTaskResource(task));
169 | 
170 |         return {
171 |           contents: [
172 |             {
173 |               uri: uri.href,
174 |               mimeType: "application/json",
175 |               text: JSON.stringify(
176 |                 {
177 |                   tasks: taskResources,
178 |                   pagination: {
179 |                     total: result.total,
180 |                     page: result.page,
181 |                     limit: result.limit,
182 |                     totalPages: result.totalPages,
183 |                   },
184 |                 },
185 |                 null,
186 |                 2,
187 |               ),
188 |             },
189 |           ],
190 |         };
191 |       } catch (error) {
192 |         logger.error("Error listing tasks", error as Error, {
193 |           ...reqContext,
194 |           // error is now part of the Error object passed to logger
195 |           uri: uri.href,
196 |         });
197 | 
198 |         throw new McpError(
199 |           BaseErrorCode.INTERNAL_ERROR,
200 |           `Failed to list tasks: ${error instanceof Error ? error.message : String(error)}`,
201 |         );
202 |       }
203 |     },
204 |   );
205 | 
206 |   // Get task by ID
207 |   server.resource(
208 |     "task-by-id",
209 |     ResourceTemplates.TASK,
210 |     {
211 |       name: "Task by ID",
212 |       description: "Retrieves a single task by its unique identifier",
213 |       mimeType: "application/json",
214 |     },
215 |     async (uri, params) => {
216 |       const reqContext = requestContextService.createRequestContext({
217 |         operation: "getTaskById",
218 |         resourceUri: uri.href,
219 |         taskIdParam: params.taskId,
220 |       });
221 |       try {
222 |         const taskId = params.taskId as string;
223 | 
224 |         logger.info("Fetching task by ID", {
225 |           ...reqContext,
226 |           taskId, // Already in reqContext
227 |           uri: uri.href, // Already in reqContext
228 |         });
229 | 
230 |         if (!taskId) {
231 |           throw new McpError(
232 |             BaseErrorCode.VALIDATION_ERROR,
233 |             "Task ID is required",
234 |           );
235 |         }
236 | 
237 |         // Query the database
238 |         const task = await TaskService.getTaskById(taskId);
239 | 
240 |         if (!task) {
241 |           throw new McpError(
242 |             TaskErrorCode.TASK_NOT_FOUND,
243 |             `Task with ID ${taskId} not found`,
244 |             { taskId },
245 |           );
246 |         }
247 | 
248 |         // Convert to resource object
249 |         const taskResource = toTaskResource(task);
250 | 
251 |         return {
252 |           contents: [
253 |             {
254 |               uri: uri.href,
255 |               mimeType: "application/json",
256 |               text: JSON.stringify(taskResource, null, 2),
257 |             },
258 |           ],
259 |         };
260 |       } catch (error) {
261 |         // Handle specific error cases
262 |         if (error instanceof McpError) {
263 |           throw error;
264 |         }
265 | 
266 |         logger.error("Error fetching task by ID", error as Error, {
267 |           ...reqContext,
268 |           // error is now part of the Error object passed to logger
269 |           parameters: params,
270 |         });
271 | 
272 |         throw new McpError(
273 |           BaseErrorCode.INTERNAL_ERROR,
274 |           `Failed to fetch task: ${error instanceof Error ? error.message : String(error)}`,
275 |         );
276 |       }
277 |     },
278 |   );
279 | 
280 |   // List tasks by project
281 |   server.resource(
282 |     "tasks-by-project",
283 |     ResourceTemplates.TASKS_BY_PROJECT,
284 |     {
285 |       name: "Tasks by Project",
286 |       description: "Retrieves all tasks belonging to a specific project",
287 |       mimeType: "application/json",
288 |     },
289 |     async (uri, params) => {
290 |       const reqContext = requestContextService.createRequestContext({
291 |         operation: "listTasksByProject",
292 |         resourceUri: uri.href,
293 |         projectIdParam: params.projectId,
294 |       });
295 |       try {
296 |         const projectId = params.projectId as string;
297 | 
298 |         logger.info("Listing tasks for project", {
299 |           ...reqContext,
300 |           projectId, // Already in reqContext
301 |           uri: uri.href, // Already in reqContext
302 |         });
303 | 
304 |         if (!projectId) {
305 |           throw new McpError(
306 |             BaseErrorCode.VALIDATION_ERROR,
307 |             "Project ID is required",
308 |           );
309 |         }
310 | 
311 |         // Verify the project exists
312 |         const project = await ProjectService.getProjectById(projectId);
313 |         if (!project) {
314 |           throw new McpError(
315 |             ProjectErrorCode.PROJECT_NOT_FOUND,
316 |             `Project with ID ${projectId} not found`,
317 |             { projectId },
318 |           );
319 |         }
320 | 
321 |         // Parse query parameters
322 |         const queryParams = new URLSearchParams(uri.search);
323 |         const filters: TaskFilterOptions = {
324 |           projectId,
325 |         };
326 | 
327 |         // Parse status parameter using TaskStatus enum
328 |         const status = queryParams.get("status");
329 |         if (status) {
330 |           switch (status) {
331 |             case TaskStatus.BACKLOG:
332 |               filters.status = "backlog";
333 |               break;
334 |             case TaskStatus.TODO:
335 |               filters.status = "todo";
336 |               break;
337 |             case TaskStatus.IN_PROGRESS:
338 |               filters.status = "in-progress";
339 |               break;
340 |             case TaskStatus.COMPLETED:
341 |               filters.status = "completed";
342 |               break;
343 |             default:
344 |               logger.warning(
345 |                 `Invalid status value: ${status}, ignoring filter`,
346 |                 { ...reqContext, invalidStatus: status },
347 |               );
348 |           }
349 |         }
350 | 
351 |         // Parse priority parameter using PriorityLevel enum
352 |         const priority = queryParams.get("priority");
353 |         if (priority) {
354 |           switch (priority) {
355 |             case PriorityLevel.LOW:
356 |               filters.priority = "low";
357 |               break;
358 |             case PriorityLevel.MEDIUM:
359 |               filters.priority = "medium";
360 |               break;
361 |             case PriorityLevel.HIGH:
362 |               filters.priority = "high";
363 |               break;
364 |             case PriorityLevel.CRITICAL:
365 |               filters.priority = "critical";
366 |               break;
367 |             default:
368 |               logger.warning(
369 |                 `Invalid priority value: ${priority}, ignoring filter`,
370 |                 { ...reqContext, invalidPriority: priority },
371 |               );
372 |           }
373 |         }
374 | 
375 |         // Parse assignedTo parameter
376 |         const assignedTo = queryParams.get("assignedTo");
377 |         if (assignedTo) {
378 |           filters.assignedTo = String(assignedTo);
379 |         }
380 | 
381 |         // Parse taskType parameter
382 |         const taskType = queryParams.get("taskType");
383 |         if (taskType) {
384 |           filters.taskType = String(taskType);
385 |         }
386 | 
387 |         // Parse tags parameter
388 |         const tags = queryParams.get("tags");
389 |         if (tags) {
390 |           // Split comma-separated tags
391 |           filters.tags = String(tags)
392 |             .split(",")
393 |             .map((tag) => tag.trim());
394 |         }
395 | 
396 |         // Parse sort parameters
397 |         const sortBy = queryParams.get("sortBy");
398 |         if (sortBy) {
399 |           // Validate sortBy value
400 |           const validSortByValues = ["priority", "createdAt", "status"];
401 |           if (validSortByValues.includes(sortBy)) {
402 |             filters.sortBy = sortBy as "priority" | "createdAt" | "status";
403 |           } else {
404 |             logger.warning(
405 |               `Invalid sortBy value: ${sortBy}, using default sorting`,
406 |               { ...reqContext, invalidSortBy: sortBy },
407 |             );
408 |           }
409 |         }
410 | 
411 |         const sortDirection = queryParams.get("sortDirection");
412 |         if (sortDirection) {
413 |           // Validate sortDirection value
414 |           const validDirections = ["asc", "desc"];
415 |           if (validDirections.includes(sortDirection)) {
416 |             filters.sortDirection = sortDirection as "asc" | "desc";
417 |           } else {
418 |             logger.warning(
419 |               `Invalid sortDirection value: ${sortDirection}, using default direction`,
420 |               { ...reqContext, invalidSortDirection: sortDirection },
421 |             );
422 |           }
423 |         }
424 | 
425 |         // Parse pagination parameters
426 |         const page = queryParams.has("page")
427 |           ? parseInt(queryParams.get("page") || "1", 10)
428 |           : 1;
429 | 
430 |         const limit = queryParams.has("limit")
431 |           ? parseInt(queryParams.get("limit") || "20", 10)
432 |           : 20;
433 | 
434 |         // Add pagination to filters
435 |         filters.page = page;
436 |         filters.limit = limit;
437 | 
438 |         // Query the database
439 |         const result = await TaskService.getTasks(filters);
440 | 
441 |         // Map Neo4j tasks to resource objects
442 |         const taskResources = result.data.map((task) => toTaskResource(task));
443 | 
444 |         return {
445 |           contents: [
446 |             {
447 |               uri: uri.href,
448 |               mimeType: "application/json",
449 |               text: JSON.stringify(
450 |                 {
451 |                   projectId,
452 |                   projectName: project.name,
453 |                   tasks: taskResources,
454 |                   pagination: {
455 |                     total: result.total,
456 |                     page: result.page,
457 |                     limit: result.limit,
458 |                     totalPages: result.totalPages,
459 |                   },
460 |                 },
461 |                 null,
462 |                 2,
463 |               ),
464 |             },
465 |           ],
466 |         };
467 |       } catch (error) {
468 |         // Handle specific error cases
469 |         if (error instanceof McpError) {
470 |           throw error;
471 |         }
472 | 
473 |         logger.error("Error listing tasks for project", error as Error, {
474 |           ...reqContext,
475 |           // error is now part of the Error object passed to logger
476 |           parameters: params,
477 |         });
478 | 
479 |         throw new McpError(
480 |           BaseErrorCode.INTERNAL_ERROR,
481 |           `Failed to list tasks for project: ${error instanceof Error ? error.message : String(error)}`,
482 |         );
483 |       }
484 |     },
485 |   );
486 | }
487 | 
```

--------------------------------------------------------------------------------
/examples/backup-example/tasks.json:
--------------------------------------------------------------------------------

```json
  1 | [
  2 |   {
  3 |     "description": "Design and implement a comprehensive Contact section that provides visitors with multiple ways to connect with cyanheads professionally. This section should facilitate easy communication while protecting against spam and ensuring data privacy. Features will include a professional contact form with validation and spam protection, direct email contact options with appropriate mail-to links, social media profile links with recognizable icons, integration with a serverless function for secure form submission handling, visual feedback for form submission status, and optional scheduling integration for booking calls or meetings. The design should be approachable yet professional, encouraging potential clients or employers to reach out.",
  4 |     "title": "Develop Contact Section and Form",
  5 |     "priority": "high",
  6 |     "tags": ["frontend", "backend", "form", "functionality"],
  7 |     "createdAt": "2025-03-26T18:40:55.545Z",
  8 |     "urls": "[]",
  9 |     "taskType": "generation",
 10 |     "completionRequirements": "This task is complete when the Contact section provides multiple functional methods of communication, implements a working contact form with proper validation and submission handling, includes proper error states and success messaging, protects against spam submissions, maintains responsiveness across all device sizes, and integrates seamlessly with the backend functionality for processing contact requests.",
 11 |     "id": "task_5d3304ef2c9c4d6b9288fea38ed6ba84",
 12 |     "outputFormat": "The output will include React components for the Contact section, a functional form with validation logic, integration with backend services for form processing, responsive layouts, and appropriate styling that aligns with the overall design system.",
 13 |     "projectId": "portfolio-main",
 14 |     "updatedAt": "2025-03-26T18:40:55.545Z",
 15 |     "status": "todo"
 16 |   },
 17 |   {
 18 |     "description": "Create an engaging 'About' section that tells cyanheads' professional story and highlights their personality as a developer. This section should strike a balance between personal and professional details, communicate cyanheads' journey into software development, and highlight their passion for technology. Components will include a comprehensive bio detailing their background, professional philosophy, and career trajectory; an enhanced professional photo or creative avatar; interactive elements showcasing personality traits and interests; and subtle animations that enhance the storytelling experience without distracting from the content.",
 19 |     "title": "Develop About Section",
 20 |     "priority": "medium",
 21 |     "tags": ["frontend", "content", "biography", "UI/UX"],
 22 |     "createdAt": "2025-03-26T18:40:19.593Z",
 23 |     "urls": "[]",
 24 |     "taskType": "generation",
 25 |     "completionRequirements": "This task is complete when the About section provides a compelling narrative of cyanheads' professional journey, effectively communicates their unique value proposition as a developer, demonstrates their personality and values, and is fully responsive across all device sizes. The content should be engaging, well-written, and presented in a visually appealing manner that aligns with the overall design system.",
 26 |     "id": "task_4a224bcddf5246afaa732834d84b4b73",
 27 |     "outputFormat": "The output will include React components for the About section, optimized images, animated elements, and professional biography content that tells a cohesive story.",
 28 |     "projectId": "portfolio-main",
 29 |     "updatedAt": "2025-03-26T18:40:19.593Z",
 30 |     "status": "todo"
 31 |   },
 32 |   {
 33 |     "description": "Design and implement an interactive professional experience timeline that showcases cyanheads' career progression, work history, and professional achievements. This section should effectively communicate their growth as a developer and highlight key roles and responsibilities. Features will include a visually appealing chronological timeline of employment history, detailed job descriptions for each position, highlighting key responsibilities and achievements, technologies used in each role, interactive elements that reveal additional details about specific projects or accomplishments, and visual elements that represent company logos or project outcomes. The timeline should convey progression and growth in skills and responsibilities over time.",
 34 |     "title": "Build Professional Experience Timeline",
 35 |     "priority": "medium",
 36 |     "tags": ["frontend", "content", "experience", "timeline"],
 37 |     "createdAt": "2025-03-26T18:40:46.152Z",
 38 |     "urls": "[]",
 39 |     "taskType": "generation",
 40 |     "completionRequirements": "This task is complete when the Experience section accurately represents cyanheads' professional history in an engaging timeline format, includes comprehensive details about each position and role, incorporates interactive elements that enhance user engagement with the content, maintains responsiveness across all device sizes, and effectively communicates career progression and professional growth.",
 41 |     "id": "task_3f5dd265abf04cc3983550a7b2a5f7fd",
 42 |     "outputFormat": "The output will include React components for the Experience timeline, data structures for organizing employment history, interactive timeline visualization, responsive layouts for different screen sizes, and animations or transitions that enhance the presentation of career information.",
 43 |     "projectId": "portfolio-main",
 44 |     "updatedAt": "2025-03-26T18:40:46.152Z",
 45 |     "status": "todo"
 46 |   },
 47 |   {
 48 |     "description": "Design and implement a comprehensive Projects section that showcases cyanheads' best work and technical capabilities. This section will feature a curated selection of significant projects with detailed case studies, highlighting the technologies used, problems solved, and outcomes achieved. Each project showcase should include project title, description, technologies used, project thumbnail or screenshot gallery, key challenges and solutions, links to live demos and repositories, and role/contributions for team projects. The design should allow users to filter projects by technology, type, or complexity, and provide an engaging visual presentation that draws attention to cyanheads' best work.",
 49 |     "title": "Create Projects Showcase Section",
 50 |     "priority": "high",
 51 |     "tags": ["frontend", "content", "projects", "portfolio"],
 52 |     "createdAt": "2025-03-26T18:40:29.372Z",
 53 |     "urls": "[]",
 54 |     "taskType": "generation",
 55 |     "completionRequirements": "This task is complete when the Projects section effectively showcases at least 5 significant projects with comprehensive details, provides easy navigation and filtering capabilities, maintains responsiveness across all device sizes, includes visual elements that highlight the projects' features, and properly links to external resources such as GitHub repositories and live demos where applicable.",
 56 |     "id": "task_fba80d348f274e908de4ee988df754cc",
 57 |     "outputFormat": "The output will include React components for the Projects section, a data structure for project information, filtering functionality, responsive project cards or detailed views, image galleries, and integration with external links.",
 58 |     "projectId": "portfolio-main",
 59 |     "updatedAt": "2025-03-26T18:40:29.372Z",
 60 |     "status": "todo"
 61 |   },
 62 |   {
 63 |     "description": "Design and develop a visually engaging Skills and Technologies section that effectively showcases cyanheads' technical expertise and proficiency levels. This section should provide visitors with a clear understanding of cyanheads' technical capabilities and specialized knowledge areas. Features will include an organized presentation of skills categorized by type (frontend, backend, cloud, etc.), visual indicators of proficiency levels for each skill, interactive elements that reveal additional details about experience with specific technologies, and dynamic visuals such as progress bars or skill charts that make the information engaging and easily scannable. The section should highlight both technical skills (programming languages, frameworks, tools) and soft skills relevant to software development.",
 64 |     "title": "Implement Skills and Technologies Section",
 65 |     "priority": "medium",
 66 |     "tags": ["frontend", "content", "skills", "technical"],
 67 |     "createdAt": "2025-03-26T18:40:37.799Z",
 68 |     "urls": "[]",
 69 |     "taskType": "generation",
 70 |     "completionRequirements": "This task is complete when the Skills section presents a comprehensive overview of cyanheads' technical capabilities, provides visual differentiation between skill categories and proficiency levels, includes interactive elements that enhance user engagement, maintains responsiveness across all device sizes, and accurately represents cyanheads' expertise in a way that aligns with industry expectations.",
 71 |     "id": "task_0fa6a009306b41108f5292475754c33a",
 72 |     "outputFormat": "The output will include React components for the Skills section, data structures for organizing skill information, interactive visualizations, responsive layouts, and animations or transitions that enhance the presentation of technical capabilities.",
 73 |     "projectId": "portfolio-main",
 74 |     "updatedAt": "2025-03-26T18:40:37.799Z",
 75 |     "status": "todo"
 76 |   },
 77 |   {
 78 |     "description": "Design and implement responsive navigation and footer components that provide intuitive site navigation and essential information across all pages. The navigation should enhance user experience by providing clear structure and accessibility, while the footer should contain important links and contact information. Features will include a responsive navigation bar that adapts between desktop and mobile layouts, smooth-scrolling navigation to page sections, a mobile-friendly hamburger menu with animations, social media links with recognizable icons, copyright information and privacy policy links in the footer, and potentially a newsletter signup or contact shortcut in the footer. Both components should maintain consistency with the overall design system.",
 79 |     "title": "Implement Navigation and Footer Components",
 80 |     "priority": "high",
 81 |     "tags": ["frontend", "navigation", "UI/UX", "layout"],
 82 |     "createdAt": "2025-03-26T18:41:03.752Z",
 83 |     "urls": "[]",
 84 |     "taskType": "generation",
 85 |     "completionRequirements": "This task is complete when the navigation provides intuitive access to all sections of the portfolio, adapts appropriately to all screen sizes, implements smooth scrolling or page transitions, includes proper active state indicators, and the footer contains all necessary information and links styled consistently with the overall design.",
 86 |     "id": "task_30dc34e190cf4c6690e8e4002269b8d5",
 87 |     "outputFormat": "The output will include React components for the navigation and footer, responsive behavior implementation, interaction animations, and proper integration with page sections and routes.",
 88 |     "projectId": "portfolio-main",
 89 |     "updatedAt": "2025-03-26T18:41:03.752Z",
 90 |     "status": "todo"
 91 |   },
 92 |   {
 93 |     "description": "Set up the initial project structure using React and Next.js, implement a consistent design system, and establish the core layout components. This task involves creating the repository, configuring development tools (ESLint, Prettier, TypeScript), setting up the build system, and implementing the base UI components that will be used throughout the site. The design system should include typography, color palette, spacing standards, and reusable UI components.",
 94 |     "title": "Project Setup and Design System Implementation",
 95 |     "priority": "high",
 96 |     "tags": ["setup", "design", "architecture", "frontend"],
 97 |     "createdAt": "2025-03-26T18:40:02.343Z",
 98 |     "urls": "[]",
 99 |     "taskType": "generation",
100 |     "completionRequirements": "This task is complete when the repository is set up with proper configuration, the design system is fully implemented with documentation, core layout components are created, and the development environment is ready for feature implementation. All base components should be responsive and accessible.",
101 |     "id": "task_ac11f7a5f83c4f339cae63de850ecda6",
102 |     "outputFormat": "The output will include a well-structured GitHub repository with proper documentation, a design system implementation with Tailwind CSS or styled-components, responsive layout components, and a working development environment.",
103 |     "projectId": "portfolio-main",
104 |     "updatedAt": "2025-03-26T18:40:02.343Z",
105 |     "status": "in-progress"
106 |   },
107 |   {
108 |     "description": "Design and implement the main homepage with an engaging hero section that effectively introduces cyanheads as a software developer. The homepage should make a strong first impression, clearly communicate cyanheads' professional identity, and guide visitors to explore other sections of the portfolio. Features will include an animated hero section with a professional headshot or avatar, a concise headline describing cyanheads' role and expertise, a brief tagline or value proposition, and call-to-action buttons directing to key sections of the site. The design should be visually appealing while maintaining fast load times.",
109 |     "title": "Implement Homepage and Hero Section",
110 |     "priority": "high",
111 |     "tags": ["frontend", "design", "homepage", "UI/UX"],
112 |     "createdAt": "2025-03-26T18:40:11.834Z",
113 |     "urls": "[]",
114 |     "taskType": "generation",
115 |     "completionRequirements": "This task is complete when the homepage is fully implemented with responsive design across all device sizes, the hero section effectively communicates cyanheads' identity and specialization, animations are smooth and performant, and clear navigation paths are established to other sections of the portfolio.",
116 |     "id": "task_4da902b4e18a46c4b5b5d1043c7d0665",
117 |     "outputFormat": "The output will include responsive React components for the homepage and hero section, optimized images, animation implementations, and integration with the navigation system.",
118 |     "projectId": "portfolio-main",
119 |     "updatedAt": "2025-03-26T18:40:11.834Z",
120 |     "status": "todo"
121 |   },
122 |   {
123 |     "description": "Optimize the portfolio website for maximum performance, implement SEO best practices, and set up deployment infrastructure with CI/CD pipelines. This task is crucial for ensuring the website loads quickly, ranks well in search engines, and can be easily updated. Activities will include implementing image optimization and lazy loading, configuring proper caching strategies, setting up a CDN for static assets, implementing code splitting and bundle optimization, adding metadata and structured data for SEO, configuring automated testing in the CI pipeline, and establishing a streamlined deployment process to a production environment. The optimization should focus on achieving high Lighthouse scores across all categories (Performance, Accessibility, Best Practices, SEO).",
124 |     "title": "Performance Optimization and Deployment",
125 |     "priority": "medium",
126 |     "tags": ["deployment", "performance", "optimization", "SEO", "DevOps"],
127 |     "createdAt": "2025-03-26T18:41:18.292Z",
128 |     "urls": "[]",
129 |     "taskType": "integration",
130 |     "completionRequirements": "This task is complete when the website achieves Lighthouse scores of at least 90 in all categories, loads quickly on both desktop and mobile devices, implements proper SEO tags and structured data, has a functional CI/CD pipeline for automated testing and deployment, and is successfully deployed to a production environment with proper monitoring in place.",
131 |     "id": "task_3e955c8424fa48dbb6e9bcc27c0bed92",
132 |     "outputFormat": "The output will include optimized build configurations, CI/CD pipeline setup, deployment scripts, performance test results showing before/after improvements, and documentation for the deployment process and infrastructure.",
133 |     "projectId": "portfolio-main",
134 |     "updatedAt": "2025-03-26T18:41:18.292Z",
135 |     "status": "todo"
136 |   }
137 | ]
138 | 
```

