Skills, MCP, context engineering
MCP servers and clients
Run an MCP tool server
Learning objectives
- Run an MCP tool server
- Connect an agent client to it
- Compare to ad-hoc tool registration
Why MCP exists: tools as a service boundary
Before the Model Context Protocol (MCP), every agent host reinvented tool wiring: custom JSON schemas in Python, ad-hoc HTTP wrappers, copy-pasted OAuth flows. MCP standardizes how clients (IDEs, chat hosts, agent runtimes) discover and invoke servers that expose tools, resources, and prompts over a defined transport (stdio, SSE, or streamable HTTP).
For AI engineers, MCP is not magic — it is RPC with a catalog. The win is separation of concerns: your billing team ships an MCP server; your agent team consumes it without importing billing's Python package into the agent repo.
Core primitives:
- Tools — Model-invokable functions with JSON Schema inputs.
- Resources — Readable data URIs (files, configs, live views).
- Prompts — Templated prompt fragments the host can pull (less common in custom agents).
The agent loop you built earlier stays the same: model emits tool call → host executes → result returns as a message. MCP replaces how the host reaches the implementation.
Callout — Industry pattern: Platform teams expose internal capabilities as MCP servers behind corporate auth gateways — analogous to Uber-style agent platforms where identity and tool access are centralized, not embedded in each demo script.
Running an MCP tool server
Start with the smallest useful server: one read tool, one write tool (or write stubbed). Official SDKs exist for Python and TypeScript; the Python pattern is:
- Define tools with name, description, and
inputSchema. - Implement handlers that return structured
TextContentor JSON. - Run over stdio for local dev (simplest) or SSE when a remote host connects.
Example mental model for a get_weather tool:
- Input schema:
{ "city": string, "units": "celsius" | "fahrenheit" } - Handler: call weather API, return
{ "temp": 22, "conditions": "cloudy" } - Errors: map HTTP 404 to
{ "error": "CITY_NOT_FOUND" }— never raw stack traces to the model.
Local dev checklist:
- Server starts without agent attached (
python server.pyornpx @modelcontextprotocol/...). list_toolsreturns expected schemas.- Handlers are unit-tested without an LLM in the loop.
Keep servers stateless where possible. Session state belongs in the agent host or a durable store, not hidden globals in the MCP process.
Connecting an agent client
The client side:
- Spawn or connect to the server (stdio pipe or URL).
- Call
initializehandshake. - Fetch tool list and map MCP tools to your host's tool registry (same names the model sees).
- On model tool call, dispatch to MCP
call_tooland normalize the result into your message format.
Bridge code is thin but easy to get wrong:
- Schema drift — MCP schema must match what you register with the model API.
- Timeout alignment — MCP call timeout ≥ model tool timeout ≥ user-facing SLA.
- Process lifecycle — stdio servers die when the agent exits; reconnect logic matters for long-running hosts.
Log every MCP invocation with server_id, tool_name, latency, and error class — same as first-party tools.
MCP vs ad-hoc tool registration
| Concern | Ad-hoc in-process | MCP server |
|---|---|---|
| Deployment | Same process as agent | Separate process/service |
| Language | Must match agent | Any language with SDK |
| Versioning | Git monorepo | Server semver + contract tests |
| Auth | Shared memory | Gateway, mTLS, tokens per server |
| Testing | Import and mock | Contract tests + integration |
| Latency | Lowest | +IPC/network overhead |
Choose ad-hoc when tools are trivial and co-located (calculator, string utils). Choose MCP when:
- Multiple agents or products share the same capability.
- The capability owner is a different team or repo.
- You need sandboxing (crash the server, not the agent).
- You will swap implementations (mock server in CI, real server in staging).
Stripe-style context hydration patterns often combine MCP resources (read current account state) with tools (mutate) so the model sees fresh data without giant prompt paste.
Security and auth (lab → production path)
Course labs use open local stdio. Production adds:
- Identity propagation — Agent passes user/service token; MCP server validates scopes before handler runs.
- Allowlists — Which agents may call which servers.
- Audit — Immutable log of mutating tool calls with actor ID.
Your micro-project uses mock authz: a header or env var ACTOR_ROLE=support|admin that gates dangerous tools. That mirrors gateway checks without building full SSO.
Never put API keys inside MCP tool results returned to the model. Redact at the server boundary.
Debugging MCP integrations
Common failures:
- Empty tool list — Handshake incomplete; wrong transport; server stderr swallowed.
- Schema validation errors — Model emits extra fields; tighten
additionalProperties: falsein schema. - Hung calls — Deadlock on stdio if server prints debug logs to stdout (always log to stderr).
- Stale server — Agent caches tool list at startup; restart after server deploy or implement refresh.
Keep a direct MCP client script (CLI or Inspector) to invoke tools without the LLM. When something breaks, bisect: server alone → client bridge → full agent.
Engineering problem (staff framing)
MCP standardizes tool/resource plumbing between hosts and servers — integration contract, not magic intelligence.
Diagram — MCP client/server
sequenceDiagram
participant H as Host/agent
participant C as MCP client
participant S as MCP server
H->>C: use tool
C->>S: JSON-RPC
S-->>C: result
C-->>H: observation
Precise definitions & mental model
Servers expose tools/resources/prompts; clients mediate; auth boundaries.
Tradeoffs — when to use what
MCP portability vs custom tight integrations.
Failure modes (interview + on-call)
Overprivileged servers; trusting remote tool descriptions blindly.
Production & OSS practices
Sandbox servers; least privilege; contract tests.
Micro-project: MCP tool server + agent
In your portfolio:
- Implement an MCP server with ≥2 tools (one read, one mutating with mock authz).
- Connect your hand-rolled agent as MCP client; model must successfully call both tools in one session.
- Write
MCP_vs_local.mdcomparing this setup to in-process tools from the agent module — latency, test story, ownership. - Add one contract test: expected tool names and required schema fields.
- Capture a trace: model reasoning → MCP
call_tool→ normalized result.
Acceptance: stranger can run server + agent from README; mutating tool rejects unauthorized role.
Checklist
- MCP server runs standalone and passes handler unit tests
- Agent lists and invokes MCP tools end-to-end
- Mock authz on mutating tool documented
- Comparison doc: MCP vs ad-hoc registration
- Contract test in CI or documented local command
ShipAI delivery model is: