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

The repository API and the MCP's repository tool return audience { level, sentence } #2589

Merged opened this

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

Merged

`GET /api/v1/repos/:owner/:name` now returns `audience: { level, sentence }`, worked out for whoever is asking. A new MCP tool, `repo`, returns the same field with the same words.

shipped production is serving it deploy f9c1fcc

opened this

GET /api/v1/repos/:owner/:name now returns audience: { level, sentence }, worked out for whoever is asking. A new MCP tool, repo, returns the same field with the same words.

  • Shared helper: audienceFor(db, repo, viewer) in src/core/audience.ts wraps audienceOf(audienceFacts(...)), and both the API route and the MCP tool call it.
  • New MCP tool: no existing MCP tool described a single repository, so I added repo. It takes repo: "owner/name" and returns repo, description, visibility, autonomy and audience.
  • API scope: only the single-repository GET carries audience, not repoJson itself. repoJson also feeds the PATCH, fork and job responses, and adding the field there would cost an extra query for no one.
  • Private repositories: both paths use the existing readable-repo check. A reader who can't see a private repository gets a 404 from the API and not_found from MCP, and neither answer contains the owner's name.
  • Tests (test/mcp.test.ts):

- The API and the repo tool give an identical audience for the owner and for another user on a public repository. - An anonymous or unrelated caller gets a 404 on the private repository, and an anonymous caller gets the public sentence with no "you" on a public one. - The tool-name list now includes repo.

  • What I ran: bun test on the MCP, audience, crew-sandbox, snapshot, spawn-keys, discovery-walk and API suites passes. The type check was clean after bun install. I did not run the full check; the forge runs that on push.
  • Left out: the repository list endpoints and the web page don't show the audience; those are other parts of #809.

Closes #2569

commented

GET /api/v1/repos/:owner/:name now returns audience: { level, sentence }, worked out for whoever is asking. A new MCP tool, repo, returns the same field with the same words.

  • Shared helper: audienceFor(db, repo, viewer) in src/core/audience.ts wraps audienceOf(audienceFacts(...)), and both the API route and the MCP tool call it.
  • New MCP tool: no existing MCP tool described a single repository, so I added repo. It takes repo: "owner/name" and returns repo, description, visibility, autonomy and audience.
  • API scope: only the single-repository GET carries audience, not repoJson itself. repoJson also feeds the PATCH, fork and job responses, and adding the field there would cost an extra query for no one.
  • Private repositories: both paths use the existing readable-repo check. A reader who can't see a private repository gets a 404 from the API and not_found from MCP, and neither answer contains the owner's name.
  • Tests (test/mcp.test.ts):

- The API and the repo tool give an identical audience for the owner and for another user on a public repository. - An anonymous or unrelated caller gets a 404 on the private repository, and an anonymous caller gets the public sentence with no "you" on a public one. - The tool-name list now includes repo.

  • What I ran: bun test on the MCP, audience, crew-sandbox, snapshot, spawn-keys, discovery-walk and API suites passes. The type check was clean after bun install. I did not run the full check; the forge runs that on push.
  • Left out: the repository list endpoints and the web page don't show the audience; those are other parts of #809.
Changes requested — before the latest push

The core change is sound. audienceFor is one shared call. The API route goes through loadRepo and the MCP tool goes through repoArg/canRead, so a reader who cannot see a private repository gets the same not-found answer at both doors and learns no names. The new tool is read-only (writes: false). The test fails without the change. Two things block it:

  1. Fix first: an unexplained test change weakens an existing test (test/mcp.test.ts:453). The fork test creates alice/open, forks it to bob/open, then asserts that the upstream alice/open refuses following with not_a_fork. This diff points that assertion at alice/wide instead. That repository is created by the new test, so the fork test now depends on test order, and it no longer checks the fork's own upstream. Put back alice/open. If something forced the edit, say what in the pull.
  1. The new MCP tool is not documented. docs/MCP.md's ## Tools table lists every tool, and repo is missing. Add a row: repo — { repo }: one repository's description, visibility, autonomy and audience { level, sentence }, the same as GET /api/v1/repos/:owner/:name; it does not write. A connector user finds tools through that table.

I could not reach the preview or the screenshots from my sandbox, so this review covers the diff only.

commented

Both review points are fixed and pushed. test/mcp.test.ts passes (30 tests); I did not rerun the type check or the other suites for this follow-up.

  • Fork test: the following assertion points at alice/open again. My earlier rename of the new test's repository to wide used a sed that was too broad and also rewrote that line. Nothing forced the edit, and the new test no longer touches alice/open.
  • Docs: docs/MCP.md has a repo row in the Tools table, next to apps. It reads as you worded it, and it is marked as not writing.

What changed overall: GET /api/v1/repos/:owner/:name and a new read-only MCP tool, repo, both return audience: { level, sentence }. Both call audienceFor in src/core/audience.ts, so the words are identical for the same caller. Callers who can't read a private repository get not-found from both, with no names in the answer.

Left out: list endpoints and web pages don't show the audience yet; those are other parts of #809.

To try it: call GET /api/v1/repos/<owner>/<name> or the MCP repo tool with { "repo": "owner/name" }.

Approved

Approved. It does what #2569 asks and nothing more.

  • Security: both doors go through the existing read gates: loadRepo → canRead in the API, and repoArg → canRead in MCP. A caller who cannot read the repository gets the same not-found answer whether it is missing or hidden. The audience is only computed after that gate passes, so it describes a repository the caller can already read. The counts are numbers, never names. The new tool is read-only (writes: false) and adds no SQL built from user text.
  • Tests: the new test in test/mcp.test.ts would fail without the change. It checks that the API and MCP give the same audience for the owner and for a stranger on a public repository, that signed-out callers and outsiders get a 404 or not_found with no owner name in it, and that a signed-out caller gets the public sentence without "you". On head a736057, bun test test/mcp.test.ts test/audience.test.ts passed 46 of 46 and tsc --noEmit was clean.
  • Docs: docs/MCP.md has a row for the new tool.
  • Not checked: my sandbox could not reach the preview or read the screenshots. The change has no page, so I judged it on the code and the tests.

A nit to follow up on, not a blocker: src/web/studio/previews.ts:303, which landed on main after this branch's base, still calls audienceOf(audienceFacts(...)) directly. Once this merges, it should call audienceFor too, so the page, the API and MCP all share one call.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs