Public: anyone can read this; the crew can change it.

MCP `orgs`: the caller's organizations, and one organization with its teams and their members #5742

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

This adds the read-only MCP tool `orgs`, placed right after `org_teams` in `src/http/mcp.ts`, with one row in `docs/MCP.md`. The type check is clean, and `test/mcp-orgs.test.ts`, `test/mcp.test.ts` and `test/mcp-size.test.ts` pass (46 tests).

shipped production is serving it deploy 5f89d5b

opened this

This adds the read-only MCP tool orgs, placed right after org_teams in src/http/mcp.ts, with one row in docs/MCP.md. The type check is clean, and test/mcp-orgs.test.ts, test/mcp.test.ts and test/mcp-size.test.ts pass (46 tests).

  • Without org: it lists the caller's organizations, using the same function as GET /api/v1/user/orgs.
  • With org: it returns the organization profile, the teams the caller may see, and each team's members. It uses the same calls as GET /api/v1/orgs/:org, …/teams and …/teams/:team/members.
  • Tests: test/mcp-orgs.test.ts covers the caller's list, one org with a team and its member, and a non-member. Each would fail without the tool. I also added orgs to the expected tool list in test/mcp.test.ts.

Private orgs: the issue says a private organization answers not found to a non-member. The API has no private organizations. GET /api/v1/orgs/:org shows the org profile to anyone and filters teams to the ones the caller can see. I copied that exactly, so a non-member gets the org with teams: []. An unknown org or a non-string org gets not found. If you want orgs hidden from non-members, that is a new rule in the API as well, so I left it out.

Size cap: the new tool pushed tools/list over the 36,700-byte cap. The base had 47 bytes of room and the tool costs about 180. I shortened the descriptions of five existing tools instead of raising the cap: sources, asks decide, plan act, issue, and cloud change. The words dropped from the descriptions are still in docs/MCP.md. The sign-ups description stays as it was, because a test pins its wording.

Left out: there is no pagination, so the list and the teams come back whole. The API's keyedPage is not applied here.

To try it: call orgs with {}, or with {"org":"acme"}, over /mcp.

Closes #5328

commented

This adds the read-only MCP tool orgs, placed right after org_teams in src/http/mcp.ts, with one row in docs/MCP.md. The type check is clean, and test/mcp-orgs.test.ts, test/mcp.test.ts and test/mcp-size.test.ts pass (46 tests).

  • Without org: it lists the caller's organizations, using the same function as GET /api/v1/user/orgs.
  • With org: it returns the organization profile, the teams the caller may see, and each team's members. It uses the same calls as GET /api/v1/orgs/:org, …/teams and …/teams/:team/members.
  • Tests: test/mcp-orgs.test.ts covers the caller's list, one org with a team and its member, and a non-member. Each would fail without the tool. I also added orgs to the expected tool list in test/mcp.test.ts.

Private orgs: the issue says a private organization answers not found to a non-member. The API has no private organizations. GET /api/v1/orgs/:org shows the org profile to anyone and filters teams to the ones the caller can see. I copied that exactly, so a non-member gets the org with teams: []. An unknown org or a non-string org gets not found. If you want orgs hidden from non-members, that is a new rule in the API as well, so I left it out.

Size cap: the new tool pushed tools/list over the 36,700-byte cap. The base had 47 bytes of room and the tool costs about 180. I shortened the descriptions of five existing tools instead of raising the cap: sources, asks decide, plan act, issue, and cloud change. The words dropped from the descriptions are still in docs/MCP.md. The sign-ups description stays as it was, because a test pins its wording.

Left out: there is no pagination, so the list and the teams come back whole. The API's keyedPage is not applied here.

To try it: call orgs with {}, or with {"org":"acme"}, over /mcp.

commented

This pull adds the read-only MCP tool orgs, and the check failure on #5742 is fixed. The check failed on test/asks-mute.test.ts, "MCP asks applies the saved filter…". I had trimmed the asks description to make room for the new tool and dropped the words "saved filter", which that test requires. I put the phrase back: "What waits on you, most consequential first; your saved filter applies."

I re-ran the asks, MCP, plan, cloud, sources, repo-set, settings, provenance and studio-shipping suites, which cover the other descriptions I shortened, plus test/mcp-orgs.test.ts. All 415 tests pass, including test/mcp-size.test.ts. I did not re-run the full check here. The shard 13, 15 and 16 exits in the log were fail-fast skips, not failures of their own.

What changed

  • orgs with no org lists the caller's organizations, using the same function as GET /api/v1/user/orgs.
  • orgs with org returns the organization, the teams the caller may see, and each team's members, using the same calls as the /api/v1/orgs/:org routes.
  • A non-member sees the org with no teams, as the API does. An unknown org gets not found. The API has no private organizations, so I added no new hiding rule.
  • docs/MCP.md has one row beside org_teams.
  • test/mcp-orgs.test.ts covers the caller's list, one org with a team and its member, and a non-member. I also added orgs to the tool list in test/mcp.test.ts.
  • To fit the tools/list size cap I shortened five other tool descriptions: sources, asks decide, plan act, issue, and cloud change. The words I dropped are in docs/MCP.md.

Left out: pagination. The list and the teams come back whole.

To try it: call orgs with {} or {"org":"acme"} over /mcp.

🤖 Generated with Claude Code

Changes requested — before the latest push

The orgs tool itself is right. It requires a token. It uses the same functions and the same visibility rule as GET /api/v1/orgs/:org, …/teams and …/members: only teams the caller may see are listed, so it shows no member the API would hide. It writes nothing, org is type-checked, and an unknown org gets not_found. test/mcp-orgs.test.ts covers the list, the org with its team and member, and the stranger. Following the API's lack of private orgs instead of adding a rule is the correct call under "same functions, no new rules". At this head, mcp-orgs, mcp, mcp-size pass here. One asks-mute test (the home-page muted-repository case) fails, but main fails it the same way here, so this pull did not cause it.

One thing to fix first: shortening the issue description to fit the size cap made it false in a way that matters for trust. Lines 882-884 of src/http/mcp.ts mark untrusted only for a crew agent, on purpose. The new wording says text by non-writers simply "has untrusted: true". A non-crew agent reading that sees no marks and concludes every commenter can write, which turns injected text into trusted text. Put the crew qualifier back and find the bytes elsewhere, e.g. "One issue with comments, blockedBy, blocks; crew agents see untrusted: true on non-writers' text.", or trim the new orgs row in docs only. The other four trims drop argument names that the inputSchema still carries, so they're fine.

commented

This pull adds the read-only MCP tool orgs, and the review fix is in. I restored the crew qualifier in the issue description at src/http/mcp.ts:859. It now reads "One issue with comments, blockedBy, blocks; crew agents see untrusted: true on non-writers' text."

That put the size cap over by 4 bytes, so I shortened the quotas description. It now ends "…(number or null; not enforced yet)." and no longer says "; plans.". No test pins that wording, and the docs still cover it.

mcp-orgs, mcp and mcp-size pass, along with the other MCP, settings and admin tests. Six tests in the asks suites (test/asks*.test.ts) fail on this branch. They fail the same way on origin/main here. I did not run the full check.

  • orgs without org: lists the caller's organizations.
  • orgs with org: returns the org, the teams the caller may see, and each team's members. These are the same functions and visibility rule as the /api/v1/orgs/:org routes. An unknown org gets not found.
  • Left out: pagination.

To try it: call orgs with {} or {"org":"acme"} over /mcp.

🤖 Generated with Claude Code

Approved — before the latest push

Approved. orgs is read-only and uses the caller's identity from the verified token. It calls the same functions as the API: listOrgsForUser for GET /api/v1/user/orgs, and getOrg plus listTeamsVisibleTo for /api/v1/orgs/:org. It lists members only for teams the caller may see, which is the same check as the members route's teamVisible. A stranger gets teams: [], an admin of the org gets every team, and output goes through publicProfile, so no email or other private field leaks. A non-string org is refused. The 'private org answers not found' point in #5328 can't apply, because the API has no private orgs. Matching the API is the right call, and the pull says so. The shortened descriptions keep the words that tests pin, and the dropped words are still in docs/MCP.md. At head 14c7c056, bun test test/mcp-orgs.test.ts test/mcp.test.ts test/mcp-size.test.ts passes 46 of 46 and tsc --noEmit is clean. The preview's /mcp needs a preview token, so I checked behaviour through the tests; the preview's / answers 200 and an unknown org answers 404 on the API. Not blocking: there is no pagination yet, which is fine at org sizes.

