diff --git a/forge-core/mcp/platform_token.go b/forge-core/mcp/platform_token.go index c787715..ec7990f 100644 --- a/forge-core/mcp/platform_token.go +++ b/forge-core/mcp/platform_token.go @@ -98,6 +98,19 @@ func (p *platformTokenSource) fetch(ctx context.Context) (string, time.Duration, req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+identity) + // Tenancy headers, exactly as the admission + remote-session-store + // clients send them: the platform verifies a PER-ORG HS256 token, so it + // needs Org-Id to select the signing secret BEFORE it can validate the + // bearer (without this the endpoint 401s "missing org-id header"), and + // Workspace-Id to authorize the request against the entry (entitlement). + // Read from the standard FORGE_ORG_ID / FORGE_WORKSPACE_ID env the + // platform always injects; omitted when empty (standalone/dev). + if org := os.Getenv("FORGE_ORG_ID"); org != "" { + req.Header.Set("Org-Id", org) + } + if ws := os.Getenv("FORGE_WORKSPACE_ID"); ws != "" { + req.Header.Set("Workspace-Id", ws) + } client := p.client if client == nil { diff --git a/forge-core/mcp/platform_token_test.go b/forge-core/mcp/platform_token_test.go index 8d15f32..bdd73e6 100644 --- a/forge-core/mcp/platform_token_test.go +++ b/forge-core/mcp/platform_token_test.go @@ -16,9 +16,19 @@ import ( // server ref, and can emit a (forbidden) refresh_token. func newFakeResolver(t *testing.T, expiresIn int, includeRefresh bool) (*httptest.Server, *int) { t.Helper() + t.Setenv("FORGE_ORG_ID", "org-42") + t.Setenv("FORGE_WORKSPACE_ID", "ws-7") hits := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits++ + // Tenancy headers must ride along so the platform can select the + // per-org HS256 secret + authorize the workspace (the "missing + // org-id header" 401 fix). + if r.Header.Get("Org-Id") != "org-42" || r.Header.Get("Workspace-Id") != "ws-7" { + w.WriteHeader(http.StatusUnauthorized) + _ = json.NewEncoder(w).Encode(map[string]string{"error": "missing org-id header"}) + return + } if r.Header.Get("Authorization") != "Bearer agent-cred-1" { w.WriteHeader(http.StatusUnauthorized) return