KDnuggets ★ 93 5 min

Build Your First MCP Server in Python (Stateless Spec Edition)

🔗 https://www.kdnuggets.com/build-your-first-mcp-server-in-python-stateless-spec-edition

📌 MCP 規範轉無狀態,Python 兩個檔案就能起一個伺服器

TL;DR:2026-07-28 版 MCP 規範拿掉了 session 機制,搭配 Python SDK v2 的 MCPServer API,寫 function 就能生出完整的工具、資源與 prompt 伺服器。

以前要串接 MCP,第一步永遠是處理 session:建立連線、維護 Mcp-Session-Id,確保每個請求都綁在正確的上下文裡。現在這一步直接消失了。

🤔 為什麼突然不用管 session 了

今年夏天發布的 2026-07-28 MCP 規範把協定核心改成了「無狀態」:客戶端不再需要先建立協定 session 才能發請求,伺服器端的一般請求也不再依賴 Mcp-Session-Id。這個改動的直接好處是 MCP 伺服器可以直接架在一般的 HTTP 基礎設施後面做水平擴充,不用額外處理 session 親和性(session affinity)的問題。同時,官方 Python SDK 也進到 v2 穩定版,提供了更高階的 MCPServer API,讓開發者用一般的 Python function 定義 tool、resource、prompt,不用自己寫 JSON Schema。

🧩 一個 function 加一個 decorator,就是一個 MCP 介面

教學示範建了一個小型「開發者知識庫」伺服器,暴露三個 MCP 原語:工具 search_kb(query, limit)、資源 kb://articles、prompt 模板 draft_support_reply(customer_message)。整支伺服器完全沒有使用者 session 狀態,每個請求都自帶所有需要的資訊,正好示範了新的無狀態模型。

核心設計理念很直白:

from mcp.server import MCPServer

mcp = MCPServer(
    "Developer Support KB",
    instructions=(
        "Use the knowledge-base tools to answer support questions. "
        "Prefer retrieved KB information over guessing."
    ),
)

MCPServer 是目前 SDK 推薦給大多數場景使用的高階介面;SDK 另外保留了一個低階的 Server class,留給需要精確控制 schema、協定 metadata 或自訂方法的情境。

工具的定義完全靠型別提示(type hints)推導出來:

@mcp.tool()
def search_kb(query: str, limit: int = 3) -> list[dict[str, str]]:
    """Search the support knowledge base."""
    ...

沒有手寫 JSON Schema、沒有手寫參數解析器。query: str 與 limit: int = 3 直接被 SDK 轉成 MCP 的 input schema,預設值讓參數在 schema 裡變成可選。換句話說,函式的簽章本身就是介面定義。

資源與工具的分工也很清楚:資源像是 GET,用來把資料讀進上下文;工具像是 POST,用來執行動作。

@mcp.resource("kb://articles")
def list_articles() -> str:
    """Return the available knowledge-base articles."""
    ...

Prompt 則是給使用者或 host 主動呼叫的範本:

@mcp.prompt()
def draft_support_reply(customer_message: str) -> str:
    """Create a prompt for drafting a concise support response."""
    ...

三者共用同一套 decorator 風格的伺服器介面,是這套 SDK 讀起來特別「平易近人」的原因。

💻 從零到可連線只需要幾行指令

專案建立用 uv:

mkdir first-mcp-server
cd first-mcp-server
uv init
uv add "mcp[cli]"

或用 pip 安裝 mcp[cli](官方建議裝 CLI extra,因為它附帶開發指令與 MCP Inspector 工作流)。整個專案可以小到只有 server.py 和 client.py 兩個檔案,不需要額外框架樣板。寫完上述三個原語後,伺服器最後用:

if __name__ == "__main__":
    mcp.run("streamable-http")

跑在 Streamable HTTP 上,就是一個可被外部連線的完整 MCP 應用。SDK 要求 Python 3.10 以上版本。

🎯 實務啟示

如果你的團隊已經在用 MCP 串接內部工具或知識庫,這次規範轉向無狀態是個很好的時機重新檢視架構:不用再為每個客戶端維護 session 生命週期,可以直接用一般的 HTTP 負載平衡與多副本部署來擴充 MCP 伺服器。而 Python SDK 的 decorator 模式也意味著,既有的業務函式只要補上型別提示,幾乎可以直接變成 MCP 工具,門檻比想像中低。

🔗 來源

#MCP #Python #LLM #AIAgent #API #OpenProtocol #DeveloperTools #StreamableHTTP #SDK #AIEngineering

原始資料 KDnuggets · 收集於 2026-10-09
來源原標題
Build Your First MCP Server in Python (Stateless Spec Edition)
作者
Kanwal Mehreen
原始連結
https://www.kdnuggets.com/build-your-first-mcp-server-in-python-stateless-spec-edition

摘要原文

Build Your First MCP Server in Python (Stateless Spec Edition) MCP just got simpler. Learn how to build a stateless MCP server in Python and expose tools, resources, and prompts over HTTP. By Kanwal Mehreen , KDnuggets Technical Editor & Content Specialist on October 9, 2026 in Python The Model Context Protocol changed in an important way this summer. With the 2026-07-28 MCP specification , the protocol core is now stateless : modern clients no longer need to establish a protocol session before making requests, and servers no longer rely on Mcp-Session-Id for ordinary requests. That makes MCP servers much easier to scale behind normal HTTP infrastructure. At the same time, the official Python SDK has moved to v2 as its current stable line and provides a higher-level MCPServer API for defining tools, resources, and prompts with regular Python functions. In this tutorial, we will build a small but complete MCP server in Python, run it over Streamable HTTP, inspect it locally, and connect to it with a Python MCP client. Let's get started. What Are We Building? We will create a small developer knowledge-base server . It will expose three MCP primitives: Tool: search_kb(query, limit) Resource: kb://articles Prompt: draft_support_reply(customer_message) The server will contain no user session state. Every request will contain everything required to process it, which makes it a good example of the new stateless MCP model. Conceptually: LLM Host | | MCP request v +-----------------------+ | Python MCP Server | | | | search_kb() | | kb://articles | | draft_support_reply() | +-----------------------+ Step 1: Creating the Project The current Python SDK requires Python 3.10 or newer. The official documentation recommends installing the CLI extra because it gives us the development command and MCP Inspector workflow. Using uv : mkdir first-mcp-server cd first-mcp-server uv init uv add "mcp[cli]" Or with pip : pip install "mcp[cli]" Your project can be as small as: first-mcp-server/ ├── server.py └── client.py No framework boilerplate is required. Step 2: Creating Your First MCP Server Create server.py : from mcp.server import MCPServer mcp = MCPServer( "Developer Support KB", instructions=( "Use the knowledge-base tools to answer support questions. " "Prefer retrieved KB information over guessing." ), ) MCPServer is the high-level server API in the current Python SDK. For most servers, this is the API you want. The SDK also exposes a lower-level Server class, but that is intended for cases where you need exact control over schemas, protocol metadata, or custom methods. Now let's give our server some data. ARTICLES = [ { "id": "python-env", "title": "Creating a Python virtual environment", "body": ( "Create a virtual environment with `python -m venv .venv`, " "then activate it before installing dependencies." ), }, { "id": "reset-password", "title": "Resetting your password", "body": ( "Open Account Settings, choose Security, and select " "Reset Password. A verification email will be sent." ), }, { "id": "api-rate-limit", "title": "Understanding API rate limits", "body": ( "API rate limits restrict the number of requests allowed " "within a time window. Clients should retry using " "exponential backoff after receiving a rate-limit response." ), }, ] So far this is just Python. The interesting part begins when we expose functions through MCP. Step 3: Adding an MCP Tool An MCP tool is a function the model can decide to call. Add this to server.py : @mcp.tool() def search_kb(query: str, limit: int = 3) -> list[dict[str, str]]: """Search the support knowledge base. Args: query: Words or phrases to search for. limit: Maximum number of articles to return. """ query = query.lower() matches = [] for article in ARTICLES: searchable_text = ( article["title"] + " " + article["body"] ).lower() if query in searchable_text: matches.append(article) return matches[:limit] Notice what we did not write. There is no JSON Schema. There is no manually written tool manifest. There is no argument parser. The SDK derives the tool definition from the Python function itself. Its type hints become the MCP input schema, and defaults such as: limit: int = 3 make parameters optional in the generated schema. The official SDK documentation uses exactly this pattern. Conceptually, your function: def search_kb( query: str, limit: int = 3 ) becomes something similar to: { "name": "search_kb", "inputSchema": { "type": "object", "properties": { "query": { "type": "string" }, "limit": { "type": "integer", "default": 3 } }, "required": ["query"] } } This is one of the reasons MCP development in Python feels pleasantly ordinary: your function signature is effectively your interface definition. Step 4: Adding a Resource Tools are actions the model can call. Resources are different. They expose information that the host application can load into context. Add: @mcp.resource("kb://articles") def list_articles() -> str: """Return the available knowledge-base articles.""" lines = [] for article in ARTICLES: lines.append( f"{article['id']}: {article['title']}" ) return "\n".join(lines) The resource has the URI: kb://articles A client can read it without invoking a tool. Resource ≈ data that can be read Tool ≈ function that can perform work The SDK documentation roughly compares resources with GET -like behavior and tools with action-oriented POST -like behavior. Step 5: Adding an MCP Prompt We can also expose a reusable prompt template. @mcp.prompt() def draft_support_reply(customer_message: str) -> str: """Create a prompt for drafting a concise support response.""" return f""" You are a technical support assistant. Write a concise and helpful response to this customer message: {customer_message} Use the support knowledge base when relevant. Do not invent product policies. """.strip() Again, this is just a Python function plus a decorator. Prompts are generally initiated by the user or host rather than autonomously invoked by the model. The current SDK supports tools, resources, and prompts through the same decorator-oriented server interface. At this point, server.py looks like this: from mcp.server import MCPServer mcp = MCPServer( "Developer Support KB", instructions=( "Use the knowledge-base tools to answer support questions. " "Prefer retrieved KB information over guessing." ), ) ARTICLES = [ { "id": "python-env", "title": "Creating a Python virtual environment", "body": ( "Create a virtual environment with `python -m venv .venv`, " "then activate it before installing dependencies." ), }, { "id": "reset-password", "title": "Resetting your password", "body": ( "Open Account Settings, choose Security, and select " "Reset Password. A verification email will be sent." ), }, { "id": "api-rate-limit", "title": "Understanding API rate limits", "body": ( "API rate limits restrict the number of requests allowed " "within a time window. Clients should retry using " "exponential backoff after receiving a rate-limit response." ), }, ] @mcp.tool() def search_kb( query: str, limit: int = 3, ) -> list[dict[str, str]]: """Search the support knowledge base.""" query = query.lower() matches = [] for article in ARTICLES: searchable_text = ( article["title"] + " " + article["body"] ).lower() if query in searchable_text: matches.append(article) return matches[:limit] @mcp.resource("kb://articles") def list_articles() -> str: """Return the available knowledge-base articles.""" return "\n".join( f"{article['id']}: {article['title']}" for article in ARTICLES ) @mcp.prompt() def draft_support_reply( customer_message: str, ) -> str: """Create a support-response prompt.""" return f""" You are a technical support assistant. Write a concise and helpful response to this customer message: {customer_message} Use the support knowledge base when relevant. Do not invent product policies. """.strip() if __name__ == "__main__": mcp.run("streamable-http") That is a complete network-accessible MCP application.

由 claude-code/sonnet 自動生成