@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 a
500 lines (458 loc) • 15.2 kB
text/typescript
/**
* 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");
}