Init
This commit is contained in:
@@ -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';
|
||||
Reference in New Issue
Block a user