@toon-protocol/client-mcp 0.26.2
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/LICENSE +190 -0
- package/README.md +261 -0
- package/dist/anon-proxy-6N362VEV-M7AX2QD7.js +24 -0
- package/dist/anon-proxy-6N362VEV-M7AX2QD7.js.map +1 -0
- package/dist/chunk-245J23EB.js +278 -0
- package/dist/chunk-245J23EB.js.map +1 -0
- package/dist/chunk-2SGZPDGE.js +625 -0
- package/dist/chunk-2SGZPDGE.js.map +1 -0
- package/dist/chunk-32QD72IL.js +83 -0
- package/dist/chunk-32QD72IL.js.map +1 -0
- package/dist/chunk-5YIZ2JQO.js +205 -0
- package/dist/chunk-5YIZ2JQO.js.map +1 -0
- package/dist/chunk-LR7W2ISE.js +657 -0
- package/dist/chunk-LR7W2ISE.js.map +1 -0
- package/dist/chunk-QTDCFXPF.js +2802 -0
- package/dist/chunk-QTDCFXPF.js.map +1 -0
- package/dist/chunk-VA7XC4FD.js +185 -0
- package/dist/chunk-VA7XC4FD.js.map +1 -0
- package/dist/chunk-WMYY5I3H.js +10818 -0
- package/dist/chunk-WMYY5I3H.js.map +1 -0
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +137 -0
- package/dist/daemon.js.map +1 -0
- package/dist/ed25519-OFFWPWRE.js +26 -0
- package/dist/ed25519-OFFWPWRE.js.map +1 -0
- package/dist/gateway-QOK47RKS-HB65KIKC.js +15 -0
- package/dist/gateway-QOK47RKS-HB65KIKC.js.map +1 -0
- package/dist/hmac-7WSXTWW4.js +11 -0
- package/dist/hmac-7WSXTWW4.js.map +1 -0
- package/dist/index.d.ts +642 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +80 -0
- package/dist/mcp.js.map +1 -0
- package/dist/sha512-LMOIUNFJ.js +33 -0
- package/dist/sha512-LMOIUNFJ.js.map +1 -0
- package/dist/socks5-WTJBYGME-IXWLQDE7.js +138 -0
- package/dist/socks5-WTJBYGME-IXWLQDE7.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
TOOL_DEFINITIONS,
|
|
5
|
+
dispatchTool
|
|
6
|
+
} from "./chunk-5YIZ2JQO.js";
|
|
7
|
+
import {
|
|
8
|
+
ControlClient,
|
|
9
|
+
defaultConfigPath,
|
|
10
|
+
isDaemonRunning,
|
|
11
|
+
readConfigFile,
|
|
12
|
+
spawnDaemonDetached,
|
|
13
|
+
waitForReady
|
|
14
|
+
} from "./chunk-WMYY5I3H.js";
|
|
15
|
+
import "./chunk-32QD72IL.js";
|
|
16
|
+
import "./chunk-QTDCFXPF.js";
|
|
17
|
+
import "./chunk-LR7W2ISE.js";
|
|
18
|
+
import "./chunk-VA7XC4FD.js";
|
|
19
|
+
import "./chunk-245J23EB.js";
|
|
20
|
+
|
|
21
|
+
// src/mcp.ts
|
|
22
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import {
|
|
25
|
+
CallToolRequestSchema,
|
|
26
|
+
ListToolsRequestSchema
|
|
27
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
28
|
+
function log(msg) {
|
|
29
|
+
console.error(`[toon-mcp] ${msg}`);
|
|
30
|
+
}
|
|
31
|
+
function controlPlaneUrl() {
|
|
32
|
+
const file = readConfigFile(
|
|
33
|
+
process.env["TOON_CLIENT_CONFIG"] ?? defaultConfigPath()
|
|
34
|
+
);
|
|
35
|
+
const port = Number(
|
|
36
|
+
process.env["TOON_CLIENT_HTTP_PORT"] ?? file.httpPort ?? 8787
|
|
37
|
+
);
|
|
38
|
+
return `http://127.0.0.1:${port}`;
|
|
39
|
+
}
|
|
40
|
+
async function ensureDaemon(url) {
|
|
41
|
+
if (isDaemonRunning()) return;
|
|
42
|
+
const client = new ControlClient({ baseUrl: url });
|
|
43
|
+
if (await client.ping()) return;
|
|
44
|
+
log("daemon not running \u2014 spawning detached");
|
|
45
|
+
try {
|
|
46
|
+
const pid = spawnDaemonDetached();
|
|
47
|
+
log(`spawned toon-clientd (pid ${pid}); waiting for control plane`);
|
|
48
|
+
await waitForReady(url, 2e4);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
log(
|
|
51
|
+
`failed to spawn daemon: ${err instanceof Error ? err.message : String(err)}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function main() {
|
|
56
|
+
const url = controlPlaneUrl();
|
|
57
|
+
const control = new ControlClient({ baseUrl: url });
|
|
58
|
+
void ensureDaemon(url);
|
|
59
|
+
const server = new Server(
|
|
60
|
+
{ name: "toon-client", version: "0.1.0" },
|
|
61
|
+
{ capabilities: { tools: {} } }
|
|
62
|
+
);
|
|
63
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
64
|
+
tools: TOOL_DEFINITIONS
|
|
65
|
+
}));
|
|
66
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
67
|
+
const name = request.params.name;
|
|
68
|
+
const args = request.params.arguments ?? {};
|
|
69
|
+
if (!await control.ping()) await ensureDaemon(url);
|
|
70
|
+
return await dispatchTool(control, name, args);
|
|
71
|
+
});
|
|
72
|
+
const transport = new StdioServerTransport();
|
|
73
|
+
await server.connect(transport);
|
|
74
|
+
log(`ready; proxying to ${url}`);
|
|
75
|
+
}
|
|
76
|
+
main().catch((err) => {
|
|
77
|
+
log(err instanceof Error ? err.stack ?? err.message : String(err));
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `toon-mcp` — a thin MCP stdio server exposing the TOON client to a Claude\n * agent (Desktop or Code). It holds NO chain keys and NO long-lived\n * connections: every tool maps to an HTTP call against the always-on\n * `toon-clientd` daemon, which it auto-spawns (detached) if it is not running.\n *\n * Works on both surfaces:\n * • Claude Desktop — `claude_desktop_config.json` mcpServers entry.\n * • Claude Code — `claude mcp add toon -- toon-mcp` (or `.mcp.json`).\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { ControlClient } from './control-client.js';\nimport { dispatchTool, TOOL_DEFINITIONS } from './mcp-tools.js';\nimport { defaultConfigPath, readConfigFile } from './daemon/config.js';\nimport {\n isDaemonRunning,\n spawnDaemonDetached,\n waitForReady,\n} from './daemon/lifecycle.js';\n\n/** stdout carries the MCP protocol — all logging must go to stderr. */\nfunction log(msg: string): void {\n console.error(`[toon-mcp] ${msg}`);\n}\n\n/** Resolve the daemon control-plane URL without needing the mnemonic. */\nfunction controlPlaneUrl(): string {\n const file = readConfigFile(\n process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath()\n );\n const port = Number(\n process.env['TOON_CLIENT_HTTP_PORT'] ?? file.httpPort ?? 8787\n );\n return `http://127.0.0.1:${port}`;\n}\n\n/**\n * Make sure the daemon is up: if the lock shows it running, return; otherwise\n * spawn it detached and wait until the control plane is reachable. Best-effort\n * — failures surface as readable tool errors rather than crashing the server.\n */\nasync function ensureDaemon(url: string): Promise<void> {\n if (isDaemonRunning()) return;\n const client = new ControlClient({ baseUrl: url });\n if (await client.ping()) return;\n log('daemon not running — spawning detached');\n try {\n const pid = spawnDaemonDetached();\n log(`spawned toon-clientd (pid ${pid}); waiting for control plane`);\n await waitForReady(url, 20_000);\n } catch (err) {\n log(\n `failed to spawn daemon: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\nasync function main(): Promise<void> {\n const url = controlPlaneUrl();\n const control = new ControlClient({ baseUrl: url });\n\n // Kick off daemon startup; don't block server init on it (anon bootstrap is\n // slow). Tools report \"bootstrapping — retry\" until it is ready.\n void ensureDaemon(url);\n\n const server = new Server(\n { name: 'toon-client', version: '0.1.0' },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOL_DEFINITIONS,\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n const args = (request.params.arguments ?? {}) as Record<string, unknown>;\n // If the daemon went away, try once to bring it back before dispatching.\n if (!(await control.ping())) await ensureDaemon(url);\n // Our ToolResult is a structural subset of CallToolResult (content + isError);\n // the SDK's handler union also carries a task-augmented variant we never use.\n return (await dispatchTool(control, name, args)) as CallToolResult;\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n log(`ready; proxying to ${url}`);\n}\n\nmain().catch((err) => {\n log(err instanceof Error ? (err.stack ?? err.message) : String(err));\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAYA,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAWP,SAAS,IAAI,KAAmB;AAC9B,UAAQ,MAAM,cAAc,GAAG,EAAE;AACnC;AAGA,SAAS,kBAA0B;AACjC,QAAM,OAAO;AAAA,IACX,QAAQ,IAAI,oBAAoB,KAAK,kBAAkB;AAAA,EACzD;AACA,QAAM,OAAO;AAAA,IACX,QAAQ,IAAI,uBAAuB,KAAK,KAAK,YAAY;AAAA,EAC3D;AACA,SAAO,oBAAoB,IAAI;AACjC;AAOA,eAAe,aAAa,KAA4B;AACtD,MAAI,gBAAgB,EAAG;AACvB,QAAM,SAAS,IAAI,cAAc,EAAE,SAAS,IAAI,CAAC;AACjD,MAAI,MAAM,OAAO,KAAK,EAAG;AACzB,MAAI,6CAAwC;AAC5C,MAAI;AACF,UAAM,MAAM,oBAAoB;AAChC,QAAI,6BAA6B,GAAG,8BAA8B;AAClE,UAAM,aAAa,KAAK,GAAM;AAAA,EAChC,SAAS,KAAK;AACZ;AAAA,MACE,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC7E;AAAA,EACF;AACF;AAEA,eAAe,OAAsB;AACnC,QAAM,MAAM,gBAAgB;AAC5B,QAAM,UAAU,IAAI,cAAc,EAAE,SAAS,IAAI,CAAC;AAIlD,OAAK,aAAa,GAAG;AAErB,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,eAAe,SAAS,QAAQ;AAAA,IACxC,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,EAChC;AAEA,SAAO,kBAAkB,wBAAwB,aAAa;AAAA,IAC5D,OAAO;AAAA,EACT,EAAE;AAEF,SAAO,kBAAkB,uBAAuB,OAAO,YAAY;AACjE,UAAM,OAAO,QAAQ,OAAO;AAC5B,UAAM,OAAQ,QAAQ,OAAO,aAAa,CAAC;AAE3C,QAAI,CAAE,MAAM,QAAQ,KAAK,EAAI,OAAM,aAAa,GAAG;AAGnD,WAAQ,MAAM,aAAa,SAAS,MAAM,IAAI;AAAA,EAChD,CAAC;AAED,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,sBAAsB,GAAG,EAAE;AACjC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,MAAI,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW,OAAO,GAAG,CAAC;AACnE,UAAQ,WAAW;AACrB,CAAC;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
SHA384,
|
|
4
|
+
SHA512,
|
|
5
|
+
SHA512_224,
|
|
6
|
+
SHA512_256,
|
|
7
|
+
sha384,
|
|
8
|
+
sha512,
|
|
9
|
+
sha512_224,
|
|
10
|
+
sha512_256
|
|
11
|
+
} from "./chunk-LR7W2ISE.js";
|
|
12
|
+
import "./chunk-VA7XC4FD.js";
|
|
13
|
+
|
|
14
|
+
// ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/sha512.js
|
|
15
|
+
var SHA5122 = SHA512;
|
|
16
|
+
var sha5122 = sha512;
|
|
17
|
+
var SHA3842 = SHA384;
|
|
18
|
+
var sha3842 = sha384;
|
|
19
|
+
var SHA512_2242 = SHA512_224;
|
|
20
|
+
var sha512_2242 = sha512_224;
|
|
21
|
+
var SHA512_2562 = SHA512_256;
|
|
22
|
+
var sha512_2562 = sha512_256;
|
|
23
|
+
export {
|
|
24
|
+
SHA3842 as SHA384,
|
|
25
|
+
SHA5122 as SHA512,
|
|
26
|
+
SHA512_2242 as SHA512_224,
|
|
27
|
+
SHA512_2562 as SHA512_256,
|
|
28
|
+
sha3842 as sha384,
|
|
29
|
+
sha5122 as sha512,
|
|
30
|
+
sha512_2242 as sha512_224,
|
|
31
|
+
sha512_2562 as sha512_256
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=sha512-LMOIUNFJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/src/sha512.ts"],"sourcesContent":["/**\n * SHA2-512 a.k.a. sha512 and sha384. It is slower than sha256 in js because u64 operations are slow.\n *\n * Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and\n * [the paper on truncated SHA512/256](https://eprint.iacr.org/2010/548.pdf).\n * @module\n * @deprecated\n */\nimport {\n SHA384 as SHA384n,\n sha384 as sha384n,\n sha512_224 as sha512_224n,\n SHA512_224 as SHA512_224n,\n sha512_256 as sha512_256n,\n SHA512_256 as SHA512_256n,\n SHA512 as SHA512n,\n sha512 as sha512n,\n} from './sha2.ts';\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const SHA512: typeof SHA512n = SHA512n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const sha512: typeof sha512n = sha512n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const SHA384: typeof SHA384n = SHA384n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const sha384: typeof sha384n = sha384n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const SHA512_224: typeof SHA512_224n = SHA512_224n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const sha512_224: typeof sha512_224n = sha512_224n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const SHA512_256: typeof SHA512_256n = SHA512_256n;\n/** @deprecated Use import from `noble/hashes/sha2` module */\nexport const sha512_256: typeof sha512_256n = sha512_256n;\n"],"mappings":";;;;;;;;;;;;;;AAmBO,IAAMA,UAAyB;AAE/B,IAAMC,UAAyB;AAE/B,IAAMC,UAAyB;AAE/B,IAAMC,UAAyB;AAE/B,IAAMC,cAAiC;AAEvC,IAAMC,cAAiC;AAEvC,IAAMC,cAAiC;AAEvC,IAAMC,cAAiC;","names":["SHA512","sha512","SHA384","sha384","SHA512_224","sha512_224","SHA512_256","sha512_256"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../client/dist/socks5-WTJBYGME.js
|
|
4
|
+
import { createConnection } from "net";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
var require2 = createRequire(import.meta.url);
|
|
7
|
+
function validateSocks5hUrl(socksProxy) {
|
|
8
|
+
if (!socksProxy.startsWith("socks5h://")) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`SOCKS5 proxy URL must use socks5h:// scheme (got "${socksProxy.split("://")[0]}://"). The "h" suffix ensures DNS resolution happens at the proxy, preventing leaks of .anyone hostnames.`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
const httpUrl = socksProxy.replace(/^socks5h:\/\//, "http://");
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = new URL(httpUrl);
|
|
17
|
+
} catch {
|
|
18
|
+
throw new Error(`Malformed SOCKS5 proxy URL: "${socksProxy}"`);
|
|
19
|
+
}
|
|
20
|
+
const host = parsed.hostname;
|
|
21
|
+
const port = parsed.port ? parseInt(parsed.port, 10) : 1080;
|
|
22
|
+
if (!host) {
|
|
23
|
+
throw new Error(`SOCKS5 proxy URL missing host: "${socksProxy}"`);
|
|
24
|
+
}
|
|
25
|
+
if (port < 0 || port > 65535 || !Number.isFinite(port)) {
|
|
26
|
+
throw new Error(`SOCKS5 proxy port out of range (0\u201365535): ${parsed.port}`);
|
|
27
|
+
}
|
|
28
|
+
return { host, port };
|
|
29
|
+
}
|
|
30
|
+
function createSocks5WebSocketFactory(socksProxy) {
|
|
31
|
+
validateSocks5hUrl(socksProxy);
|
|
32
|
+
const { SocksProxyAgent } = require2("socks-proxy-agent");
|
|
33
|
+
const WS = require2("ws");
|
|
34
|
+
const agent = new SocksProxyAgent(socksProxy, { timeout: 12e4 });
|
|
35
|
+
const ws = WS;
|
|
36
|
+
const WSClass = typeof ws === "function" ? ws : typeof ws.default === "function" ? ws.default : typeof ws.WebSocket === "function" ? ws.WebSocket : null;
|
|
37
|
+
if (WSClass === null) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"createSocks5WebSocketFactory: require('ws') did not yield a constructor on .default, .WebSocket, or the module root."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return (url) => (
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
new WSClass(url, { agent })
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function createSocks5Fetch(socksProxy) {
|
|
48
|
+
validateSocks5hUrl(socksProxy);
|
|
49
|
+
const { SocksProxyAgent } = require2("socks-proxy-agent");
|
|
50
|
+
const http = require2("node:http");
|
|
51
|
+
const https = require2("node:https");
|
|
52
|
+
const agent = new SocksProxyAgent(socksProxy);
|
|
53
|
+
return (input, init) => {
|
|
54
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
55
|
+
const parsedUrl = new URL(url);
|
|
56
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
57
|
+
const transport = isHttps ? https : http;
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const method = init?.method ?? "GET";
|
|
60
|
+
const headers = init?.headers ? Object.fromEntries(
|
|
61
|
+
init.headers instanceof Headers ? init.headers.entries() : Array.isArray(init.headers) ? init.headers : Object.entries(init.headers)
|
|
62
|
+
) : {};
|
|
63
|
+
const req = transport.request(
|
|
64
|
+
url,
|
|
65
|
+
{ method, headers, agent, timeout: 3e4 },
|
|
66
|
+
(res) => {
|
|
67
|
+
const chunks = [];
|
|
68
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
69
|
+
res.on("end", () => {
|
|
70
|
+
const body = Buffer.concat(chunks);
|
|
71
|
+
const responseHeaders = new Headers();
|
|
72
|
+
for (const [key, val] of Object.entries(res.headers)) {
|
|
73
|
+
if (val)
|
|
74
|
+
responseHeaders.set(
|
|
75
|
+
key,
|
|
76
|
+
Array.isArray(val) ? val.join(", ") : val
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
resolve(
|
|
80
|
+
new Response(body, {
|
|
81
|
+
status: res.statusCode ?? 200,
|
|
82
|
+
statusText: res.statusMessage ?? "",
|
|
83
|
+
headers: responseHeaders
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
req.on("error", reject);
|
|
90
|
+
req.on("timeout", () => {
|
|
91
|
+
req.destroy();
|
|
92
|
+
reject(new Error("SOCKS5 proxied request timeout"));
|
|
93
|
+
});
|
|
94
|
+
if (init?.signal) {
|
|
95
|
+
init.signal.addEventListener("abort", () => {
|
|
96
|
+
req.destroy();
|
|
97
|
+
reject(new Error("Aborted"));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (init?.body) {
|
|
101
|
+
req.write(typeof init.body === "string" ? init.body : init.body);
|
|
102
|
+
}
|
|
103
|
+
req.end();
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function probeSocks5Proxy(socksProxy, timeoutMs = 2e3) {
|
|
108
|
+
const { host, port } = validateSocks5hUrl(socksProxy);
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const socket = createConnection({ host, port }, () => {
|
|
111
|
+
socket.destroy();
|
|
112
|
+
resolve();
|
|
113
|
+
});
|
|
114
|
+
socket.setTimeout(timeoutMs, () => {
|
|
115
|
+
socket.destroy();
|
|
116
|
+
reject(
|
|
117
|
+
new Error(
|
|
118
|
+
`SOCKS5 proxy at ${host}:${port} unreachable (timeout ${timeoutMs}ms). Refusing to start without privacy transport (fail-closed).`
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
socket.on("error", (err) => {
|
|
123
|
+
socket.destroy();
|
|
124
|
+
reject(
|
|
125
|
+
new Error(
|
|
126
|
+
`SOCKS5 proxy at ${host}:${port} unreachable: ${err.message}. Refusing to start without privacy transport (fail-closed).`
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
createSocks5Fetch,
|
|
134
|
+
createSocks5WebSocketFactory,
|
|
135
|
+
probeSocks5Proxy,
|
|
136
|
+
validateSocks5hUrl
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=socks5-WTJBYGME-IXWLQDE7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../client/src/transport/socks5.ts"],"sourcesContent":["/**\n * SOCKS5 transport helpers for Node.js environments.\n *\n * This module is dynamically imported only when `transport.type === 'socks5'`\n * is configured, keeping `ws` and `socks-proxy-agent` out of browser bundles.\n */\n\nimport { createConnection } from 'node:net';\nimport { createRequire } from 'node:module';\nimport type SocksProxyAgentModule from 'socks-proxy-agent';\nimport type WSModule from 'ws';\nimport type httpModule from 'node:http';\nimport type httpsModule from 'node:https';\n\n// ESM-safe require. This module builds as ESM (tsup `format: ['esm']`) with\n// `socks-proxy-agent`/`ws` external, so a bare `require(...)` would be rewritten\n// by esbuild into a `__require` shim that throws\n// `Dynamic require of \"socks-proxy-agent\" is not supported` for any pure-ESM\n// consumer — breaking the SOCKS5/ATOR transport entirely (the npm-consumer\n// toon-client image surfaced this). Building a real require off `import.meta.url`\n// (the same pattern as docker/esbuild.config.mjs's banner) keeps the\n// synchronous, browser-guarded `require(...)` calls below working in the\n// published ESM bundle while leaving the deps external. Browser bundlers never\n// reach this code: the module is dynamically imported only when\n// `transport.type === 'socks5'`, which is Node-only.\nconst require = createRequire(import.meta.url);\n\n/**\n * Parses and validates a `socks5h://` URL.\n * Enforces `socks5h://` scheme (not `socks5://`) to prevent DNS leaks —\n * `.anyone` hostnames must be resolved by the proxy, not locally.\n *\n * Mirrors the connector's `transport/socks-url.ts` validation logic.\n */\nexport function validateSocks5hUrl(socksProxy: string): {\n host: string;\n port: number;\n} {\n if (!socksProxy.startsWith('socks5h://')) {\n throw new Error(\n `SOCKS5 proxy URL must use socks5h:// scheme (got \"${socksProxy.split('://')[0]}://\"). ` +\n 'The \"h\" suffix ensures DNS resolution happens at the proxy, preventing leaks of .anyone hostnames.'\n );\n }\n\n // Parse by converting to http:// for URL constructor compatibility\n const httpUrl = socksProxy.replace(/^socks5h:\\/\\//, 'http://');\n let parsed: URL;\n try {\n parsed = new URL(httpUrl);\n } catch {\n throw new Error(`Malformed SOCKS5 proxy URL: \"${socksProxy}\"`);\n }\n\n const host = parsed.hostname;\n const port = parsed.port ? parseInt(parsed.port, 10) : 1080;\n\n if (!host) {\n throw new Error(`SOCKS5 proxy URL missing host: \"${socksProxy}\"`);\n }\n if (port < 0 || port > 65535 || !Number.isFinite(port)) {\n throw new Error(`SOCKS5 proxy port out of range (0–65535): ${parsed.port}`);\n }\n\n return { host, port };\n}\n\n/**\n * Creates a WebSocket factory that routes connections through a SOCKS5 proxy.\n * Uses the `ws` npm package (Node.js only) which accepts an `agent` option.\n */\nexport function createSocks5WebSocketFactory(\n socksProxy: string\n): (url: string) => WebSocket {\n validateSocks5hUrl(socksProxy);\n\n // Resolved via the module-scoped ESM-safe `require` (createRequire) above.\n const { SocksProxyAgent } =\n require('socks-proxy-agent') as typeof SocksProxyAgentModule;\n const WS = require('ws') as typeof WSModule;\n\n // 120s timeout: the socks library's default is 30s, which is too short for\n // the ATOR network to build circuits to fresh hidden services from certain\n // network paths (e.g., Akash datacenter → public ATOR proxy → local HS).\n // 120s gives the proxy time to find a working introduction-point circuit.\n const agent = new SocksProxyAgent(socksProxy, { timeout: 120_000 });\n\n // CJS/ESM interop: `require('ws')` can return any of three shapes depending on\n // the bundler/loader: the class directly (pure CJS); `{ default: WSClass, ... }`\n // (esModuleInterop=true / synthetic default); or `{ WebSocket: WSClass, ... }`\n // (named export, no default). Walk the ladder so this factory works in all\n // three environments. Pass 2 code review 2026-05-18: previous `(WS as any).default ?? WS`\n // would accept a namespace object as a \"constructor\" and throw cryptically.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const ws = WS as any;\n const WSClass = (typeof ws === 'function'\n ? ws\n : typeof ws.default === 'function'\n ? ws.default\n : typeof ws.WebSocket === 'function'\n ? ws.WebSocket\n : null) as unknown as typeof WSModule.prototype.constructor;\n if (WSClass === null) {\n throw new Error(\n \"createSocks5WebSocketFactory: require('ws') did not yield a constructor on .default, .WebSocket, or the module root.\"\n );\n }\n\n return (url: string) =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n new (WSClass as any)(url, { agent }) as unknown as WebSocket;\n}\n\n/**\n * Creates a fetch wrapper that routes HTTP requests through a SOCKS5 proxy.\n * Uses `socks-proxy-agent` with Node.js `http`/`https` modules (not native\n * fetch, which uses undici and doesn't support SocksProxyAgent's dispatcher).\n */\nexport function createSocks5Fetch(socksProxy: string): typeof fetch {\n validateSocks5hUrl(socksProxy);\n\n // Resolved via the module-scoped ESM-safe `require` (createRequire) above.\n const { SocksProxyAgent } =\n require('socks-proxy-agent') as typeof SocksProxyAgentModule;\n const http = require('node:http') as typeof httpModule;\n const https = require('node:https') as typeof httpsModule;\n\n const agent = new SocksProxyAgent(socksProxy);\n\n return (input: string | URL | Request, init?: RequestInit) => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.href\n : input.url;\n const parsedUrl = new URL(url);\n const isHttps = parsedUrl.protocol === 'https:';\n const transport = isHttps ? https : http;\n\n return new Promise<Response>((resolve, reject) => {\n const method = init?.method ?? 'GET';\n const headers = init?.headers\n ? Object.fromEntries(\n init.headers instanceof Headers\n ? init.headers.entries()\n : Array.isArray(init.headers)\n ? init.headers\n : Object.entries(init.headers)\n )\n : {};\n\n const req = transport.request(\n url,\n { method, headers, agent, timeout: 30_000 },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n const body = Buffer.concat(chunks);\n const responseHeaders = new Headers();\n for (const [key, val] of Object.entries(res.headers)) {\n if (val)\n responseHeaders.set(\n key,\n Array.isArray(val) ? val.join(', ') : val\n );\n }\n resolve(\n new Response(body, {\n status: res.statusCode ?? 200,\n statusText: res.statusMessage ?? '',\n headers: responseHeaders,\n })\n );\n });\n }\n );\n\n req.on('error', reject);\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('SOCKS5 proxied request timeout'));\n });\n\n if (init?.signal) {\n init.signal.addEventListener('abort', () => {\n req.destroy();\n reject(new Error('Aborted'));\n });\n }\n\n if (init?.body) {\n req.write(typeof init.body === 'string' ? init.body : init.body);\n }\n req.end();\n });\n };\n}\n\n/**\n * Probes SOCKS5 proxy reachability with a TCP connection test.\n * Fail-closed: throws if the proxy is unreachable within the timeout.\n *\n * @param socksProxy - `socks5h://host:port` URL\n * @param timeoutMs - Connection timeout in milliseconds (default: 2000)\n */\nexport async function probeSocks5Proxy(\n socksProxy: string,\n timeoutMs = 2000\n): Promise<void> {\n const { host, port } = validateSocks5hUrl(socksProxy);\n\n return new Promise<void>((resolve, reject) => {\n const socket = createConnection({ host, port }, () => {\n socket.destroy();\n resolve();\n });\n\n socket.setTimeout(timeoutMs, () => {\n socket.destroy();\n reject(\n new Error(\n `SOCKS5 proxy at ${host}:${port} unreachable (timeout ${timeoutMs}ms). ` +\n 'Refusing to start without privacy transport (fail-closed).'\n )\n );\n });\n\n socket.on('error', (err) => {\n socket.destroy();\n reject(\n new Error(\n `SOCKS5 proxy at ${host}:${port} unreachable: ${err.message}. ` +\n 'Refusing to start without privacy transport (fail-closed).'\n )\n );\n });\n });\n}\n"],"mappings":"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","names":["require"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toon-protocol/client-mcp",
|
|
3
|
+
"version": "0.26.2",
|
|
4
|
+
"description": "Always-on local daemon + MCP server letting a Claude agent (Desktop or Code) act as a TOON Protocol client: pay-to-write publishing, free reads, channel/balance management, and mill swaps.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Jonathan Green",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"toon-clientd": "./dist/daemon.js",
|
|
12
|
+
"toon-mcp": "./dist/mcp.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
29
|
+
"@toon-format/toon": "^1.4.0",
|
|
30
|
+
"fastify": "^5.0.0",
|
|
31
|
+
"nostr-tools": "^2.20.0",
|
|
32
|
+
"viem": "^2.0.0",
|
|
33
|
+
"ws": "^8.18.0"
|
|
34
|
+
},
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"@solana/web3.js": "^1.90.0",
|
|
37
|
+
"mina-signer": "^3.0.0",
|
|
38
|
+
"o1js": "2.14.0",
|
|
39
|
+
"socks-proxy-agent": "^8.0.5"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"@types/ws": "^8.5.10",
|
|
44
|
+
"tsup": "^8.0.0",
|
|
45
|
+
"typescript": "^5.3.0",
|
|
46
|
+
"vitest": "^1.0.0",
|
|
47
|
+
"@toon-protocol/core": "1.4.1",
|
|
48
|
+
"@toon-protocol/client": "0.9.1"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
57
|
+
"test:coverage": "vitest run --coverage"
|
|
58
|
+
}
|
|
59
|
+
}
|