Skip to main content
The MCP Inspector is great for manual exploration, but real integrations need a programmatic client. In this lesson you write a client from scratch in your chosen language and verify it can connect to a server, list capabilities, and invoke tools.

Learning objectives

By the end of this lesson you will be able to:
  • Understand what an MCP client does and why you need one
  • Write a client that connects to an MCP server over stdio
  • List and invoke tools, resources, and prompts programmatically
  • Run both client and server together in a local workflow

What a client does

An MCP client:
  • Imports the MCP SDK and a transport module
  • Instantiates a transport that knows how to start the server process
  • Creates a Client object and connects it to the transport
  • Calls listTools, listResources, listPrompts to discover server capabilities
  • Calls callTool, readResource, getPrompt to invoke them

Example client

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["build/index.js"]
});

const client = new Client({ name: "example-client", version: "1.0.0" });
await client.connect(transport);

// List capabilities
const tools = await client.listTools();
const resources = await client.listResources();
const prompts = await client.listPrompts();

// Call a tool
const result = await client.callTool({
  name: "add",
  arguments: { a: 5, b: 3 }
});

// Read a resource
const resource = await client.readResource({ uri: "greeting://world" });

// Get a prompt
const prompt = await client.getPrompt({
  name: "review-code",
  arguments: { code: "console.log('hello')" }
});

Step-by-step walkthrough

1

Import the libraries

You need two things: the Client class and a transport. For local servers, use stdio transport. For HTTP servers, use SSE or Streamable HTTP transport.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2

Instantiate client and transport

The stdio transport takes command and args — the exact command used to start the server process. The client connects through that transport.
const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"]
});

const client = new Client({ name: "example-client", version: "1.0.0" });
await client.connect(transport);
3

List server capabilities

const tools = await client.listTools();
const resources = await client.listResources();
const prompts = await client.listPrompts();
4

Invoke features

// Call a tool
const result = await client.callTool({
  name: "add",
  arguments: { a: 5, b: 3 }
});

// Read a resource
const resource = await client.readResource({
  uri: "greeting://world"
});

// Get a prompt
const promptResult = await client.getPrompt({
  name: "review-code",
  arguments: { code: "console.log('hello')" }
});
5

Run the client

Add to package.json scripts: "client": "tsc && node build/client.js", then:
npm run client
You should see output similar to:
Prompt: Please review this code: console.log("hello");
Resource template: file
Tool result: { content: [ { type: 'text', text: '9' } ] }

Key takeaways

  • A client can discover and invoke any capability on a server — tools, resources, and prompts.
  • The stdio transport starts the server as a child process, so you don’t need to manage it separately.
  • A programmatic client is ideal for automated workflows and CI/CD pipelines.
  • In the next lesson, you’ll add an LLM to the client so users interact via natural language.

Build docs developers (and LLMs) love