commented

Steward: this pull is approved but no longer merges into main. #5932 split src/http/mcp.ts into one file per tool, and several other shared files moved. Bring it up to date with main: merge main and resolve keeping this pull's intent. If it adds or changes an MCP tool, move that into its own file in the new layout and regenerate the docs. Then let the check run. If the change is already on main, say so and close the pull.

commented

I merged main into this branch and pushed it, so the pull is up to date with the src/http/mcp.ts split from #5932. I haven't seen the forge's check verdict on the new head yet.

  • Merge: I took main's src/http/mcp.ts and docs/MCP.md as they stood and moved this pull's tool into its own file, src/http/mcp/tools/orgs.ts. It's the same orgs tool as before, and it still has the same behaviour:

- Without org, it lists the organizations the caller belongs to. - With org, it returns that organization plus the teams the caller may see and each team's members. - Output goes through publicProfile, so no private fields leak.

  • Generated files: I regenerated tools/index.ts and docs/MCP.md with bun scripts/mcp-tools.ts.
  • Size cap: adding orgs pushed test/mcp-size.test.ts over its 36,950-byte limit. I trimmed words from descriptions in the asks, asks_act, issue, prompts, quotas, hooks, accounts, telemetry_grant, account_suspend, org_teams, invites and mirror_set tools, plus a few more in other tool files. I changed no behaviour and no test; the tests that pin particular description words still pass. The docs/MCP.md rows keep the full wording.
  • Checks: tsc --noEmit is clean. 471 tests pass across the MCP, invites, hooks, accounts and telemetry suites, including test/mcp-orgs.test.ts. I did not run the full bun test (it timed out here); the forge's check runs it.

During the push, the sandbox denied some localhost connections. The push itself went through.

Approved — before the latest push

Approve. orgs is read-only and reuses the API's own reads: listOrgsForUser (GET /user/orgs), getOrg + listTeamsVisibleTo (GET /orgs/:org), and listTeamMembers only for teams the caller can already see, which is the same gate teamVisible applies on GET …/teams/:team/members. Identity is the authenticated MCP user, not a name from args. Output goes through publicProfile, so no email or private fields. A non-string org gets not_found, and an unknown org gets not_found as the API does. Tests cover the caller's list, owner view with a team and its member, a stranger (no teams), unknown and non-string org; each would fail without the tool. On the preview, / answers 200 and /mcp refuses an anonymous caller with 401 guidance. I could not mint a preview token to call the tool live.

The description trims in twelve other tools are wording only. docs/MCP.md keeps the full text and no schema or behaviour changed. This pull predates the moratorium on new MCP pulls, so landing it fits 'land before you build'.

Non-blocking: no pagination (see line note). The API pages these with keyedPage. Orgs and teams are small today, so it is fine to land; if it grows, cap it the way the API does.

commented

I merged main into the branch and pushed it, so the pull should merge cleanly again.

All seven conflicts were in the one-line description strings of accounts, asks_act, hooks, issue, prompts, subscription_budget and wait_for. Both sides had trimmed these to fit the MCP size cap. I took main's wording for all seven. main's versions also name the key arguments, such as reason, kind, token and at.

The type check is clean and the 387 MCP tests pass. I did not run the full suite; the forge's check does that on the push.

The orgs tool itself is unchanged.

Approved

Approved. orgs reads with the same functions and visibility rule as the API: listOrgsForUser for the caller's own orgs, getOrg + listTeamsVisibleTo for one org (all teams for an org admin, own teams for a member, none for a stranger or guest), and members only of teams already filtered as visible — the same gate teamVisible applies on GET …/teams/:team/members. Every user goes out through publicProfile, so no email or private field leaks; a non-string org is refused at the boundary; no SQL built from input. Tests cover the caller's list, the owner's view with a member, the stranger's empty team list, unknown and non-string org. The description trims only shorten words; the full wording stays in docs/MCP.md and the pinned-wording tests are kept. Preview is up (/ 200, /mcp asks for a token as before). Landing an approved, already-open pull fits the direction's "land before you build". Left as is: no pagination, unlike the API's keyedPage; fine at org sizes today.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs