code-graph-context 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Service Initialization
3
+ * Handles initialization of external services like Neo4j schema and OpenAI assistant
4
+ */
5
+ import fs from 'fs/promises';
6
+ import { join } from 'path';
7
+ import { Neo4jService } from '../storage/neo4j/neo4j.service.js';
8
+ import { FILE_PATHS, LOG_CONFIG } from './constants.js';
9
+ import { initializeNaturalLanguageService } from './tools/natural-language-to-cypher.tool.js';
10
+ import { debugLog } from './utils.js';
11
+ /**
12
+ * Initialize all external services required by the MCP server
13
+ */
14
+ export const initializeServices = async () => {
15
+ await Promise.all([initializeNeo4jSchema(), initializeNaturalLanguageService()]);
16
+ };
17
+ /**
18
+ * Enrich raw Neo4j schema with FairSquare domain context
19
+ */
20
+ const enrichSchemaWithDomainContext = (rawSchema) => {
21
+ return {
22
+ rawSchema,
23
+ domainContext: {
24
+ framework: 'FairSquare',
25
+ description: 'Custom TypeScript framework for microservices with dependency injection and repository patterns',
26
+ nodeTypes: {
27
+ Controller: {
28
+ description: 'HTTP request handlers that extend the Controller base class',
29
+ purpose: 'Entry points for HTTP API endpoints',
30
+ commonProperties: ['name', 'filePath', 'sourceCode'],
31
+ exampleQuery: 'MATCH (c:Controller) WHERE c.name =~ ".*Credit.*" RETURN c',
32
+ },
33
+ Service: {
34
+ description: 'Business logic layer with @Injectable decorator',
35
+ purpose: 'Encapsulate business logic and orchestrate data operations',
36
+ commonProperties: ['name', 'filePath', 'dependencies'],
37
+ exampleQuery: 'MATCH (s:Service)-[:INJECTS]->(dep) RETURN s.name, collect(dep.name) as dependencies',
38
+ },
39
+ Repository: {
40
+ description: 'Data access layer that extends Repository base class',
41
+ purpose: 'Abstract database operations and provide data access interface',
42
+ commonProperties: ['name', 'filePath', 'dals'],
43
+ exampleQuery: 'MATCH (r:Repository)-[:USES_DAL]->(d:DAL) RETURN r.name, collect(d.name) as dals',
44
+ },
45
+ DAL: {
46
+ description: 'Data Access Layer - direct database interaction classes',
47
+ purpose: 'Execute database queries and manage data persistence',
48
+ commonProperties: ['name', 'filePath'],
49
+ exampleQuery: 'MATCH (d:DAL)<-[:USES_DAL]-(r:Repository) RETURN d.name, count(r) as usedByCount',
50
+ },
51
+ PermissionManager: {
52
+ description: 'Security layer for authorization checks',
53
+ purpose: 'Control access to resources and validate permissions',
54
+ commonProperties: ['name', 'filePath'],
55
+ exampleQuery: 'MATCH (c:Controller)-[:PROTECTED_BY]->(pm:PermissionManager) RETURN c.name, pm.name',
56
+ },
57
+ VendorClient: {
58
+ description: 'External service integration clients',
59
+ purpose: 'Interface with third-party APIs and services',
60
+ commonProperties: ['name', 'filePath'],
61
+ exampleQuery: 'MATCH (v:VendorClient)<-[:INJECTS]-(s) RETURN v.name, collect(s.name) as usedBy',
62
+ },
63
+ RouteDefinition: {
64
+ description: 'Explicit route definitions from route files. CRITICAL: Individual route details (method, path, authenticated, handler, controllerName) are stored in the "context" property as a JSON string.',
65
+ purpose: 'Map HTTP paths and methods to controller handlers',
66
+ commonProperties: ['name', 'context', 'filePath', 'sourceCode'],
67
+ contextStructure: 'The context property contains JSON with structure: {"routes": [{"method": "POST", "path": "/v1/endpoint", "controllerName": "SomeController", "handler": "methodName", "authenticated": true}]}',
68
+ parsingInstructions: 'To get individual routes: (1) Parse JSON with apoc.convert.fromJsonMap(rd.context) (2) UNWIND the routes array (3) Access route.method, route.path, route.handler, route.authenticated, route.controllerName',
69
+ exampleQuery: 'MATCH (rd:RouteDefinition) WITH rd, apoc.convert.fromJsonMap(rd.context) AS ctx UNWIND ctx.routes AS route RETURN route.method, route.path, route.controllerName, route.handler, route.authenticated ORDER BY route.path',
70
+ },
71
+ HttpEndpoint: {
72
+ description: 'Methods that handle HTTP requests',
73
+ purpose: 'Process incoming HTTP requests and return responses',
74
+ commonProperties: ['name', 'filePath', 'sourceCode'],
75
+ exampleQuery: 'MATCH (e:HttpEndpoint)<-[r:ROUTES_TO_HANDLER]-(rd) WHERE apoc.convert.fromJsonMap(r.context).authenticated = true RETURN e.name, apoc.convert.fromJsonMap(r.context).path as path',
76
+ },
77
+ },
78
+ relationships: {
79
+ INJECTS: {
80
+ description: 'Dependency injection relationship from @Injectable decorator',
81
+ direction: 'OUTGOING',
82
+ example: 'Controller -[:INJECTS]-> Service',
83
+ commonPatterns: ['Controller -> Service', 'Service -> Repository', 'Service -> VendorClient'],
84
+ },
85
+ USES_DAL: {
86
+ description: 'Repository uses Data Access Layer for database operations',
87
+ direction: 'OUTGOING',
88
+ example: 'Repository -[:USES_DAL]-> DAL',
89
+ commonPatterns: ['Repository -> DAL'],
90
+ },
91
+ ROUTES_TO: {
92
+ description: 'Route definition points to a Controller',
93
+ direction: 'OUTGOING',
94
+ example: 'RouteDefinition -[:ROUTES_TO]-> Controller',
95
+ commonPatterns: ['RouteDefinition -> Controller'],
96
+ },
97
+ ROUTES_TO_HANDLER: {
98
+ description: 'Route definition points to a specific handler method',
99
+ direction: 'OUTGOING',
100
+ example: 'RouteDefinition -[:ROUTES_TO_HANDLER]-> HttpEndpoint',
101
+ contextProperties: ['path', 'method', 'authenticated', 'handler', 'controllerName'],
102
+ contextNote: 'IMPORTANT: context is stored as a JSON string. Access properties using apoc.convert.fromJsonMap(r.context).propertyName',
103
+ commonPatterns: ['RouteDefinition -> HttpEndpoint (Method)'],
104
+ },
105
+ PROTECTED_BY: {
106
+ description: 'Controller is protected by a PermissionManager',
107
+ direction: 'OUTGOING',
108
+ example: 'Controller -[:PROTECTED_BY]-> PermissionManager',
109
+ commonPatterns: ['Controller -> PermissionManager'],
110
+ },
111
+ },
112
+ commonQueryPatterns: [
113
+ {
114
+ intent: 'Find all HTTP endpoints',
115
+ query: 'MATCH (e:HttpEndpoint) RETURN e.name, e.filePath',
116
+ },
117
+ {
118
+ intent: 'Find service dependency chain',
119
+ query: 'MATCH path = (c:Controller)-[:INJECTS*1..3]->(s) RETURN [n in nodes(path) | n.name] as chain',
120
+ },
121
+ {
122
+ intent: 'Find all authenticated routes',
123
+ query: 'MATCH (rd:RouteDefinition)-[r:ROUTES_TO_HANDLER]->(m) WHERE apoc.convert.fromJsonMap(r.context).authenticated = true RETURN apoc.convert.fromJsonMap(r.context).path as path, apoc.convert.fromJsonMap(r.context).method as method, m.name',
124
+ },
125
+ {
126
+ intent: 'Find controllers without permission managers',
127
+ query: 'MATCH (c:Controller) WHERE NOT (c)-[:PROTECTED_BY]->(:PermissionManager) RETURN c.name',
128
+ },
129
+ {
130
+ intent: 'Find what services a controller uses',
131
+ query: 'MATCH (c:Controller {name: $controllerName})-[:INJECTS]->(s:Service) RETURN s.name',
132
+ },
133
+ {
134
+ intent: 'Find complete execution path from controller to database',
135
+ query: 'MATCH path = (c:Controller)-[:INJECTS*1..3]->(r:Repository)-[:USES_DAL]->(d:DAL) WHERE c.name = $controllerName RETURN [n in nodes(path) | n.name] as executionPath',
136
+ },
137
+ ],
138
+ },
139
+ };
140
+ };
141
+ /**
142
+ * Initialize Neo4j schema by fetching and caching it locally
143
+ */
144
+ const initializeNeo4jSchema = async () => {
145
+ try {
146
+ const neo4jService = new Neo4jService();
147
+ const rawSchema = await neo4jService.getSchema();
148
+ // Enrich schema with FairSquare domain context
149
+ const enrichedSchema = enrichSchemaWithDomainContext(rawSchema);
150
+ const schemaPath = join(process.cwd(), FILE_PATHS.schemaOutput);
151
+ await fs.writeFile(schemaPath, JSON.stringify(enrichedSchema, null, LOG_CONFIG.jsonIndentation));
152
+ await debugLog('Neo4j schema cached successfully with domain context', { schemaPath });
153
+ }
154
+ catch (error) {
155
+ await debugLog('Failed to initialize Neo4j schema', error);
156
+ // Don't throw - service can still function without cached schema
157
+ }
158
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Hello Tool
3
+ * Simple test tool to verify MCP connection
4
+ */
5
+ import { TOOL_NAMES, TOOL_METADATA, MESSAGES } from '../constants.js';
6
+ import { createSuccessResponse } from '../utils.js';
7
+ export const createHelloTool = (server) => {
8
+ server.registerTool(TOOL_NAMES.hello, {
9
+ title: TOOL_METADATA[TOOL_NAMES.hello].title,
10
+ description: TOOL_METADATA[TOOL_NAMES.hello].description,
11
+ inputSchema: {},
12
+ }, async () => createSuccessResponse(MESSAGES.success.hello));
13
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * MCP Tool Factory
3
+ * Centralized tool creation and registration
4
+ */
5
+ import { createHelloTool } from './hello.tool.js';
6
+ import { createNaturalLanguageToCypherTool } from './natural-language-to-cypher.tool.js';
7
+ import { createParseTypescriptProjectTool } from './parse-typescript-project.tool.js';
8
+ import { createSearchCodebaseTool } from './search-codebase.tool.js';
9
+ import { createTestNeo4jConnectionTool } from './test-neo4j-connection.tool.js';
10
+ import { createTraverseFromNodeTool } from './traverse-from-node.tool.js';
11
+ /**
12
+ * Register all MCP tools with the server
13
+ */
14
+ export const registerAllTools = (server) => {
15
+ // Register basic tools
16
+ createHelloTool(server);
17
+ createTestNeo4jConnectionTool(server);
18
+ // Register core functionality tools
19
+ createSearchCodebaseTool(server);
20
+ createTraverseFromNodeTool(server);
21
+ createNaturalLanguageToCypherTool(server);
22
+ // Register project parsing tool
23
+ createParseTypescriptProjectTool(server);
24
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Natural Language to Cypher Tool
3
+ * Converts natural language queries to Cypher using OpenAI GPT-4
4
+ */
5
+ import { z } from 'zod';
6
+ import { NaturalLanguageToCypherService } from '../../core/embeddings/natural-language-to-cypher.service.js';
7
+ import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
8
+ import { TOOL_NAMES, TOOL_METADATA, MESSAGES } from '../constants.js';
9
+ import { createErrorResponse, createSuccessResponse, formatQueryResults, debugLog } from '../utils.js';
10
+ // Service instance - initialized asynchronously
11
+ let naturalLanguageToCypherService = null;
12
+ /**
13
+ * Initialize the Natural Language to Cypher service
14
+ */
15
+ export const initializeNaturalLanguageService = async () => {
16
+ try {
17
+ const service = new NaturalLanguageToCypherService();
18
+ const schemaPath = 'neo4j-apoc-schema.json';
19
+ await service.getOrCreateAssistant(schemaPath);
20
+ naturalLanguageToCypherService = service;
21
+ await debugLog('Natural Language to Cypher service initialized successfully');
22
+ }
23
+ catch (error) {
24
+ await debugLog('Failed to initialize Natural Language to Cypher service', error);
25
+ naturalLanguageToCypherService = null;
26
+ }
27
+ };
28
+ export const createNaturalLanguageToCypherTool = (server) => {
29
+ server.registerTool(TOOL_NAMES.naturalLanguageToCypher, {
30
+ title: TOOL_METADATA[TOOL_NAMES.naturalLanguageToCypher].title,
31
+ description: TOOL_METADATA[TOOL_NAMES.naturalLanguageToCypher].description,
32
+ inputSchema: {
33
+ query: z.string().describe('Natural language query to convert to Cypher'),
34
+ },
35
+ }, async ({ query }) => {
36
+ try {
37
+ if (!naturalLanguageToCypherService) {
38
+ await debugLog('Natural language service not available', { query });
39
+ return createSuccessResponse(MESSAGES.errors.serviceNotInitialized);
40
+ }
41
+ await debugLog('Natural language to Cypher conversion started', { query });
42
+ const cypherResult = await naturalLanguageToCypherService.promptToQuery(query);
43
+ const neo4jService = new Neo4jService();
44
+ // Execute the generated Cypher query
45
+ const results = await neo4jService.run(cypherResult.cypher, cypherResult.parameters ?? {});
46
+ await debugLog('Cypher query executed', {
47
+ cypher: cypherResult.cypher,
48
+ resultsCount: results.length,
49
+ });
50
+ const formattedResponse = formatQueryResults(results, query, cypherResult);
51
+ return createSuccessResponse(JSON.stringify(formattedResponse, null, 2));
52
+ }
53
+ catch (error) {
54
+ console.error('Natural language to Cypher error:', error);
55
+ await debugLog('Natural language to Cypher error', { query, error });
56
+ return createErrorResponse(error);
57
+ }
58
+ });
59
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Parse TypeScript Project Tool
3
+ * Parses TypeScript/NestJS projects and builds Neo4j graph
4
+ */
5
+ import { writeFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { z } from 'zod';
8
+ import { CORE_TYPESCRIPT_SCHEMA } from '../../core/config/schema.js';
9
+ import { EmbeddingsService } from '../../core/embeddings/embeddings.service.js';
10
+ import { ParserFactory } from '../../core/parsers/parser-factory.js';
11
+ import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
12
+ import { TOOL_NAMES, TOOL_METADATA, DEFAULTS, FILE_PATHS, LOG_CONFIG } from '../constants.js';
13
+ import { GraphGeneratorHandler } from '../handlers/graph-generator.handler.js';
14
+ import { createErrorResponse, createSuccessResponse, formatParseSuccess, formatParsePartialSuccess, debugLog, } from '../utils.js';
15
+ export const createParseTypescriptProjectTool = (server) => {
16
+ server.registerTool(TOOL_NAMES.parseTypescriptProject, {
17
+ title: TOOL_METADATA[TOOL_NAMES.parseTypescriptProject].title,
18
+ description: TOOL_METADATA[TOOL_NAMES.parseTypescriptProject].description,
19
+ inputSchema: {
20
+ projectPath: z.string().describe('Path to the TypeScript project root directory'),
21
+ tsconfigPath: z.string().describe('Path to TypeScript project tsconfig.json file'),
22
+ clearExisting: z.boolean().optional().describe('Clear existing graph data first'),
23
+ excludeNodeTypes: z
24
+ .array(z.string())
25
+ .optional()
26
+ .describe('Node types to skip during parsing, e.g. ["TestFile", "Parameter"]'),
27
+ projectType: z
28
+ .enum(['nestjs', 'fairsquare', 'both', 'vanilla', 'auto'])
29
+ .optional()
30
+ .default('auto')
31
+ .describe('Project framework type (auto-detect by default)'),
32
+ },
33
+ }, async ({ tsconfigPath, projectPath, clearExisting, projectType = 'auto' }) => {
34
+ try {
35
+ await debugLog('TypeScript project parsing started', {
36
+ projectPath,
37
+ tsconfigPath,
38
+ clearExisting,
39
+ projectType,
40
+ });
41
+ // Create parser with auto-detection or specified type
42
+ let parser;
43
+ if (projectType === 'auto') {
44
+ parser = await ParserFactory.createParserWithAutoDetection(projectPath, tsconfigPath);
45
+ }
46
+ else {
47
+ parser = ParserFactory.createParser({
48
+ workspacePath: projectPath,
49
+ tsConfigPath: tsconfigPath,
50
+ projectType: projectType,
51
+ });
52
+ }
53
+ // Parse the workspace
54
+ const { nodes, edges } = await parser.parseWorkspace();
55
+ const { nodes: cleanNodes, edges: cleanEdges } = parser.exportToJson();
56
+ console.log(`Parsed ${cleanNodes.length} nodes / ${cleanEdges.length} edges`);
57
+ await debugLog('Parsing completed', {
58
+ nodeCount: cleanNodes.length,
59
+ edgeCount: cleanEdges.length,
60
+ });
61
+ // Create graph JSON output
62
+ const outputPath = join(projectPath, FILE_PATHS.graphOutput);
63
+ // Get detected framework schemas from parser
64
+ const frameworkSchemas = parser['frameworkSchemas']?.map((s) => s.name) ?? ['Auto-detected'];
65
+ const graphData = {
66
+ nodes: cleanNodes,
67
+ edges: cleanEdges,
68
+ metadata: {
69
+ coreSchema: CORE_TYPESCRIPT_SCHEMA.name,
70
+ frameworkSchemas,
71
+ projectType,
72
+ generated: new Date().toISOString(),
73
+ },
74
+ };
75
+ writeFileSync(outputPath, JSON.stringify(graphData, null, LOG_CONFIG.jsonIndentation));
76
+ console.log(`Graph data written to ${outputPath}`);
77
+ // Attempt to import to Neo4j
78
+ try {
79
+ const neo4jService = new Neo4jService();
80
+ const embeddingsService = new EmbeddingsService();
81
+ const graphGeneratorHandler = new GraphGeneratorHandler(neo4jService, embeddingsService);
82
+ const result = await graphGeneratorHandler.generateGraph(outputPath, DEFAULTS.batchSize, clearExisting);
83
+ console.log('Graph generation completed:', result);
84
+ await debugLog('Neo4j import completed', result);
85
+ const successMessage = formatParseSuccess(cleanNodes.length, cleanEdges.length, result);
86
+ return createSuccessResponse(successMessage);
87
+ }
88
+ catch (neo4jError) {
89
+ console.error('Neo4j import failed:', neo4jError);
90
+ await debugLog('Neo4j import failed', neo4jError);
91
+ const partialSuccessMessage = formatParsePartialSuccess(cleanNodes.length, cleanEdges.length, outputPath, neo4jError.message);
92
+ return createSuccessResponse(partialSuccessMessage);
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.error('Parse tool error:', error);
97
+ await debugLog('Parse tool error', { projectPath, tsconfigPath, error });
98
+ return createErrorResponse(error);
99
+ }
100
+ });
101
+ };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Search Codebase Tool
3
+ * Semantic search using vector embeddings to find relevant code
4
+ */
5
+ import { z } from 'zod';
6
+ import { EmbeddingsService } from '../../core/embeddings/embeddings.service.js';
7
+ import { Neo4jService, QUERIES } from '../../storage/neo4j/neo4j.service.js';
8
+ import { TOOL_NAMES, TOOL_METADATA, DEFAULTS, MESSAGES } from '../constants.js';
9
+ import { TraversalHandler } from '../handlers/traversal.handler.js';
10
+ import { createErrorResponse, createSuccessResponse, debugLog } from '../utils.js';
11
+ export const createSearchCodebaseTool = (server) => {
12
+ server.registerTool(TOOL_NAMES.searchCodebase, {
13
+ title: TOOL_METADATA[TOOL_NAMES.searchCodebase].title,
14
+ description: TOOL_METADATA[TOOL_NAMES.searchCodebase].description,
15
+ inputSchema: {
16
+ query: z.string().describe('Natural language query to search the codebase'),
17
+ limit: z
18
+ .number()
19
+ .int()
20
+ .optional()
21
+ .describe(`Maximum number of results to return (default: ${DEFAULTS.searchLimit})`)
22
+ .default(DEFAULTS.searchLimit),
23
+ maxDepth: z
24
+ .number()
25
+ .int()
26
+ .optional()
27
+ .describe(`Maximum depth to traverse relationships (default: ${DEFAULTS.traversalDepth}, max: 10)`)
28
+ .default(DEFAULTS.traversalDepth),
29
+ maxNodesPerChain: z
30
+ .number()
31
+ .int()
32
+ .optional()
33
+ .describe('Maximum chains to show per depth level (default: 5, applied independently at each depth)')
34
+ .default(5),
35
+ skip: z.number().int().optional().describe('Number of results to skip for pagination (default: 0)').default(0),
36
+ includeCode: z
37
+ .boolean()
38
+ .optional()
39
+ .describe('Include source code snippets in results (default: true)')
40
+ .default(true),
41
+ snippetLength: z
42
+ .number()
43
+ .int()
44
+ .optional()
45
+ .describe(`Length of code snippets to include (default: ${DEFAULTS.codeSnippetLength})`)
46
+ .default(DEFAULTS.codeSnippetLength),
47
+ useWeightedTraversal: z
48
+ .boolean()
49
+ .optional()
50
+ .describe('Use weighted traversal strategy that scores each node for relevance (default: false)')
51
+ .default(true),
52
+ },
53
+ }, async ({ query, limit = DEFAULTS.searchLimit, maxDepth = DEFAULTS.traversalDepth, maxNodesPerChain = 5, skip = 0, includeCode = true, snippetLength = DEFAULTS.codeSnippetLength, useWeightedTraversal = true, }) => {
54
+ try {
55
+ await debugLog('Search codebase started', { query, limit });
56
+ const neo4jService = new Neo4jService();
57
+ const embeddingsService = new EmbeddingsService();
58
+ const traversalHandler = new TraversalHandler(neo4jService);
59
+ const embedding = await embeddingsService.embedText(query);
60
+ const vectorResults = await neo4jService.run(QUERIES.VECTOR_SEARCH, {
61
+ limit: parseInt(limit.toString()),
62
+ embedding,
63
+ });
64
+ if (vectorResults.length === 0) {
65
+ await debugLog('No relevant code found', { query, limit });
66
+ return createSuccessResponse(MESSAGES.errors.noRelevantCode);
67
+ }
68
+ const startNode = vectorResults[0].node;
69
+ const nodeId = startNode.properties.id;
70
+ await debugLog('Vector search completed, starting traversal', {
71
+ nodeId,
72
+ resultsCount: vectorResults.length,
73
+ maxDepth,
74
+ maxNodesPerChain,
75
+ skip,
76
+ includeCode,
77
+ snippetLength,
78
+ });
79
+ return await traversalHandler.traverseFromNode(nodeId, embedding, {
80
+ maxDepth,
81
+ direction: 'BOTH', // Show both incoming (who calls this) and outgoing (what this calls)
82
+ includeCode,
83
+ maxNodesPerChain,
84
+ skip,
85
+ summaryOnly: false,
86
+ snippetLength,
87
+ title: `Exploration from Node: ${nodeId}`,
88
+ useWeightedTraversal,
89
+ });
90
+ }
91
+ catch (error) {
92
+ console.error('Search codebase error:', error);
93
+ await debugLog('Search codebase error', error);
94
+ return createErrorResponse(error);
95
+ }
96
+ });
97
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Test Neo4j Connection Tool
3
+ * Verifies Neo4j connectivity and APOC plugin availability
4
+ */
5
+ import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
6
+ import { TOOL_NAMES, TOOL_METADATA, MESSAGES } from '../constants.js';
7
+ import { createErrorResponse, createSuccessResponse } from '../utils.js';
8
+ export const createTestNeo4jConnectionTool = (server) => {
9
+ server.registerTool(TOOL_NAMES.testNeo4jConnection, {
10
+ title: TOOL_METADATA[TOOL_NAMES.testNeo4jConnection].title,
11
+ description: TOOL_METADATA[TOOL_NAMES.testNeo4jConnection].description,
12
+ inputSchema: {},
13
+ }, async () => {
14
+ const driver = new Neo4jService().getDriver();
15
+ try {
16
+ const session = driver.session();
17
+ try {
18
+ const basicResult = await session.run(MESSAGES.neo4j.connectionTest);
19
+ const apocResult = await session.run(MESSAGES.neo4j.apocTest);
20
+ const apocCount = apocResult.records[0].get('apocFunctions').toNumber();
21
+ const message = MESSAGES.neo4j.connectionSuccess
22
+ .replace('{}', basicResult.records[0].get('message'))
23
+ .replace('{}', basicResult.records[0].get('timestamp'))
24
+ .replace('{}', apocCount.toString());
25
+ return createSuccessResponse(message);
26
+ }
27
+ finally {
28
+ await session.close();
29
+ }
30
+ }
31
+ catch (error) {
32
+ const errorMessage = `${MESSAGES.errors.connectionTestFailed}: ${error.message}\n${MESSAGES.errors.neo4jRequirement}`;
33
+ return createErrorResponse(errorMessage);
34
+ }
35
+ finally {
36
+ await driver.close();
37
+ }
38
+ });
39
+ };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Traverse From Node Tool
3
+ * Deep graph traversal from a specific node with pagination support
4
+ */
5
+ import { z } from 'zod';
6
+ import { MAX_TRAVERSAL_DEPTH } from '../../constants.js';
7
+ import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
8
+ import { TOOL_NAMES, TOOL_METADATA, DEFAULTS } from '../constants.js';
9
+ import { TraversalHandler } from '../handlers/traversal.handler.js';
10
+ import { createErrorResponse, sanitizeNumericInput, debugLog } from '../utils.js';
11
+ export const createTraverseFromNodeTool = (server) => {
12
+ server.registerTool(TOOL_NAMES.traverseFromNode, {
13
+ title: TOOL_METADATA[TOOL_NAMES.traverseFromNode].title,
14
+ description: TOOL_METADATA[TOOL_NAMES.traverseFromNode].description,
15
+ inputSchema: {
16
+ nodeId: z.string().describe('The node ID to start traversal from'),
17
+ maxDepth: z
18
+ .number()
19
+ .int()
20
+ .optional()
21
+ .describe(`Maximum depth to traverse (default: ${DEFAULTS.traversalDepth}, max: ${MAX_TRAVERSAL_DEPTH})`)
22
+ .default(DEFAULTS.traversalDepth),
23
+ skip: z
24
+ .number()
25
+ .int()
26
+ .optional()
27
+ .describe(`Number of results to skip for pagination (default: ${DEFAULTS.skipOffset})`)
28
+ .default(DEFAULTS.skipOffset),
29
+ direction: z
30
+ .enum(['OUTGOING', 'INCOMING', 'BOTH'])
31
+ .optional()
32
+ .describe('Filter by relationship direction: OUTGOING (what this calls), INCOMING (who calls this), BOTH (default)')
33
+ .default('BOTH'),
34
+ relationshipTypes: z
35
+ .array(z.string())
36
+ .optional()
37
+ .describe('Filter by specific relationship types (e.g., ["INJECTS", "USES_REPOSITORY"]). If not specified, shows all relationships.'),
38
+ includeCode: z
39
+ .boolean()
40
+ .optional()
41
+ .describe('Include source code snippets in results (default: true, set to false for structure-only view)')
42
+ .default(true),
43
+ maxNodesPerChain: z
44
+ .number()
45
+ .int()
46
+ .optional()
47
+ .describe('Maximum chains to show per depth level (default: 5, applied independently at each depth)')
48
+ .default(5),
49
+ summaryOnly: z
50
+ .boolean()
51
+ .optional()
52
+ .describe('Return only summary with file paths and statistics (default: false)')
53
+ .default(false),
54
+ snippetLength: z
55
+ .number()
56
+ .int()
57
+ .optional()
58
+ .describe(`Code snippet character length when includeCode is true (default: ${DEFAULTS.codeSnippetLength})`)
59
+ .default(DEFAULTS.codeSnippetLength),
60
+ },
61
+ }, async ({ nodeId, maxDepth = DEFAULTS.traversalDepth, skip = DEFAULTS.skipOffset, direction = 'BOTH', relationshipTypes, includeCode = true, maxNodesPerChain = 5, summaryOnly = false, snippetLength = DEFAULTS.codeSnippetLength, }) => {
62
+ try {
63
+ const sanitizedMaxDepth = sanitizeNumericInput(maxDepth, DEFAULTS.traversalDepth, MAX_TRAVERSAL_DEPTH);
64
+ const sanitizedSkip = sanitizeNumericInput(skip, DEFAULTS.skipOffset);
65
+ await debugLog('Node traversal started', {
66
+ nodeId,
67
+ maxDepth: sanitizedMaxDepth,
68
+ skip: sanitizedSkip,
69
+ direction,
70
+ relationshipTypes,
71
+ includeCode,
72
+ maxNodesPerChain,
73
+ summaryOnly,
74
+ snippetLength,
75
+ });
76
+ const neo4jService = new Neo4jService();
77
+ const traversalHandler = new TraversalHandler(neo4jService);
78
+ return await traversalHandler.traverseFromNode(nodeId, [], {
79
+ maxDepth: sanitizedMaxDepth,
80
+ skip: sanitizedSkip,
81
+ direction,
82
+ relationshipTypes,
83
+ includeStartNodeDetails: true,
84
+ includeCode,
85
+ maxNodesPerChain,
86
+ summaryOnly,
87
+ snippetLength,
88
+ title: `Node Traversal from: ${nodeId}`,
89
+ });
90
+ }
91
+ catch (error) {
92
+ console.error('Node traversal error:', error);
93
+ await debugLog('Node traversal error', { nodeId, error });
94
+ return createErrorResponse(error);
95
+ }
96
+ });
97
+ };