Babashka MCP Server
by bmorphism
Verified
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListToolsRequestSchema,
CallToolRequestSchema,
ErrorCode,
McpError
} from "@modelcontextprotocol/sdk/types.js";
import { exec } from "child_process";
import { promisify } from "util";
import {
BabashkaCommandArgs,
BabashkaCommandResult,
CachedCommand,
CONFIG,
isValidBabashkaCommandArgs
} from "./types.js";
const execAsync = promisify(exec);
class BabashkaServer {
private server: Server;
private recentCommands: CachedCommand[] = [];
constructor() {
this.server = new Server({
name: "babashka-mcp-server",
version: "0.1.0"
}, {
capabilities: {
resources: {},
tools: {}
}
});
this.setupHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
this.server.onerror = (error) => {
console.error("[MCP Error]", error);
};
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupHandlers(): void {
this.setupResourceHandlers();
this.setupToolHandlers();
}
private setupResourceHandlers(): void {
// List available resources (recent commands)
this.server.setRequestHandler(
ListResourcesRequestSchema,
async () => ({
resources: this.recentCommands.map((cmd, index) => ({
uri: `babashka://commands/${index}`,
name: `Recent command: ${cmd.code.slice(0, 50)}${cmd.code.length > 50 ? '...' : ''}`,
mimeType: "application/json",
description: `Command executed at ${cmd.timestamp}`
}))
})
);
// Read specific resource
this.server.setRequestHandler(
ReadResourceRequestSchema,
async (request) => {
const match = request.params.uri.match(/^babashka:\/\/commands\/(\d+)$/);
if (!match) {
throw new McpError(
ErrorCode.InvalidRequest,
`Unknown resource: ${request.params.uri}`
);
}
const index = parseInt(match[1]);
const command = this.recentCommands[index];
if (!command) {
throw new McpError(
ErrorCode.InvalidRequest,
`Command result not found: ${index}`
);
}
return {
contents: [{
uri: request.params.uri,
mimeType: "application/json",
text: JSON.stringify(command, null, 2)
}]
};
}
);
}
private setupToolHandlers(): void {
// List available tools
this.server.setRequestHandler(
ListToolsRequestSchema,
async () => ({
tools: [{
name: "execute",
description: "Execute Babashka (bb) code",
inputSchema: {
type: "object",
properties: {
code: {
type: "string",
description: "Babashka code to execute"
},
timeout: {
type: "number",
description: "Timeout in milliseconds (default: 30000)",
minimum: 1000,
maximum: 300000
}
},
required: ["code"]
}
}]
})
);
// Handle tool calls
this.server.setRequestHandler(
CallToolRequestSchema,
async (request) => {
if (request.params.name !== "execute") {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
if (!isValidBabashkaCommandArgs(request.params.arguments)) {
throw new McpError(
ErrorCode.InvalidParams,
"Invalid Babashka command arguments"
);
}
try {
const timeout = request.params.arguments.timeout || CONFIG.DEFAULT_TIMEOUT;
const { stdout, stderr } = await execAsync(
`${CONFIG.BABASHKA_PATH} -e "${request.params.arguments.code.replace(/"/g, '\\"')}"`,
{ timeout }
);
const result: BabashkaCommandResult = {
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode: 0
};
// Cache the command result
this.recentCommands.unshift({
code: request.params.arguments.code,
result,
timestamp: new Date().toISOString()
});
// Keep only recent commands
if (this.recentCommands.length > CONFIG.MAX_CACHED_COMMANDS) {
this.recentCommands.pop();
}
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
const result: BabashkaCommandResult = {
stdout: "",
stderr: error.message || "Unknown error",
exitCode: error.code || 1
};
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}],
isError: true
};
}
}
);
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Babashka MCP server running on stdio");
}
}
const server = new BabashkaServer();
server.run().catch(console.error);