tunnel-mcp 0.1.8 → 0.1.9

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/CHANGELOG.md CHANGED
@@ -11,6 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  - Nothing yet.
13
13
 
14
+ ## [0.1.9] - 2026-07-02
15
+
16
+ ### Added
17
+
18
+ - **`tunnel_open` now returns `invite`** — a ready-to-forward plain-text message
19
+ containing the one-time setup command (`claude mcp add tunnel -- npx -y
20
+ tunnel-mcp`) and the join link, so the host's human can paste one message to
21
+ the other developer instead of explaining the install. The tool description
22
+ directs the agent to relay it verbatim.
23
+
14
24
  ## [0.1.8] - 2026-07-02
15
25
 
16
26
  ### Added
@@ -171,7 +181,8 @@ install-skill` copies the `tunnel-etiquette` skill into `~/.claude/skills`
171
181
  declaring a fix "confirmed".
172
182
  - Test suite of 109 tests built with vitest, developed test-first (TDD).
173
183
 
174
- [Unreleased]: https://github.com/zachlikefolio/tunnel-mcp/compare/v0.1.8...HEAD
184
+ [Unreleased]: https://github.com/zachlikefolio/tunnel-mcp/compare/v0.1.9...HEAD
185
+ [0.1.9]: https://github.com/zachlikefolio/tunnel-mcp/compare/v0.1.8...v0.1.9
175
186
  [0.1.8]: https://github.com/zachlikefolio/tunnel-mcp/compare/v0.1.7...v0.1.8
176
187
  [0.1.7]: https://github.com/zachlikefolio/tunnel-mcp/compare/v0.1.6...v0.1.7
177
188
  [0.1.6]: https://github.com/zachlikefolio/tunnel-mcp/compare/v0.1.5...v0.1.6
package/README.md CHANGED
@@ -96,11 +96,12 @@ it isn't already on your `PATH` — there's nothing extra to install.
96
96
 
97
97
  > "Open a tunnel to pair on debugging the checkout flow."
98
98
 
99
- Claude calls `tunnel_open({ goal })` and returns a join link. Share that link
100
- with the other developer over a trusted channel (Slack DM, etc.) — **it's a
101
- secret**, since it contains the encryption key for the session. The link is
102
- **single-use and expires after ~10 minutes** (`tunnel_open` reports
103
- `joinLinkExpiresInSec`), so share it promptly.
99
+ Claude calls `tunnel_open({ goal })` and hands back a ready-to-forward
100
+ **invite** one plain-text message containing the one-time setup command and
101
+ the join link. Paste it to the other developer over a trusted channel (Slack
102
+ DM, etc.) — **the link is a secret**, since it contains the encryption key for
103
+ the session. It is **single-use and expires after ~10 minutes**
104
+ (`tunnel_open` reports `joinLinkExpiresInSec`), so share it promptly.
104
105
 
105
106
  **Guest** — paste the link and ask Claude to join:
106
107
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The ready-to-forward invite a host's human sends to the other developer.
3
+ * Every tunnel needs a second dev with tunnel-mcp installed — so the invite
4
+ * carries the one-time setup command alongside the join link, making each
5
+ * session recruit its own second participant with zero friction.
6
+ */
7
+ export declare function buildInvite(opts: {
8
+ goal: string;
9
+ joinLink: string;
10
+ expiresInSec: number;
11
+ }): string;
package/dist/invite.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The ready-to-forward invite a host's human sends to the other developer.
3
+ * Every tunnel needs a second dev with tunnel-mcp installed — so the invite
4
+ * carries the one-time setup command alongside the join link, making each
5
+ * session recruit its own second participant with zero friction.
6
+ */
7
+ export function buildInvite(opts) {
8
+ const mins = Math.max(1, Math.ceil(opts.expiresInSec / 60));
9
+ return [
10
+ `You're invited to a Claude-agent tunnel — goal: "${opts.goal}"`,
11
+ ``,
12
+ `1) One-time setup (skip if you already have tunnel-mcp):`,
13
+ ` claude mcp add tunnel -- npx -y tunnel-mcp`,
14
+ `2) Then tell your Claude:`,
15
+ ` Join this tunnel: ${opts.joinLink}`,
16
+ ``,
17
+ `The link is single-use and expires in ~${mins} minute${mins === 1 ? '' : 's'}.`,
18
+ ].join('\n');
19
+ }
package/dist/session.d.ts CHANGED
@@ -32,6 +32,7 @@ export declare class TunnelSession {
32
32
  joinLink: string;
33
33
  status: string;
34
34
  joinLinkExpiresInSec: number;
35
+ invite: string;
35
36
  }>;
36
37
  join(joinLink: string, guestName: string): Promise<{
37
38
  tunnelId: string;
package/dist/session.js CHANGED
@@ -7,6 +7,7 @@ import { GuestClient } from './relay/guestClient.js';
7
7
  import { ensureCloudflared as realEnsure } from './cloudflared/provision.js';
8
8
  import { startCloudflared as realStart } from './cloudflared/tunnelProcess.js';
9
9
  import { DEFAULT_LISTEN_TIMEOUT_MS, DEFAULT_IDLE_TEARDOWN_MS, DEFAULT_JOIN_LINK_TTL_MS, OPEN_RETRY_ATTEMPTS, } from './config.js';
10
+ import { buildInvite } from './invite.js';
10
11
  const DEFAULT_DEPS = {
11
12
  ensureCloudflared: realEnsure,
12
13
  startCloudflared: (bin, port) => realStart(bin, port),
@@ -76,11 +77,13 @@ export class TunnelSession {
76
77
  void this.close();
77
78
  });
78
79
  relay.submitLocal(buildSystem('host', `tunnel opened — goal: ${goal}`));
80
+ const joinLinkExpiresInSec = Math.round(joinTtlMs / 1000);
79
81
  return {
80
82
  tunnelId,
81
83
  joinLink,
82
84
  status: 'waiting_for_guest',
83
- joinLinkExpiresInSec: Math.round(joinTtlMs / 1000),
85
+ joinLinkExpiresInSec,
86
+ invite: buildInvite({ goal, joinLink, expiresInSec: joinLinkExpiresInSec }),
84
87
  };
85
88
  }
86
89
  async join(joinLink, guestName) {
package/dist/tools.js CHANGED
@@ -28,7 +28,7 @@ function register(server, name, schema, cb) {
28
28
  }
29
29
  export function registerTools(server, session, opts) {
30
30
  register(server, 'tunnel_open', {
31
- description: 'Open a tunnel as host and get a join link to share. The link is a secret — share it over a trusted channel. It is single-use (works for exactly one guest) and expires (see joinLinkExpiresInSec in the result), so tell the human to share it promptly.',
31
+ description: 'Open a tunnel as host and get a join link to share. The link is a secret — share it over a trusted channel. It is single-use (works for exactly one guest) and expires (see joinLinkExpiresInSec in the result), so tell the human to share it promptly. The result includes `invite`: a ready-to-forward plain-text message with the one-time setup command and the join link — show it to your human verbatim so they can paste it straight to the other developer.',
32
32
  inputSchema: { goal: z.string() },
33
33
  }, async ({ goal }) => ok(await session.open(goal, opts.displayName)));
34
34
  register(server, 'tunnel_join', {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tunnel-mcp",
3
3
  "mcpName": "io.github.zachlikefolio/tunnel-mcp",
4
- "version": "0.1.8",
4
+ "version": "0.1.9",
5
5
  "description": "Let two developers' Claude agents talk directly through an ephemeral, end-to-end-encrypted tunnel.",
6
6
  "type": "module",
7
7
  "bin": {