UNPKG

@apideck/docs-mcp

Version:

MCP server that lets agents search and retrieve your documentation at query time instead of relying on stale training data.

96 lines • 3.78 kB
// SPDX-License-Identifier: MIT import { NodeStreamableHTTPServerTransport } from './node-http.js'; import { createServer } from './server.js'; const CORS_HEADERS = [ ['Access-Control-Allow-Origin', '*'], ['Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'], ['Access-Control-Allow-Headers', 'Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Authorization, X-MCP-Client'], ['Access-Control-Expose-Headers', 'Mcp-Session-Id'], ]; const json = (res, status, body) => { res.statusCode = status; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(body)); }; const header = (req, name) => { const v = req.headers[name]; return Array.isArray(v) ? v[0] : v; }; const callerOf = (req) => { const client = header(req, 'x-mcp-client'); const userAgent = header(req, 'user-agent'); return { ...(client !== undefined ? { client } : {}), ...(userAgent !== undefined ? { userAgent } : {}), }; }; export const createHttpHandler = (opts) => { const getStore = async () => { if (typeof opts.store === 'function') return opts.store(); await opts.store.load(); return opts.store; }; const getExtraTools = async () => { if (!opts.extraTools) return []; return typeof opts.extraTools === 'function' ? opts.extraTools() : opts.extraTools; }; return async (req, res) => { if (opts.cors !== false) for (const [k, v] of CORS_HEADERS) res.setHeader(k, v); const method = req.method ?? 'GET'; if (method === 'OPTIONS') { res.statusCode = 204; res.end(); return; } let store; try { store = await getStore(); } catch (err) { json(res, 500, { error: `Failed to index docs: ${String(err)}` }); return; } if (method === 'GET') { const stats = store.stats(); const extraTools = await getExtraTools(); json(res, 200, { name: opts.name ?? 'docs-mcp', description: opts.about ?? 'Read-only MCP server over a documentation set.', transport: 'streamable-http', tools: ['search_docs', 'get_doc', 'list_docs', ...extraTools.map((t) => t.name)], pages: stats.pages, sections: stats.sections, indexed_at: stats.indexedAt?.toISOString() ?? null, version: opts.version ?? process.env['npm_package_version'] ?? '0.0.0', ...opts.info, }); return; } if (method !== 'POST') { json(res, 405, { error: 'Method not allowed' }); return; } const extraTools = await getExtraTools(); const server = createServer({ store, ...(opts.name !== undefined ? { name: opts.name } : {}), ...(opts.version !== undefined ? { version: opts.version } : {}), ...(opts.about !== undefined ? { about: opts.about } : {}), ...(opts.instructions !== undefined ? { instructions: opts.instructions } : {}), ...(extraTools.length > 0 ? { extraTools } : {}), ...(opts.onToolCall !== undefined ? { onToolCall: opts.onToolCall, caller: callerOf(req) } : {}), }); const transport = (opts.transportFactory ?? (() => new NodeStreamableHTTPServerTransport({})))(); res.on('close', () => { void transport.close(); void server.close(); }); await server.connect(transport); await transport.handleRequest(req, res, req.body); }; }; //# sourceMappingURL=http.js.map