claude-code-tailscale 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -0
- package/dist/fetch-via-tailnet.d.ts +22 -0
- package/dist/fetch-via-tailnet.js +63 -0
- package/dist/fetch-via-tailnet.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +485 -0
- package/dist/index.js.map +1 -0
- package/dist/tshelper.d.ts +55 -0
- package/dist/tshelper.js +203 -0
- package/dist/tshelper.js.map +1 -0
- package/package.json +45 -0
- package/scripts/postinstall.mjs +107 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Claude Code Tailscale Plugin
|
|
2
|
+
|
|
3
|
+
A Claude Code MCP server that joins a [Tailscale](https://tailscale.com) tailnet as an ephemeral device, giving Claude access to internal services, APIs, and resources on your tailnet.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Claude Code
|
|
9
|
+
└── MCP Server (TypeScript)
|
|
10
|
+
└── tshelper (Go binary using tsnet)
|
|
11
|
+
└── SOCKS5 proxy on localhost
|
|
12
|
+
└── routes through your tailnet
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
When the MCP server starts, it immediately spawns `tshelper`, which:
|
|
16
|
+
1. Joins your tailnet as an ephemeral device using `tsnet`
|
|
17
|
+
2. Starts a SOCKS5 proxy on localhost
|
|
18
|
+
3. Returns the proxy address to the MCP server
|
|
19
|
+
|
|
20
|
+
Claude can then use `tailscale_fetch` to access any service on your tailnet — internal APIs, dashboards, databases — without exposing them to the internet.
|
|
21
|
+
|
|
22
|
+
When the session ends, the device explicitly logs out and is immediately removed from your tailnet.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- [Go 1.24+](https://go.dev/dl/) (to build tshelper)
|
|
27
|
+
- [Node.js 20+](https://nodejs.org/)
|
|
28
|
+
- A Tailscale account with an [auth key](https://login.tailscale.com/admin/settings/keys) (ephemeral, reusable recommended) or an [OAuth client](https://tailscale.com/kb/1215/oauth-clients)
|
|
29
|
+
|
|
30
|
+
## Setup
|
|
31
|
+
|
|
32
|
+
### 1. Build
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Clone the repo
|
|
36
|
+
git clone https://github.com/rosskukulinski/claude-code-tailscale.git
|
|
37
|
+
cd claude-code-tailscale
|
|
38
|
+
|
|
39
|
+
# Build the Go helper
|
|
40
|
+
cd tshelper && go build -o ../bin/tshelper . && cd ..
|
|
41
|
+
|
|
42
|
+
# Install npm dependencies and build TypeScript
|
|
43
|
+
npm install && npm run build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Configure
|
|
47
|
+
|
|
48
|
+
Set your Tailscale credentials (pick one):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Option A: Auth key (simplest)
|
|
52
|
+
export TS_AUTHKEY="tskey-auth-..."
|
|
53
|
+
|
|
54
|
+
# Option B: OAuth client secret (preferred for automation/CI)
|
|
55
|
+
export TS_CLIENT_SECRET="tskey-client-..."
|
|
56
|
+
export TS_TAGS="tag:claude-code" # required for OAuth
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. Add to Claude Code
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
claude mcp add tailscale -- node /path/to/claude-code-tailscale/dist/index.js
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or add to your project's `.mcp.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"tailscale": {
|
|
71
|
+
"command": "node",
|
|
72
|
+
"args": ["/path/to/claude-code-tailscale/dist/index.js"],
|
|
73
|
+
"env": {
|
|
74
|
+
"TS_AUTHKEY": "tskey-auth-..."
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or with an OAuth client secret:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"tailscale": {
|
|
87
|
+
"command": "node",
|
|
88
|
+
"args": ["/path/to/claude-code-tailscale/dist/index.js"],
|
|
89
|
+
"env": {
|
|
90
|
+
"TS_CLIENT_SECRET": "tskey-client-...",
|
|
91
|
+
"TS_TAGS": "tag:claude-code"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Tools
|
|
99
|
+
|
|
100
|
+
| Tool | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `tailscale_status` | Show tailnet connection status, IPs, and hostname |
|
|
103
|
+
| `tailscale_fetch` | HTTP fetch through the tailnet (access internal services) |
|
|
104
|
+
| `tailscale_connect_test` | Test TCP connectivity to a tailnet host:port |
|
|
105
|
+
|
|
106
|
+
### Example Usage
|
|
107
|
+
|
|
108
|
+
Once configured, Claude can access your tailnet services:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
> Use tailscale_fetch to get the status of my internal API at http://api-server.tail1234.ts.net/health
|
|
112
|
+
|
|
113
|
+
> Check if my database server is reachable: tailscale_connect_test db.tail1234.ts.net port 5432
|
|
114
|
+
|
|
115
|
+
> What's my tailnet connection status?
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
| Environment Variable | Required | Default | Description |
|
|
121
|
+
|---------------------|----------|---------|-------------|
|
|
122
|
+
| `TS_AUTHKEY` | One of `TS_AUTHKEY` or `TS_CLIENT_SECRET` | — | Tailscale auth key (ephemeral recommended) |
|
|
123
|
+
| `TS_CLIENT_SECRET` | One of `TS_AUTHKEY` or `TS_CLIENT_SECRET` | — | OAuth client secret (`tskey-client-...`). Preferred for automation/CI |
|
|
124
|
+
| `TS_TAGS` | Required for OAuth | — | Comma-separated ACL tags (e.g., `tag:claude-code,tag:dev`) |
|
|
125
|
+
| `TS_HOSTNAME` | No | `cc-{user}-{context}-{rand}` | Tailnet hostname override (random suffix always appended) |
|
|
126
|
+
| `TS_STATE_DIR` | No | `~/.cache/claude-code-tailscale/{hostname}` | State directory |
|
|
127
|
+
| `TS_CONTROL_URL` | No | — | Custom control server (for Headscale) |
|
|
128
|
+
| `TS_EPHEMERAL` | No | `true` | Register as ephemeral node |
|
|
129
|
+
|
|
130
|
+
**Hostname format:** `cc-{user}-{context}-{random4}` where `{context}` is the git repo name (or machine hostname if not in a repo) and `{random4}` is 4 random hex chars for uniqueness across concurrent sessions.
|
|
131
|
+
|
|
132
|
+
## Architecture
|
|
133
|
+
|
|
134
|
+
This plugin follows the same pattern as [kong-tailscale-plugin](https://github.com/rosskukulinski/kong-tailscale-plugin):
|
|
135
|
+
|
|
136
|
+
- **Kong plugin**: LuaJIT FFI → libtailscale → `tailscale_loopback()` → SOCKS5 → cosocket
|
|
137
|
+
- **This plugin**: Go tsnet binary → SOCKS5 proxy → Node.js `http.request` with `socks-proxy-agent`
|
|
138
|
+
|
|
139
|
+
The Go helper (`tshelper`) uses `tsnet.Server` directly instead of libtailscale's C API, since we're already in Go. It exposes a SOCKS5 proxy on localhost that the TypeScript MCP server routes through.
|
|
140
|
+
|
|
141
|
+
Key design decisions:
|
|
142
|
+
- **Eager connection**: Tailnet joins immediately on MCP server start (tools wait if not yet ready)
|
|
143
|
+
- **Ephemeral by default**: Device auto-removes when the session ends, with explicit logout for instant cleanup
|
|
144
|
+
- **Unique hostnames**: Each session gets a unique hostname (`cc-{user}-{repo}-{rand}`) to avoid collisions
|
|
145
|
+
- **OAuth + auth key support**: Works with both `TS_CLIENT_SECRET` (OAuth) and `TS_AUTHKEY` (plain auth keys)
|
|
146
|
+
- **No system Tailscale required**: tsnet embeds a full Tailscale node in the Go binary
|
|
147
|
+
- **SOCKS5 loopback**: Same proven proxy pattern as the Kong plugin
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
Apache 2.0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP fetch through the Tailscale SOCKS5 proxy.
|
|
3
|
+
*
|
|
4
|
+
* Uses node:http/node:https with socks-proxy-agent, since Node.js native
|
|
5
|
+
* fetch (undici) doesn't support http.Agent. Same pattern as the Kong
|
|
6
|
+
* plugin's SOCKS5 cosocket path, but in Node.js.
|
|
7
|
+
*/
|
|
8
|
+
import { SocksProxyAgent } from "socks-proxy-agent";
|
|
9
|
+
export interface FetchOptions {
|
|
10
|
+
method?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
body?: string;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface FetchResult {
|
|
16
|
+
status: number;
|
|
17
|
+
statusMessage: string;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
body: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function createSocksAgent(socks5Addr: string): SocksProxyAgent;
|
|
22
|
+
export declare function fetchViaTailnet(url: string, agent: SocksProxyAgent, options?: FetchOptions): Promise<FetchResult>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP fetch through the Tailscale SOCKS5 proxy.
|
|
3
|
+
*
|
|
4
|
+
* Uses node:http/node:https with socks-proxy-agent, since Node.js native
|
|
5
|
+
* fetch (undici) doesn't support http.Agent. Same pattern as the Kong
|
|
6
|
+
* plugin's SOCKS5 cosocket path, but in Node.js.
|
|
7
|
+
*/
|
|
8
|
+
import http from "node:http";
|
|
9
|
+
import https from "node:https";
|
|
10
|
+
import { SocksProxyAgent } from "socks-proxy-agent";
|
|
11
|
+
export function createSocksAgent(socks5Addr) {
|
|
12
|
+
return new SocksProxyAgent(`socks5h://${socks5Addr}`);
|
|
13
|
+
}
|
|
14
|
+
export async function fetchViaTailnet(url, agent, options = {}) {
|
|
15
|
+
const { method = "GET", headers = {}, body, timeout = 30000 } = options;
|
|
16
|
+
const parsed = new URL(url);
|
|
17
|
+
const isHTTPS = parsed.protocol === "https:";
|
|
18
|
+
const lib = isHTTPS ? https : http;
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const req = lib.request({
|
|
21
|
+
hostname: parsed.hostname,
|
|
22
|
+
port: parsed.port || (isHTTPS ? 443 : 80),
|
|
23
|
+
path: parsed.pathname + parsed.search,
|
|
24
|
+
method,
|
|
25
|
+
headers: {
|
|
26
|
+
...headers,
|
|
27
|
+
host: parsed.host,
|
|
28
|
+
},
|
|
29
|
+
agent,
|
|
30
|
+
timeout,
|
|
31
|
+
}, (res) => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
34
|
+
res.on("end", () => {
|
|
35
|
+
const respHeaders = {};
|
|
36
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
37
|
+
if (typeof value === "string") {
|
|
38
|
+
respHeaders[key] = value;
|
|
39
|
+
}
|
|
40
|
+
else if (Array.isArray(value)) {
|
|
41
|
+
respHeaders[key] = value.join(", ");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
resolve({
|
|
45
|
+
status: res.statusCode ?? 0,
|
|
46
|
+
statusMessage: res.statusMessage ?? "",
|
|
47
|
+
headers: respHeaders,
|
|
48
|
+
body: Buffer.concat(chunks).toString("utf-8"),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
res.on("error", reject);
|
|
52
|
+
});
|
|
53
|
+
req.on("error", reject);
|
|
54
|
+
req.on("timeout", () => {
|
|
55
|
+
req.destroy(new Error(`Request timed out after ${timeout}ms`));
|
|
56
|
+
});
|
|
57
|
+
if (body) {
|
|
58
|
+
req.write(body);
|
|
59
|
+
}
|
|
60
|
+
req.end();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=fetch-via-tailnet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-via-tailnet.js","sourceRoot":"","sources":["../src/fetch-via-tailnet.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAgBpD,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,OAAO,IAAI,eAAe,CAAC,aAAa,UAAU,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,KAAsB,EACtB,UAAwB,EAAE;IAE1B,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IACxE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAEnC,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CACrB;YACE,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,IAAI,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM;YACrC,MAAM;YACN,OAAO,EAAE;gBACP,GAAG,OAAO;gBACV,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB;YACD,KAAK;YACL,OAAO;SACR,EACD,CAAC,GAAG,EAAE,EAAE;YACN,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACtD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,MAAM,WAAW,GAA2B,EAAE,CAAC;gBAC/C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;oBACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;wBAC9B,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;oBAC3B,CAAC;yBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;wBAChC,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACtC,CAAC;gBACH,CAAC;gBAED,OAAO,CAAC;oBACN,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC;oBAC3B,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,EAAE;oBACtC,OAAO,EAAE,WAAW;oBACpB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;iBAC9C,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,CAAC,CACF,CAAC;QAEF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACxB,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACrB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,2BAA2B,OAAO,IAAI,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code Tailscale MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Joins a Tailscale tailnet as an ephemeral device and exposes tools for
|
|
6
|
+
* Claude to interact with tailnet resources (fetch internal APIs, check
|
|
7
|
+
* status, test connectivity).
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* MCP Server (this file, TypeScript)
|
|
11
|
+
* └── spawns tshelper (Go binary using tsnet)
|
|
12
|
+
* └── SOCKS5 proxy on localhost
|
|
13
|
+
* └── routes through tailnet
|
|
14
|
+
*
|
|
15
|
+
* Same pattern as kong-tailscale-plugin, adapted for Node.js + MCP.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code Tailscale MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Joins a Tailscale tailnet as an ephemeral device and exposes tools for
|
|
6
|
+
* Claude to interact with tailnet resources (fetch internal APIs, check
|
|
7
|
+
* status, test connectivity).
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* MCP Server (this file, TypeScript)
|
|
11
|
+
* └── spawns tshelper (Go binary using tsnet)
|
|
12
|
+
* └── SOCKS5 proxy on localhost
|
|
13
|
+
* └── routes through tailnet
|
|
14
|
+
*
|
|
15
|
+
* Same pattern as kong-tailscale-plugin, adapted for Node.js + MCP.
|
|
16
|
+
*/
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { TsHelper } from "./tshelper.js";
|
|
21
|
+
import { createSocksAgent, fetchViaTailnet } from "./fetch-via-tailnet.js";
|
|
22
|
+
const helper = new TsHelper();
|
|
23
|
+
let agent = null;
|
|
24
|
+
let connectPromise = null;
|
|
25
|
+
// Connect lazily on first tool invocation to avoid wasting tsnet nodes
|
|
26
|
+
// when agents don't use tailscale tools.
|
|
27
|
+
function startConnection() {
|
|
28
|
+
connectPromise = helper.start().then((info) => {
|
|
29
|
+
agent = createSocksAgent(info.socks5_addr);
|
|
30
|
+
}).catch((err) => {
|
|
31
|
+
console.error(`[tailscale] connection failed: ${err.message}`);
|
|
32
|
+
connectPromise = null;
|
|
33
|
+
throw err;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function ensureConnected() {
|
|
37
|
+
if (helper.connected && agent)
|
|
38
|
+
return;
|
|
39
|
+
if (connectPromise) {
|
|
40
|
+
await connectPromise;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Lazy: start on first tool call
|
|
44
|
+
startConnection();
|
|
45
|
+
await connectPromise;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Call the tshelper HTTP control API and return MCP-formatted content.
|
|
49
|
+
*/
|
|
50
|
+
async function callAPI(path, options) {
|
|
51
|
+
await ensureConnected();
|
|
52
|
+
try {
|
|
53
|
+
const res = await helper.callAPI(path, options);
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: `API call failed: ${err.message}` }],
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// --- MCP Server ---
|
|
72
|
+
const server = new McpServer({
|
|
73
|
+
name: "tailscale",
|
|
74
|
+
version: "0.1.0",
|
|
75
|
+
});
|
|
76
|
+
server.tool("tailscale_status", "Show Tailscale connection status: tailnet IPs, hostname, and proxy address. Call this first to verify the tailnet connection is working.", {}, async () => {
|
|
77
|
+
await ensureConnected();
|
|
78
|
+
const info = helper.info;
|
|
79
|
+
return {
|
|
80
|
+
content: [{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: [
|
|
83
|
+
`Status: Connected`,
|
|
84
|
+
`Hostname: ${info.hostname}`,
|
|
85
|
+
`Tailnet IPs: ${info.tailnet_ips.join(", ")}`,
|
|
86
|
+
`SOCKS5 Proxy: ${info.socks5_addr}`,
|
|
87
|
+
`API: ${info.api_addr}`,
|
|
88
|
+
].join("\n"),
|
|
89
|
+
}],
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
server.tool("tailscale_fetch", "Fetch a URL through the Tailscale tailnet. Use this to access internal services, APIs, and resources that are only reachable via the tailnet (e.g., *.ts.net hostnames, 100.x.y.z addresses). Supports GET, POST, PUT, DELETE, PATCH, HEAD.", {
|
|
93
|
+
url: z.string().describe("The URL to fetch (e.g., http://my-service.tail1234.ts.net/api/v1/status)"),
|
|
94
|
+
method: z
|
|
95
|
+
.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"])
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("HTTP method (default: GET)"),
|
|
98
|
+
headers: z
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("HTTP headers as JSON object string, e.g. '{\"Authorization\": \"Bearer xxx\"}'"),
|
|
102
|
+
body: z
|
|
103
|
+
.string()
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Request body (for POST/PUT/PATCH)"),
|
|
106
|
+
timeout: z
|
|
107
|
+
.number()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Request timeout in milliseconds (default: 30000)"),
|
|
110
|
+
}, async ({ url, method, headers, body, timeout }) => {
|
|
111
|
+
await ensureConnected();
|
|
112
|
+
let parsedHeaders;
|
|
113
|
+
if (headers) {
|
|
114
|
+
try {
|
|
115
|
+
parsedHeaders = JSON.parse(headers);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: "Invalid headers JSON" }],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const result = await fetchViaTailnet(url, agent, {
|
|
126
|
+
method: method ?? "GET",
|
|
127
|
+
headers: parsedHeaders,
|
|
128
|
+
body,
|
|
129
|
+
timeout: timeout ?? 30000,
|
|
130
|
+
});
|
|
131
|
+
const headerLines = Object.entries(result.headers)
|
|
132
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
133
|
+
.join("\n");
|
|
134
|
+
// Truncate very large responses
|
|
135
|
+
const maxBody = 100_000;
|
|
136
|
+
const bodyText = result.body.length > maxBody
|
|
137
|
+
? result.body.slice(0, maxBody) + `\n\n... (truncated, ${result.body.length} bytes total)`
|
|
138
|
+
: result.body;
|
|
139
|
+
return {
|
|
140
|
+
content: [{
|
|
141
|
+
type: "text",
|
|
142
|
+
text: [
|
|
143
|
+
`HTTP ${result.status} ${result.statusMessage}`,
|
|
144
|
+
headerLines,
|
|
145
|
+
"",
|
|
146
|
+
bodyText,
|
|
147
|
+
].join("\n"),
|
|
148
|
+
}],
|
|
149
|
+
isError: result.status >= 400,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text", text: `Fetch failed: ${err.message}` }],
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
server.tool("tailscale_connect_test", "Test TCP connectivity to a tailnet host and port. Useful for verifying a service is reachable before attempting to fetch from it.", {
|
|
160
|
+
host: z.string().describe("Tailnet hostname or IP (e.g., my-server.tail1234.ts.net or 100.x.y.z)"),
|
|
161
|
+
port: z
|
|
162
|
+
.number()
|
|
163
|
+
.int()
|
|
164
|
+
.min(1)
|
|
165
|
+
.max(65535)
|
|
166
|
+
.describe("TCP port to test"),
|
|
167
|
+
}, async ({ host, port }) => {
|
|
168
|
+
await ensureConnected();
|
|
169
|
+
const start = Date.now();
|
|
170
|
+
try {
|
|
171
|
+
// Use a minimal HTTP request to test TCP connectivity through SOCKS5
|
|
172
|
+
await fetchViaTailnet(`http://${host}:${port}/`, agent, {
|
|
173
|
+
method: "HEAD",
|
|
174
|
+
timeout: 10000,
|
|
175
|
+
});
|
|
176
|
+
const elapsed = Date.now() - start;
|
|
177
|
+
return {
|
|
178
|
+
content: [{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: `Connected to ${host}:${port} via tailnet (${elapsed}ms)`,
|
|
181
|
+
}],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const elapsed = Date.now() - start;
|
|
186
|
+
// Connection errors still mean we tested connectivity
|
|
187
|
+
if (err.message.includes("ECONNREFUSED") || err.message.includes("ECONNRESET")) {
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `TCP connection to ${host}:${port} was rejected (${elapsed}ms) — host is reachable but port is closed or service not listening`,
|
|
192
|
+
}],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
content: [{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: `Connection to ${host}:${port} failed after ${elapsed}ms: ${err.message}`,
|
|
199
|
+
}],
|
|
200
|
+
isError: true,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
// --- New Tools (via HTTP control API) ---
|
|
205
|
+
server.tool("tailscale_dns_lookup", "Resolve a DNS name using the tailnet's DNS resolver (MagicDNS). Useful for looking up *.ts.net hostnames and other tailnet DNS records.", {
|
|
206
|
+
name: z.string().describe("DNS name to look up (e.g., my-server.tail1234.ts.net.)"),
|
|
207
|
+
type: z
|
|
208
|
+
.enum(["A", "AAAA", "CNAME", "TXT", "MX", "SRV", "NS", "PTR"])
|
|
209
|
+
.optional()
|
|
210
|
+
.describe("DNS record type (default: A)"),
|
|
211
|
+
}, async ({ name, type }) => {
|
|
212
|
+
return callAPI("/api/dns-lookup", {
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: { name, type: type ?? "A" },
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
server.tool("tailscale_whois", "Look up information about a tailnet IP address or node: who owns it, its hostname, OS, tags, and key expiry.", {
|
|
218
|
+
addr: z.string().describe("Tailnet IP address to look up (e.g., 100.x.y.z or [fd7a::x]:port)"),
|
|
219
|
+
}, async ({ addr }) => {
|
|
220
|
+
return callAPI(`/api/whois?addr=${encodeURIComponent(addr)}`);
|
|
221
|
+
});
|
|
222
|
+
server.tool("tailscale_devices", "List all devices on the tailnet with their IPs, hostnames, OS, online status, tags, and traffic stats.", {}, async () => {
|
|
223
|
+
return callAPI("/api/devices");
|
|
224
|
+
});
|
|
225
|
+
server.tool("tailscale_ping", "Ping a tailnet peer by IP to test connectivity and measure latency. Supports disco, TSMP, ICMP, and peerapi ping types.", {
|
|
226
|
+
ip: z.string().describe("Tailnet IP address to ping (e.g., 100.x.y.z)"),
|
|
227
|
+
type: z
|
|
228
|
+
.enum(["disco", "TSMP", "ICMP", "peerapi"])
|
|
229
|
+
.optional()
|
|
230
|
+
.describe("Ping type (default: disco)"),
|
|
231
|
+
}, async ({ ip, type }) => {
|
|
232
|
+
return callAPI("/api/ping", {
|
|
233
|
+
method: "POST",
|
|
234
|
+
body: { ip, type: type ?? "disco" },
|
|
235
|
+
timeout: 20000,
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
server.tool("tailscale_port_forward", "Create, list, or close port forwards from localhost to a tailnet host. Creates a local TCP listener that forwards connections through the tailnet.", {
|
|
239
|
+
action: z
|
|
240
|
+
.enum(["create", "list", "close"])
|
|
241
|
+
.describe("Action to perform"),
|
|
242
|
+
host: z
|
|
243
|
+
.string()
|
|
244
|
+
.optional()
|
|
245
|
+
.describe("Tailnet hostname or IP to forward to (required for create)"),
|
|
246
|
+
port: z
|
|
247
|
+
.number()
|
|
248
|
+
.int()
|
|
249
|
+
.min(1)
|
|
250
|
+
.max(65535)
|
|
251
|
+
.optional()
|
|
252
|
+
.describe("Remote port to forward to (required for create)"),
|
|
253
|
+
id: z
|
|
254
|
+
.string()
|
|
255
|
+
.optional()
|
|
256
|
+
.describe("Port forward ID to close (required for close)"),
|
|
257
|
+
}, async ({ action, host, port, id }) => {
|
|
258
|
+
switch (action) {
|
|
259
|
+
case "create":
|
|
260
|
+
if (!host || !port) {
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: "host and port are required for create action" }],
|
|
263
|
+
isError: true,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return callAPI("/api/port-forward", {
|
|
267
|
+
method: "POST",
|
|
268
|
+
body: { host, port },
|
|
269
|
+
});
|
|
270
|
+
case "list":
|
|
271
|
+
return callAPI("/api/port-forwards");
|
|
272
|
+
case "close":
|
|
273
|
+
if (!id) {
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: "text", text: "id is required for close action" }],
|
|
276
|
+
isError: true,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return callAPI(`/api/port-forward?id=${encodeURIComponent(id)}`, {
|
|
280
|
+
method: "DELETE",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
server.tool("tailscale_acl_check", "Show the current ACL packet filter rules as seen by this node. Useful for debugging access issues between tailnet devices.", {}, async () => {
|
|
285
|
+
return callAPI("/api/acl-check");
|
|
286
|
+
});
|
|
287
|
+
server.tool("tailscale_netcheck", "Show DERP relay regions and optionally probe a specific region for connectivity diagnostics. When full is true, performs active STUN probes to measure latency (ms) to every DERP region, reports the preferred DERP relay, and checks UDP/IPv4/IPv6 connectivity — similar to `tailscale netcheck` on the CLI. Without full, only lists available regions (no latency data).", {
|
|
288
|
+
region: z
|
|
289
|
+
.string()
|
|
290
|
+
.optional()
|
|
291
|
+
.describe("DERP region code to probe (e.g., 'nyc', 'sfo'). If omitted, just lists regions. Ignored when full is true."),
|
|
292
|
+
full: z
|
|
293
|
+
.boolean()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Set to true to run active STUN probes and measure per-region latency in milliseconds. Returns: preferredDERP, preferredDERPName, udp, ipv4, ipv6, and regionLatency[] sorted by latency ascending. Takes ~5-10 seconds."),
|
|
296
|
+
}, async ({ region, full }) => {
|
|
297
|
+
if (full) {
|
|
298
|
+
return callAPI("/api/netcheck-full", { timeout: 30000 });
|
|
299
|
+
}
|
|
300
|
+
const path = region
|
|
301
|
+
? `/api/netcheck?region=${encodeURIComponent(region)}`
|
|
302
|
+
: "/api/netcheck";
|
|
303
|
+
return callAPI(path, { timeout: 20000 });
|
|
304
|
+
});
|
|
305
|
+
server.tool("tailscale_cert", "Get TLS certificate information for a tailnet domain. Shows issuer, expiry, and DNS names without exposing the private key.", {
|
|
306
|
+
domain: z.string().describe("Tailnet domain to get certificate for (e.g., my-host.tail1234.ts.net)"),
|
|
307
|
+
}, async ({ domain }) => {
|
|
308
|
+
return callAPI(`/api/cert-info?domain=${encodeURIComponent(domain)}`, {
|
|
309
|
+
timeout: 20000,
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
// --- Inter-Agent Communication ---
|
|
313
|
+
server.tool("tailscale_agents", "Discover other Claude Code agents on the tailnet. Returns a list of cc-* peers with their hostnames, IPs, and online status. Use this to find agents you can communicate with.", {}, async () => {
|
|
314
|
+
return callAPI("/api/agents");
|
|
315
|
+
});
|
|
316
|
+
server.tool("tailscale_send_message", "Send a message to another Claude Code agent on the tailnet. The recipient agent can read it with tailscale_check_messages. Use to: \"*\" to broadcast to all agents.", {
|
|
317
|
+
to: z.string().describe("Recipient agent hostname (e.g., cc-ross-frontend-a7b2) or \"*\" for broadcast"),
|
|
318
|
+
subject: z.string().describe("Message subject"),
|
|
319
|
+
body: z.string().describe("Message body (max 64KB)"),
|
|
320
|
+
type: z
|
|
321
|
+
.enum(["message", "task", "task-result", "info"])
|
|
322
|
+
.optional()
|
|
323
|
+
.describe("Message type (default: message). Use 'task' for delegation, 'task-result' for responses, 'info' for FYI"),
|
|
324
|
+
in_reply_to: z
|
|
325
|
+
.string()
|
|
326
|
+
.optional()
|
|
327
|
+
.describe("ID of the message this is replying to"),
|
|
328
|
+
}, async ({ to, subject, body, type, in_reply_to }) => {
|
|
329
|
+
return callAPI("/api/messages/send", {
|
|
330
|
+
method: "POST",
|
|
331
|
+
body: { to, subject, body, type: type ?? "message", in_reply_to: in_reply_to ?? "" },
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
server.tool("tailscale_check_messages", "Check your message inbox for messages from other Claude Code agents. Returns unread messages by default.", {
|
|
335
|
+
unread_only: z
|
|
336
|
+
.boolean()
|
|
337
|
+
.optional()
|
|
338
|
+
.describe("Only return unread messages (default: true)"),
|
|
339
|
+
from: z
|
|
340
|
+
.string()
|
|
341
|
+
.optional()
|
|
342
|
+
.describe("Filter messages by sender hostname"),
|
|
343
|
+
type: z
|
|
344
|
+
.enum(["message", "task", "task-result", "info"])
|
|
345
|
+
.optional()
|
|
346
|
+
.describe("Filter messages by type"),
|
|
347
|
+
ack: z
|
|
348
|
+
.string()
|
|
349
|
+
.optional()
|
|
350
|
+
.describe("Message ID to acknowledge/mark as read"),
|
|
351
|
+
}, async ({ unread_only, from, type, ack }) => {
|
|
352
|
+
// If ack is provided, mark the message as read first
|
|
353
|
+
if (ack) {
|
|
354
|
+
const ackResult = await callAPI(`/api/messages/${encodeURIComponent(ack)}/ack`, {
|
|
355
|
+
method: "POST",
|
|
356
|
+
});
|
|
357
|
+
if (ackResult.isError)
|
|
358
|
+
return ackResult;
|
|
359
|
+
}
|
|
360
|
+
const params = new URLSearchParams();
|
|
361
|
+
if (unread_only !== false)
|
|
362
|
+
params.set("unread", "true");
|
|
363
|
+
if (from)
|
|
364
|
+
params.set("from", from);
|
|
365
|
+
if (type)
|
|
366
|
+
params.set("type", type);
|
|
367
|
+
const query = params.toString();
|
|
368
|
+
return callAPI(`/api/messages${query ? `?${query}` : ""}`);
|
|
369
|
+
});
|
|
370
|
+
// --- Agent Teams (opt-in via TS_ENABLE_TEAMS=1) ---
|
|
371
|
+
if (process.env.TS_ENABLE_TEAMS === "1") {
|
|
372
|
+
server.tool("tailscale_team_create", "Create a named team with this agent as lead. Other agents can then join this team to share tasks and coordinate work across machines.", {
|
|
373
|
+
name: z.string().describe("Team name (e.g., 'api-migration', 'frontend-refactor')"),
|
|
374
|
+
}, async ({ name }) => {
|
|
375
|
+
return callAPI("/api/team/create", {
|
|
376
|
+
method: "POST",
|
|
377
|
+
body: { name },
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
server.tool("tailscale_team_join", "Join a team hosted by another agent on the tailnet. Once joined, you can see and claim shared tasks.", {
|
|
381
|
+
lead_hostname: z.string().describe("Hostname of the team lead agent (e.g., cc-ross-backend-a7b2)"),
|
|
382
|
+
}, async ({ lead_hostname }) => {
|
|
383
|
+
return callAPI("/api/team/join", {
|
|
384
|
+
method: "POST",
|
|
385
|
+
body: { lead_hostname },
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
server.tool("tailscale_team_info", "Show current team membership, roles, and status. Returns team name, lead, and all members.", {}, async () => {
|
|
389
|
+
return callAPI("/api/team");
|
|
390
|
+
});
|
|
391
|
+
server.tool("tailscale_task_create", "Create a task on the team's shared task list. Tasks can be claimed by any team member.", {
|
|
392
|
+
subject: z.string().describe("Brief task title in imperative form (e.g., 'Update API response schema')"),
|
|
393
|
+
description: z.string().describe("Detailed description of what needs to be done"),
|
|
394
|
+
activeForm: z.string().optional().describe("Present continuous form shown while working (e.g., 'Updating API schema')"),
|
|
395
|
+
blocks: z.array(z.string()).optional().describe("Task IDs that this task blocks"),
|
|
396
|
+
blockedBy: z.array(z.string()).optional().describe("Task IDs that must complete before this one"),
|
|
397
|
+
}, async ({ subject, description, activeForm, blocks, blockedBy }) => {
|
|
398
|
+
await ensureConnected();
|
|
399
|
+
return callAPI("/api/tasks", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
body: {
|
|
402
|
+
subject,
|
|
403
|
+
description,
|
|
404
|
+
activeForm: activeForm ?? "",
|
|
405
|
+
blocks: blocks ?? [],
|
|
406
|
+
blockedBy: blockedBy ?? [],
|
|
407
|
+
createdBy: helper.info?.hostname ?? "",
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
server.tool("tailscale_task_list", "List tasks on the team's shared task list. Filter by status to find available, in-progress, or completed tasks.", {
|
|
412
|
+
status: z
|
|
413
|
+
.enum(["pending", "in_progress", "completed", "all"])
|
|
414
|
+
.optional()
|
|
415
|
+
.describe("Filter by task status (default: all)"),
|
|
416
|
+
}, async ({ status }) => {
|
|
417
|
+
const params = status ? `?status=${encodeURIComponent(status)}` : "";
|
|
418
|
+
return callAPI(`/api/tasks${params}`);
|
|
419
|
+
});
|
|
420
|
+
server.tool("tailscale_task_update", "Claim, complete, update, or delete a task on the shared task list.", {
|
|
421
|
+
id: z.string().describe("Task ID (e.g., 'task-1')"),
|
|
422
|
+
action: z
|
|
423
|
+
.enum(["claim", "complete", "update", "delete"])
|
|
424
|
+
.describe("Action: 'claim' to start working on it, 'complete' to mark done, 'update' to modify fields, 'delete' to remove"),
|
|
425
|
+
subject: z.string().optional().describe("New subject (for update action)"),
|
|
426
|
+
description: z.string().optional().describe("New description (for update action)"),
|
|
427
|
+
activeForm: z.string().optional().describe("New activeForm (for update action)"),
|
|
428
|
+
addBlocks: z.array(z.string()).optional().describe("Task IDs to add to blocks list"),
|
|
429
|
+
addBlockedBy: z.array(z.string()).optional().describe("Task IDs to add to blockedBy list"),
|
|
430
|
+
}, async ({ id, action, subject, description, activeForm, addBlocks, addBlockedBy }) => {
|
|
431
|
+
await ensureConnected();
|
|
432
|
+
const encodedId = encodeURIComponent(id);
|
|
433
|
+
switch (action) {
|
|
434
|
+
case "claim":
|
|
435
|
+
return callAPI(`/api/tasks/${encodedId}/claim`, {
|
|
436
|
+
method: "POST",
|
|
437
|
+
body: { owner: helper.info?.hostname ?? "" },
|
|
438
|
+
});
|
|
439
|
+
case "complete":
|
|
440
|
+
return callAPI(`/api/tasks/${encodedId}`, {
|
|
441
|
+
method: "PATCH",
|
|
442
|
+
body: { status: "completed" },
|
|
443
|
+
});
|
|
444
|
+
case "update": {
|
|
445
|
+
const patch = {};
|
|
446
|
+
if (subject !== undefined)
|
|
447
|
+
patch.subject = subject;
|
|
448
|
+
if (description !== undefined)
|
|
449
|
+
patch.description = description;
|
|
450
|
+
if (activeForm !== undefined)
|
|
451
|
+
patch.activeForm = activeForm;
|
|
452
|
+
if (addBlocks !== undefined)
|
|
453
|
+
patch.addBlocks = addBlocks;
|
|
454
|
+
if (addBlockedBy !== undefined)
|
|
455
|
+
patch.addBlockedBy = addBlockedBy;
|
|
456
|
+
return callAPI(`/api/tasks/${encodedId}`, {
|
|
457
|
+
method: "PATCH",
|
|
458
|
+
body: patch,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
case "delete":
|
|
462
|
+
return callAPI(`/api/tasks/${encodedId}`, {
|
|
463
|
+
method: "DELETE",
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
// --- Lifecycle ---
|
|
469
|
+
async function main() {
|
|
470
|
+
// Connection is lazy: starts on first tool invocation via ensureConnected()
|
|
471
|
+
const transport = new StdioServerTransport();
|
|
472
|
+
await server.connect(transport);
|
|
473
|
+
// Graceful shutdown
|
|
474
|
+
const shutdown = async () => {
|
|
475
|
+
await helper.stop();
|
|
476
|
+
process.exit(0);
|
|
477
|
+
};
|
|
478
|
+
process.on("SIGINT", shutdown);
|
|
479
|
+
process.on("SIGTERM", shutdown);
|
|
480
|
+
}
|
|
481
|
+
main().catch((err) => {
|
|
482
|
+
console.error("MCP server failed:", err);
|
|
483
|
+
process.exit(1);
|
|
484
|
+
});
|
|
485
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages the tshelper Go binary lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Spawns tshelper as a child process, reads the JSON startup info from stdout,
|
|
5
|
+
* and provides the SOCKS5 proxy coordinates to the MCP server.
|
|
6
|
+
*/
|
|
7
|
+
export interface TsHelperInfo {
|
|
8
|
+
tailnet_ips: string[];
|
|
9
|
+
hostname: string;
|
|
10
|
+
socks5_addr: string;
|
|
11
|
+
api_addr: string;
|
|
12
|
+
agent_port: number;
|
|
13
|
+
}
|
|
14
|
+
export interface APIResponse {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
data?: unknown;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class TsHelper {
|
|
20
|
+
private proc;
|
|
21
|
+
private _info;
|
|
22
|
+
private _startPromise;
|
|
23
|
+
/** Max retry attempts for tsnet connection (handles rate-limiting, transient failures) */
|
|
24
|
+
private static readonly MAX_RETRIES;
|
|
25
|
+
private static readonly BACKOFF_BASE_MS;
|
|
26
|
+
get info(): TsHelperInfo | null;
|
|
27
|
+
get connected(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Start the tshelper binary and wait for it to connect to the tailnet.
|
|
30
|
+
* Idempotent: returns the same promise if already starting/started.
|
|
31
|
+
*/
|
|
32
|
+
start(): Promise<TsHelperInfo>;
|
|
33
|
+
/**
|
|
34
|
+
* Attempt to start tshelper with exponential backoff retries.
|
|
35
|
+
* Handles transient failures like control plane rate-limiting.
|
|
36
|
+
*/
|
|
37
|
+
private _doStartWithRetry;
|
|
38
|
+
private _doStart;
|
|
39
|
+
/**
|
|
40
|
+
* Call the tshelper HTTP control API.
|
|
41
|
+
*/
|
|
42
|
+
callAPI(path: string, options?: {
|
|
43
|
+
method?: string;
|
|
44
|
+
body?: unknown;
|
|
45
|
+
timeout?: number;
|
|
46
|
+
}): Promise<APIResponse>;
|
|
47
|
+
/**
|
|
48
|
+
* Stop the tshelper process gracefully.
|
|
49
|
+
*/
|
|
50
|
+
stop(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Find the tshelper binary, checking several locations.
|
|
53
|
+
*/
|
|
54
|
+
private findBinary;
|
|
55
|
+
}
|
package/dist/tshelper.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages the tshelper Go binary lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Spawns tshelper as a child process, reads the JSON startup info from stdout,
|
|
5
|
+
* and provides the SOCKS5 proxy coordinates to the MCP server.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { once } from "node:events";
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import http from "node:http";
|
|
14
|
+
export class TsHelper {
|
|
15
|
+
proc = null;
|
|
16
|
+
_info = null;
|
|
17
|
+
_startPromise = null;
|
|
18
|
+
/** Max retry attempts for tsnet connection (handles rate-limiting, transient failures) */
|
|
19
|
+
static MAX_RETRIES = 3;
|
|
20
|
+
static BACKOFF_BASE_MS = 2000;
|
|
21
|
+
get info() {
|
|
22
|
+
return this._info;
|
|
23
|
+
}
|
|
24
|
+
get connected() {
|
|
25
|
+
return this._info !== null && this.proc !== null && this.proc.exitCode === null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start the tshelper binary and wait for it to connect to the tailnet.
|
|
29
|
+
* Idempotent: returns the same promise if already starting/started.
|
|
30
|
+
*/
|
|
31
|
+
async start() {
|
|
32
|
+
if (this.connected) {
|
|
33
|
+
return this._info;
|
|
34
|
+
}
|
|
35
|
+
if (this._startPromise) {
|
|
36
|
+
return this._startPromise;
|
|
37
|
+
}
|
|
38
|
+
this._startPromise = this._doStartWithRetry();
|
|
39
|
+
this._startPromise.catch(() => {
|
|
40
|
+
// Reset so a retry can attempt again
|
|
41
|
+
this._startPromise = null;
|
|
42
|
+
});
|
|
43
|
+
return this._startPromise;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Attempt to start tshelper with exponential backoff retries.
|
|
47
|
+
* Handles transient failures like control plane rate-limiting.
|
|
48
|
+
*/
|
|
49
|
+
async _doStartWithRetry() {
|
|
50
|
+
let lastErr = null;
|
|
51
|
+
for (let attempt = 0; attempt < TsHelper.MAX_RETRIES; attempt++) {
|
|
52
|
+
if (attempt > 0) {
|
|
53
|
+
const delay = TsHelper.BACKOFF_BASE_MS * Math.pow(2, attempt - 1);
|
|
54
|
+
console.error(`[tailscale] retrying tshelper start (attempt ${attempt + 1}/${TsHelper.MAX_RETRIES}) after ${delay}ms...`);
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return await this._doStart();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
lastErr = err;
|
|
62
|
+
// Clean up failed process before retry
|
|
63
|
+
if (this.proc && this.proc.exitCode === null) {
|
|
64
|
+
this.proc.kill("SIGKILL");
|
|
65
|
+
}
|
|
66
|
+
this.proc = null;
|
|
67
|
+
this._info = null;
|
|
68
|
+
// Don't retry config errors (missing binary, missing auth)
|
|
69
|
+
if (err.message.includes("binary not found") ||
|
|
70
|
+
err.message.includes("TS_AUTHKEY or TS_CLIENT_SECRET is required")) {
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
console.error(`[tailscale] tshelper start failed (attempt ${attempt + 1}): ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`tshelper failed after ${TsHelper.MAX_RETRIES} attempts: ${lastErr?.message}`);
|
|
77
|
+
}
|
|
78
|
+
async _doStart() {
|
|
79
|
+
const binPath = this.findBinary();
|
|
80
|
+
if (!binPath) {
|
|
81
|
+
throw new Error("tshelper binary not found. Build it with: cd tshelper && go build -o ../bin/tshelper .");
|
|
82
|
+
}
|
|
83
|
+
if (!process.env.TS_AUTHKEY && !process.env.TS_CLIENT_SECRET) {
|
|
84
|
+
throw new Error("either TS_AUTHKEY or TS_CLIENT_SECRET is required");
|
|
85
|
+
}
|
|
86
|
+
this.proc = spawn(binPath, [], {
|
|
87
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
88
|
+
env: { ...process.env },
|
|
89
|
+
});
|
|
90
|
+
// Forward stderr to parent stderr for diagnostics
|
|
91
|
+
this.proc.stderr?.on("data", (chunk) => {
|
|
92
|
+
process.stderr.write(chunk);
|
|
93
|
+
});
|
|
94
|
+
// Collect stderr for error reporting on failure
|
|
95
|
+
const stderrChunks = [];
|
|
96
|
+
this.proc.stderr?.on("data", (chunk) => {
|
|
97
|
+
stderrChunks.push(chunk.toString());
|
|
98
|
+
});
|
|
99
|
+
// Read the first line of stdout — this is the JSON startup info
|
|
100
|
+
const rl = createInterface({ input: this.proc.stdout });
|
|
101
|
+
const infoPromise = new Promise((resolve, reject) => {
|
|
102
|
+
const timeout = setTimeout(() => {
|
|
103
|
+
reject(new Error(`tshelper timed out waiting for tailnet connection (60s)\nstderr: ${stderrChunks.join("")}`));
|
|
104
|
+
}, 60_000);
|
|
105
|
+
rl.once("line", (line) => {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
try {
|
|
108
|
+
resolve(JSON.parse(line));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
reject(new Error(`tshelper output invalid JSON: ${line}`));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
this.proc.once("exit", (code) => {
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
if (code !== 0 && !this._info) {
|
|
117
|
+
reject(new Error(`tshelper exited with code ${code}\nstderr: ${stderrChunks.join("")}`));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
this._info = await infoPromise;
|
|
122
|
+
return this._info;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Call the tshelper HTTP control API.
|
|
126
|
+
*/
|
|
127
|
+
async callAPI(path, options) {
|
|
128
|
+
if (!this._info) {
|
|
129
|
+
throw new Error("tshelper not connected");
|
|
130
|
+
}
|
|
131
|
+
const method = options?.method ?? "GET";
|
|
132
|
+
const timeout = options?.timeout ?? 30000;
|
|
133
|
+
const url = `http://${this._info.api_addr}${path}`;
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const req = http.request(url, { method, timeout }, (res) => {
|
|
136
|
+
const chunks = [];
|
|
137
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
138
|
+
res.on("end", () => {
|
|
139
|
+
const body = Buffer.concat(chunks).toString();
|
|
140
|
+
try {
|
|
141
|
+
resolve(JSON.parse(body));
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
reject(new Error(`Invalid JSON from API: ${body.slice(0, 200)}`));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
req.on("error", (err) => reject(new Error(`API request failed: ${err.message}`)));
|
|
149
|
+
req.on("timeout", () => {
|
|
150
|
+
req.destroy();
|
|
151
|
+
reject(new Error(`API request timed out after ${timeout}ms`));
|
|
152
|
+
});
|
|
153
|
+
if (options?.body !== undefined) {
|
|
154
|
+
req.setHeader("Content-Type", "application/json");
|
|
155
|
+
req.write(JSON.stringify(options.body));
|
|
156
|
+
}
|
|
157
|
+
req.end();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Stop the tshelper process gracefully.
|
|
162
|
+
*/
|
|
163
|
+
async stop() {
|
|
164
|
+
if (!this.proc || this.proc.exitCode !== null) {
|
|
165
|
+
this.proc = null;
|
|
166
|
+
this._info = null;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.proc.kill("SIGTERM");
|
|
170
|
+
// Wait up to 5 seconds for graceful shutdown
|
|
171
|
+
const exitPromise = once(this.proc, "exit");
|
|
172
|
+
const timeout = setTimeout(() => {
|
|
173
|
+
this.proc?.kill("SIGKILL");
|
|
174
|
+
}, 5000);
|
|
175
|
+
try {
|
|
176
|
+
await exitPromise;
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
clearTimeout(timeout);
|
|
180
|
+
this.proc = null;
|
|
181
|
+
this._info = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Find the tshelper binary, checking several locations.
|
|
186
|
+
*/
|
|
187
|
+
findBinary() {
|
|
188
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
189
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
190
|
+
const candidates = [
|
|
191
|
+
join(__dirname, "..", "bin", `tshelper${ext}`),
|
|
192
|
+
join(__dirname, "..", "tshelper", `tshelper${ext}`),
|
|
193
|
+
`tshelper${ext}`, // in PATH
|
|
194
|
+
];
|
|
195
|
+
for (const p of candidates) {
|
|
196
|
+
if (p === "tshelper" || existsSync(p)) {
|
|
197
|
+
return p;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=tshelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tshelper.js","sourceRoot":"","sources":["../src/tshelper.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-tailscale",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Claude Code plugin that joins a Tailscale tailnet as an ephemeral device using libtailscale",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-code-tailscale": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"build:go": "node scripts/build-go.mjs",
|
|
13
|
+
"build:all": "npm run build:go && npm run build",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"start": "node dist/index.js",
|
|
16
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/",
|
|
20
|
+
"scripts/postinstall.mjs"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
24
|
+
"socks-proxy-agent": "^8.0.5"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"typescript": "^5.7.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"license": "Apache-2.0",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/rosskukulinski/claude-code-tailscale"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"claude-code",
|
|
40
|
+
"tailscale",
|
|
41
|
+
"mcp",
|
|
42
|
+
"ai-agents",
|
|
43
|
+
"libtailscale"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall script: downloads the prebuilt tshelper binary from
|
|
5
|
+
* the GitHub Release matching the package version.
|
|
6
|
+
*
|
|
7
|
+
* Skips the download when:
|
|
8
|
+
* - bin/tshelper already exists (local dev / pre-built)
|
|
9
|
+
* - Running in a CI environment with CI=true
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { get } from "node:https";
|
|
16
|
+
import { createReadStream } from "node:fs";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const root = join(__dirname, "..");
|
|
21
|
+
const binDir = join(root, "bin");
|
|
22
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
23
|
+
const binaryPath = join(binDir, `tshelper${ext}`);
|
|
24
|
+
|
|
25
|
+
// Skip if binary already exists (local dev)
|
|
26
|
+
if (existsSync(binaryPath)) {
|
|
27
|
+
console.log(`[postinstall] tshelper already exists at ${binaryPath}, skipping download`);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Platform mapping: Node → Go naming
|
|
32
|
+
const platformMap = {
|
|
33
|
+
darwin: "darwin",
|
|
34
|
+
linux: "linux",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const archMap = {
|
|
38
|
+
arm64: "arm64",
|
|
39
|
+
x64: "amd64",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const goos = platformMap[process.platform];
|
|
43
|
+
const goarch = archMap[process.arch];
|
|
44
|
+
|
|
45
|
+
if (!goos || !goarch) {
|
|
46
|
+
console.error(
|
|
47
|
+
`[postinstall] Unsupported platform: ${process.platform}/${process.arch}. ` +
|
|
48
|
+
`Supported: darwin/arm64, darwin/x64, linux/arm64, linux/x64. ` +
|
|
49
|
+
`You can build from source: cd tshelper && go build -o ../bin/tshelper .`
|
|
50
|
+
);
|
|
51
|
+
process.exit(0); // exit 0 so npm install doesn't fail
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Read version from package.json
|
|
55
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
|
|
56
|
+
const version = pkg.version;
|
|
57
|
+
const repo = "rosskukulinski/claude-code-tailscale";
|
|
58
|
+
const binaryName = `tshelper-${goos}-${goarch}`;
|
|
59
|
+
const url = `https://github.com/${repo}/releases/download/v${version}/${binaryName}`;
|
|
60
|
+
|
|
61
|
+
console.log(`[postinstall] Downloading tshelper for ${goos}/${goarch} (v${version})...`);
|
|
62
|
+
|
|
63
|
+
mkdirSync(binDir, { recursive: true });
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Follow redirects (GitHub releases redirect to S3/CDN).
|
|
67
|
+
*/
|
|
68
|
+
function download(url, dest, maxRedirects = 5) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
if (maxRedirects <= 0) return reject(new Error("Too many redirects"));
|
|
71
|
+
|
|
72
|
+
get(url, { headers: { "User-Agent": "claude-code-tailscale-postinstall" } }, (res) => {
|
|
73
|
+
// Follow redirects
|
|
74
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
75
|
+
return download(res.headers.location, dest, maxRedirects - 1).then(resolve, reject);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (res.statusCode !== 200) {
|
|
79
|
+
return reject(new Error(`Download failed: HTTP ${res.statusCode} from ${url}`));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const file = createWriteStream(dest);
|
|
83
|
+
res.pipe(file);
|
|
84
|
+
file.on("finish", () => {
|
|
85
|
+
file.close();
|
|
86
|
+
resolve();
|
|
87
|
+
});
|
|
88
|
+
file.on("error", (err) => {
|
|
89
|
+
unlinkSync(dest);
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
}).on("error", reject);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await download(url, binaryPath);
|
|
98
|
+
chmodSync(binaryPath, 0o755);
|
|
99
|
+
console.log(`[postinstall] tshelper installed to ${binaryPath}`);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error(`[postinstall] Failed to download tshelper: ${err.message}`);
|
|
102
|
+
console.error(
|
|
103
|
+
`[postinstall] You can build from source: cd tshelper && go build -o ../bin/tshelper .`
|
|
104
|
+
);
|
|
105
|
+
// Don't fail the install — the user might build from source
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|