mcpField
F
fmsg

fmsg

远程服务
github.com通信协作46 天前更新

关于 fmsg

fmsg-mcp 通过托管方的 Web API 将 AI Agent 连接到 fmsg 消息地址,支持托管或自托管部署。它支持本地 stdio 和共享的 Streamable HTTP 部署,并可按用户使用 API 密钥或 OAuth 身份验证,提供消息、会话、回复和附件工具。

主要功能

  1. 01发送和接收消息
  2. 02读取并跟进消息会话
  3. 03创建草稿、回复和管理收件人
  4. 04添加回应并标记已读
  5. 05交换和保存附件
  6. 06使用 API 密钥或 OAuth 身份验证

适用场景

  1. 01将 Agent 连接到 fmsg 地址
  2. 02等待并回复收到的消息
  3. 03发送文件并管理消息附件

MCP 相关

该 MCP 服务将 fmsg 消息操作提供为工具,并提供消息和会话资源,以及 chat、reply 和 send_file 提示词。客户端可通过 stdio 或 Streamable HTTP 连接,并使用 API 密钥或配置的 OAuth 进行身份验证。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://{oauth_mcp_endpoint}
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

全部服务地址
Streamable HTTPhttps://{oauth_mcp_endpoint}
Streamable HTTPhttps://{api_key_mcp_endpoint}需要授权
安装包
npm@markmnl/fmsg-mcp1.2.3

工具

详细介绍

fmsg-mcp

Tests npm License: MIT

一个 MCP 服务,为任何 AI Agent 提供专属的 fmsg 地址:通过已部署的 fmsg Web API 发送消息、跟进线程、添加回应、交换 附件并等待回复。可通过 stdio 连接到 Claude Code、Claude Desktop、Cursor 和 VS Code 等宿主, 也可通过带 API 密钥标头或已配置 OAuth 的 HTTP 连接。

你可以选择任何 fmsg 托管服务提供商,也可以自行托管。此项目未内置托管服务提供商或身份提供商。

  • stdio,适用于本地宿主:每个服务进程使用一个地址,通过两个环境变量进行配置。
  • Streamable HTTP,适用于共享或远程部署:一个端点为多个用户提供服务,每个用户通过自己的 fmsg API 密钥或 OAuth 连接进行身份验证。
  • fmsg Web API 客户端已导出,可供复用:import { FmsgClient } from "@markmnl/fmsg-mcp/client"。

1. 选择连接方式

OAuth:连接并登录

从 fmsg 托管服务提供商获取启用了 OAuth 的 MCP URL,将其添加到 AI 宿主并登录。 使用此连接无需 API 密钥或本地安装。你的 AI 宿主必须支持授权服务器的客户端注册方法;请参阅 OAuth 入门指南。 运营者可通过 HTTP OAuth 启用此功能。

API 密钥:本地使用或通过 HTTP 使用

你将以 fmsg 地址的身份发送消息,并通过 fmsg 主机签发的 API 密钥(fmsgk_…)进行身份验证:

  • 使用托管服务提供商? 在 fmsg 托管服务提供商处创建帐户,并获取 Agent 将使用的地址对应的 API URL 和 API 密钥。
  • 自行托管? 使用 fmsg-docker 运行整个服务栈,并通过 fmsg-webapi api-key create 签发密钥。

使用下文本地安装所需的 API URL 和密钥,或使用提供商的 API 密钥 MCP 端点,并添加 Authorization: Bearer fmsgk_... 标头。该端点必须使用接受你密钥的 fmsg Web API。

Agent 的密钥通常对应一个子地址,例如 @[email protected],其所有者为 @[email protected]。所有者可在客户端切换到该子地址,但只能跟进自己参与的线程。 因此,除非 include_owner 为 false,否则 send_message、reply 和 create_draft 会将 所有者添加为收件人;whoami 会报告所有者。

MCP Registry

在官方 MCP Registry 中查找 io.github.markmnl/fmsg-mcp。该条目提供本地 npm 安装,以及可配置的 OAuth 或 API 密钥远程连接。 对于远程连接,请提供 fmsg 托管服务提供商或你自己的部署所提供的 MCP 端点;Registry 输入需填写不含 https:// 前缀的主机名和路径,例如 mcp.example.com/mcp。 OAuth 登录信息会从该端点自动发现;API 密钥选项还会要求你提供密钥。 如果 AI 宿主无法配置 Registry URL 模板,请直接添加完整的 MCP URL。

