Skip to main content
Glama
xrxs-ai

xrxs MCP Server

Official
by xrxs-ai
README.md
# @xrxs-ai/dsh-xrxs-mcp

> English documentation: [README.en.md](README.en.md)

连接 **xrxs MCP 服务器** 的 DeepSeek Harness(Cordis)插件:把服务器的 HR 业务工具(考勤、薪酬、组织、招聘等)注册为模型可调用的原生工具,并支持 **MCP OAuth 2.0 认证**(RFC 8414/9728 发现、RFC 7591 动态客户端注册、授权码 + PKCE S256、refresh token 轮换),也支持静态 Bearer token 或匿名访问。

## 特性

- **页面优先配置**:bundle 默认启用、空闲待命;打开内置 `/xrxs-mcp` 设置页,填服务器 URL,点「保存并连接」即可 —— 无需编辑配置文件。页面保存的配置覆盖补丁配置,且免重启即时生效。
- **OAuth 优先**:首次连接自动发现授权服务器 → 注册公开客户端 → 打开浏览器完成授权(loopback 回调)→ 持久化令牌;后续连接在 refresh token 有效期内静默刷新,不再打扰用户。
- 兼容 xrxs 服务器的两种鉴权部署:内嵌标准 OAuth(`--oauth-issuer-url`)与远程 OAuth Bearer 门卫(`--auth-server-url`,走 `resource_metadata` 标准发现)。
- 可显式固定 issuer(跳过发现)。
- 工具命名 `mcp__<serverName>__<rawName>`,跨重启稳定,权限与会话历史不失效。
- 连接监督:断线按指数退避自动重连;交互式 OAuth 每次故障只提示一次,绝不空转重试。
- 令牌按服务器 URL 各存一个 JSON 文件,POSIX 权限 `0600`,原子写入。
- **Skill 资源包自动安装与在线更新**:默认指向生产资源包
  (`https://cli.xinrenxinshi.com/api/open/mcp/skills/mcp-prod/package.tgz`,可在设置页改),
  插件自动下载、校验并安装到 DSH skill 扫描根 `~/.dsh/skills`(免重启热生效);启动检查 +
  定时轮询 + 页面手动「立即检查更新」,只换装自己名下的 skill,不碰手工安装的;
  页面「清除本地 Skill」可一键卸载本插件安装的全部 skill。

## 使用

以 npm 包形式在 `cordis.yml`(或 profile overlay)中加载插件,一个实例连接一台服务器,多实例对应多服务器/多租户。

> 语义:**启动绝不影响桌面、启动不弹窗**。OAuth 默认走**动态注册**:网关按
> `client_name` 规则放行(如 `cinlynDshDesktop*`),未命中会报
> `unknown registrar`;插件用 `auth.clientName` 做 RFC 7591 注册,回调固定为
> `http://127.0.0.1:<redirectPort>/callback`。`oauth` 模式只在**本地已有 OAuth
> 会话**时自动连接(静默刷新);否则停在 `needs-login`,等你在设置/侧边栏手动
> 连接(`controllerOf(...).login()` 或按钮)时再做注册+授权。

> **推荐:从设置页配置。** 安装 bundle 后打开 `<local-web-url>/xrxs-mcp`,
> 填服务器 URL(高级配置:clientName / redirectPort / 预注册 clientId),点
> 「保存并连接」。下面的 YAML 是文件方式的备选写法;页面保存的配置优先于它。

```yaml
- id: xrxs-mcp
  name: '@xrxs-ai/dsh-xrxs-mcp'
  config:
    serverName: xrxs
    url: https://mcp.example.com/mcp
    auth:
      mode: oauth
      clientName: 'cinlynDshDesktop'        # 必须命中网关注册规则
      redirectPort: 42351                    # 固定回调端口
      scopes: []                            # [] = 服务器默认 scope
      autoConnect: true                     # 已存在会话时自动连接
      # issuerUrl: https://mcp.example.com  # 可选:固定 RFC 8414 issuer
      # openBrowser: false                  # 改为打印 URL 供手动打开
      # tokenFile: /绝对路径.json            # 默认 ~/.xrxs/dsh-xrxs-mcp/<sha256(url)>.json
    failOnStartupError: false              # 绝不让桌面起不来
    skills:
      packageUrl: ''                        # skill 资源包 tgz 地址;空 = 不启用
      autoUpdate: true                      # 定时轮询在线更新
      intervalMs: 3600000                   # 轮询间隔(5分钟~24小时)
      # installDir: /绝对路径                # 默认 ~/.dsh/skills(DSH 用户级扫描根)
```

连接流程(由用户触发,绝不在启动时):
`GET /.well-known/oauth-authorization-server` → 发现 `registration_endpoint` →
`POST <registration_endpoint>`(含 `client_name`/`grant_types`/`redirect_uris`)
→ 保存返回的 `client_id` → `/authorize`(PKCE S256)→ loopback 回调 → 换令牌 →
持久化到 `tokenFile`(`0600`)。后续启动用已存会话静默刷新。

如果要用**预注册客户端**(不再联系注册端点):填 `auth.clientId` 并把
`auth.allowDynamicRegistration` 设为 `false`(见下方预注册小节)。

静态令牌:

```yaml
    auth:
      mode: static
      accessToken: !!js process.env.XRXS_ACCESS_TOKEN
```

匿名(服务器未开启 HTTP 鉴权):

```yaml
    auth:
      mode: none
```

其余字段:`toolCallTimeoutMs`(默认 60000)、`failOnStartupError`(默认 false)、`reconnect.*`(自动重连策略,默认开启,指数退避,单次故障最多 10 次)。

### 预注册客户端(不联系注册端点)

当 xrxs 直接给你一个固定的 `client_id`(线下开通),而不是按 `client_name`
规则走动态注册时,可让插件直接使用它、完全不访问注册端点:

```yaml
    auth:
      mode: oauth
      clientId: <xrxs 分配的 client-id>          # 设置后跳过动态注册
      # clientSecret: <secret>                    # 服务器签发了 secret 才填
      # tokenEndpointAuthMethod: client_secret_post   # 仅当服务器要求
      redirectPort: 42351                         # 必须与登记的回调端口一致
```

设置 `clientId` 后流程不再请求 `/register`,直接带预注册 id 走 `/authorize`。
loopback 回调 URI 固定为 `http://127.0.0.1:42351/callback`,请把这个 URI 完整
登记给 xrxs。若服务器签发了 client secret,再加 `clientSecret`(token 端点
需要密钥鉴权时再配 `tokenEndpointAuthMethod`;xrxs 网关声明的是 `none`)。

## 开发与验证

```sh
pnpm install
pnpm run typecheck
pnpm run test        # 单元测试(loopback 测试需可绑定 127.0.0.1)
pnpm run build
```

对真实 xrxs MCP 服务器(内嵌 OAuth 模式)的端到端冒烟:

```sh
XRXS_MCP_SERVER_BIN=/path/to/cinlyn-mcp-server/.venv/bin/xrxs-mcp \
XRXS_SCHEMA=/path/to/cinlyn-mcp-server/examples/attendance.schema.json \
pnpm run smoke:oauth
```

## 目录

```
src/index.ts        插件入口(name/inject/Config/apply)
src/config.ts       Schemastery 配置与解析
src/transport.ts    按鉴权模式构建 Streamable HTTP 传输
src/connection.ts   连接监督 + OAuth 交互门
src/tools.ts        工具发现/注册/调用桥
src/names.ts        公开工具名推导
src/oauth/          OAuth provider、loopback 服务器、浏览器打开、令牌存储
tests/              node:test 单元测试
scripts/smoke-oauth.ts  对真实 xrxs-mcp 的端到端冒烟
```

## Skill 资源包自动安装与更新

本插件可以把一套 skill 资源包自动下载、安装到 DSH 的 skill 扫描根,让桌面
**免重启热加载**配套 skill(DSH 的 skill 发现机制:放进扫描根即生效)。

### 资源包格式

一个 `tar czf` 压缩包(`.tgz`),**顶层就是若干 skill 目录**:

```
skills.tgz
├── hr-leave/
│   ├── SKILL.md          # frontmatter: name(kebab-case,须与目录名一致) + description(必填)
│   └── references/…      # 任意资源子目录
└── hr-office/
    └── SKILL.md
```

