posit-dev / posit-dev/mcp-repl
repl: multi-line input without trailing blank line leaves REPL in continuation mode across MCP calls
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)
-
Auto-append a terminator server-side. Before feeding
inputto the embedded REPL, append\n(or\n\n) if it doesn't already end with one. Backward-compatible — single-line inputs are unaffected. -
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 whencompileraisesSyntaxError: incomplete input. Matches whatcode.InteractiveConsoledoes, but applied to the whole MCP input at once. -
Document the behavior loudly in the tool description. Add to the
repltool'sdescription: "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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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