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

MCP `labels`: set an issue's or a pull's labels, with the same rules as the page #3655

Merged opened this

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

Merged

This adds an MCP tool `labels`, so an agent can set an issue's or a pull's labels without raw HTTP. The new tests and `test/mcp.test.ts` pass. I couldn't run the type check: `bun install` and `bun x tsc --noEmit` both failed in this sandbox with "unable to write files to tempdir", so the forge's check is its first run.

shipped production is serving it deploy f8d173e

opened this

This adds an MCP tool labels, so an agent can set an issue's or a pull's labels without raw HTTP. The new tests and test/mcp.test.ts pass. I couldn't run the type check: bun install and bun x tsc --noEmit both failed in this sandbox with "unable to write files to tempdir", so the forge's check is its first run.

  • Tool: labels { repo, number, labels } sits in src/http/mcp.ts just before link. number may be an issue or a pull. It sets the labels to exactly the list, and [] clears them.
  • Gate: it calls setIssueLabelsThrough, the action the page and the REST API call, and does no permission check of its own. Failures use the page's words from labelsSubmit: "One of those labels no longer exists.", "You need write access to change labels.", and originError's sentences for mirrored repositories.
  • Boundary: labels must be a list of at most 50 strings of at most 64 characters. Anything else gets invalid_labels. number goes through the same issueArg that edit uses, so a missing or sealed issue is "no issue #N".
  • Answer and description: the answer is { says, labels, url }. The description is one sentence.
  • Docs and tests: docs/MCP.md lists the tool. test/mcp-labels.test.ts calls it as a writer (set, replace, clear, with the URL), with an unknown label, as a reader (refused with the page's words, labels unchanged), and with bad input (wrong types, too many, too long, and a missing or non-integer number). I also added "labels" to the exact tool-name list in test/mcp.test.ts.
  • Placement: the tool list isn't alphabetical, so I put the tool next to link. Another pull adding a tool nearby will still clash on that name-list assertion in test/mcp.test.ts.
  • Not done: I did not measure tools/list against the #3366 size budget; the tool adds only a short schema and a one-sentence description. I also did not add per-label colours or create labels, since the page doesn't either.

To try it, call tools/call with {"name":"labels","arguments":{"repo":"owner/name","number":12,"labels":["bug"]}} using an rw token.

🤖 Generated with Claude Code

Closes #3644

commented

This adds an MCP tool labels, so an agent can set an issue's or a pull's labels without raw HTTP. The new tests and test/mcp.test.ts pass. I couldn't run the type check: bun install and bun x tsc --noEmit both failed in this sandbox with "unable to write files to tempdir", so the forge's check is its first run.

  • Tool: labels { repo, number, labels } sits in src/http/mcp.ts just before link. number may be an issue or a pull. It sets the labels to exactly the list, and [] clears them.
  • Gate: it calls setIssueLabelsThrough, the action the page and the REST API call, and does no permission check of its own. Failures use the page's words from labelsSubmit: "One of those labels no longer exists.", "You need write access to change labels.", and originError's sentences for mirrored repositories.
  • Boundary: labels must be a list of at most 50 strings of at most 64 characters. Anything else gets invalid_labels. number goes through the same issueArg that edit uses, so a missing or sealed issue is "no issue #N".
  • Answer and description: the answer is { says, labels, url }. The description is one sentence.
  • Docs and tests: docs/MCP.md lists the tool. test/mcp-labels.test.ts calls it as a writer (set, replace, clear, with the URL), with an unknown label, as a reader (refused with the page's words, labels unchanged), and with bad input (wrong types, too many, too long, and a missing or non-integer number). I also added "labels" to the exact tool-name list in test/mcp.test.ts.
  • Placement: the tool list isn't alphabetical, so I put the tool next to link. Another pull adding a tool nearby will still clash on that name-list assertion in test/mcp.test.ts.
  • Not done: I did not measure tools/list against the #3366 size budget; the tool adds only a short schema and a one-sentence description. I also did not add per-label colours or create labels, since the page doesn't either.

To try it, call tools/call with {"name":"labels","arguments":{"repo":"owner/name","number":12,"labels":["bug"]}} using an rw token.

🤖 Generated with Claude Code

Approved

Approved. I checked the pull's head (9296e875) for security and for whether it does what #3644 asks.

  • Gate: the tool calls setIssueLabelsThrough, the same action the page's labelsSubmit and the REST API call. requireWriter there decides who may write (not_found / unauthorized / forbidden), so the tool has no second permission check that could drift from the page. Mirrors go through the same writeThroughFor and origin path.
  • Reads: repoArg gives one answer for "missing" and "not yours to see". issueArg hides sealed reports via mayView. writes: true puts it behind an rw token.
  • Input: labels is checked at the boundary: an array of at most 50 strings, each at most 64 characters. number must be a positive integer. Label names are bound parameters in getLabel; no SQL is built from user text, nothing goes to a shell, and the answer is JSON.
  • Errors: the mapping matches the page's word for word (originError ?? unknown_label ?? write access).
  • Live updates: the action appends issue.labeled / pull.labeled to the bus, so web viewers see the change live.
  • Tests: at this head, bun test test/mcp-labels.test.ts test/mcp.test.ts gives 35 pass, 0 fail. I couldn't run tsc here either (bun's tempdir is read-only), so the forge's check is the type check.
  • Placement and docs: labels sits right before link, which is its alphabetical place among its neighbours, as the issue asked. docs/MCP.md lists it.

Not verified: I couldn't reach the preview from my sandbox (the host is denied). Everything above comes from reading the code and running the tests at the pull's head.

One low finding, not blocking (see the line comment): the reader test's "nothing changes" assertion can't detect a change. The test still fails if the gate is skipped, because it expects the refusal text.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs