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

MCP `app_notes`: an app's admin lists the notes sent from its forge button, hides one, bans or unbans its sender #7273

Merged opened this

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

Merged

This adds an MCP tool, `app_notes` (`{ app, action?, note? }`), so an app's admin can handle notes from the forge button through MCP, as they already can on the studio page and in the terminal. With no `action` it lists the shown notes and the banned senders, as `ownerNotes` returns them. `hide`, `ban` or `unban` with a `note` id goes through `moderateNote` or `unbanSender`, so it uses the same ad…

shipped production is serving it deploy da65c36

opened this

This adds an MCP tool, app_notes ({ app, action?, note? }), so an app's admin can handle notes from the forge button through MCP, as they already can on the studio page and in the terminal. With no action it lists the shown notes and the banned senders, as ownerNotes returns them. hide, ban or unban with a note id goes through moderateNote or unbanSender, so it uses the same admin check and the same steps as the page: hiding closes the note's issue, and its picture is dropped.

What it answers:

  • Admin check: anyone who is not an admin of the app's repository gets forbidden, both when listing and when acting.
  • Not found: an unknown app, or an unknown note, is not_found.
  • Invalid: an unknown action, or a missing or too-long note id, is invalid.
  • Read-only tokens: writing needs an rw token. A call with no action counts as a read, so a read-only token can list notes but cannot act on them.

What to look at:

  • src/http/mcp/tools/app_notes.ts is the tool.
  • src/http/mcp/tools/index.ts and the tools table in docs/MCP.md were regenerated by bun scripts/mcp-tools.ts.
  • test/mcp-app-notes.test.ts sends notes through the button's endpoint (feedback() in src/apps/feedback.ts), then lists, hides, bans and unbans them through /mcp. It checks the database after each step. It also checks the refusals: a team member with write access but not admin on an org repository, a read-only token, an unknown app or note, and an unknown action.
  • test/mcp.test.ts gets one line: it lists every tool name, so app_notes is added to that list. The old list was correct; it just did not include the new tool.

I made it a new tool rather than an action on an existing one, because no existing tool covers app notes and the issue asks for this one. Its description is one sentence. bun test test/mcp-size.test.ts passes without raising any limit.

bun x tsc --noEmit is clean. scripts/fast.ts picked 857 test files (about 75 minutes), so instead I ran the MCP-wide tests (mcp, mcp-size, mcp-tools, mcp-docs, mcp-write-scope, llms-tools, mcp-cards-coverage, mcp-discovery-walk) plus apps-feedback and the new test. After the one-line list fix, mcp, mcp-size and the new test pass on a re-run; the others passed on the first run.

To try it: on a preview, send a note from an app's forge button, then call app_notes with { "app": "owner/name" }, and then with { "app": "owner/name", "action": "hide", "note": "<id>" }.

🤖 Generated with Claude Code

Closes #7214

commented

This adds an MCP tool, app_notes ({ app, action?, note? }), so an app's admin can handle notes from the forge button through MCP, as they already can on the studio page and in the terminal. With no action it lists the shown notes and the banned senders, as ownerNotes returns them. hide, ban or unban with a note id goes through moderateNote or unbanSender, so it uses the same admin check and the same steps as the page: hiding closes the note's issue, and its picture is dropped.

What it answers:

  • Admin check: anyone who is not an admin of the app's repository gets forbidden, both when listing and when acting.
  • Not found: an unknown app, or an unknown note, is not_found.
  • Invalid: an unknown action, or a missing or too-long note id, is invalid.
  • Read-only tokens: writing needs an rw token. A call with no action counts as a read, so a read-only token can list notes but cannot act on them.

What to look at:

  • src/http/mcp/tools/app_notes.ts is the tool.
  • src/http/mcp/tools/index.ts and the tools table in docs/MCP.md were regenerated by bun scripts/mcp-tools.ts.
  • test/mcp-app-notes.test.ts sends notes through the button's endpoint (feedback() in src/apps/feedback.ts), then lists, hides, bans and unbans them through /mcp. It checks the database after each step. It also checks the refusals: a team member with write access but not admin on an org repository, a read-only token, an unknown app or note, and an unknown action.
  • test/mcp.test.ts gets one line: it lists every tool name, so app_notes is added to that list. The old list was correct; it just did not include the new tool.

I made it a new tool rather than an action on an existing one, because no existing tool covers app notes and the issue asks for this one. Its description is one sentence. bun test test/mcp-size.test.ts passes without raising any limit.

bun x tsc --noEmit is clean. scripts/fast.ts picked 857 test files (about 75 minutes), so instead I ran the MCP-wide tests (mcp, mcp-size, mcp-tools, mcp-docs, mcp-write-scope, llms-tools, mcp-cards-coverage, mcp-discovery-walk) plus apps-feedback and the new test. After the one-line list fix, mcp, mcp-size and the new test pass on a re-run; the others passed on the first run.

To try it: on a preview, send a note from an app's forge button, then call app_notes with { "app": "owner/name" }, and then with { "app": "owner/name", "action": "hide", "note": "<id>" }.

🤖 Generated with Claude Code

Approved

Approved. The tool is a thin door over the actions the studio card (web/studio/notes.ts noteModerate) and the terminal (tui/screens/notes.ts) already use: ownerNotes, moderateNote and unbanSender each check canAdmin on the authenticated MCP user. Writes need an rw token because of writes: true and readsWithout: "action". The input checks match the page's (an action from the enum, a note id of 1–64 characters, not_found for an unknown note). planRepo/repoArg resolves the app, so a private repository is not exposed. No SQL or shell is built from input. The JSON output carries no HTML.

The tests prove the behaviour end to end. A note goes in through the real feedback() endpoint. Listing, hide (which also closes the issue), ban and unban are checked against the database. The refusals are covered too: a writer who is not an admin, an ro token trying to act, an unknown app or note, a bad action and a missing note. bun test test/mcp-app-notes.test.ts passes at b155661a (3 pass). On the preview, / answers 200 and /mcp without a token refuses as it should. Small and single-purpose, it reuses the existing actions, and the tools/list size budget holds without raising any limit.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs