Treat Your MCP Server as a Tool Boundary (stdio, schemas, and safe local calls)
Attributed Chinese → English compile
Source: MCP Server 实战:从协议到本地工具调用
Original author: AJie (AJie's Blog) · Published: 2026-06-03
This is a faithful English rewrite for learning. It is not original research by the compiler. Always link the Chinese original; do not present this compile as the source work.
Most first MCP tutorials jump straight to “register a tool, wire stdio, celebrate the demo.” AJie’s post is useful because it starts one layer earlier: an MCP Server is a capability boundary, not a dumping ground for whatever the model might want to run on your machine.
If you are wiring agents, MCP, or RAG into a product, that framing matters. Models do not “use your laptop”; they generate call intents against schemas. Your job is to expose narrow, typed, recoverable actions—and keep stdout clean so the JSON-RPC pipe does not die under log noise. Over-broad tools and silent protocol corruption show up as “the agent is flaky,” when the real failure is the tool surface.
What a good tool boundary looks like
| Dimension | Healthy MCP tool | Fragile MCP tool |
|---|---|---|
| Input | Explicit fields, types, constraints | Free-form natural language for the tool to “figure out” |
| Output | Stable structure the model can continue from | Raw logs / untyped blobs |
| Scope | One action (or one family) | “Do anything” shell god-mode |
| Failure | Explainable error the model can act on | Opaque stack traces |
Instead of exposing a generic shell, split a local project-assistant into narrow tools:
| Tool | Input | Output | Why |
|---|---|---|---|
list_project_files |
root, file types | path list | Scope the workspace |
read_project_file |
relative path, line range | snippet | Pull only needed context |
summarize_markdown_posts |
directory, limit | title / date / summary table | Inventory content |
find_internal_links |
path | in-page links | Structure checks |
check_frontmatter |
path | missing / bad fields | Pre-publish lint |
Less flexible than bash. Much more operable in production: no accidental deletes, and outputs stay consumable across turns.
Design I/O so the model guesses less
Tool descriptions are routing context for the model, not human README fluff.
Weak:
Read a file from local project.
Stronger (paraphrased from the source’s guidance):
Read a text file under the configured project root. Use when you need a specific source, markdown, or config file. Path must be relative to project root. Optional
start/limitrestrict the returned line range.
Suggested parameters for read_project_file:
Truncation and pagination beat dumping tens of thousands of lines into the context window. MCP Servers should default to summaries, slices, or pages.
The stdio call chain (local MCP’s default path)
Local MCP almost always means:
- The user configures a launch command in the host (Claude Desktop, Claude Code, Cursor, or your own host).
- The client starts a local server process.
- The server waits on stdio for initialize.
- The client lists tools.
- The model decides whether to call.
- The client forwards
tools/call. - The server runs the local capability and returns JSON-RPC over stdout.
Troubleshooting map (from the source)
| Symptom | Likely cause | First check |
|---|---|---|
| Client sees no tools | Process fail / bad path / init fail | Run the launch command alone |
| Tools listed, call fails | Schema mismatch / tool exception | Log args + structured error |
| Hang after call | No response / stdout polluted by logs | Separate stdout vs stderr |
| Works in terminal, fails in host | Env / cwd / permissions differ | Diff the client launch environment |
| Flaky | External cmds / file state | Boundary checks + clear errors |
Hard rule for stdio: never write casual debug logs to stdout. Stdout is the protocol channel. Use stderr or a log file.
Official references the source## Ship a minimum closed loop before real tools
AJie recommends a tiny ping_project tool first: accept a string; return project name, cwd, and the echoed input. You are not proving business value—you are proving:
- The client can spawn the server
- Initialize completes
- The tool appears in the list
- Args round-trip
- The response renders in the host
- stderr does not corrupt the protocol
Then add one real tool at a time and call it from the host after each addition. When something breaks, the last delta is obvious.
Many “works locally, fails in Claude Code” issues are path, env, or cwd mismatches—not SDK bugs.
Safety tiers before you expose writes
| Tier | Examples | Default policy |
|---|---|---|
| Read-only | List dir, read snippet, status query | Ship first |
| Constrained write | Drafts /## Debug order that actually works |
- Run the server start command alone.
- Verify initialize with the ping tool.
- Capture model-supplied args versus the schema.
- Cap output length.
- Return structured errors (
ok,error,message,path,hint). - Only then attach real files, APIs, or commands.
Example failure shape (paraphrased): instead of bare ENOENT, return file_not_found plus a hint to call list_project_files first. That turns a retry loop into a recovery path.
Rollout checklist for a real project
- Pick one narrow scenario (one sentence).
- Split two or three read-only tools with clear I/O.
- Prove the stdio minimum loop in the target host.
- Connect real data with truncated, structured returns.
- Add recoverable error objects.
- Guard context budget (paginate or summarize).
- Only then consider writes with directory and confirmation gates.
Why this matters if you are shipping agents / MCP into a product
Product agents fail less often on “model IQ” than on tool surface area: over-broad tools, polluted stdio, and unrecoverable errors. MCP gives you a standard discovery and call pipe; you still have to design the boundary as carefully as a public API. Pair this with RAG the same way—retrieve evidence as structured resources, execute side effects as narrow tools, and keep both behind server-side policy. That is the difference between a demo that works once and a capability you can operate.
Compiler (not original author)
English compile by YongBo Yu.
Site: https://yongbo-yu.vercel.app · GitHub: https://github.com/YongBoYu1
Original post remains © AJie / source site. Read the Chinese original before publishing any derivative. reports in a sandbox dir | Allowlisted paths + types |
| High risk | Delete, arbitrary shell, push, publish | Off by default or human confirm |
Do not accept arbitrary absolute paths. Do not ship “run any shell command” as a general tool. Prefer allowlisted roots such as reports/ or drafts/.
points at:
- Model Context Protocol docs
- MCP TypeScript SDK
Host docs for your target client (Claude Code, Cursor, etc.)
path(string): relative to project rootstart(number): start linelimit(number): max lines
Suggested structured return:
-
path,start,end,content,truncated
| Safety | Path / command / network limits | Default-open to the whole host |
AJie’s practical rule: if the model must understand intent and the tool should do deterministic work, make an MCP tool. If the work itself still needs open-ended judgment (“rewrite this article”), leave that to the model and only give it evidence tools.
Top comments (0)