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

MCP `org_teams`: create a team in an organization, add a member, and remove a team #4967

Merged opened this

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

Merged

This adds an MCP `org_teams` tool, so an organization owner can create a team, add a member and remove a team from the harness. The type check is clean and the `mcp*` tests pass, including `mcp-size`. I did not run the full suite.

shipped production is serving it deploy b8ab032

opened this

This adds an MCP org_teams tool, so an organization owner can create a team, add a member and remove a team from the harness. The type check is clean and the mcp* tests pass, including mcp-size. I did not run the full suite.

  • Tool: org_teams in src/http/mcp.ts sits right after issue_teams and takes { org, action, team, user?, permission? }.

- Actions: create (permission read, write or admin; read by default), add_member and remove. They call the same action functions as POST /api/v1/orgs/:org/teams, POST …/teams/:team/members and DELETE …/teams/:team. The owner check is the one those routes use, and the API routes are unchanged. - Scope: like those routes, it needs an admin-scope token, so it is hidden from rw tokens. - Description: one short sentence, so tools/list stays under the size limit.

  • Docs: docs/MCP.md has the org_teams row. issue_teams has no row in that file, so I put the new one just before the now row.
  • Tests: test/mcp-orgteams.test.ts covers three cases.

- An owner creates a team, adds a member and removes the team. - A taken team name is refused. - A non-owner is refused, and an unknown action is refused.

I left out removing a single member from a team; the issue didn't ask for it.

To try it, call org_teams with an admin-scope token, for example {org:"acme", action:"create", team:"docs", permission:"write"}.

Closes #4904

Approved

Clean. org_teams calls the same createTeam, addTeamMember and deleteTeam actions as the page and POST/DELETE /api/v1/orgs/:org/teams. Each one runs requireOrgAdmin on the verified actor, so the owner check is the API's own and is not reimplemented. needsAdminScope keeps the tool away from rw tokens, as the API's requireScope(ctx, "admin") does. Permission is an allowlist that defaults to read, and string inputs are type-checked and capped by the schema. On the preview, an unauthenticated tools/call for org_teams is answered unauthorized. The tests check that an owner can do all three actions, that a duplicate name is refused, that a non-owner is refused and nothing changes, and that an unknown action is refused.

Two low-severity notes, neither blocking:

  1. The team is looked up before the owner check, so a non-owner with an admin token gets not_found for a team that does not exist and forbidden for one that does. This tells them which team names exist, even teams they cannot see. GET /api/v1/orgs/:org/teams/:team/members hides those teams, but the API's write routes give the same answer through loadTeam, so this is parity, not a regression. It is worth fixing in both places together: answer not_found unless the team is in listTeamsVisibleTo.
  2. bad(x.error, x.error) sends the bare code as the message. A short sentence such as "that team name is taken" or "only an owner of the organization can do that" would read better from a harness.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs