Skip to content

A session can put a file or an image on the user's screen - #212

Merged
ghackett merged 1 commit into
mainfrom
session-mcp-pr4-editor-and-lightbox
Aug 8, 2026
Merged

A session can put a file or an image on the user's screen#212
ghackett merged 1 commit into
mainfrom
session-mcp-pr4-editor-and-lightbox

Conversation

@ghackett

@ghackett ghackett commented Aug 8, 2026

Copy link
Copy Markdown
Member

PR 4 of the session-mcp-tools spec: the open_in_editor and show_image handlers land behind the socket service #209 wired up. An agent can now put the file it's talking about on the user's screen — "I've put the failing test on screen" instead of hoping they click a path — and pop a screenshot, plot, or render into the in-app lightbox without launching an external viewer.

What changed

  • mcptools.TOOLS grows the two schemas: open_in_editor(path, line?) (line is 1-based, minimum 1) and show_image(path). The existing table-driven validator covers them — wrong types, line: 0, and stray arguments are all rejected app-side, since the socket is reachable by any local process.
  • app.py adds the two handlers plus _mcp_resolve_file: a relative path resolves against the running agent's cwd first (it may have cd'd into a worktree), then the tab's project root — the same order clickable file references use (terminal._reference_roots).
    • open_in_editor requires the file inside the session's project (can_open_in_editor), converts the 1-based line to the editor's cursor, and opens via the calling tab's own editor.
    • show_image follows the clicked-image-reference recipe: any readable image shows, inside the project or not; the lightbox's "Open in Editor" button only appears when the tab could actually open it.
  • window.py promotes _open_in_tab_editor to public open_in_tab_editor: a session-attributed call must land in the calling session's own tab — the window-level _open_in_editor picks a tab by project membership and may switch tabs, which is the wrong door for an agent-driven open.

Screenshots

A staged session called its own tools through the real socket. open_in_editor("src/widget.py", line=12) — cursor at 12:1 in the status bar:

open_in_editor result: widget.py open at line 12 in the session's editor pane

show_image on a staged render — lightbox over the window, with its Open in Editor button since the image lives inside the project:

show_image result: the render floating in the in-app lightbox

Verification

  • 75 GTK-free tests pass locally (test_mcptools + test_mcp_shim + test_mcpserver), including new schema/validation coverage for both tools and the tool-list assertions updated to track the table.
  • E2E, headless, against a throwaway app instance driving the real socket path (hello + call frames; identity faked to the open tab, since the probe isn't a descendant of the tab's shell — the ancestry walk itself was e2e-verified in A session can rename itself through the live Collins tool socket #209 and is unchanged here):
    • open_in_editor with a relative path + line → editor opened at 12:1 ✔ (screenshot)
    • show_image with an absolute path → lightbox presented ✔ (screenshot)
    • /etc/hostname → "That file is outside this session's project" ✔
    • missing file → "No such file: src/nope.py" ✔
    • non-image to show_image → "Not an image Collins can display" ✔
    • line: 0 → "'line' must be at least 1" ✔
    • missing path → "Missing required argument: path" ✔

Per the spec, v1 pre-allows nothing — both tools go through the CLI's normal permission prompts. Next up is PR 5, notify_user.

🤖 Generated with Claude Code

https://claude.ai/code/session_016dyuyFPinBYmRVsRUHYRTQ

PR 4 of the session-mcp-tools spec: the open_in_editor and show_image
handlers land behind the socket service PR 209 wired up.

- mcptools.TOOLS grows the two schemas (path required; open_in_editor
  takes an optional 1-based line). Validation is the existing table-driven
  checker; line 0, wrong types, and stray arguments are rejected app-side.
- app.py adds the handlers plus _mcp_resolve_file: relative paths resolve
  against the agent's cwd first, then the project root — the same order
  clickable file references use. open_in_editor requires the file inside
  the session's project (can_open_in_editor); show_image shows any
  readable image, with the lightbox's Open in Editor button gated on
  project membership, exactly like a clicked image reference.
- window.py promotes _open_in_tab_editor to open_in_tab_editor: the tools
  must land in the calling session's own tab, never switch tabs the way
  the window-level picker may.

E2E-verified headlessly: a throwaway instance drove both tools through
the real socket (hello + call frames); the editor opened widget.py at
line 12 and the lightbox presented a staged render, and all five negative
paths (outside-project, missing file, non-image, line 0, missing path)
returned their clean errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dyuyFPinBYmRVsRUHYRTQ
@ghackett

ghackett commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

@claude review

@ghackett
ghackett marked this pull request as ready for review August 8, 2026 18:11
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @ghackett's task in 2m 0s —— View job


Review complete ✅

  • Gather context (diff, related files)
  • Review mcptools.py schema additions
  • Review app.py handlers (open_in_editor, show_image, _mcp_resolve_file)
  • Review window.py change (open_in_tab_editor)
  • Review test updates

This is a clean, well-scoped PR. A few notes:

Correctness

  • _mcp_resolve_file (collins/app.py:892-908) is a faithful mirror of resolve_file_reference (collins/linkpatterns.py:112) / terminal._reference_roots — same root order (agent cwd, then editor_root), same expanduser/isabs handling. Good consistency with the existing clickable-reference behavior.
  • open_in_editor's project check via tab.can_open_in_editor (collins/terminal.py:2800) goes through editorfiles.is_inside, which resolves symlinks before comparing — so the "must be inside the project" guard can't be defeated by a symlink pointing outside. Good.
  • show_image correctly skips the project guard for display (matches terminal._present_image's "any readable image, inside the project or not") and only gates the lightbox's "Open in Editor" button on can_open_in_editor. Consistent with the click-driven recipe.
  • The window.py rename from _open_in_tab_editor to public open_in_tab_editor is applied at both existing call sites (_on_open_in_editor's quick-open callback and _open_in_editor's tail call) — no stale references to the old private name remain anywhere in the tree.
  • line - 1 in _mcp_open_in_editor (collins/app.py:906) correctly converts the wire's 1-based line to the editor's 0-based cursor; validated line: 0 is rejected by the schema (minimum: 1) before the handler ever sees it, so there's no risk of a -1 index slipping through.
  • Validation-before-identity ordering in run_tool_call is preserved (bad args fail identically regardless of caller), and the new tools plug into the existing handlers dict/dispatch skeleton without touching it — small, additive diff.

Nits (non-blocking)

  • _mcp_open_in_editor returns "This session's tab has no editor" when tab.editor_root is None, which reads slightly differently from the analogous "no editor" cases elsewhere, but it's clear and there's no established string to match, so this is fine as-is.
  • _mcp_resolve_file's docstring is a @staticmethod doc rather than inline — a matter of taste, not an issue.

Testing

  • Schema/validation coverage for both new tools (empty/missing path, non-integer/0/True line, unexpected line on show_image) looks thorough and matches the table-driven validator's actual branches.
  • I wasn't able to execute the test suite myself in this sandbox (no permission to run pytest), so I verified correctness by static reading against the referenced sibling implementations (terminal._open_file_reference, _present_image, linkpatterns.resolve_file_reference) rather than by running the 75 tests mentioned in the PR description — the code and tests read as consistent with each other.

No changes requested — looks ready to merge as described.

@ghackett
ghackett merged commit e250dab into main Aug 8, 2026
2 checks passed
@ghackett
ghackett deleted the session-mcp-pr4-editor-and-lightbox branch August 8, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant