開發一個 MCP 協議客戶端
MCP 並不侷限於 Claude 桌面,它可以用任何支持它的其他 LLM 客戶端使用。考慮到這一點,我們決定構建一個 MCP CLI 客戶端來展示這一點。這個 MCP 客戶端可以更快速地測試 MCP 服務器。
模型上下文協議(MCP)在 AI 領域持續獲得關注,自從 Neon MCP 服務器發佈以來(大約兩週前),社區已經在廣泛的領域內構建了數十個這些服務器 [鏈接]。然而,Claude 桌面應用程序已經成爲默認的 MCP 客戶端,大多數服務器都有專門的說明來指導如何與該客戶端集成。
但 MCP 並不侷限於 Claude 桌面,它可以用任何支持它的其他 LLM 客戶端使用。考慮到這一點,我們決定構建一個 MCP CLI 客戶端來展示這一點。這個 MCP 客戶端可以更快速地測試 MCP 服務器。
1、如何構建 MCP 客戶端
所有 MCP 客戶端都基於相同的原理並遵循相同的協議。對於工具使用(我們的用例),需要實現的主要概念如下:
1.1 MCP 服務器連接
第一步是連接到 MCP 服務器,以便它可以發現和使用服務器上的工具。
const mcpClient = new Client(
{ name: 'cli-client', version: '1.0.0' },
{ capabilities: {} },
);
// 此函數將MCP客戶端連接到MCP服務器
await mcpClient.connect(new StdioClientTransport(serverConfig));
1.2 工具列表
我們需要從 MCP 服務器獲取可用工具。這允許 LLM 知道在交互過程中可以使用哪些工具
// 此函數將返回MCP服務器上可用工具的列表
const toolsAvailable = await this.mcpClient.request(
{ method: 'tools/list' },
ListToolsResultSchema,
);
1.3 工具使用
一旦 LLM 決定使用哪個工具,我們需要調用 MCP 服務器上的工具處理程序。
// 此函數將在MCP服務器上調用工具處理程序
const toolResult = await this.mcpClient.request(
{
method: 'tools/call',
params: {
name: toolName,
arguments: toolArgs,
},
},
CallToolResultSchema,
);
1.4 LLM 集成
這是一個多步驟的過程,將 LLM 連接到可用工具:
-
向 LLM 發送初始提示
-
等待 LLM 響應工具使用
-
在 MCP 服務器上調用工具處理程序
-
將工具結果注入 LLM 的上下文中
-
向 LLM 發送下一個提示
由於我們正在使用來自 Anthropic API 的工具 API,如果僅僅依賴他們的官方 SDK,則會簡單得多。
// 1- 發送初始提示
const response = await this.anthropicClient.messages.create({
messages: [
{
role: 'user',
content: 'Can you list my Neon projects?',
},
],
model: 'claude-3-5-sonnet-20241022',
max_tokens: 8192,
tools: this.tools,
});
for (const content of response.content) {
// 2- 等待LLM響應工具使用
if (content.type === 'tool_use') {
const toolName = content.name;
const toolArgs = content.input;
// 3- 在MCP服務器上調用工具處理程序
const toolResult = await this.mcpClient.request(
{
method: 'tools/call',
params: {
name: toolName,
arguments: toolArgs,
},
},
CallToolResultSchema,
);
// 4- 將工具結果注入LLM的上下文中
const contextWithToolResult = [
...previousMessages,
{ role: 'user', content: toolResult.content },
];
// 5- 向LLM發送下一個提示
const nextResponse = await this.anthropicClient.messages.create({
messages: contextWithToolResult,
model: 'claude-3-5-sonnet-20241022',
max_tokens: 8192,
});
}
}
2、構建 CLI 客戶端
一旦我們有了所有核心組件,我們只需要構建一個酷炫的 CLI 客戶端,可以用來與 MCP 服務器交互。
2.1 LLM 處理
處理 LLM 消息和工具使用。重要的是我們要在每次交互之間保留消息,以便可以將工具結果注入 LLM 的上下文中。
private async processQuery(query: string) {
try {
// 1 - 向LLM發送用戶的查詢
this.messages.push({ role: 'user', content: query });
const response = await this.anthropicClient.messages.create({
messages: this.messages,
model: 'claude-3-5-sonnet-20241022',
tools: this.tools,
});
// 2 - 處理LLM響應
for (const content of response.content) {
if (content.type === 'text') {
process.stdout.write(content.text);
}
// 3 - 處理工具使用
if (content.type === 'tool_use') {
const toolResult = await this.mcpClient.request({
method: 'tools/call',
params: {
name: content.name,
arguments: content.input,
}
});
// 4 - 將工具結果添加到對話中
this.messages.push({
role: 'user',
content: JSON.stringify(toolResult)
});
// 5 - 獲取LLM對工具結果的響應
const nextResponse = await this.anthropicClient.messages.create({
messages: this.messages,
model: 'claude-3-5-sonnet-20241022'
});
// 6 - 顯示LLM的響應
if (nextResponse.content[0].type === 'text') {
process.stdout.write(nextResponse.content[0].text);
}
}
}
} catch (error) {
console.error('Error during query processing:', error);
}
}
2.2 聊天循環
創建一個聊天循環,用於向 LLM 發送消息並處理響應。
private async chat_loop() {
while (true) {
try {
const query = (await this.rl.question(styles.prompt)).trim();
// 處理查詢
await this.processQuery(query);
} catch (error) {
console.error(styles.error('\\\\nError:'), error);
}
}
}
2.3 入口點
設置客戶端的主入口點,初始化 MCP 客戶端,獲取工具並啓動聊天循環
// 這是客戶端的主入口點
async start() {
try {
console.log(styles.info('🤖 Interactive Claude CLI'));
console.log(
styles.info(`Type your queries or "${EXIT_COMMAND}" to exit`),
);
// 1 - 將MCP客戶端連接到MCP服務器
await this.mcpClient.connect(this.transport);
// 2 - 獲取MCP服務器上可用的工具
await this.initMCPTools();
// 3 - 啓動聊天循環
await this.chat_loop();
} catch (error) {
console.error(styles.error('Failed to initialize tools:'), error);
process.exit(1);
} finally {
this.rl.close();
process.exit(0);
}
}
2.4 運行
現在我們已經構建了一個通用的 MCP 客戶端,可以通過傳遞 MCP 服務器 URL 和其他所需參數來運行它。
const cli = new InteractiveCLI({
command: '../dist/index.js',
args: ['start', process.env.NEON_API_KEY!],
});
cli.start();
2.5 改進
這個簡單實現有兩個主要缺點:
-
流式傳輸:這個客戶端不支持流式傳輸,因此從用戶角度來看,響應可能會顯得慢一些。
-
多次工具調用:這個客戶端不會跟進多次工具調用,它總是在第一次工具調用後停止。
幸運的是,這兩個問題已經在我們在 Neon 構建的 MCP 客戶端 CLI 中得到了解決。
參考:https://neon.tech/blog/building-a-cli-client-for-model-context-protocol-servers
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/MyJx-CaGRz9H6N-Eb4oSEQ