UNPKG

@agentics.org/sparc2

Version:

SPARC 2.0 - Autonomous Vector Coding Agent + MCP. SPARC 2.0, vectorized AI code analysis, is an intelligent coding agent framework built to automate and streamline software development. It combines secure execution environments, and version control into

500 lines (458 loc) 15.2 kB
/** * MCP Server module for SPARC 2.0 using stdio transport * Implements a Model Context Protocol server for SPARC2 using the MCP SDK */ import { Server } from "npm:@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "npm:@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "npm:@modelcontextprotocol/sdk/types.js"; import { FileToProcess, SPARC2Agent } from "../agent/agent.ts"; import { logDebug, logError, logInfo } from "../logger.ts"; import { executeCode } from "../sandbox/codeInterpreter.ts"; import { createCheckpoint } from "../git/gitIntegration.ts"; /** * Start the MCP server using stdio transport * @param options Server options * @returns Promise that resolves when the server is started */ export async function startMCPServerStdio(options: { model?: string; mode?: "automatic" | "semi" | "manual" | "custom" | "interactive"; diffMode?: "file" | "function"; processing?: "sequential" | "parallel" | "concurrent" | "swarm"; configPath?: string; }): Promise<void> { // Initialize SPARC2 agent const agent = new SPARC2Agent({ model: options.model || "gpt-4o", mode: options.mode || "automatic", diffMode: options.diffMode || "file", processing: options.processing || "sequential", configPath: "./config/agent-config.toml", }); // Initialize the agent await agent.init(); await logInfo("Starting SPARC2 MCP server using stdio transport"); // Create the MCP server const server = new Server( { name: "sparc2-mcp", version: "2.0.5", }, { capabilities: { tools: {}, }, }, ); // Set up the ListTools request handler server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "analyze_code", description: "Analyze code files for issues and improvements", inputSchema: { type: "object", properties: { files: { type: "array", description: "Array of file paths to analyze", items: { type: "string", }, }, task: { type: "string", description: "Description of the analysis task", }, }, required: ["files"], }, }, { name: "modify_code", description: "Apply suggested modifications to code files", inputSchema: { type: "object", properties: { files: { type: "array", description: "Array of file paths to modify", items: { type: "string", }, }, task: { type: "string", description: "Description of the modification task", }, }, required: ["files"], }, }, { name: "execute_code", description: "Execute code in a secure sandbox", inputSchema: { type: "object", properties: { code: { type: "string", description: "Code to execute", }, language: { type: "string", description: "Programming language (python, javascript, typescript)", }, }, required: ["code", "language"], }, }, { name: "search_code", description: "Search for similar code changes", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query", }, limit: { type: "number", description: "Maximum number of results to return", }, }, required: ["query"], }, }, { name: "create_checkpoint", description: "Create a version control checkpoint", inputSchema: { type: "object", properties: { message: { type: "string", description: "Checkpoint message", }, }, required: ["message"], }, }, { name: "rollback", description: "Roll back to a previous checkpoint", inputSchema: { type: "object", properties: { commit: { type: "string", description: "Commit hash to roll back to", }, }, required: ["commit"], }, }, { name: "config", description: "Manage configuration", inputSchema: { type: "object", properties: { action: { type: "string", description: "Action to perform (get, set, list)", }, key: { type: "string", description: "Configuration key", }, value: { type: "string", description: "Configuration value (for set action)", }, }, required: ["action"], }, }, ], })); // Set up the CallTool request handler server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const toolName = request.params.name; const args = request.params.arguments; // Handle analyze_code tool if (toolName === "analyze_code") { if (!args.files || !Array.isArray(args.files) || args.files.length === 0) { throw new McpError(ErrorCode.InvalidParams, "Files array is required"); } // Convert file paths to FileToProcess objects const filesToProcess: FileToProcess[] = []; for (const file of args.files) { try { const content = await Deno.readTextFile(file); filesToProcess.push({ path: file, originalContent: content, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); await logError(`Error reading file ${file}: ${errorMessage}`); throw new McpError(ErrorCode.InternalError, `Failed to read file: ${errorMessage}`); } } // Use planAndExecute to analyze the files const results = await agent.planAndExecute( `Analyze the following files: ${args.task || ""}`, filesToProcess, ); // Convert the results to the expected format const formattedResults = results.map((result) => ({ file: result.path, issues: result.originalContent !== result.modifiedContent ? ["Changes suggested"] : [], suggestions: result.originalContent !== result.modifiedContent ? [result.modifiedContent] : [], })); return { content: [ { type: "text", text: JSON.stringify(formattedResults, null, 2), }, ], }; } // Handle modify_code tool else if (toolName === "modify_code") { if (!args.files || !Array.isArray(args.files) || args.files.length === 0) { throw new McpError(ErrorCode.InvalidParams, "Files array is required"); } // Convert file paths to FileToProcess objects const filesToProcess: FileToProcess[] = []; for (const file of args.files) { try { const content = await Deno.readTextFile(file); filesToProcess.push({ path: file, originalContent: content, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); await logError(`Error reading file ${file}: ${errorMessage}`); throw new McpError(ErrorCode.InternalError, `Failed to read file: ${errorMessage}`); } } // Use planAndExecute to modify the files const results = await agent.planAndExecute( `Modify the following files: ${args.task || ""}`, filesToProcess, ); // Write the modified content back to the files for (const result of results) { if (result.originalContent !== result.modifiedContent) { await Deno.writeTextFile(result.path, result.modifiedContent); } } // Process the results const processedResults = results.map((result) => ({ file: result.path, modified: result.originalContent !== result.modifiedContent, changes: result.originalContent !== result.modifiedContent ? ["File was modified according to suggestions"] : [], })); return { content: [ { type: "text", text: JSON.stringify(processedResults, null, 2), }, ], }; } // Handle execute_code tool else if (toolName === "execute_code") { if (!args.code) { throw new McpError(ErrorCode.InvalidParams, "Code is required"); } // Execute the code directly using the code interpreter const result = await executeCode(args.code, { language: args.language || "javascript" }); // Format the output let output = ""; if (result.logs.stdout.length > 0) { output += result.logs.stdout.join("\n"); } if (result.logs.stderr.length > 0) { output += "\n\nErrors:\n" + result.logs.stderr.join("\n"); } if (result.error) { output += "\n\nExecution Error:\n" + result.error.value; } return { content: [ { type: "text", text: output || "Execution completed successfully", }, ], }; } // Handle search_code tool else if (toolName === "search_code") { if (!args.query) { throw new McpError(ErrorCode.InvalidParams, "Search query is required"); } // Import the vector search function const { searchVectorStore } = await import("../vector/vectorStore.ts"); // Perform the search const results = await searchVectorStore(args.query, args.limit || 10); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } // Handle create_checkpoint tool else if (toolName === "create_checkpoint") { if (!args.message) { throw new McpError(ErrorCode.InvalidParams, "Checkpoint message is required"); } // Sanitize the name for Git tag const sanitizedName = args.message.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase().substring( 0, 50, ); // Create a checkpoint const hash = await createCheckpoint(sanitizedName); return { content: [ { type: "text", text: JSON.stringify( { name: sanitizedName, hash: hash, }, null, 2, ), }, ], }; } // Handle rollback tool else if (toolName === "rollback") { if (!args.commit) { throw new McpError(ErrorCode.InvalidParams, "Commit hash is required"); } // Import the rollback function const { rollbackChanges } = await import("../git/gitIntegration.ts"); // Perform the rollback const message = `Rollback to ${args.commit}`; const result = await rollbackChanges(args.commit, message); return { content: [ { type: "text", text: JSON.stringify( { commit: args.commit, success: true, result, }, null, 2, ), }, ], }; } // Handle config tool else if (toolName === "config") { if (!args.action) { throw new McpError(ErrorCode.InvalidParams, "Action is required"); } // Handle different config actions if (args.action === "get") { // Get a specific config value if (!args.key) { throw new McpError(ErrorCode.InvalidParams, "Key is required for get action"); } const configValue = Deno.env.get(args.key) || null; return { content: [ { type: "text", text: JSON.stringify({ key: args.key, value: configValue }, null, 2), }, ], }; } else if (args.action === "set") { // Set a specific config value if (!args.key) { throw new McpError(ErrorCode.InvalidParams, "Key is required for set action"); } if (args.value === undefined) { throw new McpError(ErrorCode.InvalidParams, "Value is required for set action"); } // Set the environment variable Deno.env.set(args.key, String(args.value)); return { content: [ { type: "text", text: JSON.stringify({ key: args.key, value: args.value, success: true }, null, 2), }, ], }; } else if (args.action === "list") { // List all config values (only safe ones) const safeKeys = [ "DENO_ENV", "DEBUG", "LOG_LEVEL", "VECTOR_STORE_ID", "DIFF_MODE", "PROCESSING_MODE", ]; const config: Record<string, string | null> = {}; for (const key of safeKeys) { config[key] = Deno.env.get(key) || null; } return { content: [ { type: "text", text: JSON.stringify(config, null, 2), }, ], }; } else { throw new McpError(ErrorCode.InvalidParams, `Unknown action: ${args.action}`); } } // Handle unknown tool else { throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`); } } catch (error) { if (error instanceof McpError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); await logError(`Error executing tool: ${errorMessage}`); throw new McpError(ErrorCode.InternalError, `Failed to execute tool: ${errorMessage}`); } }); // Set up error handler server.onerror = (error) => { logError(`MCP server error: ${error}`); }; // Connect to the stdio transport const transport = new StdioServerTransport(); await server.connect(transport); await logInfo("SPARC2 MCP server running on stdio"); }