Who this is for and what you will get
This guide is for readers who already have a small MCP server and want a script or program to call it, instead of clicking through a graphical tool each time. By the end you should be able to start a server from code, list what it offers, and call one of its tools. [1]
What an MCP client does
An MCP client is a custom application or script that communicates directly with an MCP server to request its resources, tools and prompts. The Inspector provides a graphical interface for working with a server by hand. A client you write yourself makes the same kinds of requests from code, which makes it possible to automate tasks and build the calls into your own workflow. [1]
Writing a client takes four steps: import the libraries, create a client and connect it to a transport, decide which resources, tools and prompts to list, and integrate the client into a host application so that a user's request invokes the matching server feature. This guide covers what a client can do, how to write one, and how to use it to confirm that a server behaves as expected. [1]
Choosing a transport
This guide uses the stdio transport, which is meant for programs running on your own machine. SSE is the other option. [1] In the stdio transport, command and args tell the client where to find the server and how to start it, so the client launches the server process itself. A client can also connect to a server that is already running. [1]
TypeScript: connect, list and call
First, import Client from the SDK's client/index.js and StdioClientTransport from client/stdio.js. [1]
Next, create the transport and the client, then connect them. The client is created with a name and a version, here example-client and 1.0.0:
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);After this runs, the client is connected over stdio to server.js. [1]
To see what the server offers, call listPrompts, listResources and listTools:
// List prompts
const prompts = await client.listPrompts();
// List resources
const resources = await client.listResources();
// list tools
const tools = await client.listTools();To call a tool, pass its name and its arguments:
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});Prompts are fetched with getPrompt, which also takes a name and arguments. The example requests review-code with a code argument, matching a prompt the server declares with code: z.string():
const promptResult = await client.getPrompt({
name: "review-code",
arguments: {
code: "console.log(\"Hello world\")"
}
})To run it, add a client script to package.json that runs tsc && node build/client.js, and then runs npm run client. [1]
Python: the same steps
The Python version describes how to start the server with StdioServerParameters. The executable is mcp, the arguments are run and server.py, and env is optional:
server_params = StdioServerParameters(
command="mcp", # Executable
args=["run", "server.py"], # Optional command line arguments
env=None, # Optional environment variables
)Then stdio_client opens the connection, ClientSession wraps it, and session.initialize() initializes the connection:
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Initialize the connection
await session.initialize()Resources and tools are listed with session.list_resources() and session.list_tools():
# List available resources
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# List available tools
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)Resources are read with read_resource and tools are called with call_tool. The example reads greeting://hello and calls the add tool:
# Read a resource
print("READING RESOURCE")
content, mime_type = await session.read_resource("greeting://hello")
# Call a tool
print("CALL TOOL")
result = await session.call_tool("add", arguments={"a": 1, "b": 7})
print(result.content)Run the client with python client.py. [1]
The server the client calls
The client needs a server to talk to. The exercise server offers an addition tool, add, and a greeting resource, greeting://{name}. The TypeScript version declares the tool with server.tool:
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);The Python version uses FastMCP and decorators:
# server.py
from mcp.server.fastmcp import FastMCP
# Create an MCP server
mcp = FastMCP("Demo")
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"The arguments and, in some cases, the names used in a call must match what the server declares. [1]
Common problems and limits
- Names and arguments that do not match. Tool names, prompt names, resource URIs and arguments in the client must agree with the server's declarations. [1]
- Start the server first for Java. The Java example uses SSE to reach
http://localhost:8080, so the MCP server must already be running there. [1] - The client is deliberately simple. It calls features only when the code asks, and no model decides what to call. [1]
- Commands differ by language. The .NET client is started with
dotnet run, and its transport'sArgumentscan point to a.csprojfile or to the executable. [1]
Why list before you call
The approach is to discover first and call second. Listing tools, prompts and resources shows the client what the server actually offers, and only then does the code call a feature by name and arguments. In a script, this lets the code check the server's real capabilities rather than relying on assumptions written into the code. A client can both discover and invoke server features. [1]
How resource URIs match templates
The URI passed to a read has to match a template the server declares. In the example, file://example.txt matches the server's file://{name} template, and example.txt is mapped to the name parameter. The Python exercise server declares its greeting resource with @mcp.resource("greeting://{name}") in the same way. [1]
Clients and the Inspector side by side
The Inspector suits manual testing: browse the tool list, fill in arguments by hand, and read the result. A client suits repeating the same calls from code or embedding them in your own program. Both are ways to test a server's capabilities, so it is reasonable to explore with the Inspector first and then pin the calls down in a client. [1]
Next steps
Use the Inspector to check a server's tools and resources, then write a client that calls them; using both is the quickest way to test. The repository's solution folder has complete clients and servers for TypeScript, Java, Python, .NET and Rust, each with its own README, to compare against. [1]
Adapted from lesson 2 of the introductory section of Microsoft's open-source course *mcp-for-beginners*, "Creating a client"[1], released under the MIT License.
Sources
- 1Creating a clientMicrosoft mcp-for-beginners (MIT)
Cite this page: Building an MCP client. TellWorld, https://www.tellworld.net/practice/practice-mcp-client. Updated 2026-11-14