嗨大家,我是 Debuguy。
前面七天我們建了一個能多人協作的 Slack ChatBot 架構,但說實話,雖然我們做了一個 ChatBot,但目的上來說不是要做一個只能聊天的機器人。
真正的目標是要讓它能解決實際問題。
我們花了這麼多心力讓 LLM 學會「對話」,但 LLM 本質上還是被困在預先訓練的世界裡:
「就像一個超級聰明但被綁在椅子上的人,只能動嘴巴不能動手...」
我們需要的不是會聊天的 Slack Bot,而是能真正解決問題的助手。
MCP(Model Context Protocol)是讓 LLM 能「動手做事」的標準化協議。簡單說就是給 LLM 裝上各種「外掛工具」的介面。
雖然概念不新,但 MCP 的標準化讓整個生態系變得更有秩序。不管只是 Claude Desktop 還是其他支援 MCP 的平台,都能共用相同的工具。
在所有可能的 MCP Server 中,我選 Playwright 有幾個原因:
1. 立竿見影的效果
從「我無法查看網頁」變成能實際執行網頁操作和資訊查詢。
2. 生活化的應用場景
比如查詢颱風假資訊、查看網站狀態等日常工作需求。
3. 除錯友善
開發期間可以用 non-headless 方式啟動 Playwright 提供視覺化的執行過程,出問題時容易診斷。
先建立 config/mcp-config.json
:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp",
"--no-sandbox",
"--isolated"
"--browser",
"chrome"
]
}
}
}
// src/index.ts
import { readFileSync } from 'fs';
import { join } from 'path';
import { createMcpHost } from '@genkit-ai/mcp';
async function startGenkitFlow() {
// 省略
// 載入 MCP 配置
const host = createMcpHost(
JSON.parse(readFileSync(join(process.cwd(), 'config/mcp-config.json'), 'utf-8'))
);
const chatFlow = ai.defineFlow({
// 省略
},
async ({ messages }) => {
const { newMessages, history } = organizeMessages(messages);
// 取得 MCP 工具和資源
const tools = await host.getActiveTools(ai);
const resources = await host.getActiveResources(ai);
return (await ai.prompt('chatbot')({
botUserId: process.env['SLACK_BOT_USER_ID']!,
prompt: newMessages,
}, {
messages: history,
tools, // 注入工具
resources, // 注入資源
toolChoice: 'auto', // 讓 LLM 自動決定是否使用工具
})).text;
}
);
// ... rest of the code
}
程式碼的重點有兩個:
toolChoice: 'auto'
讓 LLM 根據需求自動決定是否使用工具讓我們看看實際的效果。
當 MCP 工具註冊後,Developer UI 可以查看:
每次 MCP 工具使用都會留下詳細記錄:
今天我們成功讓 ChatBot 具備了真正的行動能力:
技術架構的完善:
使用體驗的革命性提升:
「現在我的 Bot 終於不再是只會聊天了!」
明天我們要來解決另一個重要的使用者體驗問題:如何讓 AI 的思考過程變得透明?
當 AI 在執行複雜任務時,使用者往往不知道它在做什麼,這種「黑盒子」的等待體驗很糟糕,尤其有時候要等到結果需要三十秒以上的時候,會不知道 application 是不是壞了!明天我們就要來著手解決這個問題。
完整的原始碼在這裡,包含 MCP 配置和 Playwright 工具整合!
如果框架不支援 MCP 怎麼辦?
在這個專案的早期,我使用的是 @google/generative-ai
套件,結果開發到一半發現這個套件被 Deprecated 了!
「當下的心情就像打開冰箱拿飲料想暢飲一番才發現過期了一樣...」
而且更慘的是,這個舊套件根本不支援 MCP。
但當時對我來說不是大問題,因為 MCP 的本質就是 Function Calling 的標準化協議。
MCP 實際上定義了三個核心概念:
既然本質上是 Function Calling,我們完全可以自己實作轉換!
我們可以使用 @modelcontextprotocol/sdk
來連接 MCP Server,然後自己轉換成對應框架的 Function Calling 格式。
以下程式碼皆由 AI 生成,各位客官就當虛擬碼看看吧
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
async function createMcpTools() {
// 1. 連接到 MCP Server
const client = new Client({
name: 'my-slack-bot',
version: '1.0.0',
});
await client.connect(playwrightMcpServer);
// 2. 取得 MCP Server 的工具清單
const { tools } = await client.listTools();
// 3. 轉換成 Function Calling 格式
const functionDeclarations = tools.map(tool => ({
name: tool.name,
description: tool.description,
parameters: tool.inputSchema, // MCP 的 JSON Schema 格式
}));
return {
tools: functionDeclarations,
executeFunction: async (name: string, args: any) => {
// 4. 實際執行 MCP 工具
const result = await client.callTool({
name,
arguments: args,
});
return result.content;
}
};
}
實際使用(以舊版 Google SDK 為例):
// 這是請 AI 寫的範例,但概念上是這樣沒錯
import { GoogleGenerativeAI } from '@google/generative-ai';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
async function chatWithMcp(userMessage: string) {
const { tools, executeFunction } = await createMcpTools();
// 建立支援 Function Calling 的模型
const model = genAI.getGenerativeModel({
model: 'gemini-2.5-flash-lite',
tools: [{ functionDeclarations: tools }], // 注入轉換後的工具
});
const chat = model.startChat();
let result = await chat.sendMessage(userMessage);
// 處理 Function Calling
let response = result.response;
if (response.functionCalls()) {
const functionCalls = response.functionCalls();
for (const call of functionCalls) {
// 執行 MCP 工具
const functionResult = await executeFunction(call.name, call.args);
// 把結果回傳給 LLM
result = await chat.sendMessage([{
functionResponse: {
name: call.name,
response: { content: functionResult }
}
}]);
}
}
return result.response.text();
}
--
AI 的發展變化很快,目前這個想法以及專案也還在實驗中。但也許透過這個過程大家可以有一些經驗和想法互相交流,歡迎大家追蹤這個系列。
也歡迎追蹤我的 Threads @debuguy.dev