Skip to content

MCP setup#3941

Open
gilgardosh wants to merge 23 commits into
mainfrom
mcp-setup
Open

MCP setup#3941
gilgardosh wants to merge 23 commits into
mainfrom
mcp-setup

Conversation

@gilgardosh

Copy link
Copy Markdown
Collaborator

No description provided.

@gilgardosh gilgardosh self-assigned this Jul 15, 2026
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 15, 2026 15:37 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 15, 2026 15:37 — with GitHub Actions Inactive

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the specification and implementation blueprint for the Accounter MCP Connector, establishing a clear, multi-phase plan to expose read-only capabilities to Claude clients. The review feedback highlights key areas for improvement, including propagating the user's bearer token to the upstream GraphQL client, reusing existing shared auth utilities for token verification, enforcing strict size and TTL limits on the in-process cache to prevent memory exhaustion, and aligning Auth0 environment variables with the existing server configuration.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread docs/mcp/implementation-blueprint.md Outdated
Comment thread docs/mcp/implementation-blueprint.md Outdated
Comment thread docs/mcp/spec.md Outdated
Comment thread docs/mcp/spec.md Outdated
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@accounter/client 0.1.0-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/gmail-listener 0.1.3-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/green-invoice-graphql 0.8.7-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/hashavshevet-mesh 0.2.13-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/israeli-vat-scraper 0.1.13-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/modern-poalim-scraper 0.10.7-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/payper-mesh 0.2.13-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/scraper-app 0.0.3-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/server 0.2.0-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/shaam-uniform-format-generator 0.2.7-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎
@accounter/shaam6111-generator 0.1.9-alpha-20260726170304-a8f28680bb8a1caff7ea293c8dd4b42714b2ae88 npm ↗︎ unpkg ↗︎

@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 16, 2026 12:32 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 16, 2026 12:32 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 11:35 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 11:35 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:01 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:01 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:03 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:03 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:10 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:11 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:43 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:43 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:55 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 20, 2026 12:55 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:32 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:32 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:33 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:33 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:41 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:41 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:50 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 15:50 — with GitHub Actions Inactive
github-actions Bot and others added 5 commits July 26, 2026 11:47
… 06) (#3971)

* feat(mcp-server): OAuth protected resource metadata route (Prompt 06)

Serve RFC 9728 protected-resource metadata so Claude clients can discover the
Auth0 authorization server (docs/mcp/spec.md §6.2).

- src/oauth/metadata.ts: config-driven buildProtectedResourceMetadata() and
  protectedResourceMetadataUrl() helpers; well-known path constant
- GET /.well-known/oauth-protected-resource returns { resource,
  authorization_servers, bearer_methods_supported: ["header"] } as JSON, with
  resource matching MCP_PUBLIC_BASE_URL exactly
- unit tests for the document shape + route-level test through requestHandler

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* test(mcp-server): use vi.stubEnv for the metadata route test

Address review: stub env vars via vi.stubEnv/vi.unstubAllEnvs instead of
mutating process.env directly, to avoid cross-test pollution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

---------

Co-authored-by: Claude <noreply@anthropic.com>
… 07) (#3972)

* feat(mcp-server): 401 challenge helper for auth discovery (Prompt 07)

Add a standards-compliant unauthenticated response for MCP OAuth discovery
(docs/mcp/spec.md §6.2, §10.1) and gate the MCP endpoint on token presence.

- src/oauth/challenge.ts: buildWwwAuthenticateHeader() and sendUnauthorized()
  emitting 401 + WWW-Authenticate: Bearer with a resource_metadata pointer to
  the well-known document (transport-level, never a JSON-RPC/tool error)
- handler.ts: hasBearerToken() presence check; POST /mcp without a bearer
  token now returns the 401 challenge (token validity verified in Prompt 08)
- tests for the header builder, sendUnauthorized, the presence check, and the
  401 path through mcpHttpHandler

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): RFC-compliant WWW-Authenticate header

Address review:
- only emit error_description alongside error (RFC 6750 §3)
- escape " and \ in quoted-string parameter values (RFC 7235)
- use vi.stubEnv in the 401 handler test instead of mutating process.env

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

---------

Co-authored-by: Claude <noreply@anthropic.com>
…rompt 08) (#3973)

* feat(mcp-server): bearer token extraction and Auth0 verification (Prompt 08)

Verify Auth0 access tokens on the MCP endpoint (docs/mcp/spec.md §6.4, §8/§10.1),
reusing the server package's jose-based JWKS approach.

- src/auth/token.ts: extractBearerToken(); verifyAccessTokenWithKey() enforcing
  issuer/audience/signature/expiry and mapping claims to a typed AuthPrincipal
  (subject, scopes from scope+permissions, email); TokenVerificationError
  carries no token material; per-request principal store
- src/auth/verifier.ts: env-backed verifyAccessToken() using a cached remote
  JWKS (createRemoteJWKSet) keyed by JWKS URL
- handler.ts: POST /mcp now extracts + verifies the token; invalid/expired ->
  401 with error="invalid_token"; missing -> 401 challenge; failures logged by
  reason only (never the token)
- add jose dependency; unit tests for extraction, claim mapping, and
  valid/wrong-issuer/wrong-audience/expired/wrong-key verification

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): don't mask JWKS/infra failures as 401; tidy principal types