2. 安装

需要 Node.js 22 或更高版本。

Claude Code
claude mcp add fmsg --scope user \
  --env FMSG_API_URL=https://api.example.com \
  --env FMSG_API_KEY=fmsgk_... \
  -- npx -y @markmnl/fmsg-mcp

之后,在任意会话中输入:"给 @[email protected] 发送一条关于发布的便笺"、"我的 fmsg 收件箱里有什么?"、 "等待 Bob 回复并回答他"。/fmsg:chat、/fmsg:reply 和 /fmsg:send_file 可作为提示词使用。

Claude Desktop、Cursor、VS Code 和其他 stdio 宿主

添加一个服务条目,并使用相同的命令;只有配置文件不同:

{
  "mcpServers": {
    "fmsg": {
      "command": "npx",
      "args": ["-y", "@markmnl/fmsg-mcp"],
      "env": { "FMSG_API_URL": "https://api.example.com", "FMSG_API_KEY": "fmsgk_..." }
    }
  }
}

(Claude Desktop:claude_desktop_config.json;Cursor:.cursor/mcp.json;VS Code:"servers" 下的 .vscode/mcp.json, 并设置 "type": "stdio"。)

远程(Streamable HTTP)模式

运行一个服务,供多个用户使用。每个客户端都将自己的 fmsg API 密钥作为 bearer 令牌发送;服务会在 fmsg 主机上使用该密钥进行交换,并以该地址的身份运行。不得设置 FMSG_API_KEY。

FMSG_API_URL=https://api.example.com FMSG_MCP_ALLOWED_HOSTS=mcp.example.com \
  npx -y @markmnl/fmsg-mcp --http 0.0.0.0:8765
# or
docker build -t fmsg-mcp --build-arg FMSG_MCP_COMMIT=$(git rev-parse HEAD) \
  --build-arg FMSG_MCP_VERSION=$(git describe --tags --abbrev=0) .
docker run -e FMSG_API_URL=https://api.example.com \
  -e FMSG_MCP_ALLOWED_HOSTS=mcp.example.com -p 8765:8765 fmsg-mcp

MCP 端点为 /mcp;/healthz 会报告运行状态、版本和提交记录。whoami 会报告相同信息,因此客户端可以确认当前连接的是哪个部署:npm run build 会从 git 记录这些信息;如果构建时没有 git 历史记录,上方的 Docker 构建参数也会记录这些信息(也可以在运行时设置 FMSG_MCP_COMMIT)。请使用支持显式配置 Authorization: Bearer fmsgk_... 标头的客户端。每个调用方都需提供自己的密钥;共享标头意味着共享 fmsg 身份。若要通过浏览器登录且不使用用户 API 密钥,请配置 HTTP OAuth:运营方需提供颁发方、资源 URI 和交换客户端。用户可在支持该颁发方客户端注册方式的主机中添加公开 MCP URL。FMSG_MCP_AUTH_MODE=oauth+api-key 可在同一 URL 上同时接受 OAuth 和 API 密钥调用方。

通过 Claude Code over HTTP:

claude mcp add --transport http fmsg --scope user https://mcp.example.com/mcp \
  --header "Authorization: Bearer fmsgk_..."

请在 TLS 终止反向代理后部署,并在绑定到非环回地址时将 FMSG_MCP_ALLOWED_HOSTS 设为公开主机名;否则启动会失败。来自其他来源的浏览器客户端还需要在 FMSG_MCP_ALLOWED_ORIGINS 中配置精确来源,例如 https://app.example.com。绑定到环回地址时,默认允许任意端口上的环回浏览器来源,包括位于 http://localhost:6274 的 MCP Inspector。设置显式来源列表后,将替代默认的环回来源设置。允许的预检请求无需凭据;实际 MCP 请求始终需要身份验证。wait_for_message 会将请求保持打开状态,最长可达 FMSG_MCP_WAIT_MAX_SECONDS(230 秒),因此代理的空闲超时时间应至少设为 240 秒。收到 SIGTERM 时,服务会以 interrupted 结果结束尚未完成的等待,客户端可据此恢复;其他请求有 5 秒完成时间,之后服务退出,远短于服务管理器的停止超时时间。有关使用 Caddy 部署到环回地址的示例,请参阅 TLS 反向代理示例。

