feat(connectors): clean-install bundling + Google OAuth foundation
Make connectors work on a clean Mac with no Node/npm installed, and lay
the foundation for one-click Google sign-in.
Clean-install bundling:
- bundle scripts: `npm run bundle` -> self-contained dist/connectd.cjs (CJS,
no node_modules); `bundle:servers` -> pre-bundled MCP servers (fs-server.mjs
for "Your files", runs without npx); `bundle:all`.
- runtime resolution in the bridge: a connector config can use command "@node"
(the bundled runtime, process.execPath) and arg "@bundled/<server>" (a server
shipped alongside the bridge), resolved at spawn time via
NEURON_CONNECTD_BUNDLE_DIR. So remote connectors + "Your files" work offline,
zero deps.
- graceful ENOENT -> needs_setup ("coming soon") instead of a cryptic spawn
crash for not-yet-bundled npx connectors on a clean machine.
Google OAuth foundation (google-oauth.ts):
- desktop-style authorization-code + PKCE flow, refresh, Keychain storage,
gated on GOOGLE_OAUTH_CLIENT_ID (honest needs_setup until configured).
- new auth mode "google" for stdio connectors + /google/oauth/start and
/google/callback endpoints. NOTE: pending the Neuron Google OAuth client +
Google verification; not certified end-to-end yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+21
@@ -85,6 +85,27 @@ export function startServer(bridge) {
|
||||
const result = await bridge.finishOAuth(id, code);
|
||||
return sendHtml(res, result.ok ? 200 : 400, authResultPage(result.ok, result.error ?? ""));
|
||||
}
|
||||
// POST /google/oauth/start { id } — begin a Google one-click sign-in, return the consent URL.
|
||||
if (method === "POST" && url === "/google/oauth/start") {
|
||||
const parsed = await parseJsonBody(req);
|
||||
if (!parsed || !parsed.id)
|
||||
return sendJson(res, 400, { ok: false, error: "missing id" });
|
||||
const result = await bridge.startGoogleOAuth(parsed.id);
|
||||
return sendJson(res, result.ok ? 200 : 400, result);
|
||||
}
|
||||
// GET /google/callback?code=..&state=.. — Google redirects here after consent.
|
||||
if (method === "GET" && url.startsWith("/google/callback")) {
|
||||
const q = new URL(url, `http://${HOST}:${PORT}`).searchParams;
|
||||
const id = q.get("state"); // we pass the connector id as `state`
|
||||
const code = q.get("code");
|
||||
const err = q.get("error");
|
||||
if (err)
|
||||
return sendHtml(res, 400, authResultPage(false, err));
|
||||
if (!id || !code)
|
||||
return sendHtml(res, 400, authResultPage(false, "missing id or code"));
|
||||
const result = await bridge.finishGoogleOAuth(id, code);
|
||||
return sendHtml(res, result.ok ? 200 : 400, authResultPage(result.ok, result.error ?? ""));
|
||||
}
|
||||
// GET /mcp/auto-approved — the soul reads this to decide which mcp__* calls skip the
|
||||
// approval card (per-connector opt-in, off by default).
|
||||
if (method === "GET" && url === "/mcp/auto-approved") {
|
||||
|
||||
Reference in New Issue
Block a user