FileUploadServer 开发记录:MCP 接口、分布式部署与踩坑(2026-08-02)

FileUploadServer 开发记录(2026-08-02)

记录范围:MCP 接口开发、分布式部署(网关 + WS 节点)、公开访问问题排查与屏蔽、部署流程优化 涉及版本b26d430(feat: MCP接口开发 + 网关更新(屏蔽公开访问)+ 部署skill优化)


一、概述

本次开发围绕三件事:

  1. 为文件服务器实现 MCP 接口FileUploadServer.Mcp),把上传/下载/删除/列表/详情/公开设置暴露为 AI 可调用的 6 个工具
  2. 分布式部署:网关(Web)更新到最新代码、清理 WS 存储节点重复进程
  3. 公开访问问题排查:定位 /p/ 路径 503 根因,最终决策屏蔽公开访问并标注问题待整改

二、MCP 接口开发

2.1 需求与方案

依据 doc/MCP-Development-Guide.md 实现。关键决策(与用户确认):

决策点 选择 理由
协议实现 手写 JSON-RPC 2.0 over stdio 零第三方依赖、完全掌控自定义错误码(-32602/-32xxx)、与项目手写 WS 协议的风格一致
鉴权 启动时环境变量注入 Master Admin Key 密钥不进入 LLM 上下文,安全
超时/重试 上传/下载 300s,其他 30s;5xx 指数退避重试 2 次 符合文档 §3
错误码 400/404→-32602、401/403→-32003、429→-32004、503→-32005、超时→-32000、连接失败→-32001 文档 §5

2.2 实现结构

FileUploadServer.Mcp/
├── Program.cs               # stdio 主循环、配置加载、优雅退出
├── McpServerConfig.cs       # 配置(BaseUrl / MasterKey / 超时 / 重试)
├── McpLogger.cs             # 结构化日志(写 stderr,不污染 stdout)
├── Protocol/                # JsonRpcRequest/Response/Error、McpError、StdioTransport
├── Server/                  # McpServer(生命周期状态机)、ToolDefinition(6工具定义)
├── Services/                # McpHttpClient(鉴权/超时/重试)、FileToolHandlers、ErrorMapper
└── Models/                  # FileItemDto、ApiKeyDto

核心设计

  • 双层错误模型:参数校验失败 → JSON-RPC error(-32602);下游 HTTP 失败 → CallToolResult isError:true + text 含错误码
  • tools/call 永远返回 CallToolResult(content[].text + isError),业务错误不抛 JSON-RPC error
  • 状态机initialize → notifications/initialized 门控,未初始化返回 -32002
  • 可测试性McpServer.HandleAsync(JsonRpcRequest) 可被测试直接驱动,不依赖真实 stdio

2.3 6 个工具

工具 HTTP 端点 校验
file_list GET /api/files
file_info GET /api/files/ file_id 必填 int
file_upload POST /api/files(multipart) local_file_path 必填、文件存在
file_download GET /api/files/download/ file_id 必填 int,Base64 返回
file_delete DELETE /api/files/ file_id 必填 int
file_set_public PUT /api/admin/files//public is_public=true 时必须 public_path

2.4 测试

FileUploadServer.Tests/Mcp/12 个测试文件 + 3 个助手,52 个测试全部通过,覆盖文档 §8 全部用例(LIFE/LIST/FL/FI/UP/DL/DEL/PUB/AUTH/RETRY/ERR/E2E)。


三、部署记录

3.1 网关更新(111.229.53.125:7000)

  • 原部署是 7-11 版本(自包含 ./FileUploadServer.Web),Web 项目有一批未提交改动未上线
  • 流程:备份配置 → 停旧进程 → 上传 → 恢复配置 → 启动 → 验证
  • 关键Storage.Mode: WebSocket 保持不变,encryption.key 绝不能覆盖

3.2 WS 存储节点清理(192.168.1.4)

  • 发现 3 个同 client-id 的重复 WsClient 进程(7-11 当天先后启动)→ 全部清理为单实例
  • 发现 WS 认证长期失败:旧 client-secret 的 SHA256 与数据库 ClientSecretHash 不匹配 → 通过 regenerate-secret API 生成新密钥
  • 单实例 + 正确密钥后稳定连接(Connected successfully

3.3 MCP Server 接入

  • MCP Server 走 stdio,运行在 Claude Code 本机,不部署到远程
  • 配置 .mcp.jsonFILE_SERVER_BASE_URL=http://111.229.53.125:7000 + Admin 密钥

四、踩坑汇总(操作踩坑)

4.1 MCP 开发踩坑

现象 解决
静态初始化顺序 ToolDefinitions.All 声明在工具定义前,捕获到 null All 移到 6 个定义之后(C# 静态字段按文本顺序初始化)
静态 JsonObject 共享 重复序列化报 "The node already has a parent" ToJson()InputSchema.DeepClone()
JsonNode.TryGetValue TryGetValue<T> 只存在于 JsonValueJsonNode 调用报错 node is JsonValue value 再调用
原始插值字符串 $$""" 与 JSON 大括号冲突(CS9007) 改用普通字符串 + 转义拼接
.NET 10 multipart Content-Disposition: form-data; name=path 不带引号 断言改为 name=path
文档自相矛盾 RETRY-02 期望 503→-32001,ERR-05 期望 503→-32005 采用语义正确的 -32005
HttpUtility 不可用 HttpUtility.ParseQueryString 属 System.Web,.NET Core 无 URL 字符串拼接

4.2 部署踩坑

现象 解决
pkill -f 误杀 SSH 会话 pkill -f 'FileUploadServer.Web' 匹配到 ssh 命令行自身 → 连接断开(exit 255) pgrep -f '\./FileUploadServer\.Web' 取精确 PID 再 kill
text file busy 运行中的可执行文件无法覆盖(scp 失败) 先停进程再上传
nohup 挂起 ssh nohup 启动的子进程持有 ssh 管道 → 命令超时挂起 启动命令与验证命令分离执行
WS secret 不匹配 认证 token 计算:服务端用 ClientSecretHash,客户端先 SHA256(secret)——必须传数据库哈希值 实际是旧 secret 哈希与库不符,用 regenerate-secret 解决
多进程争抢 3 个 WsClient 同 id 反复 Disconnected: Connection lost 清理为单实例
sshpass 不可用 密码认证无 sshpass SSH_ASKPASS + setsid 方式
沙箱管道丢环境变量 Bash 工具管道中内联环境变量丢失 用输入重定向 < file 代替管道

4.3 公开访问排查踩坑

现象 解决
StartsWithSegments("/p/") 不匹配 PathString.StartsWithSegments("/p/")/p/public/... 返回 False(实测) 定位到 ApiKeyAuthMiddleware:27 的既有 bug,被 PublicFileMiddleware 屏蔽前掩盖
老文件 tag mismatch 新文件公开访问解密成功,老文件(7-11)解密失败 老密文与当前密钥不匹配(数据层问题),改代码无法修复,只能重传
本地解密验证 vs 网关结果矛盾 本地用密钥文件解密失败,网关公开访问新文件却成功 最终确认网关用密钥文件,本地验证方法正确;老文件确实无法解密

五、总结归纳

5.1 架构认知修正

  • 本项目的"前后端分离"是**网关(Web)+ WS 存储节点(WsClient)**的分布式架构,不是传统 Web 前后端
  • 访问路径只有两条:API 请求(FileApiController)和公开访问(/p/,本应只读本地文件)
  • PublicFileMiddleware 的 WS 直接读取(Step 8.5)是未提交的新增改动,违背"访问统一走 API"的分层架构,且对加密文件解密失败——本次已屏蔽

5.2 设计原则沉淀

  1. 分层:中间件不绕过 API 层直接操作存储策略
  2. 不改好的部分:API 下载返回密文(前端解密)是既有设计,未被破坏
  3. 数据 vs 代码问题:老密文无法解密是数据问题,改代码无效,需重传
  4. 部署标准化:部署前 git 提交推送 + 敏感信息检查 + 部署后清理(已写入 skill)

六、后续计划

6.1 公开访问整改(高优先)

  1. 统一封装:公开访问走 FileApiController.Download 的共享封装(或公开文件限定本地磁盘)
  2. 修复 ApiKeyAuthMiddleware:27 bugStartsWithSegments("/p/")StartsWithSegments("/p")Path.StartsWith("/p/")
  3. 重新启用 PublicFileMiddleware(修复后),删除其 Step 8.5 或改为走 API 封装

6.2 老文件恢复

  • p.txt/d.txt/fresh.txt/Markdown入门.md 等老文件密文无法解密 → 如需恢复,由用户提供原文件重新上传

6.3 MCP 增强

  • 实现 resources/read(暴露文件元数据为资源,约 20 行)
  • 可选:编写 OpenAPI → tools/list 的自动生成脚本

6.4 运维

  • 网关配置备份(.bak)确认稳定后清理
  • 定期检查 WS 节点单实例状态(多进程会导致断连)
  • 部署 skill 随实践持续完善

附:本次关键产物

  • FileUploadServer.Mcp/ — MCP Server(手写 JSON-RPC 2.0 over stdio)
  • FileUploadServer.Tests/Mcp/ — 52 个单元测试
  • .claude/skills/deploy-file-upload-server/SKILL.md — 优化后的部署 skill(git 提交推送 + 敏感信息检查 + 部署后清理)
  • .claude/skills/file-upload-server-mcp/SKILL.md — MCP 接入 skill
  • doc/MCP-Development-Guide.mddoc/MCPdoc.md — MCP 设计文档
📥 导出 Markdown