Skip to content

Commit 2db6915

Browse files
committed
feat(agent): teach default agent to set up MCP servers
The default agent's MCP guidance only covered *using* already-connected servers and told it to never touch MCP config. Asked to add or set up a new server, the model fell back on the MCP hosts in its training data (Claude Code / Claude Desktop), cited ~/.claude.json, and refused — claiming it had "no tool to edit" the config, despite having file I/O. Extend the system prompt's MCP section so the agent knows it runs in Pythinker: config lives at ~/.pythinker/mcp.json (global) and ./.pythinker/mcp.json (project), and it can add a server via `pythinker mcp add` or by writing that JSON. Keep the honest caveat that a newly added server only connects on the next Pythinker start, and forbid citing non-Pythinker (Claude) config paths. Add a regression test asserting the prompt names the real config files and CLI, keeps the restart caveat, and steers off the Claude-host paths.
1 parent 30395a8 commit 2db6915

3 files changed

Lines changed: 29 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ GitHub Releases page; `0.8.0` is the new starting line.
2121
- **Agent phase-0 enhancements.** Adds a model-invocable cross-session Recall tool (search and read prior sessions on demand, sanitized and read-only for subagents), read-only MCP resources/prompts surfaced as tools, project-scoped `.pythinker/mcp.json` layering, subagent token/cost roll-up to the orchestrator, and truncated tool output that spills to disk with a recovery hint instead of being lost.
2222
- **No more spurious `coroutine … was never awaited` warnings.** Dropped Sentry's `AsyncioIntegration`, whose `create_task` monkeypatch wrapped every coroutine and — when a task was cancelled before its first step during turn/prompt teardown — orphaned the inner coroutine, printing `WireUISide.receive` and prompt_toolkit "never awaited" `RuntimeWarning`s to the console. The integration added no spans (tracing/profiling are off), and exception capture for async tasks is preserved by the existing asyncio exception handler.
2323
- **Read-only profile guard hardened against version-pinned interpreters.** Inline-code interpreter invocations that use a version-suffixed or absolute binary (`python3.14 -c …`, `/usr/bin/python3.12 -c …`, `node20 -e …`) are now classified as mutating/destructive just like the bare `python`/`node` forms, so they can no longer bypass a read-only subagent profile or skip destructive deliberation.
24+
- **The agent sets up MCP servers on request instead of refusing.** Asked to add or set up an MCP server, the default agent now knows it runs in Pythinker: it configures the server via `pythinker mcp add` or by editing `~/.pythinker/mcp.json` / `./.pythinker/mcp.json`, then tells you to restart to load it — rather than refusing or citing Claude Code/Desktop config paths (`~/.claude.json`) it cannot use.
2425

2526
## 0.38.0 (2026-06-08)
2627

src/pythinker_code/agents/default/system.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ When handling the user's request, if it involves creating, modifying, or running
114114

115115
MCP (Model Context Protocol) servers expose their capabilities as ordinary tools that are already connected and present in your toolset (their descriptions name the originating server). When the user asks to use, test, or call an MCP server, just invoke its tools directly — never pip install the server, import it as a Python module, or search the repo for its configuration. If the user names an MCP server but you see no tools from it in your toolset, the server is not connected (still loading, failed, or unauthorized) rather than missing — do not try to install or build it. Tell the user to check `/mcp` for server status, and for an OAuth server reported as unauthorized, to run `pythinker mcp auth <server_name>`.
116116

117+
When the user asks you to **add, install, or set up a new MCP server** (as opposed to using one that is already connected), you can and should do it — you are running in **Pythinker**, whose MCP configuration is a JSON file you have the tools to edit. This is not Claude Code or Claude Desktop, so never reference `~/.claude.json`, `claude_desktop_config.json`, or any non-Pythinker config path. Server definitions live under the `mcpServers` map in `./.pythinker/mcp.json` (project-scoped, applies to this workspace) and `~/.pythinker/mcp.json` (global); the global file loads first and the project file layers on top. Configure a server either by running `pythinker mcp add <name> …` via `Shell` (e.g. `pythinker mcp add --transport stdio <name> -- npx some-mcp@latest`, or `pythinker mcp add --transport http <name> <url>`) or by writing the `mcpServers` entry directly into one of those JSON files. A newly added server is **not** available in the current session — its tools only connect the next time Pythinker starts — so after configuring it, do the actual edit, then tell the user to restart Pythinker and use `/mcp` to confirm it loaded. Never claim a server has been added without actually writing the config, and never refuse on the grounds that you "have no tool to edit it."
118+
117119
If the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. Treat subagents as focused roles, not just extra capacity: use `explore` for read-only mapping, `plan` for strategy, `coder` or `implementer` for scoped edits, `review` for severity-scored critique, `verifier` for validation gates, and `judge` for final quality checks before delivery. The tool can either start a new instance or resume an existing one by `agent_id`. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context because a newly created subagent instance does not automatically see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it instead of creating a new instance. Default to foreground subagents. Use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes, and you do not need the result immediately to decide your next step. Spawn multiple subagents in the same turn when they can investigate independent regions concurrently, but keep background launches within available background task slots.
118120

119121
If the `RunAgents` tool is available, prefer it over repeated one-by-one `Agent` calls for bounded map-reduce work: parallel scouting, independent review plus verification, or scout/plan/implement/review batches. Keep each child prompt focused and include a shared `base_prompt` with the user goal, repository constraints, and required output format. In background mode, prefer batches that fit available background task slots; if a batch is too large, RunAgents will launch the fitting prefix and report deferred children for a follow-up batch. Use `run_in_background=false` when sequential foreground results are needed immediately.

tests/core/test_load_agent.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,32 @@ def test_system_prompt_contains_platform_info(builtin_args: BuiltinSystemPromptA
5555
assert builtin_args.PYTHINKER_SHELL in prompt
5656

5757

58+
def test_system_prompt_explains_adding_mcp_servers(builtin_args: BuiltinSystemPromptArgs):
59+
"""The agent must know it can set up a *new* MCP server itself, in Pythinker.
60+
61+
Without this, the model falls back on the MCP hosts in its training data
62+
(Claude Code / Claude Desktop), cites `~/.claude.json`, and wrongly refuses
63+
— claiming it "has no tool to edit" the config. The prompt must ground it in
64+
Pythinker's real MCP config files and the `pythinker mcp add` CLI, while
65+
keeping the honest "restart to load" caveat.
66+
"""
67+
from pythinker_code.agentspec import DEFAULT_AGENT_FILE
68+
69+
prompt = _load_system_prompt(
70+
DEFAULT_AGENT_FILE.parent / "system.md",
71+
{"ROLE_ADDITIONAL": ""},
72+
builtin_args,
73+
)
74+
75+
# Grounded in Pythinker's real config + CLI, not a host from training data.
76+
assert ".pythinker/mcp.json" in prompt
77+
assert "pythinker mcp add" in prompt
78+
# The honest caveat survives: a new server loads on restart, not mid-session.
79+
assert "restart" in prompt.lower()
80+
# Explicitly steers off the Claude-host hallucination seen in the wild.
81+
assert "not Claude Code or Claude Desktop" in prompt
82+
83+
5884
def test_system_prompt_treats_injected_date_as_authoritative(
5985
builtin_args: BuiltinSystemPromptArgs,
6086
):

0 commit comments

Comments
 (0)