
1. 项目概述为什么今天必须亲手搭一个 MCP ServerMCP Server——这个词最近在开发者圈子里出现的频率已经快赶上“JSON-RPC”和“本地工具链”了。它不是某个大厂新推的云服务也不是某款付费插件的营销话术而是一个正在快速落地的、实实在在的协议层基础设施。简单说MCPModel Context Protocol解决的是一个非常具体又极其普遍的痛点大模型怎么安全、可控、可追溯地调用你电脑上已有的真实工具比如让AI自动打开Excel处理数据、调用Python脚本清洗日志、启动Wireshark抓包分析、甚至控制KiCad完成PCB设计检查——这些操作过去要么靠硬编码集成要么靠不稳定的剪贴板中转要么干脆手动点鼠标。MCP Server 就是那个站在模型和工具之间的“调度员守门人记录员”。我第一次接触这个需求是在帮一家做工业设备预测性维护的客户做POC时。他们训练了一个故障诊断模型但模型输出的“建议更换轴承”之后下一步该干什么总不能让工程师再手动打开PLC配置软件去下发指令吧我们试过用LangChain的Tool抽象也试过自研HTTP微服务封装Python脚本但都卡在权限隔离、上下文传递、错误溯源这三关上。直到看到MCP规范草案里那张清晰的三层架构图Client模型前端→ MCP Server本地代理→ Tool真实可执行程序才意识到——我们缺的不是功能而是标准协议层。MCP Server 不是替代你的工具而是给所有工具装上统一的“USB-C接口”让任何兼容MCP的客户端都能即插即用。所以“从 0 到 1 构建自己的工具服务”这句话里的“自己”指的不是从零写一个全新工具而是把散落在你系统里的、早已存在的生产力工具Excel、Python、Git、FFmpeg、甚至AutoCAD的命令行接口通过MCP协议标准化地暴露出来形成一个受控、可审计、可组合的本地服务网络。它不依赖云端API不上传你的数据不绑定特定厂商核心逻辑全在你自己的机器上跑。这正是当前很多技术决策者最看重的——可控性。你不需要成为协议专家但必须理解它的设计哲学最小信任、显式授权、结构化上下文。接下来的内容就是我用两周时间从读第一行MCP RFC文档到让本地Chrome插件成功调用一个Python数据分析脚本的全过程复盘。所有代码、配置、踩坑记录全部公开你可以直接抄作业。2. 协议与架构深度拆解MCP Server 到底在做什么2.1 理解 MCP 的本质不是 API而是“工具操作系统”很多人第一眼看到 MCP会下意识把它当成另一个 RESTful API 规范。这是最大的认知偏差。MCP 的核心定位是为大模型提供一个标准化的、面向工具Tool的操作系统接口。它不关心你模型内部怎么推理只定义三件事工具发现DiscoveryServer 如何告诉 Client “我这里有哪些工具可用每个工具长什么样”工具调用InvocationClient 如何向 Server 发起一次调用请求参数怎么传格式怎么约定结果反馈Response ErrorServer 执行完后如何把结果、进度、错误信息以结构化方式回传给 Client这三点共同构成了一个闭环的“工具生命周期管理协议”。它刻意避开了 HTTP 状态码、OAuth2 授权、JWT Token 这些 Web 层概念因为它的运行环境默认是本地可信域localhost。你不需要 HTTPS 证书不需要跨域配置不需要用户登录态——因为调用方比如你浏览器里的一个插件和被调用方你电脑上的 Python 脚本本就是同一个物理设备上的进程。这种设计极大降低了入门门槛但也意味着安全性完全依赖于本地进程隔离和显式授权机制。这也是为什么 MCP Server 必须由用户主动启动并明确告知“允许哪些工具被调用”。2.2 JSON-RPCMCP 的底层通信骨架MCP 协议本身不定义传输层它选择 JSON-RPC 2.0 作为其默认的序列化与通信协议。这不是随意选的而是经过深思熟虑的权衡轻量且成熟JSON-RPC 是一个极简的远程过程调用规范只有method、params、id、result、error几个核心字段。没有 REST 那么多动词GET/POST/PUT/DELETE和资源路径设计负担也没有 gRPC 那样需要预编译 IDL 文件。对于一个主要在 localhost 上跑、调用频率不高但要求语义清晰的协议来说JSON-RPC 的“够用就好”哲学非常契合。双向流支持MCP 规范中有一个关键能力叫progress即工具执行过程中可以主动推送中间状态比如“已处理 50% 的文件”、“正在连接数据库…”。JSON-RPC 2.0 原生支持通知Notification消息Client 可以订阅这些progress事件而无需轮询或建立额外的 WebSocket 连接。这大大简化了 Server 端的实现复杂度。语言无关性只要能解析 JSON就能实现 MCP Server。Python 的jsonrpcserver库、Node.js 的json-rpc-2.0、Go 的gorilla/rpc甚至 Rust 的jsonrpsee都能无缝对接。这意味着你完全可以根据手头现有工具的开发语言来选择 Server 实现方案而不是被框架绑架。举个实际例子当 Chrome 插件Client想调用你的data_cleaner.py工具时它发出的 JSON-RPC 请求长这样{ jsonrpc: 2.0, method: data_cleaner.run, params: { input_file: /home/user/reports/raw.csv, output_format: xlsx, remove_duplicates: true }, id: 42 }而 Server 执行完毕后返回的响应可能是{ jsonrpc: 2.0, result: { status: success, output_file: /home/user/reports/cleaned.xlsx, row_count: 1247, duration_ms: 328 }, id: 42 }整个过程没有 URL 路径没有 HTTP Header没有 Cookie只有纯粹的“调用什么方法、传什么参数、得到什么结果”。这就是 MCP 的干净之处。2.3 MCP Server 的核心职责远不止是转发器一个合格的 MCP Server绝不能只是一个简单的“请求转发器”。它必须承担起以下四个关键角色缺一不可工具注册中心RegistryServer 启动时必须扫描并加载所有已声明的工具。每个工具需要提供一份tool.json描述文件包含名称、描述、输入参数 SchemaJSON Schema 格式、输出 Schema、是否支持progress事件等元信息。Server 将这些信息汇总响应 Client 的list_tools请求。我见过太多初学者直接把 Python 脚本路径硬编码进 Server结果导致工具列表无法动态更新或者参数校验形同虚设。安全沙箱Sandbox这是 MCP Server 区别于普通 RPC 服务的最关键一点。Server 必须对每个工具的执行环境进行严格管控。例如禁止工具访问/etc/shadow或用户主目录以外的敏感路径限制内存占用不超过 512MBCPU 时间不超过 30 秒强制工具以非 root 用户身份运行对subprocess.Popen的shellTrue参数进行拦截防止命令注入。 这些不是可选项而是 MCP 规范明确要求的“最小安全基线”。我在测试阶段就因为没加内存限制导致一个失控的ffmpeg转码任务吃光了 16GB 内存差点把整台机器拖垮。上下文桥接器Context BridgeMCP 的核心价值之一是让模型能“记住”之前调用过的工具结果。比如模型先调用web_search获取信息再调用summarize工具处理搜索结果。Server 必须在两次调用之间安全地传递前一个工具的result作为后一个工具的params输入。这要求 Server 维护一个轻量级的、基于id或session_id的上下文缓存并确保缓存数据不会被恶意 Client 伪造或越界访问。审计日志生成器Audit Logger每一次工具调用无论成功失败Server 都必须生成一条结构化日志至少包含时间戳、Client IP虽然是 localhost但记录为127.0.0.1、调用的工具名、参数摘要注意脱敏如密码字段显示为***、执行耗时、返回状态码。这条日志不仅是事后排查的依据更是未来实现“谁在什么时候调用了什么”的合规性基础。我建议直接对接系统的syslog或写入一个独立的mcp-audit.log文件而不是打印到 stdout。3. 从零开始搭建实操步骤与核心代码详解3.1 环境准备与依赖选择为什么选 Python FastAPI搭建 MCP Server 的技术栈选择本质上是一场“开发效率”与“生产稳定性”的平衡游戏。我对比过几种主流方案纯 Node.jsExpress json-rpc-2.0启动快生态丰富但对 Python 工具的调用需要child_process参数序列化和错误捕获比较繁琐且 Node.js 的spawn在 Windows 上对.bat文件的支持不如 Python 稳定。Gogin jsonrpc2性能无敌二进制部署方便但 Go 的exec.Command调用外部 Python 脚本时环境变量继承尤其是PYTHONPATH容易出问题调试周期长。Rustaxum jsonrpsee理论上最安全但学习曲线陡峭社区对 MCP 的现成支持几乎为零90% 的工作都要自己造轮子。最终我选择了Python 3.10 FastAPI jsonrpcserver的组合。理由很实在Python 是绝大多数数据科学、自动化脚本的首选语言你的工具大概率已经是.py文件FastAPI 提供了开箱即用的异步支持、自动文档Swagger UI、依赖注入极大简化了 Server 的 HTTP 层封装jsonrpcserver库虽然小众但代码干净async_dispatch方法完美匹配 FastAPI 的异步路由且对progress事件的支持只需几行代码。安装命令如下建议在虚拟环境中操作python -m venv mcp_env source mcp_env/bin/activate # Linux/macOS # mcp_env\Scripts\activate # Windows pip install fastapi uvicorn jsonrpcserver pydantic python-dotenv提示不要用pip install fastapi[all]它会安装一堆你用不到的依赖如ujson,orjson反而增加潜在冲突风险。按需安装更稳妥。3.2 工具注册与描述tool.json是你的契约MCP Server 的灵魂不在代码而在tool.json。它不是一份技术文档而是 Server 和 Client 之间的一份法律契约。Client 会严格按照这个文件定义的 Schema 来构造请求参数Server 也必须严格按照它来校验和解析。一个典型的data_cleaner.py工具其配套的tool.json应该长这样{ name: data_cleaner.run, description: 清洗CSV或Excel文件支持去重、格式转换、空值填充, input_schema: { $schema: https://json-schema.org/draft/2020-12/schema, type: object, properties: { input_file: { type: string, description: 输入文件的绝对路径必须存在且可读 }, output_format: { type: string, enum: [csv, xlsx, json], default: xlsx }, remove_duplicates: { type: boolean, default: false }, fill_na_value: { type: [string, number, null], default: } }, required: [input_file] }, output_schema: { type: object, properties: { status: {type: string, enum: [success, error]}, output_file: {type: string}, row_count: {type: integer}, duration_ms: {type: number} } }, supports_progress: true, executable_path: ./tools/data_cleaner.py }关键点解析name字段必须全局唯一且遵循namespace.action的命名规范如git.commit,excel.export避免clean这种过于宽泛的名字。input_schema使用标准 JSON SchemaServer 启动时会用jsonschema.validate()进行预校验。如果 Client 传了{input_file: 123}数字而非字符串Server 会直接返回InvalidParams错误根本不会调用工具。executable_path是相对路径指向你的工具脚本。Server 会以该路径为基准构建完整的绝对路径。这比硬编码绝对路径更利于版本管理和 Docker 部署。我专门写了一个tool_registry.py模块负责扫描./tools/目录下的所有tool.json文件并验证其合法性import json import os from jsonschema import validate, ValidationError from pathlib import Path def load_tool_definitions(tool_dir: str ./tools) - dict: tools {} tool_dir_path Path(tool_dir) for tool_json in tool_dir_path.rglob(tool.json): try: with open(tool_json, r, encodingutf-8) as f: defn json.load(f) # 强制校验 schema validate(instancedefn, schemaTOOL_SCHEMA) # 解析 executable_path 为绝对路径 exec_path tool_dir_path / defn[executable_path] if not exec_path.exists(): raise ValueError(fExecutable not found: {exec_path}) tools[defn[name]] { definition: defn, script_path: str(exec_path.resolve()) } except (ValidationError, ValueError, json.JSONDecodeError) as e: print(f❌ Invalid tool definition {tool_json}: {e}) continue return tools这个模块会在 Server 启动时被调用任何不符合规范的tool.json都会被静默跳过并打印错误日志。这保证了 Server 的健壮性——坏工具不会拖垮整个服务。3.3 核心 Server 实现FastAPI JSON-RPC 的优雅结合真正的魔法发生在main.py里。我们的目标是用 FastAPI 提供一个/rpc端点接收所有 JSON-RPC 请求并将其分发给对应的工具。代码结构如下from fastapi import FastAPI, Request, Response from fastapi.responses import JSONResponse from jsonrpcserver import async_dispatch, method, Result, Success, Error from jsonrpcserver.exceptions import InvalidParams, MethodNotFound import asyncio import logging from tool_registry import load_tool_definitions from sandbox_executor import execute_tool_safely # 我们稍后会实现这个 app FastAPI(titleMCP Server, version0.1.0) TOOLS load_tool_definitions() # 全局加载工具定义 app.post(/rpc) async def handle_rpc(request: Request): 处理所有 JSON-RPC 2.0 请求 try: # 1. 读取原始 body保持字节流避免 FastAPI 自动 decode 导致乱码 body await request.body() json_request body.decode(utf-8) # 2. 使用 jsonrpcserver 的 async_dispatch 进行分发 # 注意这里我们不直接注册 method而是动态查找 response await async_dispatch( json_request, methods{ list_tools: list_tools_handler, get_tool_info: get_tool_info_handler, run_tool: run_tool_handler, } ) # 3. 返回标准 JSON-RPC 响应 return JSONResponse(contentresponse, media_typeapplication/json) except Exception as e: logging.error(fRPC dispatch error: {e}) return JSONResponse( content{jsonrpc: 2.0, error: {code: -32603, message: Internal error}, id: None}, status_code500 ) # Handler 实现 method async def list_tools_handler() - Result: 返回所有已注册工具的 name 和 description return Success([ {name: name, description: tool[definition][description]} for name, tool in TOOLS.items() ]) method async def get_tool_info_handler(name: str) - Result: 返回指定工具的完整定义 if name not in TOOLS: raise MethodNotFound(fTool {name} not found) return Success(TOOLS[name][definition]) method async def run_tool_handler(name: str, params: dict) - Result: 执行指定工具返回结果或错误 if name not in TOOLS: raise MethodNotFound(fTool {name} not found) tool_def TOOLS[name] script_path tool_def[script_path] # 关键调用沙箱执行器 try: result await execute_tool_safely(script_path, params, tool_def[definition]) return Success(result) except Exception as e: logging.error(fTool {name} execution failed: {e}) return Error(code-32000, messagestr(e))这段代码的精妙之处在于它没有把每个工具都注册为一个独立的method而是用一个通用的run_tool方法通过name参数动态路由。这使得新增工具无需修改 Server 代码只需放好tool.json和脚本即可。async_dispatch是异步的能充分利用 FastAPI 的并发能力。即使一个工具执行慢比如ffmpeg转码也不会阻塞其他请求。所有异常都被捕获并转化为标准的 JSON-RPC 错误码如-32601表示方法不存在-32602表示参数错误Client 可以统一处理无需关心底层是 Python 还是 Shell 报错。3.4 沙箱执行器安全运行外部工具的核心execute_tool_safely是整个 Server 的安全心脏。它必须做到隔离、限流、监控、超时。我的实现基于asyncio.subprocess并集成了psutil进行资源监控import asyncio import psutil import tempfile import os from pathlib import Path async def execute_tool_safely(script_path: str, params: dict, tool_def: dict) - dict: 在安全沙箱中执行工具脚本 # 1. 创建临时工作目录隔离文件操作 with tempfile.TemporaryDirectory() as temp_dir: # 2. 将 params 序列化为 JSON 文件供脚本读取 input_file Path(temp_dir) / input.json input_file.write_text(json.dumps(params, ensure_asciiFalse), encodingutf-8) # 3. 构建执行命令 cmd [ python, str(script_path), --input, str(input_file), --output-dir, temp_dir ] # 4. 启动子进程设置严格限制 process await asyncio.create_subprocess_exec( *cmd, cwdtemp_dir, stdoutasyncio.subprocess.PIPE, stderrasyncio.subprocess.PIPE, env{**os.environ, PYTHONUNBUFFERED: 1}, # 强制实时输出 limit1024*1024 # 限制单次读取缓冲区大小 ) # 5. 启动资源监控协程 monitor_task asyncio.create_task( monitor_process_resources(process.pid, max_memory_mb512, timeout_sec30) ) try: # 6. 等待进程结束带超时 stdout, stderr await asyncio.wait_for( process.communicate(), timeout30 ) # 7. 等待监控任务完成 await monitor_task # 8. 解析脚本输出 output_file Path(temp_dir) / output.json if output_file.exists(): result json.loads(output_file.read_text(encodingutf-8)) return result else: raise RuntimeError(fTool {script_path} did not generate output.json) except asyncio.TimeoutError: # 9. 超时则强制终止 process.kill() await process.wait() raise TimeoutError(Tool execution timed out) except Exception as e: process.kill() await process.wait() raise e async def monitor_process_resources(pid: int, max_memory_mb: int, timeout_sec: int): 监控子进程的内存和CPU使用超限则杀死 start_time asyncio.get_event_loop().time() proc psutil.Process(pid) while True: try: # 检查是否已退出 if not proc.is_running(): break # 检查内存 memory_info proc.memory_info() if memory_info.rss max_memory_mb * 1024 * 1024: proc.kill() raise MemoryError(fProcess exceeded {max_memory_mb} MB memory limit) # 检查超时 if asyncio.get_event_loop().time() - start_time timeout_sec: proc.kill() raise TimeoutError(Resource monitoring timeout) await asyncio.sleep(0.5) # 每500ms检查一次 except psutil.NoSuchProcess: break except psutil.AccessDenied: break这个执行器的关键设计临时目录隔离每个工具都在独立的tempfile.TemporaryDirectory()中运行脚本无法访问你的真实家目录除非你显式在params中传入绝对路径而input_schema会校验该路径是否在白名单内。参数文件化不通过命令行参数传递复杂 JSON容易被 shell 注入而是将params写入一个临时 JSON 文件再让脚本读取。这彻底杜绝了命令注入风险。双超时机制asyncio.wait_for控制总执行时间monitor_process_resources协程独立监控内存两者互为保险。资源感知psutil的介入让 Server 能真正“看见”工具的资源消耗而不是靠猜测。3.5 工具脚本编写规范让 Python 脚本变成 MCP 工具最后一步也是最容易被忽视的一步如何编写一个符合 MCP 规范的工具脚本它不是随便写个print(Hello)就行。我为你提炼了四条铁律必须接受--input和--output-dir参数这是 MCP Server 与你脚本约定的“握手信号”。脚本启动后第一件事就是读取--input指向的 JSON 文件解析出params执行完毕后必须将结果写入--output-dir/output.json。必须支持progress事件如果声明了如果你的tool.json里写了supports_progress: true那么你的脚本在执行过程中应该定期向stdout输出一行 JSON格式为{event: progress, data: {percent: 50, message: 正在处理第1000行...}}。Server 会捕获这些行并通过 JSON-RPC 的 notification 机制推送给 Client。错误必须可捕获所有异常必须被捕获并写入output.json格式为{status: error, message: 详细错误信息, code: 123}。不要让脚本崩溃否则 Server 会收到一个空的stderr无法给出有意义的错误提示。输出必须结构化output.json的内容必须严格匹配tool.json中定义的output_schema。Server 会用 JSON Schema 进行校验不匹配则视为执行失败。一个符合规范的data_cleaner.py示例#!/usr/bin/env python3 import argparse import json import pandas as pd import time import sys def main(): parser argparse.ArgumentParser() parser.add_argument(--input, requiredTrue, helpInput params JSON file) parser.add_argument(--output-dir, requiredTrue, helpOutput directory) args parser.parse_args() # 1. 读取输入 with open(args.input, r, encodingutf-8) as f: params json.load(f) # 2. 验证必要参数 if not params.get(input_file) or not isinstance(params[input_file], str): write_error(args.output_dir, input_file is required and must be a string) return # 3. 开始执行模拟进度 for i in range(1, 101): # 4. 发送 progress 事件到 stdoutServer 会捕获 if i % 10 0: print(json.dumps({event: progress, data: {percent: i, message: fProcessing... {i}%}}, ensure_asciiFalse)) sys.stdout.flush() # 强制刷新确保 Server 立即收到 time.sleep(0.05) # 模拟耗时操作 # 5. 执行核心逻辑此处省略真实数据处理 try: df pd.read_csv(params[input_file]) if params.get(remove_duplicates, False): df df.drop_duplicates() output_file f{args.output_dir}/cleaned.{params.get(output_format, xlsx)} if params.get(output_format) xlsx: df.to_excel(output_file, indexFalse) else: df.to_csv(output_file, indexFalse) # 6. 写入成功结果 result { status: success, output_file: output_file, row_count: len(df), duration_ms: int((time.time() - start_time) * 1000) } write_output(args.output_dir, result) except Exception as e: write_error(args.output_dir, str(e)) def write_output(output_dir: str, data: dict): with open(f{output_dir}/output.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def write_error(output_dir: str, message: str): write_output(output_dir, {status: error, message: message}) if __name__ __main__: main()这个脚本就是你 MCP 生态中的一个“原子单元”。它不关心 Server 怎么调用它只专注做好一件事接收结构化输入产生结构化输出过程可观察。当你把这样的脚本放进./tools/目录Server 启动后它就自动变成了一个可被任何 MCP Client 调用的服务。4. 常见问题与排查技巧实录那些文档里不会写的坑4.1 “Chrome MCP Server 使用教程”失效的真相CSP 与 localhost 的战争网上流传的所谓“Chrome MCP Server 教程”十有八九卡在第一步Client 无法连接到http://localhost:8000/rpc。你以为是端口没开防火墙挡了其实根源在于 Chrome 的Content Security Policy (CSP)。现代 Chrome 扩展Manifest V3默认禁止所有http://请求除非你在manifest.json中显式声明{ content_security_policy: { extension_pages: script-src self; object-src self; }, host_permissions: [http://localhost:8000/*] }但即便如此当你在扩展的 popup 页面里用fetch调用http://localhost:8000/rpc时Chrome 仍会报错Refused to connect to http://localhost:8000/rpc because it violates the following Content Security Policy directive: connect-src self.这是因为connect-src指令默认只允许https://和chrome-extension://协议。解决方案有两个推荐方案用chrome.runtime.sendNativeMessage。这是 Chrome 专为 Native Messaging 设计的 API它绕过了 CSP 限制且更安全。你需要在manifest.json中注册一个 native hostexternally_connectable: { matches: [http://localhost:8000/*] }然后在扩展代码中chrome.runtime.sendNativeMessage(com.example.mcpserver, rpcRequest, (response) { console.log(MCP response:, response); });这要求你的 MCP Server 实现一个 Native Messaging Host一个监听 stdin/stdout 的 Python 脚本但这恰恰是 MCP 规范推荐的生产部署方式。临时方案禁用 Chrome 的安全策略仅开发用。启动 Chrome 时加上参数google-chrome --unsafely-treat-insecure-origin-as-securehttp://localhost:8000 --user-data-dir/tmp/chrome-test --user-data-dir/tmp/chrome-test这会告诉 Chrome“把http://localhost:8000当作安全源”。但切记这只是开发调试用永远不要在生产环境启用。4.2 “奥创中心的 tool 下载了之后点不开”Windows 上的 PATH 与权限陷阱很多用户下载了第三方工具比如 KiCad 的kicad-mcp-server.exe双击无反应任务管理器里也看不到进程。这通常不是程序坏了而是两个经典 Windows 问题PATH 未包含 Python 解释器如果这个工具是用 Python 写的.pyz或pyinstaller打包它启动时会尝试调用系统python.exe。但 Windows 默认不把 Python 加入 PATH导致启动失败。解决方案重新安装 Python在安装向导里勾选“Add Python to PATH”然后重启命令行。UAC 权限提升失败某些工具尤其是需要访问 COM 端口或 USB 设备的在启动时会请求管理员权限。如果用户双击.exeUAC 弹窗可能被后台窗口遮挡用户没看到程序就静默退出了。解决方案右键点击.exe选择“以管理员身份运行”或者在工具的快捷方式属性里勾选“高级” → “以管理员身份运行”。我遇到过一个真实案例一个用于控制 Arduino 的 MCP Tool在用户双击时完全没反应。用Process Monitor抓取日志才发现它在尝试打开COM3时被ACCESS_DENIED拦截。解决方案就是在tool.json的description里用醒目的文字注明“此工具需要管理员权限请右键选择‘以管理员身份运行’”。4.3 “登录失败failed to start login server”MCP Server 与传统 Login Server 的混淆搜索热词里频繁出现的login server error其实是个严重的概念混淆。MCP Server根本不处理用户登录。它假设调用者Client已经完成了身份认证比如 Chrome 扩展已经获得了用户授权或者桌面应用已经通过系统登录态鉴权。MCP 的login相关错误99% 都是因为端口被占用你启动了两个 MCP Server 实例都试图监听8000端口。用lsof -i :8000macOS/Linux或netstat -ano | findstr :8000Windows找到占用进程kill -9 PID干掉它。配置文件损坏tool.json里写了executable_path: ../bad/path.py而这个路径根本不存在。Server 启动时会静默失败但uvicorn日志里会有一行ERROR: Application startup failed。解决方案启动 Server 时加上--log-level debug查看详细错误堆栈。Python 环境不一致你在 VS Code 里用 Python 3.11 运行 Server但tool.json指向的脚本依赖pandas1.5.3而系统全局 Python 是 3.9导致导入失败。解决方案永远用同一个虚拟环境启动 Server 和运行工具。在tool.json的executable_path里不要写python而要写./venv/bin/pythonLinux/macOS或./venv/Scripts/python.exeWindows的绝对路径。4.4 性能瓶颈排查当ffmpeg让整个 Server 卡死MCP Server 的最大性能挑战从来不是并发数而是单个 CPU 密集型工具的执行。比如用ffmpeg转码一个 4K 视频它会吃满一个 CPU 核心导致其他工具请求排队等待。这不是 Server 的 bug而是设计使然。排查思路第一步确认是 CPU 还是 I/O 瓶颈。用htop或top观察 Server 进程的%CPU和%MEM。如果%CPU接近 100