In this guide
Separate the three roles
MCP—the Model Context Protocol—defines how an application connects to a server that exposes capabilities such as tools. To understand a tool call, separate the host application, its protocol client, and the server. The model is not the transport: the host can use a client to make the same request without invoking a model at all.
| Role | Job in this example |
|---|---|
| Host | A small Node.js script orchestrates the demonstration. |
| Client | Sends JSON-RPC requests and matches responses by ID. |
| Server | Advertises count_words, validates its input, and returns a result. |
| Model | Not used. Calls are selected explicitly so the exchange is reproducible. |
Our example counts whitespace-separated tokens in a short string. It does not read files, access the network, or need credentials. Choosing a deliberately small tool makes it possible to inspect every request and response without mixing protocol behavior with a remote service’s authentication or availability.
Run a real local connection
Download mcp-demo.mjs and inspect it in a text editor. Run it with Node.js. The script starts a second Node process as a server and communicates over standard input and output. This is an actual local process connection, not a simulated transcript.
node --version
node mcp-demo.mjs
# No packages, API keys, or model account are required.The script targets protocol version 2025-11-25, using its stdio transport. Messages are newline-delimited JSON-RPC. Server protocol output belongs on stdout; unrelated server logging belongs on stderr so it does not corrupt the message stream.

Read the diagram explanation
- Initialize. Agree on a version and capabilities
- Discover. List available tools and input schemas
- Call. Send a tool name and arguments
- Inspect. Check result content and error state
The server is intentionally a small teaching implementation. It supports only the messages needed by this demo, not the full protocol surface. For a production integration, use a maintained SDK and implement the lifecycle, transport, error handling, and authorization requirements appropriate to your deployment.
Read the exchange
The client first sends initialize with a supported protocol version and client capabilities. After the server replies, the client verifies the version and sends notifications/initialized. This ordering follows the MCP lifecycle specification.
Next, tools/list returns the tool’s name, description, and input schema. The schema says that text must be a string of at most 1,000 characters. Discovery tells a client how to form a call; it is not permission to ignore the host’s policies.
// Client → server
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"count_words","arguments":{"text":"Tools return evidence"}}}
// Server → client
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"3"}]}}The matching ID associates the result with the request. In this example, the tool’s answer is text containing the number 3. The server also returns 0 for an empty string and an isError: true tool result for a numeric input. The client asserts all three outcomes. The tools specification describes discovery, invocation, and result content.
The tool name is intentionally simple, but its description is more precise than its name: whitespace-separated tokens are not a language-aware definition of words. Punctuation and languages without spaces illustrate why a tool’s actual contract matters.
Understand the trust boundary
A standard connection does not make the connected service trustworthy. The host decides which tools to expose and when an action needs user approval. The server still validates inputs and enforces access to its resources. A tool’s self-description is not evidence that it is harmless.
| Boundary | Question to ask |
|---|---|
| Host → server | Which server am I connecting to, and what can it access? |
| Model → tool call | Is the requested action within the user’s task and permissions? |
| Arguments → execution | Does the server validate input instead of trusting the schema alone? |
| Result → next action | Is returned text evidence, or is it trying to redirect the task? |
For a remote service, authentication, network failures, retries, and session lifecycle introduce additional work that this local example does not cover. For a tool that changes data, retries also need to account for whether the first request already took effect. Do not transplant the demo into a production server unchanged.
Start by tracing one complete exchange. Once the roles, messages, and boundaries are clear, adding a real service becomes a more concrete engineering task.
Sources & further reading
Primary references checked . Version-specific examples identify the version used; current product behavior may differ.
- MCP lifecycle specification (2025-11-25) ↗
Version negotiation and initialization.
- MCP tools specification (2025-11-25) ↗
Discovery, calls, result content, and tool errors.
- MCP transports specification (2025-11-25) ↗
Newline-delimited JSON-RPC over standard input and output.