工具

工具功能说明
whoami此服务代表的地址;如果是子地址,则还包括其 owner;已知时提供公共 API URL;传输方式(stdio 或 http,即 Streamable HTTP);短名称默认值;以及此服务的版本和提交记录
resolve_address将短名称转换为 @user@domain(先查目录,再查默认域名)
list_messages收件箱,按时间从新到旧排列,含预览、附件类型和 size(compressed 表示正文已压缩发送);隐藏表情回应,除非设置 include_reactions,否则绝不将其计为未读
list_sent已发送消息,含每位收件人的投递状态
get_message单条消息,含标头、完整正文、附件、表情回应;如果是回复,还包括根消息的 thread_topic
get_thread从线程根消息到某条消息的脉络(scope: "lineage";不包括该线程中的其他回复),含无法查看的消息所对应的空缺、每条消息的标记和新增收件人、根消息的 thread_topic,以及不会建议回复终止消息或禁止回复消息的 next 步骤
send_message开始新线程;立即发送(fmsg 消息不可修改)
reply在某个线程中回复;默认回复所有人,拒绝回复终止消息和禁止回复的父消息
create_draft创建未发送的消息或回复(reply_to),其他 fmsg 客户端可以查看;通过带公共 MCP URL 的 HTTP 使用时,会为大型附件返回经过身份验证的上传 URL
send_draft发送草稿及其附件
delete_draft丢弃未发送的草稿
add_recipients为已发送的消息添加 recipients(add_to 是已弃用的别名)
react设置或清除你的表情回应
mark_read将收到的消息标记为已读
download_attachment内联获取小型附件:文本以文本形式返回,图像以图像块形式返回,其他文件以 base64 资源形式返回;如果文件名不正确,则会列出该消息拥有的文件名
get_attachment_download_url获取无需凭据的 URL 和资源链接,用于下载经过身份验证的二进制文件;设置 presigned: true 时,还会返回一个有效期为 5 分钟、只能使用一次且无需 Authorization 标头的链接,并附上文件大小、sha256 和 curl 命令。仅限 HTTP,且需要公共 MCP URL
get_attachment_upload_url获取一个有效期为 5 分钟、只能使用一次的链接,该链接可将指定大小和 sha256 的单个文件 PUT 到草稿中,且无需 Authorization 标头;附带 curl 命令;然后调用 send_draft。仅限 HTTP,且需要公共 MCP URL
save_attachment将附件流式传输到配置的本地文件夹;仅限 stdio,通过 FMSG_MCP_DOWNLOAD_DIR 启用
delivery_status每位收件人的投递时间和 fmsg 响应代码(200 表示已接受;未记录时为 null);对于收到的消息,则显示主机自身的收件记录;via 的值为 to 或 add_to(后续添加)
wait_for_message阻塞等待下一条收到的消息(WebSocket 推送),按线程批量返回,附带线程上下文、跳过的表情回应,以及会指出光标越过的其他线程消息(pending_ids)的 next 步骤

每个工具都会返回可读 Markdown 和 structuredContent。ID 是十进制字符串。消息正文会标明为来自其他方的数据,而非指令;包含其他方文字的结构化结果也会在 untrusted_content_notice 中重复这一说明,因为有些主机会显示结构化结果,而不是文本。附件类型由记录该附件的主机提供;如果主机未记录,则根据文件扩展名确定,因此每个工具报告的类型相同;每个工具也会按相同顺序(按文件名)列出附件。文本中的地址使用代码片段格式,以确保复制后内容完全一致(@[email protected],绝不添加 Markdown 转义);地址中不允许出现反斜杠。消息的 size 是主机存储的正文大小:对于 compressed 为 true 的收到消息,该值是压缩后的长度;而你自己发送的消息则保留未压缩长度。

输出架构兼容性

一些 MCP 主机会在添加连接器时缓存每个工具的 outputSchema,并根据该副本验证之后的每个结果。因此,输出架构采用开放且可扩展的设计:对象接受未知属性;可能增加取值的字段(状态、传输方式、分辨率、投递状态)均为字符串,当前取值列在其描述中;只有自 0.2.5 起就存在的字段才是必填字段。各版本会添加字段和取值,不会删除或重命名它们。0.2.6 及更早版本发布的架构是封闭的,因此缓存了旧架构的主机会拒绝包含新字段的结果(例如“不得包含其他属性”);移除并重新添加一次连接器即可刷新架构。

资源 fmsg://message/{id} 和 fmsg://thread/{id} 会向附加资源的主机公开相同内容;提示词 chat 和 reply 用于编排等待 → 回复循环和引导式回复,而 send_file 会通过服务端提供的最高效途径发送文件。

配置

变量默认值用途
FMSG_API_URL—fmsg Web API 的基础 URL(必填)
FMSG_API_KEY—fmsgk_… 密钥;仅限 stdio 模式
FMSG_API_PUBLIC_URLFMSG_API_URL(HTTP 模式:仅限 HTTPS)whoami 向用户报告的 Web API URL;绝不用于请求。若服务端通过内部地址访问 Web API,请设置此项。若 FMSG_API_URL 使用明文 HTTP,且未设置此项,HTTP 模式不会报告 URL。验证方式与 FMSG_API_URL 相同
FMSG_MCP_AUTH_MODEapi-keyHTTP 身份验证:api-key、oauth 或 oauth+api-key(在同一端点接受两种方式);参见 OAuth 设置 和组合模式
FMSG_MCP_PUBLIC_URLOAuth 资源 URL,否则未设置公共 MCP 端点,包含 /mcp;启用 HTTP 下载链接工具和草稿上传 URL。除回环地址外,必须使用 HTTPS;启用 OAuth 时,必须与 FMSG_MCP_OAUTH_RESOURCE_URL 相同
FMSG_ALLOW_INSECURE_HTTP已禁用仅设为 1,才允许在受信任的开发/专用网络上通过明文 HTTP 访问 API;默认允许回环 HTTP
FMSG_DEFAULT_DOMAIN—允许解析简短名称:bob → @bob@<domain>。caller 会将其解析到服务端在每个请求中代表的地址所属域名,因此 @[email protected] 会解析出 @[email protected];目录条目仍优先
FMSG_DIRECTORY—将简短名称映射到完整地址的 JSON 文件
FMSG_MCP_DOWNLOAD_DIR—在 stdio 模式下启用 save_attachment;新文件的保存文件夹,文件名由消息 ID 和文件名组成
FMSG_MCP_UPLOAD_DIR—在 stdio 模式下允许附件使用 path 参数:服务端会读取此文件夹(或其子文件夹)中的文件,而不是由模型写出文件
FMSG_MCP_WAIT_MAX_SECONDS230单次 wait_for_message 调用的最长等待时间
FMSG_MCP_STATE_SECRET每个进程随机生成至少 32 字节;用于预签名链接的 HMAC 密钥。链接仍只能在签发它们的实例上使用
FMSG_MCP_HOST / FMSG_MCP_PORT127.0.0.1 / 8765HTTP 绑定地址(或 --http host:port)
FMSG_MCP_ALLOWED_HOSTS回环地址名称以逗号分隔的 Host 请求头允许列表;非回环绑定时必填
FMSG_MCP_ALLOWED_ORIGINS同源;回环绑定时允许回环来源以逗号分隔的浏览器来源,包含协议和端口;显式列表会取代默认的回环来源;不接受仅含主机名的值
FMSG_MCP_KEY_CACHE_MAX / FMSG_MCP_KEY_CACHE_TTL_SECONDS500 / 1800HTTP 客户端缓存上限;TTL 仅适用于 API 密钥调用方

API 密钥会被交换为短期访问令牌,服务端会自动续期。 API URL 不得包含凭据、查询字符串或片段。经过身份验证的请求不会跟随重定向;请直接配置最终 API URL。 若要将附件直接保存到磁盘,请在 stdio 服务的环境中添加 FMSG_MCP_DOWNLOAD_DIR, 例如 /home/you/Downloads/fmsg。可选的 save_attachment 工具会将文件流式传输到该 文件夹,而不会将文件字节发送到模型上下文中。它仅接受消息 ID 和附件文件名,并创建 新文件,例如 123-report.pdf。重复保存时会使用 123-report-1.pdf、 123-report-2.pdf 等名称,不会修改现有文件。异常文件名会转换为便于跨平台使用的名称; 请使用返回的 saved_to 路径。只要传输仍在进行,流式下载就可以超过 60 秒;60 秒的 空闲超时用于检测传输停滞。

内联下载默认上限为 256 KiB,以便控制模型处理的文件内容大小。对于较大的本地文件(stdio), 请使用 save_attachment;对于远程文件(HTTP),请使用 get_attachment_download_url; 如果 AI 主机能够处理更多内联内容,也可以显式提高 max_inline_bytes。工具描述只会列出 正在运行的服务已注册的工具。

对于远程文件下载,请使用消息 ID 和文件名调用 get_attachment_download_url。 该工具会返回元数据和一个 HTTPS resource_link;AI 主机会使用现有 MCP 连接的 Authorization 标头下载原始字节。API 密钥运营者可通过设置 FMSG_MCP_PUBLIC_URL=https://mcp.example.com/mcp 启用该工具;OAuth 部署会自动复用已配置的资源 URL。请参阅二进制附件下载。 链接不包含凭据,且本身不授予任何访问权限。主机必须支持经过身份验证的 HTTP 下载;不带该标头 直接在普通浏览器中点击会返回 401。如果主机不支持此功能,请使用内联下载。下载会以流式方式 进行,不受内联大小限制,并使用原始 Content-Type 和下载文件名。MCP 服务不会存储任何文件。

发送附件

send_message、reply 和 create_draft 中的每个附件必须且只能使用以下一种方式:

  • data_base64:由模型编写的 base64 数据。数据会经过模型,因此只能尽力保证准确:适用于小文件(已验证不超过 16 KB 的文件可精确传输)。务必同时传入 sha256,对于更大的文件,优先使用 path 或预签名上传链接。接受标准或 URL 安全字母表,但同一个值中不能混用。解码采用严格模式:如有任何其他字符、长度或填充错误,或末尾有多余字符,系统会拒绝该附件并指出其名称,而不会发送损坏的文件。
  • data_text:文本,以其 UTF-8 字节发送;没有 base64 编码开销。
  • path(stdio 模式下需配置 FMSG_MCP_UPLOAD_DIR):该文件夹中的文件,由服务流式传输。路径相对于该文件夹;如果路径或符号链接指向该文件夹之外,则会被拒绝。

为任何附件添加 sha256(十六进制),服务会据此校验字节;如果不匹配,会在发送前拒绝该附件。通过 HTTP 发送大于常规大小限制的文件时,请调用 create_draft,使用连接的 Authorization 标头将文件 PUT 到返回的上传 URL(可选 gzip 压缩,并附带 x-content-sha256),然后调用 send_draft;请参阅附件上传。按原样发送文件:fmsg 会在发送时自行压缩消息正文和附件。

即使主机无法在自行发起的 HTTP 请求中附加连接凭据,也仍然可以避免将文件字节传入模型。get_attachment_upload_url 会为草稿中的每个文件返回一个一次性、有效期为 5 分钟的链接,该链接绑定文件大小和 sha256,并附带可直接运行的 curl 命令。下载时,get_attachment_download_url 配合 presigned: true 也会执行相同操作。链接本身授权进行一次传输。链接不包含 API 密钥或访问令牌,但在使用或过期前应将其视为机密。它仅适用于签发该链接的服务实例。

模型会根据服务说明和工具描述了解可用路由,这些内容只会列出正在运行的服务所提供的路由。如果存在更合适的路由,模型仍以 base64 编写大文件,结果中会返回一条指出该路由的 hint。send_file 提示词会引导模型使用最佳路由。上传 URL 要求主机能够在自行发起的 HTTP 请求中发送 MCP 连接的凭据:例如脚本、SDK 客户端,以及持有 fmsg API 密钥的编码 Agent。聊天主机通常无法做到这一点,因此对它们而言,data_text 和 data_base64 仍是可用路由。

Web API 已经以原始字节传输附件。只有在 MCP 的 JSON 结果中嵌入二进制内容时,才会使用 Base64,这是 MCP 二进制资源的要求。save_attachment 和经过身份验证的 HTTP 下载可避免这种编码,并使文件字节不进入模型上下文。请参阅 issue #6。

通过 stdio 运行时,即使配置缺失或无效,主机仍可发现这些工具。工具调用会说明配置错误及修复方法;更正设置后,请重启 MCP 服务。

安全

  • 消息访问、配额和收件人接受规则由 fmsg-webapi 和主机服务强制执行。MCP 会以调用者的身份转发每项操作,并传递上游错误。
  • download_attachment 不会写入本地文件。可选的 save_attachment 仅会在操作员配置的文件夹中写入自动生成的文件名,并使用独占创建方式,不会覆盖文件。
  • 已发送的消息无法编辑或撤回;发送工具会在说明中注明这一点,并添加 destructiveHint 注释来描述其影响。是否需要批准由 AI 主机决定;fmsg-mcp 不设额外的确认环节。
  • 发出的正文、主题和错误文本中会对选定格式的 API 密钥/令牌进行脱敏;发送工具会报告脱敏数量。包括 fmsg API 密钥、JWT、私钥块、源代码托管令牌(ghp_…、github_pat_…、glpat-…)、云访问密钥 ID(AKIA…、ASIA…)和带标签的秘密访问密钥、AIza… API 密钥、聊天平台令牌(xox…)、支付平台密钥(sk_live_…、rk_live_…、sk_test_…、whsec_…)以及 sk-… API 密钥。这不是通用的数据丢失防护,也不会扫描二进制附件。
  • 不会对消息大小或收件人是否接受作任何假设:fmsg 主机自身的响应和投递代码会原样传递。
  • 服务器会发布 MCP instructions(在会话开始时展示给模型),告知 Agent 使用这些工具,而不是本地 fmsg CLI 或缓存的凭据;执行已获授权的任务和自动化,无需反复确认;并将消息内容视为数据。
  • 请参阅 SECURITY.md。

使用客户端库

import { FmsgClient } from "@markmnl/fmsg-mcp/client";

const client = new FmsgClient("https://api.example.com", process.env.FMSG_API_KEY!);
console.log(await client.address());
const inbox = await client.listInbox(10);
const sent = await client.send({ to: ["@[email protected]"], topic: "Hi", body: "Hello from code" });
console.log(sent.id, sent.redactions);
client.close();

send() 会在创建草稿前替换正文和主题中的选定凭据模式。结果包含替换数量(redactions)、被替换的类型(redacted,例如 github_token)以及传输的 topic。附件不会更改。对于大文件,请使用 streamAttachment() 逐步读取;请读取完流或取消流。

具有自有授权集成的应用可以传入 TokenProvider,而不是 API 密钥字符串。客户端会在并发请求之间共享续期操作,并固定已验证的地址。请参阅 令牌提供程序契约。可执行文件的可选 OAuth 模式 也使用此库接口。

开发

npm ci
npm run typecheck && npm run build && npm test
npx @modelcontextprotocol/inspector node dist/index.js          # stdio, with FMSG_API_URL/FMSG_API_KEY set
bash .github/scripts/run-fmsg-docker-e2e.sh                     # end to end on two real fmsg stacks

有关目录结构和约定,请参阅 AGENTS.md;有关剩余集成工作,请参阅 ROADMAP.md;有关发行说明,请参阅 CHANGELOG.md。

发布

发布标记为 vX.Y.Z 的稳定 GitHub 版本以运行检查、发布 npm 包,然后使用相同版本更新 MCP Registry。两者均使用 OIDC 身份验证;无需专用的注册表密钥。草稿和预发行版不会发布。注册表发布作为单独的作业运行,因此如果 npm 发布成功后该作业失败,请在发布工作流中使用 Re-run failed jobs。

MIT 许可

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。