Skip to main content
Glama

BYOB MCP Server

by ndisidore
index.tsβ€’6.36 kB
import { Hono } from "hono"; import { McpServer, StreamableHttpTransport } from "mcp-lite"; import { z } from "zod"; import { ToolRunner, getContainerBinding } from "./containers"; import { getContainer } from "@cloudflare/containers"; import type { Env, Tool, RegisterToolRequest } from "./types"; // Export Container class so it can be used as Durable Object export { ToolRunner }; const app = new Hono<{ Bindings: Env }>(); // ============================================================================= // Tool Registration API // ============================================================================= app.post("/api/register-tool", async (c) => { try { const body: RegisterToolRequest = await c.req.json(); // Validate required fields if (!body.name || !body.description || !body.schema || !body.containerClass) { return c.json( { error: "Missing required fields: name, description, schema, containerClass" }, 400 ); } // Since we only have one container now, container class is always "toolrunner" // But we'll keep the field for backwards compatibility const containerClass = body.containerClass?.toLowerCase() || "toolrunner"; // Insert tool into D1 const result = await c.env.DB.prepare( `INSERT INTO tools (name, description, input_schema, container_class, instance_type, entrypoint) VALUES (?, ?, ?, ?, ?, ?)` ) .bind( body.name, body.description, JSON.stringify(body.schema), containerClass, body.instanceType || "lite", body.entrypoint || null ) .run(); if (!result.success) { return c.json({ error: "Failed to register tool", details: result.error }, 500); } return c.json({ success: true, message: "Tool registered successfully", toolName: body.name, }); } catch (error: any) { console.error("Error registering tool:", error); return c.json( { error: "Failed to register tool", details: error.message }, 500 ); } }); // ============================================================================= // Tool Listing API (for debugging) // ============================================================================= app.get("/api/tools", async (c) => { try { const { results } = await c.env.DB.prepare( "SELECT * FROM tools ORDER BY created_at DESC" ).all<Tool>(); return c.json({ tools: results.map((tool) => ({ ...tool, input_schema: JSON.parse(tool.input_schema), })), }); } catch (error: any) { console.error("Error listing tools:", error); return c.json({ error: "Failed to list tools", details: error.message }, 500); } }); // ============================================================================= // MCP Endpoint with Dynamic Tool Discovery // ============================================================================= app.all("/mcp", async (c) => { const mcp = new McpServer({ name: "byob-mcp-server", version: "1.0.0", schemaAdapter: (schema) => z.toJSONSchema(schema as z.ZodType), }); // Fetch all tools from D1 const { results: tools } = await c.env.DB.prepare( "SELECT * FROM tools" ).all<Tool>(); console.log(`Loaded ${tools.length} tools from D1`); // Dynamically register each tool with the MCP server for (const tool of tools) { const inputSchema = JSON.parse(tool.input_schema); // Convert JSON Schema to Zod schema // For simplicity, we'll use z.any() and validate later // In production, you'd want proper JSON Schema -> Zod conversion const zodSchema = z.object( Object.keys(inputSchema.properties || {}).reduce((acc, key) => { acc[key] = z.any(); return acc; }, {} as Record<string, z.ZodAny>) ); mcp.tool(tool.name, { description: tool.description, inputSchema: zodSchema, handler: async (args) => { try { console.log(`Invoking tool: ${tool.name} with args:`, args); // Get the container binding (we only have one now) const containerBinding = getContainerBinding(c.env); // Get or create container instance // Use a consistent name for the container so we can reuse it const containerName = "toolrunner-instance"; const container = getContainer(containerBinding, containerName); // Call the container's /execute endpoint const response = await container.fetch("http://container/execute", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(args), }); if (!response.ok) { const errorText = await response.text(); return { content: [ { type: "text", text: `Container error: ${response.status} ${response.statusText}\n${errorText}`, }, ], isError: true, }; } const result = await response.json(); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error: any) { console.error(`Error invoking tool ${tool.name}:`, error); return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], isError: true, }; } }, }); } const transport = new StreamableHttpTransport(); const httpHandler = transport.bind(mcp); const response = await httpHandler(c.req.raw); return response; }); // ============================================================================= // Health Check // ============================================================================= app.get("/", (c) => { return c.json({ name: "BYOB MCP Server", endpoints: { mcp: "/mcp", registerTool: "POST /api/register-tool", listTools: "GET /api/tools", }, containerInfo: { single_universal_container: true, supports: ["echo", "uppercase", "jq"] } }); }); export default app;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ndisidore/cf-byob-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server