- Posted on
- • Artificial Intelligence
Common MCP Mistakes
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Common MCP Mistakes: 5 Pitfalls Linux Devs Make (and How to Fix Them)
If your MCP server “runs” but your client can’t see any tools, responses time out, or you keep tripping “method not found,” you’re not alone. A handful of repeatable mistakes cause most MCP headaches—and they’re fixable with a few disciplined checks from the shell.
This post explains what goes wrong, why it matters, and how to verify and fix issues quickly using familiar Linux tools. You’ll walk away with concrete steps and copy‑paste checks you can add to your workflow today.
Note: In this article, MCP refers to the Model Context Protocol—an emerging standard for LLM clients to talk to tool and retrieval servers over stdio or WebSocket using JSON‑RPC.
Quick prerequisites (CLI you’ll actually use)
We’ll validate JSON, poke servers, and do quick environment checks with curl and jq. If you also build MCP servers in Node or Python, install those too.
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq nodejs npm python3 python3-pip
- Fedora/RHEL (dnf):
sudo dnf install -y curl jq nodejs npm python3 python3-pip
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq nodejs npm python3 python3-pip
Why these mistakes matter
MCP clients are strict: they derive everything—what tools exist, how to call them, what resources are available—from your initialize handshake and the JSON Schemas you return. A single malformed field, stray log on stdout (stdio transport), or missing dependency can render your server invisible or flaky. The good news: you can catch nearly all of it locally with a few shell checks.
Mistake 1: Printing logs to stdout (stdio transport)
Symptom: The client can’t parse your messages, or you see “Invalid JSON”/framing errors out of nowhere.
Cause: When using stdio transport, stdout is reserved for JSON‑RPC. Any logging belongs on stderr. Even one debug print to stdout will corrupt the stream.
Fix:
- In Bash entrypoints, redirect all logging to stderr.
#!/usr/bin/env bash
set -euo pipefail
# Send all echo/printf output to stderr by default
exec 1>&2
echo "[info] Starting MCP server..."
# Your server binary should write machine JSON to stdout; don't echo there.
your_mcp_server_bin "$@"
- In Python, set logging to stderr explicitly.
import logging, sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
- In Node.js, use console.error for logs; reserve process.stdout for protocol writes.
console.error("starting...");
process.stdout.write(JSON.stringify(msg) + "\n"); // protocol only
Quick check: run your server and ensure no human logs appear on stdout.
your_mcp_server 1>protocol.out 2>logs.err
# protocol.out should contain only JSON-RPC lines
head -n1 protocol.out | jq .
Mistake 2: Bad initialize response or capabilities
Symptom: The client connects but shows no tools/resources, or calls fail due to “capability not supported.”
Cause: The initialize response must advertise accurate capabilities, tools, and resources. Missing fields, wrong shapes, and empty URIs are common.
Fix: Validate your initialize payload structure locally with jq. Here’s a minimal shape to sanity‑check (your fields will vary by implementation):
Expected fragments you should be returning (conceptual example):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"capabilities": {
"tools": [
{
"name": "grep_file",
"description": "Search for a pattern in a file",
"inputSchema": {
"type": "object",
"properties": {
"pattern": { "type": "string" },
"path": { "type": "string" }
},
"required": ["pattern", "path"],
"additionalProperties": false
}
}
],
"resources": [
{
"uriTemplate": "file://{path}",
"name": "Local file access",
"description": "Read-only file resources"
}
]
}
}
}
Smoke test your initialize once:
# Suppose you captured an initialize response to init.json
jq -e '
.jsonrpc == "2.0" and
.result.capabilities.tools | type == "array" and
(.result.capabilities.tools[] | has("name") and has("inputSchema"))
' init.json >/dev/null && echo "initialize looks plausible"
Common gotchas:
Missing inputSchema.required means clients may pass nulls or fail validation.
Misnaming fields (e.g., “inputsSchema” vs “inputSchema”).
Advertising resources with uriTemplate you don’t actually handle.
Mistake 3: JSON Schema mismatches between declaration and implementation
Symptom: Client refuses to call your tool, or sends arguments your code doesn’t expect.
Cause: The tool’s JSON Schema is the contract. If you say the parameter is “path” but your code expects “file,” the call fails or silently behaves wrong.
Broken:
# initialize advertises:
"inputSchema": {
"type": "object",
"properties": {
"pattern": { "type": "string" },
"path": { "type": "string" }
},
"required": ["pattern", "path"]
}
# implementation (Python) expects "file" instead of "path"
def grep_file(pattern: str, file: str): ...
Fixed either by updating the schema:
"properties": {
"pattern": { "type": "string" },
"file": { "type": "string" }
},
"required": ["pattern", "file"]
Or by adapting your implementation to accept the schema’s keys.
Tip: Write a tiny local contract test that calls your handler with exactly the schema you advertise.
echo '{"pattern":"todo","path":"README.md"}' | jq -r '
. as $args |
if (has("pattern") and has("path")) then "OK" else "BAD" end
'
Mistake 4: Long‑running tools without progress or timeouts
Symptom: The client drops the call, or users think nothing is happening.
Cause: Some MCP clients assume timely responses or expect streaming/progress signals. If your tool shells out to a multi‑minute task, either stream partial results or emit periodic progress to keep the client engaged (per your client’s conventions).
Fix ideas:
Chunk output where supported (e.g., return batched lines).
If the client supports notifications or progress, emit “still working…” at intervals.
For truly long jobs, return a “ticket”/resource URI quickly, then let the client poll or fetch the final artifact.
Bash pattern to avoid buffering delays:
# Produce output line-by-line
stdbuf -oL your_long_task | while IFS= read -r line; do
# wrap 'line' in your JSON-RPC stream message
printf '%s\n' "$line"
done
Also set explicit timeouts and default limits so runaway jobs don’t wedge the session.
Mistake 5: PATH, missing dependencies, and unsafe permissions
Symptom: Works on your laptop, breaks inside containers/CI or a different distro.
Causes:
Your tool shells out to curl, jq, or grep that isn’t installed.
PATH differences between login shells and service environments.
Overbroad filesystem access or unreadable target files.
Fix: Add a “preflight” in your server startup that validates environment and bails with a clear error (to stderr).
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "[error] missing $1" >&2; exit 127; }; }
need_cmd curl
need_cmd jq
# Principle of least privilege: check read access if you expect to touch files
test -r "/some/dir" || { echo "[error] cannot read /some/dir" >&2; exit 1; }
# Normalize a safe PATH
export PATH="/usr/local/bin:/usr/bin:/bin"
If you’re missing tools, install them:
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq
- Fedora/RHEL (dnf):
sudo dnf install -y curl jq
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq
Security notes:
Don’t pass unvalidated user strings into the shell; avoid
sh -c "$user_input".Prefer whitelisting flags/paths and escaping arguments.
Keep read-only defaults; only elevate when absolutely required.
A quick Bash smoke test you can adapt
Use this to sanity‑check any JSON‑RPC snippet your server outputs (or to validate captured traffic):
# Validate that each line is valid JSON-RPC 2.0 with an id or method
while IFS= read -r line; do
if ! printf '%s' "$line" | jq -e '
.jsonrpc=="2.0" and ((has("id") and has("result") or has("error")) or has("method"))
' >/dev/null; then
echo "[bad] not valid JSON-RPC: $line" >&2
exit 1
fi
done < protocol.out
echo "[ok] JSON-RPC frames look plausible"
And to inspect your initialize’s advertised tools quickly:
jq -r '.result.capabilities.tools[]?.name' init.json
Conclusion and next steps
Most MCP instability comes down to five areas: stdout vs stderr, a bad initialize, schema drift, long‑running calls without feedback, and environment gaps. The fixes are straightforward once you make them part of your routine.
Your next steps:
Add the preflight and JSON checks above to your dev script or CI.
Validate your initialize and tool schemas with jq before you ship.
Standardize logging to stderr and reserve stdout for protocol.
If you build in Node/Python, keep curl and jq handy for shell‑level introspection:
- apt:
sudo apt install -y curl jq - dnf:
sudo dnf install -y curl jq - zypper:
sudo zypper install -y curl jq
- apt:
If you want a checklist version of this post or a tiny init/schema validator script, tell me what client and transport you’re using (stdio or WebSocket), and I’ll tailor one you can drop into your repo.