fdf8fb5cda
The sidecar that isolates all MCP wire complexity from the soul. Binds
loopback 127.0.0.1:7771 only. The soul reaches it over flat HTTP; the bridge
owns stdio/streamable-HTTP transports, OAuth (PKCE), Keychain secrets, server
lifecycle, config, and a tool-schema-hash poisoning guard.
HTTP contract: GET /mcp/tools, /mcp/servers, /mcp/auto-approved, /healthz;
POST /mcp/call, /mcp/oauth/start, /mcp/servers/{add,toggle,auto-approve,
remove,secret}; GET /mcp/oauth/callback.
Config: ~/.neuron/connectors.json (servers, no secrets). Secrets in macOS
Keychain (service ai.neuron.connect, account = serverId). Spec:
docs/research/mcp-connectors-adoption-spec.md.
Phases 1-3 verified end to end (stdio + HTTP transport, Keychain token auth,
OAuth round-trip); Phase 4/5 (CRUD + auto-approve + schema-hash) added for the
ConnectorsView UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
1.9 KiB
JavaScript
48 lines
1.9 KiB
JavaScript
// A minimal remote MCP server over Streamable HTTP that REQUIRES a bearer token.
|
|
// Used to verify neuron-connectd's http transport + Keychain-backed auth end to
|
|
// end, with no external dependency. Wrong/missing token → 401.
|
|
//
|
|
// TEST_MCP_TOKEN=secret-123 TEST_MCP_PORT=7795 node test/auth-http-mcp-server.mjs
|
|
import http from "node:http";
|
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
|
|
const TOKEN = process.env.TEST_MCP_TOKEN || "secret-123";
|
|
const PORT = Number(process.env.TEST_MCP_PORT || 7795);
|
|
|
|
function buildServer() {
|
|
const server = new McpServer({ name: "auth-test", version: "0.1.0" });
|
|
server.registerTool(
|
|
"whoami",
|
|
{ description: "Return the authenticated identity.", inputSchema: {} },
|
|
async () => ({ content: [{ type: "text", text: "authenticated-ok: bearer token accepted" }] })
|
|
);
|
|
return server;
|
|
}
|
|
|
|
http
|
|
.createServer(async (req, res) => {
|
|
// Bearer auth gate — the whole point of the test.
|
|
const auth = req.headers["authorization"] || "";
|
|
if (auth !== `Bearer ${TOKEN}`) {
|
|
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" });
|
|
res.end(JSON.stringify({ error: "unauthorized" }));
|
|
return;
|
|
}
|
|
let body = "";
|
|
for await (const chunk of req) body += chunk;
|
|
const parsed = body ? JSON.parse(body) : undefined;
|
|
|
|
const server = buildServer();
|
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
res.on("close", () => {
|
|
transport.close();
|
|
server.close();
|
|
});
|
|
await server.connect(transport);
|
|
await transport.handleRequest(req, res, parsed);
|
|
})
|
|
.listen(PORT, "127.0.0.1", () => {
|
|
console.error(`[auth-test-mcp] listening on http://127.0.0.1:${PORT} (token gated)`);
|
|
});
|