# Creating a client Clients are custom applications or scripts that communicate directly with an MCP Server to request resources, tools, and prompts. Unlike using the inspector tool, which provides a graphical interface for interacting with the server, writing your own client allows for programmatic and automated interactions. This enables developers to integrate MCP capabilities into their own workflows, automate tasks, and build custom solutions tailored to specific needs. ## Overview This lesson introduces the concept of clients within the Model Context Protocol (MCP) ecosystem. You'll learn how to write your own client and have it connect to an MCP Server. ## Learning Objectives By the end of this lesson, you will be able to: - Understand what a client can do. - Write your own client. - Connect and test the client with an MCP server to ensure the latter works as expected. ## What goes into writing a client? To write a client, you'll need to do the following: - **Import the correct libraries**. You'll be using the same library as before, just different constructs. - **Instantiate a client**. This will involve creating a client instance and connect it to the chosen transport method. - **Decide on what resources to list**. Your MCP server comes with resources, tools and prompts, you need to decide which one to list. - **Integrate the client to a host application**. Once you know the capabilities of the server you need to integrate this your host application so that if a user types a prompt or other command the corresponding server feature is invoked. Now that we understand at high level what we're about to do, let's look at an example next. ### An example client Let's have a look at this example client: ### TypeScript ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const transport = new StdioClientTransport({ command: "node", args: ["server.js"] }); const client = new Client( { name: "example-client", version: "1.0.0" } ); await client.connect(transport); // List prompts const prompts = await client.listPrompts(); // Get a prompt const prompt = await client.getPrompt({ name: "example-prompt", arguments: { arg1: "value" } }); // List resources const resources = await client.listResources(); // Read a resource const resource = await client.readResource({ uri: "file:///example.txt" }); // Call a tool const result = await client.callTool({ name: "example-tool", arguments: { arg1: "value" } }); ``` In the preceding code we: - Import the libraries - Create an instance of a client and connect it using stdio for transport. - List prompts, resources and tools and invoke them all. There you have it, a client that can talk to an MCP Server. Let's take our time in the next exercise section and break down each code snippet and explain what's going on. ## Exercise: Writing a client As said above, let's take our time explaining the code, and by all means code along if you want. ### -1- Import the libraries Let's import the libraries we need, we will need references to a client and to our chosen transport protocol, stdio. stdio is a protocol for things meant to run on your local machine. SSE is another transport protocol we will show in future chapters but that's your other option. For now though, let's continue with stdio. #### TypeScript ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; ``` #### Python ```python from mcp import ClientSession, StdioServerParameters, types from mcp.client.stdio import stdio_client ``` #### .NET ```csharp using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using ModelContextProtocol.Client; ``` #### Java For Java, you'll create a client that connects to the MCP server from the previous exercise. Using the same Java Spring Boot project structure from [Getting Started with MCP Server](../01-first-server/solution/java), create a new Java class called `SDKClient` in the `src/main/java/com/microsoft/mcp/sample/client/` folder and add the following imports: ```java import java.util.Map; import org.springframework.web.reactive.function.client.WebClient; import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; ``` #### Rust You will need to add the following dependencies to your `Cargo.toml` file. ```toml [package] name = "calculator-client" version = "0.1.0" edition = "2024" [dependencies] rmcp = { version = "0.5.0", features = ["client", "transport-child-process"] } serde_json = "1.0.141" tokio = { version = "1.46.1", features = ["rt-multi-thread"] } ``` From there, you can import the necessary libraries in your client code. ```rust use rmcp::{ RmcpError, model::CallToolRequestParam, service::ServiceExt, transport::{ConfigureCommandExt, TokioChildProcess}, }; use tokio::process::Command; ``` Let's move on to instantiation. ### -2- Instantiating client and transport We will need to create an instance of the transport and that of our client: #### TypeScript ```typescript const transport = new StdioClientTransport({ command: "node", args: ["server.js"] }); const client = new Client( { name: "example-client", version: "1.0.0" } ); await client.connect(transport); ``` In the preceding code we've: - Created an stdio transport instance. Note how it specifies command and args for how to find and start up the server as that's something we will need to do as we create the client. ```typescript const transport = new StdioClientTransport({ command: "node", args: ["server.js"] }); ``` - Instantiated a client by giving it a name and version. ```typescript const client = new Client( { name: "example-client", version: "1.0.0" }); ``` - Connected the client to the chosen transport. ```typescript await client.connect(transport); ``` #### Python ```python from mcp import ClientSession, StdioServerParameters, types from mcp.client.stdio import stdio_client # Create server parameters for stdio connection server_params = StdioServerParameters( command="mcp", # Executable args=["run", "server.py"], # Optional command line arguments env=None, # Optional environment variables ) async def run(): async with stdio_client(server_params) as (read, write): async with ClientSession( read, write ) as session: # Initialize the connection await session.initialize() if __name__ == "__main__": import asyncio asyncio.run(run()) ``` In the preceding code we've: - Imported the needed libraries - Instantiated a server parameters object as we will use this to run the server so we can connect to it with our client. - Defined a method `run` that in turn calls `stdio_client` which starts a client session. - Created an entry point where we provide the `run` method to `asyncio.run`. #### .NET ```dotnet using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using ModelContextProtocol.Client; var builder = Host.CreateApplicationBuilder(args); builder.Configuration .AddEnvironmentVariables() .AddUserSecrets(); var clientTransport = new StdioClientTransport(new() { Name = "Demo Server", Command = "dotnet", Arguments = ["run", "--project", "path/to/file.csproj"], }); await using var mcpClient = await McpClient.CreateAsync(clientTransport); ``` In the preceding code we've: - Imported the needed libraries. - Create an stdio transport and created a client `mcpClient`. The latter is something we will use to list and invoke features on the MCP Server. Note, in "Arguments", you can either point to the *.csproj* or to the executable. #### Java ```java public class SDKClient { public static void main(String[] args) { var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080")); new SDKClient(transport).run(); } private final McpClientTransport transport; public SDKClient(McpClientTransport transport) { this.transport = transport; } public void run() { var client = McpClient.sync(this.transport).build(); client.initialize(); // Your client logic goes here } } ``` In the preceding code we've: - Created a main method that sets up an SSE transport pointing to `http://localhost:8080` where our MCP server will be running. - Created a client class that takes the transport as a constructor parameter. - In the `run` method, we create a synchronous MCP client using the transport and initialize the connection. - Used SSE (Server-Sent Events) transport which is suitable for HTTP-based communication with Java Spring Boot MCP servers. #### Rust Note this Rust client assumes the server is a sibling project named "calculator-server" in the same directory. The code below will start the server and connect to it. ```rust async fn main() -> Result<(), RmcpError> { // Assume the server is a sibling project named "calculator-server" in the same directory let server_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .expect("failed to locate workspace root") .join("calculator-server"); let client = () .serve( TokioChildProcess::new(Command::new("cargo").configure(|cmd| { cmd.arg("run").current_dir(server_dir); })) .map_err(RmcpError::transport_creation::)?, ) .await?; // TODO: Initialize // TODO: List tools // TODO: Call add tool with arguments = {"a": 3, "b": 2} client.cancel().await?; Ok(()) } ``` ### -3- Listing the server features Now, we have a client that can connect to should the program be run. However, it doesn't actually list its features so let's do that next: #### TypeScript ```typescript // List prompts const prompts = await client.listPrompts(); // List resources const resources = await client.listResources(); // list tools const tools = await client.listTools(); ``` #### Python ```python # 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) ``` Here we list the available resources, `list_resources()` and tools, `list_tools` and print them out. #### .NET ```dotnet foreach (var tool in await client.ListToolsAsync()) { Console.WriteLine($"{tool.Name} ({tool.Description})"); } ``` Above is an example how we can list the tools on the server. For each tool, we then print out its name. #### Java ```java // List and demonstrate tools ListToolsResult toolsList = client.listTools(); System.out.println("Available Tools = " + toolsList); // You can also ping the server to verify connection client.ping(); ``` In the preceding code we've: - Called `listTools()` to get all available tools from the MCP server. - Used `ping()` to verify that the connection to the server is working. - The `ListToolsResult` contains information about all tools including their names, descriptions, and input schemas. Great, now we've captures all the features. Now the question is when do we use them? Well, this client is pretty simple, simple in the sense that we will need to explicitly call the features when we want them. In the next chapter, we will create a more advanced client that has access to it's own large language model, LLM. For now though, let's see how we can invoke the features on the server: #### Rust In the main function, after initializing the client, we can initialize the server and list some of its features. ```rust // Initialize let server_info = client.peer_info(); println!("Server info: {:?}", server_info); // List tools let tools = client.list_tools(Default::default()).await?; println!("Available tools: {:?}", tools); ``` ### -4- Invoke features To invoke the features we need to ensure we specify the correct arguments and in some cases the name of what we're trying to invoke. #### TypeScript ```typescript // Read a resource const resource = await client.readResource({ uri: "file:///example.txt" }); // Call a tool const result = await client.callTool({ name: "example-tool", arguments: { arg1: "value" } }); // call prompt const promptResult = await client.getPrompt({ name: "review-code", arguments: { code: "console.log(\"Hello world\")" } }) ``` In the preceding code we: - Read a resource, we call the resource by calling `readResource()` specifying `uri`. Here's what it most likely look like on the server side: ```typescript server.resource( "readFile", new ResourceTemplate("file://{name}", { list: undefined }), async (uri, { name }) => ({ contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) ); ``` Our `uri` value `file://example.txt` matches `file://{name}` on the server. `example.txt` will be mapped to `name`. - Call a tool, we call it by specifying its `name` and its `arguments` like so: ```typescript const result = await client.callTool({ name: "example-tool", arguments: { arg1: "value" } }); ``` - Get prompt, to get a prompt, you call `getPrompt()` with `name` and `arguments`. The server code looks like so: ```typescript server.prompt( "review-code", { code: z.string() }, ({ code }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please review this code:\n\n${code}` } }] }) ); ``` and your resulting client code therefore looks like so to match what's declared on the server: ```typescript const promptResult = await client.getPrompt({ name: "review-code", arguments: { code: "console.log(\"Hello world\")" } }) ``` #### Python ```python # 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) ``` In the preceding code, we've: - Called a resource called `greeting` using `read_resource`. - Invoked a tool called `add` using `call_tool`. #### .NET 1. Let's add some code to call a tool: ```csharp var result = await mcpClient.CallToolAsync( "Add", new Dictionary() { ["a"] = 1, ["b"] = 3 }, cancellationToken:CancellationToken.None); ``` 1. To print out the result, here's some code to handle that: ```csharp Console.WriteLine(result.Content.First(c => c.Type == "text").Text); // Sum 4 ``` #### Java ```java // Call various calculator tools CallToolResult resultAdd = client.callTool(new CallToolRequest("add", Map.of("a", 5.0, "b", 3.0))); System.out.println("Add Result = " + resultAdd); CallToolResult resultSubtract = client.callTool(new CallToolRequest("subtract", Map.of("a", 10.0, "b", 4.0))); System.out.println("Subtract Result = " + resultSubtract); CallToolResult resultMultiply = client.callTool(new CallToolRequest("multiply", Map.of("a", 6.0, "b", 7.0))); System.out.println("Multiply Result = " + resultMultiply); CallToolResult resultDivide = client.callTool(new CallToolRequest("divide", Map.of("a", 20.0, "b", 4.0))); System.out.println("Divide Result = " + resultDivide); CallToolResult resultHelp = client.callTool(new CallToolRequest("help", Map.of())); System.out.println("Help = " + resultHelp); ``` In the preceding code we've: - Called multiple calculator tools using `callTool()` method with `CallToolRequest` objects. - Each tool call specifies the tool name and a `Map` of arguments required by that tool. - The server tools expect specific parameter names (like "a", "b" for mathematical operations). - Results are returned as `CallToolResult` objects containing the response from the server. #### Rust ```rust // Call add tool with arguments = {"a": 3, "b": 2} let a = 3; let b = 2; let tool_result = client .call_tool(CallToolRequestParam { name: "add".into(), arguments: serde_json::json!({ "a": a, "b": b }).as_object().cloned(), }) .await?; println!("Result of {:?} + {:?}: {:?}", a, b, tool_result); ``` ### -5- Run the client To run the client, type the following command in the terminal: #### TypeScript Add the following entry to your "scripts" section in *package.json*: ```json "client": "tsc && node build/client.js" ``` ```sh npm run client ``` #### Python Call the client with the following command: ```sh python client.py ``` #### .NET ```sh dotnet run ``` #### Java First, ensure your MCP server is running on `http://localhost:8080`. Then run the client: ```bash # Build you project ./mvnw clean compile # Run the client ./mvnw exec:java -Dexec.mainClass="com.microsoft.mcp.sample.client.SDKClient" ``` Alternatively, you can run the complete client project provided in the solution folder `03-GettingStarted\02-client\solution\java`: ```bash # Navigate to the solution directory cd 03-GettingStarted/02-client/solution/java # Build and run the JAR ./mvnw clean package java -jar target/calculator-client-0.0.1-SNAPSHOT.jar ``` #### Rust ```bash cargo fmt cargo run ``` ## Assignment In this assignment, you'll use what you've learned in creating a client but create a client of your own. Here's a server you can use that you need to call via your client code, see if you can add more features to the server to make it more interesting. ### TypeScript ```typescript import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Create an MCP server const server = new McpServer({ name: "Demo", version: "1.0.0" }); // Add an addition tool server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] }) ); // Add a dynamic greeting resource server.resource( "greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), async (uri, { name }) => ({ contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) ); // Start receiving messages on stdin and sending messages on stdout async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCPServer started on stdin/stdout"); } main().catch((error) => { console.error("Fatal error: ", error); process.exit(1); }); ``` ### Python ```python # 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}!" ``` ### .NET ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ModelContextProtocol.Server; using System.ComponentModel; var builder = Host.CreateApplicationBuilder(args); builder.Logging.AddConsole(consoleLogOptions => { // Configure all logs to go to stderr consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace; }); builder.Services .AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); await builder.Build().RunAsync(); [McpServerToolType] public static class CalculatorTool { [McpServerTool, Description("Adds two numbers")] public static string Add(int a, int b) => $"Sum {a + b}"; } ``` See this project to see how you can [add prompts and resources](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/samples/EverythingServer/Program.cs). Also, check this link for how to invoke [prompts and resources](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol/Client/). ### Rust In the [previous section](../01-first-server), you learned how to create a simple MCP server with Rust. You can continue to build on that or check this link for more Rust-based MCP server examples: [MCP Server Examples](https://github.com/modelcontextprotocol/rust-sdk/tree/main/examples/servers) ## Solution The **solution folder** contains complete, ready-to-run client implementations that demonstrate all the concepts covered in this tutorial. Each solution includes both client and server code organized in separate, self-contained projects. ### 📁 Solution Structure The solution directory is organized by programming language: ```text solution/ ├── typescript/ # TypeScript client with npm/Node.js setup │ ├── package.json # Dependencies and scripts │ ├── tsconfig.json # TypeScript configuration │ └── src/ # Source code ├── java/ # Java Spring Boot client project │ ├── pom.xml # Maven configuration │ ├── src/ # Java source files │ └── mvnw # Maven wrapper ├── python/ # Python client implementation │ ├── client.py # Main client code │ ├── server.py # Compatible server │ └── README.md # Python-specific instructions ├── dotnet/ # .NET client project │ ├── dotnet.csproj # Project configuration │ ├── Program.cs # Main client code │ └── dotnet.sln # Solution file ├── rust/ # Rust client implementation | ├── Cargo.lock # Cargo lock file | ├── Cargo.toml # Project configuration and dependencies | ├── src # Source code | │ └── main.rs # Main client code └── server/ # Additional .NET server implementation ├── Program.cs # Server code └── server.csproj # Server project file ``` ### 🚀 What Each Solution Includes Each language-specific solution provides: - **Complete client implementation** with all features from the tutorial - **Working project structure** with proper dependencies and configuration - **Build and run scripts** for easy setup and execution - **Detailed README** with language-specific instructions - **Error handling** and result processing examples ### 📖 Using the Solutions 1. **Navigate to your preferred language folder**: ```bash cd solution/typescript/ # For TypeScript cd solution/java/ # For Java cd solution/python/ # For Python cd solution/dotnet/ # For .NET ``` 2. **Follow the README instructions** in each folder for: - Installing dependencies - Building the project - Running the client 3. **Example output** you should see: ```text Prompt: Please review this code: console.log("hello"); Resource template: file Tool result: { content: [ { type: 'text', text: '9' } ] } ``` For complete documentation and step-by-step instructions, see: **[📖 Solution Documentation](./solution/README.md)** ## 🎯 Complete Examples We've provided complete, working client implementations for all programming languages covered in this tutorial. These examples demonstrate the full functionality described above and can be used as reference implementations or starting points for your own projects. ### Available Complete Examples | Language | File | Description | |----------|------|-------------| | **Java** | [`client_example_java.java`](./client_example_java.java) | Complete Java client using SSE transport with comprehensive error handling | | **C#** | [`client_example_csharp.cs`](./client_example_csharp.cs) | Complete C# client using stdio transport with automatic server startup | | **TypeScript** | [`client_example_typescript.ts`](./client_example_typescript.ts) | Complete TypeScript client with full MCP protocol support | | **Python** | [`client_example_python.py`](./client_example_python.py) | Complete Python client using async/await patterns | | **Rust** | [`client_example_rust.rs`](./client_example_rust.rs) | Complete Rust client using Tokio for async operations | Each complete example includes: - ✅ **Connection establishment** and error handling - ✅ **Server discovery** (tools, resources, prompts where applicable) - ✅ **Calculator operations** (add, subtract, multiply, divide, help) - ✅ **Result processing** and formatted output - ✅ **Comprehensive error handling** - ✅ **Clean, documented code** with step-by-step comments ### Getting Started with Complete Examples 1. **Choose your preferred language** from the table above 2. **Review the complete example file** to understand the full implementation 3. **Run the example** following the instructions in [`complete_examples.md`](./complete_examples.md) 4. **Modify and extend** the example for your specific use case For detailed documentation about running and customizing these examples, see: **[📖 Complete Examples Documentation](./complete_examples.md)** ### 💡 Solution vs. Complete Examples | **Solution Folder** | **Complete Examples** | |--------------------|--------------------- | | Full project structure with build files | Single-file implementations | | Ready-to-run with dependencies | Focused code examples | | Production-like setup | Educational reference | | Language-specific tooling | Cross-language comparison | Both approaches are valuable - use the **solution folder** for complete projects and the **complete examples** for learning and reference. ## Key Takeaways The key takeaways for this chapter is the following about clients: - Can be used to both discover and invoke features on the server. - Can start a server while it starts itself (like in this chapter) but clients can connect to running servers as well. - Is a great way to test out server capabilities next to alternatives like the Inspector as was described in the previous chapter. ## Additional Resources - [Building clients in MCP](https://modelcontextprotocol.io/quickstart/client) ## Samples - [Java Calculator](../samples/java/calculator/README.md) - [.Net Calculator](../samples/csharp/) - [JavaScript Calculator](../samples/javascript/README.md) - [TypeScript Calculator](../samples/typescript/README.md) - [Python Calculator](../samples/python/) - [Rust Calculator](../samples/rust/) ## What's Next - Next: [Creating a client with an LLM](../03-llm-client/README.md)