Skip to content

feat(mcp): Add scoped read tools for conversations, review items, and automations - #2573

Merged
dcramer merged 5 commits into
mainfrom
feat/mcp-scoped-read-tools
Nov 3, 2026
Merged

dcramer merged 5 commits into
mainfrom
feat/mcp-scoped-read-tools

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Extends the remote MCP server at /api/mcp from one tool to nine. The tools are split across four OAuth scopes. No tool starts agent work: nothing sends a message, answers or dismisses a Review item, or runs an Automation.

Scopes

Scope Tools
conversations:read searchConversations, getConversation, listConversationMessages
conversations:manage archiveConversation, stopConversation
review_items:read searchReviewItems, getReviewItem
automations:read searchAutomations, getAutomation
  • oauth/scopes.ts owns the scope list and the consent copy. TOOLS_BY_SCOPE in server.ts maps each scope to its tools. Each request registers only the tools of the token's granted scopes, so a client never sees a tool it can't call.
  • Authorize accepts a space-separated scope. A request without one asks for every scope. An unknown scope returns invalid_scope.
  • The consent page lists each requested scope, with a description.
  • Grants keep scope as one space-separated string. Grants issued before this change (conversations:read) still parse and keep working. To get the new tools, users connect again.
  • Refresh rejects a scope that differs from the grant's, instead of silently narrowing it.
  • WWW-Authenticate and both metadata documents now advertise every scope.

Tools

  • getConversation returns metadata, the Brief (without its activity counts and keywords), and the last assistant reply. It does not return the transcript.
  • listConversationMessages pages user and assistant messages backward from newest. It uses the existing signed conversation cursor. Each page scans at most 200 report events. Tool calls are excluded.
  • Every conversation tool except searchConversations first runs isConversationParticipant. That keeps the same membership rule that searchConversations already relies on.
  • archiveConversation and stopConversation wrap the existing REST functions. A 4xx HTTPException becomes a tool error. stopConversation needs the work queue, so the MCP handler now receives it from app.ts.
  • Review items are read-only on purpose. deliverReviewItemChange sends a submit or dismiss as a message to every linked Conversation through the work queue, which would trigger agent runs. Results include canAct and a dashboard link, so the person answers there.
  • Automations: list (with mine / attention / state filters) and get, plus the 10 newest runs. Cost and chart fields are left out.
  • The server sends instructions saying the tools are read-mostly and cannot start work.

Not in this PR

  • MCP resources (junior://conversations/{id}) and attachment downloads.
  • Pagination for searchConversations. The feed query has no cursor today, and the dashboard has the same 50-item limit.
  • pauseAutomation and any tool that triggers runs. Those would go behind separate opt-in scopes later.

Tests and docs

tests/integration/api/mcp/route.test.ts adds three scenarios that run through real Junior wiring:

  • Scope selection: the consent page lists every scope, tools are filtered per scope, an invalid scope is rejected, and a refresh that changes the scope is rejected.
  • Conversations: Brief plus last reply, message paging across two pages, a non-participant gets "not found", and stop and archive work.
  • Review items and Automations: only the viewer's items are listed, another user's item can't be read, and a missing Automation returns "not found".

The operate/mcp-server page and api/mcp/AGENTS.md are updated with the scope and no-run invariants.

Checks run locally: pnpm typecheck, root pnpm lint, pnpm docs:check, the MCP integration and unit tests, and the conversation stop and archive integration tests. I did not run an end-to-end test with a real Claude Code client.

via @dcramer.

--

View Session [Sentry]

@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Nov 3, 2026 2:47am UTC

Request Review

…tools

# Conflicts:
#	packages/junior/src/api/mcp/AGENTS.md
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

5 screenshot changes — 5 changed · 0 added · 0 removed

Review screenshots in Frameshift

Conversation Attachment · Desktop
Conversation Attachment · Desktop
Changed
Conversation Attachment · Mobile
Conversation Attachment · Mobile
Changed
Conversation Attachment Modal · Desktop
Conversation Attachment Modal · Desktop
Changed
Conversation Share Details · Desktop
Conversation Share Details · Desktop
Changed
Settings Mcp · Desktop
Settings Mcp · Desktop
Changed

listConversations, listReviewItems, and listAutomations all take a text
query and filters, so name them searchConversations, searchReviewItems,
and searchAutomations. listConversationMessages keeps its name because it
pages one transcript and has no query.

Co-Authored-By: David Cramer <[email protected]>

@dcramer dcramer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally each tool would have its own side-by-side file with tests covering it

@dcramer

dcramer commented Nov 3, 2026

Copy link
Copy Markdown
Member

i need the tests added / refactored

Each tool in src/api/mcp/tools now has a test file at the same path under
tests/integration/api/mcp/tools. Each file covers the tool's main result
and its access or failure rule. route.test.ts keeps only the OAuth,
transport, and scope scenarios.

The shared OAuth helpers move to the mcp-http fixture. Conversation,
Review item, and Automation seeding moves to a new mcp-tools fixture.

stopConversation now checks participation first. A non-participant got
"Only conversation participants can stop this turn.", which showed that
the Conversation exists. Like the other conversation tools, it now
reports "Conversation not found."
@dcramer
dcramer merged commit 46a1668 into main Nov 3, 2026
46 checks passed
@dcramer
dcramer deleted the feat/mcp-scoped-read-tools branch November 3, 2026 02:54

This branch was successfully deployed

1 active deployment
Preview – junior-docs — ab043129 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant