TSLinkTSLink 文档

TSLink 作为 MCP 服务器

通过 stdio 或 tailnet-only HTTP 控制面,使用 44 个 owner 工具分享和管理本地服务

查看 Markdown

场景指南:在手机上使用编程 agent 的 Web UI。

TSLink 的同一个 registry 通过两种传输提供 44 个 owner 工具:本地 stdio 子进程(tslink mcp)和可选的 tailnet-only Streamable HTTP endpoint(tslink serve --mcp)。本页说明如何控制 TSLink 本身。代理已有第三方 MCP server 见 MCP 服务器托管。

注册到本地 MCP client

json
{"mcpServers":{"tslink":{"command":"tslink","args":["mcp"]}}}

如果 client 的 PATH 不同,使用已安装二进制的绝对路径。不要在 client 配置中加入凭据。默认分享不需要存储凭据;需要浏览器注册时,TSLink 返回 needs_login。

MCP 进程本身不监听网络。调用 share、add、template_apply 可能安装或启动独立的后台网关及其 tsnet 服务;正常安装公告写入 stderr。

stdio 契约

项目行为
stdout仅 JSON-RPC frames,每行一个 JSON object
stderr诊断、日志和正常安装公告;成功 session 不保证 stderr 为空
--jsontslink mcp --json 以 exit 2 拒绝;stdout 保留给协议 frames
版本当前 2026-07-28;兼容 2025-11-25、2025-06-18、2025-03-26、2024-11-05
协商当前 client 在每个请求的 _meta 中提供版本,不需要 initialize。旧 client 使用 initialize 后发 notifications/initialized;旧握手回显支持版本,否则回退为 2025-11-25
EOF已读请求的答案排空后退出;关闭 stdin 时仍需继续读取 stdout
EOF watchdogstdin 关闭 6m 后仍运行的调用会被取消;给 handler 5s 返回,之后命令 exit 1。stdout 若仍阻塞,再最多等待 5s 后放弃响应
信号SIGINT/SIGTERM 取消在途调用,回滚仍等待 URL 的 share,并 exit 1。第二个信号立即终止
结束 session 的输入malformed JSON、不是 JSON-RPC message 的 JSON 值、JSON-RPC batch 或超过 1,048,576 bytes 的记录会结束 session、丢弃在途答案,后面的记录不再读取
重复在途 id不会得到答案;调用完成前不要复用 id
URL 轮询url.wait 是 Go duration,上限 5m;超过上限是 usage error

不要假设 malformed 输入会获得可恢复的 JSON-RPC error。session 结束后重新启动子进程。协议协商和错误由 SDK 处理。serverInfo.version 标识已安装构建,未打版本时为 dev。

旧 client 的握手起始如下:

json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0.0"}}}
json
{"jsonrpc":"2.0","method":"notifications/initialized"}

工具与完整输入字段

owner 的 tools/list 提供 44 个工具;精简会话只展示其被允许的集合,每个工具提供 inputSchema 与 outputSchema。输入严格校验,未知字段和不正确类型会被拒绝。下表列出全部输入字段;已安装构建的实时 schema 仍是权威。daemon 生命周期、安装、login/logout 和配置仍仅通过 CLI 提供。

会话工具索引

本地 owner 提供 44 个、viewer 12 个、people-manager 18 个工具,请用真实会话 tools/list 发现。下表明确必填参数,可选字段仍遵守实时 schema;参数不能扩大角色。V = viewer、A = app-operator、P = people-manager,全部行对 owner 可用。精简角色另受应用/期限范围限制,请求工具还需 portal-owner 检查。

工具必填参数可选参数精简角色
access_explainservice: string无V/A/P
access_log无app: string, decision: string, limit: integer, since: string, until: string, who: stringV/A/P
access_summary无app: string, decision: string, limit: integer, since: string, until: string, who: stringV/A/P
addname: string, type: stringallow: array, control_url: string, dir: string, ephemeral: boolean, funnel: boolean, funnel_ttl: string, health: object, no_auto_provision: boolean, no_daemon_install: boolean, preserve_host: boolean, public_ack: boolean, request_limits: object, requestable: boolean, tags: array, target: string仅 owner
app_restartapp: string无A
apps_detect无无仅 owner
doctor无probe_external: booleanV/A/P
extendservice: stringack_never: boolean, for: string, regrant: boolean, until: string, who: stringA/P
guest_createapp: string, for: stringlabel: string, pin: string, print_link: boolean, public: boolean仅 owner
guest_list无无仅 owner
guest_revokeid: string无仅 owner
guest_showid: string无仅 owner
health无无V/A/P
invite_deviceemail: string, service: stringallow_exit_node: boolean, multi_use: boolean, print_link: boolean仅 owner
invite_list无show_urls: boolean仅 owner
invite_resendinvite_id: string, kind: string无仅 owner
invite_revokeinvite_id: string, kind: string无仅 owner
invite_useremail: stringprint_link: boolean, role: string仅 owner
list无无V/A/P
logs无last: integer, level: string, since: string, source: string仅 owner
mcp_audit无无仅 owner
people_addapps: array, who: stringack_never: boolean, for: string, invite: boolean, print_links: boolean, qr: boolean, qr_invite: string, until: string仅 owner
people_grantapp: string, for: string, who: string无A/P
people_list无无V/A/P
people_removewho: stringreconcile_invites: object仅 owner
people_revokeapp: string, who: string无A/P
people_updatewho: stringack_never: boolean, apps: array, for: string, invite: boolean, print_links: boolean, qr: boolean, qr_invite: string, reconcile_invites: object, replace_invites: object, until: string仅 owner
portal_disable无无仅 owner
portal_enableowner: stringadmins: array, funnel: boolean, hostname: string仅 owner
recipe_applyrecipe_id: stringallow: string, control_url: string, ephemeral: boolean, force_unsafe_public: boolean, funnel: boolean, funnel_ttl: string, health: object, name: string, no_auto_provision: boolean, no_daemon_install: boolean, preserve_host: boolean, public_ack: boolean, request_limits: object, tags: string, target: string仅 owner
recipe_list无无V/A/P
recipe_planrecipe_id: stringallow: string, control_url: string, ephemeral: boolean, force_unsafe_public: boolean, funnel: boolean, funnel_ttl: string, health: object, name: string, no_auto_provision: boolean, no_daemon_install: boolean, preserve_host: boolean, public_ack: boolean, request_limits: object, tags: string, target: string仅 owner
requests_approvefor: string, id: stringack_never: booleanP
requests_denyid: stringreason: stringP
requests_list无无P
sharetarget: stringallow: array, ephemeral: boolean, funnel: boolean, funnel_ttl: string, name: string, no_daemon_install: boolean, preserve_host: boolean, public_ack: boolean, request_limits: object, tags: array仅 owner
status无无V/A/P
tags_list无无V/A/P
tags_setservice: string, tag: string无仅 owner
template_applyname: stringno_daemon_install: boolean仅 owner
template_list无无V/A/P
template_planname: string无仅 owner
unsharename: string无仅 owner
urlname: stringwait: stringV/A/P

extend 必须给 for/until 之一,精简角色必须指定 who。Guest 创建需 for,首次启用 gate 需 public: true。成员永久授权需 ack_never: true;新的 guest/公开拒绝 never。普通 qr 返回 payload 文本,MCP 没有 qr_png 参数。portal_enable.funnel: true 被拒绝。

share#

分享已有文件、目录或 HTTP 端口。目录目标提供可浏览内容;单文件目标只提供该文件。匹配的 target/name 可以复用;无关的名称冲突会加数字后缀。

字段类型 / 必填行为
targetstring,必填已有路径、1..65535 端口或 host:port HTTP 目标
namestring可选 DNS label,最多 63 字符,^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
ephemeralboolean默认 true;指 tailnet 节点生命周期,不自动删除 registry
no_daemon_installboolean要求网关已经运行,不自动安装
allowstring array登录邮箱或 tag:;省略则无额外 allow 规则,人员策略仍生效;与 Funnel 冲突
tagsstring array每项以 tag: 开头;有存储凭据的模式使用已配置默认标签
funnelboolean默认 false;公开要求 HTTP 端口目标、public_ack: true 且无 allow
public_ackboolean默认 false;明确确认公开互联网访问
funnel_ttlstring共同相对/绝对期限语法,至少 1h,默认 24h,公开上限默认 7d;新的公开拒绝 never。
preserve_hostboolean仅 proxy,转发节点 canonical 外部 Host;默认 false 重写 upstream Host,Origin 保持不变
request_limitsobjectmax_body、unlimited_ack、header_timeout、read_timeout、idle_timeout;限制上传大小与空闲,不限制请求频率

share 最多等待 30s 获取就绪或授权信息。status 始终存在;ready 带 url、name,needs_login 带 auth_url。公开分享还可能带 funnel_expires_at、funnel_rearmed。复用仍有效的分享会保留原截止时间;已过期 Funnel 可以按请求时长重新启用。

add#

写入或替换具名 registry 服务。默认确保网关存在,返回当前 URL/授权证据,不额外等待 URL;用 url 轮询。

字段类型 / 必填行为
namestring,必填DNS label,最多 63 字符,与 share.name 同一格式;同名条目被替换
typestring,必填proxy、file、tcp
targetstringproxy/TCP 必填、file 拒绝。proxy 接受 host:port 或 URL,TCP 接受 host:port
dirstringfile 要求绝对目录路径;proxy/TCP 拒绝
allowstring arrayHTTP 登录邮箱或 tag:;TCP 及 Funnel 拒绝
tagsstring arraytag: 条目;有存储凭据模式使用已配置默认标签
ephemeralboolean默认 false
funnelboolean默认 false;要求 proxy、public_ack: true、无 allow、无 control_url
public_ackboolean默认 false;明确公开确认
funnel_ttlstring共同相对/绝对期限语法,至少 1h,默认 24h,公开上限默认 7d;新的公开拒绝 never。
no_daemon_installboolean保存配置,不安装后台服务
no_auto_provisionboolean默认 false;关闭 Funnel 策略自动配置,仅 Funnel 可用
control_urlstring如 Headscale 的自定义 control server;与 Funnel 冲突
preserve_hostboolean仅 proxy,转发节点 canonical 外部 Host;默认 false 重写 upstream Host,Origin 保持不变
request_limitsobjectmax_body、unlimited_ack、header_timeout、read_timeout、idle_timeout;限制上传大小与空闲,不限制请求频率
healthobjectpath、status_min、status_max、body_contains、timeout、interval;path/status/body 仅 proxy
requestableboolean明确在私有 portal 请求表单披露名称;默认 false,仅私有 HTTP/文件。

读取 created、url、url_pending、endpoint、exposure、funnel_rearmed,以及可选的 auth_url / next / warnings,区分注册与就绪。目标校验拒绝字面量 link-local、unspecified、cloud-metadata IP 及 metadata.google.internal;不会解析其它主机名,也不在连接时重新检查目的地址。获授权 agent 可以指定 daemon 网络可达的其它地址。

其余 17 个工具

标记为无字段的工具,完整输入就是空 object {}。

工具完整输入字段结果 / 语义
list无services:准确或 pending URL/state,以及 funnel_requested、funnel_active、funnel_state、可选截止时间/剩余时间/error
unsharename:必填 DNS-label stringok、name、removed、device_cleaned、device_cleanup_skipped,可选 device_skip_reason / device_warning
status无authenticated 是 node_authorized 别名;分别读取 credential_stored、授权/服务数量、daemon_running、supervision,可选 status、auth_url、next
urlname:必填 DNS-label string;wait:可选 Go duration,最多 5m,空或非正数不轮询准确的 name、url、state;pending 返回 url_not_ready,不会猜主机名
tags_list无注册服务名和本地记录的 tags
tags_setservice:必填 string;tag:必填、以 tag: 开头的 string用一个 tag 替换服务的本地 tags;返回 service、tags
access_explainservice:必填 string本地暴露/执行证据、未知外部策略、后端认证假设;不是有效 tailnet 授权证明
doctorprobe_external:可选 boolean,默认 falsefindings、counts、supervision、运行时/凭据证据;health_exit_code 只报告 CLI 健康严重性,warning/critical 本身不让 MCP tool 失败
logssource:err 或 out,默认 err;last:integer 1..1000,默认 100;level:可选最低 debug、info、warn、error;since:正 Go duration,默认 1h,最多 168h只读、有界日志窗口,见下文
invite_useremail:必填 string;role:可选 member、admin、billing-admin、it-admin、network-admin、auditor,默认 member;print_link:boolean,默认 false真实 tailnet 邀请,需确认收件人和 role;print_link 返回 bearer URL 而不发邮件
invite_deviceservice、email:必填 strings;print_link、multi_use、allow_exit_node:booleans,默认 false给 tailnet 外的人发送一个已证明归属服务设备的真实邀请
invite_listshow_urls:boolean,默认 false读取未完成邀请;默认隐藏 bearer URLs;complete: false 表示部分设备未能检查
invite_revokekind:必填 user 或 device;invite_id:必填 string取消真实邀请;结果含 revoked 和 remote-side-effect plan
invite_resendkind:必填 user 或 device;invite_id:必填 string给原收件人重发真实邮件;不支持 print_link 邀请
template_list无内置模板与服务数;不安装第三方应用
template_planname:必填 string只读计划;dry_run: true、applied: false
template_applyname:必填 string;no_daemon_install:可选 boolean写入模板缺失服务、保留已有同名条目;未禁用时按需安装网关;读取 applied、created、skipped

邀请 URL 是 bearer credential。公开 Funnel 和邀请变更会产生真实外部影响;agent 应在调用前与用户确认具体动作。MCP 发送非 member role 的用户邀请或允许 exit-node use 的设备邀请,还要求 owner 通过 mcp.allow_elevated_invites 显式允许,否则返回 mcp_elevated_invite_refused。unshare 也标记为 destructive:它执行与 tslink remove 相同的精确所有权设备及 node-state 删除,server instructions 要求客户端先与用户确认。

logs 与身份边界#

应用/访问日志写入 stderr(tslink.err.log);out 是 daemon stdout。MCP 返回 source、file、可选 level、since、since_at、lines、count、matched、truncated、可选 truncated_reason(line_limit / byte_limit),以及 redacted: true。脱敏后的返回文本还限制在 256 KiB 内。

凭据材料、可识别的 Tailscale 登录/邀请 URLs 和邮箱被脱敏,这会减少偶然进入模型上下文的内容,但不保证清除所有 secret 形态。tslink logs CLI 原样读取同一文件,所以脱敏不是阻止有 shell 的 agent 访问 secret 的边界。用 access_log / access_summary 查看访问历史中的有界记录与 receipt,身份可缺失,也可能丢失或有崩溃缺口。Proxy 身份头保留独立的 best-effort 缓存边界。

完整示例:报告到 URL

完成适用版本协商后调用:

json
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"share","arguments":{"target":"/absolute/path/report.html","name":"report"}}}

成功交接把同一载荷作为 JSON 放进 content[0].text,并作为已解析 object 放进 structuredContent:

json
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\"status\":\"needs_login\",\"auth_url\":\"https://login.tailscale.com/a/...\"}"}],"structuredContent":{"status":"needs_login","auth_url":"https://login.tailscale.com/a/..."}}}

把 auth_url 交给用户。完成授权后,用 {"name":"report","wait":"30s"} 调用 url,把 structuredContent.url 交回。用 list 查看就绪状态,无需重发 share。用完后,用 {"name":"report"} 调用 unshare。

unshare 是幂等的:不存在的名称可以同时返回 ok: true 和 removed: false。本地删除与远端设备清理分开;远端清理要求 API client 和准确所有权证明。默认零凭据流程可以删除 registry 条目,但留下远端设备。

错误与取消

工具/业务失败返回 isError: true,content[0].text 为可读错误,structuredContent 包含 ok: false、数字 CLI code 和带稳定 code/message、可能有 next 的 error。needs_login 是成功结果。未知 method(-32601)和无效参数(-32602)等属于 JSON-RPC 错误,不是 CLI 信封。结束 session 的输入遵循上方终止规则,不是可逐行恢复的错误。

使用唯一 request id;放弃请求时使用 MCP cancellation notifications。关闭 stdin 会排空已读请求,不会立即取消。要取消整个进程应发信号,并持续读取 stdout 直到退出。

远程 MCP 控制面

tslink serve --mcp 在专用 TLS tsnet 节点的 https://<node>.<tailnet>.ts.net/mcp 提供同一组 44 个 owner 工具。默认名 tslink-mcp,可用 mcp.node_name 覆盖;状态目录为 ~/.config/tslink/mcp-node/。它不是 registry 服务,不能通过 Funnel 发布。

启用

控制面默认关闭。设置 --mcp 或 mcp.enabled: true;两者都要求 config.json 中非空 owner mcp.allow 或明确 mcp.bindings。MCP 配置需直接编辑;tslink config set 也管理受支持的 access-log 设置:

json
{"mcp":{"enabled":true,"allow":["you@example.com","tag:ops"],"node_name":"tslink-mcp","events_keepalive":"20s"}}

配置后采用明确的 daemon 重启或前台 serve --mcp 流程;已有网关运行时不要启动第二个。

边界

  • listener 只在自己的 tsnet 节点上提供 tailnet-only TLS,不绑定主机接口,也不通过 Funnel。获授权 peer 有高权限控制能力,包括公开分享和真实邀请。
  • mcp.allow 匹配 WhoIs 登录邮箱 / tag:;没有有效 owner 条目或 bindings 会拒绝启动。Scope 身份拒绝使用 HTTP 403 与 mcp_scope_denied,见角色与应用范围。
  • Origin 若存在,必须与 endpoint 自己的 HTTPS origin 完全一致,否则在授权前 403;无 Origin 的请求继续进入授权。
  • Streamable HTTP 为 stateless,请求 body 上限 1 MiB。client 请求必须来自能访问 tailnet 的设备;云端 connector 不会因为你的电脑可达就能访问私有地址。
  • 有存储凭据的控制面节点是 ephemeral;零凭据浏览器注册节点是持久的,禁用后可能需要显式删除 tailnet 设备。崩溃也可能留下等待 Tailscale 清理的设备。

/events 事件流#

同一远程节点提供仅 owner 可用的 SSE,沿用 Origin 与调用者检查。精简角色得到 404,改为轮询工具。只发具名 snapshot、update、keepalive,不发默认 message。必须为这些名称注册 handler,只有 onmessage 不会收到任何事件。

每个 payload 带 type。SSE id: 是 instance-global 的 event_id,与每条 stream 的 sequence 不同;按 event_id 去重。instance 变化意味着 daemon 重启,应丢弃缓存状态。没有 retry: 字段,也没有 Last-Event-ID replay;每次重连都收到完整 snapshot。

mcp.events_keepalive 是 Go duration,默认 20s,范围 5s 到 5m。此 endpoint 属于远程控制面,不是 roadmap 的 admin REST/dashboard API。

相关文档

人员与应用 recipe 工具

