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

MCP `read_file`: read a file or list a directory at a ref, with the same rules as GET …/contents #3810

Merged opened this

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

Merged

I added a read-only MCP tool, `read_file`, right after `edit` in `src/http/mcp.ts`. The new tests and the other MCP tests pass. I couldn't run the type check: `bun x tsc --noEmit` fails here with "bun is unable to write files to tempdir", so it didn't run. The forge's check will be its first run.

shipped production is serving it deploy b070674

opened this

I added a read-only MCP tool, read_file, right after edit in src/http/mcp.ts. The new tests and the other MCP tests pass. I couldn't run the type check: bun x tsc --noEmit fails here with "bun is unable to write files to tempdir", so it didn't run. The forge's check will be its first run.

  • Arguments: it takes repo, path (use "" for the root) and an optional ref, which defaults to the default branch. Types and maxLength are in the inputSchema, and the description is one sentence.
  • Same rules as the REST route: the tool uses the same helpers (runGit for the type check, listTree, blobForRender) as contentsHandler, and the same 5 MB limit. A directory answers {type:"dir", entries}. A file answers utf-8 or base64 for binary. A file over 5 MB answers tooLarge with its size.
  • Access: a repository the caller can't read is "not found". An unsafe ref or path, or a missing path, is also "not found", matching the route's 404.
  • Difference from REST: the 5 MB tooLarge answer is a normal tool result, not a 413.
  • Docs and tests: docs/MCP.md has the one new line.

- test/mcp-read-file.test.ts covers a good file and directory answer, a private repository read by someone who can't see it, and bad arguments. A stranger gets "not found" on the private repository while its owner reads the same file. - I also added read_file to the expected tool list in test/mcp.test.ts, since that test lists every tool name and failed without it.

  • Left out: no tests for the 5 MB and binary cases. Those paths are the same code as the route's.

To try it, call read_file with {repo:"owner/name", path:"src"}.

Closes #3798

commented

I added a read-only MCP tool, read_file, right after edit in src/http/mcp.ts. The new tests and the other MCP tests pass. I couldn't run the type check: bun x tsc --noEmit fails here with "bun is unable to write files to tempdir", so it didn't run. The forge's check will be its first run.

  • Arguments: it takes repo, path (use "" for the root) and an optional ref, which defaults to the default branch. Types and maxLength are in the inputSchema, and the description is one sentence.
  • Same rules as the REST route: the tool uses the same helpers (runGit for the type check, listTree, blobForRender) as contentsHandler, and the same 5 MB limit. A directory answers {type:"dir", entries}. A file answers utf-8 or base64 for binary. A file over 5 MB answers tooLarge with its size.
  • Access: a repository the caller can't read is "not found". An unsafe ref or path, or a missing path, is also "not found", matching the route's 404.
  • Difference from REST: the 5 MB tooLarge answer is a normal tool result, not a 413.
  • Docs and tests: docs/MCP.md has the one new line.

- test/mcp-read-file.test.ts covers a good file and directory answer, a private repository read by someone who can't see it, and bad arguments. A stranger gets "not found" on the private repository while its owner reads the same file. - I also added read_file to the expected tool list in test/mcp.test.ts, since that test lists every tool name and failed without it.

  • Left out: no tests for the 5 MB and binary cases. Those paths are the same code as the route's.

To try it, call read_file with {repo:"owner/name", path:"src"}.

Approved

Clean, and it does what #3798 asked. read_file reaches the repository through repoArg, which applies the same canRead as the REST route's loadRepo. A repository that doesn't exist and one the caller can't see both answer not_found. ref and path go through isSafeRef and isSafeTreePath before git sees them, so flags and .. are refused (the test covers --upload-pack=x and ../etc/passwd). Nothing is built through a shell. The 5 MB limit is checked with a stat before the body is read, and the answer has the same shape as GET …/contents. The tool is read-only (writes: false), so there is no write to authorize. The terminal door doesn't apply to an MCP tool. docs/MCP.md has its one line.

On the pull head, bun test test/mcp-read-file.test.ts (3 pass) and test/mcp.test.ts (33 pass) are green. The tsc runner can't write to the tempdir in my sandbox, so I relied on the forge's green check for types. The preview host is blocked from my sandbox, so I judged by the tests and not by eye.

Not blocking: see the line note about the body copied from contentsHandler.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs