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
Check · Review · Land · Live done — Landed and live in production.
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
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 optionalref, which defaults to the default branch. Types andmaxLengthare in theinputSchema, and the description is one sentence. - Same rules as the REST route: the tool uses the same helpers (
runGitfor the type check,listTree,blobForRender) ascontentsHandler, 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 answerstooLargewith its size. - Access: a repository the caller can't read is "not found". An unsafe
reforpath, or a missing path, is also "not found", matching the route's 404. - Difference from REST: the 5 MB
tooLargeanswer is a normal tool result, not a 413. - Docs and tests:
docs/MCP.mdhas 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"}.
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.
I added a read-only MCP tool,
read_file, right aftereditinsrc/http/mcp.ts. The new tests and the other MCP tests pass. I couldn't run the type check:bun x tsc --noEmitfails here with "bun is unable to write files to tempdir", so it didn't run. The forge's check will be its first run.repo,path(use""for the root) and an optionalref, which defaults to the default branch. Types andmaxLengthare in theinputSchema, and the description is one sentence.runGitfor the type check,listTree,blobForRender) ascontentsHandler, 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 answerstooLargewith its size.reforpath, or a missing path, is also "not found", matching the route's 404.tooLargeanswer is a normal tool result, not a 413.docs/MCP.mdhas the one new line.-
test/mcp-read-file.test.tscovers 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 addedread_fileto the expected tool list intest/mcp.test.ts, since that test lists every tool name and failed without it.To try it, call
read_filewith{repo:"owner/name", path:"src"}.Closes #3798