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. 1.Host selects integration
  2. 2.Declare revision & capabilities
  3. 3.Request a tool
  4. 4.Enforce resource policy
  5. 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

mcp-contract.mjs
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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.