PostHog / PostHog/posthog

MCP: switch-organization commits unvalidated state and reports success on a failed org fetch

Open Beginner friendly
#78,629 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug feature/mcp team/self-driving
Dominant language
Python
Stars
39.9k
Forks
3.4k
Avg merge
6h 51m
Merged PRs (30d)
232

Description

Summary

switch-organization writes orgId to the session cache before (and independently of) fetching the organization, and returns a success string whether or not that fetch succeeded. switch-project had the same shape, was identified as a bug, and was fixed — the fix was not applied to the organization tool.

services/mcp/src/tools/organizations/setActive.ts:

const { orgId } = params
await context.cache.set('orgId', orgId)          // committed unconditionally

let org: CachedOrg | undefined
const orgResult = await context.api.organizations().get({ orgId })
if (orgResult.success) {                          // failure silently ignored
    org = orgResult.data
    ...
}
...
    : `Switched to organization ${orgId}`         // success either way

Compare services/mcp/src/tools/projects/setActive.ts:28-43, whose comment describes precisely this failure mode:

Validate before committing the session: only switch to a project the user can actually access. Previously the projectId was cached before the fetch, so a bad id (or a project the session can't reach) silently "succeeded" and every later call failed with an opaque error instead.

That validation landed in #71976 for projects only.

Impact

An agent calling switch-organization with an id it cannot access is told the switch worked. Nothing contradicts that: subsequent org-nested reads fail with opaque 403/404s that don't mention the switch, and the agent has no reason to suspect the org it believes it is in. The stale orgId also persists in the token-keyed cache (7-day TTL) rather than being rolled back.

For clients that pin x-posthog-organization-id, the resolver re-pins on every request so the drift is corrected immediately. For clients that don't pin, it persists.

To be clear about severity: this is a correctness/UX problem, not an access-control one. Django enforces the token's scoped_organizations on org-nested views, so an out-of-scope org id yields failed requests rather than data. The issue is that the MCP layer reports success for something that didn't happen.

Suggested fix

Mirror projects/setActive.ts: fetch first, throw wrapError(...) on failure with a message naming the recovery path, and only cache.set('orgId', ...) once the fetch succeeds.

Notes

Verified by reading source at 34c5044330427. Found while building an org-scoped OAuth integration against mcp.posthog.com.

Possibly related: with a project pinned via x-posthog-project-id, switch-project is advertised but inert — the header re-pins projectId on every request (request-state-resolver.ts:121-126), so the tool reports an honest success and the next read still comes from the pinned project. #73023 stopped excluding switch-project from the roster when only a project is pinned, but the pin still overrides it. Happy to split that into its own issue if it's not the same discussion.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with services/mcp/src/tools/organizations/setActive.ts and compare it with services/mcp/src/tools/projects/setActive.ts:28-43, then inspect the organization fetch and cache update flow. Done means an inaccessible or invalid organization does not update the session cache or report success, while a successful fetch switches the active organization. Confirm the behavior against the failure scenario described in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.