Environment
- OfficeCLI
v1.0.144
- Windows 11 x64
- MCP over stdio
- Python 3.x used only as the minimal JSON-RPC client below
Summary
After one successful batch --input call through officecli mcp, the next tools/call can disappear from the MCP server's main reader and never receive a response.
The same recipe completes in under one second when run through the standalone CLI, and one batch in a fresh MCP process also completes in under one second. The failure appears when two explicit-input batch calls are sent sequentially through the same MCP process.
Minimal reproduction
Save and run this script with officecli available on PATH:
import json
import os
import queue
import shutil
import subprocess
import tempfile
import threading
import time
exe = shutil.which("officecli")
assert exe, "officecli is not on PATH"
root = tempfile.mkdtemp(prefix="officecli-mcp-two-batch-")
deck = os.path.join(root, "deck.pptx")
theme = os.path.join(root, "theme.json")
page = os.path.join(root, "page.json")
with open(theme, "w", encoding="utf-8") as f:
json.dump([
{"command": "set", "path": "/theme", "props": {"accent1": "1D42D8", "lt1": "FFFFFF"}},
{"command": "set", "path": "/slidemaster[1]", "props": {"background": "lt1"}},
], f)
with open(page, "w", encoding="utf-8") as f:
json.dump([
{"command": "add", "parent": "/", "type": "slide", "props": {"layout": "blank"}},
{"command": "add", "parent": "/slide[1]", "type": "textbox", "props": {"text": "Hello"}},
], f)
proc = subprocess.Popen(
[exe, "mcp"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
bufsize=1,
)
responses = queue.Queue()
threading.Thread(
target=lambda: [responses.put(line.rstrip("\r\n")) for line in proc.stdout],
daemon=True,
).start()
def call(request_id, method, params, timeout=5):
request = {"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}
started = time.monotonic()
proc.stdin.write(json.dumps(request, separators=(",", ":")) + "\n")
proc.stdin.flush()
try:
response = json.loads(responses.get(timeout=timeout))
print(request_id, round(time.monotonic() - started, 3), response.get("id"))
return response
except queue.Empty:
print(request_id, "TIMEOUT")
return None
try:
call(1, "initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "repro", "version": "1"},
})
call(2, "tools/call", {"name": "officecli", "arguments": {"command": ["create", deck, "--json"]}})
call(3, "tools/call", {"name": "officecli", "arguments": {"command": ["batch", deck, "--input", theme, "--json"]}})
call(4, "tools/call", {"name": "officecli", "arguments": {"command": ["batch", deck, "--input", page, "--json"]}}, timeout=10)
finally:
proc.terminate()
try:
proc.wait(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
Observed on v1.0.144: requests 1-3 return quickly; request 4 times out without a JSON-RPC response. Running the page batch directly as a standalone CLI command completes in about one second.
Likely cause
McpServer.RunAsync owns a StreamReader over standard input for JSON-RPC. CommandBuilder.Batch.cs also evaluates redirected stdin by starting:
Task.Run(() => StdIn.Peek())
This happens before the OFFICECLI_BATCH_ALLOW_STDIN_REDIRECT warning suppression is checked, including when --input or --commands already provides the complete payload.
When the 50ms wait expires, the Peek task is abandoned but not cancelled. It remains blocked on the same process stdin used by the MCP JSON-RPC reader and can consume or buffer the next request line. The MCP main loop then never sees that tools/call.
Expected behavior
- Explicit
--input or --commands must not inspect or read stdin.
- In MCP mode, stdin must be reserved exclusively for JSON-RPC.
batch <file> without an explicit input source should fail immediately with actionable guidance instead of reading the MCP transport.
- Two sequential batch calls must each return the response carrying their own request ID.
Suggested regression coverage
- Start a real
officecli mcp process.
- Send
create, an explicit-input theme batch, and a second explicit-input page batch.
- Assert both batch responses arrive within a bounded interval and preserve request IDs.
- Send an MCP batch without
--input/--commands; assert it fails immediately and that the following ping or tools/call still succeeds.
- Preserve ordinary standalone
officecli batch file < recipe.json behavior.
Environment
v1.0.144Summary
After one successful
batch --inputcall throughofficecli mcp, the nexttools/callcan disappear from the MCP server's main reader and never receive a response.The same recipe completes in under one second when run through the standalone CLI, and one batch in a fresh MCP process also completes in under one second. The failure appears when two explicit-input batch calls are sent sequentially through the same MCP process.
Minimal reproduction
Save and run this script with
officecliavailable onPATH:Observed on
v1.0.144: requests 1-3 return quickly; request 4 times out without a JSON-RPC response. Running the page batch directly as a standalone CLI command completes in about one second.Likely cause
McpServer.RunAsyncowns aStreamReaderover standard input for JSON-RPC.CommandBuilder.Batch.csalso evaluates redirected stdin by starting:This happens before the
OFFICECLI_BATCH_ALLOW_STDIN_REDIRECTwarning suppression is checked, including when--inputor--commandsalready provides the complete payload.When the 50ms wait expires, the Peek task is abandoned but not cancelled. It remains blocked on the same process stdin used by the MCP JSON-RPC reader and can consume or buffer the next request line. The MCP main loop then never sees that
tools/call.Expected behavior
--inputor--commandsmust not inspect or read stdin.batch <file>without an explicit input source should fail immediately with actionable guidance instead of reading the MCP transport.Suggested regression coverage
officecli mcpprocess.create, an explicit-input theme batch, and a second explicit-input page batch.--input/--commands; assert it fails immediately and that the followingpingortools/callstill succeeds.officecli batch file < recipe.jsonbehavior.