本地默认 owner;已交付的 MCP 角色与应用范围限制操作,不隔离 shell/文件系统,也不授予应用访问。变更 receipt 有界,见访问历史。

工具完整输入字段含义
people_addwho:必填登录账户;apps:必填非空 string array;for:时长或 never;invite、print_links:boolean ; until: string; ack_never, qr: boolean; qr_invite: string保存私有 HTTP/文件授权,首次授权收紧应用。创建邀请需用户自己的 API token,明确 print 才展示 bearer link
people_list无读取人员、授权、期限与撤销记录
people_updatewho:必填;apps、for、invite、print_links;reconcile_invites、replace_invites:应用到 ID 的 object ; until: string; ack_never, qr: boolean; qr_invite: string需指定应用、期限、QR 生成或 invite: true。Invite-only 重试复用完成 ID;reconcile 需主人核对;replace 需 invite-only 且保留授权期限
people_removewho:必填;reconcile_invites:应用到 ID 的 object先保存拒绝,再清理待接受邀请;查看 complete 与 cleanup,已接受网络分享可能保留
apps_detect无有界 loopback GET 检测;complete: false 为部分结果,不能证明应用安全
recipe_list无带版本 catalog,含端口、应用配置、健康路径与安全级别
recipe_plan下方字段只读预览
recipe_apply与 recipe_plan 相同应用审阅过的 plan,已有名称不变,不安装或配置后端

两个 recipe 工具接受以下全部字段:

字段类型与行为
recipe_id必填 string,从 recipe_list 取得
name、target可选 string,覆盖服务名及 loopback HTTP(S) 主机端口
allow、tags可选、逗号分隔的 string,与 service 工具的 array 不同
health、request_limitsObject,与上方 add 字段相同
preserve_hostBoolean 覆盖;省略使用 recipe 的 Host 默认值
ephemeral、no_daemon_install可选 boolean
funnel、public_ack、no_auto_provision可选 boolean;公开需要 funnel: true、public_ack: true 和合适的应用认证
funnel_ttl共同相对/绝对期限语法,至少 1h,默认 24h,公开上限默认 7d;新的公开拒绝 never。
control_url可选 string,与 Funnel 冲突
force_unsafe_public危险的 boolean override,覆盖 never-public recipe 拒绝,不代表应用已认证

以相同选项先调用 recipe_plan 再 recipe_apply。人员修改工具要求确认人、应用与期限,因为它们可能收紧已有访问。TCP 和 Funnel 无法按人授权。撤销阻止新请求,不关闭已接受的流。见人员分享。

七天授权的工具参数:

json
{"who":"alice@example.com","apps":["preview"],"for":"7d"}

注册并完成 preview 入网后,用 people_add 执行。读取 person、complete、invites、message 与 invite_requirement;命令成功仍可能有未完成的邀请。

来源:TSLink MCP 服务注册、人员工具、recipe 工具。

新访问工具的使用边界

health 读取后端观察,access_log/access_summary 查询保留的范围内历史。people_grant/people_revoke 修改已存在人员的单应用授权。extend 设为当前时间加时长,到期需明确 regrant。app_restart 排队重启 gateway 节点,不是后端重启或已完成结果,之后轮询 status/health;精简 operator 不能重启已有公共 Funnel 应用。

guest_create/list/show/revoke 仅 owner 可用,支持单 HTTP proxy 应用;链接/PIN 可转发,不证明人的身份。portal_enable/disable 管理每台主机的私有节点;远程 enable 需当前 portal owner,首次设置/恢复需本地进行。requests_list/approve/deny 只给 owner/people-manager;远程还需当前真人 portal-owner 身份,admin designation 本身不够。精简角色不能新建未知人员或修改受保护的 portal-owner/admin 记录。

mcp_audit 仅 owner 可用。变更前持久保存 intent,缺 completion 表示结果未知。访问历史与 receipt 分别有界且可能有缺口,不保证完整审计或合规。见访客链接、portal/请求、角色、期限和访问历史。

目录