feat(mcp): OAuth per-user connectors (§15) — providers, PKCE copy-paste flow, env credential delivery

- oauth_providers registry table (per-provider client creds) + db/oauth_providers.rs
- mcp/oauth.rs: authorization-code + PKCE S256, RAM-only TTL'd flow store, copy-paste consent
- mcp/install.rs + verify.rs: connector file install + manifest verification
- activate persists a pending row (needs_oauth); /mcp/oauth/start + /complete exchange code for refresh token
- credential delivery via env var on docker exec (google_authorized_user JSON), never on disk
- mcp_catalog/mcp_user_servers: additive OAuth columns (ensure_column), catalog_name/oauth_provider/deliver_json bare TEXT snapshots
- frontend: connector-detail.js (OAuth login panel), shared/connector-common.js, connectors.js admin Sign-in providers modal
- API: /mcp/providers (admin OAuth creds), /mcp/oauth/start|complete
- .gitignore: add /homes/ (instance data), /connectors/, /reset.sh; drop stale /secrets/
This commit is contained in:
2026-07-17 21:47:51 +01:00
parent bcd8f7b5c0
commit e6c4e202a4
28 changed files with 3349 additions and 553 deletions
+10
View File
@@ -139,14 +139,24 @@ pub fn router() -> Router<Arc<Skald>> {
// admin: catalog + globally-active connectors
.route("/mcp/catalog", get(mcp::catalog_list).post(mcp::catalog_upsert))
.route("/mcp/catalog/{id}", delete(mcp::catalog_delete))
// The icon of an installed connector, off the local `connectors/` folder.
// Any logged-in user, not just a catalog manager — see `catalog_icon`.
.route("/mcp/catalog/{name}/icon", get(mcp::catalog_icon))
.route("/mcp/global", get(mcp::global_list).post(mcp::global_enable))
.route("/mcp/global/{id}", delete(mcp::global_delete))
.route("/mcp/global/{id}/access", get(mcp::global_get_access).put(mcp::global_set_access))
// admin: OAuth providers (client credentials for per-user sign-in, §15)
.route("/mcp/providers", get(mcp::providers_list).post(mcp::providers_upsert))
.route("/mcp/providers/{name}", delete(mcp::providers_delete))
// user: available catalog + per-user activation
.route("/mcp/available", get(mcp::available))
.route("/mcp/activate", post(mcp::activate))
.route("/mcp/test", post(mcp::test))
.route("/mcp/activated", get(mcp::activated_list))
.route("/mcp/activated/{id}", delete(mcp::deactivate))
// user: interactive OAuth login for a pending per-user connector (§15)
.route("/mcp/oauth/start", post(mcp::oauth_start))
.route("/mcp/oauth/complete", post(mcp::oauth_complete))
// Dev / debug
.route("/dev/debug_mode", get(dev::get_debug_mode).post(dev::set_debug_mode).put(dev::set_debug_mode))
.route("/dev/llm-requests", get(dev::list_llm_requests))