# MCP file upload for case input context

There is no pull request. The GitHub token for `hammer-mt` is invalid (`gh auth status` fails against github.com), so this could not be opened on `EveryInc/checks`. The reviewable change is `upload-input.patch` in this directory. It applies cleanly onto commit `18df437ab76efbc91c4c7796959be20c489049e9` (`git apply --check` succeeded against that tree).

```bash
git fetch origin 18df437ab76efbc91c4c7796959be20c489049e9
git checkout -b mcp-upload-input 18df437ab76efbc91c4c7796959be20c489049e9
git apply /path/to/upload-input.patch
bin/rails test test/models/case_inputs_test.rb test/integration/mcp_test.rb test/integration/upload_api_test.rb
```

The edited tree is also in `code/`.

## What was missing

A case already stores the files a model starts with (`Case` `has_many_attached :inputs`). The bundle writes those paths beside `prompt.md`, and `run.sh` copies them into `work/`. Nothing in the MCP connection could attach them. `add_task` told the agent to ask the owner to upload inputs in the app, and the app has no input-upload form. The agent pasted source files into the prompt instead.

Assumption: "input context" means those case inputs, the workspace beside the brief, not `eval/` fixtures or skill files. Eval files are what the checks read; `run.sh` never copies `eval/`, `cases/`, or `skill/` into the workspace.

## What to send the team

New MCP tool `upload_input` (18 tools on `/mcp`; the old README said 15 and was already missing `create_benchmark` and `get_task`).

| Argument | Required | Meaning |
|---|---|---|
| `task_slug` | yes | Task that owns the case |
| `case_slug` | no | Defaults to `default` |
| `path` | yes | Path relative to the case, e.g. `notes/slack-dm.md` or `files/draft.docx` |
| `content` | one of these | File as text |
| `content_base64` | one of these | Raw bytes as standard base64 |
| `content_type` | no | MIME type; guessed from the path when omitted |

The same path again replaces the file. The response lists every input on the case and an `inputs_url`. `get_case` and `get_task` now include that list, and the next bundle contains the bytes. The task page and the result page show the workspace path under Given files (a nested file is `notes/slack-dm.md`, not just `slack-dm.md`).

Files over 8 MB are refused by the tool with that `inputs_url` and nothing saved. The same bearer token can POST them as multipart to `POST /benchmarks/:slug/tasks/:task/cases/:case/inputs` with fields `path` and `file`. That route accepts up to 500 MB, the same ceiling as the importer. Owners and editors only; reviewers get the usual write refusal over MCP and 403 over HTTP.

Paths that would not survive into `work/` are rejected and nothing is saved: absolute paths, `..`, `prompt.md` (that text belongs in the prompt), an `instruction_override` path, and anything under `eval/`, `cases/`, or `skill/`.

`add_task` and `add_case` now say to call `upload_input` instead of telling the owner to upload in the app. The server instructions and `get_benchmark_context` say the same thing, so an agent does not paste a Slack thread into the brief.

## Tests

`bin/rails test test/models/case_inputs_test.rb test/integration/mcp_test.rb test/integration/upload_api_test.rb` — 19 runs, 174 assertions, 0 failures. That covers text and binary upload, replace-in-place, the bundle zip path (`01-dashboard/cases/02-q3/notes/slack-dm.md`), the task page link text, path rejection, a reviewer, the HTTP route, and the over-8 MB handoff to `inputs_url`.

The task page and result page label change was not opened in a browser. The task-page assertion is in `McpTest`.
