This commit is contained in:
Colby McHenry
2026-01-18 16:25:00 -06:00
parent 08ccabb5a9
commit cc6e7a5c89
57 changed files with 23315 additions and 1 deletions
+205
View File
@@ -0,0 +1,205 @@
/**
* CodeGraph MCP Server
*
* Model Context Protocol server that exposes CodeGraph functionality
* as tools for AI assistants like Claude.
*
* @module mcp
*
* @example
* ```typescript
* import { MCPServer } from 'codegraph';
*
* const server = new MCPServer('/path/to/project');
* await server.start();
* ```
*/
import CodeGraph from '../index';
import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport';
import { tools, ToolHandler } from './tools';
/**
* MCP Server Info
*/
const SERVER_INFO = {
name: 'codegraph',
version: '0.1.0',
};
/**
* MCP Protocol Version
*/
const PROTOCOL_VERSION = '2024-11-05';
/**
* MCP Server for CodeGraph
*
* Implements the Model Context Protocol to expose CodeGraph
* functionality as tools that can be called by AI assistants.
*/
export class MCPServer {
private transport: StdioTransport;
private cg: CodeGraph | null = null;
private toolHandler: ToolHandler | null = null;
private projectPath: string;
constructor(projectPath: string) {
this.projectPath = projectPath;
this.transport = new StdioTransport();
}
/**
* Start the MCP server
*/
async start(): Promise<void> {
// Open CodeGraph for the project
if (!CodeGraph.isInitialized(this.projectPath)) {
throw new Error(`CodeGraph not initialized in ${this.projectPath}. Run 'codegraph init' first.`);
}
this.cg = await CodeGraph.open(this.projectPath);
this.toolHandler = new ToolHandler(this.cg);
// Start listening for messages
this.transport.start(this.handleMessage.bind(this));
// Keep the process running
process.on('SIGINT', () => this.stop());
process.on('SIGTERM', () => this.stop());
}
/**
* Stop the server
*/
stop(): void {
if (this.cg) {
this.cg.close();
this.cg = null;
}
this.transport.stop();
process.exit(0);
}
/**
* Handle incoming JSON-RPC messages
*/
private async handleMessage(message: JsonRpcRequest | JsonRpcNotification): Promise<void> {
// Check if it's a request (has id) or notification (no id)
const isRequest = 'id' in message;
switch (message.method) {
case 'initialize':
if (isRequest) {
await this.handleInitialize(message as JsonRpcRequest);
}
break;
case 'initialized':
// Notification that client has finished initialization
// No action needed - the client is ready
break;
case 'tools/list':
if (isRequest) {
await this.handleToolsList(message as JsonRpcRequest);
}
break;
case 'tools/call':
if (isRequest) {
await this.handleToolsCall(message as JsonRpcRequest);
}
break;
case 'ping':
if (isRequest) {
this.transport.sendResult((message as JsonRpcRequest).id, {});
}
break;
default:
if (isRequest) {
this.transport.sendError(
(message as JsonRpcRequest).id,
ErrorCodes.MethodNotFound,
`Method not found: ${message.method}`
);
}
}
}
/**
* Handle initialize request
*/
private async handleInitialize(request: JsonRpcRequest): Promise<void> {
// We accept the client's protocol version but respond with our supported version
this.transport.sendResult(request.id, {
protocolVersion: PROTOCOL_VERSION,
capabilities: {
tools: {},
},
serverInfo: SERVER_INFO,
});
}
/**
* Handle tools/list request
*/
private async handleToolsList(request: JsonRpcRequest): Promise<void> {
this.transport.sendResult(request.id, {
tools: tools,
});
}
/**
* Handle tools/call request
*/
private async handleToolsCall(request: JsonRpcRequest): Promise<void> {
const params = request.params as {
name: string;
arguments?: Record<string, unknown>;
};
if (!params || !params.name) {
this.transport.sendError(
request.id,
ErrorCodes.InvalidParams,
'Missing tool name'
);
return;
}
const toolName = params.name;
const toolArgs = params.arguments || {};
// Validate tool exists
const tool = tools.find(t => t.name === toolName);
if (!tool) {
this.transport.sendError(
request.id,
ErrorCodes.InvalidParams,
`Unknown tool: ${toolName}`
);
return;
}
// Execute the tool
if (!this.toolHandler) {
this.transport.sendError(
request.id,
ErrorCodes.InternalError,
'Server not initialized'
);
return;
}
const result = await this.toolHandler.execute(toolName, toolArgs);
this.transport.sendResult(request.id, result);
}
}
// Export for use in CLI
export { StdioTransport } from './transport';
export { tools, ToolHandler } from './tools';