@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
JavaScript
// 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