aiArch

Workflow · last verified 2026-07-13

The MCP integration workflow: model capabilities, test them, ship them

What this workflow is

This is the practice of integrating capabilities into agents via MCP — deciding what deserves to be a server, designing tools agents can actually use, testing and evaluating them, wiring the clients that matter, and keeping the whole surface current. It is part of the Engineering Workflow Library: one prescribed way of working, with real commands, the artifacts each step produces, and a dated changelog.

This page is the workflow. For what MCP is and a from-scratch build tutorial, see how to build an MCP server; for when MCP beats a plain API integration, see MCP vs API. Nothing from either is duplicated here.

The job this workflow does

MCP makes it easy to expose a capability and easy to expose it badly: a server that mirrors your REST API one endpoint per tool, floods the context window with raw rows, was tested only by clicking around, and pins nothing — so the Python SDK's v2 rewrite or the next spec revision breaks it in production. The gap between "it responds in the Inspector" and "agents reliably complete tasks with it" is design and verification discipline, not more tools.

This workflow closes that gap in eight steps, in order. Each produces an artifact you keep: a boundary decision, a pinned scaffold, agent-optimized tool definitions, a CI test script, an eval task set, client config, a deployed remote, a version record. We run this ourselves — aiArch's coach agent calls the public aws-knowledge and cloudflare-docs MCP servers live in production (src/lib/docsMcp.ts), so the client-side half of this page is practiced, not theorized.

It prescribes one good way. Where the ecosystem offers three options, this page states the correct default and moves on.

The workflow

1. Decide the boundary: one capability, one server

Before scaffolding, decide two things. First, the boundary: one server per capability (billing, docs search, deploy control) — not one server per database table, and not one mega-server for everything. A server is a unit of trust, deployment, and versioning; scope it like one.

Second, whether this needs to be an MCP server at all. The decision comes down to who consumes it:

  • Multiple clients or teams will use it (Claude Code, the API, other agents) → an MCP server. The protocol is the point: write once, every MCP client can call it.
  • One app you own, tools only, no subprocess wanted → in-process plain tools (the Agent SDK's tool() + createSdkMcpServer() — MCP-shaped, no transport). Also the only path if you need prompts, resources, or tight control over execution.
  • A remote URL server your API-side agent should call, tools only → don't build anything; wire the API's MCP connector (step 6).

If nobody outside one process will ever call it, skip this workflow and define plain tools — a transport nobody crosses is pure overhead.

2. Scaffold and pin — exactly

Prescription: Python with FastMCP from the official SDK. Typed function signatures become JSON Schema automatically, and a working server is a dozen lines. (The TypeScript SDK — @modelcontextprotocol/sdk 1.29.0, McpServer + registerTool — mirrors everything below; use it if your stack is TS.)

uv init billing-mcp && cd billing-mcp
uv add "mcp[cli]==1.28.1"   # exact pin — see the v2 warning below
# server.py — the smallest real server
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("billing")

@mcp.tool()
def billing_invoice_status(invoice_id: str) -> str:
    """Fetch one invoice's status, amount, and last error. Returns a compact summary."""
    return lookup_summary(invoice_id)

if __name__ == "__main__":
    mcp.run(transport="stdio")   # "streamable-http" when you ship remote (step 7)

Pin the exact version. The Python SDK's v2 rewrite is in beta (2.0.0b1) and renames FastMCP to MCPServer — an unpinned mcp dependency will eventually resolve into a breaking rewrite. ==1.28.1, not >=. Same discipline on the TS side: 1.29.0 in the lockfile, minors reviewed.

If you work in Claude Code, don't hand-scaffold at all: /plugin install mcp-server-dev@claude-plugins-official, then /mcp-server-dev:build-mcp-server generates the project for you.

3. Design tools for agents, not APIs

This is the step that decides whether the server works, and it is where API instincts mislead. An agent is not a REST client: every token a tool returns is context the model re-reads on every subsequent turn, and every extra tool is a choice the model can get wrong. Anthropic's tool-writing guidance boils down to four rules:

  • Namespace by service and resourceasana_projects_search, not search. With dozens of tools mounted from multiple servers, unprefixed names collide in the model's head. (The 2025-11-25 spec adds first-party tool-naming guidance, SEP-986.)
  • Consolidate workflows into fewer, higher-level tools. One schedule_event that finds availability and books beats list_users + list_events + create_event chained by the model — fewer round-trips, fewer wrong turns.
  • Return compact, high-signal results. Name over UUID, summary over row. Give heavy tools a response_format: "concise" | "detailed" enum — in Anthropic's testing, concise-by-default cut token consumption by roughly two-thirds. Paginate and truncate with guidance in the response ("50 of 1,200 results — filter by date to narrow"), never dump.
  • Unambiguous parameter names. user_id, not user (id? name? email?). The model fills these blind; ambiguity becomes malformed calls.

Descriptions are prompts — the model chooses and invokes tools by reading them. Anthropic's evals found "small refinements to tool descriptions can yield dramatic improvements," which makes descriptions the highest-leverage lines in the repo. Two hard budgets to design under: Claude Code truncates tool descriptions at 2KB, and warns at 10k output tokens with a 25k default cap (MAX_MCP_OUTPUT_TOKENS). A tool that routinely returns more than that is a design bug, not a limit problem.

4. Test in the Inspector, then in a real client

Two test layers, in order. The Inspector for the protocol surface — do tools list, do schemas parse, do calls return what you designed:

uv run mcp dev server.py                 # Python: Inspector UI at :6274
npx @modelcontextprotocol/inspector node build/index.js   # TS equivalent

# CI: the same Inspector, scripted — fails the build on a broken tool surface
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list

Then a real client, because the Inspector can't tell you whether a model can use the tools. Mount it in Claude Code (local scope — your machine only, no team footprint yet):

claude mcp add billing -- uv run python server.py
# then in a session: ask for something the tools should handle, watch which get called

Prototype against Claude Code deliberately — it is the fastest loop for watching an agent misread a description, pick the wrong tool, or drown in a verbose result. Every confusion you see here is a description or schema fix, and it's ten times cheaper to find now than in step 5's transcripts.

5. Eval the tool surface

Manual poking finds the obvious failures; evals find the rest. Anthropic's methodology from writing tools for agents, condensed to its loop:

  • Generate realistic multi-tool tasks from real workflows — "find the customer's failed invoice and summarize why it failed," not "call billing_invoice_status once."
  • Run them through a simple agentic loop against your server.
  • Measure runtime, tool-call count, token consumption, and error rate — a correct answer via nine calls and 40k tokens is still a failing design.
  • Read the transcripts. The metrics say something is wrong; the transcripts say what — a misleading description, an ambiguous parameter, a result the model can't parse.
  • Hold out a test set. You will iterate descriptions against the eval tasks; unheld-out tasks measure your memorization of them, not the design.

Fix the surface, re-run, repeat. The eval task set is a permanent artifact: it re-gates every description tweak, SDK bump, and spec migration from here on. This is the step most MCP servers skip, and the reason most MCP servers get quietly unmounted.

6. Wire the clients that matter

Three client surfaces, one correct config for each:

The team, in Claude Code — project scope. Commit .mcp.json to the repo; teammates get prompted to approve on first use (reset with claude mcp reset-project-choices). Secrets ride ${VAR} expansion, never literals:

{
  "mcpServers": {
    "billing": {
      "type": "http",
      "url": "https://billing-mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer ${BILLING_MCP_TOKEN}" }
    }
  }
}

Ad-hoc remote servers: claude mcp add --transport http <name> <url> (the sse transport is deprecated — don't add new ones); OAuth-protected servers authenticate via /mcp in-session or claude mcp login <name>.

Server-side API callers — the MCP connector: an mcp_servers array on the Messages request with beta header anthropic-beta: mcp-client-2025-11-20. URL servers only, tools only — no stdio, no prompts/resources. The API platform calls your remote server for you; no client code.

Your own Agent SDK app — in-process, as decided in step 1: tool() + createSdkMcpServer(), tools surfacing as mcp__{server}__{tool} in allowedTools. Same tool-design rules from step 3 apply verbatim.

Context cost at the client is part of your design surface: Claude Code's tool search defers full tool schemas by default, loading them on demand — which helps a large mounted surface, but doesn't excuse one. Fewer, better tools still win.

7. Ship remote when there's more than one consumer

A stdio server on one laptop serves one person. The moment a second consumer appears — a teammate, the API connector, another agent — ship it as a remote server on Streamable HTTP: a single /mcp endpoint accepting POST and GET, replying JSON or SSE per request. (The old HTTP+SSE dual-endpoint transport is deprecated — don't build new servers on it.) Our prescription is Cloudflare Workers, the same platform aiArch itself runs on:

npm create cloudflare@latest -- billing-mcp --template=cloudflare/ai/demos/remote-mcp-authless
# stateless tools → createMcpHandler(); per-session state → McpAgent (Durable Objects)
npx wrangler deploy

Remote means the security requirements stop being optional. From the spec and its security-best-practices page, four are non-negotiable:

  • OAuth 2.1 for HTTP transports: PKCE (S256) required, RFC 9728 protected-resource metadata required, and the RFC 8707 resource parameter required so tokens are audience-bound to your server. The 2025-11-25 revision adds CIMD — an HTTPS URL as client_id — as the recommended client registration path. On Cloudflare, the KV-backed OAuthProvider in the agents framework implements the server side. (stdio servers skip all this — credentials come from the environment.)
  • Validate the Origin header on every request — the DNS-rebinding defense. Local development servers bind to localhost, never 0.0.0.0.
  • Token passthrough is forbidden. Your server accepts only tokens issued for it (audience validation) and never forwards a client's token upstream — that's the confused-deputy hole.
  • Sessions are not authentication. MCP-Session-Id is optional state, generated non-deterministically; authorize every request on its token, never its session id.

And carry one asymmetry into your threat model: tool results and descriptions from servers you mount are untrusted input to the model unless you trust the server — prompt injection rides in on both. Mount third-party servers with the same care you'd give a dependency with shell access.

8. Version it and keep it current

MCP versions by date — the current spec revision is 2025-11-25 — and clients and servers negotiate it: on HTTP transports, requests after initialization carry the MCP-Protocol-Version header. Record in the repo which revision you built against (this server: 2025-11-25, which also made JSON Schema 2020-12 the default dialect and added experimental long-running tasks).

The maintenance loop, on a calendar rather than an incident:

# quarterly, or on any SDK/spec release announcement
uv lock --check                    # still pinned to what you verified?
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
# re-run the step-5 eval set before adopting any SDK/spec bump

Discovery: the official registry (registry.modelcontextprotocol.io) is live but in preview, not GA — the API is frozen at v0.1 and data can change. When your server is public, publish with the mcp-publisher CLI and a server.json (namespace auth via GitHub OAuth/OIDC or DNS), but treat registry presence as forward-looking, not a distribution channel yet. And when a server's tool count grows past what fits a context window comfortably, the scale path is code execution over MCP — Anthropic's engineering write-up covers it; it is out of scope here.

Artifacts this workflow produces

  • The server repo — SDK pinned exactly (mcp==1.28.1 / TS 1.29.0), spec revision recorded.
  • Tool definitions — namespaced names, agent-optimized descriptions, compact result contracts with response_format where results run heavy.
  • The Inspector CLI script--cli invocations in CI that fail the build on a broken tool surface.
  • The .mcp.json entry — project-scoped, committed, secrets via ${VAR}.
  • The eval task set — realistic multi-tool tasks with a held-out split, re-run before every change.
  • server.json — registry metadata, ready for when the registry reaches GA.

Pitfalls

  • Mirroring your REST API one endpoint per tool. Agents aren't API clients — consolidate workflows into fewer higher-level tools, or watch the model chain five calls to do one job and get one wrong.
  • Unpinned Python SDK. v2 is in beta and renames FastMCP to MCPServer. A >= constraint is a scheduled outage; pin ==1.28.1 and review bumps.
  • Verbose tool results. Raw rows and full objects burn the context window on every subsequent turn — and hit Claude Code's 25k output-token cap. Compact by design, not by truncation.
  • Inspector-only testing. The Inspector proves the protocol works, not that a model can use the tools. Steps 4b and 5 exist because those are different claims.
  • Building on the deprecated HTTP+SSE transport. New remote servers are Streamable HTTP — one /mcp endpoint. claude mcp add --transport sse is likewise deprecated.
  • Skipping Origin validation. That's the DNS-rebinding defense; a local server without it is reachable from any web page the developer visits.
  • Passing client tokens upstream. Explicitly forbidden by the spec's security guidance — audience-validate, mint your own downstream credentials.
  • Treating sessions as identity. MCP-Session-Id is hijackable state, not authentication. Every request authorizes on its token.
  • Trusting third-party tool descriptions. Descriptions and results are prompt-injection carriers; a malicious server you mount is inside the model's context.

When not to use this workflow

Skip MCP entirely when one application you own is the only consumer and tools are all you need — in-process plain tools (step 1's second branch) give you the same tool-design discipline with no transport, no auth surface, and no deployment. The protocol earns its overhead exactly when the capability outlives a single process: multiple clients, multiple teams, or a public surface.

Skip the build half when the server already exists — for a remote URL server you just consume, this workflow reduces to steps 6 and 8: wire it, and re-verify it on a calendar. And if what you actually need to decide is whether MCP beats a direct API integration at all, that decision framework lives in MCP vs API, not here.

Changelog

  • 2026-07-13 — initial version, verified against MCP spec 2025-11-25, TS SDK 1.29.0, Python SDK 1.28.1.
Sources & provenance

The Python SDK v1/v2 split and the API connector's beta header are the facts here most likely to drift — re-verify both before adopting anything newer than the versions named above. Corrections: hello@aiarch.dev.

Learn the engineering underneath the workflow.

aiArch teaches MCP, tool design, evals, and production agent architecture by building — on a platform whose own coach calls public MCP servers live in production. The build is the curriculum.

Free sample — no signup · every claim cited · full curriculum is waitlist-only