posit-dev / posit-dev/mcp-repl

repl: multi-line input without trailing blank line leaves REPL in continuation mode across MCP calls

Open
#165 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
70
Forks
5
PR merge metrics
No merged PRs in 30d

Description

Summary

When the repl tool receives multi-line Python with indented blocks (def, for, if, class, etc.), the embedded REPL stays in continuation mode unless the input ends with a blank line (\n\n). Without that terminator, the call appears to hang, and worse, the next repl call's input gets interpreted as further lines of the unterminated block — leaking pending continuation state across MCP tool calls.

This is the natural behavior of an interactive Python REPL (it can't know if more indented lines are coming), but the repl MCP tool presents input as a single-string argument, which most agentic callers reasonably treat as one logically-complete block. The line-mode semantics aren't obvious from the tool description.

Repro

Configure posit-mcp-repl==0.2.0 in any MCP client. From an agent or scripted MCP call:

repl(input="for x in range(3):\n    print(x)")

Observed: the call doesn't complete (or completes with no output and the REPL is now stuck in continuation mode). A follow-up call like:

repl(input="print('hello')")

doesn't print hello — its input is treated as another (mis-indented) line of the prior block.

Adding a trailing blank line fixes it:

repl(input="for x in range(3):\n    print(x)\n\n")  # works

Impact on agentic use

Coding agents (Claude Code, Codex, Cursor) typically construct input as a single multi-line Python program — they don't think of themselves as typing into an interactive shell line-by-line. In our skill (Inspect AI log analysis at https://github.com/meridianlabs-ai/inspect-skills), the agent's first instinct is to send something like:

import pandas as pd
df = pd.DataFrame(...)
for col in df.columns:
    print(col)

without the trailing \n\n. The agent observes the failure, retries, eventually figures out the rule by trial and error (and may call repl_reset along the way, wiping persistent state). Wasted tool calls, lost state, confused logs.

We've documented a workaround in our skill ("always end multi-line inputs with \n\n"), but it'd be nicer to fix this at the server.

Suggested fixes (in increasing scope)

  1. Auto-append a terminator server-side. Before feeding input to the embedded REPL, append \n (or \n\n) if it doesn't already end with one. Backward-compatible — single-line inputs are unaffected.

  2. Parse and exec whole blocks. Use compile(input, '<repl>', 'exec') (Python) / equivalent in R to detect when the input is a complete logical unit and execute it as one statement, falling back to line-mode only when compile raises SyntaxError: incomplete input. Matches what code.InteractiveConsole does, but applied to the whole MCP input at once.

  3. Document the behavior loudly in the tool description. Add to the repl tool's description: "Multi-line inputs with indented blocks must end with a blank line (\n\n)." Easiest fix; doesn't change behavior but at least surfaces the rule to callers that read tool descriptions.

(1) is probably the smallest robust fix and removes the footgun entirely. (3) is the cheap stopgap.

Environment

  • posit-mcp-repl==0.2.0 (from PyPI)
  • Launched via uvx --from posit-mcp-repl==0.2.0 mcp-repl --interpreter python --sandbox workspace-write
  • Client: Claude Code (also reproduced via Codex)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the repl MCP entry point that feeds input to the embedded Python REPL, then reproduce the indented-block example with and without the trailing blank line. Trace how input is retained between calls and verify that a complete multi-line input finishes without leaving continuation state that affects the next call.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.