iT邦幫忙

2

打造你的第一個 MCP Server:從概念到實作

  • 分享至 

  • xImage
  •  

打造你的第一個 MCP Server:從概念到實作

建議在我的部落格中 https://blog.ray-realms.com/article/73b7d4c5-fc5e-49e2-a7df-c675c4703300 閱讀此文章,以獲取更好的閱讀體驗

開場:為什麼需要 MCP?從真實痛點出發

你是一位熱愛學習的工程師,這些年累積了超過 1000 篇個人筆記,散落在本地資料夾裡。涵蓋前端框架、後端架構、演算法、系統設計⋯⋯每一篇都是你花時間整理的寶貴知識。

某天,你想問 ChatGPT:「幫我找所有關於 React Hooks 的筆記,並整理出我還沒掌握的進階用法。」

這個需求聽起來很合理,對吧?但實際上,要讓 AI 助手做到這件事,困難重重。

AI對大檔案感到困難重重

現有方案都不夠好

讓我們快速看幾個現有的處理方式:

手動複製貼上? 效率極差,每次都要重複,而且 AI 只能看到你貼的部分,無法主動探索更多相關筆記。

批次上傳檔案? 跟剛剛一樣的問題,你得先猜哪些檔案相關,容易遺漏,而且每個新問題都要重新上傳。

手動貼上一堆檔案很麻煩

用 Claude Code 或 Cursor? 它們的能力被綁死在特定工具裡,而且這些工具就是很會 Coding,不太會討論、發想創意。更重要的是,缺乏標準化 —— 每個工具都自己搞一套。

claude

寫個 REST API? 這是最接近的方案,但有個致命問題:REST API 是為「程式」設計的,不是為「LLM」設計的。 LLM 不知道你有哪些 API,需要每次對話都重新被告知規格,還得自己猜該呼叫哪個 endpoint。

這就是 MCP 要解決的問題。


MCP 是什麼?AI 義肢製作手冊

不知道你有沒有看過 Cyberpunk 2077

在這個世界觀上,大部分的人都會安裝一推超酷的義肢,更強壯的手臂、更壯的機械腿...,但這些義肢要能正常運作,有個前提:義肢的接口必須符合標準規格。如果每個義肢廠商都自己搞一套接口,那你的手臂根本裝不上去。

MCP 就像是一本「AI 義肢製作手冊」。

ChatGPT、Claude 這些 AI 會根據這本手冊打造對應的「安裝槽」,而任何人都可以根據這份手冊,設計給 AI 使用的「義肢」,然後直接裝上去。

重點來了:MCP 只是一本製作手冊,用嚴謹的話來講就是:

  • AI 與工具溝通的標準協定
  • 讓 AI 能自動發現工具的機制
  • 標準化的權限和安全邊界

它只決定「接口的部分長怎樣」,確保你做出來的義肢可以順利裝到其他 AI 身上。至於這個義肢有什麼功能?讀檔案、查資料庫、發 email、控制你的智慧家居⋯⋯隨便你,完全自由

與 REST API 的差異

MCP 是為 LLM 設計的標準化協定,讓 AI 能夠自動發現和使用你的工具。

關鍵差異:

特性 REST API MCP
設計對象 給「開發者」用 給「LLM」用
發現機制 需要讀文件 AI 自動發現工具
使用方式 需要寫程式呼叫 AI 自動選擇並呼叫
參數格式 需要查文件 設計 MCP 時,都會定義好

MCP vs REST API

在提到 MCP 之前,必須得提一下什麼是 「Agent」?

這裡快速釐清一下概念。

AI(嚴格來說是 LLM)就是一個只會思考、無法行動的大腦。 對,他只是一顆大腦,沒手沒腳,無法行動。他可以思考得很深入、很精彩,但你問他「幫我查一下天氣」,他只能回答「抱歉我查不了」。

那什麼是 Agent 呢 簡單說,Agent = AI 大腦 + 能力(工具)。給 AI 裝上「手」(查天氣的工具)、「腳」(控制智慧家居的工具),他就從一顆只會想的大腦,變成一個能實際做事的 Agent。

MCP 就是定義「怎麼給 AI 裝義肢」的標準。

如果你對 AI Agent 的概念想要更深入了解,推薦閱讀我之前撰寫的書籍《從零開始,打造一個生成式 AI 平台》https://www.books.com.tw/products/0011029234 ,裡頭有更完整的講解。


MCP 的核心概念:Tools — AI 的「義肢」

如果說 AI 的大腦是語言模型,那麼 Tools 就是 AI 的義肢

沒有義肢,大腦再聰明也只能思考,無法行動。有了 Tools,AI 才能:

  • 獲取資訊 — 讀取你的檔案、查詢資料庫、呼叫 API
  • 執行動作 — 發送郵件、建立文件、修改資料
  • 改變狀態 — 更新設定、記錄日誌、觸發流程

Tools 的三個關鍵特性

1. AI 自己決定什麼時候用

這是 Tools 最重要的特性。你不需要明確指示「請呼叫 XXX 工具」,AI 會根據對話情境自己判斷。 AI 自己決定:

  • 什麼時候用工具
  • 用哪個工具
  • 傳什麼參數

2. Tool 本質就是一個 Function

Tools 就是一個 Function,他執行完後的結果,會送回給 LLM 就像下面這三個 Function

  • list_notes()→ 返回:[note1.md, note2.md, note3.md]
  • read_note(path: "note1.md")→ 返回:筆記的完整內容
  • search_notes(query: "React")→ 返回:包含 "React" 的筆記列表和摘要

每個 Tool 的呼叫都會產生結果,AI 會根據結果決定下一步動作。 這就像是你問助理「幫我查一下檔案」,他真的會去翻檔案櫃。

3. 有明確的規格書(Schema-Defined)

每個 Tool 都有清楚的說明書,你必須替每一個工具定義好以下參數

  • 工具名稱,這個工具叫什麼
  • 工具說明,這個工具是做什麼的
  • 輸入參數,這個工具可以傳入什麼,需要提供什麼參數
  • 輸出參數,這個工具會回傳什麼

基本上還是剛剛提到的,Tool 的本質就是一個 Function,只是你必須詳細交代 LLM 該怎麼用、什麼時會去用這個 Function


練習題

你正在開發一個「個人知識庫」的 MCP Server。下列哪個功能「不應該」設計成 Tool?

A) 搜尋筆記內容(根據關鍵字找出相關筆記)
B) 筆記分類規則(說明你如何組織筆記的 markdown 文件)
C) 建立新筆記(在指定目錄建立 .md 檔案)
D) 統計筆記數量(計算各分類有多少筆記)

正確答案: B) 筆記分類規則(說明你如何組織筆記的 markdown 文件)

工具是一個 Function,是一個可以被執行的行為 筆記分類規則 ,其實就是很單純的文字、命令告訴 LLM 該怎麼做

建議在我的部落格中繼續閱讀

https://blog.ray-realms.com/article/73b7d4c5-fc5e-49e2-a7df-c675c4703300 閱讀此文章,以獲取更好的學習體驗

我的部落格透過 滾動視差 設計,如同我本人一步一步與你講解、教學每一段程式碼

目標:實作第一個 Tool:列出所有筆記

我們要解決什麼問題? 現在我們要讓 AI 能夠「看到」你電腦裡有哪些筆記檔案。 這個工具很簡單:

  • 輸入:不需要參數(列出所有筆記就好)
  • 輸出:每個筆記的名稱 + 路徑
  • 目的:讓 AI 知道有哪些筆記可以讀取

第一步,創建一個簡單的 HTTP 伺服器


💻 建立 Node.js 專案與基本伺服器

🎯 學習目標:

  • 初始化 Node.js 專案並安裝必要套件
  • 建立 Express 伺服器並設定基本路由
  • 配置 TypeScript 環境以支援 MCP 開發

步驟 1: 創建專案資料夾

# 首先,創建一個資料夾當作專案資料夾
# 我這邊就叫做「my-notes-mcp-server」
mkdir my-notes-mcp-server
cd my-notes-mcp-server

建立專案資料夾


步驟 2: 初始化專案並安裝套件

npm init -y
npm install @modelcontextprotocol/sdk express zod
npm install -D @types/express @types/node tsx typescript

安裝套件


步驟 3: 建立基本 Express 伺服器

import express from 'express';

// 1. 建立 Express 應用程式
const app = express();

app.use(express.json());

// 2. 設定路由
// 健康檢查 endpoint
app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok',
    message: 'Server is running!' 
  });
});

// 3. 啟動伺服器
const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

基本 Express 伺服器


步驟 4: 配置 package.json 腳本

{
  "name": "my-notes-mcp-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "tsx watch index.ts", // [新增] 開發模式,支援熱重載,讓程式碼修改後自動重啟
    "start": "tsx index.ts", // [新增] 生產模式啟動
    "build": "tsc", // [新增] 編譯 TypeScript 為 JavaScript
    "serve": "node dist/index.js" // [新增] 執行編譯後的 JavaScript
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.20.2",
    "express": "^5.1.0",
    "zod": "^3.25.76"
  },
  "devDependencies": {
    "@types/express": "^5.0.4",
    "@types/node": "^24.9.1",
    "tsx": "^4.20.6",
    "typescript": "^5.9.3"
  }
}

配置 package.json


步驟 5: 建立 TypeScript 配置

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "./dist"
  },
  "include": ["*.ts"]
}

TypeScript 配置


步驟 6: 啟動伺服器測試

npm run dev

在瀏覽器中打開 http://localhost:8080/health ,應該可以看到健康檢查回應。

啟動伺服器


第二步,開始打造一個最簡單的 MCP 伺服器


💻 建立 MCP Server 核心架構

🎯 學習目標:

  • 導入 MCP SDK 並建立 McpServer 實例
  • 註冊第一個 Tool 並定義其規格
  • 實現 Tool 的執行邏輯與回傳格式
  • 設定 MCP HTTP 端點以接收請求

步驟 1: 導入 MCP Server

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; // [新增] 導入 MCP Server 類別,這是 MCP 協定的核心實現

const app = express();

app.use(express.json());

// 建立 MCP Server // [新增] 建立 MCP Server 實例
const server = new McpServer({ // [新增] 配置 MCP Server,定義名稱和版本,這些資訊會被客戶端顯示
  name: 'my-notes-server', // [新增] 伺服器名稱,用於識別此 MCP 服務
  version: '1.0.0' // [新增] 版本號,用於追蹤更新和相容性
}); // [新增] MCP Server 建立完成,負責處理連接、工具註冊和訊息路由

app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok',
    message: 'Server is running!' 
  });
});

const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

步驟 2: 註冊第一個 Tool

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const app = express();

app.use(express.json());

// 建立 MCP Server
const server = new McpServer({
  name: 'my-notes-server',
  version: '1.0.0'
});

server.registerTool( // [新增] 註冊工具,這是讓 AI 發現並使用此功能的關鍵方法
  'list_files' // 工具名稱 // [新增] 工具的唯一識別名稱,應該簡潔且能讓 LLM 理解其功能(例如:列出檔案)
); // [新增] 工具註冊完成,現在 MCP Server 知道有一個名為 list_files 的工具可用

app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok',
    message: 'Server is running!' 
  });
});

const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

步驟 3: 定義 Tool 規格書

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod'; // [新增] 導入 Zod,用於定義輸入輸出 schema,確保資料驗證和類型安全

const app = express();

app.use(express.json());

// 建立 MCP Server
const server = new McpServer({
  name: 'my-notes-server',
  version: '1.0.0'
});

server.registerTool(
  'list_files',
  { // [新增] Tool 的規格定義,這是給 LLM 的「說明書」,告訴 AI 這個工具怎麼用
    title: '列出檔案', // [新增] 工具的顯示名稱,人性化描述
    description: '列出所有 Markdown 檔案', // [新增] 工具的功能說明,讓 AI 知道何時該使用此工具
    inputSchema: {}, // 這裡不需要傳入任何參數 // [新增] 輸入 schema,定義工具需要的參數(此例為空,表示無參數)
    outputSchema: { // [新增] 輸出 schema,定義工具回傳的資料結構
      files: z.array(z.string()) // [新增] 預期輸出為一個字串陣列,代表檔案清單
    } // [新增] Schema 定義完成,確保輸入輸出格式一致
  },
);

app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok',
    message: 'Server is running!' 
  });
});

const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

步驟 4: 實現 Tool 執行邏輯

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

const app = express();

app.use(express.json());

// 建立 MCP Server
const server = new McpServer({
  name: 'my-notes-server',
  version: '1.0.0'
});

server.registerTool(
  'list_files',
  {
    title: '列出檔案',
    description: '列出所有 Markdown 檔案',
    inputSchema: {}, // 這裡不需要傳入任何參數 
    outputSchema: {
      files: z.array(z.string())
    }
  },
  async () => { // [新增] Tool 的執行函數,當 AI 呼叫此工具時會執行此 async function
    return { // [新增] 回傳物件,必須符合 MCP 協定格式
      content: [                               // ← 必須是陣列 // [新增] content 陣列,包含回傳給 AI 的資料項目
        { // [新增] 每個 content 項目定義類型和內容
          type: 'text',                        // ← 內容類型 // [新增] 內容類型,目前支援 text、image 等
          text: '目前還沒有檔案'                // ← 實際內容 // [新增] 文字內容,AI 會直接「閱讀」此內容
        } // [新增] 預設回傳,模擬無檔案狀態
      ] // [新增] 回傳格式確保 AI 能正確解析結果
    }; // [新增] 執行函數完成,回傳值會直接傳送給呼叫的 LLM
  } // [新增] Tool 註冊完整,現在 AI 可以呼叫並獲得回應
);

app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok',
    message: 'Server is running!' 
  });
});

const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

步驟 5: 支援多媒體回傳(選用)

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const app = express();

app.use(express.json());

// 建立 MCP Server
const server = new McpServer({
  name: "my-notes-server",
  version: "1.0.0",
});

server.registerTool(
  "list_files",
  {
    title: "列出檔案",
    description: "列出所有 Markdown 檔案",
    inputSchema: {}, // 這裡不需要傳入任何參數
    outputSchema: {
      files: z.array(z.string()),
    },
  },
  async () => {
    return {
      content: [
        {
          type: "text",
          text: "目前還沒有檔案",
        },
        {// 簡單展示一下如果要回傳圖片該怎麼做 // [新增] 示範如何在回傳中加入圖片,擴展工具的豐富度
          type: "image",// [新增] 設定內容類型為圖片,讓 AI 能處理視覺資料
          data: "base64 編碼的圖片",// [新增] 圖片資料,使用 base64 編碼傳輸
          mimeType: "image/png",// [新增] MIME 類型,指定圖片格式
        },// [新增] 多媒體支援讓工具更強大,可回傳圖表、截圖等
      ],
    };
  }
);

app.get("/health", (req, res) => {
  res.json({
    status: "ok",
    message: "Server is running!",
  });
});

const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

(右側程式碼僅為舉例)


步驟 6: 加入結構化內容

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const app = express();

app.use(express.json());

// 建立 MCP Server
const server = new McpServer({
  name: "my-notes-server",
  version: "1.0.0",
});

server.registerTool(
  "list_files",
  {
    title: "列出檔案",
    description: "列出所有 Markdown 檔案",
    inputSchema: {}, // 這裡不需要傳入任何參數
    outputSchema: {
      files: z.array(z.string()),
    },
  },
  async () => {
    const output = { files: [] };              // ← 先用空陣列 // [新增] 準備結構化輸出,符合 outputSchema 定義

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(output, null, 2),// [新增] 將結構化資料轉為格式化 JSON 字串,讓 AI 能「閱讀」易懂的文字
        }
      ],
      structuredContent: output                // 同時提供結構化資料 // [新增] 結構化內容,供程式端解析,與 content 內容同步但格式更嚴謹
    };
  }
);

app.get("/health", (req, res) => {
  res.json({
    status: "ok",
    message: "Server is running!",
  });
});

const port = 8080;

app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 健康檢查:http://localhost:${port}/health`);
});

步驟 7: 設定 MCP HTTP 端點

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; // [新增] 導入 HTTP 傳輸層,處理 MCP 協定訊息
import { z } from 'zod';

const app = express();
app.use(express.json());

const server = new McpServer({
  name: 'my-notes-server',
  version: '1.0.0'
});

server.registerTool(
  'list_files',
  {
    title: '列出檔案',
    description: '列出所有 Markdown 檔案',
    inputSchema: {},
    outputSchema: { files: z.array(z.string()) }
  },
  async () => {
    const output = { files: [] };

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(output, null, 2),
        }
      ],
      structuredContent: output 
    };
  }
);

// MCP endpoint // [新增] 建立 MCP 專用端點,接收來自 AI 客戶端的請求
app.post('/mcp', async (req, res) => { // [新增] POST 路由處理 MCP 訊息,使用 streamable HTTP 傳輸
  try { // [新增] 錯誤處理包裝,確保伺服器穩定
    const transport = new StreamableHTTPServerTransport({ // [新增] 建立 HTTP 傳輸實例,支援 JSON 回應和會話管理
      sessionIdGenerator: undefined, // [新增] 預設會話 ID 生成器
      enableJsonResponse: true // [新增] 啟用 JSON 回應格式,方便調試
    }); // [新增] 傳輸層建立完成,負責 MCP 協定封裝

    /* @cat-caption */
    res.on('close', () => transport.close()); // [新增] 處理 HTTP 連接關閉,清理資源避免記憶體洩漏
    
    await server.connect(transport); // [新增] 連接 MCP Server 與傳輸層,讓伺服器能透過 HTTP 接收請求
    await transport.handleRequest(req, res, req.body); // [新增] 處理實際請求,將請求 body 傳遞給 MCP 協定層解析
  } catch (error) { // [新增] 捕獲所有錯誤,提供標準化錯誤回應
    console.error('MCP 請求錯誤:', error); // [新增] 記錄錯誤日誌
    if (!res.headersSent) { // [新增] 確保回應尚未發送,避免重複錯誤
      res.status(500).json({ // [新增] 回傳標準 JSON-RPC 錯誤格式,符合 MCP 協定
        jsonrpc: '2.0', // [新增] JSON-RPC 版本
        error: { code: -32603, message: 'Internal server error' }, // [新增] 標準錯誤碼和訊息
        id: null // [新增] 請求 ID,通常為 null 在錯誤時
      }); // [新增] 錯誤回應完成,確保客戶端能正確處理
    } // [新增] 錯誤處理確保 API 穩定性
  } // [新增] MCP 端點設定完成,現在外部 AI 可以透過 HTTP 連接此伺服器
}); // [新增] 端點暴露,MCP 服務正式可用

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

const port = 8080;
app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 MCP endpoint:http://localhost:${port}/mcp`);
});

第三步,測試看看 MCP 伺服器有沒有建立成功


💻 MCP 伺服器測試與驗證

🎯 學習目標:

  • 使用官方 MCP Inspector 工具測試連接
  • 驗證 Tool 註冊與執行功能
  • 確認 MCP 端點正常回應

步驟 1: 啟動 MCP Inspector

npx @modelcontextprotocol/inspector http://localhost:8080/mcp

啟動 Inspector


步驟 2: 開啟測試介面

輸入完成後,他會開啟一個網站。

Inspector 介面


步驟 3: 設定傳輸類型

確保 Transport Type 是 Streamable HTTP。

設定傳輸類型


步驟 4: 設定 MCP URL

確保 URL 是 http://localhost:8080/mcp

設定 URL


步驟 5: 建立連接

按下 Connect。

建立連接


步驟 6: 驗證連接成功

你應該可以看到連接成功,你可以點擊中間的 List Tools。

連接成功


步驟 7: 查看註冊的 Tools

應該可以看到我們創建的工具「list files」。

查看 Tools


第四步,實作檔案系統讀取

我們來把 list_files 的功能給實際做出來 由於本教學的重點是 MCP,我會用很快的速度帶過去,就不特別提細節了


💻 實作檔案讀取功能

🎯 學習目標:

  • 整合 Node.js fs 模組讀取本地檔案
  • 過濾並處理 Markdown 檔案清單
  • 實現錯誤處理與使用者友善回應

步驟 1: 建立 notes 資料夾

為了方便測試,我建議在專案資料夾中建立一個 notes 資料夾 我們的 mcp 工具會把該資料夾當作我們的筆記目錄。

建立 notes 資料夾


步驟 2: 整合檔案系統讀取

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';
import fs from 'fs/promises'; /* @cat-caption */ // [新增] 導入檔案系統模組(Promise 版本),用於非同步讀取目錄和檔案
import path from 'path';/* @cat-caption */ // [新增] 導入路徑處理模組,處理檔案路徑和副檔名

const app = express();
app.use(express.json());

// 筆記目錄/* @cat-caption */ // [新增] 定義筆記檔案的根目錄路徑,讓工具知道從哪裡讀取檔案
const NOTES_DIR = './notes';/* @cat-caption */ // [新增] 相對路徑指向專案中的 notes 資料夾

const server = new McpServer({
  name: 'my-notes-server',
  version: '1.0.0'
});

server.registerTool(
  'list_files',
  {
    title: '列出檔案',
    description: '列出所有 Markdown 檔案',
    inputSchema: {},
    outputSchema: { files: z.array(z.string()) }
  },
  async () => {
    try {/* @cat-caption */ // [新增] 使用 try-catch 包裝檔案操作,處理可能發生的 I/O 錯誤
      const entries = await fs.readdir(NOTES_DIR, { withFileTypes: true });/* @cat-caption */ // [新增] 非同步讀取目錄,返回檔案和子目錄的詳細資訊
      const files = entries/* @cat-caption */ // [新增] 過濾出只有檔案(非目錄)且副檔名為 .md 的項目
        .filter(entry => entry.isFile() && entry.name.endsWith('.md'))/* @cat-caption */ // [新增] 雙重過濾:確認是檔案且是 Markdown 格式
        .map(entry => entry.name);/* @cat-caption */ // [新增] 提取檔案名稱,形成乾淨的字串陣列
      /* @cat-caption */
      const output = { files };/* @cat-caption */ // [新增] 封裝輸出物件,符合預定義的 schema 結構
      /* @cat-caption */
      return {/* @cat-caption */ // [新增] 成功回傳,包含文字內容和結構化資料
        content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],/* @cat-caption */ // [新增] 將輸出轉為格式化 JSON,讓 AI 易讀
        structuredContent: output/* @cat-caption */ // [新增] 提供結構化版本,供後續處理使用
      };/* @cat-caption */
    } catch (error) {/* @cat-caption */ // [新增] 捕獲讀取錯誤(如目錄不存在、權限問題)
      return {/* @cat-caption */ // [新增] 錯誤回傳,標記為錯誤狀態並提供使用者友善訊息
        content: [{ type: 'text', text: `錯誤:${(error as Error).message}` }],/* @cat-caption */ // [新增] 將錯誤訊息轉為文字,讓 AI 能理解問題
        isError: true/* @cat-caption */ // [新增] 明確標記錯誤,讓 MCP 協定能特殊處理
      };/* @cat-caption */
    }/* @cat-caption */ // [新增] 錯誤處理確保工具穩定,即使檔案系統出問題也能優雅降級
  }
);

app.post('/mcp', async (req, res) => {
  try {
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined,
      enableJsonResponse: true
    });
    res.on('close', () => transport.close());
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error('MCP 請求錯誤:', error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: '2.0',
        error: { code: -32603, message: 'Internal server error' },
        id: null
      });
    }
  }
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

const port = 8080;
app.listen(port, () => {
  console.log(`🚀 伺服器運行中:http://localhost:${port}`);
  console.log(`📍 MCP endpoint:http://localhost:${port}/mcp`);
  console.log(`📂 筆記目錄:${NOTES_DIR}`);
});

實作檔案讀取

在 /notes 資料夾中,放一些檔案方便測試。


步驟 3: 測試檔案讀取功能

回到剛剛提到的 mcp 測試網站(來自 第三步,測試看看 MCP 伺服器有沒有建立成功)

你可以執行看看 list_files 工具 應該可以看到所有檔案。

測試檔案讀取


第五步,接到 ChatGPT 或者 Claude 的前置作業:Ngrok

恭喜你!現在我們的 MCP Server 已經跑起來了,但有個問題:ChatGPT 在雲端,怎麼連到你電腦上的 localhost:3000?

(不知道為什麼讓我想到這個工程師老梗

Threads 笑話

外部的電腦是沒辦法連線到我 localhost 的網站,該怎麼辦呢? 難道只能部署到雲端嗎?

這邊推薦給大家一個超讚的免費服務 ngrok 他可以開了一個「臨時的公開網址」,外面的人可以把信件寄到這公開網址,而 ngrok 會把收到的信再轉交給你家裡的電腦。

這樣就可以讓 ChatGPT、Claude 直接連到你的電腦的 MCP 伺服器了


💻 使用 Ngrok 建立公開隧道

🎯 學習目標:

  • 安裝並配置 Ngrok 服務
  • 建立本地伺服器到公開網址的隧道
  • 獲取臨時公開 URL 用於 AI 整合

步驟 1: 註冊並登入 Ngrok

首先,打開 ngrok 的服務 https://ngrok.com/ 並且登入。

Ngrok 註冊


步驟 2: 安裝 Ngrok

造著他的方式安裝他,以 macos homebrew 為例

打開終端機 - 先輸入 brew install ngrok 安裝整個程式碼 - 然後輸入ngrok config add-authtoken XXX 登入你的帳號

這樣就完成安裝了。

安裝 Ngrok


步驟 3: 建立 HTTP 隧道

ngrok http 8080

建立隧道


步驟 4: 獲取公開 URL

Ok! 他接下來會生成一個網址 恭喜你,這就是你 MCP 的網址,把它複製下來 終端機不要關掉,一但關掉這個網址會失效

任何針對這個網址的請求,都會直接送到你電腦中的 localhost:8080。

公開 URL


第六步之一,接到 ChatGPT

請注意!ChatGPT 的 MCP 功能需要訂閱(每個月 20 美金方案以上)才可以使用


💻 整合 ChatGPT MCP 連接

🎯 學習目標:

  • 在 ChatGPT 中啟用開發者模式
  • 建立自訂 MCP 連接器
  • 配置 Ngrok URL 並驗證連接

步驟 1: 開啟 ChatGPT 設定

打開你的 ChatGPT,並點擊左下角。

開啟設定


步驟 2: 進入應用程式設定

選擇「設定」。

選擇設定


步驟 3: 選擇連接器設定

選擇「應用程式和連接器」。

應用程式設定


步驟 4: 進入進階設定

滑到最底下,選擇「進階設定」。

進階設定


步驟 5: 啟用開發者模式

開啟「開發者模式」,並回到上一步。

開發者模式


步驟 6: 建立新連接器

點擊「建立」。

建立連接器


步驟 7: 輸入連接器名稱

輸入這個 MCP 伺服器的名稱。

輸入名稱


步驟 8: 配置 MCP URL

重點來了! 貼上 ngrok 的網址 記得!!最後要加上 /mcp。

配置 URL


步驟 9: 設定驗證方式

把「驗證」改成「無驗證」。

無驗證


步驟 10: 確認並建立

點擊「我了解並繼續」,最後選擇建立。

確認建立


步驟 11: 驗證連接成功

順利的話,就可以看到我們的 MCP 服務。

連接成功


步驟 12: 開始對話測試

到這邊就可以直接跟 ChatGPT 聊天,順利的話,恭喜成功串接 MCP 到 ChatGPT 上頭!

對話測試


第六步之二,接到 Claude

請注意!Claude 的 MCP 功能需要訂閱(每個月 20 美金方案以上)才可以使用

Claude 的邏輯跟 ChatGPT 很像,


💻 整合 Claude MCP 連接

🎯 學習目標:

  • 在 Claude 中新增自訂連接器
  • 配置相同 Ngrok URL
  • 驗證跨平台 MCP 相容性

步驟 1: 開啟 Claude 設定

打開你的 Claude,並點擊左下角。

開啟 Claude


步驟 2: 進入設定選單

選擇「Settings」。

Claude 設定


步驟 3: 新增自訂連接器

選擇「Connectors」並點擊「Add custom connector」。

新增連接器


步驟 4: 輸入連接器名稱

輸入這個 MCP 伺服器的名稱。

Claude 名稱


步驟 5: 配置 MCP URL

重點來了! 貼上 ngrok 的網址 記得!!最後要加上 /mcp 最後按下 Add。

Claude URL


步驟 6: 驗證連接成功

順利的話,就可以看到我們的 MCP 服務。

Claude 連接


步驟 7: 開始對話測試

到這邊就可以直接跟 ChatGPT 聊天,順利的話,恭喜成功串接 MCP 到 Claude 上頭!

Claude 對話


你已經學會了

理解 MCP 解決的核心問題

  • 為什麼 REST API 不夠
  • AI 需要自動發現和使用工具的能力
  • 標準化協定的重要性

掌握 Tools 的設計哲學

  • Model-Controlled:AI 決定何時使用
  • Action-Oriented:執行動作、產生結果
  • Schema-Defined:明確的輸入輸出規格

建立第一個實用的 MCP Server

  • 完整的 list_notes tool 實作
  • 錯誤處理和友善的訊息

整合到真實的 AI 應用

  • 使用 ngrok 建立公開 URL
  • 連接到 ChatGPT、Claude
  • 實際對話測試

但這只是開始⋯⋯

你現在擁有了一個可運作的 MCP Server,但它還很基礎。 下一篇則會更深入 MCP 的精髓,講解 Resources 和 Prompts 的功能 我會分享很多開發 MCP 必定會遇到大問題以及解法,好比身份驗證、很多必須避開的坑

在 AI 時代,重點不是寫多少 code,而是理解問題、設計方案、驗證效果。Code 可以讓 AI 幫你寫,思考無法委託。 準備好了嗎?讓我們在下篇繼續深入 MCP 的世界!



圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言