Address review:
- verifyAccessTokenWithKey only wraps genuine token-validation errors (by jose
  error code) into TokenVerificationError; infrastructure failures (e.g. a JWKS
  fetch timeout) propagate so the request surfaces as a 5xx instead of a
  misleading 401
- handler.authenticate rethrows non-TokenVerificationError so those become 500
- use the AuthPrincipal type directly in the import and authenticate() return
  type instead of Awaited<ReturnType<...>>
- tests: infra error propagation at both the verifier and handler layers

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* prettier

---------

Co-authored-by: Claude <noreply@anthropic.com>
…a) (#4050)

* feat(server): expose caller's own business memberships (MCP Prompt 08a)

Add a `myMemberships` GraphQL query to the auth module that returns the
authenticated caller's own business memberships. Gated with `@requiresAuth`
only (never `@requiresRole`, never dependent on a selected business scope) so
the MCP connector can read a user's businesses before any scope is chosen.

The resolver reuses the memberships already resolved on the request auth
context (`AuthContextProvider.getAuthContext()`) — no new DB query. An
authenticated user with no memberships returns an empty list, not an error.

The `MyBusinessMembership` type is prefixed to avoid colliding with the
existing `common` module `BusinessMembership` type, and uses `roleId` to map
cleanly onto the MCP connector's internal membership shape (Prompt 08b).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(server): role-gate myMemberships and give MyBusinessMembership an id

- Gate `myMemberships` with `@requiresAnyRole(["business_owner", "accountant"])`
  instead of leaving it authenticated-only.
- Add a required `id: ID!` to `MyBusinessMembership` (composite of the caller's
  user id and the business id) and drop the strict-id-in-types eslint workaround.
- Populate the composite `id` in the resolver and update the doc comment.
- Strengthen the error-wrapping test to assert the wrapper's message,
  `extensions.code`, and original error, and add a test that GraphQLErrors from
  the auth context are rethrown unchanged (addresses PR review comment).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dkv1EjQ1ygKUFrmEWUhS5p

---------

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 15:20 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 15:20 — with GitHub Actions Inactive
github-actions Bot and others added 2 commits July 26, 2026 15:22
…aims (#4046)

* docs(mcp): resolve business memberships from the server, not token claims

An Auth0 access token carries identity only (sub/email); it does not contain a
user's Accounter business memberships or roles. Those live in the server DB
(business_users, keyed by auth0_user_id) and are resolved per request. The
blueprint/spec previously assumed memberships arrive as a token claim, which
would resolve to an empty scope for every real caller.

Correct the docs to describe resolving memberships from the Accounter server by
forwarding the caller's bearer token and reading a new `myMemberships` query —
much simpler than email-ingestion-gateway's control-plane token, since the MCP
holds a real end-user token.

- spec.md: rewrite §6.4 (token = identity only), add the resolution mechanism to
  §7.1, clarify §7.3 narrows server-resolved memberships, and fix the §12.2
  phase step wording.
- implementation-blueprint.md: insert prerequisite Prompt 08a (server
  `myMemberships` query) and Prompt 08b (MCP upstream membership source) between
  prompts 08 and 09 (lettered to avoid renumbering already-branched prompts);
  reframe Prompt 09 to consume the upstream source; update the workstream/slice/
  step-list references and the progress note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* docs(mcp): address review comments on membership resolution spec (#4052)

- spec.md §6.4: document email as an optional claim (sub plus optional
  email/email_verified) to match the server's JWT parsing, which coerces
  a missing email to null
- blueprint Prompt 08b: describe token forwarding as a requirement of the
  new MembershipSource rather than existing behavior (the handler makes no
  upstream calls yet)
- blueprint Prompt 08b: introduce the upstream GraphQL client + helpers
  (graphql-client.ts, getUpstreamClient, coerceMembership, resolveAuthContext)
  as part of the prompt instead of assuming they already exist


Claude-Session: https://claude.ai/code/session_017MqCVWqb56h8vjw8uyZ4rD

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:03 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:03 — with GitHub Actions Inactive
…4010)

* feat(mcp-server): map verified identity to business membership context (Prompt 09)

Build an internal auth context from a verified token, mirroring the server's
tenant-isolation model (docs/mcp/spec.md §7).

- src/auth/identity.ts: McpAuthContext (userId, roles, memberships,
  defaultReadScope); readScopeFromMemberships + narrowReadScope +
  resolveRequestedReadScope (subset-only narrowing, reject out-of-scope);
  pluggable MembershipSource defaulting to a `memberships` token claim;
  IdentityMappingError for an unresolvable user; per-request context store
- handler: resolve and store the auth context after token verification
- unit tests for multi-business mapping, default/narrowed scope, claim parsing,
  and the missing-subject failure mode

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): harden membership claim coercion

Address review: exclude arrays from object membership entries and only accept a
string/number roleId (never stringify arbitrary objects/arrays).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): reject malformed membership roles; 401 on identity mapping failure

Address PR review feedback on Prompt 09:

- coerceMembership now rejects a membership entry whose role is present but of a
  non-primitive type (object/array/boolean) instead of coercing it to '' — a
  malformed role could otherwise still contribute its businessId to the derived
  read scope. A missing role is still allowed (empty role).
- The MCP handler now treats IdentityMappingError (e.g. a verified token with no
  subject) as an invalid_token 401 rather than letting it surface as a 5xx;
  genuine infrastructure errors still propagate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

---------

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:23 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:23 — with GitHub Actions Inactive
…t 10) (#4011)

* feat(mcp-server): curated tool registry and input validation (Prompt 10)

Add the production-ready tool registry abstraction (docs/mcp/spec.md §8.3).

- src/tools/registry.ts: ToolDefinition (name, description, strict zod input
  schema, auth policy, pure handler); ToolRegistry with register/get/has/list
  and duplicate-name rejection; describe() renders input schemas to JSON Schema
  (additionalProperties:false) for tools/list
- validateToolInput(): strict parsing with unknown-field rejection and a
  deterministic VALIDATION_ERROR payload (code, message, field-path issues)
- ToolAuthPolicy / DataClassification / ToolExecutionContext contracts for the
  policy evaluator and handlers in later steps
- unit tests for registration, duplicates, ordering, describe(), and schema
  validation (valid, missing required, unknown field, type mismatch)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): honor MCP kill-switch; harden tools/call params & schema strictness

Address PR review feedback on Prompt 10:

- MCP_ENABLED=0 now actually disables the connector: the MCP transport (`/mcp`)
  and its OAuth protected-resource-metadata discovery route return 404 (health
  only), instead of being served while the logs claim "disabled mode".
- `tools/call` returns a clear InvalidParams ("params must be an object") when
  `params` is an array or other non-object, instead of a misleading
  "Unknown tool: undefined".
- `registry.describe()` renders JSON Schema from the same `.strict()` schema that
  `validateToolInput` enforces, so the advertised `additionalProperties: false`
  provably matches the runtime unknown-field rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

---------

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:36 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:36 — with GitHub Actions Inactive
…#4012)

Enforce required roles and business-scope constraints before tool execution
(docs/mcp/spec.md §7).

- src/tools/policy.ts: evaluateToolPolicy() checks required roles (any-of) and
  resolves the request's read scope as a subset of the caller's memberships,
  denying out-of-scope requests and empty scope (when required) with a
  deterministic AUTHORIZATION_ERROR; authorizeToolCall() convenience for a
  registered tool
- unit tests for allow, role deny, out-of-scope deny, empty-scope deny, subset
  narrowing, any-of roles, and no-role-gate


Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:43 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:43 — with GitHub Actions Inactive
…4013)

* feat(mcp-server): hardened upstream GraphQL client (Prompt 12)

Add the shared read-only upstream GraphQL client used by tool handlers
(docs/mcp/spec.md §5.3, §10.3, §10.4).

- src/upstream/graphql-client.ts: UpstreamGraphQLClient with strict timeout +
  cancellation (AbortController), bounded retries for idempotent read failures
  only (network/timeout/5xx; never 4xx or GraphQL errors), correlation-id and
  Authorization header propagation (token never logged), and sanitized
  UpstreamError (UPSTREAM_ERROR/TIMEOUT_ERROR, no internal details)
- read-only guard refuses mutations/subscriptions; createReadOperation exposes
  only typed read wrappers to tools (no generic execute surface)
- src/upstream/default-client.ts: env-backed, memoized client factory
- unit tests for success/header propagation, read-only guard, timeout, retry
  eligibility (503 retried, 400/401 not, exhaustion), and error sanitization

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): harden upstream client against malformed input

Address review:
- read-only guard rejects mutation/subscription anywhere in the document (not
  just leading), preventing a multi-operation + operationName bypass
- sanitizeGraphQLErrors tolerates null/non-object entries in the errors array
- wrap response.json() and guard the body shape so a non-JSON/invalid response
  becomes a sanitized UpstreamError instead of a raw SyntaxError/TypeError

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

* fix(mcp-server): cap upstream error strings; extend timeout over body read

Address PR review feedback on Prompt 12:

- sanitizeGraphQLErrors now normalizes whitespace and caps each message (200
  chars) and the combined string (500 chars), so a verbose/multi-line upstream
  error can neither bloat logs nor smuggle internal detail into the summary.
- The request timeout budget now spans the entire exchange — obtaining the
  response AND consuming its body — instead of being cleared right after headers
  arrive. An abort raised while reading the body maps to a retryable
  TIMEOUT_ERROR rather than being misclassified as a non-JSON response.
- Clarify the UpstreamError doc: its message carries no upstream internals; the
  local JS stack is our own call site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo

---------

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:48 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 16:48 — with GitHub Actions Inactive
#4051)

* feat(mcp-server): resolve memberships from the server (MCP Prompt 08b)

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

* fix(mcp-server): drop unused businessName; guard non-object upstream 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

---------

Co-authored-by: Claude <noreply@anthropic.com>
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 17:01 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 26, 2026 17:01 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant