
如果你也把大模型聊天窗口玩到没意思了——问知识它答得头头是道但一问“我这个数据库里超过7天未付款的订单有哪些”它就只能摊手说“我无法直接访问你的数据库”那你就已经在MCP的射程范围里了。MCPModel Context Protocol说白了就是一套让AI模型能调用外部工具的统一插口标准它把“模型”和“工具”之间的连接方式变成了一套通用协议。最近后台和群里被MCP相关的问题持续轰炸干脆把自己从零手搓MCP Server、再一路部署到线上并完成客户端配置的整套流程整理出来。这篇文章适合刚听说MCP但还没上过手的前后端开发也适合已经被各种MCP配置搞到头皮发麻的初级工程师。全篇不讲虚的直接把能复现的步骤、可参考的代码和我踩过的坑都摊开来写。1. 动手之前先把MCP掰开揉碎设计及原理拆解1.1 MCP到底是谁和谁在通信很多人第一次看MCP的介绍都会被一堆名词搞晕Host、Client、Server怎么又多出来一个客户端其实拆开来看就一句话AI应用是HostHost内部内嵌一个MCP ClientMCP Client负责和MCP Server通信MCP Server才是真正执行工具逻辑的地方。我用一个“商城App”的类比帮你记住Host就是商城App本身你做用户看到的是它Client是App里的收银台负责对接各个外部商家Server就是商家本身就差MCP Server必须单独进程运行Claude Desktop和Cursor这类宿主程序会自己拉起这个进程。Host和Server之间通过JSON-RPC 2.0格式的消息互相喊话。MCP Server内部主要暴露三类“能力”这个在协议里称为原语原语作用生活类比Tools工具让模型可以触发一个动作比如查数据库、发HTTP请求点单按钮按一下厨房就动起来Resources资源以URI方式暴露数据比如读取某个配置项、加载某个文件片段菜单可以随时翻看但不会让厨房炒菜Prompts提示模板预置一组可复用的提示词模板招牌套餐一键点好固定搭配这三类东西才是MCP Server真正要实现的全部“业务”。至于你看到的各种MCP服务器实现无非是把这三类能力包装成代码里的一个个方法而已。1.2 为什么值得自己从0写一个现在网上随手一搜就是一堆现成MCP ServerGitHub上有各种“awesome-mcp”合集连数据库、浏览器、设计稿都有官方或社区实现。那为什么还要自己从0写最核心原因是你的业务别人写不了。我想把一个内部订单系统暴露给AI翻遍GitHub也找不到一个能直接连我司数据库、又只开放查询权限还带上风控规则的现成server。找人改不如自己手搓因为MCP Server本身没有“魔法”它就是一个接收请求、执行业务、返回结果的普通服务你写接口怎么设计写MCP Server就怎么设计。另一个原因是安全边界需要自己控制。直接装一个第三方MCP Server等于让它可以访问本地文件、环境变量甚至整个网络。自己写能清楚地知道暴露了什么、底层的权限边界在哪。尤其是公司内部用代码可控比功能丰富更重要。从学习角度讲手搓一遍以后再看别人的实现会非常轻松。协议虽然叫协议但本质只是一个消息格式约定你见过一次“揭开锅盖”的样子后面所有工具都只是不断重复同样的模式。1.3 语言与传输方案怎么选这件事不用纠结太久我给出两个完全可行的组合。语言层面Python和TypeScript是最主流的两个选择。Python有官方维护的mcp包里面提供了FastMCP/Server两套API上手快TypeScript有官方的modelcontextprotocol/sdk如果你本来做前端或Node后端用起来完全没有心智负担。我的建议很朴素你平时熟悉哪个写哪个MCP协议本身和语言无关。传输模式则需要结合部署场景想清楚。早期MCP主推stdio和SSE两种模式现在官方趋势是推荐“Streamable HTTP”一种支持服务端推送的HTTP传输方式。我把它俩放在一起对比维度stdio模式Streamable HTTP模式通信方式父进程与子进程通过标准输入输出通过HTTP POST请求可流式返回适用场景本地桌面客户端、个人开发调试远程服务、多用户共享、生产集群配置成本配置命令和参数拉起子进程配置URL和鉴权头需考虑网络安全调试难度需要看标准输出日志可以用curl直接模拟请求优点进程间隔离权限清晰无需开端口可横向扩展方便接入API网关我的选型经验只想在本机让Claude Desktop调用你的小工具选stdio就够了简单直接只要打算给团队用、放到服务器上甚至对外开放直接用HTTP模式省得后面返工。后面开发部分我先带你把服务写出来部署章节再展开讲这两套模式的具体落地。2. 开发部分从空目录到一个能跑的服务2.1 环境准备与工程初始化我以Python路线来演示如果你用TypeScript思路完全一样只是SDK和装饰器写法不同。前置要求其实很低Python 3.10以上有一个顺手用的虚拟环境工具。我推荐直接用uv管理Python环境和依赖创建项目特别快。没有的话用系统python3 -m venv也没问题。# 创建并进入项目目录 mkdir mcp-demo cd mcp-demo # 创建虚拟环境 uv venv source .venv/bin/activate # Windows下执行 .venv\Scripts\activate # 安装官方Python SDK pip install mcp[cli]装完之后项目结构不需要太复杂我习惯这样组织mcp-demo/ ├── .venv/ # 虚拟环境 ├── server/ │ ├── __init__.py │ └── demo.py # MCP Server核心代码 ├── config/ │ └── app_config.json # 一些资源数据 ├── .env # 密钥、环境变量不要提交到仓库 └── pyproject.toml这里想多说一句如果你打算后面做远程部署从一开始就把server/目录独立出来并且把配置文件和密钥分开写会省很多事。我已经见过太多人把所有代码平铺在一个main.py里最后要上线时拆也不是不拆也不是。2.2 第一个工具注册一个最低可用的 Tool一个MCP Server的“最小闭环”包含三件事创建Server实例、注册工具、运行进程。下面这段代码就是一个完整可运行的MCP Server。from mcp.server.fastmcp import FastMCP import datetime # 实例化Server名字会出现在宿主应用的列表里 mcp FastMCP(demo-time) mcp.tool() def current_time(timezone: str local) - str: 返回当前时间。参数 timezone 仅支持 local 或 UTC。 if timezone UTC: return datetime.datetime.now(datetime.timezone.utc).isoformat() return datetime.datetime.now().isoformat() if __name__ __main__: mcp.run()就这么点代码它已经是一个标准MCP Server。运行python server/demo.py就能启动但默认挂在stdio模式上直接跑不会有任何输出而是在等待父进程用JSON-RPC消息来“敲门”。注意函数名就是工具名函数注释就是工具描述参数列表会被自动解析成JSON Schema传给模型。这意味着AI能不能正确调用你的工具很大程度取决于你把描述写得清不清楚。比如把上面的注释改成“返回当前时间”模型也能工作但如果参数是时区缩写最好在描述里补充“比如输入Asia/Shanghai或UTC”模型调用成功率高很多。我建议你每写完一个工具就顺手把“这个工具解决了什么问题、什么场景下应该被调用、参数的含义和可选值”写清楚。这些文字不是给人看的是给模型看的它看不到你的代码实现只能靠描述理解工具意图。2.3 资源Resource与提示Prompt也别漏Tools经常被单独拎出来讲但一个真正好用的MCP Server通常会三类原语搭配使用。Resources适合暴露“只读数据”。比如应用配置、版本号、知识库片段可以让模型在对话中引用。代码写起来也很直观mcp.resource(config://app) def get_app_config() - str: 返回当前应用的配置文件内容。 import json with open(config/app_config.json, r, encodingutf-8) as f: return json.dumps(json.load(f), ensure_asciiFalse, indent2)资源通过URI来标识config://app这种自定义协议完全没问题。客户端如果需要读取这块内容会通过URL去访问。你还可以写在资源函数里返回任何可序列化的值。Prompts则用来预置一些提示模板。比如一个“创建周报”的Prompt可以接受项目名和本周关键词展开成一段结构化的提示词给模型用mcp.prompt() def weekly_report(project: str, keywords: str) - str: return f请帮我生成一份周报。 项目{project} 关键词{keywords} 要求分点描述语言精练每点不超过30字。什么时候用哪个我的经验是模型需要“干活”的时候用Tool模型需要“读资料”的时候用Resource模型需要“按固定套路生成内容”的时候用Prompt。不需要刻意把三种能力都堆上去按需暴露就好暴露得越多模型选错工具的概率也会变大。2.4 参数校验与异常返回最容易翻车的地方我见过不少新写MCP服务的人工具跑通一次就以为完事了结果模型一旦传了个预期之外的值整个工具直接抛异常客户端看到的就是一句大而化之的Tool execution failed没有任何可诊断的信息。第一个坑是参数类型不够严格。FastMCP会依据类型注解自动生成校验规则int就是整数str就是字符串但如果你遇到枚举或者复杂嵌套结构建议显式定义Pydantic模型来收口。比如一个查询工具想要接受“日期范围”和“状态”参数别用两个裸字符串拼直接定义成一个模型再传给函数。from pydantic import BaseModel, Field class QueryParams(BaseModel): start_date: str Field(description开始日期格式YYYY-MM-DD) end_date: str Field(description结束日期格式YYYY-MM-DD) status: str Field(defaultall, description状态筛选可选 all/pending/paid) mcp.tool() def query_order(params: QueryParams) - list: 按日期和状态查询订单。 # 你的业务逻辑 return [{id: 1, status: params.status}]第二个坑是错误处理。不要让异常直接冒到协议层最好在工具内部捕获预期内的错误并且明确告诉模型“你给参数给错了”还是“业务执行失败”。MCP协议里允许返回带isError标记的结构化内容演示代码里可以这样处理mcp.tool() def divide(a: float, b: float) - dict: 计算 a 除以 b。 if b 0: return { content: [{type: text, text: 除数不能为0请重新传入参数}], isError: True, } return {content: [{type: text, text: str(a / b)}]}这样做的好处是模型拿到错误信息后可以自行调整参数再试一次而不是直接整个对话卡死。3. 部署部分从本地进程到远程服务3.1 本地部署stdio 模式与客户端对接本地部署最典型的场景就是把写好的MCP Server接到Claude Desktop、VS Code或Cursor里。这里以Claude Desktop为例它的配置文件是claude_desktop_config.json在配置里声明一个server即可{ mcpServers: { time-server: { command: python, args: [/Users/jerry/mcp-demo/server/demo.py] } } }配置完重启Claude Desktop在MCP管理页面就能看到time-server在线。这时候模型已经知道你机器上跑着一个能看时间的工具你只需要在对话里问“现在几点了”它就会自动调用。这里有几个特别容易踩的细节command建议写绝对路径或者用uv run python这种完整前缀。因为桌面应用启动时不会加载你的shell配置文件PATH里可能根本没有你平时用的Python环境。args里的脚本路径也写绝对路径。我用过相对路径结果因为桌面应用的“当前工作目录”和我想的不一样折腾了半天。Windows上路径分隔符是反斜杠记得转义[C:\\Users\\jerry\\mcp-demo\\server\\demo.py]。这个模式的本质是宿主程序负责创建子进程然后通过标准输入和输出传输JSON-RPC消息。所以你在终端里手动python server/demo.py不会看到输出那是正常的stdio模式下输出通道被协议消息占用了。如果实在想看日志把日志写到文件里。3.2 远程部署把 MCP Server 改成 HTTP 模式如果想让多个团队成员都能用这个MCP服务就不能把进程拉在各自电脑上得部署到服务器上。远程部署的核心是把传输方式从stdio切换成HTTP。官方SDK提供了一个简单的切换方式在mcp.run()时指定if __name__ __main__: mcp.run(transporthttp)启动后服务会监听本机端口默认暴露的路径是/mcp。你没有看错就是这一行改动一个MCP HTTP服务就起来了。它会自动处理JSON-RPC over HTTP、流式响应、会话管理这些底层细节。不过生产环境我不会直接裸跑这个进程一定会套一层反向代理和进程守护。我的标准做法是用systemd管理Python进程再用Caddy或者Nginx做反向代理这里给一段最小systemd配置作参考[Unit] DescriptionMCP Demo Server Afternetwork.target [Service] WorkingDirectory/opt/mcp-demo ExecStart/opt/mcp-demo/.venv/bin/python /opt/mcp-demo/server/demo.py Restartalways EnvironmentFile/opt/mcp-demo/.env [Install] WantedBymulti-user.target然后反向代理配置里把example.com/mcp转发到本机的MCP端口。部署完以后远程MCP地址就是https://example.com/mcp。这里要特别留意环境变量。密钥、数据库连接串都放到.env文件绝不写进代码。我见过有人在代码里硬编码API Key结果打包镜像时泄漏到私有仓库非常不安全。3.3 鉴权与安全远程服务必须补刀一个MCP Server一旦暴露在公网等于你把内部工具接口开放给了全世界任何会发HTTP请求的人都能让你的工具跑起来。这是远程部署最危险的地方鉴权一步都不能省。最简单的做法是在网关层加一个校验。如果你用Caddy或Nginx可以加基础认证更好的方案是用API Key做头部校验。下面是一个用FastAPI手写中间层的思路在这个层面挡住未授权请求from fastapi import FastAPI, Header, HTTPException import secrets app FastAPI() def verify(api_key: str Header(default, aliasX-API-Key)): if not secrets.compare_digest(api_key, your-long-random-key): raise HTTPException(status_code401, detailunauthorized) app.post(/mcp) async def mcp_endpoint(payload: dict, api_key: str Header(default, aliasX-API-Key)): verify(api_key) # 将请求转发给MCP应用处理 return await proxy_to_mcp(payload)对于生产环境的更高要求MCP协议也支持接入OAuth2设备授权流适合大型组织统一管理访问权限。对于团队内部中小规模使用API Key已经足够。另外提醒一点MCP Server里暴露的工具权限等于模型能执行的权限。如果你注册了一个“执行SQL”的工具模型的能力边界就是你的SQL权限边界。建议在做远程部署前先列一张清单哪些工具可以公开、哪些只能内网访问、哪些需要额外的管理员审批。这个清单既是安全文档也是后面配置网关ACL的输入。4. 配置进阶与生态联动4.1 在常用宿主里配置 MCPClaude / VS Code / Cursor / Dify一个写好的MCP Server能不能被用起来取决于客户端配置对不对。这里我整理一份常用配置速查Claude Desktop和Cursor走的是同一套JSON配置方式前面已经给过stdio示例。如果连的是远程服务配置就变成URL形式{ mcpServers: { remote-tools: { url: https://mcp.example.com/mcp, headers: { Authorization: Bearer your-token-here } } } }VS Code里不需要手改JSON直接命令面板搜MCP: Add Server选择stdio或者HTTP填参数即可。添加成功后状态栏会有MCP相关图标可以展开查看工具列表。Dify这类AI应用开发平台也支持接入MCP服务。原理和上面一样只是在平台上填写远程MCP地址和鉴权头然后在Agent应用里把MCP提供的工具当作插件节点来引用。对于做AI产品的团队把MCP Server接入Dify等于让平台上的Agent应用直接获得外部数据能力。4.2 把本地模型和第三方工具接进来Ollama、Figma、BlenderMCP生态边界比你想象中宽得多它并不绑定某一个模型厂商。你在Ollama本地部署的模型也可以通过MCP调用外部工具形成“本地模型外部工具”的组合。我做实验时就是在Ollama上跑一个本地模型同时挂一个MCP Server提供搜索能力模型能自己在对话里决定什么时候调用搜索。设计类工具也有大量MCP实践。比如Figma MCP能让模型读取设计稿里的图层和样式数据用于前端代码生成Blender MCP则能让模型通过命令行控制3D建模软件的某些操作。这类现成服务很多没必要自己写但它们都遵循同一个协议说明你现在写的MCP Server未来同样可以被其他支持MCP的工具挂载。数据库场景我建议优先考虑现成实现。MySQL、PostgreSQL、SQL Server都有对应的MCP Server通常只要配好连接串就能用。如果这些现成服务不够贴合自己的表结构也可以按本文2.4节的方式写一个自定义查询工具本质上就是封装SQL执行逻辑。4.3 多服务与统一入口聚合网关的玩法当你手上的MCP Server多起来以后会遇到一个新的烦恼客户端配置里逐渐堆满三四个server每个都维护各自的鉴权和地址。这时候可以考虑在入口层做聚合。做法是在客户端和实际MCP Server之间加一层代理网关网关对外暴露一个统一地址内部再按工具名或请求头路由到不同的上游Server。这样客户端只需要配置一个MCP地址而运维上可以在网关层统一做鉴权、限流、日志审计。现在社区已经有若干开源的MCP代理实现大体思路是网关实现MCP协议的服务端和客户端两边收到请求后转发给对应的上游Server。如果你准备走这条路线建议先验证当前SDK版本和代理实现是否兼容因为MCP协议版本迭代很快旧代理可能不支持新的Streamable HTTP传输。5. 常见问题与排错实录5.1 我遇到的几个高频问题写MCP Server最麻烦的不是代码而是“看起来服务起来了但客户端就是连不上”。这里把我自己踩过的高频问题整理成一张速查表现象常见原因解决思路客户端报Cannot connect to MCP serverstdio模式下command或args配置错误进程没有正常启动在终端手动运行完整命令确认进程能起来检查绝对路径服务起来了但工具列表为空函数没有加mcp.tool()装饰器或Python文件里没有实例化Server逐行检查装饰器尤其是复制粘贴时容易缩进错乱请求报参数校验错误模型传入的参数与Schema不一致或类型注解写得太宽泛用Pydantic模型定义复杂参数描述里写清每个字段取值范围HTTP模式下请求返回404访问路径不是MCP暴露的/mcp路径或没有走对端口确认配置里的URL路径用curl直接打一遍验证每次调用都重新初始化FastMCP默认没有做连接池或全局状态复用全局变量初始化一次不要在工具函数里重复建连接密钥泄漏到日志打印了Authorization头或完整URL日志输出前做脱敏统一用***替换token5.2 调试三板斧日志、Inspector、手测JSON-RPC第一板斧是日志。MCP Server运行在stdio模式下日志不能直接打到标准输出否则会和协议消息串在一起。SDK一般会把协议本身的日志输出到stderr你自己的业务日志建议用logging模块写文件。远程部署时更是如此systemd有journalctl可以看进程输出这比猜要快得多。第二板斧是MCP Inspector。官方提供了一个可视化调试工具能让你在浏览器界面里连接MCP Server直接查看工具列表、手动调用工具、查看返回结果。启动命令很简单npx modelcontextprotocol/inspector打开界面后选择标准输入输出模式填上启动你Python脚本的命令就能连上正在调试的Server。这一步能帮你确认“到底是Server有问题还是客户端配置有问题”。第三板斧是直接在终端模拟JSON-RPC请求。远程部署后用curl最容易确认服务是否活着curl -N -X POST https://mcp.example.com/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -H X-API-Key: your-token \ -d {jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:test,version:1.0}}}如果这一步能看到返回结果说明服务和鉴权都通了再不通过就逐层排查反向代理、防火墙、鉴权逻辑。5.3 把坑留在岸上一些长期受益的习惯我做完第一个MCP Server之后最大的体会有三件事。第一版本锁好。把SDK版本固定下来别随手pip install -U。MCP协议还在快速演进中SDK升级后很可能出现之前能用的某个方法被废弃。第二权限最小化。无论本地还是远程只暴露必要的工具。不要因为写起来顺手就把一堆内部接口一股脑封装成工具。模型不可控工具越多越危险。第三命名规范。工具名和参数名尽量用动词开头且不要包含特殊符号。模型主要通过名字和描述理解工具query_order比getData2好理解一万倍。最后再分享一个小经验手搓完第一个MCP Server之后回头看会发现这玩意儿没那么玄乎本质上就是写了一个遵循特定消息格式的JSON-RPC服务。真正让我觉得有价值的是它让我重新审视了“哪些能力可以放心交给AI来调度”。MCP给的那套原语其实也在帮你梳理业务的边界哪些是只读资源、哪些是会改状态的动作、哪些是固定套路。如果你还没想好第一个Server做什么我的建议是从你最高频的“重复性工作”入手。比如把公司内部的项目模板、接口文档、常用SQL片段做成Resources和Tools让模型帮你快速生成草稿或查询结果。我自己把会议纪要和代码仓库MR草稿接进MCP之后日常沟通效率确实上了一个台阶之前要开三个系统才能凑齐的信息现在一句对话就能拿到草稿。希望这篇能让你少踩几个坑赶紧去搓一个属于你自己的MCP Server试试。