Client Screen
Sign inStart free

Handing a report to your coding agent

Every report your clients file already contains what a coding agent needs: the page and the element they pointed at, their browser and viewport, the console messages and failed requests from that moment, the screenshot they marked up, and their own words.

There are two ways to get it there. Copy as prompt is the manual one: a button on every issue that copies a self-contained Markdown briefing you paste into whatever you use. The MCP server is the automatic one: your agent connects to Client Screen directly, lists your open reports, reads one in full, fixes it, and sets it to “Please check” — which emails your client and asks them to verify, without you touching the board.

Copy as prompt

On an issue page, press Copy as prompt. You get a Markdown block: the issue key and status, the page and route (and the path the client took to get there), the element’s selector and HTML, the environment, the console and network buffers, anything your app attached through ClientScreen.setContext(), the client’s raw words, the client-visible part of the conversation, and links to both screenshots.

Those screenshot links are signed and expire 24 hours after the block was produced. They need no login, which is exactly what makes them usable by a coding agent — and exactly why they are short-lived. If your deployment has no FILE_SIGNING_SECRET set, the block says so in place of the links rather than quietly leaving them out.

The MCP server

Client Screen speaks the Model Context Protocol over streamable HTTP at the address below. Settings → API keys in the dashboard shows the same command with your key already in it.

https://scintillating-civet-800.eu-west-1.convex.site/mcp

It is stateless: every call is one authenticated HTTP request. Any MCP client that can send an Authorization header will work; two are spelled out below.

1. Create an API key

Dashboard → Settings → API keysNew key. Give it a name you will recognise later and pick its scopes:

  • readlist_projects, list_issues, get_issue.
  • writeadd_comment, set_status, ask_reporter.

The key is shown once, at creation. Copy it then; there is no way to read it back, because only its SHA-256 is stored. Lost one? Revoke it and make another. Revoking takes effect on the next call.

A key belongs to a workspace and can reach nothing outside it. Anything it writes is recorded as yours, with a note in the issue’s activity saying which key did it.

2. Point your agent at it

Claude Code — one command:

claude mcp add --transport http client-screen \
  https://scintillating-civet-800.eu-west-1.convex.site/mcp \
  --header "Authorization: Bearer cs_your_key"

Add --scope project to share the server with everyone working in that repository (the key then lands in .mcp.json, so use --scope local, the default, if the repo is public).

Cursor.cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project:

{
  "mcpServers": {
    "client-screen": {
      "url": "https://scintillating-civet-800.eu-west-1.convex.site/mcp",
      "headers": { "Authorization": "Bearer cs_your_key" }
    }
  }
}

Any other MCP client — give it the URL above as a streamable HTTP (not SSE, not stdio) server, with one header: Authorization: Bearer cs_your_key. The server answers POST only; a GET or DELETE is a 405, because there is no session to stream or close.

3. The tools

Tool Scope What it does
list_projects read Your client sites, with how many reports are open on each. Start here: every other tool names a project by the prefix this returns.
list_issues(project, status?) read The 50 newest reports in a project, optionally in one status.
get_issue(project, number) read The whole briefing — the same content as “Copy as prompt” — plus the marked-up screenshot as an image (over 1 MB it is left at the signed link instead).
add_comment(project, number, body, visibility) write internal is a private note; client is a reply the reporter receives by email.
set_status(project, number, status) write new, accepted, in_progress, ready_for_check, done, wont_do.
ask_reporter(project, number, question) write A client-visible question; the reporter is emailed and their answer lands on the issue.

A project is named by its prefix (WEB, case-insensitive) or by its id. An issue is named by its number within the project — the 3 of WEB-3.

The loop worth knowing: after fixing something, set_status to ready_for_check. The client gets an email asking them to verify, and your board moves without you.

What it will refuse

  • An unknown or revoked key: 401.
  • A read-only key on a write tool: a tool error saying so, not a broken connection — the agent keeps the read tools.
  • A project in another workspace: not found, whether named by prefix or by id.
  • Too many calls: 429 with a Retry-After header, at 300 calls per 10 minutes per key. Calls that present no working key are counted per IP address instead, against the same budget as the reporting endpoints, and that budget is checked before a key is looked up.

Trying it by hand

curl -s https://scintillating-civet-800.eu-west-1.convex.site/mcp \
  -H "Authorization: Bearer cs_your_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The Accept header carrying both types is required by the protocol; without it the transport answers 406.

For maintainers

  • The server is convex/mcp.ts: the official TypeScript MCP SDK’s McpServer and its WebStandardStreamableHTTPServerTransport, running inside a Convex HTTP action. One server is built per request; nothing is kept between calls.
  • The briefing is built by one pure function, buildPrompt in convex/lib/prompt.ts, from data gathered by issues.promptData (the dashboard) or issues.forAgent (the MCP server). The two paths render the same block by construction.
  • Signed links are convex/lib/sign.ts: HMAC-SHA256 over <storageId>.<expiry> with the deployment’s FILE_SIGNING_SECRET. Rotating that secret invalidates every outstanding link at once. Set it with npx convex env set FILE_SIGNING_SECRET "$(openssl rand -hex 32)".