diff --git a/.env.example b/.env.example index 79c9d22..e7c7513 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,4 @@ OPENAI_API_KEY="x" OPENAI_MODEL="gpt-5.2" LOG_LEVEL="info" -ARCADE_API_KEY="x" -USER_ID="<>@gmail.com" +ARCADE_GATEWAY_URL="https://api.arcade.dev/mcp/your-gateway-slug" diff --git a/.gitignore b/.gitignore index b311acc..9d6dd5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env +.context node_modules .DS_Store agent \ No newline at end of file diff --git a/README.md b/README.md index e6a1924..f0c22b3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - Bun installed on your computer - https://bun.com/docs/installation - An OpenAI API Key - https://openai.com/ -- An Arcade.dev API key - https://www.arcade.dev/ +- An Arcade MCP Gateway URL - https://docs.arcade.dev/en/guides/mcp-gateways ## This Repo @@ -24,7 +24,13 @@ cp .env.example .env To run: ```bash -./agent.ts +./agent.ts chat +``` + +You can also override the gateway URL via CLI flag: + +```bash +./agent.ts chat --gateway-url https://api.arcade.dev/mcp/your-gateway-slug ``` To Compile as a single-file executable diff --git a/agent.ts b/agent.ts index ebc7b0f..e3675c9 100755 --- a/agent.ts +++ b/agent.ts @@ -6,6 +6,7 @@ import { Config } from "./classes/config"; import { Logger } from "./classes/logger"; import { setOpenAIClient } from "./utils/client"; +import { createMcpServer } from "./utils/tools"; import { GeneralAgent } from "./agents/general"; @@ -15,13 +16,23 @@ setOpenAIClient(config); program.version(pkg.version).name(pkg.name).description(pkg.description); -process.on("SIGINT", () => { +let mcpServer: ReturnType | undefined; + +async function cleanup() { + if (mcpServer) { + await mcpServer.close().catch(() => {}); + } +} + +process.on("SIGINT", async () => { console.log("SIGINT: ๐Ÿ‘‹ Bye!"); + await cleanup(); process.exit(0); }); -process.on("SIGTERM", () => { +process.on("SIGTERM", async () => { console.log("SIGTERM: ๐Ÿ‘‹ Bye!"); + await cleanup(); process.exit(0); }); @@ -30,19 +41,34 @@ program .description("Start an interactive chat session with the agent") .argument("[message]", "The message to start the chat session with") .option( - "-t, --toolkits ", - "Comma-separated list of toolkits to use (e.g., gmail,slack)", - "gmail,slack", + "-g, --gateway-url ", + "Arcade MCP Gateway URL (overrides ARCADE_GATEWAY_URL env var)", ) .action(async (message, options) => { + if (options.gatewayUrl) { + config.arcade_gateway_url = options.gatewayUrl; + } + + if (!config.arcade_gateway_url) { + console.error( + "Error: Gateway URL is required. Set ARCADE_GATEWAY_URL env var or pass --gateway-url.", + ); + process.exit(1); + } + + mcpServer = createMcpServer(config); + await mcpServer.connect(); + const agent = new GeneralAgent(config, logger); - const toolkitNames = options.toolkits.split(",").map((t) => t.trim()); await agent.interactiveChat( async (input: string) => { - await agent.chat(input, toolkitNames); + await agent.chat(input, [mcpServer!]); }, message, - toolkitNames, + async () => { + await cleanup(); + process.exit(0); + }, ); }); diff --git a/agents/general.ts b/agents/general.ts index 06593d4..58a701c 100644 --- a/agents/general.ts +++ b/agents/general.ts @@ -1,23 +1,24 @@ import { WrappedAgent } from "../classes/wrappedAgent"; import type { Config } from "../classes/config"; import type { Logger } from "../classes/logger"; +import type { MCPServerStreamableHttp } from "@openai/agents"; import chalk from "chalk"; export class GeneralAgent extends WrappedAgent { constructor(config: Config, logger: Logger) { const instructions = ` You are a general-purpose AI/LLM agent that can assist with a wide range of tasks. -You can take many actions via the toolkits provided to you. -ALWAYS prefer to call tools, but only when you are CERTAIN that you understand the user's request. Otherwise, ask clarifying questions. Do not rely on any pre-existing knowledge - only use the toolkits provided to you. +You can take many actions via the tools provided to you. +ALWAYS prefer to call tools, but only when you are CERTAIN that you understand the user's request. Otherwise, ask clarifying questions. Do not rely on any pre-existing knowledge - only use the tools provided to you. Unless otherwise specified, you should respond in Markdown, and in Table format when you have multiple items to list. You are in a terminal window, and the size of the terminal is ${Bun.env.COLUMNS}x${Bun.env.LINES}. `; super("GeneralAgent", instructions, config, logger); } - async chat(prompt: string, toolkitNames: string[] = []) { + async chat(prompt: string, mcpServers: MCPServerStreamableHttp[] = []) { this.logger.startSpan(chalk.gray(`Thinking...`)); - const stream = await this.run(prompt, toolkitNames); + const stream = await this.run(prompt, mcpServers); this.logger.endSpan(); return stream; } diff --git a/bun.lock b/bun.lock index 07648c0..08e3dc7 100644 --- a/bun.lock +++ b/bun.lock @@ -5,12 +5,11 @@ "": { "name": "email-agent-template", "dependencies": { - "@arcadeai/arcadejs": "^1.9.0", "@commander-js/extra-typings": "^14.0.0", - "@openai/agents": "^0.0.13", + "@openai/agents": "^0.4.11", "chalk": "^5.4.1", "commander": "^14.0.0", - "openai": "^5.10.2", + "openai": "^6.20.0", "ora": "^8.2.0", }, "devDependencies": { @@ -23,45 +22,37 @@ }, }, "packages": { - "@arcadeai/arcadejs": ["@arcadeai/arcadejs@1.9.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7", "zod": "^3.24.2" } }, "sha512-sV31uE+DBzzBdlF91PT/OGeYI7Mfmx4l3HVYkw/oDRZV04qJThzkDunoZKO14t1URgsi8FXrieaaOiFLgiiSKA=="], - "@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.16.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-8ofX7gkZcLj9H9rSd50mCgm3SSF8C7XoclxJuLoV0Cz3rEQ1tv9MZRYYvJtm9n1BiEQQMzSmE/w2AEkNacLYfg=="], + "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], - "@openai/agents": ["@openai/agents@0.0.13", "", { "dependencies": { "@openai/agents-core": "0.0.13", "@openai/agents-openai": "0.0.13", "@openai/agents-realtime": "0.0.13", "debug": "^4.4.0", "openai": "^5.10.1" } }, "sha512-KOi/TVRb9iqPSQcXV7lSYIxeD5ITtzjR57RSRVwB/0U/+iQfrvAuRqgw/bOc5yxtdFRfmjo42ITEjxuIHy8NMg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], - "@openai/agents-core": ["@openai/agents-core@0.0.13", "", { "dependencies": { "@openai/zod": "npm:zod@3.25.40 - 3.25.67", "debug": "^4.4.0", "openai": "^5.10.1" }, "optionalDependencies": { "@modelcontextprotocol/sdk": "^1.12.0" }, "peerDependencies": { "zod": "3.25.40 - 3.25.67" }, "optionalPeers": ["zod"] }, "sha512-INLW7WJFm8g2DugnHCVwkXlDRjj5PqzZRdkYLyUOwXQtQBBejWpNquh4TwXLpl3Tj3dQTiu7E7UOKGNy3aqNnQ=="], + "@openai/agents": ["@openai/agents@0.4.11", "", { "dependencies": { "@openai/agents-core": "0.4.11", "@openai/agents-openai": "0.4.11", "@openai/agents-realtime": "0.4.11", "debug": "^4.4.0", "openai": "^6.20.0" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-g4/5TMoV5gU/VSn8iPwuGFKpImWUdhCnHwxSF0kJxoHnbl+sJa67+iXTejUwrfeCV8fMCrtcnqfkyE6sG6gaUA=="], - "@openai/agents-openai": ["@openai/agents-openai@0.0.13", "", { "dependencies": { "@openai/agents-core": "0.0.13", "@openai/zod": "npm:zod@3.25.40 - 3.25.67", "debug": "^4.4.0", "openai": "^5.10.1" } }, "sha512-Q9wJymzrzOIqIemsHS/KgDAeRtTqzFKNjJzxdM+YK8HEs5yQxTRXJuWtE1dsGnJagHSb0AF0dijO9BTtL//z8w=="], + "@openai/agents-core": ["@openai/agents-core@0.4.11", "", { "dependencies": { "debug": "^4.4.0", "openai": "^6.20.0" }, "optionalDependencies": { "@modelcontextprotocol/sdk": "^1.26.0" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-aZ4q/socPrtD4VQL5K2qKMV+fIps0x5Cnd7LsYbcO34aQ4daWvaV4IBAAyIYJdRTIZgr5HNWplF0IViqVUWnXQ=="], - "@openai/agents-realtime": ["@openai/agents-realtime@0.0.13", "", { "dependencies": { "@openai/agents-core": "0.0.13", "@openai/zod": "npm:zod@3.25.40 - 3.25.67", "@types/ws": "^8.18.1", "debug": "^4.4.0", "ws": "^8.18.1" } }, "sha512-1hyk3OZUd89/QqJVGidRkauwdv2F0QZ2/6SibWDG6SH+LfwblXyePBGiI8bGS7QslEohp8UP5kmK7DedY4sCJw=="], + "@openai/agents-openai": ["@openai/agents-openai@0.4.11", "", { "dependencies": { "@openai/agents-core": "0.4.11", "debug": "^4.4.0", "openai": "^6.20.0" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-8WssBd6hK6JhP7YvOA7JbChB7zeXDdeXOq8tQ2I2eTcSF62+OuX7UeCx4A1NxR++S2oX4w/MotmgziDnIuNACA=="], - "@openai/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], + "@openai/agents-realtime": ["@openai/agents-realtime@0.4.11", "", { "dependencies": { "@openai/agents-core": "0.4.11", "@types/ws": "^8.18.1", "debug": "^4.4.0", "ws": "^8.18.1" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-/STO8yBCvaNxGoW4jwQtVg4+GSKlK7y1x9ybF50O+34syJSMhHAbE3YQqT5jx2ohAA3A3AuqrsBQMdfEDn4UsQ=="], "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], - "@types/node": ["@types/node@18.19.120", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA=="], - - "@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], + "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], "bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], @@ -77,8 +68,6 @@ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - "commander": ["commander@14.0.0", "", {}, "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA=="], "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], @@ -97,8 +86,6 @@ "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -115,34 +102,24 @@ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], "eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="], - "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], + "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], - "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], - - "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], - - "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -159,18 +136,18 @@ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], + "hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="], - "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], @@ -181,7 +158,11 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], @@ -191,9 +172,9 @@ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], @@ -201,10 +182,6 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -215,7 +192,7 @@ "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], - "openai": ["openai@5.10.2", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-n+vi74LzHtvlKcDPn9aApgELGiu5CwhaLG40zxLTlFQdoSJCLACORIPC2uVQ3JEYAbqapM+XyRKFy2Thej7bIw=="], + "openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], @@ -231,14 +208,14 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], @@ -277,26 +254,16 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -305,36 +272,20 @@ "zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], - "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], + "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - "@types/node-fetch/@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + "body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "@types/ws/@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "body-parser/qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], - "bun-types/@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], - - "express/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "body-parser/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], - "send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], - - "type-is/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], - - "@types/node-fetch/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], - - "@types/ws/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], - - "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "bun-types/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], - - "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "body-parser/raw-body/http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "body-parser/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], } } diff --git a/classes/config.ts b/classes/config.ts index 1a83a77..ff20946 100644 --- a/classes/config.ts +++ b/classes/config.ts @@ -6,8 +6,7 @@ export class Config { public readonly log_level: LogLevel; public readonly log_color: boolean = true; public readonly log_timestamps: boolean = true; - public readonly arcade_api_key: string; - public readonly user_id: string; + public arcade_gateway_url: string | undefined; constructor() { const openai_api_key = Bun.env.OPENAI_API_KEY; @@ -28,16 +27,6 @@ export class Config { } this.log_level = log_level as LogLevel; - const arcade_api_key = Bun.env.ARCADE_API_KEY; - if (!arcade_api_key) { - throw new Error("ARCADE_API_KEY key is required"); - } - this.arcade_api_key = arcade_api_key; - - const user_id = Bun.env.USER_ID; - if (!user_id) { - throw new Error("USER_ID key is required"); - } - this.user_id = user_id; + this.arcade_gateway_url = Bun.env.ARCADE_GATEWAY_URL; } } diff --git a/classes/wrappedAgent.ts b/classes/wrappedAgent.ts index 0a34a70..fbfeb96 100644 --- a/classes/wrappedAgent.ts +++ b/classes/wrappedAgent.ts @@ -2,14 +2,13 @@ import { Agent, type AgentInputItem, Handoff, - Runner, - type Tool, + run, + type MCPServerStreamableHttp, user, } from "@openai/agents"; import { Config } from "./config"; import type { Logger } from "./logger"; -import { prepareTools } from "../utils/tools"; import * as readline from "readline"; import chalk from "chalk"; @@ -35,38 +34,25 @@ export abstract class WrappedAgent { public async run( prompt: string, - toolkitNames: string[] = [], + mcpServers: MCPServerStreamableHttp[] = [], handoffs: Handoff[] = [], maxTurns = 10, ) { - const tools: Tool[] = []; - - for (const toolkitName of toolkitNames) { - const toolkitTools = await prepareTools( - this.config, - this.logger, - toolkitName, - ); - tools.push(...toolkitTools); - } - if (!this.agent) { this.agent = new Agent({ name: this.name, model: this.config.openai_model, instructions: this.instructions, - tools, + mcpServers, handoffs, }); } else { - this.agent.tools = tools; + this.agent.mcpServers = mcpServers; } - const runner = new Runner(this.agent); - this.history.push(user(prompt)); - const stream = await runner.run(this.agent, this.history, { + const stream = await run(this.agent, this.history, { maxTurns, stream: true, }); @@ -92,15 +78,11 @@ export abstract class WrappedAgent { public async interactiveChat( execMethod: (input: string) => Promise, initialMessage?: string, - toolkitNames: string[] = [], onExit: () => void = () => process.exit(0), ) { this.logger.info( `๐Ÿค– Starting chat session with your agent (${this.config.openai_model})`, ); - this.logger.info( - `๐Ÿ“ฆ Available toolkits: ${chalk.cyan(toolkitNames.join(", "))}`, - ); this.logger.info("๐Ÿ’ก Type 'quit', 'exit', or 'bye' to end the session"); this.logger.info("๐Ÿ’ก Type 'clear' to clear the conversation history"); @@ -110,11 +92,11 @@ export abstract class WrappedAgent { ) => { await new Promise((resolve) => { // Create a custom output stream that colors user input green - const mutableStdout = new (require('stream')).Writable({ + const mutableStdout = new (require("stream").Writable)({ write: (chunk: any, encoding: any, callback: any) => { // Color the input text green process.stdout.write(chalk.green(chunk.toString()), callback); - } + }, }); mutableStdout.columns = process.stdout.columns; mutableStdout.rows = process.stdout.rows; @@ -126,7 +108,7 @@ export abstract class WrappedAgent { }); rl.question(questionText, async (answer) => { - process.stdout.write('\n'); // Add newline after green input + process.stdout.write("\n"); // Add newline after green input await handleInput(answer.trim()); rl.close(); resolve(true); @@ -145,7 +127,7 @@ export abstract class WrappedAgent { if (input === "clear") { this.history = []; this.logger.info("๐Ÿงน Conversation history cleared!"); - return await execMethod("Hello - we are starting a new conversation. You have access to the following toolkits: " + toolkitNames.join(", ")); + return await execMethod("Hello - we are starting a new conversation."); } return await execMethod(input); diff --git a/package.json b/package.json index f312857..7bc7849 100644 --- a/package.json +++ b/package.json @@ -14,19 +14,19 @@ "scripts": { "format": "prettier --write .", "lint": "prettier --check .", - "compile": "bun build agent.ts --compile --outfile agent" + "compile": "bun build agent.ts --compile --outfile agent", + "clear": "rm -rf .context/arcade" }, "dependencies": { - "@arcadeai/arcadejs": "^1.9.0", "@commander-js/extra-typings": "^14.0.0", - "@openai/agents": "^0.0.13", + "@openai/agents": "^0.4.11", "chalk": "^5.4.1", "commander": "^14.0.0", - "openai": "^5.10.2", + "openai": "^6.20.0", "ora": "^8.2.0" }, "devDependencies": { "@types/bun": "latest", "prettier": "^3.6.2" } -} \ No newline at end of file +} diff --git a/tests/config.test.ts b/tests/config.test.ts index a58b40f..b177487 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -25,8 +25,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); const config = new Config(); @@ -34,16 +33,16 @@ describe("Config", () => { expect(config.openai_api_key).toBe("test-openai-key"); expect(config.openai_model).toBe("gpt-4-turbo"); expect(config.log_level).toBe("info" as LogLevel); - expect(config.arcade_api_key).toBe("test-arcade-key"); - expect(config.user_id).toBe("test-user-id"); + expect(config.arcade_gateway_url).toBe( + "https://api.arcade.dev/mcp/test-gateway", + ); }); it("should throw error when OPENAI_API_KEY is missing", async () => { // Set up all required env vars except OPENAI_API_KEY Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); expect(() => { @@ -55,8 +54,7 @@ describe("Config", () => { // Set up all required env vars except OPENAI_MODEL Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); expect(() => { @@ -68,8 +66,7 @@ describe("Config", () => { // Set up all required env vars except LOG_LEVEL Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); expect(() => { @@ -77,30 +74,14 @@ describe("Config", () => { }).toThrow("LOG_LEVEL key is required"); }); - it("should throw error when ARCADE_API_KEY is missing", async () => { - // Set up all required env vars except ARCADE_API_KEY + it("should allow ARCADE_GATEWAY_URL to be optional", async () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "info"; - Bun.env.USER_ID = "test-user-id"; const { Config } = await import("../classes/config"); - expect(() => { - new Config(); - }).toThrow("ARCADE_API_KEY key is required"); - }); - - it("should throw error when USER_ID is missing", async () => { - // Set up all required env vars except USER_ID - Bun.env.OPENAI_API_KEY = "test-openai-key"; - Bun.env.OPENAI_MODEL = "gpt-4-turbo"; - Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - - const { Config } = await import("../classes/config"); - expect(() => { - new Config(); - }).toThrow("USER_ID key is required"); + const config = new Config(); + expect(config.arcade_gateway_url).toBeUndefined(); }); it("should have default values for optional properties", async () => { @@ -108,8 +89,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); const config = new Config(); @@ -123,8 +103,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "debug"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); const config = new Config(); @@ -135,8 +114,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); const config = new Config(); @@ -147,8 +125,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "warn"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); const config = new Config(); @@ -159,8 +136,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "error"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); const config = new Config(); @@ -175,8 +151,7 @@ describe("Config", () => { Bun.env.OPENAI_API_KEY = "test-openai-key"; Bun.env.OPENAI_MODEL = "gpt-4-turbo"; Bun.env.LOG_LEVEL = "info"; - Bun.env.ARCADE_API_KEY = "test-arcade-key"; - Bun.env.USER_ID = "test-user-id"; + Bun.env.ARCADE_GATEWAY_URL = "https://api.arcade.dev/mcp/test-gateway"; const { Config } = await import("../classes/config"); config = new Config(); @@ -188,8 +163,7 @@ describe("Config", () => { expect(config).toHaveProperty("log_level"); expect(config).toHaveProperty("log_color"); expect(config).toHaveProperty("log_timestamps"); - expect(config).toHaveProperty("arcade_api_key"); - expect(config).toHaveProperty("user_id"); + expect(config).toHaveProperty("arcade_gateway_url"); }); it("should have correct property types", () => { @@ -198,8 +172,7 @@ describe("Config", () => { expect(typeof config.log_level).toBe("string"); expect(typeof config.log_color).toBe("boolean"); expect(typeof config.log_timestamps).toBe("boolean"); - expect(typeof config.arcade_api_key).toBe("string"); - expect(typeof config.user_id).toBe("string"); + expect(typeof config.arcade_gateway_url).toBe("string"); }); }); }); diff --git a/utils/tools.ts b/utils/tools.ts index 127abc7..bcb74d4 100644 --- a/utils/tools.ts +++ b/utils/tools.ts @@ -1,57 +1,173 @@ -import { Arcade } from "@arcadeai/arcadejs"; +import { MCPServerStreamableHttp, getLogger } from "@openai/agents"; +import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js"; +import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; +import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"; +import type { + OAuthClientMetadata, + OAuthClientInformationFull, + OAuthTokens, +} from "@modelcontextprotocol/sdk/shared/auth.js"; import type { Config } from "../classes/config"; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; -import { tool } from "@openai/agents"; -import { Logger } from "../classes/logger"; -import chalk from "chalk"; - -export async function prepareTools( - config: Config, - logger: Logger, - toolkitName: string, - limit = 100, -) { - const client = new Arcade({ apiKey: config.arcade_api_key }); - const toolkit = await client.tools.list({ toolkit: toolkitName, limit }); - - if (toolkit.items.length === 0) { - throw new Error(`No tools found for toolkit: ${toolkitName}`); - } - - const executeOrAuthorizeZodToolWithLogging = (tool: any) => { - return async (input: any) => { - const toolName = tool.toolDefinition.qualified_name as string; - logger.incrementToolCalls(); - logger.updateSpan( - `executing tool \`${toolName}\` ${config.log_color ? chalk.gray(`(${JSON.stringify(input)})`) : `(${JSON.stringify(input)})`}`, - "โณ", - ); - const startTime = Date.now(); - try { - const result = await executeOrAuthorizeZodTool(tool)(input); - const endTime = Date.now(); - const duration = endTime - startTime; - logger.updateSpan( - `completed execution of tool \`${toolName}\` in ${duration}ms`, - "โœ”๏ธ", - ); - return result; - } catch (error) { - const endTime = Date.now(); - const duration = endTime - startTime; - const msg = `failed execution of tool \`${toolName}\` in ${duration}ms: ${error}`; - logger.updateSpan(config.log_color ? chalk.red(msg) : msg, "โŒ"); - throw error; - } +import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"; +import { join } from "path"; + +const ARCADE_DIR = join(import.meta.dir, "..", ".context", "arcade"); +const TOKENS_FILE = join(ARCADE_DIR, "tokens.json"); +const CLIENT_FILE = join(ARCADE_DIR, "client.json"); +const VERIFIER_FILE = join(ARCADE_DIR, "verifier.txt"); + +function ensureDir() { + if (!existsSync(ARCADE_DIR)) { + mkdirSync(ARCADE_DIR, { recursive: true }); + } +} + +function readJson(path: string): T | undefined { + try { + if (existsSync(path)) return JSON.parse(readFileSync(path, "utf-8")); + } catch {} + return undefined; +} + +function writeJson(path: string, data: unknown) { + ensureDir(); + writeFileSync(path, JSON.stringify(data, null, 2)); +} + +class ArcadeOAuthProvider implements OAuthClientProvider { + private _authCodePromise: Promise | null = null; + private _resolveAuthCode: ((code: string) => void) | null = null; + + get redirectUrl() { + return "http://localhost:9876/callback"; + } + + get clientMetadata(): OAuthClientMetadata { + return { + redirect_uris: [this.redirectUrl], + client_name: "arcade-cli-agent", + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + token_endpoint_auth_method: "none", }; + } + + clientInformation(): OAuthClientInformationFull | undefined { + return readJson(CLIENT_FILE); + } + + saveClientInformation(info: OAuthClientInformationFull): void { + writeJson(CLIENT_FILE, info); + } + + tokens(): OAuthTokens | undefined { + return readJson(TOKENS_FILE); + } + + saveTokens(tokens: OAuthTokens): void { + writeJson(TOKENS_FILE, tokens); + } + + async redirectToAuthorization(authorizationUrl: URL): Promise { + const url = authorizationUrl.toString(); + console.log(`\nOpening browser for authentication...\n ${url}\n`); + const cmd = + process.platform === "darwin" + ? "open" + : process.platform === "win32" + ? "start" + : "xdg-open"; + Bun.spawn([cmd, url]); + + // Set up a promise that will resolve with the auth code from the callback + this._authCodePromise = new Promise((resolve) => { + this._resolveAuthCode = resolve; + }); + + // Start a temporary HTTP server to capture the callback + const server = Bun.serve({ + port: 9876, + fetch: (req) => { + const reqUrl = new URL(req.url); + if (reqUrl.pathname === "/callback") { + const code = reqUrl.searchParams.get("code"); + if (code && this._resolveAuthCode) { + this._resolveAuthCode(code); + } + server.stop(); + return new Response( + "

Authentication successful!

You can close this tab.

", + { headers: { "Content-Type": "text/html" } }, + ); + } + return new Response("Not found", { status: 404 }); + }, + }); + } + + /** Wait for the OAuth callback and return the authorization code. */ + waitForAuthCode(): Promise { + if (!this._authCodePromise) { + throw new Error("No auth flow in progress"); + } + return this._authCodePromise; + } + + saveCodeVerifier(verifier: string): void { + ensureDir(); + writeFileSync(VERIFIER_FILE, verifier); + } + + codeVerifier(): string { + return readFileSync(VERIFIER_FILE, "utf-8"); + } +} + +// A logger that suppresses errors (used during the expected OAuth redirect flow) +const quietLogger = { + ...getLogger("arcade-mcp"), + error: () => {}, +}; + +export function createMcpServer(config: Config): MCPServerStreamableHttp { + const provider = new ArcadeOAuthProvider(); + const serverOptions = { + url: config.arcade_gateway_url, + name: "arcade", + authProvider: provider, + clientSessionTimeoutSeconds: 60, + timeout: 60_000, }; - const tools = toZod({ - tools: toolkit.items, - client, - userId: config.user_id, - executeFactory: executeOrAuthorizeZodToolWithLogging, - }).map(tool); + // Use a quiet logger for the initial connect attempt โ€” the SDK logs the + // expected UnauthorizedError as "Error initializing MCP server:" before + // re-throwing, which we handle below. + const server = new MCPServerStreamableHttp({ + ...serverOptions, + logger: quietLogger, + }); + + const originalConnect = server.connect.bind(server); + server.connect = async () => { + try { + await originalConnect(); + } catch (err) { + if (err instanceof UnauthorizedError) { + console.log("Waiting for browser authentication..."); + const code = await provider.waitForAuthCode(); + + await auth(provider, { + serverUrl: config.arcade_gateway_url!, + authorizationCode: code, + }); + + // Retry โ€” tokens are now saved, so auth will succeed + await originalConnect(); + } else { + throw err; + } + } + }; - return tools; + return server; }