Local AI Agents · MCP and interoperable tools · Lesson 2 of 3

Capability discovery, transports and least-privilege authentication

How a client and server agree on what a session can use before anything runs, the real trust difference between MCP's stdio and Streamable HTTP transports, MCP's own least-privilege authorization model (including its explicit stdio exemption), and the specification's own Key Principles section on consent, quoted exactly — extending Tool permissions and Filesystem and network scope's least-privilege standard by name.

Lesson · 20–30 minutes · Text-first

By the end, you can

  • Explain MCP's capability negotiation and how declared capabilities bound what a session can use, using the current specification (LA-10).
  • Compare the stdio and Streamable HTTP transports, including the security requirements the specification names for Streamable HTTP and MCP's own stdio authorization exemption (LA-10).
  • Identify the least-privilege scope for a described MCP tool grant, applying the specification's own Key Principles and scope-minimization guidance and extending Tool permissions' and Filesystem and network scope's standard by name (LA-3, LA-10).

Before you start

This is Lesson 2 of 3 in MCP and interoperable tools. It assumes Lesson 1's host/client/server roles and its three primitives. It extends two earlier lessons by name to MCP's own mechanics: Tool permissions' "a tool is a grant of authority, not a convenience" standard, and Filesystem and network scope's network boundary — its named-destination-only and named-verbs-only rules. This lesson covers how a session agrees on what it can use, the real trust difference between MCP's two standard transports, and MCP's own authorization and consent model — including its security section's Key Principles, quoted exactly rather than paraphrased, because Lesson 3 relies on their precise wording.

Capability negotiation: nothing is available until both sides declare it

Before a client and server can use any primitive, they negotiate what is even on the table. The architecture specification states this as a session-opening exchange: "clients and servers explicitly declare their supported features during initialization," and "both parties must respect declared capabilities throughout the session." A server that supports tools must declare a `tools` capability; one that wants to notify the client when its tool list changes sets that capability's own `listChanged` flag; a client that supports sampling declares that separately. Nothing assumed, nothing implicit — a client cannot call a tool a server never declared, and a server cannot request something a client never said it could handle.

This is the protocol-level analogue of Tool schemas and the call/result round-trip's own JSON Schema contract, but scoped to the whole session rather than one function: instead of one tool's parameters being spelled out before it can be called, an entire session's available primitives are spelled out before anything in it can run. A dispatcher built in Tool use and the agent loop checked one tool call against its own schema; an MCP client checks an entire session against the capabilities both sides just agreed to.

Two transports, two different trust pictures

MCP defines two standard transports, and the specification's own account of each makes the trust difference between them concrete rather than assumed.

**stdio**: "the client launches the MCP server as a subprocess," reading and writing JSON-RPC messages over the process's own standard input and output. This is the shape Local-agent mental model's own workspace boundaries already apply naturally: the server runs as a local child process, under the same user's own machine and permissions, with no separate network exposure by default.

**Streamable HTTP**: "the server operates as an independent process that can handle multiple client connections," reachable over ordinary HTTP requests to a single MCP endpoint. Because this transport can be reached over a network at all, the specification pairs it with an explicit security warning worth quoting in full: "Servers **MUST** validate the `Origin` header on all incoming connections to prevent DNS rebinding attacks"; "When running locally, servers **SHOULD** bind only to localhost (127.0.0.1) rather than all network interfaces (0.0.0.0)"; and "Servers **SHOULD** implement proper authentication for all connections." The specification names the concrete consequence of skipping these: "attackers could use DNS rebinding to interact with local MCP servers from remote websites" — precisely Filesystem and network scope's own exfiltration concern, now aimed at the MCP server itself rather than at a task's own outbound calls.

Choosing between the two transports is not a style preference — it is a boundary decision in exactly Filesystem and network scope's sense. A server that only ever needs to run alongside its own host, on the same machine, has a straightforward case for stdio: no network surface to secure at all. A server meant to be reached by more than one client, or hosted somewhere other than the user's own machine, needs Streamable HTTP's three protections in place — Origin validation, localhost binding when local, and authentication — as a named requirement, not an afterthought.

Authentication: MCP's own least-privilege spine

MCP's authorization specification opens with a framing worth stating precisely, because it draws a real distinction rather than a single blanket rule: "Authorization is **OPTIONAL** for MCP implementations. When supported: Implementations using an HTTP-based transport **SHOULD** conform to this specification. Implementations using an STDIO transport **SHOULD NOT** follow this specification, and instead retrieve credentials from the environment."

That stdio exemption is not a gap in the protocol — it is the same discipline Secrets and data boundaries already built for this course's own local labs, now stated as the specification's own guidance: a locally-launched subprocess should pull whatever credential it needs from its environment, the same way this course's earlier labs kept an API key out of a request body and out of a hard-coded string. HTTP-based servers, by contrast, sit behind a real network boundary reachable by more than the process that launched them, and the specification points them at a real authorization framework — OAuth 2.1 — to control who can reach them at all.

Once an HTTP-based server is behind OAuth, the next question is exactly Tool permissions' question again: how much access does a granted scope actually carry? The specification's own scope-selection guidance names the least-privilege principle directly: a client "**SHOULD** follow the principle of least privilege by requesting only the scopes necessary for their intended operations," and its companion security guidance warns against the excessive-grant pattern this course has already named twice — a token "carrying broad scopes (`files:*`, `db:*`, `admin:*`) that were granted up front" creates "expanded blast radius" and "difficult revocation without re-consenting the entire surface." This is the same failure mode OWASP's own guidance on excessive agency names generally: "an LLM extension has permissions on downstream systems that are not needed for the intended operation" — MCP's scopes give that pattern a formal, tokenized name, but the underlying discipline Tool permissions and Filesystem and network scope already taught is identical: grant exactly what the task needs, nothing "in case it's useful later."

One honest nuance from the same authorization specification, so the least-privilege principle above is not overread: a general-purpose MCP client often cannot know in advance which scopes a specific task will need, so the specification's own fallback — when the server's challenge names no scope — is for the client to request everything in the server's advertised `scopes_supported` and let the authorization server and the user's consent screen narrow it from there. That is exactly why the real least-privilege weight sits on the server side of the design: what scopes a server defines, how narrowly each is cut, and which ones it advertises as the baseline decide what "requesting everything" can ever amount to.

The specification's own Key Principles, quoted exactly

MCP's specification devotes a named section to security and trust, opening with a direct statement of stakes: "The Model Context Protocol enables powerful capabilities through arbitrary data access and code execution paths." Its four Key Principles are worth quoting exactly, because Lesson 3 builds its entire untrusted-content argument on the third one:

1. **User Consent and Control** — "Users must explicitly consent to and understand all data access and operations. Users must retain control over what data is shared and what actions are taken." 2. **Data Privacy** — "Hosts must obtain explicit user consent before exposing user data to servers. Hosts must not transmit resource data elsewhere without user consent." 3. **Tool Safety** — "Tools represent arbitrary code execution and must be treated with appropriate caution... In particular, descriptions of tool behavior such as annotations should be considered untrusted, unless obtained from a trusted server. Hosts must obtain explicit user consent before invoking any tool." 4. **LLM Sampling Controls** — "Users must explicitly approve any LLM sampling requests," controlling "whether sampling occurs at all," "the actual prompt that will be sent," and "what results the server can see."

Every one of these reads as a specific, protocol-level version of a boundary this course has already built: consent and control map onto Autonomy boundaries' automatic/approval-required/out-of-scope classification; data privacy maps onto Secrets and data boundaries' "the agent can read it is a bigger claim than it sounds"; and tool safety's own untrusted-annotations warning is this module's central theme, stated by the specification itself rather than inferred by this course — Lesson 3 picks it up directly.

A worked example: two designs for the same read-receipts MCP server

A team builds an MCP server exposing a `list_receipts` tool over Streamable HTTP, since more than one client needs to reach it.

Version A skips the transport's own protections — no Origin validation, bound to `0.0.0.0` rather than localhost even though it only ever needs to serve clients on the same machine — and grants every client a single long-lived token scoped to `files:*`, because scoping each client's access separately felt like unnecessary setup work. A DNS-rebinding attack against this server, or a leaked token, reaches every file the server can touch, not just receipts.

Version B validates the `Origin` header on every request, binds to localhost since every client genuinely is local, and issues each client a narrowly scoped `receipts:read` token — read-only, receipts only, following the specification's own least-privilege scope guidance exactly. The same DNS-rebinding attempt is rejected at the Origin check; a leaked Version B token exposes only read access to receipts, nothing else the underlying filesystem might hold.

Accessibility notes

