mcp door: mint workspace-owner service session for team runtime bearer
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 10s
ci / frontend (push) Successful in 25s
ci / e2e (push) Skipped
ci / publish (push) Skipped

The runtime template's static clawmates_door bearer is rejected by
cm_auth::authenticate() (needs an auth_sessions row). Every per-team
agent was getting `unauthorized: missing or invalid bearer token` and
`0 tool(s) registered from 0 server(s)`.

Add AuthService::mint_service_session + users::owner_of_workspace and
mint a 30d service session in try_team_gateway_url; inject it into the
freshly-spawned team container's config.toml [[mcp.servers]] clawmates
Authorization header via prewrite_daemon_config_with_risk (bearer arg).

Follow-up: apply the same pattern to research::spawn (per-topic) and
per-loop spawn paths.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-18 13:37:27 -07:00
co-authored by Claude Opus 4.7
parent 8cd0c7bd01
commit 49f94a5360
4 changed files with 124 additions and 9 deletions
+33
View File
@@ -676,12 +676,26 @@ async fn try_team_gateway_url(
})
.ok()?;
let state_root = crate::research_container::team_state_root(team_id);
// Mint a workspace-owner service session so the team runtime's
// clawmates_door MCP calls pass cm-auth (the static bearer baked
// into the template config isn't a valid auth_sessions row and
// gets 401'd, leaving every agent with 0 tools).
let mcp_bearer = mint_workspace_service_token(pool, workspace_id)
.await
.map_err(|e| {
eprintln!("try_team_gateway_url: mint MCP bearer failed for team {team_id}: {e}");
e
})
.ok();
let spawned = crate::research_container::spawn_team(
&docker,
team_id,
std::path::Path::new(&repo_path),
&state_root,
risk_profile.as_deref(),
mcp_bearer.as_deref(),
)
.await
.map_err(|e| {
@@ -705,3 +719,22 @@ async fn try_team_gateway_url(
Some(spawned.gateway_url)
}
/// Mint a long-lived session token for the workspace owner. Used to
/// authorize internal service callers (e.g. per-team ZeroClaw runtimes
/// hitting our clawmates_door MCP endpoint) without threading a real
/// user session through the runtime template.
async fn mint_workspace_service_token(
pool: &PgPool,
workspace_id: WorkspaceId,
) -> Result<String, String> {
let owner = cm_db::repo::users::owner_of_workspace(pool, workspace_id)
.await
.map_err(|e| format!("owner_of_workspace: {e}"))?;
let auth = cm_auth::AuthService::new(pool.clone());
let token = auth
.mint_service_session(owner, time::Duration::days(30))
.await
.map_err(|e| format!("mint_service_session: {e}"))?;
Ok(token.secret().to_string())
}