MCP Ecosystem Integrations
You know what MCP is (T3) and how Claude uses it (C4). This chapter is the ecosystem: the growing catalog of ready-made servers, the transports and registries that connect them, and — most importantly — how to run third-party MCP servers without opening a security hole.
An agent is only as useful as what it can reach. This section is about connecting agents to tools/data (via MCP) and to other agents (via protocols like A2A), plus the operations of running them — tracing, guardrails, and observability. It's the 'agents in the real world' section.
The words you'll hear (in plain terms):
| Term | What it actually means |
|---|---|
| interoperability | different systems working together through shared standards. |
| MCP | Model Context Protocol — a standard plug for giving an agent tools/data. |
| A2A / agent protocols | standards for agents to talk to other agents. |
| observability / tracing | seeing what an agent did step-by-step, to debug and audit it. |
| guardrails | safety checks on what an agent accepts or produces. |
What you need before starting:
- Having built an agent (Ch 4) and seen MCP (C4) helps.
- Python basics; comfort with APIs and tools.
- Specific labs note their own installs (LangSmith, guardrail libs).
New to the topic? Read this box, then take the chapters in order — each section is tagged essential → expert so you always know the depth you're at.
Learning objectives
- Survey the MCP ecosystem: reference & third-party servers, hosts, registries.
- Distinguish the transports (stdio vs streamable HTTP) and when each fits.
- Connect an existing server to a host and to your own agent.
- Handle auth and multi-server setups cleanly.
- Vet a third-party server for the real security risks before trusting it.
Why an ecosystem matters essential
Picture the bad old world for a second, because it's the whole reason this chapter exists. Every team that wanted an AI assistant to read GitHub had to write its own GitHub integration. Every team that wanted it to query Postgres wrote its own Postgres integration. So did the next team, and the next — each one re-solving the same plumbing, each one a slightly different, slightly broken snowflake. That is the world before a standard: enormous duplicated effort, and every app is an island that can only reach the tools its own authors had time to hand-wire.
Here's the mental model that makes MCP click. Think of it like USB. Before USB, every device had its own connector — a printer cable, a mouse cable, a serial port — and adding a new device often meant a new card and new drivers. USB defined one plug, and suddenly any device that spoke USB worked with any computer that had a USB port; neither side had to know about the other in advance, they just had to agree on the shape of the plug. MCP is that plug for agents and tools: the host is the computer, the servers are the devices, and the protocol is the port they all agree on. Because the plug is standard, someone else can build the "device" (the server) and you just plug it in.
The common mistake here is thinking the ecosystem is just "a list of servers you can download." It's more than convenience — it's an economic phase change. When integrations are bespoke, the total work grows as apps times tools: every new tool multiplies across every app that wants it. When they go through one standard, the work grows as apps plus tools — you write M servers, you have N hosts, and any host reaches any server for free. A product collapses into a sum. That single arithmetic shift — spelled out in the diagram below and in the first practice-ladder exercise — is why thousands of servers now exist that you can adopt instead of build, and why "is there already a server for this?" has quietly become the first question on any integration task.
MCP's whole point (T3) is turning an N×M integration problem — every app × every tool — into N+M: write a server once, and any MCP-aware host can use it. The payoff of that standard is the ecosystem it creates. Instead of hand-writing a GitHub or Postgres or Slack integration, you connect a server someone already built and maintains. This chapter is about consuming that ecosystem well.
This picture explains why an ecosystem exists at all. On the left are the programs that want tools (the hosts); on the right is one shared pool of ready-made servers. MCP is the standard plug in the middle that lets any host use any server.
- The left column lists three different hosts —
Claude Code,your agent, and anIDE / app. These are the things a human actually uses. - The arrows all pass through the same label — MCP. That's the point: every host connects the same standard way, so no host needs custom wiring for each tool.
- The right column is the shared pool of servers (
github,postgres,filesystem,slack…). Each server is written once and reused by every host — that's the caption's "write once, reused by every host". - So the value of the ecosystem is reuse: instead of hand-building a GitHub or database integration inside every app, you connect a server someone already built.
In short: Many hosts, one pool of servers, MCP as the universal connector between them. Adopting a server is faster than building one — if you vet what you plug in.
The ecosystem landscape essential
The moment the ecosystem is worth using, it's big enough to get lost in. Search for "MCP server" and you'll find thousands of results — official ones, company ones, weekend-project ones — all wearing the same badge and all technically speaking the same protocol. The trap is treating them as interchangeable because they share a name. They are not. The difference between them isn't what they do; it's who stands behind them, and that difference is the whole reason this section exists before the fun part of connecting one.
A useful analogy is app stores versus sideloading. A reference server is like a first-party app shipped by the platform itself — canonical, audited, the thing everyone else copies. A vendor server is like the official app from a company you already have an account with: GitHub's own server, a database vendor's own server — first-party, and the company's reputation is on the line. A community server is like an APK you found on a forum: it might be brilliant, it might be abandoned, it might be actively hostile, and the only way to know is to look. Hosts are the phones that run these apps; registries are the store directories you browse to find them.
So read the table below not as five neutral buckets but as a trust gradient. As you slide from reference to vendor to community, the variety explodes and the guarantees evaporate — and your obligation to check what you're plugging in rises to match. That gradient is exactly why the very next thing after this table is a warning box, and why the security lab later is the most important skill in the chapter: the ecosystem hands you enormous leverage and asks, in return, that you know whose code you're about to run.
Read the top row left-to-right along the arrow — that direction is the whole message: trust drops and variety rises as you move rightward.
- The top three boxes are the server tiers. Green Reference (official, audited) is the safest; amber Vendor (first-party from a named company) is in between; red Community (thousands, uneven quality) is the riskiest.
- The colored labels below — "vet lightly / vet / vet hard" — spell out that your checking duty rises as trust falls; a random community server earns the most scrutiny.
- The bottom two blue boxes are the other players: hosts are the apps that actually run servers, and registries are the catalogs where you go to discover them.
In short: Servers slide from safe (reference) to risky (community); how hard you vet slides to match — and hosts run them while registries help you find them.
| Category | What it is | Examples |
|---|---|---|
| Reference servers | Official servers maintained by the MCP project — canonical, well-audited | filesystem, git, fetch, memory, time |
| Vendor servers | First-party servers from a service's own company | GitHub, databases, SaaS tools (official) |
| Community servers | Third-party servers built by the community — huge variety, uneven quality | Thousands, across every niche |
| Hosts / clients | Apps that consume servers | Claude Code (C3), Claude Desktop, IDEs, your agent (C4) |
| Registries | Directories to discover servers | Official + community catalogs |
Transports: stdio vs streamable HTTP essential
Here's where a lot of people first get stuck — and it's usually because they're asking the wrong question. They agonize over which transport is "better," as if one were faster or more modern. That's not the decision. The transport isn't a quality choice, it's a location choice: it simply describes how the host reaches the server, and that follows entirely from where the server physically lives. Get the location right and the transport picks itself.
The plainest way to see it: a transport is just the wire between two programs. If the server sits on your own machine, the cheapest possible wire is to launch it as a child process and talk through the pipes the operating system already gives every process — that's stdio (standard in / standard out), the same plumbing your shell uses when you pipe one command into another. No network, no ports, no login; the host starts the program and they whisper down a pipe. If instead the server lives somewhere else — a shared service, a hosted URL other people also use — you can't reach it with a pipe, so you talk to it the way you'd talk to any web service, over HTTP. That's streamable HTTP.
And here's the part beginners under-weight: the two are not equally free. stdio is nearly responsibility-free because it never leaves your machine — but it also means the host launches a binary, so "only run code you trust" is the whole safety story. streamable HTTP buys you reach and sharing, but the moment a server is exposed at a URL it becomes a real networked dependency, and everything you know about running a service comes back: authentication and authorization (who's allowed in and what can they do), TLS (so nobody reads the traffic), rate limits (so nobody hammers it). The common mistake is copying a remote setup for a local tool and inheriting all that operational burden for nothing — or, worse, exposing a server over HTTP with no auth because the stdio tutorial "just worked." Match the transport to where the server lives, and own the responsibilities that come with the remote choice. The diagram lays the two side by side.
A host and a server have to talk over some channel — that channel is called a transport. This diagram shows the two common ones side by side, and the deciding question is simple: where does the server run?
- The left half (stdio — local) shows the host launching a
local processon the same machine. They talk over "pipes" (standard in/out), which is fast and needs no network — best for personal tools like filesystem or git. - The right half (streamable HTTP — remote) shows the host reaching a server at a
URLover the network. This is for hosted or shared servers — and it's the transport the API MCP connector uses. - Read the small captions under each: local means low latency, no network; remote means you now own auth and TLS (a login/token and encryption), because it's a real network service exposed to the outside.
- The arrow is the same in both — a host talking to a server. Only the channel and the responsibilities differ.
In short: stdio = server on your machine, simple and local. Streamable HTTP = server behind a URL, powerful but you must secure the connection. Pick by where the server lives.
| Transport | Use when | Watch for |
|---|---|---|
| stdio | Server runs locally beside the host; personal tools | The host launches a process — only run trusted binaries |
| Streamable HTTP | Server is hosted / shared across users | TLS, authN/authZ, rate limits — it's a network service (O3) |
Lab I1.1 · Connect an existing server intermediate
This is the payoff moment — the point where "there's an ecosystem" turns into "I just gave my agent a new superpower in two lines." Everything before this section was setup; here you actually reach into that shared pool and pull out a working capability you didn't have to write. And the quietly remarkable thing is how little you do: you never hand-write a GitHub tool, never parse its API, never touch its auth flow. You point at a server, say "you may use it," and the model discovers the tools itself and calls them through the exact same tool-use loop you built back in C2. The integration you'd have spent a day on becomes a config line.
The one idea to hold onto as you read the lab is that connecting is two distinct steps, and mixing them up is the most common beginner error. First you declare the server — you tell the host "here is a server called gh, at this URL (or this local command)." That only names it; it does nothing yet. Then you enable its toolset — you tell the model "you're allowed to use the tools from gh." These are separate on purpose: declaring is about where the server is, enabling is about whether the model may touch it. It mirrors how you'd onboard a human contractor — first you record who they are, then, separately, you grant them access to a system. Keeping the two apart is what lets you connect five servers but only expose the two a given task actually needs.
Notice the two paths in the lab are the same idea wearing different clothes. The host path (claude mcp add) is you telling a ready-made app like Claude Code to manage a local server for you — fastest, no code, great for personal tools over stdio. The agent path (the Python call) is you doing the same declare-then-enable dance yourself, in your own program, usually against a remote server by URL. Same protocol underneath; the only difference is who's holding the config. Do it once in each and the pattern is yours for every server you'll ever add.
Read it left-to-right: two setup steps, then the payoff. The whole point is that the two steps are separate.
- Box 1 (blue, "Declare") just names the server — you give it a handle and a URL or local command. This says where it is and nothing more; it grants no access yet.
- Box 2 (amber, "Enable") is the permission step: you tell the model it may use that server's toolset. Declaring and enabling are the two separate lines the lab keeps apart.
- Box 3 (green, "Model calls tools") is the reward — only after both steps does the model discover the server's tools and call them through the same tool-use loop from C2. You never hand-wrote the tool.
In short: Declare (where it is) → enable (may it be used) → the model calls the tools itself. Splitting the two lets you connect many servers but expose only the ones a task needs.
- To a host (e.g. Claude Code). Register a local (stdio) server — the host launches it and its tools appear in-session (C4):
shell (illustrative)
claude mcp add filesystem # stdio server, launched locally claude mcp list # confirm it's connected - To your own agent (via the API connector). Point at a remote server by URL and reference it from a toolset (C4):
connect.py
resp = client.beta.messages.create( model="claude-opus-4-8", max_tokens=1024, betas=["mcp-client-2025-11-20"], mcp_servers=[{"type":"url","name":"gh","url":"https://studybydoing.in/mcp"}], tools=[{"type":"mcp_toolset","mcp_server_name":"gh"}], messages=[{"role":"user","content":"List my open PRs."}], ) - Use it. Ask a question that needs the server's tools; the host/agent discovers and calls them through the same tool-use loop you built in C2.
This block does the same job as the shell commands above — connect a server and use its tools — but from inside your own agent's code, pointing at a remote server by URL. It's one normal model call with two extra pieces: which server to use, and permission to use it.
betas=["mcp-client-2025-11-20"]switches on the MCP-connector feature for this call.modelandmax_tokensare the usual settings you've seen on every call.mcp_servers=[{...}]declares the server: itstypeis"url"(a remote, streamable-HTTP server), you give it a shortname("gh") and itsurl. The name is the handle you'll reuse below.tools=[{"type":"mcp_toolset", "mcp_server_name":"gh"}]is the permission step: it tells the model "you may use the tools from the server calledgh." Declaring a server and enabling its toolset are two separate lines on purpose.- The
messagesask a real question —"List my open PRs.". The model then discovers the GitHub server's tools and calls them through the same tool-use loop you built earlier; you didn't hand-write a GitHub tool.
What the output means: The model answers using the remote server's tools — e.g. it calls the GitHub server to fetch and list your open pull requests, with no custom integration code on your side.
Try this: Change the url and name to a different server, add a matching entry in both mcp_servers and tools, and you've connected a second server. That pairing — declare + enable — is the pattern for every server you add.
This is the fastest way to give a host new tools: register a server from the command line. These two shell commands add a local server to Claude Code and then check it connected — no code required.
claude mcp add filesystemtells the host "start using the filesystem server." Because it's a stdio server, the host will launch it as a local process when needed and pipe messages to it.claude mcp listprints the servers the host knows about, so you can confirm it's connected before you rely on it. Always verify — a typo or a missing binary shows up here, not mid-task.- Once added, the server's tools appear automatically inside your session; you don't wire anything else up.
What the output means: After the add, the list command shows filesystem among the connected servers — proof the host can now reach its tools.
Try this: This is the host path (Claude Code adds a server for you). The next block is the agent path — the same idea, but done in your own Python code.
Auth & multi-server setups intermediate
One connected server is a toy; a real agent has a keyring. The demo where you plug in a single filesystem server is lovely, but production looks different: a GitHub server, a database server, a Slack server, a cloud server — each one wanting a credential, each one a door into something that matters. The instant you have more than one, two problems you could ignore before become the whole game: where do the secrets live, and which server does the model reach for. This section is about keeping both under control.
Start with credentials, because the mistake here is the expensive one. The tempting move is to paste a token into the prompt or the conversation so the server "has what it needs." Never do this. Think of the model's context as a glass box that gets logged, cached, and sometimes shown to users — anything you put in it can leak out of it. A token belongs in the server's configuration or the connector, handed over out-of-band, so the model can use a capability without ever seeing the key. And give each server the smallest key that still works: a read-only database user, a token scoped to one repo. That way a compromised or misbehaving server can only do the little you allowed — the same least-privilege instinct from T1, applied to tools.
The second, subtler trap is the one that feels like generosity: connecting lots of servers "just in case." More servers is not more capability — past a handful it's less. Every server you attach dumps its tools into the model's context and adds branches to its decision-making, so a model facing forty tools picks worse and costs more than one facing six, the same way a menu with two hundred dishes gets you a worse dinner than a focused one. Give servers clear, distinct names so the model routes correctly, connect only what the task in front of you needs, and if your surface genuinely is large, that's what tool-search (C4) exists for. The table sets out the concrete practices.
Follow the token from the green box on the left and watch which path it takes — the solid arrow is allowed, the dashed red one is forbidden.
- The green "secret / token" box is a credential a server needs. The solid blue arrow hands it to the server config (bottom, blue) out-of-band — privately, not through the conversation.
- The dashed red arrow to the "model context" box (red) is the mistake to avoid: the context is logged, cached, and can be shown to users, so a key pasted there can leak. "never here" marks it.
- From the config, a solid arrow reaches the backend (green) with a least-privilege credential — the smallest key that still works — so a misbehaving server can only do the little you allowed.
In short: Secret → server config → backend, never through the model's context. The model uses the capability without ever seeing the key, and each key is scoped to the minimum.
| Concern | Practice |
|---|---|
| Credentials | Pass auth via the server config / connector token — never paste keys into prompts or conversation (C4, T1) |
| Multiple servers | Each declared server needs a matching toolset; give servers clear names so the model routes correctly (C4) |
| Least privilege | Scope each server's credential to the minimum (read-only DB user, narrowly-scoped token) |
| Tool sprawl | Ten servers = dozens of tools = a confused model; connect only what a task needs |
Lab I1.2 · Vetting a third-party server intermediate
If you remember one thing from this whole chapter, make it this. The single most dangerous sentence in the ecosystem is "I found a server that does exactly what I need." That excitement is precisely where people get burned, because an MCP server is not a plugin, a package, or a widget — it is code that runs with your agent's permissions and a pipe into your data. Connecting one is the same category of act as running a stranger's script on your laptop while logged into your bank. The convenience is real; so is the risk, and the risk is easy to forget exactly because the convenience is so seductive.
Why is this worse than an ordinary library you'd pip install? Because of what a server can do by design. It defines the tools the model sees, so it can offer an innocent-looking tool that quietly does something else. It returns data the model treats as input, so a hostile server can smuggle instructions into a "result" and hijack your agent — that's prompt injection arriving through the tool channel (T1). And a local stdio server literally runs as a process on your machine, so it can touch any file or network the process can. A malicious one can exfiltrate secrets, poison the model, or burn the credentials you handed it. This is not hypothetical; it's a documented, named risk class.
So the mental model to adopt is a gate, not a download button. Nothing connects to your agent until it has passed through a checkpoint that answers three questions: provenance (who built and maintains this — reference and vendor beat an unknown author), scope (what does it actually ask to access, and is that the least it needs), and audit (the source is usually open — read it; does it call home, over-reach, exfiltrate?). Add isolation for local servers (sandbox them, don't hand out broad filesystem or network access) and treat every tool result as untrusted input. The common failure is letting convenience wave a server past the gate — "it's popular," "it's probably fine." Popularity is not provenance. If you cannot answer "what does this server actually do?", that unanswered question is your verdict: don't connect it. The diagram draws the gate; the table below turns it into a checklist you can actually run.
This is the chapter's key safety idea drawn as a picture: an untrusted server does not connect straight to your agent — it must pass through a vetting gate first. Read it left to right as a one-way journey with a checkpoint in the middle.
- The left box (red, "3rd-party server / untrusted") is any server you found in the ecosystem but haven't checked. Red = do not trust it yet.
- The middle box ("vetting gate") is the checkpoint. Its three words — provenance · scope · audit — are the questions you must answer: who made it?, what can it access?, and what does the code actually do?
- The right box (green, "your agent / only if it passes") is reached only after the gate. The arrow goes one way — nothing skips the check.
- The lesson: adopting a server is a trust decision, like accepting outside code into your system, not a harmless plugin install. An MCP server runs code and touches your data.
In short: Untrusted server → vetting gate (who / what access / what it does) → your agent, and only if it passes. Convenience never earns a server a shortcut around the gate.
| Vet for… | Question to answer |
|---|---|
| Provenance | Who publishes & maintains it? Reference/vendor > unknown community author |
| Scope of access | What tools/resources does it expose, and what credentials does it want? Least privilege |
| What it actually does | Read the source (it's usually open); does it exfiltrate, call home, over-reach? |
| Isolation | Run local servers in a sandbox; don't give one broad filesystem/network access it doesn't need |
| Injection surface | Tool results are untrusted input — a server returning attacker-controlled data can carry prompt injection (T1) |
Building vs consuming advanced
Engineers love to build, and that instinct will cost you here. There's a real pull to write your own server — it feels like control, it feels like craft, and T3 just taught you how. But in a mature ecosystem, reaching for the keyboard is usually the wrong default. The person who ships fastest and breaks least is the one who asks "does a good server for this already exist?" and, most of the time, adopts it. Building is the exception you justify, not the reflex you indulge.
The way to make the call without agonizing is to treat "adopt a vetted, maintained server" as the default and look only for the specific reasons that default breaks. There are exactly three worth building for. One: nothing good exists — the tool or data source you need has no maintained server, so someone has to write it, and that someone is you. Two: it's your own system — you're exposing an internal service to agents, which by definition no one else can build because only you know its shape and can decide what it should reveal. Three: you need tight control — your logic is specific or sensitive enough that a general server can't capture it and you want to own exactly what's exposed. If none of those three fire, adopting wins: it's faster, someone else maintains it, and it's had more eyes on it than a fresh weekend build.
This is deliberately the same judgment you've made before — inline tool versus framework in L2, MCP versus a hand-written tool in C4 — and it keeps recurring because it's the core economic question of a standard: reuse the shared thing when a trustworthy shared thing exists, build the bespoke thing when the capability is genuinely yours. What's changed, and what makes this section land in the "advanced" tier, is that the ecosystem has tipped the odds. "A maintained option exists" used to be rare; now it's the common case, so the honest default has flipped from build to adopt. Just remember that "adopt" always means "adopt and vet" — the previous section's gate doesn't get skipped because you chose the fast path.
Read it top-to-bottom as a decision: one question routes you left (build) or right (adopt).
- The top box is the trigger — you need some capability. The amber box below asks the one deciding question: does a vetted, maintained server already exist that fits?
- The right branch (green "ADOPT") is the labelled default: if a good server fits, adopt it — it ships faster, someone else maintains it, and it's had more eyes than a fresh build.
- The left branch (red "BUILD") is the exception, reached only for the three specific reasons listed: nothing good exists, it's your own internal system, or your logic is too specific to reuse.
- The bottom line is the caveat that never drops: adopting always means adopting and vetting — the security gate from the previous section still applies.
In short: One question — does a vetted server fit? Yes → adopt (the default). No, or one of the three build reasons fires → build (the exception).
Common pitfalls advanced
Almost every ecosystem accident traces back to the same root cause: treating a server like a harmless app instead of like code you're inviting into your system. The mistakes below aren't random — they're what happens when convenience quietly wins an argument it shouldn't. You connect the shiny community server without checking who wrote it; you hand it a broad token because scoping one is annoying; you attach ten servers "to be safe"; you trust a tool result because results have always looked trustworthy. Each feels reasonable in the moment, and each is the exact failure the earlier sections were built to prevent.
Read the table as a set of reflexes to install, not rules to memorize. The through-line is the one this chapter keeps returning to: a server is a trust boundary, so the safe move is always the conservative one — vet before connecting, scope credentials to the minimum, connect only what the task needs, treat every tool result as untrusted, sandbox anything running locally, and follow the current transport rather than an out-of-date tutorial. If you catch yourself about to take a shortcut in the left column, that's the signal to stop and apply the fix in the right.
| Pitfall | Fix |
|---|---|
| Connecting community servers unvetted | Check provenance, scope, and source first |
| Handing a server broad credentials | Least privilege; scope tokens/DB users narrowly |
| Connecting many servers "just in case" | Only what the task needs; use tool-search for large sets |
| Trusting tool results as safe | Treat all server output as untrusted input (T1) |
| Running a local server with full system access | Sandbox it; restrict filesystem/network |
| Following SSE-only transport tutorials | Use current streamable HTTP; check the server's docs |
Exercises advanced
Exercise I1.1 — Connect & use
Context: The fastest way to make MCP concrete is to add a reference server to a host, invoke one of its tools, and confirm from the trace that the tool actually ran.
Your task: Add a reference server (filesystem or fetch) to a host, then use one of its tools with an explicit read-only instruction, and confirm from the trace that the tool ran.
Requirements:
- Add a reference server (filesystem or fetch) to a host
- Invoke one of its tools with an explicit read-only instruction
- Confirm from the trace that the tool actually ran
- Note which transport it used and why
💡 Hint: The transport falls out of how the server runs — a local reference server is stdio; check the trace for the actual tool-call event, not just the reply.
Exercise I1.2 — Vet a server
Context: A community MCP server runs with your agent's credentials, so before you connect one you work through a security vetting table and make an explicit trust call.
Your task: Pick any community MCP server and fill in the security vetting table for it (provenance, scope of access, what the source does, isolation needs, injection surface), then decide whether you'd connect it to an agent with real credentials.
Requirements:
- Fill in provenance, scope, what the source actually does, isolation, and injection surface
- Weigh red flags (unknown author, over-broad credentials, closed source, unexplained network calls)
- Weigh green flags (reference/vendor origin, minimal scope, readable source, active maintenance)
- Make an explicit connect / don't-connect decision and justify it
💡 Hint: If you can't answer “what does this server actually do?” from the source, that unanswered question is itself the verdict.
Show what to look for
Red flags: unknown author, requests broad credentials it doesn't need, closed source, network calls to unexplained endpoints. Green flags: reference/vendor origin, minimal declared scope, readable source, active maintenance. If you can't answer "what does it do?", that's a no.
Exercise I1.3 — Build-or-adopt
Context: The build-vs-consume decision recurs for every capability a real project needs, so practising it against three concrete capabilities builds the instinct.
Your task: For three capabilities your capstone might need (read Terraform state, query CloudWatch, post to Slack), decide adopt-a-server vs build-your-own and justify each with the building-vs-consuming criteria.
Requirements:
- Take three concrete capabilities
- Decide adopt vs build for each
- Justify with the building-vs-consuming criteria (availability, maintenance, trust, customization)
- Prefer adopting a vetted, maintained server where one fits
💡 Hint: Reuse the build-or-consume logic from the Industry rung — each capability's answer turns on whether a trustworthy maintained server already exists.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: MCP's payoff is the ecosystem: write a server once and any host can use it, which collapses the integration count from N×M bespoke connectors to N+M.
Your task: Model the integration count — N apps × M tools without a standard vs N+M with one — and print the savings.
Requirements:
- Return
n×mwithout a standard,n+mwith one - Compute both for a concrete N and M
- Print the saving (the difference)
- Make the economic argument that reusable servers form an ecosystem
💡 Hint: It's the classic N×M vs N+M contrast — a standard turns a product into a sum.
Show solution
The core economic argument, as arithmetic (pure Python):
def integrations(n_apps, m_tools, standardized):
return n_apps + m_tools if standardized else n_apps * m_tools
n, m = 6, 8
print("without MCP (N×M):", integrations(n, m, False)) # 48 bespoke integrations
print("with MCP (N+M) :", integrations(n, m, True)) # 14
print("saved:", integrations(n,m,False) - integrations(n,m,True), "integrations") # 34
Without a standard, every app must integrate every tool (N×M). MCP makes each server write-once and reusable by any host, collapsing the work to N+M — the whole reason an ecosystem forms.
Context: MCP servers speak over stdio (a local subprocess) or streamable HTTP (remote/networked); the transport follows where the server runs and who owns auth and TLS.
Your task: Write a selector that picks the transport from the deployment shape and note the tradeoff.
Requirements:
- Pick stdio for local-only servers (subprocess, simplest, no network exposure)
- Pick streamable HTTP for remote/shared servers
- Note HTTP needs auth + hardening
- Tie the choice to where the server runs and who owns auth/TLS
💡 Hint: Local means stdio and no network surface; anything remote or multi-client means HTTP and the auth burden that comes with it.
Show solution
Transport selection from where the server runs (concept logic, no SDK):
def pick_transport(runs_locally, needs_remote_access, multiple_clients):
if runs_locally and not needs_remote_access:
return "stdio — local subprocess, simplest, no network exposure"
if needs_remote_access or multiple_clients:
return "streamable HTTP — networked, shareable, needs auth + hardening"
return "stdio — default for a single local tool"
print(pick_transport(True, False, False)) # stdio
print(pick_transport(False, True, True)) # streamable HTTP
stdio is the simplest and safest for a local tool the host launches as a subprocess; streamable HTTP is for remote or shared servers — more reach, but now you own auth and network hardening.
Context: You don't reimplement a tool that a maintained MCP server already offers — you launch the server and discover its tools, which is capability discovery in action.
Your task: Show the real MCP client shape: launch a stdio server and list its tools, using only documented MCP Python SDK APIs and labelling it as needing the SDK.
Requirements:
- Configure a stdio server with
StdioServerParameters(command + args) - Open it with
stdio_client()and aClientSession - Call
session.initialize() - Discover tools with
session.list_tools() - Frame it as discovering a maintained server's tools, not reimplementing them
💡 Hint: Initialize the session, then list_tools() — discovery hands you the server's capabilities rather than you hard-coding them.
Show solution
The real client-connect shape — needs pip install mcp:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command="uvx", args=["mcp-server-git"])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools() # discover what it offers
print([t.name for t in tools.tools])
# result = await session.call_tool("git_status", {"repo_path": "."})
asyncio.run(main())
You do not reimplement the tool — you launch a server someone maintains, initialize a session, and discover its tools. Any MCP-aware host connects the same way, which is the ecosystem payoff in code.
Context: Real agents connect several servers at once, some needing tokens, and secrets must come from the environment — never inlined into the config.
Your task: Model a host config holding multiple servers with per-server auth, and a resolver that returns the connection params for a named server.
Requirements:
- Hold a map of server name → config (transport, command/args or url, and an auth-env var name)
- Resolve a named server by reading its token from the environment
- Set an
authorizedflag and strip the raw env-var name - Never inline secrets in the config
- Show stdio tools needing no auth while HTTP servers carry env-sourced tokens
💡 Hint: Store only the name of the env var in config; the resolver reads the actual token at connect time, so no secret ever lives in the map.
Show solution
A multi-server host config with per-server auth (pure Python, mirrors real host config files):
import os
SERVERS = {
"git": {"transport": "stdio", "command": "uvx", "args": ["mcp-server-git"]},
"github": {"transport": "http", "url": "https://mcp.example.com/github",
"auth_env": "GITHUB_TOKEN"},
"postgres":{"transport": "stdio", "command": "uvx", "args": ["mcp-server-postgres"],
"auth_env": "DATABASE_URL"},
}
def resolve(name):
cfg = dict(SERVERS[name])
if "auth_env" in cfg:
token = os.environ.get(cfg["auth_env"])
cfg["authorized"] = token is not None # never inline secrets
cfg.pop("auth_env")
return cfg
print(resolve("git")) # no auth needed
print(resolve("github")) # authorized depends on env token
Keep secrets in the environment, not the config, and let the host hold many servers behind one map. HTTP servers carry tokens; local stdio tools usually need none — the resolver centralizes that difference.
Context: A third-party MCP server runs with your agent's permissions, so vetting it is a supply-chain trust decision over provenance, scope, secret handling, and transport — a discipline, not a fixed allow-list.
Your task: Write a checklist scorer over the real risks that returns a trust verdict.
Requirements:
- Accumulate a risk score from flags (closed source, broad scope, plaintext secrets, unauthenticated HTTP, unmaintained)
- Weight the more dangerous flags higher
- Return TRUST / REVIEW / REJECT from score bands
- Include notes on which flags fired
- Encode vetting as provenance/scope/secret/transport, not an allow-list
💡 Hint: Sum weighted risk flags into a score, then band it — plaintext secrets and broad scope should weigh heaviest.
Show solution
A vetting scorecard for the real risks the lesson names:
def vet(server):
risk = 0; notes = []
if not server.get("open_source"): risk += 2; notes.append("closed source")
if server.get("requests_broad_scope"): risk += 2; notes.append("broad permissions")
if server.get("wants_plaintext_secrets"): risk += 3; notes.append("plaintext secrets")
if server.get("transport") == "http" and not server.get("uses_auth"):
risk += 2; notes.append("unauthenticated HTTP")
if not server.get("maintained"): risk += 1; notes.append("unmaintained")
verdict = "TRUST" if risk == 0 else ("REVIEW" if risk < 4 else "REJECT")
return verdict, risk, notes
print(vet({"open_source":True,"maintained":True,"transport":"stdio"}))
# ('TRUST', 0, [])
print(vet({"open_source":False,"wants_plaintext_secrets":True,"transport":"http"}))
# ('REJECT', ...)
A server you connect can act with your agent's permissions and see its data, so vet source, scope, secret handling, and transport before trusting one. Learn the vetting discipline, not a fixed allow-list — the ecosystem moves fast.
Context: As a lead, adopting a vetted, maintained MCP server is the default win; you build your own only when none exists, it fails vetting, or your logic is too specific.
Your task: Decide whether to adopt an existing MCP server or build your own, weighing availability, maintenance, trust, and customization, and print the decision with reasoning.
Requirements:
- BUILD when no server exists
- BUILD/fork when an existing one fails vetting
- BUILD on the pattern when your logic is too specific
- CONSUME when a maintained, vetted server fits
- CONSUME cautiously (pin a version) otherwise
💡 Hint: Consuming a maintained, vetted server is the default; each BUILD branch is a specific reason that default doesn't hold.
Show solution
The build-vs-consume decision every ecosystem adopter faces:
def build_or_consume(exists, well_maintained, passes_vetting, needs_custom_logic):
if not exists:
return "BUILD — no server exists for this tool/data source"
if not passes_vetting:
return "BUILD (or fork) — existing server fails security vetting"
if needs_custom_logic:
return "BUILD on the pattern — your logic is too specific to reuse as-is"
if well_maintained:
return "CONSUME — adopt the maintained server; faster than building"
return "CONSUME cautiously — pin a version and watch for abandonment"
print(build_or_consume(True, True, True, False)) # CONSUME
print(build_or_consume(True, True, False, False)) # BUILD (or fork)
Consuming a maintained, vetted server is faster than building and is the default; you build only when nothing exists, the candidate fails vetting, or your logic is too specific to reuse. Adopting well is usually the win.
✓ Checkpoint — you can move on when you can…
- Describe the ecosystem tiers (reference / vendor / community) and hosts/registries.
- Choose stdio vs streamable HTTP for a given server.
- Connect a server to a host and to your own agent.
- Handle auth and multi-server setups with least privilege.
- Vet a third-party server for provenance, scope, and injection risk.
Knowledge check check yourself
MCP turns an N×M integration problem into N+M. Explain what that means and why it creates a server ecosystem.
Show answer
When would you choose the stdio transport versus streamable HTTP for an MCP server, and what extra responsibility does the remote choice add?