From f109ca4591c9a90ec324cf37ac27a9a2b6abc4c9 Mon Sep 17 00:00:00 2001 From: ProgrammerIn-wonderland Date: Tue, 2 Jun 2026 20:51:21 -0400 Subject: [PATCH] beginnings of mcp --- package-lock.json | 1481 +++++++++++++++++ src/mcp-connector/.gitignore | 5 + src/mcp-connector/README.md | 181 ++ src/mcp-connector/mcpb/manifest.json | 63 + src/mcp-connector/mcpb/server/index.cjs | 132 ++ src/mcp-connector/package.json | 22 + src/mcp-connector/scripts/buildPreamble.mjs | 56 + src/mcp-connector/src/index.js | 6 + src/mcp-connector/src/mcp.js | 191 +++ src/mcp-connector/src/s2w-router.js | 218 +++ src/mcp-connector/src/tools.js | 278 ++++ .../template/puter-portable.template | 51 + src/mcp-connector/webpack.config.cjs | 62 + src/mcp-connector/wrangler.toml | 18 + 14 files changed, 2764 insertions(+) create mode 100644 src/mcp-connector/.gitignore create mode 100644 src/mcp-connector/README.md create mode 100644 src/mcp-connector/mcpb/manifest.json create mode 100644 src/mcp-connector/mcpb/server/index.cjs create mode 100644 src/mcp-connector/package.json create mode 100644 src/mcp-connector/scripts/buildPreamble.mjs create mode 100644 src/mcp-connector/src/index.js create mode 100644 src/mcp-connector/src/mcp.js create mode 100644 src/mcp-connector/src/s2w-router.js create mode 100644 src/mcp-connector/src/tools.js create mode 100644 src/mcp-connector/template/puter-portable.template create mode 100644 src/mcp-connector/webpack.config.cjs create mode 100644 src/mcp-connector/wrangler.toml diff --git a/package-lock.json b/package-lock.json index 4c3ad30b3..bb5ec3f93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1661,6 +1661,157 @@ "specificity": "bin/cli.js" } }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", + "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/@cloudflare/kv-asset-handler/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", + "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.14", + "workerd": "^1.20250124.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz", + "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz", + "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz", + "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz", + "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz", + "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/color-helpers": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", @@ -1844,6 +1995,30 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "dev": true, + "license": "ISC", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", @@ -2576,6 +2751,16 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@fastify/cors": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.2.0.tgz", @@ -8372,6 +8557,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -8566,6 +8761,16 @@ "node": ">=8" } }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "printable-characters": "^1.0.42" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -8837,6 +9042,13 @@ "ieee754": "^1.1.13" } }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -9331,6 +9543,21 @@ "node": ">=0.10.0" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -9349,6 +9576,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -9859,6 +10098,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10668,6 +10914,19 @@ "node": ">=18.0.0" } }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -10772,6 +11031,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -11600,6 +11866,24 @@ "node": ">= 0.4" } }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/get-source/node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true, + "license": "MIT" + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -12412,6 +12696,14 @@ "node": ">= 10" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -14028,6 +14320,90 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/miniflare": { + "version": "3.20250718.3", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz", + "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "stoppable": "1.1.0", + "undici": "^5.28.5", + "workerd": "1.20250718.0", + "ws": "8.18.0", + "youch": "3.3.4", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/miniflare/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/miniflare/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/miniflare/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/miniflare/node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -14205,6 +14581,16 @@ } } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mysql2": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.3.tgz", @@ -14566,6 +14952,13 @@ ], "license": "MIT" }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -15360,6 +15753,13 @@ "renderkid": "^3.0.0" } }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true, + "license": "Unlicense" + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -15497,6 +15897,10 @@ "node": ">=6" } }, + "node_modules/puter-mcp-connector": { + "resolved": "src/mcp-connector", + "link": true + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -15925,6 +16329,63 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" } }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-inject/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup-plugin-inject/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true, + "license": "MIT" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -16550,6 +17011,17 @@ } } }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -16782,6 +17254,14 @@ "source-map": "^0.6.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -16825,6 +17305,17 @@ "dev": true, "license": "MIT" }, + "node_modules/stacktracey": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.2.0.tgz", + "integrity": "sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -16847,6 +17338,17 @@ "dev": true, "license": "MIT" }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -17481,6 +17983,13 @@ "node": "*" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -17528,6 +18037,20 @@ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, + "node_modules/unenv": { + "version": "2.0.0-rc.14", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", + "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.1", + "ohash": "^2.0.10", + "pathe": "^2.0.3", + "ufo": "^1.5.4" + } + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -18175,6 +18698,941 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "license": "MIT" }, + "node_modules/workerd": { + "version": "1.20250718.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz", + "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250718.0", + "@cloudflare/workerd-darwin-arm64": "1.20250718.0", + "@cloudflare/workerd-linux-64": "1.20250718.0", + "@cloudflare/workerd-linux-arm64": "1.20250718.0", + "@cloudflare/workerd-windows-64": "1.20250718.0" + } + }, + "node_modules/wrangler": { + "version": "3.114.17", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.17.tgz", + "integrity": "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.3.4", + "@cloudflare/unenv-preset": "2.0.2", + "@esbuild-plugins/node-globals-polyfill": "0.2.3", + "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.17.19", + "miniflare": "3.20250718.3", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.14", + "workerd": "1.20250718.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=16.17.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250408.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/wrangler/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/wrangler/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrangler/node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -18329,6 +19787,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/youch": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", + "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^0.7.1", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", @@ -19371,6 +20841,17 @@ "webpack-cli": "^5.1.1" } }, + "src/mcp-connector": { + "name": "puter-mcp-connector", + "version": "0.1.0", + "license": "AGPL-3.0-only", + "devDependencies": { + "terser-webpack-plugin": "^5.3.14", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.1", + "wrangler": "^3.90.0" + } + }, "src/puter-js": { "name": "@heyputer/puter.js", "version": "2.5.0", diff --git a/src/mcp-connector/.gitignore b/src/mcp-connector/.gitignore new file mode 100644 index 000000000..abe588781 --- /dev/null +++ b/src/mcp-connector/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.wrangler/ +dist/ +.dev.vars +*.log diff --git a/src/mcp-connector/README.md b/src/mcp-connector/README.md new file mode 100644 index 000000000..17e2f988e --- /dev/null +++ b/src/mcp-connector/README.md @@ -0,0 +1,181 @@ +# Puter MCP Connector (Cloudflare Workers) + +An [MCP](https://modelcontextprotocol.io) server that runs on Cloudflare Workers +and lets **anyone use their own Puter auth token** to drive their Puter account's +filesystem and subdomains from an MCP client (Claude, Cursor, etc.). + +The Worker stores **no credentials of its own**. Every request carries the +caller's token via a standard `Authorization: Bearer ` header, and all +operations run as that user. + +## Tools + +### Filesystem +| Tool | Description | +| --- | --- | +| `fs_read_file` | Read a file (UTF-8 or base64; optional offset/length). | +| `fs_stat` | Stat a file or directory (size, type, timestamps, uid). | +| `fs_write_file` | Create/overwrite a file (UTF-8 or base64 content). | +| `fs_mkdir` | Create a directory (optionally creating missing parents). | +| `fs_delete` | Delete a file or directory (recursive by default). | +| `fs_readdir` | List the entries of a directory. | + +### Subdomains +| Tool | Description | +| --- | --- | +| `subdomains_list` | List the caller's subdomains. | +| `subdomains_get` | Get a subdomain by name. | +| `subdomains_create` | Create a subdomain (optionally pointing at a `root_dir`). | +| `subdomains_update` | Update a subdomain's `root_dir`. | +| `subdomains_delete` | Delete a subdomain. | + +### Paths +Tools pass paths straight to puter.js, so the usual conventions apply: absolute +(`/your-username/Desktop/file.txt`), home-relative (`~/Desktop/file.txt`), or +relative (`Desktop/file.txt`, resolved against your home directory). + +## How it works + +This is a **fork of [`src/worker`](../worker)** — Puter's port of puter.js to a +Cloudflare Worker runtime — with two changes: + +1. **No `me.puter`.** The original worker creates a worker-owned puter instance + from `globalThis.puter_auth`. That's removed: this connector holds no + credentials of its own. +2. **`user.puter` from the `Authorization` header.** The per-request puter + instance is built from `Authorization: Bearer ` instead of the + original `puter-auth` header. + +Because it's the real port, the tools call genuine `puter.fs.*` and +`puter.hosting.*` methods on a real puter.js instance (created via +`init_puter_portable(token, origin, 'userPuter')`, which runs puter.js inside an +isolated `with` context so concurrent requests don't share auth/cache state). + +The MCP transport is **Streamable HTTP**: a single endpoint accepting JSON-RPC +2.0 over `POST` (single message or batch array). The server is stateless. The +MCP layer ([`src/mcp.js`](src/mcp.js)) is a small hand-rolled JSON-RPC dispatcher +registered onto the forked router as routes. + +### Build pipeline (same as `src/worker`) + +`npm run build` does two steps: + +1. **webpack** bundles [`src/index.js`](src/index.js) (the router + + [`src/mcp.js`](src/mcp.js) + [`src/tools.js`](src/tools.js)) into + `dist/webpackPreamplePart.js`. +2. **`scripts/buildPreamble.mjs`** inlines `#include`s in + [`template/puter-portable.template`](template/puter-portable.template) — + pulling in `../puter-js/dist/puter.js` and the webpack bundle — to produce + `dist/workerPreamble.js`, the deployable **service-worker-format** script. + +> Requires `src/puter-js/dist/puter.js` to exist. Build puter.js first if needed +> (`cd ../puter-js && npm run build`). + +## Files + +| Path | Role | +| --- | --- | +| [`src/s2w-router.js`](src/s2w-router.js) | Forked router: builds `event.user.puter` from the bearer token (no `me.puter`). | +| [`src/index.js`](src/index.js) | Entry: `initS2w()` + registers MCP routes. | +| [`src/mcp.js`](src/mcp.js) | MCP JSON-RPC dispatch (initialize / tools.list / tools.call). | +| [`src/tools.js`](src/tools.js) | The 12 tools, calling real `puter.fs.*` / `puter.hosting.*`. | +| [`template/puter-portable.template`](template/puter-portable.template) | Preamble template (defines `init_puter_portable`, inlines puter.js). | + +## Running locally + +```bash +cd src/mcp-connector +npm install +npm run dev # builds, then wrangler dev — serves on http://localhost:8787 +``` + +## Deploying + +```bash +npm run deploy # builds, then wrangler deploy +``` + +To target a self-hosted Puter instance, set `puter_endpoint` (uncomment the +`[vars]` block in `wrangler.toml`). The router reads `globalThis.puter_endpoint` +and defaults to `https://api.puter.com`. + +## Getting a Puter token + +In a browser logged into Puter, open the devtools console and run +`puter.authToken`. Treat it like a password. + +## Connecting a client + +### Option A — one-click `.mcpb` bundle (Claude Desktop etc.) + +[`puter-mcp-connector.mcpb`](puter-mcp-connector.mcpb) is a prebuilt [MCP +Bundle](https://github.com/anthropics/mcpb). Import it into a host that supports +MCPB (e.g. Claude Desktop: Settings → Extensions → install from file), then fill +in the two config fields it prompts for: + +- **Server URL** — your deployed Worker, e.g. `https://puter-mcp..workers.dev/` + (or `http://127.0.0.1:8799/` for local `wrangler dev`). +- **Puter Auth Token** — your personal token (stored as a secret). + +Because the connector is a *remote* HTTP Worker but MCPB extensions run a *local* +process, the bundle ships a tiny zero-dependency Node stdio↔HTTP proxy +([`mcpb/server/index.cjs`](mcpb/server/index.cjs)) that forwards JSON-RPC to your +Worker with the token attached as `Authorization: Bearer`. + +Rebuild the bundle after changing the proxy or manifest: + +```bash +npm run pack:mcpb # -> puter-mcp-connector.mcpb +``` + +The `.mcpb` is unsigned; hosts may warn it's from an unknown developer. To +self-sign: `npx @anthropic-ai/mcpb sign --self-signed puter-mcp-connector.mcpb`. + +### Option B — direct HTTP + +Point any MCP client that supports HTTP transport at the Worker URL and add your +token as a bearer header. Example (`mcp.json`-style): + +```json +{ + "mcpServers": { + "puter": { + "url": "https://puter-mcp..workers.dev/", + "headers": { "Authorization": "Bearer YOUR_PUTER_TOKEN" } + } + } +} +``` + +## Quick smoke test with curl + +```bash +URL=http://localhost:8787 +TOKEN=your_puter_token + +# initialize +curl -s $URL -H 'content-type: application/json' -d '{ + "jsonrpc":"2.0","id":1,"method":"initialize", + "params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}} +}' + +# list tools +curl -s $URL -H 'content-type: application/json' \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' + +# stat your home directory +curl -s $URL -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{ + "jsonrpc":"2.0","id":3,"method":"tools/call", + "params":{"name":"fs_stat","arguments":{"path":"~"}} +}' + +# write then read a file +curl -s $URL -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{ + "jsonrpc":"2.0","id":4,"method":"tools/call", + "params":{"name":"fs_write_file","arguments":{"path":"~/Desktop/hello.txt","content":"hi from MCP"}} +}' + +# list subdomains +curl -s $URL -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ + -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"subdomains_list","arguments":{}}}' +``` diff --git a/src/mcp-connector/mcpb/manifest.json b/src/mcp-connector/mcpb/manifest.json new file mode 100644 index 000000000..7b113afe0 --- /dev/null +++ b/src/mcp-connector/mcpb/manifest.json @@ -0,0 +1,63 @@ +{ + "manifest_version": "0.3", + "name": "puter-mcp-connector", + "display_name": "Puter", + "version": "0.1.0", + "description": "Use your own Puter account's filesystem and subdomains over MCP.", + "long_description": "Connects an MCP client to a deployed Puter MCP Worker. Authenticates with your own Puter token (no shared credentials) and exposes tools to read/write/stat files, make directories, delete entries, and manage subdomains. The bundle runs a tiny local stdio<->HTTP proxy that forwards JSON-RPC to your Worker with your token attached.", + "author": { + "name": "Puter Technologies Inc.", + "url": "https://puter.com" + }, + "homepage": "https://puter.com", + "documentation": "https://github.com/HeyPuter/puter/tree/main/src/mcp-connector", + "license": "AGPL-3.0-only", + "keywords": ["puter", "filesystem", "hosting", "subdomains", "fs"], + "server": { + "type": "node", + "entry_point": "server/index.cjs", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/server/index.cjs"], + "env": { + "PUTER_MCP_URL": "${user_config.server_url}", + "PUTER_TOKEN": "${user_config.puter_token}" + } + } + }, + "tools_generated": false, + "tools": [ + { "name": "fs_read_file", "description": "Read a file (UTF-8 or base64; optional offset/length)." }, + { "name": "fs_stat", "description": "Stat a file or directory (size, type, timestamps, uid)." }, + { "name": "fs_write_file", "description": "Create or overwrite a file (UTF-8 or base64 content)." }, + { "name": "fs_mkdir", "description": "Create a directory (optionally creating missing parents)." }, + { "name": "fs_delete", "description": "Delete a file or directory (recursive by default)." }, + { "name": "fs_readdir", "description": "List the entries of a directory." }, + { "name": "subdomains_list", "description": "List the caller's subdomains." }, + { "name": "subdomains_get", "description": "Get a subdomain by name." }, + { "name": "subdomains_create", "description": "Create a subdomain (optionally pointing at a root_dir)." }, + { "name": "subdomains_update", "description": "Update a subdomain's root directory." }, + { "name": "subdomains_delete", "description": "Delete a subdomain." } + ], + "user_config": { + "server_url": { + "type": "string", + "title": "Server URL", + "description": "URL of your deployed Puter MCP Worker, e.g. https://puter-mcp..workers.dev/ (use http://127.0.0.1:8799/ for local `wrangler dev`).", + "required": true, + "default": "https://puter-mcp.example.workers.dev/" + }, + "puter_token": { + "type": "string", + "title": "Puter Auth Token", + "description": "Your personal Puter token. Get it from a logged-in Puter browser tab: open the devtools console and run `puter.authToken`. Treated as a secret.", + "required": true, + "sensitive": true + } + }, + "compatibility": { + "runtimes": { + "node": ">=18.0.0" + } + } +} diff --git a/src/mcp-connector/mcpb/server/index.cjs b/src/mcp-connector/mcpb/server/index.cjs new file mode 100644 index 000000000..ffc448739 --- /dev/null +++ b/src/mcp-connector/mcpb/server/index.cjs @@ -0,0 +1,132 @@ +#!/usr/bin/env node +// +// Puter MCP connector — local stdio <-> remote HTTP proxy. +// +// The Puter MCP server is a REMOTE Cloudflare Worker (Streamable HTTP). MCPB +// extensions, however, launch a LOCAL server process that speaks MCP over +// stdio. This tiny zero-dependency proxy bridges the two: it reads newline- +// delimited JSON-RPC messages from stdin, POSTs each to the Worker with the +// caller's Puter token attached as `Authorization: Bearer `, and writes +// the Worker's JSON response back to stdout. +// +// Config comes from environment variables (populated by the MCPB host from +// user_config — see manifest.json): +// PUTER_MCP_URL - URL of your deployed Worker (e.g. https://...workers.dev/) +// PUTER_TOKEN - your personal Puter auth token +// +// Uses only Node built-ins so the bundle needs no node_modules. + +'use strict'; + +const http = require('node:http'); +const https = require('node:https'); +const readline = require('node:readline'); +const { URL } = require('node:url'); + +const ENDPOINT = process.env.PUTER_MCP_URL; +const TOKEN = process.env.PUTER_TOKEN || ''; + +const logErr = (...parts) => process.stderr.write(`[puter-mcp] ${parts.join(' ')}\n`); + +if (!ENDPOINT) { + logErr('FATAL: PUTER_MCP_URL is not set. Configure the "Server URL" in the extension settings.'); + process.exit(1); +} + +let endpointUrl; +try { + endpointUrl = new URL(ENDPOINT); +} catch { + logErr(`FATAL: PUTER_MCP_URL is not a valid URL: ${ENDPOINT}`); + process.exit(1); +} +const transport = endpointUrl.protocol === 'https:' ? https : http; + +// POST one raw JSON-RPC line to the Worker. Resolves to {status, text} or {error}. +function forward(rawLine) { + return new Promise((resolve) => { + const body = Buffer.from(rawLine, 'utf8'); + const headers = { + 'content-type': 'application/json', + accept: 'application/json', + 'content-length': body.length, + }; + if (TOKEN) headers.authorization = `Bearer ${TOKEN}`; + + const req = transport.request( + endpointUrl, + { method: 'POST', headers }, + (res) => { + const chunks = []; + res.on('data', (c) => chunks.push(c)); + res.on('end', () => + resolve({ status: res.statusCode, text: Buffer.concat(chunks).toString('utf8') }), + ); + }, + ); + req.on('error', (error) => resolve({ error })); + req.write(body); + req.end(); + }); +} + +function writeMessage(text) { + process.stdout.write(text.endsWith('\n') ? text : `${text}\n`); +} + +const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity }); + +// Requests are forwarded concurrently; don't exit on stdin EOF until every +// in-flight request has resolved (otherwise piped input loses its responses). +let pending = 0; +let stdinClosed = false; +const maybeExit = () => { + if (stdinClosed && pending === 0) process.exit(0); +}; + +async function handleLine(raw) { + // Best-effort parse only to recover an id for error reporting. + let id; + try { + const parsed = JSON.parse(raw); + if (parsed && !Array.isArray(parsed)) id = parsed.id; + } catch { + // Not our problem to validate — let the server reject it. + } + + const res = await forward(raw); + + if (res.error) { + logErr(`request failed: ${res.error.message}`); + // Only requests (with an id) expect a response. + if (id !== undefined && id !== null) { + writeMessage( + JSON.stringify({ + jsonrpc: '2.0', + id, + error: { code: -32000, message: `Cannot reach Puter MCP server: ${res.error.message}` }, + }), + ); + } + return; + } + + // 202 (notification ack) or an empty body => nothing to relay. + if (res.status === 202 || !res.text) return; + writeMessage(res.text); +} + +rl.on('line', (line) => { + const raw = line.trim(); + if (!raw) return; + pending += 1; + handleLine(raw).finally(() => { + pending -= 1; + maybeExit(); + }); +}); + +rl.on('close', () => { + stdinClosed = true; + maybeExit(); +}); diff --git a/src/mcp-connector/package.json b/src/mcp-connector/package.json new file mode 100644 index 000000000..40faa56bb --- /dev/null +++ b/src/mcp-connector/package.json @@ -0,0 +1,22 @@ +{ + "name": "puter-mcp-connector", + "version": "0.1.0", + "description": "Cloudflare Worker MCP server for Puter filesystem and subdomain operations, running the real puter.js port and authenticated per-caller with their own Puter token.", + "type": "module", + "private": true, + "main": "dist/workerPreamble.js", + "scripts": { + "build": "webpack --config webpack.config.cjs --mode production && node ./scripts/buildPreamble.mjs", + "dev": "npm run build && wrangler dev", + "deploy": "npm run build && wrangler deploy", + "pack:mcpb": "npx -y @anthropic-ai/mcpb@latest pack mcpb puter-mcp-connector.mcpb" + }, + "devDependencies": { + "terser-webpack-plugin": "^5.3.14", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.1", + "wrangler": "^3.90.0" + }, + "author": "Puter Technologies Inc.", + "license": "AGPL-3.0-only" +} diff --git a/src/mcp-connector/scripts/buildPreamble.mjs b/src/mcp-connector/scripts/buildPreamble.mjs new file mode 100644 index 000000000..08e4961da --- /dev/null +++ b/src/mcp-connector/scripts/buildPreamble.mjs @@ -0,0 +1,56 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Forked from src/worker/scripts/buildPreamble.mjs. Lives at src/mcp-connector, +// a sibling of src/puter-js, so puter-js is at ../puter-js (same as the original). + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const workerDir = path.resolve(scriptDir, '..'); +const templatePath = path.join(workerDir, 'template', 'puter-portable.template'); +const outputDir = path.join(workerDir, 'dist'); +const outputPath = path.join(outputDir, 'workerPreamble.js'); + +// Build a version stamp: puter-js version + short git SHA +const puterJsPkg = JSON.parse( + await readFile(path.resolve(workerDir, '../puter-js/package.json'), 'utf-8'), +); +let gitSha = 'unknown'; +try { + gitSha = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim(); +} catch { /* not in a git repo — keep "unknown" */ } +const preambleVersion = `${puterJsPkg.version}+${gitSha}`; + +const inlineIncludes = async (filePath) => { + const fileContents = await readFile(filePath, 'utf-8'); + const lines = fileContents.split('\n'); + const expandedLines = []; + + for (const line of lines) { + const includeMatch = /^([ \t]*)#include "([^"]+)"$/.exec(line); + if (!includeMatch) { + expandedLines.push(line); + continue; + } + + const [, indent, relativePath] = includeMatch; + const includedPath = path.resolve(path.dirname(filePath), relativePath); + const includedContents = await inlineIncludes(includedPath); + for (const includedLine of includedContents.split('\n')) { + expandedLines.push( + includedLine.length > 0 + ? `${indent}${includedLine}` + : includedLine, + ); + } + } + + return expandedLines.join('\n'); +}; + +await mkdir(outputDir, { recursive: true }); +const versionBanner = `var __PUTER_PREAMBLE_VERSION__ = ${JSON.stringify(preambleVersion)};\n`; +const preambleSource = await inlineIncludes(templatePath); +await writeFile(outputPath, versionBanner + preambleSource); +console.log(`Wrote ${outputPath} (puter-js ${preambleVersion})`); diff --git a/src/mcp-connector/src/index.js b/src/mcp-connector/src/index.js new file mode 100644 index 000000000..f42e0c8af --- /dev/null +++ b/src/mcp-connector/src/index.js @@ -0,0 +1,6 @@ +import initS2w from './s2w-router.js'; +import registerMcpRoutes from './mcp.js'; + +// Bring up the (forked) Puter worker router, then register the MCP routes on it. +initS2w(); +registerMcpRoutes(globalThis.router); diff --git a/src/mcp-connector/src/mcp.js b/src/mcp-connector/src/mcp.js new file mode 100644 index 000000000..9153ba862 --- /dev/null +++ b/src/mcp-connector/src/mcp.js @@ -0,0 +1,191 @@ +// Minimal MCP (Model Context Protocol) server over the Streamable HTTP transport, +// wired onto the forked Puter worker router. +// +// `registerMcpRoutes(router)` attaches: +// POST / and POST /mcp -> MCP JSON-RPC endpoint +// GET /, /mcp, /health -> discovery / health +// +// Tool handlers run against the caller's REAL puter.js instance, which the +// router builds from the Authorization: Bearer header (event.user.puter). + +import { TOOL_MAP, listTools, asText } from './tools.js'; + +const PROTOCOL_VERSION = '2025-06-18'; +const SUPPORTED_PROTOCOL_VERSIONS = new Set(['2025-06-18', '2025-03-26', '2024-11-05']); + +const SERVER_INFO = { + name: 'puter-mcp', + title: 'Puter MCP Server', + version: '0.1.0', +}; + +// JSON-RPC error codes. +const PARSE_ERROR = -32700; +const INVALID_REQUEST = -32600; +const METHOD_NOT_FOUND = -32601; +const INVALID_PARAMS = -32602; +const INTERNAL_ERROR = -32603; + +function rpcResult(id, result) { + return { jsonrpc: '2.0', id, result }; +} + +function rpcError(id, code, message, data) { + const error = { code, message }; + if (data !== undefined) error.data = data; + return { jsonrpc: '2.0', id: id ?? null, error }; +} + +function toolError(message) { + return { content: [{ type: 'text', text: message }], isError: true }; +} + +/** + * Handle a single JSON-RPC message. Returns a response object, or `null` for + * notifications (which must not produce a response). + * + * @param {object} msg The parsed JSON-RPC message. + * @param {object|undefined} userPuter The caller's puter instance (or undefined if unauthenticated). + */ +async function handleMessage(msg, userPuter) { + if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0' || typeof msg.method !== 'string') { + return rpcError(msg?.id ?? null, INVALID_REQUEST, 'Invalid JSON-RPC request'); + } + + const { id, method, params } = msg; + const isNotification = id === undefined; + + try { + switch (method) { + case 'initialize': { + const requested = params?.protocolVersion; + const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) + ? requested + : PROTOCOL_VERSION; + return rpcResult(id, { + protocolVersion, + capabilities: { tools: { listChanged: false } }, + serverInfo: SERVER_INFO, + instructions: + 'Puter MCP server. Authenticate with your own Puter token via the ' + + 'Authorization: Bearer header. Provides filesystem and subdomain tools.', + }); + } + + case 'ping': + return rpcResult(id, {}); + + case 'notifications/initialized': + case 'notifications/cancelled': + case 'notifications/roots/list_changed': + return null; // notifications get no response + + case 'tools/list': + return rpcResult(id, { tools: listTools() }); + + case 'tools/call': + return await handleToolCall(id, params, userPuter); + + default: + if (isNotification) return null; + return rpcError(id, METHOD_NOT_FOUND, `Method not found: ${method}`); + } + } catch (err) { + if (isNotification) return null; + return rpcError(id, INTERNAL_ERROR, err?.message || 'Internal error'); + } +} + +async function handleToolCall(id, params, userPuter) { + const name = params?.name; + const tool = name && TOOL_MAP.get(name); + if (!tool) { + return rpcError(id, INVALID_PARAMS, `Unknown tool: ${name}`); + } + + if (!userPuter) { + // Surface auth problems as a tool error so MCP clients display it inline. + return rpcResult(id, toolError('Missing Authorization: Bearer header.')); + } + + const args = params.arguments || {}; + try { + const value = await tool.handler(userPuter, args); + + // Handlers may return { text } to emit raw text, otherwise we JSON-encode. + const text = value && typeof value === 'object' && typeof value.text === 'string' + ? value.text + : asText(value); + const result = { content: [{ type: 'text', text }] }; + if (value && typeof value === 'object' && value._meta) result._meta = value._meta; + return rpcResult(id, result); + } catch (err) { + return rpcResult(id, toolError(formatPuterError(err))); + } +} + +// puter.js rejects with various shapes (Error, {error}, {message,code}, string). +function formatPuterError(err) { + if (!err) return 'Tool execution failed'; + if (typeof err === 'string') return err; + if (err.message) return err.code ? `${err.message} (${err.code})` : err.message; + if (err.error) return typeof err.error === 'string' ? err.error : (err.error.message || JSON.stringify(err.error)); + try { + return JSON.stringify(err); + } catch { + return 'Tool execution failed'; + } +} + +function jsonResponse(body) { + return new Response(JSON.stringify(body), { + status: 200, + headers: { 'content-type': 'application/json' }, + }); +} + +// MCP Streamable HTTP POST handler (one request object or a batch array). +async function mcpPost(event) { + const userPuter = event.user && event.user.puter; + + let payload; + try { + payload = await event.request.json(); + } catch { + return jsonResponse(rpcError(null, PARSE_ERROR, 'Invalid JSON body')); + } + + if (Array.isArray(payload)) { + if (payload.length === 0) { + return jsonResponse(rpcError(null, INVALID_REQUEST, 'Empty batch')); + } + const responses = (await Promise.all(payload.map((m) => handleMessage(m, userPuter)))).filter(Boolean); + if (responses.length === 0) return new Response(null, { status: 202 }); + return jsonResponse(responses); + } + + const response = await handleMessage(payload, userPuter); + if (response === null) return new Response(null, { status: 202 }); // notification + return jsonResponse(response); +} + +// Discovery / health. +function mcpInfo() { + return { + name: 'puter-mcp', + description: + 'MCP server for Puter filesystem and subdomain operations. POST JSON-RPC to ' + + 'this endpoint with your Puter token in the Authorization: Bearer header.', + transport: 'streamable-http', + tools: listTools().map((t) => t.name), + }; +} + +/** Attach the MCP routes to the (already-initialized) router. */ +export default function registerMcpRoutes(router) { + router.post('/', mcpPost); + router.post('/mcp', mcpPost); + router.get('/', mcpInfo); + router.get('/mcp', mcpInfo); + router.get('/health', mcpInfo); +} diff --git a/src/mcp-connector/src/s2w-router.js b/src/mcp-connector/src/s2w-router.js new file mode 100644 index 000000000..7bbad0274 --- /dev/null +++ b/src/mcp-connector/src/s2w-router.js @@ -0,0 +1,218 @@ +// Forked from src/worker/src/s2w-router.js for the Puter MCP server. +// +// Differences from the original worker router: +// 1. There is NO `globalThis.me` / worker-owned puter instance. This server +// holds no credentials of its own — every request runs as the caller. +// 2. The per-request puter instance (`event.user.puter`) is generated from the +// standard `Authorization: Bearer ` header instead of `puter-auth`. + +const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +const buildRouteMatcher = (route) => { + let pattern = '^'; + const paramNames = []; + + for (let index = 0; index < route.length; index += 1) { + const char = route[index]; + if (char === ':' || char === '*') { + let name = ''; + let offset = index + 1; + while (offset < route.length) { + const nextChar = route[offset]; + if (!/[A-Za-z0-9_]/.test(nextChar)) { + break; + } + name += nextChar; + offset += 1; + } + + if (name.length === 0) { + pattern += escapeRegex(char); + continue; + } + + paramNames.push(name); + pattern += char === ':' ? '([^/]+)' : '(.*)'; + index = offset - 1; + continue; + } + + pattern += escapeRegex(char); + } + + pattern += '$'; + const regex = new RegExp(pattern); + return (pathname) => { + const matches = regex.exec(pathname); + if (!matches) return false; + + const params = {}; + for (let index = 0; index < paramNames.length; index += 1) { + params[paramNames[index]] = matches[index + 1]; + } + return { params }; + }; +}; + +// Error responses as JSON (with CORS) so clients never get a non-JSON body they +// might try to parse — notably MCP OAuth discovery probes against unknown paths. +const jsonError = (status, message) => + new Response(JSON.stringify({ error: { code: status, message } }), { + status, + headers: { + 'content-type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + }); + +// Pull the bearer token out of the Authorization header. +const getBearerToken = (request) => { + const header = request.headers.get('authorization'); + if (!header) return null; + const match = /^Bearer\s+(.+)$/i.exec(header.trim()); + return match ? match[1].trim() : null; +}; + +function initS2w () { + const router = { + routing: true, + handleCors: true, + map: new Map(), + custom(eventName, route, eventListener) { + const matchExp = buildRouteMatcher(route); + if (!this.map.has(eventName)) { + this.map.set(eventName, [[matchExp, eventListener]]); + return; + } + this.map.get(eventName).push([matchExp, eventListener]); + }, + get(...args) { + this.custom('GET', ...args); + }, + post(...args) { + this.custom('POST', ...args); + }, + options(...args) { + this.custom('OPTIONS', ...args); + }, + put(...args) { + this.custom('PUT', ...args); + }, + delete(...args) { + this.custom('DELETE', ...args); + }, + async handleOptions(request) { + const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS', + 'Access-Control-Max-Age': '86400', + }; + if ( + request.headers.get('Origin') !== null && + request.headers.get('Access-Control-Request-Method') !== null && + request.headers.get('Access-Control-Request-Headers') !== null + ) { + return new Response(null, { + headers: { + ...corsHeaders, + 'Access-Control-Allow-Headers': + request.headers.get( + 'Access-Control-Request-Headers', + ), + }, + }); + } + return new Response(null, { + headers: { + Allow: 'GET, HEAD, POST, OPTIONS', + }, + }); + }, + async route(event) { + // Generate the caller's puter instance from the Authorization header. + // No `me`/worker-owned instance: this server is purely a pass-through + // for the caller's own token. + const token = getBearerToken(event.request); + if (token) { + event.requestor = { + puter: init_puter_portable( + token, + globalThis.puter_endpoint || 'https://api.puter.com', + 'userPuter', + ), + }; + event.user = event.requestor; + } + + const mappings = this.map.get(event.request.method); + if (this.handleCors && event.request.method === 'OPTIONS' && !mappings) { + return this.handleOptions(event.request); + } + if (!mappings) { + // JSON (not plain text) so clients that probe unknown paths — + // e.g. an MCP client's OAuth discovery — can parse the body + // instead of throwing a JSON syntax error on it. + return jsonError(404, `No routes for request method ${event.request.method}`); + } + + const url = new URL(event.request.url); + try { + for (const mapping of mappings) { + const results = mapping[0](url.pathname); + if (!results) continue; + + event.params = results.params; + let response = await mapping[1](event); + if (!(response instanceof Response)) { + try { + if ( + response instanceof Blob || + response instanceof ArrayBuffer || + response instanceof Uint8Array.__proto__ || + response instanceof ReadableStream || + response instanceof URLSearchParams || + typeof response === 'string' + ) { + response = new Response(response); + } else { + response = new Response(JSON.stringify(response), { + headers: { + 'content-type': 'application/json', + }, + }); + } + } catch { + throw new Error( + 'Returned response by handler was neither a Response object nor an object which can implicitly be converted into a Response object', + ); + } + } + if ( + this.handleCors && + !response.headers.has('access-control-allow-origin') + ) { + response.headers.set('Access-Control-Allow-Origin', '*'); + } + return response; + } + } catch (error) { + return jsonError(500, String(error && error.message ? error.message : error)); + } + + // No matching route for this path. JSON 404 (see note above) — this + // is also what OAuth/well-known discovery probes will hit. + return jsonError(404, 'Path not found'); + }, + }; + + globalThis.router = router; + self.addEventListener('fetch', (event) => { + if (!router.routing) { + return false; + } + event.respondWith(router.route(event)); + return true; + }); +} + +export default initS2w; diff --git a/src/mcp-connector/src/tools.js b/src/mcp-connector/src/tools.js new file mode 100644 index 000000000..c7eb404df --- /dev/null +++ b/src/mcp-connector/src/tools.js @@ -0,0 +1,278 @@ +// MCP tool definitions + handlers. +// +// Handlers receive the caller's REAL puter.js instance (the same `puter` object +// the in-repo worker exposes, created from the Authorization header) and call +// genuine puter.fs.* / puter.hosting.* methods. +// +// Each entry has a JSON-Schema `inputSchema` (advertised via tools/list) and a +// `handler(puter, args)`; the MCP layer wraps the return value into `content`. + +/** Decode a base64 string into a Uint8Array (Workers have atob). */ +function base64ToBytes(b64) { + const binary = atob(b64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes; +} + +/** Encode bytes to base64 (btoa is byte-unsafe for >0xFF, so chunk over a Uint8Array). */ +function bytesToBase64(bytes) { + let binary = ''; + const chunk = 0x8000; + for (let i = 0; i < bytes.length; i += chunk) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunk)); + } + return btoa(binary); +} + +/** Normalize puter.fs.read output (a Blob/Response-like) into text or base64. */ +async function decodeReadResult(result, encoding) { + let bytes; + if (result instanceof Blob) { + bytes = new Uint8Array(await result.arrayBuffer()); + } else if (result instanceof ArrayBuffer) { + bytes = new Uint8Array(result); + } else if (result instanceof Uint8Array) { + bytes = result; + } else if (typeof result === 'string') { + if (encoding === 'base64') { + bytes = new TextEncoder().encode(result); + } else { + return { content: result, encoding: 'utf8', bytes: result.length }; + } + } else if (result && typeof result.arrayBuffer === 'function') { + bytes = new Uint8Array(await result.arrayBuffer()); + } else { + // Fallback: stringify whatever we got. + const text = typeof result === 'object' ? JSON.stringify(result) : String(result); + return { content: text, encoding: 'utf8', bytes: text.length }; + } + + if (encoding === 'base64') { + return { content: bytesToBase64(bytes), encoding: 'base64', bytes: bytes.length }; + } + return { content: new TextDecoder().decode(bytes), encoding: 'utf8', bytes: bytes.length }; +} + +export const TOOLS = [ + // ----- filesystem ------------------------------------------------------ + { + name: 'fs_read_file', + description: + 'Read the contents of a file in Puter. Returns UTF-8 text by default; ' + + 'pass encoding="base64" for binary files. Supports optional byte offset/length.', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'File path. Absolute (/user/...), ~/relative, or relative to home.' }, + encoding: { type: 'string', enum: ['utf8', 'base64'], default: 'utf8' }, + offset: { type: 'integer', minimum: 0, description: 'Byte offset to start reading from.' }, + length: { type: 'integer', minimum: 1, description: 'Maximum number of bytes to read.' }, + }, + required: ['path'], + }, + async handler(puter, { path, encoding = 'utf8', offset, length }) { + const options = {}; + if (offset != null) options.offset = offset; + if (length != null) options.byte_count = length; + const result = await puter.fs.read(path, options); + const { content, encoding: enc, bytes } = await decodeReadResult(result, encoding); + return { _meta: { encoding: enc, bytes }, text: content }; + }, + }, + { + name: 'fs_stat', + description: 'Get metadata (name, size, type, timestamps, uid) for a file or directory in Puter.', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Path to a file or directory.' }, + return_size: { type: 'boolean', default: true, description: 'Compute size for directories.' }, + }, + required: ['path'], + }, + async handler(puter, { path, return_size }) { + return puter.fs.stat(path, { returnSize: return_size !== false }); + }, + }, + { + name: 'fs_write_file', + description: + 'Write (create or overwrite) a file in Puter. Provide content as UTF-8 text, ' + + 'or set encoding="base64" to write binary data.', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Destination file path.' }, + content: { type: 'string', description: 'File contents (UTF-8, or base64 if encoding=base64).' }, + encoding: { type: 'string', enum: ['utf8', 'base64'], default: 'utf8' }, + overwrite: { type: 'boolean', default: true, description: 'Overwrite an existing file.' }, + create_missing_parents: { + type: 'boolean', + default: false, + description: 'Create missing parent directories.', + }, + dedupe_name: { + type: 'boolean', + default: false, + description: 'Auto-rename instead of overwriting if the file exists.', + }, + }, + required: ['path', 'content'], + }, + async handler(puter, { path, content, encoding = 'utf8', overwrite = true, create_missing_parents = false, dedupe_name = false }) { + const data = encoding === 'base64' + ? new Blob([base64ToBytes(content)]) + : content; + return puter.fs.write(path, data, { + overwrite, + dedupeName: dedupe_name, + createMissingParents: create_missing_parents, + }); + }, + }, + { + name: 'fs_mkdir', + description: 'Create a directory in Puter (optionally creating missing parents).', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Directory path to create.' }, + create_missing_parents: { + type: 'boolean', + default: true, + description: 'Create intermediate directories as needed.', + }, + }, + required: ['path'], + }, + async handler(puter, { path, create_missing_parents }) { + return puter.fs.mkdir(path, { createMissingParents: create_missing_parents !== false }); + }, + }, + { + name: 'fs_delete', + description: 'Delete a file or directory in Puter. Directories are removed recursively by default.', + inputSchema: { + type: 'object', + properties: { + path: { + description: 'Path (string) or list of paths to delete.', + anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], + }, + recursive: { type: 'boolean', default: true, description: 'Recurse into directories.' }, + }, + required: ['path'], + }, + async handler(puter, { path, recursive }) { + await puter.fs.delete(path, { recursive: recursive !== false }); + return { success: true, deleted: Array.isArray(path) ? path : [path] }; + }, + }, + { + name: 'fs_readdir', + description: 'List the entries (files and subdirectories) of a directory in Puter.', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Directory path to list.' }, + }, + required: ['path'], + }, + async handler(puter, { path }) { + return puter.fs.readdir(path); + }, + }, + + // ----- subdomains (puter.hosting) -------------------------------------- + { + name: 'subdomains_list', + description: 'List all subdomains owned by the authenticated Puter user.', + inputSchema: { type: 'object', properties: {} }, + async handler(puter) { + return puter.hosting.list(); + }, + }, + { + name: 'subdomains_get', + description: 'Get a single subdomain (and its root directory) by name.', + inputSchema: { + type: 'object', + properties: { + subdomain: { type: 'string', description: 'The subdomain label, e.g. "my-site".' }, + }, + required: ['subdomain'], + }, + async handler(puter, { subdomain }) { + return puter.hosting.get(subdomain); + }, + }, + { + name: 'subdomains_create', + description: + 'Create a new subdomain. Optionally point it at a Puter directory (root_dir) to host a static site.', + inputSchema: { + type: 'object', + properties: { + subdomain: { + type: 'string', + description: 'Subdomain label (lowercase letters, digits, hyphens; max 64 chars).', + }, + root_dir: { + type: 'string', + description: 'Optional Puter directory path the subdomain serves from.', + }, + }, + required: ['subdomain'], + }, + async handler(puter, { subdomain, root_dir }) { + return root_dir + ? puter.hosting.create(subdomain, root_dir) + : puter.hosting.create(subdomain); + }, + }, + { + name: 'subdomains_update', + description: "Update an existing subdomain's root directory.", + inputSchema: { + type: 'object', + properties: { + subdomain: { type: 'string', description: 'The subdomain label to update.' }, + root_dir: { + type: 'string', + description: 'New Puter directory path to serve from.', + }, + }, + required: ['subdomain', 'root_dir'], + }, + async handler(puter, { subdomain, root_dir }) { + return puter.hosting.update(subdomain, root_dir); + }, + }, + { + name: 'subdomains_delete', + description: 'Delete a subdomain by name.', + inputSchema: { + type: 'object', + properties: { + subdomain: { type: 'string', description: 'The subdomain label to delete.' }, + }, + required: ['subdomain'], + }, + async handler(puter, { subdomain }) { + return puter.hosting.delete(subdomain); + }, + }, +]; + +export const TOOL_MAP = new Map(TOOLS.map((t) => [t.name, t])); + +/** Build the tools/list payload (strips internal handlers). */ +export function listTools() { + return TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })); +} + +/** Pretty-print a value for a text content block. */ +export function asText(value) { + return typeof value === 'string' ? value : JSON.stringify(value, null, 2); +} diff --git a/src/mcp-connector/template/puter-portable.template b/src/mcp-connector/template/puter-portable.template new file mode 100644 index 000000000..b9a1b8270 --- /dev/null +++ b/src/mcp-connector/template/puter-portable.template @@ -0,0 +1,51 @@ +// This file is not actually in the webpack project, it is handled separately. +// Forked from src/worker/template/puter-portable.template. + +if (globalThis.Cloudflare) { + // Cloudflare Workers has a faulty EventTarget implementation which doesn't + // bind "this" to the event handler. + // https://github.com/cloudflare/workerd/issues/4453 + const CfEventTarget = EventTarget; + globalThis.EventTarget = class EventTarget extends CfEventTarget { + constructor(...args) { + super(...args); + } + + addEventListener(type, listener, options) { + super.addEventListener(type, listener.bind(this), options); + } + }; +} + +// Build a real puter.js instance bound to a specific auth token. Each call with +// type 'userPuter' runs puter.js inside an isolated `with` context so concurrent +// requests don't share global mutable state (auth token, caches, etc.). +globalThis.init_puter_portable = (auth, apiOrigin, type) => { + if (type === 'userPuter') { + const goodContext = {}; + Object.getOwnPropertyNames(globalThis).forEach((name) => { + try { + goodContext[name] = globalThis[name]; + } catch {} + }); + goodContext.globalThis = goodContext; + goodContext.WorkerGlobalScope = WorkerGlobalScope; + goodContext.ServiceWorkerGlobalScope = ServiceWorkerGlobalScope; + goodContext.location = new URL('https://puter.work'); + goodContext.addEventListener = () => {}; + // @ts-ignore + with (goodContext) { + #include "../../puter-js/dist/puter.js" + } + goodContext.puter.setAPIOrigin(apiOrigin); + goodContext.puter.setAuthToken(auth); + return goodContext.puter; + } + + #include "../../puter-js/dist/puter.js" + + puter.setAPIOrigin(apiOrigin); + puter.setAuthToken(auth); +}; + +#include "../dist/webpackPreamplePart.js" diff --git a/src/mcp-connector/webpack.config.cjs b/src/mcp-connector/webpack.config.cjs new file mode 100644 index 000000000..a3cccb604 --- /dev/null +++ b/src/mcp-connector/webpack.config.cjs @@ -0,0 +1,62 @@ +// Forked from src/worker/webpack.config.cjs. +// Bundles src/index.js (router + MCP routes) into dist/webpackPreamplePart.js, +// which buildPreamble.mjs then inlines alongside puter.js into the final +// service-worker preamble (dist/workerPreamble.js). +const path = require('path'); +const webpack = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); + +module.exports = { + entry: './src/index.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'webpackPreamplePart.js', + library: { + type: 'var', + name: 'WorkerPreamble', + }, + globalObject: 'this', + }, + mode: 'production', + target: 'webworker', + resolve: { + extensions: ['.js'], + }, + externals: { + 'https://puter-net.b-cdn.net/rustls.js': 'undefined', + }, + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + keep_fnames: true, + mangle: { + keep_fnames: true, + }, + compress: { + keep_fnames: true, + }, + }, + }), + ], + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /puter\.js$/, + parser: { + dynamicImports: false, + }, + }, + ], + }, + plugins: [ + new webpack.BannerPlugin({ + banner: '// This file is pasted before user code', + raw: false, + entryOnly: false, + }), + ], +}; diff --git a/src/mcp-connector/wrangler.toml b/src/mcp-connector/wrangler.toml new file mode 100644 index 000000000..1b7f561c4 --- /dev/null +++ b/src/mcp-connector/wrangler.toml @@ -0,0 +1,18 @@ +name = "puter-mcp" +# The build inlines puter.js + the bundled router into a single service-worker +# script (built by `npm run build` before dev/deploy). +main = "dist/workerPreamble.js" +compatibility_date = "2025-01-01" + +# The preamble is assembled with a C-style #include preprocessor (see +# scripts/buildPreamble.mjs) specifically so puter.js is concatenated RAW and +# never touched by a bundler. wrangler bundles `main` by default, and that pass +# transpiles to strict-mode ESM which forbids the `with` statement used by the +# init_puter_portable sandbox ("Strict mode code may not include a with +# statement"). Uploading the script as-is keeps it a sloppy-mode service worker. +no_bundle = true + +# Optional: point the server at a self-hosted Puter API instead of the public +# one. The router reads globalThis.puter_endpoint; bind it as a var to override. +# [vars] +# puter_endpoint = "https://api.puter.com"