Sections in this lesson
MCP Fundamentals & Version Boundaries
Separate host, client and server responsibilities and distinguish modern per-request metadata from legacy initialization.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Explain tools, resources and prompts without granting automatic trust.
- Identify the exact protocol revision used by an integration.
- Validate response correlation, metadata and resource policy separately.
Before you start
The flow
- 1.Host selects integration
- 2.Declare revision & capabilities
- 3.Request a tool
- 4.Enforce resource policy
- 5.Correlate response
Roles and exposed capabilities
The host owns the user interaction and application policy. Its MCP client speaks to a server that exposes capabilities such as tools, readable resources and prompt templates. MCP standardizes integration; it is not a planner, a sandbox or proof that server content is trustworthy.
Use a maintained SDK for a real integration. A small contract fixture is useful for learning request shape and failure handling but does not implement transport lifecycle, pagination, cancellation, authentication or the complete protocol.
Do not mix protocol eras
The reviewed modern specification is 2026-07-28: requests declare version and client capabilities in params._meta rather than relying on an initialization handshake. Earlier revisions such as 2025-11-25 establish compatibility through initialize and initialized. Check the revision actually supported by your SDK/server before copying a sample.
The fixture pins modern metadata and resultType=complete, then checks JSON-RPC response IDs and an unsupported-version error. It does not claim that the Phase 2 Spring AI laboratory or every deployed MCP server supports that modern revision.
Identity metadata is not authorization
A self-reported clientInfo name cannot grant an admin role or tenant access. The fixture receives trustedTenant outside model arguments and denies a foreign resource even if the client calls itself admin. Real HTTP transports require their specified authentication/authorization and audience/permission checks.
For stdio, keep stdout reserved for protocol messages and logs on stderr. For HTTP, use the revision-specific transport and security rules; a protocol-shaped JSON object alone is not a secure endpoint.
Try it
// A request/response teaching fixture pinned to MCP 2026-07-28.// Not a complete server/client, transport, authentication or conformance implementation.export const protocolVersion = '2026-07-28';export function toolRequest(id, planId) { if (!Number.isSafeInteger(id) || id < 1) throw new Error('Invalid request ID'); return { jsonrpc: '2.0', id, method: 'tools/call', params: { name: 'readPlan', arguments: { planId }, _meta: { 'io.modelcontextprotocol/protocolVersion': protocolVersion, 'io.modelcontextprotocol/clientInfo': { name: 'teaching-host', version: '1.0.0' }, 'io.modelcontextprotocol/clientCapabilities': {}, }, }, };}export function fixtureReply(request, trustedTenant) { const fail = (code, message, data) => ({ jsonrpc: '2.0', id: request.id, error: { code, message, ...(data ? { data } : {}) } }); const meta = request.params?._meta; if (request.jsonrpc !== '2.0' || request.id == null || !meta || !meta['io.modelcontextprotocol/protocolVersion'] || !meta['io.modelcontextprotocol/clientCapabilities']) { return fail(-32602, 'Invalid params'); } if (meta['io.modelcontextprotocol/protocolVersion'] !== protocolVersion) { return fail(-32022, 'Unsupported protocol version', { supported: [protocolVersion], requested: meta['io.modelcontextprotocol/protocolVersion'] }); } if (request.method !== 'tools/call') return fail(-32601, 'Method not found'); const { name, arguments: args } = request.params; if (name !== 'readPlan' || !args || Object.keys(args).join(',') !== 'planId' || typeof args.planId !== 'string' || !/^PLAN-\d+$/.test(args.planId)) return fail(-32602, 'Invalid params'); // Tenant comes from the host's configured credential boundary, never clientInfo or tool arguments. const authorized = trustedTenant === 'tenant-a' && args.planId === 'PLAN-1'; return { jsonrpc: '2.0', id: request.id, result: { resultType: 'complete', isError: !authorized, content: [{ type: 'text', text: authorized ? 'IN_PROGRESS' : 'Forbidden' }], } };}export function validateCorrelation(request, response) { if (response.jsonrpc !== '2.0' || response.id !== request.id) throw new Error('Uncorrelated response'); if (Object.hasOwn(response, 'result') === Object.hasOwn(response, 'error')) throw new Error('Ambiguous response'); return response;}examples/agentic-ai/mcp-contract.mjs · npm run examples:agents. Request/response fixture for MCP 2026-07-28; not a full SDK, server, transport or conformance test.
Interview questions
Does MCP choose the next agent action?
No. It provides an integration protocol; orchestration belongs to the host/runtime.
Can you assume every server expects initialize?
No. Initialization-based and modern per-request revisions have different compatibility rules.
Is clientInfo a verified user identity?
No. Treat it as reported metadata, not a credential or authorization grant.
Practice
Create separate fixtures for a pinned legacy handshake and modern request, then reject cross-era assumptions explicitly.
Check your solution
- The protocol revision is stated.
- A mismatched response ID fails.
- Changing a client name never expands access.
Official references
Continue learning
Category sequence
Agentic AI
Lesson 5 / 6
Was this lesson useful?
Change notes: Phase 3: bilingual agent engineering lesson and executable reference.