public MCP: the bare host answers MCP (POST /) and names the connect URL (GET /)

This commit is contained in:
CRTkafa 2026-11-08 02:26:11 +07:00
parent 9081ac921d
commit b82becf7fb
4 changed files with 28 additions and 2 deletions

View file

@ -202,7 +202,13 @@ export function startHttp(o: HttpOptions): Promise<{ close: () => void; port: nu
if (resourceMeta && o.auth.kind === 'introspect' && req.method === 'GET' && path === new URL(resourceMeta).pathname)
return send(res, 200, { resource: o.auth.resource, authorization_servers: [o.auth.issuer], bearer_methods_supported: ['header'], scopes_supported: ['mcp'], resource_name: 'storebound' });
if (path === '/health' && req.method === 'GET') return send(res, 200, { ok: true, version: pkg.version });
if (path !== '/mcp') return send(res, 404, { error: 'not found' });
// public: the bare host is the MCP endpoint too (people paste "mcp.example.com" into a connector), and a browser
// opening it gets the connect line instead of a 404
if (pub && path === '/' && req.method === 'GET') {
res.writeHead(200, { ...cors, 'content-type': 'text/plain; charset=utf-8' });
return res.end(`storebound MCP server ${pkg.version} (read-only, no key)\nAdd it as a remote MCP server / custom connector with this URL. Claude Code: claude mcp add --transport http storebound <this URL>\n`);
}
if (path !== '/mcp' && !(pub && path === '/')) return send(res, 404, { error: 'not found' });
if (pub) {
if (req.method === 'OPTIONS') return send(res, 204);
const hit = pub.limiter.hit(clientKey(req.headers['x-forwarded-for'], req.socket.remoteAddress, pub.trustProxy));