打包示例(在 skill 源目录,把各 skill 目录平铺后):

```sh
tar czf skills.tgz hr-leave/ hr-office/ …
```

### 配置与更新

- 配置入口同连接配置:设置页「Skill 管理」区(资源包地址 + 自动更新开关,
  修改后点该区的「保存 Skill 设置」,与连接配置的「保存并连接」互相独立,
  保存不重启连接),或补丁配置 `skills.packageUrl` / `skills.autoUpdate` /
  `skills.intervalMs`。地址预填生产资源包;地址为空 = 不启用,不做任何下载。
- 「清除本地 Skill」按钮:只删除本插件安装的 skill(登记在状态文件里)并重置
  安装记录;手工放进扫描根的目录不受影响。删除失败的条目会保留,下次可重试。
- 触发:启动后 15 秒静默检查一次 + 定时轮询(默认 1 小时)+ 页面「立即检查更新」。
  用 SHA-256 / ETag 比对,内容没变不换装。
- 安装位置:默认 `~/.dsh/skills/`(DSH 用户级扫描根);`skills.installDir` 可覆盖。

### 安全语义

- 包内**任何一个** skill 不合法(缺 SKILL.md、name 非法或与目录名不一致、缺
  description)→ 整包拒绝,保留已安装版本。
- 只换装**插件自己安装过**的 skill(登记在
  `~/.xrxs/dsh-xrxs-mcp/skills-<serverName>.json`,0600);你手工放进扫描根的
  同名 skill 视为冲突,报告但不覆盖。
- 包里消失的、自己名下的 skill 会被移除;换装中途失败自动回滚。
- 解包器拒收软链/硬链/路径穿越,并有解包总大小与条目数上限;下载有 64MB 上限,
  URL 仅接受 http/https。

## 安全

- 令牌与注册客户端状态:目录 `0700`、文件 `0600`,请放在私有文件系统。
- loopback 仅绑定 `127.0.0.1` 并校验 OAuth `state`。
- 日志不输出令牌(一律打码)。
- 生产环境请通过 HTTPS 访问 MCP 端点。

## 在 DSH Desktop 中测试

DSH Desktop 按 profile 管理插件。本包以 **bundle** 形态发布(`dsh.bundle.patch`
→ `cordis.patch.yml`):安装为 profile 依赖后,桌面把它并入 profile 的 bundle
层,启动时由补丁插入 `xrxs-mcp` 插件行。

profile 目录在 DSH home(`$DSH_HOME`,默认 `~/.dsh`)的 `profiles/<name>` 下;
桌面 profile 名为 `desktop`,其用户补丁文件是 `cordis.patch.yml`。DSH 终端的
欢迎信息会打印实际 profile 目录。

> **重要:“看到插件”指什么。** 这是纯 Host 工具插件,可见内容只有内置设置页
> (`/xrxs-mcp`,见下文)和模型可调用的工具集(`mcp__xrxs__<tool>`),而且工具
> 只有当 ①**配置了服务器 URL**(设置页或补丁配置)、②桌面能**连通 xrxs MCP
> 服务器**、③**完成 OAuth 登录**后才出现。在此之前,“已安装插件”列表和设置页
> 是它唯一出现的地方。

### 1) 构建并打包

```sh
cd /Users/alva/Code/Work/xrxs-ai/dsh-xrxs-mcp
pnpm install && pnpm run typecheck && pnpm run test
pnpm pack    # prepack 自动构建 lib/,产出 xrxs-ai-dsh-xrxs-mcp-<version>.tgz
```

### 2) 安装到桌面 profile

在 DSH 终端(托盘 → Open DSH Terminal)中:

```sh
dsh plugin --profile desktop add /Users/alva/Code/Work/xrxs-ai/dsh-xrxs-mcp/xrxs-ai-dsh-xrxs-mcp-0.1.0-dev.4.tgz
```

`add` 会把 tarball 装入 `~/.dsh/profiles/desktop` 并自动并入
`dsh.profile.bundles`。运行中安装会提示“重启后生效”(带 config 的 bundle 行
不支持热挂载),**完整重启**后应用。卸载:
`dsh plugin --profile desktop remove @xrxs-ai/dsh-xrxs-mcp`。

### 3) 指向服务器(设置页)

