/**
* MCP Core - Inlined from @mcp-ecosystem/core
*/
import { spawn } from "child_process";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type Tool,
} from "@modelcontextprotocol/sdk/types.js";
export interface MCPServerConfig {
name: string;
version: string;
description?: string;
}
export interface ToolDefinition {
name: string;
description: string;
inputSchema: Record<string, unknown>;
handler: (args: Record<string, unknown>) => Promise<ToolResult>;
}
export interface ToolResult {
content: Array<{ type: "text"; text: string }>;
isError?: boolean;
[key: string]: unknown;
}
export interface ExecResult {
stdout: string;
stderr: string;
exitCode: number;
success: boolean;
}
export interface ExecOptions {
cwd?: string;
env?: Record<string, string>;
timeout?: number;
stdin?: string;
}
/**
* Create and configure an MCP server with tools
*/
export function createMCPServer(
config: MCPServerConfig,
tools: ToolDefinition[]
): Server {
const server = new Server(
{
name: config.name,
version: config.version,
},
{
capabilities: {
tools: {},
},
}
);
// Register tool list handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: tools.map((t) => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema,
})),
}));
// Register tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const toolName = request.params.name;
const tool = tools.find((t) => t.name === toolName);
if (!tool) {
return {
content: [{ type: "text" as const, text: `Unknown tool: ${toolName}` }],
isError: true,
};
}
try {
const args = request.params.arguments ?? {};
return await tool.handler(args as Record<string, unknown>);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text" as const, text: `Error: ${message}` }],
isError: true,
};
}
});
return server;
}
/**
* Start MCP server with stdio transport
*/
export async function startMCPServer(server: Server): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`[MCP] Server started`);
}
/**
* Helper to create a successful tool result
*/
export function success(text: string): ToolResult {
return {
content: [{ type: "text", text }],
};
}
/**
* Helper to create a JSON tool result
*/
export function json(data: unknown): ToolResult {
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
/**
* Helper to create an error tool result
*/
export function error(message: string): ToolResult {
return {
content: [{ type: "text", text: message }],
isError: true,
};
}
/**
* Execute a CLI command and return the result
*/
export async function execCommand(
command: string,
args: string[],
options: ExecOptions = {}
): Promise<ExecResult> {
return new Promise((resolve) => {
const env = {
...process.env,
...options.env,
};
const proc = spawn(command, args, {
cwd: options.cwd,
env,
timeout: options.timeout ?? 60000,
});
let stdout = "";
let stderr = "";
proc.stdout.on("data", (data) => {
stdout += data.toString();
});
proc.stderr.on("data", (data) => {
stderr += data.toString();
});
if (options.stdin) {
proc.stdin.write(options.stdin);
proc.stdin.end();
}
proc.on("close", (code) => {
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode: code ?? 0,
success: code === 0,
});
});
proc.on("error", (err) => {
resolve({
stdout: "",
stderr: err.message,
exitCode: 1,
success: false,
});
});
});
}
/**
* Execute a heroku command with API key
*/
export async function execHeroku(
args: string[],
apiKey: string,
options: ExecOptions = {}
): Promise<ExecResult> {
return execCommand("heroku", args, {
...options,
env: {
...options.env,
HEROKU_API_KEY: apiKey,
},
});
}
/**
* Parse JSON output safely
*/
export function parseJsonOutput<T>(output: string): T | null {
try {
return JSON.parse(output);
} catch {
return null;
}
}
export { Server, StdioServerTransport };