feat(mcp-server): resolve memberships from the server (MCP Prompt 08b)#4051
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
⚠️ Not ready to approve
The new upstream membership mapper can throw an unsanitized TypeError if the upstream returns data: null, bypassing the intended UpstreamError semantics.
Pull request overview
Adds an upstream-backed membership resolution path to the MCP server so business memberships are resolved from the Accounter GraphQL server (myMemberships) via bearer-token pass-through, rather than derived from token claims, and wires that source into request authentication.
Changes:
- Introduces an upstream
MembershipSource(createUpstreamMembershipSource) that callsmyMembershipsand maps rows via the sharedcoerceMembership. - Updates MCP auth flow to use the upstream membership source during
authenticate(forwardingAuthorization+ correlation id). - Adds unit tests for the upstream membership source and stubs upstream access in existing MCP transport tests to keep them hermetic.
File summaries
| File | Description |
|---|---|
| packages/mcp-server/src/upstream/memberships.ts | New upstream membership source + myMemberships read operation wrapper and mapping logic. |
| packages/mcp-server/src/upstream/tests/memberships.test.ts | New unit coverage for mapping, forwarding context, empty/malformed payloads, and error propagation. |
| packages/mcp-server/src/mcp/handler.ts | Auth now builds and passes an upstream membership source to resolveAuthContext. |
| packages/mcp-server/src/mcp/tests/handler.test.ts | Mocks upstream membership source/client accessor to avoid network/env coupling in transport tests. |
| packages/mcp-server/src/auth/identity.ts | Exports coerceMembership for reuse by the upstream membership source. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Low
Note
Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2806ce8 to
f4dc13c
Compare
f12202f to
2dfb62b
Compare
f4dc13c to
1068e23
Compare
Resolve the caller's business memberships by forwarding their bearer token to the Accounter server's `myMemberships` query, and make that the wired membership source — the connector no longer derives memberships from token claims (spec §6.4, §7.1). - Add an upstream `MembershipSource` (`createUpstreamMembershipSource`) that issues `myMemberships` via the existing `createReadOperation` + `UpstreamGraphQLClient`, threading the caller's Authorization header and correlation id, and maps rows through the shared `coerceMembership`. - Wire it into `resolveAuthContext` in the MCP handler, replacing the default claims source. The `MembershipSource` seam stays pluggable and `membershipsFromClaims` remains exported for tests. - Error semantics: any upstream/auth/infra failure throws (surfaces as 401/5xx) so a failed lookup never degrades into a silent empty scope; a genuinely empty server result maps to []. A non-list payload is treated as an upstream contract violation. The token is never logged. Precheck note: the forwarded token authenticates to the server only if the server accepts the MCP's Auth0 audience. If the audiences differ, a token-acquisition step (shared audience or Auth0 token-exchange) is needed before this is enabled in a live environment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo
…data (08b)
Address PR review feedback on Prompt 08b:
- The myMemberships query no longer selects businessName: the internal
BusinessMembership shape keeps only { businessId, roleId }, so the field was
fetched and immediately dropped.
- Guard against a non-object `data` (e.g. a misbehaving upstream returning
`data: null`, which the client passes through since only `undefined` is "no
data") before reading `data.myMemberships`, raising a sanitized UpstreamError
instead of a raw TypeError.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo
2dfb62b to
e1791c5
Compare
Summary
Implements Prompt 08b — MCP: resolve memberships from the server
(see
docs/mcp/implementation-blueprint.mdanddocs/mcp/spec.md§6.4, §7.1).The connector never derives business memberships from token claims. It resolves the caller's businesses by forwarding the user's bearer token to the Accounter GraphQL server and reading them back from the dedicated
myMembershipsquery (added server-side in Prompt 08a, #4050). Because the connector holds a real end-user token, "authenticating to the server" is plain token pass-through — no Auth0 custom claim and no shared service secret.Changes
upstream/memberships.ts(new) — an upstreamMembershipSource:MY_MEMBERSHIPS_QUERY+ a typed, read-onlycreateReadOperationwrapper that mapsmyMembershipsrows to the internalBusinessMembershipshape via the sharedcoerceMembership.createUpstreamMembershipSource({ client, authorization, correlationId })returns aMembershipSourcethat forwards the caller'sAuthorizationheader and correlation id. Identity is carried by the forwarded token, so the principal isn't consulted.[]. A non-list payload violates the server's non-null[MyBusinessMembership!]!contract and is treated as an upstream error.auth/identity.ts— exportcoerceMembershipso the upstream source maps rows the same way the claims source does. TheMembershipSourceseam stays pluggable andmembershipsFromClaimsremains exported for tests.mcp/handler.ts—authenticatenow builds the upstream source (getUpstreamClient()+ the request'sAuthorizationheader + correlation id) and passes it toresolveAuthContext, replacing the default claims source. The token is never logged. Scoped the route's header comment now that the auth path makes one upstream call.upstream/__tests__/memberships.test.ts(new) — success maps rows to the internal shape (droppingbusinessName); forwards auth header + correlation id and issuesmyMemberships; empty result ⇒[]; malformed rows dropped; non-list ⇒ throws; upstream failure propagates.mcp/__tests__/handler.test.ts— stub the membership source + upstream client accessor so the existing transport tests stay hermetic (no network / no upstream env required).Precheck (per the prompt)
The forwarded token authenticates to the server only if the server accepts the MCP's Auth0 audience. This can't be exercised in CI (no live Auth0). Before enabling in a live environment, verify a real user token against an existing
@requiresAuthserver query; if the audiences differ, add a token-acquisition step (shared audience or Auth0 token-exchange) before wiring. Called out in the source docs.Validation
yarn workspace @accounter/mcp-server build(tsc) — passes.vitest --project unit packages/mcp-server— 150/150 pass.eslintclean on the changed source files;prettierformatted.🤖 Generated with Claude Code
Generated by Claude Code