hanahmily commented on code in PR #1010: URL: https://github.com/apache/skywalking-banyandb/pull/1010#discussion_r2974401794
########## docs/operation/mcp/setup.md: ########## @@ -131,6 +127,89 @@ curl http://localhost:17913/api/healthz grpcurl -plaintext localhost:17912 list ``` +## HTTP Transport Mode + +By default the MCP server communicates over standard I/O (`TRANSPORT=stdio`), which is suitable for desktop clients such as Claude Desktop. Set `TRANSPORT=http` to expose the server as an HTTP endpoint instead. This mode uses the MCP [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) transport, which supports both streaming (SSE) and direct JSON responses. + +### Start in HTTP Mode + +```bash +# Default port 3000 +TRANSPORT=http BANYANDB_ADDRESS=localhost:17900 node dist/index.js + +# Custom port +TRANSPORT=http MCP_PORT=8080 BANYANDB_ADDRESS=localhost:17900 node dist/index.js +``` + +The server prints the listening address on startup: + +``` +BanyanDB MCP HTTP server listening on :3000/mcp +``` + +The single endpoint is `POST /mcp` (also handles `GET /mcp` for server-to-client notifications). All other paths return 404. Review Comment: The HTTP handler only processes requests after fully reading the body via `req.on('end', ...)`. There's no explicit GET handling. If the transport doesn't support GET, this documentation claim is incorrect. ########## mcp/src/index.ts: ########## @@ -33,11 +38,86 @@ dotenv.config(); setupGlobalErrorHandlers(); const BANYANDB_ADDRESS = process.env.BANYANDB_ADDRESS || 'localhost:17900'; -const LLM_API_KEY = process.env.LLM_API_KEY; -const LLM_BASE_URL = process.env.LLM_BASE_URL; +const TRANSPORT = process.env.TRANSPORT || 'stdio'; +const mcpPortRaw = parseInt(process.env.MCP_PORT || '3000', 10); +if (!Number.isFinite(mcpPortRaw) || mcpPortRaw <= 0 || mcpPortRaw > 65535) { + log.error(`Invalid MCP_PORT value "${process.env.MCP_PORT}": must be an integer between 1 and 65535. Defaulting to 3000.`); Review Comment: Port validation logs error but doesn't exit. This could mask configuration errors ########## mcp/src/index.ts: ########## @@ -33,11 +38,86 @@ dotenv.config(); setupGlobalErrorHandlers(); const BANYANDB_ADDRESS = process.env.BANYANDB_ADDRESS || 'localhost:17900'; -const LLM_API_KEY = process.env.LLM_API_KEY; -const LLM_BASE_URL = process.env.LLM_BASE_URL; +const TRANSPORT = process.env.TRANSPORT || 'stdio'; +const mcpPortRaw = parseInt(process.env.MCP_PORT || '3000', 10); +if (!Number.isFinite(mcpPortRaw) || mcpPortRaw <= 0 || mcpPortRaw > 65535) { + log.error(`Invalid MCP_PORT value "${process.env.MCP_PORT}": must be an integer between 1 and 65535. Defaulting to 3000.`); +} +const MCP_PORT = Number.isFinite(mcpPortRaw) && mcpPortRaw > 0 && mcpPortRaw <= 65535 ? mcpPortRaw : 3000; + +// Prompt schema for generate_BydbQL — used with registerPrompt which requires a type cast +// due to MCP SDK 1.x Zod v3/v4 compatibility layer causing TS2589 deep type instantiation. +const generateBydbQLPromptSchema = { + description: z.string().describe( + "Natural language description of the query (e.g., 'list the last 30 minutes service_cpm_minute', 'show the last 30 zipkin spans order by time')", + ), + resource_type: z.enum(['stream', 'measure', 'trace', 'property']).optional().describe('Optional resource type hint: stream, measure, trace, or property'), + resource_name: z.string().optional().describe('Optional resource name hint (stream/measure/trace/property name)'), + group: z.string().optional().describe('Optional group hint, for example the properties group'), +} as const; + +type QueryHints = { + description?: string; + BydbQL?: string; + resource_type?: string; + resource_name?: string; + group?: string; +}; + +function normalizeQueryHints(args: unknown): QueryHints { + if (!args || typeof args !== 'object') { + return {}; + } -async function main() { - // Create MCP server + const rawArgs = args as Record<string, unknown>; + return { + description: typeof rawArgs.description === 'string' ? rawArgs.description.trim() : undefined, + BydbQL: typeof rawArgs.BydbQL === 'string' ? rawArgs.BydbQL.trim() : typeof rawArgs.bydbql === 'string' ? rawArgs.bydbql.trim() : undefined, Review Comment: Why do you use two different casings? Could you choose one? It looks like lowercase matches the parameter's convention. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
