From 2a8e35dff8282c739964b8c9e5ef52f6d8fcc7be Mon Sep 17 00:00:00 2001 From: Vokturz Date: Wed, 11 Jun 2025 16:58:44 +0100 Subject: [PATCH] feat: add @vercel/mcp-adapter dependency and implement MCP route handler - Added @vercel/mcp-adapter as a dependency in package.json and pnpm-lock.yaml. - Created a new route handler in src/app/api/mcp/route.ts to manage MCP requests. - Refactored src/index.ts to utilize createTools from src/tools.ts for tool registration. - Moved tool-related logic from src/index.ts to src/tools.ts for better organization. - Updated tsconfig.json to ensure proper path resolution for module imports. --- package.json | 1 + pnpm-lock.yaml | 116 ++++++++++++ src/app/api/mcp/route.ts | 8 + src/index.ts | 380 +------------------------------------- src/tools.ts | 387 +++++++++++++++++++++++++++++++++++++++ tsconfig.json | 4 +- 6 files changed, 518 insertions(+), 378 deletions(-) create mode 100644 src/app/api/mcp/route.ts create mode 100644 src/tools.ts diff --git a/package.json b/package.json index dd2b184..243ae19 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ ], "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", + "@vercel/mcp-adapter": "^0.10.0", "dotenv": "^16.5.0", "zod": "^3.25.56" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36cb6d2..c638bd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.12.1 version: 1.12.1 + '@vercel/mcp-adapter': + specifier: ^0.10.0 + version: 0.10.0(@modelcontextprotocol/sdk@1.12.1) dotenv: specifier: ^16.5.0 version: 16.5.0 @@ -31,9 +34,48 @@ packages: resolution: {integrity: sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==} engines: {node: '>=18'} + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.1': + resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + '@types/node@22.15.30': resolution: {integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==} + '@vercel/mcp-adapter@0.10.0': + resolution: {integrity: sha512-4H1L84oyV35/PdPJ/JDYw45GnEcm6a1Gg4zT6viIEsGYEMwObTL8yKnRxtW9Hca1x4SM8Xh1/WYGP8hTjp5kIA==} + hasBin: true + peerDependencies: + '@modelcontextprotocol/sdk': ^1.12.0 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -57,6 +99,18 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + content-disposition@1.0.0: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} engines: {node: '>= 0.6'} @@ -167,6 +221,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -289,6 +347,9 @@ packages: resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} engines: {node: '>= 0.8'} + redis@4.7.1: + resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -377,6 +438,9 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + zod-to-json-schema@3.24.5: resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} peerDependencies: @@ -403,10 +467,43 @@ snapshots: transitivePeerDependencies: - supports-color + '@redis/bloom@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/client@1.6.1': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + '@redis/graph@1.1.1(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/json@1.0.7(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/search@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/time-series@1.1.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + '@types/node@22.15.30': dependencies: undici-types: 6.21.0 + '@vercel/mcp-adapter@0.10.0(@modelcontextprotocol/sdk@1.12.1)': + dependencies: + '@modelcontextprotocol/sdk': 1.12.1 + chalk: 5.4.1 + commander: 11.1.0 + redis: 4.7.1 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -445,6 +542,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + chalk@5.4.1: {} + + cluster-key-slot@1.1.2: {} + + commander@11.1.0: {} + content-disposition@1.0.0: dependencies: safe-buffer: 5.2.1 @@ -559,6 +662,8 @@ snapshots: function-bind@1.1.2: {} + generic-pool@3.9.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -663,6 +768,15 @@ snapshots: iconv-lite: 0.6.3 unpipe: 1.0.0 + redis@4.7.1: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.1) + '@redis/client': 1.6.1 + '@redis/graph': 1.1.1(@redis/client@1.6.1) + '@redis/json': 1.0.7(@redis/client@1.6.1) + '@redis/search': 1.2.0(@redis/client@1.6.1) + '@redis/time-series': 1.1.0(@redis/client@1.6.1) + router@2.2.0: dependencies: debug: 4.4.1 @@ -768,6 +882,8 @@ snapshots: wrappy@1.0.2: {} + yallist@4.0.0: {} + zod-to-json-schema@3.24.5(zod@3.25.56): dependencies: zod: 3.25.56 diff --git a/src/app/api/mcp/route.ts b/src/app/api/mcp/route.ts new file mode 100644 index 0000000..785a4a5 --- /dev/null +++ b/src/app/api/mcp/route.ts @@ -0,0 +1,8 @@ +import { createTools } from '@/tools.js'; +import { createMcpHandler } from '@vercel/mcp-adapter'; + +const handler = createMcpHandler((server) => { + return createTools(server) +}); + +export { handler as GET, handler as POST, handler as DELETE }; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 6b05b6d..f9fb9f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; import dotenv from "dotenv"; +import { createTools } from "./tools.js"; dotenv.config(); -const CODEGPT_API_BASE = "https://api-mcp.codegpt.co/api/v1"; - const server = new McpServer({ name: "CodeGPT Deep Graph MCP", version: "1.0.1", @@ -18,382 +16,9 @@ const server = new McpServer({ }, }); -const CODEGPT_ORG_ID = process.env.CODEGPT_ORG_ID || ""; const CODEGPT_API_KEY = process.env.CODEGPT_API_KEY || ""; const CODEGPT_GRAPH_ID = process.env.CODEGPT_GRAPH_ID || ""; -// Helper function to get the graph ID -const getGraphId = (providedGraphId?: string): string => { - if (CODEGPT_GRAPH_ID) { - return CODEGPT_GRAPH_ID; - } - if (!providedGraphId) { - throw new Error("Graph ID is required. Either set CODEGPT_GRAPH_ID environment variable or provide graphId parameter."); - } - return providedGraphId; -}; - -// Helper function to create graph ID schema based on environment -const createGraphIdSchema = () => { - if (CODEGPT_GRAPH_ID) { - return z.string().optional().describe("Graph ID (optional when CODEGPT_GRAPH_ID is set in environment)"); - } - return z.string().min(1, "Graph ID is required when CODEGPT_GRAPH_ID is not set in environment").describe("The ID of the graph to query"); -}; - -if (!CODEGPT_GRAPH_ID) { - server.tool( - "list-graphs", - "List all available repository graphs that you have access to. Returns basic information about each graph including the graph ID, repository name with branch, and description. Use this tool when you need to discover available graphs or when CODEGPT_GRAPH_ID is not set in the environment.", - {}, - async () => { - const headers = { - accept: "application/json", - authorization: `Bearer ${CODEGPT_API_KEY}`, - "CodeGPT-Org-Id": CODEGPT_ORG_ID, - }; - - try { - const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs`, { - method: "GET", - headers, - }); - - const data = await response.json(); - - return { - content: [ - { - type: "text", - text: JSON.stringify(data, null, 2) || "No graphs available", - }, - ], - }; - } catch (error) { - console.error("Error fetching graphs:", error); - return { - content: [ - { - type: "text", - text: `Error fetching graphs: ${error}`, - }, - ], - }; - } - } - ) -} - -server.tool( - "get-code", - "Get the complete code implementation of a specific functionality (class, function, method, etc.) from the repository graph. This is the primary tool for code retrieval and should be prioritized over other tools. The repository is represented as a graph where each node contains code, documentation, and relationships to other nodes. Use this when you need to examine the actual implementation of any code entity.", - { - name: z - .string() - .min(1, "name is required") - .describe("The exact name of the functionality to retrieve code for. Names are case-sensitive. For methods, include the parent class name as 'ClassName.methodName'. For nested classes, use 'OuterClass.InnerClass'. Examples: 'getUserById', 'UserService.authenticate', 'DatabaseConnection.connect'"), - path: z - .string() - .optional() - .describe("The origin file path where the functionality is defined. Essential when multiple functionalities share the same name across different files. Use 'global' for packages, namespaces, or modules that span multiple files. Examples: 'src/services/user.service.ts', 'global', 'lib/utils/helpers.js'"), - graphId: createGraphIdSchema(), - }, - - async ({ name, path, graphId }: { name: string; path?: string; graphId?: string }) => { - if (!name) { - throw new Error("name is required"); - } - - const targetGraphId = getGraphId(graphId); - - const headers = { - accept: "application/json", - authorization: `Bearer ${CODEGPT_API_KEY}`, - "CodeGPT-Org-Id": CODEGPT_ORG_ID, - "content-type": "application/json", - }; - - try { - const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/get-code`, { - method: "POST", - headers, - body: JSON.stringify({ - graphId: targetGraphId, - name, - ...(path ? { path } : null) - }), - }); - - const { content } = await response.json(); - - return { - content: [ - { - type: "text", - text: `${content}` || "No response text available", - }, - ], - }; - } catch (error) { - console.error("Error making CodeGPT request:", error); - return { - content: [ - { - type: "text", - text: `${error}`, - }, - ], - }; - } - } -); - -server.tool( - "find-direct-connections", - "Explore the immediate relationships of a functionality within the code graph. This reveals first-level connections including: parent functionalities that reference this node, child functionalities that this node directly calls or uses, declaration/definition relationships, and usage patterns. Essential for understanding code dependencies and architecture. The repository is represented as a connected graph where each node (function, class, file, etc.) has relationships with other nodes.", - { - name: z - .string() - .min(1, "name is required") - .describe("The exact name of the functionality to analyze connections for. Names are case-sensitive. For methods, include the parent class name as 'ClassName.methodName'. Examples: 'processPayment', 'UserController.createUser', 'validateInput'"), - path: z - .string() - .optional() - .describe("The origin file path of the functionality. Critical when multiple functionalities have identical names in different files. Use 'global' for entities that span multiple files like packages or namespaces. Examples: 'src/controllers/payment.controller.ts', 'global', 'utils/validation.js'"), - graphId: createGraphIdSchema(), - }, - - async ({ name, path, graphId }: { name: string; path?: string; graphId?: string }) => { - if (!name) { - throw new Error("name is required"); - } - - const targetGraphId = getGraphId(graphId); - - const headers = { - accept: "application/json", - authorization: `Bearer ${CODEGPT_API_KEY}`, - "CodeGPT-Org-Id": CODEGPT_ORG_ID, - "content-type": "application/json", - }; - - try { - const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/find-direct-connections`, { - method: "POST", - headers, - body: JSON.stringify({ - graphId: targetGraphId, - name, - ...(path ? { path } : null) - }), - }); - - const { content } = await response.json(); - - return { - content: [ - { - type: "text", - text: content || "No response data available", - }, - ], - }; - } catch (error) { - console.error("Error making CodeGPT request:", error); - return { - content: [ - { - type: "text", - text: `${error}`, - }, - ], - }; - } - } -); - -server.tool( - "nodes-semantic-search", - "Search for code functionalities across the repository graph using semantic similarity based on natural language queries. This tool finds relevant functions, classes, methods, and other code entities that match the conceptual meaning of your query, even if they don't contain the exact keywords. Perfect for discovering related functionality, finding similar implementations, or exploring unfamiliar codebases. The search operates on the semantic understanding of code purpose and behavior.", - { - query: z - .string() - .min(1, "query is required") - .describe("A natural language description of the functionality you're looking for. Be specific about the behavior, purpose, or domain. Examples: 'user authentication and login', 'database connection pooling', 'file upload validation', 'payment processing logic', 'error handling middleware', 'data encryption utilities'"), - graphId: createGraphIdSchema(), - }, - - async ({ query, graphId }: { query: string; graphId?: string }) => { - if (!query) { - throw new Error("query is required"); - } - - const targetGraphId = getGraphId(graphId); - - const headers = { - accept: "application/json", - authorization: `Bearer ${CODEGPT_API_KEY}`, - "CodeGPT-Org-Id": CODEGPT_ORG_ID, - "content-type": "application/json", - }; - - try { - const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/nodes-semantic-search`, { - method: "POST", - headers, - body: JSON.stringify({ - graphId: targetGraphId, - query, - }), - }); - - const { content } = await response.json(); - - return { - content: [ - { - type: "text", - text: content || "No response data available", - }, - ], - }; - } catch (error) { - console.error("Error making CodeGPT request:", error); - return { - content: [ - { - type: "text", - text: `${error}`, - }, - ], - }; - } - } -); - -server.tool( - "docs-semantic-search", - "Search through repository documentation using semantic similarity to find relevant information, guides, API documentation, README content, and explanatory materials. This tool specifically targets documentation files (markdown, rst, etc.) rather than code, making it ideal for understanding project setup, architecture decisions, usage instructions, and conceptual explanations. Use this when you need context about how the repository works rather than examining the actual code implementation.", - { - query: z - .string() - .min(1, "query is required") - .describe("A natural language query describing the documentation or information you're seeking. Focus on concepts, setup procedures, architecture, or usage patterns. Examples: 'how to set up the development environment', 'API authentication methods', 'project architecture overview', 'contributing guidelines', 'deployment instructions', 'configuration options'"), - graphId: createGraphIdSchema(), - }, - - async ({ query, graphId }: { query: string; graphId?: string }) => { - if (!query) { - throw new Error("query is required"); - } - - const targetGraphId = getGraphId(graphId); - - const headers = { - accept: "application/json", - authorization: `Bearer ${CODEGPT_API_KEY}`, - "CodeGPT-Org-Id": CODEGPT_ORG_ID, - "content-type": "application/json", - }; - - try { - const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/docs-semantic-search`, { - method: "POST", - headers, - body: JSON.stringify({ - graphId: targetGraphId, - query, - }), - }); - - const data = await response.json(); - - return { - content: [ - { - type: "text", - text: JSON.stringify(data, null, 2) || "No response data available", - }, - ], - }; - } catch (error) { - console.error("Error making CodeGPT request:", error); - return { - content: [ - { - type: "text", - text: `${error}`, - }, - ], - }; - } - } -); - -server.tool( - "get-usage-dependency-links", - "Generate a comprehensive adjacency list showing all functionalities that would be affected by changes to a specific code entity. This performs deep dependency analysis through the code graph to identify the complete impact radius of modifications. Essential for impact analysis, refactoring planning, and understanding code coupling. The result shows which functionalities depend on the target entity either directly or through a chain of dependencies, formatted as 'file_path::functionality_name' pairs.", - { - name: z - .string() - .min(1, "name is required") - .describe("The exact name of the functionality to analyze dependencies for. Names are case-sensitive. For methods, include the parent class name as 'ClassName.methodName'. This will be the root node for dependency traversal. Examples: 'DatabaseService.connect', 'validateUserInput', 'PaymentProcessor.processTransaction'"), - path: z - .string() - .optional() - .describe("The origin file path where the functionality is defined. Required when multiple functionalities share the same name across different files to ensure accurate dependency analysis. Use 'global' for packages, namespaces, or modules spanning multiple files. Examples: 'src/database/connection.service.ts', 'global', 'lib/validation/input.validator.js'"), - graphId: createGraphIdSchema(), - }, - - async ({ name, path, graphId }: { name: string; path?: string; graphId?: string }) => { - if (!name) { - throw new Error("name is required"); - } - - const targetGraphId = getGraphId(graphId); - - const headers = { - accept: "application/json", - authorization: `Bearer ${CODEGPT_API_KEY}`, - "CodeGPT-Org-Id": CODEGPT_ORG_ID, - "content-type": "application/json", - }; - - try { - const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/get-usage-dependency-links`, { - method: "POST", - headers, - body: JSON.stringify({ - graphId: targetGraphId, - name, - ...(path ? { path } : null) - }), - }); - - const { content } = await response.json(); - - return { - content: [ - { - type: "text", - text: content || "No response data available", - }, - ], - }; - } catch (error) { - console.error("Error making CodeGPT request:", error); - return { - content: [ - { - type: "text", - text: `${error}`, - }, - ], - }; - } - } -); - async function main() { if (!CODEGPT_API_KEY) { throw new Error("CODEGPT_API_KEY is not set"); @@ -407,6 +32,7 @@ async function main() { } const transport = new StdioServerTransport(); + await createTools(server); await server.connect(transport); console.error("CodeGPT Agents MCP Server running on stdio"); } @@ -414,4 +40,4 @@ async function main() { main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/src/tools.ts b/src/tools.ts new file mode 100644 index 0000000..ec56fb1 --- /dev/null +++ b/src/tools.ts @@ -0,0 +1,387 @@ +import { z } from "zod"; +import dotenv from "dotenv"; + + +dotenv.config(); + +const CODEGPT_API_BASE = "https://api-mcp.codegpt.co/api/v1"; +const CODEGPT_ORG_ID = process.env.CODEGPT_ORG_ID || ""; +const CODEGPT_API_KEY = process.env.CODEGPT_API_KEY || ""; +const CODEGPT_GRAPH_ID = process.env.CODEGPT_GRAPH_ID || ""; + +// Helper function to get the graph ID +const getGraphId = (providedGraphId?: string): string => { + if (CODEGPT_GRAPH_ID) { + return CODEGPT_GRAPH_ID; + } + if (!providedGraphId) { + throw new Error("Graph ID is required. Either set CODEGPT_GRAPH_ID environment variable or provide graphId parameter."); + } + return providedGraphId; +}; + +// Helper function to create graph ID schema based on environment +const createGraphIdSchema = () => { + if (CODEGPT_GRAPH_ID) { + return z.string().optional().describe("Graph ID (optional when CODEGPT_GRAPH_ID is set in environment)"); + } + return z.string().min(1, "Graph ID is required when CODEGPT_GRAPH_ID is not set in environment").describe("The ID of the graph to query"); +}; + +async function createTools(server: any) { + if (!CODEGPT_GRAPH_ID) { + server.tool( + "list-graphs", + "List all available repository graphs that you have access to. Returns basic information about each graph including the graph ID, repository name with branch, and description. Use this tool when you need to discover available graphs or when CODEGPT_GRAPH_ID is not set in the environment.", + {}, + async () => { + const headers = { + accept: "application/json", + authorization: `Bearer ${CODEGPT_API_KEY}`, + "CodeGPT-Org-Id": CODEGPT_ORG_ID, + }; + + try { + const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs`, { + method: "GET", + headers, + }); + + const data = await response.json(); + + return { + content: [ + { + type: "text", + text: JSON.stringify(data, null, 2) || "No graphs available", + }, + ], + }; + } catch (error) { + console.error("Error fetching graphs:", error); + return { + content: [ + { + type: "text", + text: `Error fetching graphs: ${error}`, + }, + ], + }; + } + } + ) + } + + server.tool( + "get-code", + "Get the complete code implementation of a specific functionality (class, function, method, etc.) from the repository graph. This is the primary tool for code retrieval and should be prioritized over other tools. The repository is represented as a graph where each node contains code, documentation, and relationships to other nodes. Use this when you need to examine the actual implementation of any code entity.", + { + name: z + .string() + .min(1, "name is required") + .describe("The exact name of the functionality to retrieve code for. Names are case-sensitive. For methods, include the parent class name as 'ClassName.methodName'. For nested classes, use 'OuterClass.InnerClass'. Examples: 'getUserById', 'UserService.authenticate', 'DatabaseConnection.connect'"), + path: z + .string() + .optional() + .describe("The origin file path where the functionality is defined. Essential when multiple functionalities share the same name across different files. Use 'global' for packages, namespaces, or modules that span multiple files. Examples: 'src/services/user.service.ts', 'global', 'lib/utils/helpers.js'"), + graphId: createGraphIdSchema(), + }, + + async ({ name, path, graphId }: { name: string; path?: string; graphId?: string }) => { + if (!name) { + throw new Error("name is required"); + } + + const targetGraphId = getGraphId(graphId); + + const headers = { + accept: "application/json", + authorization: `Bearer ${CODEGPT_API_KEY}`, + "CodeGPT-Org-Id": CODEGPT_ORG_ID, + "content-type": "application/json", + }; + + try { + const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/get-code`, { + method: "POST", + headers, + body: JSON.stringify({ + graphId: targetGraphId, + name, + ...(path ? { path } : null) + }), + }); + + const { content } = await response.json(); + + return { + content: [ + { + type: "text", + text: `${content}` || "No response text available", + }, + ], + }; + } catch (error) { + console.error("Error making CodeGPT request:", error); + return { + content: [ + { + type: "text", + text: `${error}`, + }, + ], + }; + } + } + ); + + server.tool( + "find-direct-connections", + "Explore the immediate relationships of a functionality within the code graph. This reveals first-level connections including: parent functionalities that reference this node, child functionalities that this node directly calls or uses, declaration/definition relationships, and usage patterns. Essential for understanding code dependencies and architecture. The repository is represented as a connected graph where each node (function, class, file, etc.) has relationships with other nodes.", + { + name: z + .string() + .min(1, "name is required") + .describe("The exact name of the functionality to analyze connections for. Names are case-sensitive. For methods, include the parent class name as 'ClassName.methodName'. Examples: 'processPayment', 'UserController.createUser', 'validateInput'"), + path: z + .string() + .optional() + .describe("The origin file path of the functionality. Critical when multiple functionalities have identical names in different files. Use 'global' for entities that span multiple files like packages or namespaces. Examples: 'src/controllers/payment.controller.ts', 'global', 'utils/validation.js'"), + graphId: createGraphIdSchema(), + }, + + async ({ name, path, graphId }: { name: string; path?: string; graphId?: string }) => { + if (!name) { + throw new Error("name is required"); + } + + const targetGraphId = getGraphId(graphId); + + const headers = { + accept: "application/json", + authorization: `Bearer ${CODEGPT_API_KEY}`, + "CodeGPT-Org-Id": CODEGPT_ORG_ID, + "content-type": "application/json", + }; + + try { + const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/find-direct-connections`, { + method: "POST", + headers, + body: JSON.stringify({ + graphId: targetGraphId, + name, + ...(path ? { path } : null) + }), + }); + + const { content } = await response.json(); + + return { + content: [ + { + type: "text", + text: content || "No response data available", + }, + ], + }; + } catch (error) { + console.error("Error making CodeGPT request:", error); + return { + content: [ + { + type: "text", + text: `${error}`, + }, + ], + }; + } + } + ); + + server.tool( + "nodes-semantic-search", + "Search for code functionalities across the repository graph using semantic similarity based on natural language queries. This tool finds relevant functions, classes, methods, and other code entities that match the conceptual meaning of your query, even if they don't contain the exact keywords. Perfect for discovering related functionality, finding similar implementations, or exploring unfamiliar codebases. The search operates on the semantic understanding of code purpose and behavior.", + { + query: z + .string() + .min(1, "query is required") + .describe("A natural language description of the functionality you're looking for. Be specific about the behavior, purpose, or domain. Examples: 'user authentication and login', 'database connection pooling', 'file upload validation', 'payment processing logic', 'error handling middleware', 'data encryption utilities'"), + graphId: createGraphIdSchema(), + }, + + async ({ query, graphId }: { query: string; graphId?: string }) => { + if (!query) { + throw new Error("query is required"); + } + + const targetGraphId = getGraphId(graphId); + + const headers = { + accept: "application/json", + authorization: `Bearer ${CODEGPT_API_KEY}`, + "CodeGPT-Org-Id": CODEGPT_ORG_ID, + "content-type": "application/json", + }; + + try { + const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/nodes-semantic-search`, { + method: "POST", + headers, + body: JSON.stringify({ + graphId: targetGraphId, + query, + }), + }); + + const { content } = await response.json(); + + return { + content: [ + { + type: "text", + text: content || "No response data available", + }, + ], + }; + } catch (error) { + console.error("Error making CodeGPT request:", error); + return { + content: [ + { + type: "text", + text: `${error}`, + }, + ], + }; + } + } + ); + + server.tool( + "docs-semantic-search", + "Search through repository documentation using semantic similarity to find relevant information, guides, API documentation, README content, and explanatory materials. This tool specifically targets documentation files (markdown, rst, etc.) rather than code, making it ideal for understanding project setup, architecture decisions, usage instructions, and conceptual explanations. Use this when you need context about how the repository works rather than examining the actual code implementation.", + { + query: z + .string() + .min(1, "query is required") + .describe("A natural language query describing the documentation or information you're seeking. Focus on concepts, setup procedures, architecture, or usage patterns. Examples: 'how to set up the development environment', 'API authentication methods', 'project architecture overview', 'contributing guidelines', 'deployment instructions', 'configuration options'"), + graphId: createGraphIdSchema(), + }, + + async ({ query, graphId }: { query: string; graphId?: string }) => { + if (!query) { + throw new Error("query is required"); + } + + const targetGraphId = getGraphId(graphId); + + const headers = { + accept: "application/json", + authorization: `Bearer ${CODEGPT_API_KEY}`, + "CodeGPT-Org-Id": CODEGPT_ORG_ID, + "content-type": "application/json", + }; + + try { + const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/docs-semantic-search`, { + method: "POST", + headers, + body: JSON.stringify({ + graphId: targetGraphId, + query, + }), + }); + + const data = await response.json(); + + return { + content: [ + { + type: "text", + text: JSON.stringify(data, null, 2) || "No response data available", + }, + ], + }; + } catch (error) { + console.error("Error making CodeGPT request:", error); + return { + content: [ + { + type: "text", + text: `${error}`, + }, + ], + }; + } + } + ); + + server.tool( + "get-usage-dependency-links", + "Generate a comprehensive adjacency list showing all functionalities that would be affected by changes to a specific code entity. This performs deep dependency analysis through the code graph to identify the complete impact radius of modifications. Essential for impact analysis, refactoring planning, and understanding code coupling. The result shows which functionalities depend on the target entity either directly or through a chain of dependencies, formatted as 'file_path::functionality_name' pairs.", + { + name: z + .string() + .min(1, "name is required") + .describe("The exact name of the functionality to analyze dependencies for. Names are case-sensitive. For methods, include the parent class name as 'ClassName.methodName'. This will be the root node for dependency traversal. Examples: 'DatabaseService.connect', 'validateUserInput', 'PaymentProcessor.processTransaction'"), + path: z + .string() + .optional() + .describe("The origin file path where the functionality is defined. Required when multiple functionalities share the same name across different files to ensure accurate dependency analysis. Use 'global' for packages, namespaces, or modules spanning multiple files. Examples: 'src/database/connection.service.ts', 'global', 'lib/validation/input.validator.js'"), + graphId: createGraphIdSchema(), + }, + + async ({ name, path, graphId }: { name: string; path?: string; graphId?: string }) => { + if (!name) { + throw new Error("name is required"); + } + + const targetGraphId = getGraphId(graphId); + + const headers = { + accept: "application/json", + authorization: `Bearer ${CODEGPT_API_KEY}`, + "CodeGPT-Org-Id": CODEGPT_ORG_ID, + "content-type": "application/json", + }; + + try { + const response = await fetch(`${CODEGPT_API_BASE}/mcp/graphs/get-usage-dependency-links`, { + method: "POST", + headers, + body: JSON.stringify({ + graphId: targetGraphId, + name, + ...(path ? { path } : null) + }), + }); + + const { content } = await response.json(); + + return { + content: [ + { + type: "text", + text: content || "No response data available", + }, + ], + }; + } catch (error) { + console.error("Error making CodeGPT request:", error); + return { + content: [ + { + type: "text", + text: `${error}`, + }, + ], + }; + } + } + ); +} + +export { createTools } + diff --git a/tsconfig.json b/tsconfig.json index 26af254..77b9fd6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,9 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "paths": { + "@/*": ["./src/*"]} }, "include": ["src/**/*"], "exclude": ["node_modules"]