跳轉到主要內容
版本:0.2.0-beta.1

教學:打造一個待辦事項管理器

在本教學中,我們將建立一個具備使用者驗證 (Authentication) 與授權 (Authorization) 的 todo manager MCP 伺服器。根據最新的 MCP 規範,我們的 MCP 伺服器將作為 OAuth 2.0 資源伺服器 (Resource Server),負責驗證存取權杖 (Access token) 並強制執行基於權限範圍 (Scope) 的權限。

完成本教學後,你將擁有:

  • ✅ 基本瞭解如何在 MCP 伺服器中設定角色型存取控制 (RBAC, Role-based access control)
  • ✅ 一個作為資源伺服器 (Resource Server) 的 MCP 伺服器,能消耗由授權伺服器 (Authorization Server) 發出的存取權杖 (Access token)
  • ✅ 一個可運作的基於權限範圍 (Scope) 的權限控管實作,適用於 todo 操作

概覽

本教學將包含以下元件:

  • MCP 用戶端 (MCP Inspector):一個用於測試 MCP 伺服器的視覺化工具,作為 OAuth 2.0 / OIDC 用戶端。它會啟動授權流程,並向 MCP 伺服器發送帶有存取權杖 (Access token) 的請求。
  • 授權伺服器 (Authorization Server):一個 OAuth 2.1 或 OpenID Connect 簽發者 (Issuer),負責管理使用者身分、驗證使用者,並向授權用戶端發出帶有適當權限範圍 (Scope) 的存取權杖 (Access token)。
  • MCP 伺服器 (Resource Server):根據最新 MCP 規範,MCP 伺服器在 OAuth 2.0 架構中作為資源伺服器 (Resource Server)。它驗證授權伺服器發出的存取權杖 (Access token),並根據權限範圍 (Scope) 強制執行 todo 操作的權限。

此架構遵循標準 OAuth 2.0 流程:

  • MCP Inspector 代表使用者請求受保護資源
  • 授權伺服器 (Authorization Server) 驗證使用者並發出存取權杖 (Access token)
  • MCP 伺服器 (Resource Server) 驗證權杖並根據授權權限提供受保護資源

以下是這些元件互動的高階流程圖:

瞭解你的授權伺服器

具備權限範圍 (Scope) 的存取權杖 (Access tokens)

若要在 MCP 伺服器中實作 角色型存取控制 (RBAC),你的授權伺服器需支援發出帶有權限範圍 (Scope) 的存取權杖 (Access token)。權限範圍 (Scope) 代表使用者被授予的權限。

Logto 透過其 API 資源 (API resources)(符合 RFC 8707: OAuth 2.0 資源標示符 (Resource Indicators))與角色 (Roles) 功能提供 RBAC 支援。設定方式如下:

  1. 登入 Logto Console(或你的自架 Logto Console)

  2. 建立 API 資源與權限範圍 (Scope):

    • 前往「API 資源」
    • 建立一個名為「Todo Manager」的新 API 資源
    • 新增以下權限範圍 (Scope):
      • create:todos:「建立新的待辦事項」
      • read:todos:「讀取所有待辦事項」
      • delete:todos:「刪除任一待辦事項」
  3. 建立角色(建議以便管理):

    • 前往「角色」
    • 建立「Admin」角色並指派所有權限範圍(create:todosread:todosdelete:todos
    • 建立「User」角色並僅指派 create:todos 權限範圍
  4. 指派權限:

    • 前往「使用者」
    • 選擇一位使用者
    • 你可以:
      • 在「角色」分頁指派角色(建議)
      • 或直接在「權限」分頁指派權限範圍

權限範圍 (Scope) 會以空格分隔字串的形式包含在 JWT 存取權杖 (Access token) 的 scope 宣告 (Claim) 中。

權杖驗證與權限檢查

根據最新 MCP 規範,MCP 伺服器在 OAuth 2.0 架構中作為 資源伺服器 (Resource Server)。作為資源伺服器,MCP 伺服器有以下職責:

  1. 權杖驗證:驗證從 MCP 用戶端收到的存取權杖 (Access token) 的真實性與完整性
  2. 權限範圍強制執行:從存取權杖中擷取並驗證權限範圍 (Scope),以決定用戶端被授權執行哪些操作
  3. 資源保護:僅在用戶端提供有效且具備足夠權限的權杖時,才提供受保護資源(執行工具)

當 MCP 伺服器收到請求時,會執行以下驗證流程:

  1. Authorization 標頭擷取存取權杖(Bearer 權杖格式)
  2. 驗證存取權杖的簽章與有效期限
  3. 從已驗證的權杖中擷取權限範圍與使用者資訊
  4. 檢查權杖是否具備執行請求操作所需的權限範圍

例如,若使用者要建立新的待辦事項,其存取權杖必須包含 create:todos 權限範圍。以下為資源伺服器驗證流程:

動態用戶端註冊 (Dynamic Client Registration)

本教學不強制要求動態用戶端註冊,但若你想自動化 MCP 用戶端在授權伺服器的註冊流程,可參考 是否需要動態用戶端註冊? 以取得更多資訊。

瞭解 todo manager 中的 RBAC

為了示範,我們會在 todo manager MCP 伺服器中實作一個簡單的角色型存取控制 (RBAC) 系統。這將讓你瞭解 RBAC 的基本原理,同時保持實作簡潔。

註解

雖然本教學以 RBAC 為基礎進行權限範圍管理,但需注意並非所有驗證 (Authentication) 簽發者 (Issuer) 都透過角色來管理權限範圍。有些簽發者可能有自己獨特的存取控制與權限管理機制。

工具與權限範圍 (Scope)

我們的 todo manager MCP 伺服器提供三個主要工具:

  • create-todo:建立新的待辦事項
  • get-todos:列出所有待辦事項
  • delete-todo:根據 ID 刪除待辦事項

為了控管這些工具的存取,我們定義以下權限範圍 (Scope):

  • create:todos:允許建立新的待辦事項
  • delete:todos:允許刪除現有待辦事項
  • read:todos:允許查詢並取得所有待辦事項列表

角色與權限

我們將定義兩個不同存取層級的角色:

角色create:todosread:todosdelete:todos
Admin
User
  • User:一般使用者,可建立待辦事項,僅能檢視或刪除自己的待辦事項
  • Admin:管理員,可建立、檢視並刪除所有待辦事項,不論擁有者為誰

資源擁有權

雖然上表明確列出每個角色被指派的權限範圍 (Scope),但還有一個重要的資源擁有權原則:

  • User 沒有 read:todosdelete:todos 權限範圍,但仍可:
    • 檢視自己的待辦事項
    • 刪除自己的待辦事項
  • Admin 具備完整權限(read:todosdelete:todos),可:
    • 檢視系統中所有待辦事項
    • 刪除任何待辦事項,不論擁有者為誰

這展現了 RBAC 系統中常見的模式:資源擁有權會隱含授予使用者對自己資源的權限,而管理角色則獲得對所有資源的明確權限。

深入學習

想更深入瞭解 RBAC 概念與最佳實踐,請參考 精通 RBAC:完整實戰範例

在你的簽發者 (Issuer) 中設定授權 (Authorization)

要實作上述存取控制系統,你需要在授權伺服器設定所需的權限範圍 (Scope)。以下是不同簽發者的設定方式:

Logto 透過 API 資源與角色功能提供 RBAC 支援。設定方式如下:

  1. 登入 Logto Console(或你的自架 Logto Console)

  2. 建立 API 資源與權限範圍 (Scope):

    • 前往「API 資源」
    • 建立一個名為「Todo Manager」的新 API 資源,並將 http://localhost:3001 設為資源標示符 (Resource indicator)。
      • 重要:資源標示符必須與你的 MCP 伺服器 URL 相符。本教學使用 http://localhost:3001,因 MCP 伺服器運行於 3001 埠。正式環境請使用實際 MCP 伺服器 URL(如 https://your-mcp-server.example.com)。
    • 建立以下權限範圍 (Scope):
      • create:todos:「建立新的待辦事項」
      • read:todos:「讀取所有待辦事項」
      • delete:todos:「刪除任一待辦事項」
  3. 建立角色(建議以便管理):

    • 前往「角色」
    • 建立「Admin」角色並指派所有權限範圍(create:todosread:todosdelete:todos
    • 建立「User」角色並僅指派 create:todos 權限範圍
    • 在「User」角色詳細頁切換至「一般」分頁,將「User」設為「預設角色」
  4. 管理使用者角色與權限:

    • 新使用者:
      • 由於已設為預設角色,將自動獲得「User」角色
    • 現有使用者:
      • 前往「使用者管理」
      • 選擇一位使用者
      • 在「角色」分頁指派角色
程式化角色管理

你也可以使用 Logto 的 Management API 以程式方式管理使用者角色。這對自動化使用者管理或建立管理後台特別有用。

請求存取權杖時,Logto 會根據使用者角色權限將權限範圍 (Scope) 加入權杖的 scope 宣告 (Claim)。

設定好授權伺服器後,使用者將收到包含其授權權限範圍的存取權杖。MCP 伺服器會根據這些權限範圍判斷:

  • 使用者是否能建立新的待辦事項(create:todos
  • 使用者是否能檢視所有待辦事項(read:todos)或僅能檢視自己的
  • 使用者是否能刪除任一待辦事項(delete:todos)或僅能刪除自己的

建立 MCP 伺服器

我們將使用 MCP 官方 SDK 來建立 todo manager MCP 伺服器。

建立新專案

建立新的 Python 專案:

mkdir mcp-todo-server
cd mcp-todo-server

# 初始化新的 Python 專案
uv init

# 使用 uv 建立虛擬環境
uv venv

# 啟用虛擬環境(使用 'uv run' 時可選擇性略過)
source .venv/bin/activate
註解

本專案使用 uv 進行套件管理,但你也可以選擇 pippoetryconda 等其他套件管理工具。

安裝 MCP SDK 與相依套件

安裝所需相依套件:

uv add "mcp[cli]" uvicorn starlette

建立 MCP 伺服器

首先,讓我們建立一個包含工具定義的基本 MCP 伺服器:

建立名為 server.py 的檔案並加入以下程式碼:

# server.py

import contextlib
from typing import Any
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount

# 初始化 FastMCP 伺服器
mcp = FastMCP(name="Todo Manager", stateless_http=True, streamable_http_path='/')

@mcp.tool()
def create_todo(content: str) -> dict[str, Any]:
    """建立新的待辦事項。需 'create:todos' 權限範圍。"""
    return {"error": "Not implemented"}

@mcp.tool()
def get_todos() -> dict[str, Any]:
    """列出待辦事項。具備 'read:todos' 權限範圍的使用者可檢視所有待辦事項。"""
    return {"error": "Not implemented"}

@mcp.tool()
def delete_todo(id: str) -> dict[str, Any]:
    """根據 id 刪除待辦事項。使用者可刪除自己的待辦事項。"""
    return {"error": "Not implemented"}

@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
    async with contextlib.AsyncExitStack() as stack:
        await stack.enter_async_context(mcp.session_manager.run())
        yield

# 建立 app
app = Starlette(
    routes=[
        Mount("/", app=mcp.streamable_http_app()),
    ],
    lifespan=lifespan,
)

以以下指令啟動伺服器:

# 使用 uvicorn 啟動 Todo Manager 伺服器
uvicorn server:app --host 127.0.0.1 --port 3001

# 或使用 uv:
# uv run uvicorn server:app --host 127.0.0.1 --port 3001

檢查 MCP 伺服器

下載並執行 MCP inspector

現在 MCP 伺服器已啟動,我們可以使用 MCP inspector 來檢查工具是否可用。

官方 MCP inspector v0.16.2 有些 bug 會影響驗證 (Authentication) 功能。為了解決這些問題,我們建立了 修正版 MCP inspector,已包含 OAuth / OIDC 驗證流程所需修正。我們也已向官方倉庫提交 PR。

執行 MCP inspector:

git clone https://github.com/mcp-auth/inspector.git -b patch/0.16.2-fixes
cd inspector
npm install
npm run dev

MCP inspector 會自動在預設瀏覽器開啟,或你也可以手動點擊終端機輸出的連結(請務必點擊包含 MCP_PROXY_AUTH_TOKEN 參數的連結,如 http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=458ae4a4...acab1907)。

連接 MCP inspector 至 MCP 伺服器

繼續前請檢查 MCP inspector 的以下設定:

  • Transport Type:設為 Streamable HTTP
  • URL:設為 MCP 伺服器的 URL,本例為 http://localhost:3001

現在你可以點擊「Connect」按鈕,檢查 MCP inspector 是否能連線至 MCP 伺服器。若一切正常,MCP inspector 會顯示「Connected」狀態。

檢查點:執行 todo manager 工具

  1. 在 MCP inspector 上方選單點選「Tools」分頁
  2. 點擊「List Tools」按鈕
  3. 你應該會看到 create-todoget-todosdelete-todo 工具列在頁面上,點擊可檢視工具細節
  4. 右側會出現「Run Tool」按鈕,點擊並輸入必要參數執行工具
  5. 你會看到工具回傳的 JSON 結果 {"error": "Not implemented"}

MCP inspector 首次執行畫面

與授權伺服器整合

完成本節需考慮以下幾點:

你的授權伺服器簽發者 (Issuer) URL

通常是你的授權伺服器基礎 URL,如 https://auth.example.com。有些簽發者可能為 https://example.logto.app/oidc,請參考你的簽發者文件。

如何取得授權伺服器中繼資料
如何將 MCP inspector 註冊為授權伺服器用戶端
瞭解權杖請求參數

向不同授權伺服器請求存取權杖時,指定目標資源與權限的方式可能不同,主要有:

  • 資源標示符 (Resource indicator) 模式

    • 使用 resource 參數指定目標 API(參見 [RFC 8707: OAuth 2.0 資源標示符])
    • 現代 OAuth 2.0 常見
    • 範例請求:
      {
        "resource": "http://localhost:3001",
        "scope": "create:todos read:todos"
      }
    • 伺服器會發出專屬於該資源的權杖
  • 受眾 (Audience) 模式

    • 使用 audience 參數指定權杖目標
    • 與資源標示符類似但語意不同
    • 範例請求:
      {
        "audience": "todo-api",
        "scope": "create:todos read:todos"
      }
  • 純權限範圍 (Scope) 模式

    • 僅依權限範圍,不帶 resource / audience 參數
    • 傳統 OAuth 2.0 作法
    • 範例請求:
      {
        "scope": "todo-api:create todo-api:read openid profile"
      }
    • 常以前綴命名空間權限範圍
    • 簡單 OAuth 2.0 實作常見
最佳實踐
  • 查閱簽發者文件以確認支援哪些參數
  • 有些簽發者同時支援多種方式
  • 資源標示符可提升安全性(限制受眾)
  • 建議有支援時優先使用資源標示符以強化存取控制

每個簽發者可能有不同需求,以下步驟將引導你整合 MCP inspector 與 MCP 伺服器並進行專屬設定。

註冊 MCP inspector 為用戶端

將 todo manager 與 Logto 整合非常簡單,因其為支援資源標示符與權限範圍的 OpenID Connect 簽發者,可用 http://localhost:3001 作為資源標示符保護 todo API。

由於 Logto 尚未支援動態用戶端註冊,你需手動將 MCP inspector 註冊為 Logto 租戶的用戶端:

  1. 開啟 MCP inspector,進入驗證 (Authentication) 設定並點擊「OAuth2.0 Flow」設定。複製 Redirect URI,如 http://localhost:6274/oauth/callback
  2. 登入 Logto Console(或你的自架 Logto Console)
  3. 前往「應用程式」分頁,點擊「建立應用程式」。頁面底部點選「不使用框架建立應用程式」
  4. 填寫應用程式資訊後點擊「建立應用程式」:
    • 選擇應用程式類型:選「單頁應用程式」
    • 應用程式名稱:如「MCP Inspector」
  5. 在「設定 / Redirect URIs」區塊貼上剛才複製的 Redirect URI,然後點擊底部「儲存變更」
  6. 頁面上方卡片會顯示「App ID」,請複製
  7. 回到 MCP inspector,將「App ID」貼到驗證 (Authentication) 設定的「OAuth2.0 Flow」下的「Client ID」欄位
  8. 在「Scope」欄位輸入:create:todos read:todos delete:todos,確保 Logto 回傳的存取權杖包含 todo manager 所需權限範圍

設定 MCP Auth

首先,在 MCP 伺服器專案中安裝 MCP Auth SDK。

uv add mcpauth==0.2.0b1

接下來需在 MCP 伺服器初始化 MCP Auth,主要分兩步:

  1. 取得授權伺服器中繼資料:供 MCP Auth 驗證授權伺服器發出的存取權杖,並將簽發者 (Issuer) 識別資訊納入資源中繼資料
  2. 設定受保護資源中繼資料:定義 MCP 伺服器的資源標示符與支援的權限範圍

步驟 1:取得授權伺服器中繼資料

根據 OAuth / OIDC 規範,可根據授權伺服器簽發者 (Issuer) URL 取得中繼資料。

在 Logto,你可於 Logto Console 的應用程式詳細頁「Endpoints & Credentials / Issuer endpoint」區塊找到簽發者 (Issuer) URL,格式如 https://my-project.logto.app/oidc

現在,使用 MCP Auth 工具函式取得授權伺服器中繼資料:

from mcpauth import MCPAuth
from mcpauth.config import AuthServerType
from mcpauth.utils import fetch_server_config

issuer_url = "<issuer-url>"  # 請替換為你的授權伺服器簽發者 (Issuer) URL

# 取得授權伺服器設定
auth_server_config = fetch_server_config(issuer_url, AuthServerType.OIDC) # 或 AuthServerType.OAUTH

若需其他方式取得授權伺服器中繼資料或自訂設定,請參考 其他設定方式

步驟 2:設定受保護資源中繼資料

接著,在建立 MCP Auth 實例時設定受保護資源中繼資料。MCP 伺服器將透過 MCP Auth 對外公開這些資源中繼資料。

# server.py

# 其他 import...
from mcpauth.types import ResourceServerConfig, ResourceServerMetadata

# 定義本 MCP 伺服器的資源標示符
resource_id = "http://localhost:3001"

mcp_auth = MCPAuth(
    protected_resources=ResourceServerConfig(
        metadata=ResourceServerMetadata(
            resource=resource_id,
            # 上一步取得的授權伺服器中繼資料
            authorization_servers=[auth_server_config],
            # 本 MCP 伺服器支援的權限範圍
            scopes_supported=[
                "create:todos",
                "read:todos",
                "delete:todos"
            ]
        )
    )
)

更新 MCP 伺服器

快完成了!現在要更新 MCP 伺服器,套用 MCP Auth 路由與中介軟體 (middleware),並根據使用者權限範圍實作 todo manager 工具的權限控管。

首先,套用受保護資源中繼資料路由,讓 MCP 用戶端可從 MCP 伺服器取得資源中繼資料。

# server.py

# ..其他程式碼

app = Starlette(
    routes=[
        # 設定受保護資源中繼資料路由
        # 讓 OAuth 用戶端可取得本資源伺服器的中繼資料
        *mcp_auth.resource_metadata_router().routes,
        Mount("/", app=mcp.streamable_http_app()),
    ],
    lifespan=lifespan,
)

接著,套用 MCP Auth 中介軟體至 MCP 伺服器。此中介軟體將處理所有進來請求的驗證 (Authentication) 與授權 (Authorization),確保只有授權使用者能存取 todo manager 工具。

# server.py

# 其他 import...
from starlette.middleware import Middleware

# 其他程式碼...

# 建立中介軟體
bearer_auth = Middleware(mcp_auth.bearer_auth_middleware('jwt', resource=resource_id, audience=resource_id))

app = Starlette(
    routes=[
        *mcp_auth.resource_metadata_router().routes,
        # 套用 MCP Auth 中介軟體
        Mount("/", app=mcp.streamable_http_app(), middleware=[bearer_auth]),
    ],
    lifespan=lifespan,
)

現在可以更新 todo manager 工具,讓其透過 MCP Auth 中介軟體進行驗證 (Authentication) 與授權 (Authorization)。

讓我們來更新工具實作。

# server.py

# 其他 import...

from typing import Any, List, Optional
from mcpauth.exceptions import MCPAuthBearerAuthException, BearerAuthExceptionCode
from mcpauth.types import AuthInfo, ResourceServerConfig, ResourceServerMetadata

# 下一節會提到
from service import TodoService

def assert_user_id(auth_info: Optional[AuthInfo]) -> str:
    """確認 auth_info 包含有效 user ID 並回傳。"""
    if not auth_info or not auth_info.subject:
        raise Exception("Invalid auth info")
    return auth_info.subject

def has_required_scopes(user_scopes: List[str], required_scopes: List[str]) -> bool:
    """檢查使用者是否具備所有必要權限範圍。"""
    return all(scope in user_scopes for scope in required_scopes)

# 建立 TodoService 實例
todo_service = TodoService()

@mcp.tool()
def create_todo(content: str) -> dict[str, Any]:
    """建立新的待辦事項。需 'create:todos' 權限範圍。"""
    auth_info = mcp_auth.auth_info
    user_id = assert_user_id(auth_info)
    
    # 只有具備 'create:todos' 權限範圍的使用者可建立
    user_scopes = auth_info.scopes if auth_info else []
    if not has_required_scopes(user_scopes, ["create:todos"]):
        raise MCPAuthBearerAuthException(BearerAuthExceptionCode.MISSING_REQUIRED_SCOPES)
    
    created_todo = todo_service.create_todo(content=content, owner_id=user_id)
    return created_todo

@mcp.tool()
def get_todos() -> dict[str, Any]:
    """
    列出待辦事項。具備 'read:todos' 權限範圍的使用者可檢視所有待辦事項,
    否則僅能檢視自己的。
    """
    auth_info = mcp_auth.auth_info
    user_id = assert_user_id(auth_info)
    
    # 有 'read:todos' 權限範圍可存取所有,否則僅能存取自己的
    user_scopes = auth_info.scopes if auth_info else []
    todo_owner_id = None if has_required_scopes(user_scopes, ["read:todos"]) else user_id
    
    todos = todo_service.get_all_todos(todo_owner_id)
    return {"todos": todos}

@mcp.tool()
def delete_todo(id: str) -> dict[str, Any]:
    """
    根據 id 刪除待辦事項。使用者可刪除自己的,
    具備 'delete:todos' 權限範圍者可刪除任何待辦事項。
    """
    auth_info = mcp_auth.auth_info
    user_id = assert_user_id(auth_info)
    
    todo = todo_service.get_todo_by_id(id)
    
    if not todo:
        return {"error": "Failed to delete todo"}
    
    # 只有自己的可刪除,具備 'delete:todos' 權限範圍者可刪除任何
    user_scopes = auth_info.scopes if auth_info else []
    if todo.owner_id != user_id and not has_required_scopes(user_scopes, ["delete:todos"]):
        return {"error": "Failed to delete todo"}
    
    deleted_todo = todo_service.delete_todo(id)
    
    if deleted_todo:
        return {
            "message": f"Todo {id} deleted",
            "details": deleted_todo
        }
    else:
        return {"error": "Failed to delete todo"}

現在,建立上述程式碼所用的「Todo service」以實作相關功能:

建立 service.py 作為 Todo service:

"""
簡易 Todo 服務,僅供示範用途。
以記憶體列表儲存 todos。
"""

from datetime import datetime
from typing import List, Optional, Dict, Any
import random
import string

class Todo:
    """代表一個待辦事項。"""
    
    def __init__(self, id: str, content: str, owner_id: str, created_at: str):
        self.id = id
        self.content = content
        self.owner_id = owner_id
        self.created_at = created_at
    
    def to_dict(self) -> Dict[str, Any]:
        """轉換為 dict 以供 JSON 序列化。"""
        return {
            "id": self.id,
            "content": self.content,
            "ownerId": self.owner_id,
            "createdAt": self.created_at
        }


class TodoService:
    """簡易 Todo 服務,僅供示範用途。"""
    
    def __init__(self):
        self._todos: List[Todo] = []
    
    def get_all_todos(self, owner_id: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        取得所有 todos,可選擇依 owner_id 過濾。
        
        Args:
            owner_id: 若提供,僅回傳該使用者的 todos
            
        Returns:
            todo 字典列表
        """
        if owner_id:
            filtered_todos = [todo for todo in self._todos if todo.owner_id == owner_id]
            return [todo.to_dict() for todo in filtered_todos]
        return [todo.to_dict() for todo in self._todos]
    
    def get_todo_by_id(self, todo_id: str) -> Optional[Todo]:
        """
        依 ID 取得 todo。
        
        Args:
            todo_id: 欲取得的 todo ID
            
        Returns:
            找到則回傳 Todo 物件,否則 None
        """
        for todo in self._todos:
            if todo.id == todo_id:
                return todo
        return None
    
    def create_todo(self, content: str, owner_id: str) -> Dict[str, Any]:
        """
        建立新 todo。
        
        Args:
            content: todo 內容
            owner_id: 擁有者 ID
            
        Returns:
            建立的 todo 字典
        """
        todo = Todo(
            id=self._generate_id(),
            content=content,
            owner_id=owner_id,
            created_at=datetime.now().isoformat()
        )
        self._todos.append(todo)
        return todo.to_dict()
    
    def delete_todo(self, todo_id: str) -> Optional[Dict[str, Any]]:
        """
        依 ID 刪除 todo。
        
        Args:
            todo_id: 欲刪除的 todo ID
            
        Returns:
            刪除的 todo 字典,若未找到則 None
        """
        for i, todo in enumerate(self._todos):
            if todo.id == todo_id:
                deleted_todo = self._todos.pop(i)
                return deleted_todo.to_dict()
        return None
    
    def _generate_id(self) -> str:
        """產生隨機 todo ID。"""
        return ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))

🎉 恭喜!我們已成功實作一個具備驗證 (Authentication) 與授權 (Authorization) 的完整 MCP 伺服器!

你也可以參考我們的範例程式碼:

資訊

完整 MCP 伺服器(OIDC 版本)程式碼請參考 MCP Auth Python SDK repository

檢查點:執行 todo-manager 工具

重新啟動 MCP 伺服器並於瀏覽器開啟 MCP inspector。點擊「Connect」按鈕時,你應會被導向授權伺服器的登入頁面。

登入並返回 MCP inspector 後,重複前述步驟執行 todo manager 工具。這次你將以已驗證 (Authentication) 的使用者身分使用這些工具。工具行為會根據你帳號的角色與權限而異:

  • 若以 User(僅有 create:todos 權限範圍)登入:

    • 可用 create-todo 工具建立新待辦事項
    • 僅能檢視與刪除自己的待辦事項
    • 無法檢視或刪除其他使用者的待辦事項
  • 若以 Admin(具備所有權限範圍:create:todosread:todosdelete:todos)登入:

    • 可建立新待辦事項
    • 可用 get-todos 工具檢視系統所有待辦事項
    • 可用 delete-todo 工具刪除任何待辦事項,不論擁有者

你可以透過以下方式測試不同權限層級:

  1. 登出目前 session(於 MCP inspector 點擊「Disconnect」)
  2. 以不同權限的帳號登入
  3. 重複執行相同工具,觀察行為如何隨使用者權限變化

這展現了角色型存取控制 (RBAC) 的實際運作,不同使用者對系統功能有不同存取層級。

MCP inspector todo manager 工具結果

資訊

完整 MCP 伺服器(OIDC 版本)程式碼請參考 MCP Auth Python SDK repository

結語

🎊 恭喜!你已順利完成本教學。讓我們回顧一下:

  • 建立具備 todo 管理工具(create-todoget-todosdelete-todo)的基本 MCP 伺服器
  • 實作不同使用者與管理員權限層級的角色型存取控制 (RBAC)
  • 透過 MCP Auth 將 MCP 伺服器與授權伺服器整合
  • 設定 MCP Inspector 以驗證 (Authentication) 使用者並使用帶有權限範圍的存取權杖呼叫工具

歡迎參閱其他教學與文件,充分發揮 MCP Auth 的強大功能。