Client là ứng dụng hoặc script tuỳ chỉnh giao tiếp trực tiếp với MCP server để yêu cầu resource, tool và prompt. Khác với dùng Inspector, vốn có giao diện đồ hoạ để tương tác với server, tự viết client cho phép tương tác bằng lập trình và tự động. Nhờ đó lập trình viên đưa được khả năng (capability) của MCP vào quy trình (workflow) của mình, tự động hoá tác vụ và xây giải pháp theo nhu cầu riêng.
Tổng quan
Bài này giới thiệu khái niệm client trong hệ sinh thái Model Context Protocol (MCP). Bạn sẽ học cách viết client của riêng mình và kết nối nó tới một MCP server.
Mục tiêu học tập
Sau bài này, bạn sẽ có thể:
- Hiểu client làm được gì.
- Viết client của riêng bạn.
- Kết nối và kiểm thử client với một MCP server để đảm bảo server hoạt động như mong đợi.
Viết client gồm những gì?
Để viết client, bạn cần:
- Import đúng thư viện. Bạn dùng cùng thư viện như trước, chỉ khác các thành phần.
- Khởi tạo client. Tạo một instance client và kết nối nó với cơ chế truyền tải (transport) đã chọn.
- Quyết định liệt kê resource nào. MCP server có resource, tool và prompt; bạn cần quyết định liệt kê những gì.
- Tích hợp client vào ứng dụng host. Khi đã biết capability của server, bạn tích hợp vào ứng dụng host để khi người dùng gõ prompt hoặc lệnh khác thì tính năng tương ứng của server được gọi.
Đã hiểu tổng quan việc sắp làm, hãy xem một ví dụ.
Ví dụ một client
Đây là một client mẫu bằng 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"
}
});
Trong code trên, chúng ta:
- Import các thư viện.
- Tạo một instance client và kết nối bằng transport stdio.
- Liệt kê prompt, resource, tool và gọi tất cả chúng.
Vậy là xong, một client nói chuyện được với MCP server. Ở phần bài thực hành dưới đây, chúng ta sẽ phân tích từng đoạn code.
Bài thực hành: viết client
Hãy từ từ giải thích code, và bạn cứ viết theo nếu muốn.
Bước 1: import thư viện
Hãy import các thư viện cần thiết: tham chiếu tới client và tới giao thức truyền tải đã chọn là stdio. stdio là giao thức cho những thứ chạy trên máy local. SSE là một giao thức truyền tải khác sẽ được giới thiệu ở các bài sau, đó là lựa chọn còn lại. Còn bây giờ, hãy tiếp tục với stdio.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
Với Java, bạn tạo một client kết nối tới MCP server của bài trước. Dùng cùng cấu trúc project Java Spring Boot từ bài MCP server đầu tiên, tạo một lớp Java mới tên SDKClient trong thư mục src/main/java/com/microsoft/mcp/sample/client/ và thêm các import sau:
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;
Bạn cần thêm các dependency sau vào file Cargo.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"] }
Sau đó import các thư viện cần thiết trong code client:
use rmcp::{
RmcpError,
model::CallToolRequestParam,
service::ServiceExt,
transport::{ConfigureCommandExt, TokioChildProcess},
};
use tokio::process::Command;
Tiếp theo là phần khởi tạo.
Bước 2: khởi tạo client và transport
Chúng ta cần tạo một instance transport và một instance client:
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);
Trong code trên, chúng ta:
-
Tạo một instance transport stdio. Để ý nó chỉ định command và args để tìm và khởi động server, vì đó là việc client cần làm.
typescriptconst transport = new StdioClientTransport({ command: "node", args: ["server.js"] }); -
Khởi tạo client với tên và phiên bản.
typescriptconst client = new Client( { name: "example-client", version: "1.0.0" }); -
Kết nối client với transport đã chọn.
typescriptawait client.connect(transport);
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())
Trong code trên, chúng ta:
- Import các thư viện cần thiết.
- Khởi tạo một đối tượng tham số server, dùng để chạy server cho client kết nối tới.
- Định nghĩa phương thức
run, gọistdio_clientđể bắt đầu một phiên client. - Tạo điểm vào chương trình, truyền phương thức
runchoasyncio.run.
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration
.AddEnvironmentVariables()
.AddUserSecrets<Program>();
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);
Trong code trên, chúng ta:
- Import các thư viện cần thiết.
- Tạo transport stdio và tạo client
mcpClient, dùng để liệt kê và gọi các tính năng trên MCP server.
Lưu ý, ở "Arguments" bạn có thể trỏ tới file .csproj hoặc tới file thực thi.
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
}
}
Trong code trên, chúng ta:
- Tạo phương thức main thiết lập transport SSE trỏ tới
http://localhost:8080, nơi MCP server chạy. - Tạo lớp client nhận transport làm tham số constructor.
- Trong phương thức
run, tạo một MCP client đồng bộ dùng transport và khởi tạo kết nối. - Dùng transport SSE (Server-Sent Events), phù hợp giao tiếp qua HTTP với MCP server Java Spring Boot.
Client Rust này giả định server là một project "anh em" tên "calculator-server" trong cùng thư mục. Code dưới sẽ khởi động server và kết nối tới nó.
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::<TokioChildProcess>)?,
)
.await?;
// TODO: Initialize
// TODO: List tools
// TODO: Call add tool with arguments = {"a": 3, "b": 2}
client.cancel().await?;
Ok(())
}
Bước 3: liệt kê tính năng của server
Giờ ta có một client kết nối được khi chạy chương trình. Nhưng nó chưa liệt kê tính năng của server, hãy làm việc đó:
// List prompts
const prompts = await client.listPrompts();
// List resources
const resources = await client.listResources();
// list tools
const tools = await client.listTools();
# 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)
Ở đây ta liệt kê các resource có sẵn bằng list_resources() và tool bằng list_tools, rồi in ra.
foreach (var tool in await client.ListToolsAsync())
{
Console.WriteLine($"{tool.Name} ({tool.Description})");
}
Trên đây là ví dụ liệt kê các tool trên server. Với mỗi tool, ta in ra tên của nó.
// 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();
Trong code trên, chúng ta:
- Gọi
listTools()để lấy mọi tool có sẵn từ MCP server. - Dùng
ping()để kiểm tra kết nối tới server hoạt động. ListToolsResultchứa thông tin về mọi tool, gồm tên, mô tả và input schema.
Trong hàm main, sau khi khởi tạo client, ta có thể khởi tạo server và liệt kê một số tính năng của nó.
// 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);
Tốt, giờ ta đã nắm được mọi tính năng. Câu hỏi là khi nào dùng chúng? Client này khá đơn giản, theo nghĩa ta phải gọi tường minh các tính năng khi cần. Ở bài sau, chúng ta sẽ tạo client nâng cao hơn, có LLM riêng. Còn bây giờ, hãy xem cách gọi các tính năng trên server.
Bước 4: gọi tính năng
Để gọi tính năng, cần chỉ định đúng đối số và, trong một số trường hợp, tên của thứ cần gọi.
// 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\")"
}
})
Trong code trên, chúng ta:
-
Đọc một resource bằng
readResource()vớiuri. Phía server có thể trông như sau:typescriptserver.resource( "readFile", new ResourceTemplate("file://{name}", { list: undefined }), async (uri, { name }) => ({ contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) );Giá trị
urilàfile://example.txtkhớp vớifile://{name}trên server;example.txtđược ánh xạ vàoname. -
Gọi một tool bằng cách chỉ định
namevàarguments:typescriptconst result = await client.callTool({ name: "example-tool", arguments: { arg1: "value" } }); -
Lấy prompt bằng
getPrompt()vớinamevàarguments. Code server như sau:typescriptserver.prompt( "review-code", { code: z.string() }, ({ code }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please review this code:\n\n${code}` } }] }) );nên code client tương ứng để khớp với khai báo trên server là:
typescriptconst promptResult = await client.getPrompt({ name: "review-code", arguments: { code: "console.log(\"Hello world\")" } })
# 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)
Trong code trên, chúng ta:
- Gọi resource tên
greetingbằngread_resource. - Gọi tool tên
addbằngcall_tool.
-
Thêm code để gọi một tool:
csharpvar result = await mcpClient.CallToolAsync( "Add", new Dictionary<string, object?>() { ["a"] = 1, ["b"] = 3 }, cancellationToken:CancellationToken.None); -
Để in kết quả, dùng đoạn code sau:
csharpConsole.WriteLine(result.Content.First(c => c.Type == "text").Text); // Sum 4
// 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);
Trong code trên, chúng ta:
- Gọi nhiều tool máy tính bằng phương thức
callTool()với các đối tượngCallToolRequest. - Mỗi lời gọi tool chỉ định tên tool và một
Mapđối số mà tool đó cần. - Các tool trên server cần tên tham số cụ thể (như "a", "b" cho phép toán).
- Kết quả trả về là các đối tượng
CallToolResultchứa phản hồi từ server.
// 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);
Bước 5: chạy client
Để chạy client, gõ lệnh sau trong terminal:
Thêm mục sau vào phần "scripts" trong package.json:
"client": "tsc && node build/client.js"
npm run client
Gọi client bằng lệnh:
python client.py
dotnet run
Trước tiên, đảm bảo MCP server đang chạy ở http://localhost:8080. Sau đó chạy client:
# Build you project
./mvnw clean compile
# Run the client
./mvnw exec:java -Dexec.mainClass="com.microsoft.mcp.sample.client.SDKClient"
Hoặc chạy project client hoàn chỉnh trong thư mục lời giải 03-GettingStarted\02-client\solution\java:
# 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
cargo fmt
cargo run
Bài tập
Trong bài tập này, bạn áp dụng những gì đã học để tạo client của riêng mình.
Đây là một server bạn có thể gọi từ code client; hãy thử thêm tính năng cho server để nó thú vị hơn.
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);
});
# 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}!"
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}";
}
Xem project này để biết cách thêm prompt và resource, và link này để biết cách gọi prompt và resource.
Ở bài trước, bạn đã học cách tạo một MCP server đơn giản bằng Rust. Bạn có thể tiếp tục phát triển nó, hoặc xem thêm các ví dụ MCP server bằng Rust tại MCP Server Examples.
Lời giải
Thư mục lời giải chứa các bản cài đặt client hoàn chỉnh, chạy được ngay, minh hoạ mọi khái niệm trong bài. Mỗi lời giải có cả code client lẫn server, tổ chức thành các project độc lập.
Cấu trúc lời giải
Thư mục lời giải được tổ chức theo ngôn ngữ lập trình:
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
Mỗi lời giải gồm gì
Mỗi lời giải theo ngôn ngữ cung cấp:
- Bản cài đặt client hoàn chỉnh với mọi tính năng trong bài
- Cấu trúc project chạy được với dependency và cấu hình đúng
- Script build và chạy để thiết lập, chạy dễ dàng
- README chi tiết hướng dẫn theo từng ngôn ngữ
- Ví dụ xử lý lỗi và xử lý kết quả
Dùng lời giải
-
Vào thư mục ngôn ngữ bạn chọn:
bashcd solution/typescript/ # For TypeScript cd solution/java/ # For Java cd solution/python/ # For Python cd solution/dotnet/ # For .NET -
Làm theo README trong từng thư mục để cài dependency, build project và chạy client.
-
Đầu ra mẫu bạn sẽ thấy:
textPrompt: Please review this code: console.log("hello"); Resource template: file Tool result: { content: [ { type: 'text', text: '9' } ] }
Tài liệu đầy đủ và hướng dẫn từng bước: tài liệu lời giải.
Ví dụ hoàn chỉnh
Khoá gốc cung cấp các bản cài đặt client hoàn chỉnh, chạy được cho mọi ngôn ngữ trong bài. Chúng minh hoạ đầy đủ chức năng mô tả ở trên, dùng làm tham chiếu hoặc điểm xuất phát cho project của bạn.
| Ngôn ngữ | File | Mô tả |
|---|---|---|
| Java | client_example_java.java | Client Java hoàn chỉnh dùng transport SSE, xử lý lỗi đầy đủ |
| C# | client_example_csharp.cs | Client C# hoàn chỉnh dùng transport stdio, tự khởi động server |
| TypeScript | client_example_typescript.ts | Client TypeScript hoàn chỉnh, hỗ trợ đầy đủ giao thức MCP |
| Python | client_example_python.py | Client Python hoàn chỉnh dùng async/await |
| Rust | client_example_rust.rs | Client Rust hoàn chỉnh dùng Tokio cho xử lý bất đồng bộ |
Mỗi ví dụ hoàn chỉnh gồm:
- Thiết lập kết nối và xử lý lỗi
- Khám phá server (tool, resource, prompt nếu có)
- Phép toán máy tính (add, subtract, multiply, divide, help)
- Xử lý kết quả và định dạng đầu ra
- Xử lý lỗi toàn diện
- Code sạch, có tài liệu với chú thích từng bước
Bắt đầu với ví dụ hoàn chỉnh
- Chọn ngôn ngữ từ bảng trên.
- Đọc file ví dụ để hiểu toàn bộ phần cài đặt.
- Chạy ví dụ theo hướng dẫn trong
complete_examples.md. - Sửa và mở rộng ví dụ cho tình huống của bạn.
Thư mục lời giải và ví dụ hoàn chỉnh
| Thư mục lời giải | Ví dụ hoàn chỉnh |
|---|---|
| Cấu trúc project đầy đủ với file build | Cài đặt trong một file |
| Chạy được ngay với dependency | Ví dụ code tập trung |
| Thiết lập gần với production | Tham chiếu để học |
| Công cụ riêng của từng ngôn ngữ | So sánh giữa các ngôn ngữ |
Cả hai cách đều có giá trị: dùng thư mục lời giải cho project hoàn chỉnh và ví dụ hoàn chỉnh để học và tham khảo.
Điểm chính
Những điểm chính của bài về client:
- Dùng được cả để khám phá lẫn gọi các tính năng trên server.
- Có thể khởi động server khi chính nó khởi động (như trong bài này), nhưng client cũng có thể kết nối tới server đang chạy.
- Là cách tuyệt vời để thử các capability của server, bên cạnh các lựa chọn khác như Inspector ở bài trước.
Tài liệu thêm
Mẫu code
- Java Calculator
- .NET Calculator
- JavaScript Calculator
- TypeScript Calculator
- Python Calculator
- Rust Calculator
Bài tiếp theo: Client có LLM.
Nguồn
- Dịch và biên tập từ microsoft/mcp-for-beginners · 03-GettingStarted/02-client/README.md, giấy phép MIT.