bundle 插入的行默认 `disabled: false` 并**预填生产端点**
(`https://mcp-xrxs.xinrenxinshi.com/mcp`):`dsh plugin add` 并完整重启后,
插件即为已配置状态——本地已有 OAuth 会话则静默连接,否则停在 `needs-login`,
等你在设置页完成一次授权。

打开 `http://127.0.0.1:<web-port>/xrxs-mcp`(端口见桌面设置/DSH 欢迎信息)。
表单分两部分:

- **简单配置**:服务器 URL —— 预填 `https://mcp-xrxs.xinrenxinshi.com/mcp`(生产端点);
  换成你的 xrxs MCP 端点即可。
- **高级配置**(折叠):`clientName`(预填 `cinlynDshDesktop`,须命中网关注册
  规则)、`redirectPort`(预填 `42351`,loopback 回调端口),以及可选的预注册
  `clientId` / `clientSecret`(填了则跳过动态注册)。

点「**保存并连接**」:页面校验输入,持久化到插件自己的设置文件
(`~/.xrxs/dsh-xrxs-mcp/settings-<serverName>.json`,权限 `0600`),进程内
热生效(免重启),并发起 OAuth 授权流程 —— 在浏览器里点允许。「**恢复默认**」
删除该设置文件,回到补丁配置。页面保存的配置优先于下面的补丁配置;设置页只
配置 OAuth 模式(静态令牌/匿名部署仍需用配置文件)。

#### 文件方式(可选)

如果你更习惯配置文件,编辑 `~/.dsh/profiles/desktop/cordis.patch.yml`
—— profile 补丁层在 bundle 层之后应用,用相同行 `id` 覆盖即可。把文件里的
`[]` 换成完整覆盖(或追加):

```yaml
- id: xrxs-mcp
  disabled: false
  config:
    serverName: xrxs
    url: https://mcp.example.com/mcp     # 本地测试可写 http://127.0.0.1:8000/mcp
    failOnStartupError: false
    auth:
      mode: oauth
      clientName: 'cinlynDshDesktop'        # 命中网关注册规则(client_name 放行)
      redirectPort: 42351                    # 固定回调端口(登记 URI 一致)
      # tokenFile: /绝对路径.json          # 默认 ~/.xrxs/dsh-xrxs-mcp/<hash>.json
```

该 profile 是 `patchReload: live`,通常无需重启即生效;没反应就重启一次。
**可选:环境变量门控(进阶)** —— 如果你习惯从 shell 启动桌面,可以在*自己*
的补丁里写表达式(必须加引号,`!` 开头不加引号会被当成 YAML tag):

```yaml
- id: xrxs-mcp
  disabled: !!js "!process.env.XRXS_MCP_URL"
  config:
    serverName: xrxs
    url: !!js "process.env.XRXS_MCP_URL ?? 'http://127.0.0.1:8000/mcp'"
    failOnStartupError: false
    auth:
      mode: oauth
      clientName: 'cinlynDshDesktop'        # 命中网关注册规则(client_name 放行)
      redirectPort: 42351                    # 固定回调端口(登记 URI 一致)
```

### 4) 重启并验证

1. 完整重启 DSH Desktop(托盘 Quit 后再打开,不是关窗口)。
2. 本地起一个 xrxs MCP 服务器(内嵌 OAuth 模式)快速测试:

   ```sh
   cd /Users/alva/Code/Work/xrxs-ai/cinlyn-mcp-server
   .venv/bin/xrxs-mcp --host 127.0.0.1 --port 8000 --mcp-path /mcp \
     --base-url http://127.0.0.1:9 \
     --oauth-issuer-url http://127.0.0.1:8000 \
     --oauth-state-file /tmp/xrxs-mcp-oauth.json \
     --schemas examples/attendance.schema.json
   ```

3. 在设置页完成配置(见上一节),浏览器弹出 OAuth 授权页时点允许(回调用
   loopback `http://127.0.0.1:<redirectPort>/callback`)。之后在对话里让模型
   调用 xrxs 工具(如“查询考勤周期模板”),登录后工具以 `mcp__xrxs__<tool>`
   出现(如 `mcp__xrxs__attendance_getCycleTemplateList`)。
4. 没反应就看应用日志里的 `dsh-xrxs-mcp(...)` 行:能区分是未配置、正在连接、
   等待授权,还是连不上服务器。


### 内置设置页(同源 `/xrxs-mcp`)

插件会在本地 Web 根路径挂一个同源设置页:
`http://127.0.0.1:<web-port>/xrxs-mcp`(端口见桌面设置/DSH 欢迎信息)。它是
主要的配置入口:服务器 URL 表单(高级配置:clientName / redirectPort / 预注册
clientId);「**保存并连接**」按钮负责校验、持久化
(`~/.xrxs/dsh-xrxs-mcp/settings-<serverName>.json`,`0600`)、免重启热生效并
发起 OAuth 流程;「**恢复默认**」删除页面配置、回到补丁配置。页面还显示实时
连接状态(`unconfigured` / `needs-login` / `connecting` / `connected` /
`error`),并在无法自动打开浏览器时给出可手动打开的授权 URL。已存在 OAuth
会话时启动即自动连接(页面显示“已连接”)。页面不暴露令牌(密钥字段打码);
没有 web 服务时插件仅记日志、照常运行。


连接器的入口位于**左侧边栏页脚**(设置按钮同区,通过 DSH 插槽系统注入):
一个状态按钮——侧栏展开时显示名称 + 实时状态圆点,收起时仅显示圆形图标——
点击弹出浮动面板,以同源 iframe 直接内嵌设置页:状态查看、配置填写、
「保存并连接」全部在桌面内完成,无需打开任何外部浏览器标签。
在不提供插槽系统的宿主上,若已安装 **dsh-better-sidebar**,则回退为
侧边栏 `+` 菜单中的一个 **xrxs MCP** 标签页。

### 常见问题

- **完全没有工具、日志也没有 `dsh-xrxs-mcp` 行** → 插件行没加载:确认 bundle
  装在 *desktop profile*,并重启。
- **设置页显示 `unconfigured`** → 还没填服务器 URL:在设置页填 URL 并点
  「保存并连接」(或在 profile 补丁覆盖里设 `url`)。
- **日志提示授权超时/未完成** → 在超时前点允许;loopback 只监听
  `127.0.0.1`,服务器 OAuth issuer 需为 HTTPS(或 localhost)。
- **日志提示连接失败** → 服务器不可达或 MCP URL 写错。
- **授权页要审批密码** → 服务器配置了 `--oauth-consent-secret`,在页面输入。
- **静态令牌部署** → 在 profile 覆盖里写 `auth: { mode: static, accessToken:
  <token> }`。

## 发布流程(GitHub Release)

本插件由 DSH Desktop(dsh-plugin-desktop-cinlyn)从本仓库的 GitHub Release
运行时安装并静默自动升级(每 6 小时检查一次 `/releases/latest`)。发布走
`.github/workflows/release.yml`,由 `v*` tag 触发,硬性约定如下:

1. **tag 版本必须与 `package.json` 的 `version` 完全一致**(工作流会校验,
   不一致直接失败)。消费端安装后会读取包内 `package.json` 验证版本号。
2. Release 资产必须恰好是 `pnpm pack` 产出的
   **`xrxs-ai-dsh-xrxs-mcp-<version>.tgz`**(消费端按该名字选取资产)。
3. 正式升级必须发布为**稳定 Release**(`releases/latest` 只返回最新稳定版,
   预发布 tag 如 `v0.3.0-beta.1` 会被工作流自动标记为 prerelease,不影响
   latest 指向)。
4. 仓库(至少 Release 资产)必须可匿名访问——Desktop 端不带凭证拉取
   GitHub API 与 `browser_download_url`。

发布步骤:

```sh
# 1. 升版本
pnpm version patch   # 或 minor / major,自动打 v* tag

# 2. 推送分支与 tag,触发工作流
git push github <分支> --tags

# 3. 确认 Actions 全绿,Release 页出现对应 tgz 资产
```

本地联调/staging 可用环境变量把 Desktop 端指向测试仓库:
`DSH_XRXS_MCP_RELEASES_URL=https://api.github.com/repos/<owner>/<repo>/releases/latest`。

## License

MIT — 见 [LICENSE](LICENSE)。