diff --git a/CLAUDE.md b/CLAUDE.md index e8bd388..723d80f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,30 @@ This is the StackOne AI Node SDK - a TypeScript library that transforms OpenAPI - **Lazy Loading**: Tools are created on-demand to minimize memory usage - **Extensibility**: Hooks for parameter transformation and pre-execution logic +### TypeScript Exhaustiveness Checks + +When branching on string unions, prefer the `satisfies never` pattern to guarantee compile-time exhaustiveness without introducing temporary variables. Example from `RequestBuilder.buildFetchOptions`: + +```ts +switch (bodyType) { + case 'json': + // ... + break; + case 'form': + // ... + break; + case 'multipart-form': + // ... + break; + default: { + bodyType satisfies never; // raises a type error if a new variant is added + throw new Error(`Unsupported HTTP body type: ${String(bodyType)}`); + } +} +``` + +Use this approach to keep the union definition (`type HttpBodyType = 'json' | 'multipart-form' | 'form'`) and the switch statement in sync. Adding a new union member will cause TypeScript to report the missing case at compile time. + ### Testing Strategy Tests use Bun's built-in test runner with a Jest-compatible API. Key patterns: @@ -73,4 +97,4 @@ When modifying the codebase: - Run tests frequently during development - Use `bun run rebuild` after updating OpenAPI specs - Ensure all generated files are committed (they're not gitignored) -- Follow the existing patterns for error handling and logging \ No newline at end of file +- Follow the existing patterns for error handling and logging diff --git a/README.md b/README.md index 8ced2cd..2fe153b 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,27 @@ const currentAccountId = tools.getAccountId(); // Get the current account ID [View full example](examples/account-id-usage.ts) +### Loading the Latest Tool Catalog + +Call `fetchTools()` when you want the SDK to pull the current tool definitions directly from StackOne without maintaining local specs: + +```typescript +const toolset = new StackOneToolSet({ + baseUrl: 'https://api.stackone.com', +}); + +const tools = await toolset.fetchTools(); +const employeeTool = tools.getTool('hris_list_employees'); + +const result = await employeeTool?.execute({ + query: { limit: 5 }, +}); +``` + +`fetchTools()` reuses the credentials you already configured (for example via `STACKONE_API_KEY`) and binds the returned tool objects to StackOne's actions client. + +[View full example](examples/fetch-tools.ts) + ### File Upload The `StackOneToolSet` comes with built-in transformations for file uploads: diff --git a/bun.lock b/bun.lock index 6598346..77d2284 100644 --- a/bun.lock +++ b/bun.lock @@ -4,18 +4,22 @@ "": { "name": "stackone-ai-ts", "dependencies": { + "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", + "@stackone/stackone-client-ts": "^4.28.0", "json-schema": "^0.4.0", }, "devDependencies": { "@ai-sdk/openai": "^1.1.14", "@biomejs/biome": "^1.5.3", + "@hono/mcp": "^0.1.4", "@types/bun": "^1.2.4", "@types/json-schema": "^7.0.15", "@types/node": "^22.13.5", "@typescript/native-preview": "^7.0.0-dev.20250623.1", "ai": "^4.1.46", "fs-fixture": "^2.8.1", + "hono": "^4.9.10", "lint-staged": "^15.2.0", "mkdocs": "^0.0.1", "msw": "^2.10.4", @@ -25,6 +29,7 @@ "tsdown": "^0.15.6", "type-fest": "^4.41.0", "unplugin-unused": "^0.5.1", + "zod": "^3.23.8", }, "peerDependencies": { "ai": "4.x", @@ -81,6 +86,8 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@hono/mcp": ["@hono/mcp@0.1.4", "", { "peerDependencies": { "@modelcontextprotocol/sdk": "^1.12.0", "hono": ">=4.0.0" } }, "sha512-OOMZCXaCcKw6MZoXJH1caO1FAPjJoTKRVstgzRVE8GJ/LMXvm/3OTynZYex0b5NYdMQRemd+FUjdCcYkH1pxMQ=="], + "@inquirer/confirm": ["@inquirer/confirm@5.1.14", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q=="], "@inquirer/core": ["@inquirer/core@10.1.15", "", { "dependencies": { "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA=="], @@ -101,6 +108,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.19.1", "", { "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-3Y2h3MZKjec1eAqSTBclATlX+AbC6n1LgfVzRMJLt3v6w0RCYgwLrjbxPDbhsYHt6Wdqc/aCceNJYgj448ELQQ=="], + "@mswjs/interceptors": ["@mswjs/interceptors@0.39.4", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-B82DbrGVCIBrNEfRJbqUFB0eNz0wVzqbenEpmbE71XLVU4yKZbDnRBuxz+7udc/uM7LDWDD4sRJ5tISzHf2QkQ=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.6", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g=="], @@ -151,6 +160,8 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.41", "", {}, "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw=="], + "@stackone/stackone-client-ts": ["@stackone/stackone-client-ts@4.28.0", "", { "dependencies": { "zod": "^3.20.0" } }, "sha512-tQQllPevyl8Ondbmy2VBbBwbR/N8CUMK4w+sTVcentsGfVyrX8upH8cF+LNyyYyYuClk/r8RZ68lroyjmRL9FA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="], @@ -183,10 +194,14 @@ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251006.1", "", { "os": "win32", "cpu": "x64" }, "sha512-2q+I+xhnB4qAre1wB9OVUTZbcx9FJsdUUYn/Fazj1vhbhfHjZHXgF8oI0NQE8lpVFPixFnA8gJvyxJ/9KYNfXA=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "ai": ["ai@4.3.19", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q=="], + "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=="], + "ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="], "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], @@ -199,12 +214,20 @@ "birpc": ["birpc@2.6.1", "", {}, "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ=="], + "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=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -227,8 +250,16 @@ "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], @@ -237,6 +268,8 @@ "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], @@ -245,48 +278,106 @@ "dts-resolver": ["dts-resolver@2.1.2", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="], + "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=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], + "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + "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-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], + "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=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + "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=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fs-fixture": ["fs-fixture@2.9.0", "", {}, "sha512-I/DNkzRfL71uabv3270h+lLOlJrUbQDHXb6JZxN8cgiFn0pi0dCE+etE8pzCvMuguSq34ClGXInhoaSW+1RZpw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], "get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graphql": ["graphql@16.11.0", "", {}, "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], + "hono": ["hono@4.9.10", "", {}, "sha512-AlI15ijFyKTXR7eHo7QK7OR4RoKIedZvBuRjO8iy4zrxvlY5oFCdiRG/V/lFJHCNXJ0k72ATgnyzx8Yqa5arug=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "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=="], + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -299,6 +390,8 @@ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="], "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], @@ -311,10 +404,20 @@ "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], @@ -331,8 +434,18 @@ "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], "openai": ["openai@5.23.2", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-MQBzmTulj+MM5O8SKEk/gL8a7s5mktS9zUtAkU257WjvobGc9nKcBuVwjyEEcb9SI8a8Y2G/mzn3vm9n1Jlleg=="], @@ -343,6 +456,8 @@ "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], @@ -355,12 +470,24 @@ "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="], + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "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=="], + "publint": ["publint@0.3.14", "", { "dependencies": { "@publint/pack": "^0.1.2", "package-manager-detector": "^1.3.0", "picocolors": "^1.1.1", "sade": "^1.8.1" }, "bin": { "publint": "src/cli.js" } }, "sha512-14/VNBvWsrBeqWNDw8c/DK5ERcZBUwL1rnkVx18cQnF3zadr3GfoYtvD8mxi1dhkWpaPHp8kfi92MDbjMeW3qw=="], + "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=="], + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.1", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.7.0", "unpipe": "1.0.0" } }, "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA=="], + "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -379,16 +506,36 @@ "rolldown-plugin-dts": ["rolldown-plugin-dts@0.16.11", "", { "dependencies": { "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/types": "^7.28.4", "ast-kit": "^2.1.2", "birpc": "^2.6.1", "debug": "^4.4.3", "dts-resolver": "^2.1.2", "get-tsconfig": "^4.10.1", "magic-string": "^0.30.19" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.1.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-9IQDaPvPqTx3RjG2eQCK5GYZITo203BxKunGI80AGYicu1ySFTUyugicAaTZWRzFWh9DSnzkgNeMNbDWBbSs0w=="], + "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=="], + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], + + "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], @@ -419,6 +566,8 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], @@ -429,26 +578,36 @@ "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "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.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], "unconfig": ["unconfig@7.3.3", "", { "dependencies": { "@quansync/fs": "^0.1.5", "defu": "^6.1.4", "jiti": "^2.5.1", "quansync": "^0.2.11" } }, "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "unplugin": ["unplugin@2.3.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw=="], "unplugin-unused": ["unplugin-unused@0.5.3", "", { "dependencies": { "js-tokens": "^9.0.1", "picocolors": "^1.1.1", "pkg-types": "^2.3.0", "unplugin": "^2.3.10" } }, "sha512-YZ9xXe9UQDRwDg5X4nyptZgVQeHuT84GQBpf0tEap08XGwx5jCT29P1/LTuChqno/A9SFFG14LWE5Bg/YhKeBQ=="], "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], "yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="], @@ -471,12 +630,22 @@ "@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + "body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "express/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "finalhandler/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], + "log-update/slice-ansi": ["slice-ansi@7.1.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -487,6 +656,12 @@ "rolldown-plugin-dts/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "router/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + + "send/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], diff --git a/examples/README.md b/examples/README.md index 0691be8..262e5e2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -129,6 +129,14 @@ Shows how to use custom base URLs for development or self-hosted instances. - **API Calls**: No (dry run) - **Key Features**: Custom API endpoints, development setup +#### [`fetch-tools.ts`](./fetch-tools.ts) - Live Catalog Loading + +Illustrates how to pull the latest tool catalog from StackOne and execute a tool with the fetched definitions. + +- **Account ID**: HRIS +- **API Calls**: Yes (requires valid credentials) +- **Key Features**: Catalog refresh, zero local specs, production-style execution + ### Advanced Features #### [`experimental-document-handling.ts`](./experimental-document-handling.ts) - Document Processing diff --git a/examples/fetch-tools.ts b/examples/fetch-tools.ts new file mode 100644 index 0000000..fe25bfc --- /dev/null +++ b/examples/fetch-tools.ts @@ -0,0 +1,38 @@ +/** + * Example: fetch the latest StackOne tool catalog and execute a tool. + * + * Set `STACKONE_API_KEY` (and optionally `STACKONE_BASE_URL`) before running. + * By default the script exits early in test environments where a real key is + * not available. + */ + +import process from 'node:process'; +import { StackOneToolSet } from '../src'; + +const apiKey = process.env.STACKONE_API_KEY; +const isPlaceholderKey = !apiKey || apiKey === 'test-stackone-key'; +const shouldSkip = process.env.SKIP_FETCH_TOOLS_EXAMPLE !== '0' && isPlaceholderKey; + +if (shouldSkip) { + console.log( + 'Skipping fetch-tools example. Provide STACKONE_API_KEY and set SKIP_FETCH_TOOLS_EXAMPLE=0 to run.' + ); + process.exit(0); +} + +const toolset = new StackOneToolSet({ + baseUrl: process.env.STACKONE_BASE_URL ?? 'https://api.stackone.com', +}); + +const tools = await toolset.fetchTools(); +console.log(`Loaded ${tools.length} tools`); + +const tool = tools.getTool('hris_list_employees'); +if (!tool) { + throw new Error('Tool hris_list_employees not found in the catalog'); +} + +const result = await tool.execute({ + query: { limit: 5 }, +}); +console.log('Sample execution result:', JSON.stringify(result, null, 2)); diff --git a/package.json b/package.json index 4d97153..328cf78 100644 --- a/package.json +++ b/package.json @@ -36,18 +36,22 @@ "format": "biome format --write ." }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", + "@stackone/stackone-client-ts": "^4.28.0", "json-schema": "^0.4.0" }, "devDependencies": { "@ai-sdk/openai": "^1.1.14", "@biomejs/biome": "^1.5.3", + "@hono/mcp": "^0.1.4", "@types/bun": "^1.2.4", "@types/json-schema": "^7.0.15", "@types/node": "^22.13.5", "@typescript/native-preview": "^7.0.0-dev.20250623.1", "ai": "^4.1.46", "fs-fixture": "^2.8.1", + "hono": "^4.9.10", "lint-staged": "^15.2.0", "mkdocs": "^0.0.1", "msw": "^2.10.4", @@ -56,7 +60,8 @@ "publint": "^0.3.12", "tsdown": "^0.15.6", "type-fest": "^4.41.0", - "unplugin-unused": "^0.5.1" + "unplugin-unused": "^0.5.1", + "zod": "^3.23.8" }, "peerDependencies": { "ai": "4.x", diff --git a/src/mcp.ts b/src/mcp.ts new file mode 100644 index 0000000..63cb055 --- /dev/null +++ b/src/mcp.ts @@ -0,0 +1,55 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { version } from '../package.json'; + +interface MCPClientOptions { + baseUrl: string; + headers?: Record; +} + +interface MCPClient { + /** underlying MCP client */ + client: Client; + + /** underlying transport */ + transport: StreamableHTTPClientTransport; + + /** cleanup client and transport */ + [Symbol.asyncDispose](): Promise; +} + +/** + * Create a Model Context Protocol (MCP) client. + * + * @example + * ```ts + * import { createMCPClient } from '@stackone/ai'; + * + * await using clients = await createMCPClient({ + * baseUrl: 'https://api.modelcontextprotocol.org', + * headers: { + * 'Authorization': 'Bearer YOUR_API_KEY', + * }, + * }); + * ``` + */ +export async function createMCPClient({ baseUrl, headers }: MCPClientOptions): Promise { + const transport = new StreamableHTTPClientTransport(new URL(baseUrl), { + requestInit: { + headers, + }, + }); + + const client = new Client({ + name: 'StackOne AI SDK', + version, + }); + + return { + client, + transport, + async [Symbol.asyncDispose]() { + await Promise.all([client.close(), transport.close()]); + }, + }; +} diff --git a/src/modules/requestBuilder.ts b/src/modules/requestBuilder.ts index 7969b56..9042310 100644 --- a/src/modules/requestBuilder.ts +++ b/src/modules/requestBuilder.ts @@ -1,6 +1,7 @@ import { - type ExecuteConfig, type ExecuteOptions, + type HttpBodyType, + type HttpExecuteConfig, type JsonDict, ParameterLocation, } from '../types'; @@ -19,16 +20,16 @@ class ParameterSerializationError extends Error { } /** - * Builds and executes HTTP requests + * Builds and executes HTTP requests for tools declared with kind:'http'. */ export class RequestBuilder { private method: string; private url: string; - private bodyType: 'json' | 'multipart-form' | 'form'; - private params: ExecuteConfig['params']; + private bodyType: HttpBodyType; + private params: HttpExecuteConfig['params']; private headers: Record; - constructor(config: ExecuteConfig, headers: Record = {}) { + constructor(config: HttpExecuteConfig, headers: Record = {}) { this.method = config.method; this.url = config.url; this.bodyType = config.bodyType; @@ -144,6 +145,10 @@ export class RequestBuilder { // Don't set Content-Type for FormData, it will be set automatically with the boundary break; } + default: { + this.bodyType satisfies never; + throw new Error(`Unsupported HTTP body type: ${String(this.bodyType)}`); + } } } diff --git a/src/modules/tests/requestBuilder.spec.ts b/src/modules/tests/requestBuilder.spec.ts index 676fb8f..a19890b 100644 --- a/src/modules/tests/requestBuilder.spec.ts +++ b/src/modules/tests/requestBuilder.spec.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { http, HttpResponse } from 'msw'; import { server } from '../../../mocks/node.ts'; -import { ParameterLocation } from '../../types'; +import { type HttpExecuteConfig, ParameterLocation } from '../../types'; import { StackOneAPIError } from '../../utils/errors'; import { RequestBuilder } from '../requestBuilder'; @@ -16,27 +16,28 @@ describe('RequestBuilder', () => { return recordedRequests; }; const mockConfig = { + kind: 'http', method: 'GET', url: 'https://api.example.com/test/{pathParam}', - bodyType: 'json' as const, + bodyType: 'json', params: [ - { name: 'pathParam', location: ParameterLocation.PATH, type: 'string' as const }, - { name: 'queryParam', location: ParameterLocation.QUERY, type: 'string' as const }, - { name: 'headerParam', location: ParameterLocation.HEADER, type: 'string' as const }, - { name: 'bodyParam', location: ParameterLocation.BODY, type: 'string' as const }, - { name: 'defaultParam', location: ParameterLocation.BODY, type: 'string' as const }, - { name: 'filter', location: ParameterLocation.QUERY, type: 'object' as const }, - { name: 'proxy', location: ParameterLocation.QUERY, type: 'object' as const }, - { name: 'regularObject', location: ParameterLocation.QUERY, type: 'object' as const }, - { name: 'simple', location: ParameterLocation.QUERY, type: 'string' as const }, - { name: 'simpleString', location: ParameterLocation.QUERY, type: 'string' as const }, - { name: 'simpleNumber', location: ParameterLocation.QUERY, type: 'number' as const }, - { name: 'simpleBoolean', location: ParameterLocation.QUERY, type: 'boolean' as const }, - { name: 'complexObject', location: ParameterLocation.QUERY, type: 'object' as const }, - { name: 'deepFilter', location: ParameterLocation.QUERY, type: 'object' as const }, - { name: 'emptyFilter', location: ParameterLocation.QUERY, type: 'object' as const }, + { name: 'pathParam', location: ParameterLocation.PATH, type: 'string' }, + { name: 'queryParam', location: ParameterLocation.QUERY, type: 'string' }, + { name: 'headerParam', location: ParameterLocation.HEADER, type: 'string' }, + { name: 'bodyParam', location: ParameterLocation.BODY, type: 'string' }, + { name: 'defaultParam', location: ParameterLocation.BODY, type: 'string' }, + { name: 'filter', location: ParameterLocation.QUERY, type: 'object' }, + { name: 'proxy', location: ParameterLocation.QUERY, type: 'object' }, + { name: 'regularObject', location: ParameterLocation.QUERY, type: 'object' }, + { name: 'simple', location: ParameterLocation.QUERY, type: 'string' }, + { name: 'simpleString', location: ParameterLocation.QUERY, type: 'string' }, + { name: 'simpleNumber', location: ParameterLocation.QUERY, type: 'number' }, + { name: 'simpleBoolean', location: ParameterLocation.QUERY, type: 'boolean' }, + { name: 'complexObject', location: ParameterLocation.QUERY, type: 'object' }, + { name: 'deepFilter', location: ParameterLocation.QUERY, type: 'object' }, + { name: 'emptyFilter', location: ParameterLocation.QUERY, type: 'object' }, ], - }; + } satisfies HttpExecuteConfig; beforeEach(() => { builder = new RequestBuilder(mockConfig, { 'Initial-Header': 'test' }); diff --git a/src/openapi/parser.ts b/src/openapi/parser.ts index 7bd260d..11baed6 100644 --- a/src/openapi/parser.ts +++ b/src/openapi/parser.ts @@ -1,6 +1,6 @@ import type { JSONSchema7 as JsonSchema } from 'json-schema'; import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'; -import { ParameterLocation, type ToolDefinition } from '../types'; +import { type HttpExecuteConfig, ParameterLocation, type ToolDefinition } from '../types'; // Define a type for OpenAPI document type OpenAPIDocument = OpenAPIV3.Document | OpenAPIV3_1.Document; @@ -67,6 +67,23 @@ export class OpenAPIParser { return servers.length > 0 ? servers[0].url : 'https://api.stackone.com'; } + private normalizeBodyType(bodyType: string | null): HttpExecuteConfig['bodyType'] { + // Map OpenAPI content types into the narrower set supported by ExecuteConfig. + if (!bodyType) { + return 'json'; + } + + if (bodyType === 'form-data' || bodyType === 'multipart-form') { + return 'multipart-form'; + } + + if (bodyType === 'form' || bodyType === 'application/x-www-form-urlencoded') { + return 'form'; + } + + return 'json'; + } + /** * Create a parser from a JSON string * @param specString OpenAPI specification as a JSON string @@ -552,6 +569,22 @@ export class OpenAPIParser { ); // Create tool definition with deep copies to prevent shared state + const executeConfig = { + kind: 'http', + method: method.toUpperCase(), + url: `${this._baseUrl}${path}`, + bodyType: this.normalizeBodyType(bodyType), + params: Object.entries(parameterLocations) + .filter(([name]) => !this.isRemovedParam(name)) + .map(([name, location]) => { + return { + name, + location, + type: (filteredProperties[name]?.type as JsonSchema['type']) || 'string', + }; + }), + } satisfies HttpExecuteConfig; + tools[name] = { description: operation.summary || '', parameters: { @@ -559,20 +592,7 @@ export class OpenAPIParser { properties: filteredProperties, required: filteredRequired, }, - execute: { - method: method.toUpperCase(), - url: `${this._baseUrl}${path}`, - bodyType: (bodyType as 'json' | 'multipart-form') || 'json', - params: Object.entries(parameterLocations) - .filter(([name]) => !this.isRemovedParam(name)) - .map(([name, location]) => { - return { - name, - location, - type: (filteredProperties[name]?.type as JsonSchema['type']) || 'string', - }; - }), - }, + execute: executeConfig, }; } catch (operationError) { console.error(`Error processing operation ${name}: ${operationError}`); diff --git a/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap b/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap index 4c67f47..55d8f6e 100644 --- a/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap +++ b/src/openapi/tests/__snapshots__/openapi-parser.spec.ts.snap @@ -6,6 +6,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Batch Upload Employee Document", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -1408,6 +1409,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Cancel Employee Time Off Request", "execute": { "bodyType": "json", + "kind": "http", "method": "DELETE", "params": [ { @@ -1452,6 +1454,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Create Employee", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -4101,6 +4104,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Create Employee Employment", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -4562,6 +4566,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Create Employee Skill", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -4725,6 +4730,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Create Employee Time Off Request", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -4934,6 +4940,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Create Employee Work Eligibility Request", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -6675,6 +6682,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Download Employee Document", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6739,6 +6747,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Benefit", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6803,6 +6812,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Company", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6867,6 +6877,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Company Group", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6931,6 +6942,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Cost Center Group", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6995,6 +7007,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Department Group", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7059,6 +7072,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Division Group", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7123,6 +7137,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7207,6 +7222,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get employee Custom Field Definition", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7307,6 +7323,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Document", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7380,6 +7397,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Document Category", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7444,6 +7462,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Employment", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7527,6 +7546,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Shift", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7599,6 +7619,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Skill", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7672,6 +7693,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Task", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7755,6 +7777,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employee Time Off Balance", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7838,6 +7861,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employees Time Off Request", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7921,6 +7945,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employees Work Eligibility", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -7994,6 +8019,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Employment", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8068,6 +8094,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Group", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8132,6 +8159,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Job", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8196,6 +8224,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Work Location", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8260,6 +8289,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Position", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8323,6 +8353,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Shift", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8386,6 +8417,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Task", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8460,6 +8492,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Team Group", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8524,6 +8557,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Time Entry", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8588,6 +8622,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get Time Off Policy", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8652,6 +8687,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get time off request", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8726,6 +8762,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Get time off type", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8790,6 +8827,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Invite Employee", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -8838,6 +8876,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List benefits", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -8928,6 +8967,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Companies", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9018,6 +9058,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Companies Groups", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9108,6 +9149,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Cost Center Groups", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9198,6 +9240,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Department Groups", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9288,6 +9331,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Division Groups", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9378,6 +9422,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Document Categories", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9468,6 +9513,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List employee Custom Field Definitions", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9558,6 +9604,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Documents", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9658,6 +9705,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Employments", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9768,6 +9816,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Shifts", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9890,6 +9939,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Skills", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9990,6 +10040,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Tasks", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10100,6 +10151,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Time Off Balances", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10218,6 +10270,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Assigned Time Off Policies", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10348,6 +10401,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Time Off Requests", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10480,6 +10534,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employee Work Eligibility", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10580,6 +10635,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employees", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10698,6 +10754,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Employments", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10798,6 +10855,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Groups", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10888,6 +10946,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Jobs", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10978,6 +11037,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Work Locations", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11068,6 +11128,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Positions", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11174,6 +11235,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Shifts", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11286,6 +11348,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Tasks", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11386,6 +11449,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Team Groups", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11476,6 +11540,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Time Entries", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11583,6 +11648,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List Time Off Policies", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11703,6 +11769,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List time off requests", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11825,6 +11892,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "List time off types", "execute": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -11915,6 +11983,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Update Employee", "execute": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -14547,6 +14616,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Update Employee Employment", "execute": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -15007,6 +15077,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Update Employee Task", "execute": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -15108,6 +15179,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Update Employee Time Off Request", "execute": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -15326,6 +15398,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Update Employee Work Eligibility Request", "execute": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -17076,6 +17149,7 @@ exports[`OpenAPIParser Snapshot Tests should parse all OpenAPI specs correctly 1 "description": "Upload Employee Document", "execute": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { diff --git a/src/tests/json-schema.spec.ts b/src/tests/json-schema.spec.ts index 1d7b3fe..4322153 100644 --- a/src/tests/json-schema.spec.ts +++ b/src/tests/json-schema.spec.ts @@ -86,6 +86,7 @@ const createArrayTestTool = (): StackOneTool => { }, }, { + kind: 'http', method: 'GET', url: 'https://example.com/test', bodyType: 'json', @@ -127,6 +128,7 @@ const createNestedArrayTestTool = (): StackOneTool => { }, }, { + kind: 'http', method: 'GET', url: 'https://example.com/test', bodyType: 'json', diff --git a/src/tests/meta-tools.spec.ts b/src/tests/meta-tools.spec.ts index 4b3b5ff..d3e13aa 100644 --- a/src/tests/meta-tools.spec.ts +++ b/src/tests/meta-tools.spec.ts @@ -20,6 +20,7 @@ const createMockTools = (): BaseTool[] => { required: ['name', 'email'], }, { + kind: 'http', method: 'POST', url: 'https://api.example.com/hris/employees', bodyType: 'json', @@ -39,6 +40,7 @@ const createMockTools = (): BaseTool[] => { }, }, { + kind: 'http', method: 'GET', url: 'https://api.example.com/hris/employees', bodyType: 'json', @@ -67,6 +69,7 @@ const createMockTools = (): BaseTool[] => { required: ['employeeId', 'startDate', 'endDate'], }, { + kind: 'http', method: 'POST', url: 'https://api.example.com/hris/time-off', bodyType: 'json', @@ -89,6 +92,7 @@ const createMockTools = (): BaseTool[] => { required: ['name', 'email'], }, { + kind: 'http', method: 'POST', url: 'https://api.example.com/ats/candidates', bodyType: 'json', @@ -108,6 +112,7 @@ const createMockTools = (): BaseTool[] => { }, }, { + kind: 'http', method: 'GET', url: 'https://api.example.com/ats/candidates', bodyType: 'json', @@ -136,6 +141,7 @@ const createMockTools = (): BaseTool[] => { required: ['name'], }, { + kind: 'http', method: 'POST', url: 'https://api.example.com/crm/contacts', bodyType: 'json', diff --git a/src/tests/tool.spec.ts b/src/tests/tool.spec.ts index a3ea060..92d1a6c 100644 --- a/src/tests/tool.spec.ts +++ b/src/tests/tool.spec.ts @@ -12,6 +12,7 @@ const createMockTool = (headers?: Record): BaseTool => { properties: { id: { type: 'string', description: 'ID parameter' } }, }; const executeConfig: ExecuteConfig = { + kind: 'http', method: 'GET', url: 'https://api.example.com/test/{id}', bodyType: 'json', @@ -113,6 +114,26 @@ describe('StackOneTool', () => { expect(schema.properties.id.type).toBe('string'); }); + it('should include execution metadata by default in AI SDK conversion', () => { + const tool = createMockTool(); + + const aiSdkTool = tool.toAISDK(); + const execution = aiSdkTool.test_tool.execution; + + expect(execution).toBeDefined(); + expect(execution?.config.method).toBe('GET'); + expect(execution?.config.url).toBe('https://api.example.com/test/{id}'); + expect(execution?.headers).toEqual({}); + }); + + it('should allow disabling execution metadata exposure for AI SDK conversion', () => { + const tool = createMockTool().setExposeExecutionMetadata(false); + + const aiSdkTool = tool.toAISDK(); + + expect(aiSdkTool.test_tool.execution).toBeUndefined(); + }); + it('should convert complex parameter types to zod schema', () => { const complexTool = new BaseTool( 'complex_tool', @@ -136,6 +157,7 @@ describe('StackOneTool', () => { }, }, { + kind: 'http', method: 'GET', url: 'https://example.com/complex', bodyType: 'json', @@ -229,6 +251,7 @@ describe('Tools', () => { properties: { id: { type: 'string' } }, }, { + kind: 'http', method: 'GET', url: 'https://api.example.com/test/{id}', bodyType: 'json', @@ -250,6 +273,7 @@ describe('Tools', () => { properties: { id: { type: 'string' } }, }, { + kind: 'http', method: 'GET', url: 'https://api.example.com/test/{id}', bodyType: 'json', @@ -286,6 +310,7 @@ describe('Tools', () => { properties: { name: { type: 'string' } }, }, { + kind: 'http', method: 'POST', url: 'https://api.example.com/test', bodyType: 'json', @@ -323,6 +348,7 @@ describe('Tools', () => { properties: { name: { type: 'string' } }, }, { + kind: 'http', method: 'POST', url: 'https://api.example.com/test', bodyType: 'json', diff --git a/src/tool.ts b/src/tool.ts index e4a97f2..313ef24 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -7,7 +7,11 @@ import type { ExecuteOptions, Experimental_PreExecuteFunction, Experimental_ToolCreationOptions, + HttpExecuteConfig, JsonDict, + LocalExecuteConfig, + RpcExecuteConfig, + ToolExecution, ToolParameters, } from './types'; import { StackOneError } from './utils/errors'; @@ -21,8 +25,46 @@ export class BaseTool { description: string; parameters: ToolParameters; executeConfig: ExecuteConfig; - protected requestBuilder: RequestBuilder; + protected requestBuilder?: RequestBuilder; protected experimental_preExecute?: Experimental_PreExecuteFunction; + #exposeExecutionMetadata = true; + #headers: Record; + + private createExecutionMetadata(): ToolExecution { + const config = (() => { + switch (this.executeConfig.kind) { + case 'http': + return { + kind: 'http', + method: this.executeConfig.method, + url: this.executeConfig.url, + bodyType: this.executeConfig.bodyType, + params: this.executeConfig.params.map((param) => ({ ...param })), + } satisfies HttpExecuteConfig; + case 'rpc': + return { + kind: 'rpc', + method: this.executeConfig.method, + url: this.executeConfig.url, + payloadKeys: { ...this.executeConfig.payloadKeys }, + } satisfies RpcExecuteConfig; + case 'local': + return { + kind: 'local', + identifier: this.executeConfig.identifier, + description: this.executeConfig.description, + } satisfies LocalExecuteConfig; + default: + this.executeConfig satisfies never; + throw new StackOneError('Unsupported executeConfig kind'); + } + })(); + + return { + config, + headers: this.getHeaders(), + }; + } constructor( name: string, @@ -36,7 +78,10 @@ export class BaseTool { this.description = description; this.parameters = parameters; this.executeConfig = executeConfig; - this.requestBuilder = new RequestBuilder(executeConfig, headers); + this.#headers = { ...(headers ?? {}) }; + if (executeConfig.kind === 'http') { + this.requestBuilder = new RequestBuilder(executeConfig, this.#headers); + } this.experimental_preExecute = experimental_preExecute; } @@ -44,7 +89,10 @@ export class BaseTool { * Set headers for this tool */ setHeaders(headers: Record): BaseTool { - this.requestBuilder.setHeaders(headers); + this.#headers = { ...this.#headers, ...headers }; + if (this.requestBuilder) { + this.requestBuilder.setHeaders(headers); + } return this; } @@ -52,7 +100,20 @@ export class BaseTool { * Get the current headers */ getHeaders(): Record { - return this.requestBuilder.getHeaders(); + if (this.requestBuilder) { + const currentHeaders = this.requestBuilder.getHeaders(); + this.#headers = { ...currentHeaders }; + return currentHeaders; + } + return { ...this.#headers }; + } + + /** + * Control whether execution metadata should be exposed in AI SDK conversions. + */ + setExposeExecutionMetadata(expose: boolean): this { + this.#exposeExecutionMetadata = expose; + return this; } /** @@ -60,6 +121,12 @@ export class BaseTool { */ async execute(inputParams?: JsonDict | string, options?: ExecuteOptions): Promise { try { + if (!this.requestBuilder || this.executeConfig.kind !== 'http') { + // Non-HTTP tools provide their own execute override (e.g. RPC, local meta tools). + throw new StackOneError( + 'BaseTool.execute is only available for HTTP-backed tools. Provide a custom execute implementation for non-HTTP tools.' + ); + } // Validate params is either undefined, string, or object if ( inputParams !== undefined && @@ -114,7 +181,11 @@ export class BaseTool { /** * Convert the tool to AI SDK format */ - toAISDK(options: { executable?: boolean } = { executable: true }): ToolSet { + toAISDK( + options: { executable?: boolean; execution?: ToolExecution | false } = { + executable: true, + } + ): ToolSet { const schema = { type: 'object' as const, properties: this.parameters.properties || {}, @@ -122,19 +193,35 @@ export class BaseTool { additionalProperties: false, }; + const toolDefinition: Record = { + parameters: jsonSchema(schema), + description: this.description, + }; + + const executionOption = + options.execution !== undefined + ? options.execution + : this.#exposeExecutionMetadata + ? this.createExecutionMetadata() + : false; + + if (executionOption !== false) { + toolDefinition.execution = executionOption; + } + + if (options.executable ?? true) { + toolDefinition.execute = async (args: Record) => { + try { + return await this.execute(args as JsonDict); + } catch (error) { + return `Error executing tool: ${error instanceof Error ? error.message : String(error)}`; + } + }; + } + return { [this.name]: { - parameters: jsonSchema(schema), - description: this.description, - ...(options.executable && { - execute: async (args: Record) => { - try { - return await this.execute(args as JsonDict); - } catch (error) { - return `Error executing tool: ${error instanceof Error ? error.message : String(error)}`; - } - }, - }), + ...toolDefinition, }, } as ToolSet; } @@ -257,10 +344,14 @@ export class Tools implements Iterable { /** * Convert all tools to AI SDK format */ - toAISDK(): ToolSet { + toAISDK( + options: { executable?: boolean; execution?: ToolExecution | false } = { + executable: true, + } + ): ToolSet { const result: ToolSet = {}; for (const tool of this.tools) { - Object.assign(result, tool.toAISDK()); + Object.assign(result, tool.toAISDK(options)); } return result; } @@ -405,11 +496,10 @@ export function metaSearchTools(oramaDb: OramaDb, allTools: BaseTool[]): BaseToo } as const satisfies ToolParameters; const executeConfig = { - method: 'LOCAL', - url: 'local://get-relevant-tools', - bodyType: 'json', - params: [], - } as const satisfies ExecuteConfig; + kind: 'local', + identifier: name, + description: 'local://get-relevant-tools', + } as const satisfies LocalExecuteConfig; const tool = new BaseTool(name, description, parameters, executeConfig); tool.execute = async (inputParams?: JsonDict | string): Promise => { @@ -489,11 +579,10 @@ export function metaExecuteTool(tools: Tools): BaseTool { } as const satisfies ToolParameters; const executeConfig = { - method: 'LOCAL', - url: 'local://execute-tool', - bodyType: 'json', - params: [], - } as const satisfies ExecuteConfig; + kind: 'local', + identifier: name, + description: 'local://execute-tool', + } as const satisfies LocalExecuteConfig; // Create the tool instance const tool = new BaseTool(name, description, parameters, executeConfig); diff --git a/src/toolsets/base.ts b/src/toolsets/base.ts index 6764131..23ef4a1 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -1,7 +1,21 @@ +import { StackOne } from '@stackone/stackone-client-ts'; import type { Arrayable } from 'type-fest'; -import { type BaseTool, Tools } from '../tool'; -import type { Experimental_ToolCreationOptions } from '../types'; +import { createMCPClient } from '../mcp'; +import { BaseTool, Tools } from '../tool'; +import type { + ExecuteOptions, + Experimental_ToolCreationOptions, + JsonDict, + JsonSchemaProperties, + RpcExecuteConfig, + ToolParameters, +} from '../types'; import { toArray } from '../utils/array'; +import { StackOneError } from '../utils/errors'; + +type ToolInputSchema = Awaited< + ReturnType>['client']['listTools']> +>['tools'][number]['inputSchema']; /** * Base exception for toolset errors @@ -54,6 +68,7 @@ export interface BaseToolSetConfig { authentication?: AuthenticationConfig; headers?: Record; _oasUrl?: string; + stackOneClient?: StackOne; } /** @@ -63,6 +78,7 @@ export abstract class ToolSet { protected baseUrl?: string; protected authentication?: AuthenticationConfig; protected headers: Record; + protected stackOneClient?: StackOne; protected tools: BaseTool[] = []; /** @@ -73,6 +89,7 @@ export abstract class ToolSet { this.baseUrl = config?.baseUrl; this.authentication = config?.authentication; this.headers = config?.headers || {}; + this.stackOneClient = config?.stackOneClient; // Set Authentication headers if provided if (this.authentication) { @@ -230,4 +247,207 @@ export abstract class ToolSet { } return tool; } + + /** + * Fetch tool definitions from MCP (if applicable) + */ + async fetchTools(): Promise { + if (!this.baseUrl) { + throw new ToolSetConfigError('baseUrl is required to fetch MCP tools'); + } + + // TODO(ENG-????): allow passing account/provider/action filters when fetching tools + // e.g. fetchTools({ accountIDs: ['123'], actions: ['*_list_employees'] }) + // and eventually expose helpers like stackone.setAccounts([...]) for meta usage. + await using clients = await createMCPClient({ + baseUrl: `${this.baseUrl}/mcp`, + headers: this.headers, + }); + + await clients.client.connect(clients.transport); + const listToolsResult = await clients.client.listTools(); + const actionsClient = this.getActionsClient(); + + const tools = listToolsResult.tools.map(({ name, description, inputSchema }) => + this.createRpcBackedTool({ + actionsClient, + name, + description, + inputSchema, + }) + ); + + return new Tools(tools); + } + + private getActionsClient(): StackOne { + if (this.stackOneClient) { + return this.stackOneClient; + } + + const credentials = this.authentication?.credentials ?? {}; + const apiKeyFromAuth = + this.authentication?.type === 'basic' + ? credentials.username + : this.authentication?.type === 'bearer' + ? credentials.token + : credentials.username; + + const apiKey = apiKeyFromAuth || process.env.STACKONE_API_KEY; + const password = this.authentication?.type === 'basic' ? (credentials.password ?? '') : ''; + + if (!apiKey) { + throw new ToolSetConfigError( + 'StackOne API key is required to create an actions client. Provide stackOneClient, configure authentication credentials, or set the STACKONE_API_KEY environment variable.' + ); + } + + this.stackOneClient = new StackOne({ + serverURL: this.baseUrl, + security: { + username: apiKey, + password, + }, + }); + + return this.stackOneClient; + } + + private createRpcBackedTool({ + actionsClient, + name, + description, + inputSchema, + }: { + actionsClient: StackOne; + name: string; + description?: string; + inputSchema: ToolInputSchema; + }): BaseTool { + const executeConfig = { + kind: 'rpc', + method: 'POST', + url: `${this.baseUrl}/actions/rpc`, + payloadKeys: { + action: 'action', + body: 'body', + headers: 'headers', + path: 'path', + query: 'query', + }, + } as const satisfies RpcExecuteConfig; // Mirrors StackOne RPC payload layout so metadata/debug stays in sync. + + const toolParameters = { + ...inputSchema, + + // properties are not well typed in MCP spec + properties: inputSchema?.properties as JsonSchemaProperties, + } satisfies ToolParameters; + + const tool = new BaseTool( + name, + description ?? '', + toolParameters, + executeConfig, + this.headers + ).setExposeExecutionMetadata(false); + + tool.execute = async ( + inputParams?: JsonDict | string, + options?: ExecuteOptions + ): Promise => { + try { + if ( + inputParams !== undefined && + typeof inputParams !== 'object' && + typeof inputParams !== 'string' + ) { + throw new StackOneError( + `Invalid parameters type. Expected object or string, got ${typeof inputParams}. Parameters: ${JSON.stringify(inputParams)}` + ); + } + + const parsedParams = + typeof inputParams === 'string' ? JSON.parse(inputParams) : (inputParams ?? {}); + + const currentHeaders = tool.getHeaders(); + const actionHeaders = this.buildActionHeaders(currentHeaders); + + const pathParams = this.extractRecord(parsedParams, 'path'); + const queryParams = this.extractRecord(parsedParams, 'query'); + const additionalHeaders = this.extractRecord(parsedParams, 'headers'); + if (additionalHeaders) { + for (const [key, value] of Object.entries(additionalHeaders)) { + if (value === undefined || value === null) continue; + actionHeaders[key] = String(value); + } + } + + const bodyPayload = this.extractRecord(parsedParams, 'body'); + const rpcBody: JsonDict = bodyPayload ? { ...bodyPayload } : {}; + for (const [key, value] of Object.entries(parsedParams)) { + if (key === 'body' || key === 'headers' || key === 'path' || key === 'query') { + continue; + } + rpcBody[key] = value as unknown; + } + + if (options?.dryRun) { + const requestPayload = { + action: name, + body: rpcBody, + headers: actionHeaders, + path: pathParams ?? undefined, + query: queryParams ?? undefined, + }; + + return { + url: executeConfig.url, + method: executeConfig.method, + headers: actionHeaders, + body: JSON.stringify(requestPayload), + mappedParams: parsedParams, + } satisfies JsonDict; + } + + const response = await actionsClient.actions.rpcAction({ + action: name, + body: rpcBody, + headers: actionHeaders, + path: pathParams ?? undefined, + query: queryParams ?? undefined, + }); + + return (response.actionsRpcResponse ?? {}) as JsonDict; + } catch (error) { + if (error instanceof StackOneError) { + throw error; + } + throw new StackOneError( + `Error executing RPC action ${name}: ${error instanceof Error ? error.message : String(error)}` + ); + } + }; + + return tool; + } + + private buildActionHeaders(headers: Record): Record { + const sanitizedEntries = Object.entries(headers).filter( + ([key]) => key.toLowerCase() !== 'authorization' + ); + + return Object.fromEntries(sanitizedEntries.map(([key, value]) => [key, String(value)])); + } + + private extractRecord( + params: JsonDict, + key: 'body' | 'headers' | 'path' | 'query' + ): JsonDict | undefined { + const value = params[key]; + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + return value as JsonDict; + } + return undefined; + } } diff --git a/src/toolsets/tests/__snapshots__/stackone.spec.ts.snap b/src/toolsets/tests/__snapshots__/stackone.spec.ts.snap index 300f9fb..900e1be 100644 --- a/src/toolsets/tests/__snapshots__/stackone.spec.ts.snap +++ b/src/toolsets/tests/__snapshots__/stackone.spec.ts.snap @@ -7,6 +7,7 @@ Tools { "description": "List Companies", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -141,6 +142,7 @@ Tools { "description": "Get Company", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -239,6 +241,7 @@ Tools { "description": "List employee Custom Field Definitions", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -373,6 +376,7 @@ Tools { "description": "Get employee Custom Field Definition", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -522,6 +526,7 @@ Tools { "description": "List Employees", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -694,6 +699,7 @@ Tools { "description": "Create Employee", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -3177,6 +3183,7 @@ Tools { "description": "Get Employee", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -3305,6 +3312,7 @@ Tools { "description": "Update Employee", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -5771,6 +5779,7 @@ Tools { "description": "Invite Employee", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -5843,6 +5852,7 @@ Tools { "description": "List Employee Shifts", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6014,6 +6024,7 @@ Tools { "description": "Get Employee Shift", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6125,6 +6136,7 @@ Tools { "description": "List Employee Time Off Requests", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6311,6 +6323,7 @@ Tools { "description": "Create Employee Time Off Request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -6569,6 +6582,7 @@ Tools { "description": "Get Employees Time Off Request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -6696,6 +6710,7 @@ Tools { "description": "Cancel Employee Time Off Request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "DELETE", "params": [ { @@ -6764,6 +6779,7 @@ Tools { "description": "Update Employee Time Off Request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -7036,6 +7052,7 @@ Tools { "description": "Batch Upload Employee Document", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -8415,6 +8432,7 @@ Tools { "description": "Upload Employee Document", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -9844,6 +9862,7 @@ Tools { "description": "Download Employee Document", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -9942,6 +9961,7 @@ Tools { "description": "List Employee Documents", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10091,6 +10111,7 @@ Tools { "description": "Get Employee Document", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10203,6 +10224,7 @@ Tools { "description": "List Employee Document Categories", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10337,6 +10359,7 @@ Tools { "description": "Get Employee Document Category", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10435,6 +10458,7 @@ Tools { "description": "List Employee Work Eligibility", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -10584,6 +10608,7 @@ Tools { "description": "Create Employee Work Eligibility Request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -12303,6 +12328,7 @@ Tools { "description": "Get Employees Work Eligibility", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -12415,6 +12441,7 @@ Tools { "description": "Update Employee Work Eligibility Request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -14148,6 +14175,7 @@ Tools { "description": "List Employee Time Off Balances", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -14320,6 +14348,7 @@ Tools { "description": "Get Employee Time Off Balance", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -14447,6 +14476,7 @@ Tools { "description": "List Employments", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -14596,6 +14626,7 @@ Tools { "description": "Get Employment", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -14709,6 +14740,7 @@ Tools { "description": "List Employee Employments", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -14873,6 +14905,7 @@ Tools { "description": "Create Employee Employment", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -15328,6 +15361,7 @@ Tools { "description": "Get Employee Employment", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -15455,6 +15489,7 @@ Tools { "description": "Update Employee Employment", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -15909,6 +15944,7 @@ Tools { "description": "List Groups", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16043,6 +16079,7 @@ Tools { "description": "List Department Groups", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16177,6 +16214,7 @@ Tools { "description": "List Cost Center Groups", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16311,6 +16349,7 @@ Tools { "description": "List Team Groups", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16445,6 +16484,7 @@ Tools { "description": "List Division Groups", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16579,6 +16619,7 @@ Tools { "description": "List Companies Groups", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16713,6 +16754,7 @@ Tools { "description": "Get Group", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16811,6 +16853,7 @@ Tools { "description": "Get Department Group", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -16909,6 +16952,7 @@ Tools { "description": "Get Cost Center Group", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17007,6 +17051,7 @@ Tools { "description": "Get Team Group", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17105,6 +17150,7 @@ Tools { "description": "Get Division Group", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17203,6 +17249,7 @@ Tools { "description": "Get Company Group", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17301,6 +17348,7 @@ Tools { "description": "List Jobs", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17435,6 +17483,7 @@ Tools { "description": "Get Job", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17533,6 +17582,7 @@ Tools { "description": "List Work Locations", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17667,6 +17717,7 @@ Tools { "description": "Get Work Location", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17765,6 +17816,7 @@ Tools { "description": "List Positions", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -17920,6 +17972,7 @@ Tools { "description": "Get Position", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18017,6 +18070,7 @@ Tools { "description": "List Time Entries", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18168,6 +18222,7 @@ Tools { "description": "Get Time Entry", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18266,6 +18321,7 @@ Tools { "description": "List time off requests", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18437,6 +18493,7 @@ Tools { "description": "Get time off request", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18550,6 +18607,7 @@ Tools { "description": "List Shifts", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18706,6 +18764,7 @@ Tools { "description": "Get Shift", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18803,6 +18862,7 @@ Tools { "description": "List time off types", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -18937,6 +18997,7 @@ Tools { "description": "Get time off type", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19035,6 +19096,7 @@ Tools { "description": "List Time Off Policies", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19199,6 +19261,7 @@ Tools { "description": "Get Time Off Policy", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19297,6 +19360,7 @@ Tools { "description": "List Assigned Time Off Policies", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19476,6 +19540,7 @@ Tools { "description": "List benefits", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19610,6 +19675,7 @@ Tools { "description": "Get Benefit", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19708,6 +19774,7 @@ Tools { "description": "List Employee Skills", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -19857,6 +19924,7 @@ Tools { "description": "Create Employee Skill", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "POST", "params": [ { @@ -20014,6 +20082,7 @@ Tools { "description": "Get Employee Skill", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -20126,6 +20195,7 @@ Tools { "description": "List Employee Tasks", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -20290,6 +20360,7 @@ Tools { "description": "Get Employee Task", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -20417,6 +20488,7 @@ Tools { "description": "Update Employee Task", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "PATCH", "params": [ { @@ -20532,6 +20604,7 @@ Tools { "description": "List Tasks", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { @@ -20681,6 +20754,7 @@ Tools { "description": "Get Task", "executeConfig": { "bodyType": "json", + "kind": "http", "method": "GET", "params": [ { diff --git a/src/toolsets/tests/base.spec.ts b/src/toolsets/tests/base.spec.ts index 115b989..2f56779 100644 --- a/src/toolsets/tests/base.spec.ts +++ b/src/toolsets/tests/base.spec.ts @@ -78,6 +78,7 @@ describe('ToolSet', () => { properties: { id: { type: 'string' } }, }, { + kind: 'http', method: 'GET', url: 'https://api.example.com/hris/employees/{id}', bodyType: 'json', @@ -99,6 +100,7 @@ describe('ToolSet', () => { properties: { id: { type: 'string' } }, }, { + kind: 'http', method: 'GET', url: 'https://api.example.com/crm/contacts/{id}', bodyType: 'json', diff --git a/src/toolsets/tests/openapi.spec.ts b/src/toolsets/tests/openapi.spec.ts index a84bdb1..9af7e4a 100644 --- a/src/toolsets/tests/openapi.spec.ts +++ b/src/toolsets/tests/openapi.spec.ts @@ -37,6 +37,7 @@ describe('OpenAPIToolSet', () => { required: ['id'], }, execute: { + kind: 'http', method: 'GET', url: 'https://petstore.swagger.io/v2/pet/{id}', bodyType: 'json', diff --git a/src/toolsets/tests/stackone.mcp-fetch.spec.ts b/src/toolsets/tests/stackone.mcp-fetch.spec.ts new file mode 100644 index 0000000..31609b8 --- /dev/null +++ b/src/toolsets/tests/stackone.mcp-fetch.spec.ts @@ -0,0 +1,119 @@ +import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test'; +import { StreamableHTTPTransport } from '@hono/mcp'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { StackOne } from '@stackone/stackone-client-ts'; +import { Hono } from 'hono'; +import { z } from 'zod'; +import { server as mswServer } from '../../../mocks/node'; +import { ToolSet } from '../base'; + +type MockTool = { + name: string; + description?: string; + shape: z.ZodRawShape; +}; + +async function createMockMcpServer(tools: MockTool[]) { + const mcp = new McpServer({ name: 'test-mcp', version: '1.0.0' }); + + for (const tool of tools) { + mcp.registerTool( + tool.name, + { + description: tool.description, + inputSchema: tool.shape, + }, + async ({ params }) => ({ + content: [], + structuredContent: params.arguments ?? {}, + _meta: undefined, + }) + ); + } + + const app = new Hono(); + app.all('/mcp', async (c) => { + const transport = new StreamableHTTPTransport(); + await mcp.connect(transport); + return transport.handleRequest(c); + }); + + const server = Bun.serve({ port: 0, fetch: app.fetch }); + const origin = server.url.toString().replace(/\/$/, ''); + + return { + origin, + close: () => server.stop(), + } as const; +} + +describe('ToolSet.fetchTools (MCP + RPC integration)', () => { + const mockTools = [ + { + name: 'dummy_action', + description: 'Dummy tool', + shape: { + foo: z.string(), + } satisfies MockTool['shape'], + }, + ] as const satisfies MockTool[]; + + let origin: string; + let closeServer: () => void; + let restoreMsw: (() => void) | undefined; + + beforeAll(async () => { + mswServer.close(); + restoreMsw = () => mswServer.listen({ onUnhandledRequest: 'warn' }); + + const server = await createMockMcpServer(mockTools); + origin = server.origin; + closeServer = server.close; + }); + + afterAll(() => { + closeServer(); + restoreMsw?.(); + }); + + it('creates tools from MCP catalog and wires RPC execution', async () => { + const stackOneClient = { + actions: { + rpcAction: mock(async () => ({ actionsRpcResponse: { data: null } })), + }, + } as unknown as StackOne; + + class TestToolSet extends ToolSet {} + + const toolset = new TestToolSet({ + baseUrl: origin, + headers: { 'x-account-id': 'test-account' }, + stackOneClient, + }); + + const tools = await toolset.fetchTools(); + expect(tools.length).toBe(1); + + const tool = tools.toArray()[0]; + expect(tool.name).toBe('dummy_action'); + + const aiTools = tool.toAISDK({ executable: false }); + const aiToolDefinition = aiTools.dummy_action; + expect(aiToolDefinition).toBeDefined(); + expect(aiToolDefinition.description).toBe('Dummy tool'); + expect(aiToolDefinition.parameters.jsonSchema.properties.foo.type).toBe('string'); + expect(aiToolDefinition.execution).toBeUndefined(); + + const executableTool = tool.toAISDK().dummy_action; + const result = await executableTool.execute({ foo: 'bar' }); + + expect(stackOneClient.actions.rpcAction).toHaveBeenCalledWith({ + action: 'dummy_action', + body: { foo: 'bar' }, + headers: { 'x-account-id': 'test-account' }, + path: undefined, + query: undefined, + }); + expect(result).toEqual({ data: null }); + }); +}); diff --git a/src/types.ts b/src/types.ts index 714f930..1a55dc2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,18 +56,47 @@ export type ParameterLocation = ValueOf; /** * Configuration for executing a tool against an API endpoint */ -export interface ExecuteConfig { +export interface HttpExecuteParameter { + name: string; + location: ParameterLocation; + type: JsonSchemaType; + derivedFrom?: string; // this is the name of the param that this one is derived from. +} + +export type HttpBodyType = 'json' | 'multipart-form' | 'form'; + +export interface HttpExecuteConfig { + kind: 'http'; + method: string; + url: string; + bodyType: HttpBodyType; + params: HttpExecuteParameter[]; // full list of params used to execute. Comes straight from the OpenAPI spec. +} + +export interface RpcExecuteConfig { + kind: 'rpc'; method: string; url: string; - bodyType: 'json' | 'multipart-form' | 'form'; - params: { - name: string; - location: ParameterLocation; - type: JsonSchemaType; - derivedFrom?: string; // this is the name of the param that this one is derived from. - }[]; // this params are the full list of params used to execute. This should come straight from the OpenAPI spec. + payloadKeys: { + action: string; + body?: string; + headers?: string; + path?: string; + query?: string; + }; } +export interface LocalExecuteConfig { + kind: 'local'; + identifier?: string; + description?: string; +} + +/** + * Discriminated union lets call sites branch on execution style without relying on nullable fields. + */ +export type ExecuteConfig = HttpExecuteConfig | RpcExecuteConfig | LocalExecuteConfig; + /** * EXPERIMENTAL: Options for creating tools with schema overrides and preExecute functions */ @@ -96,6 +125,20 @@ export interface ExecuteOptions { dryRun?: boolean; } +/** + * Execution metadata that can be surfaced to AI SDK tools. + */ +export interface ToolExecution { + /** + * The raw execution configuration generated from the OpenAPI specification. + */ + config: ExecuteConfig; + /** + * The headers that will be sent when executing the tool. + */ + headers: Headers; +} + /** * Schema definition for tool parameters */