MCP + npm:给五年前的老系统接上AI

前言

那套 Web 系统,当年立项时 PPT 写得天花乱坠,如今打开后台------日活两位数的,一位是测试,一位是你自己刷新页面。

同事嘴上说「早该重构了」,手上却在问 ChatGPT:「帮我把这个文件夹里的报表下下来。」AI 能聊、能写、能画图,就是进不了你们内网那扇登录页的门------不是 AI 不行,是老系统没给它发工牌。

所谓「赋能」,听起来像董事会词汇,落地就一件事:别让 Agent 重新发明一套登录和下载,而是让它走你们现成的 API。 用户还是在浏览器里扫码登录;Token 进 MCP 进程内存;Agent 说「列一下文件」「下这个附件」,和你们页面点按钮,打的是同一批接口。

本文不讲微服务改造、不上 Kubernetes,只讲怎么用 Node + npm + MCP,给「没人用但还不能关」的老系统,挂一条 AI 能走的旁路。老系统负责活着,AI 负责干活------分工明确,各得其所。


你已经有一套 web 系统:用户登录后就能列文件、下载文件。目标不是改业务栈,而是:

  1. npm 把 MCP Server 做成可安装、可发布的包;
  2. 在任意支持 MCP 的 Agent 客户端 里用 npx / node 拉起它;
  3. Agent 带上登录态,调用和页面相同的 文件列表 / 下载 API

MCP 是开放协议,不限于 Cursor 。同一 npm 包可接入 Cursor、Claude Desktop、VS Code(MCP 扩展)、Windsurf 等;下文以 mcp.json 为例,各客户端配置路径不同,stdio 启动命令相同


需要做些什么?

步骤 做什么
1 确认列表/下载 API;配置 LOGIN_URLTOKEN_SOURCE
2 写 MCP tools:login_with_browser / list_files / download_file
3 npm install(含 playwright, 执行 playwright install
4 npm publish --registry=https://admin.npm.xxxxer.me/
5 在 Agent 客户端配 mcp.jsonnpx -y --registry=... files-mcp-client@0.2.0
6 Agent 先 login_with_browser(本机 Chrome)→ Token 进内存 → 再列文件/下载

一、和现有系统怎么对齐

页面能力 MCP Tool 典型 API
登录 login_with_browser Playwright channel: chrome,凭证存内存
文件列表 list_files GET /api/files?path=
下载 download_file GET /api/files/download?id=
退出 logout / clear_auth POST /api/logout(可选)

补充 tools:auth_statusclear_authlogout(退出见 §3.4)。

鉴权走 login_with_browser 写入的进程内存,不必配 API_TOKEN。内网只在本机访问,不做内网穿透或公网暴露。


二、npm 在这条链路里干什么

npm 能力 用途
npm install 装 sdk、playwright、zod
package.json + bin MCP 可执行入口
npm publish 发到 https://admin.npm.xxxxer.me/
npx -y --registry=... 包@版本 Agent 客户端拉起 MCP(stdio)

业务系统继续跑;MCP 是旁路独立包。配置见第四节。

常见 Agent 客户端与配置文件

客户端 配置文件位置(Windows)
Cursor 项目:<仓库>\.cursor\mcp.json;全局:%USERPROFILE%\.cursor\mcp.json
Claude Desktop %APPDATA%\Claude\claude_desktop_config.json
VS Code 用户/工作区 MCP 配置(扩展提供,结构同为 mcpServers
Windsurf 同 Cursor,.windsurf/mcp.json 或设置面板

各客户端 UI 不同,但 MCP Server 段结构一致command + args + env


三、从零做一个 MCP npm 包

3.1 目录

bash 复制代码
your-files-mcp/
  package.json
  src/server.mjs          # stdio 入口
  .gitignore              # 含 .env

可参考:mcp-download-sidecar/

3.2 package.json

json 复制代码
{
  "name": "files-mcp-client",
  "version": "0.2.0",
  "type": "module",
  "bin": { "files-mcp-client": "./src/server.mjs" },
  "files": ["src"],
  "publishConfig": { "registry": "https://admin.npm.xxxxer.me/" },
  "scripts": { "start": "node src/server.mjs" },
  "engines": { "node": ">=18" },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.1",
    "playwright": "^1.52.0",
    "zod": "^3.25.28"
  }
}

playwright 必在 dependencies 里;启动时用 channel: "chrome" 调本机浏览器,不要 playwright install chromium

3.3 登录与浏览器:Token 获取并写入内存(关键代码)

完整实现见 mcp-download-sidecar/download-server.mjs。核心分四步:

① 进程内会话对象(不落盘)

js 复制代码
const session = { token: "", cookie: "", obtainedAt: null };

function getToken() {
  return session.token; // 后续 list_files / download_file 从这里读
}

② 开本机 Chrome,等用户登录

js 复制代码
const { chromium } = await import("playwright");
const { context } = await chromium.launchPersistentContext(userDataDir, {
  headless: false,
  channel: "chrome", // 本机 Chrome,不下载 Chromium
});

const page = context.pages()[0] ?? (await context.newPage());
await page.goto(LOGIN_URL, { waitUntil: "domcontentloaded" });

// 轮询:直到 localStorage / Cookie / 请求头里出现凭证
await waitForLoginCredentials(page, context, { source: TOKEN_SOURCE, timeout });

③ 从页面取出 Token(按 TOKEN_SOURCE 配置)

js 复制代码
// TOKEN_SOURCE 格式:类型:键名
// 例:cookie:ACCESS_TOKEN | localStorage:token | header:Authorization
async function extractTokenFromPage(page, source) {
  const [kind, key] = source.split(":");
  if (kind === "localStorage") {
    return page.evaluate((k) => localStorage.getItem(k) || "", key);
  }
  if (kind === "cookie") {
    const hit = (await page.context().cookies()).find((c) => c.name === key);
    return hit?.value || ""; // key=ACCESS_TOKEN 时取该 Cookie 的值
  }
  return "";
}

示例:登录后 Cookie 名为 ACCESS_TOKEN

bash 复制代码
TOKEN_SOURCE=cookie:ACCESS_TOKEN
json 复制代码
"env": {
  "TOKEN_SOURCE": "cookie:ACCESS_TOKEN"
}

登录成功后代码会:

  1. 从浏览器读到名为 ACCESS_TOKEN 的 Cookie 值 → 写入 session.token
  2. 同时把所有 Cookie 拼成 session.cookie → 后续请求带 Cookie
  3. session.token 有值,还会带 Authorization: Bearer <ACCESS_TOKEN的值>

若你们后端只认 Cookie: ACCESS_TOKEN=xxx、不认 Bearer,可只依赖 session.cookie(实现里会整串带上)。

js 复制代码
// 也可拦截页面发出的 Authorization 头(TOKEN_SOURCE=header:Authorization 时用)
let bearer = "";
context.on("request", (req) => {
  const m = /^Bearer\s+(.+)$/i.exec(req.headers()["authorization"] || "");
  if (m) bearer = m[1];
});

const token = (await extractTokenFromPage(page, TOKEN_SOURCE)) || bearer;
const cookieHeader = (await context.cookies())
  .map((c) => `${c.name}=${c.value}`)
  .join("; ");

④ 写入内存,供后续 API 使用

js 复制代码
session.token = token || "";
session.cookie = cookieHeader || "";
session.obtainedAt = new Date().toISOString();

await context.close(); // 关浏览器;凭证留在 session 里

// 之后 list_files / download_file 自动带鉴权头
function buildHeaders() {
  const headers = { Accept: "application/json, */*" };
  if (session.token) headers.Authorization = `Bearer ${session.token}`;
  if (session.cookie) headers.Cookie = session.cookie;
  return headers;
}

await fetch(`${API_BASE}/api/files`, { headers: buildHeaders() });

要点:

  • 凭证只存在 session 对象里,不写文件、不进 mcp.json
  • MCP 进程重启后 session 清空,需重新 login_with_browser
  • TOKEN_SOURCE 要和你们前端实际存 Token 的方式一致(DevTools → Application 里看 key 名)

常用环境变量:

bash 复制代码
API_BASE=https://your-app.example.com
LOGIN_URL=https://your-app.example.com/login
TOKEN_SOURCE=cookie:ACCESS_TOKEN   # Cookie 名 ACCESS_TOKEN;或 localStorage:token / header:Authorization
PLAYWRIGHT_CHANNEL=chrome
DOWNLOAD_DIR=F:/downloads/app-files
LIST_API_PATH=/api/files
DOWNLOAD_API_PATH=/api/files/download
LOGOUT_API_PATH=/api/logout

3.4 退出登录

退出分三层:

层级 做什么 怎么调
MCP 内存 清空 session,后续 API 不再带凭证 clear_auth
后端会话(推荐) 服务端 ACCESS_TOKEN 失效 logout(配 LOGOUT_API_PATH
浏览器 Cookie(可选) 本机 Chrome 里 Cookie 可能还在 用户在浏览器点「退出」

对 Agent 说「退出登录」 → 调 logout

json 复制代码
"env": { "LOGOUT_API_PATH": "/api/logout" }

logout 流程:先带当前 Cookie/Token 调后端 logout(默认 POST)→ 再清空内存(后端失败也会清内存)。

Tool 调后端 清内存
clear_auth
logout

验证:auth_statusready 应为 false。流程见图下半部分 「二、退出登录」


四、发布与接入 Agent 客户端

4.1 publish

bash 复制代码
npm login --registry=https://admin.npm.xxxxer.me/
npm publish --registry=https://admin.npm.xxxxer.me/

4.2 MCP 配置(通用)

在所用 Agent 的 MCP 配置里加入(Cursor 放 .cursor/mcp.json,Claude Desktop 放 claude_desktop_config.json,其余见上表):

json 复制代码
{
  "mcpServers": {
    "files": {
      "command": "npx",
      "args": [
        "-y",
        "--registry=https://admin.npm.xxxxer.me/",
        "files-mcp-client@0.2.0"
      ],
      "env": {
        "API_BASE": "https://你的系统域名",
        "LOGIN_URL": "https://你的系统域名/login",
        "TOKEN_SOURCE": "cookie:ACCESS_TOKEN",
        "PLAYWRIGHT_CHANNEL": "chrome",
        "DOWNLOAD_DIR": "F:/downloads/app-files"
      }
    }
  }
}

注意:--registry=... 写成一个 args 项;pin 版本;不要API_TOKEN

4.3 开发期(未 publish)

command 改为 nodeargs 指向本地 server.mjsenv 同上。跑通后再切回 npx

4.4 使用

  1. 保存配置并重启 Agent 客户端(或 Reload MCP)→ Server 显示已连接
  2. Agent 调 login_with_browser → 本机 Chrome 登录
  3. list_files / download_file
  4. MCP 进程重启后需重新登录

五、边界与验收

做 / 不做

不做
调已有登录后的 API 开无鉴权后门
Token 仅存进程内存 Token 写进 mcp.json / Git
本机访问内网 内网穿透、公网暴露

验收

  • Node ≥ 18;MCP Server 在客户端显示已连接
  • login_with_browserlist_files / download_file 成功
  • 文件落在 DOWNLOAD_DIR
  • npx -y --registry=... files-mcp-client@0.2.0 可启动
  • 仓库无密钥

六、常见坑

现象 处理
MCP 未连接 / 红灯 npx 不在客户端进程的 PATH;开发期用 node 绝对路径
npx 拉包失败 检查 --registry=https://admin.npm.xxxxer.me/
401 / 下到登录页 HTML Token 过期或未登录;重新 login_with_browser
浏览器起不来 装本机 Chrome/Edge;不要依赖下载 Chromium
相关推荐
南一Nanyi10 小时前
依赖注入和控制反转
前端·设计模式·nestjs
小村儿10 小时前
连载14-实战篇--一个半月,我一个人和 Claude Code 搭出一套数字人工程
前端·后端·ai编程
愚公移码10 小时前
蓝凌EKP18产品:流程虚拟机(PVM)
java·开发语言·前端
牧艺10 小时前
从 Tool Calling 到 MCP Server:把业务能力做成 Agent 可复用接口
agent·全栈·mcp
promiseThen10 小时前
5 分钟上手 Markdown:标题到表格、代码块与简历实战
前端
web66liang10 小时前
webpack4+vue2项目使用 sass-embedded 导致的 DockerfIle 构建失败的问题
前端
Strayer10 小时前
拓扑管网 3D 可视化大屏demo:科技感(地图 + 拓扑 + 3D 空间)
前端·three.js·数据可视化
葬送的代码人生10 小时前
从 Vue 到 React:Tailwind CSS 布局 + BFF 代理实战
前端·react.js·架构
摸鱼老王不带810 小时前
不用后端,纯前端读出"网站眼里你的真实出口 IP"——聊聊 Cloudflare 的 cdn-cgi/trace
前端