This lesson is text-first, with no images, audio, video or downloadable artifacts. All quoted specification text and worked examples appear as plain inline or block text, readable and copyable by assistive technology and keyboard-only users. The practice exercise's model answer sits behind a native disclosure control that is reachable and operable by keyboard and correctly announced by screen readers. The knowledge check uses native radio-button inputs with a visible question and options, and posts its result to a live status region so assistive technology announces the outcome without a page reload.

Practice

Identify the least-privilege scope for a calendar-notes MCP server

A calendar-notes assistant needs an MCP tool that can read a user's calendar events for the current week and draft (but not send) a short notes summary. The server's authors propose three possible OAuth scopes to grant the client: (1) calendar:full-access (read, write, delete and share on every calendar the user owns), (2) calendar:read (read-only access to every calendar the user owns), (3) calendar:read:current-week (read-only access to events in the current week only, on the one calendar the assistant is configured against).

  1. Using this lesson's least-privilege scope guidance and Tool permissions' 'match the grant to the task' standard from Module 1, choose which of the three scopes fits this task, and justify rejecting the other two.
  2. The task also never sends anything — it only drafts a summary. Using Autonomy boundaries' classification from Module 1, state whether 'send the drafted summary' should be automatic, approval-required or out of scope for this tool as currently described, and why.
  3. This server runs as a Streamable HTTP service reachable by more than one client. Name the three specific protections this lesson's transport section says a Streamable HTTP server should apply, and explain briefly what each one defends against.
  4. Using this lesson's Key Principles quotes, name which of the four principles most directly requires the host to tell the user what calendar data this tool is about to access before it runs, and quote the relevant phrase.
Compare with a bounded first version

Scope calendar:read:current-week fits the task: the assistant only needs to read the current week's events on one calendar, and this scope grants exactly that — nothing broader. calendar:full-access is rejected because it grants write, delete and share access the task never uses, and access to every calendar the user owns rather than the one configured; calendar:read is rejected because, while read-only is correct, granting every calendar rather than just the current week on the configured one is still broader than the task needs — both repeat the 'grant the whole connector because scoping it felt like extra work' pattern this course has already named in Tool permissions and this lesson's own least-privilege section. Sending the summary is not part of this tool's current description at all (it only drafts), so it is out of scope for this tool as described, not merely approval-required — if a future version adds a send capability, that action would need its own explicit classification, and given it reaches someone else's inbox it would very likely land in approval-required at minimum, per Autonomy boundaries' own reasoning. The three Streamable HTTP protections: validate the Origin header on every incoming connection, to prevent a DNS-rebinding attack from a malicious website reaching the server; bind to localhost rather than 0.0.0.0 when the server only needs to serve local clients, so it isn't reachable from the wider network at all; and implement proper authentication for all connections, so even a request that does reach the server can't act without a valid, appropriately scoped credential. The Data Privacy principle most directly requires this: 'Hosts must obtain explicit user consent before exposing user data to servers' — the host must tell the user what calendar data is about to be shared with this tool before the read happens, not only before some later action.

Knowledge check

Try the idea

A builder is writing a local, stdio-transport MCP server that needs an API key for a downstream service. Per this lesson's quoted authorization specification, what should the server do?
Low-stakes practice only. This does not score, block progress or create a learner record.

Sources and limits

This lesson synthesises the sources below into a practical learning model. It is not a security standard, legal advice or a guarantee that any particular agent design is safe.

  1. ArchitectureModel Context Protocol. Documents capability negotiation: servers and clients each declare supported features during initialization, and both parties must respect declared capabilities throughout the session.
  2. TransportsModel Context Protocol. Documents the stdio and Streamable HTTP transports and the Streamable HTTP security warning (Origin header validation, binding to localhost, authenticating connections).
  3. AuthorizationModel Context Protocol. States that authorization is optional, that HTTP-based transports should conform to the specification, and that stdio transports should not follow it and should instead retrieve credentials from the environment.
  4. Security Best PracticesModel Context Protocol. Describes the scope-minimization attack scenario: a broad, up-front-granted OAuth scope's expanded blast radius and difficult revocation.
  5. Specification overview (Key Principles)Model Context Protocol. States the four Key Principles for security and trust: user consent and control, data privacy, tool safety and LLM sampling controls.
  6. LLM06:2025 Excessive AgencyOWASP Gen AI Security Project. Defines excessive permissions as access on downstream systems beyond what the intended operation needs, the same standard this lesson applies to an MCP authorization scope.