From a8e62364dddf669b1eef5b30eba393b03d102767 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:03:37 +0100 Subject: [PATCH 01/22] chore(deps): add mcp sdk --- bun.lock | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 166 insertions(+) diff --git a/bun.lock b/bun.lock index 6598346..8016526 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "": { "name": "stackone-ai-ts", "dependencies": { + "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", "json-schema": "^0.4.0", }, @@ -101,6 +102,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=="], @@ -183,10 +186,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 +206,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 +242,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 +260,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 +270,104 @@ "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=="], "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 +380,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 +394,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 +424,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 +446,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 +460,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 +496,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 +556,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 +568,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 +620,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 +646,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/package.json b/package.json index 4d97153..1e1898b 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "format": "biome format --write ." }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", "json-schema": "^0.4.0" }, From 3d210e75147eaccab19ceac69ba7a40e4a566762 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:16:26 +0100 Subject: [PATCH 02/22] feat: fetchTool --- src/mcp.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++ src/toolsets/base.ts | 36 +++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/mcp.ts diff --git a/src/mcp.ts b/src/mcp.ts new file mode 100644 index 0000000..26ba045 --- /dev/null +++ b/src/mcp.ts @@ -0,0 +1,63 @@ +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; +} + +const DEFAULT_MCP_CLIENT_OPTIONS = { + baseUrl: 'https://api.modelcontextprotocol.org', + headers: {}, +} as const satisfies MCPClientOptions; + +/** + * 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 = DEFAULT_MCP_CLIENT_OPTIONS): 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/toolsets/base.ts b/src/toolsets/base.ts index 6764131..e354ad8 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -1,6 +1,11 @@ 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 { + ExecuteConfig, + Experimental_ToolCreationOptions, + JsonSchemaProperties, +} from '../types'; import { toArray } from '../utils/array'; /** @@ -230,4 +235,31 @@ export abstract class ToolSet { } return tool; } + + /** + * Fetch tool definitions from MCP (if applicable) + */ + async fetchTools(): Promise { + await using clients = await createMCPClient({ + baseUrl: `${this.baseUrl}/mcp`, + }); + const listToolsResult = await clients.client.listTools(); + + const tools = listToolsResult.tools.map(({ name, description, inputSchema }) => { + return new BaseTool( + name, + description ?? '', + { + ...inputSchema, + + // properties are not well typed in MCP spec + properties: inputSchema?.properties as JsonSchemaProperties, + }, + + // TODO: Implement mcp client method + {} as ExecuteConfig + ); + }); + return new Tools(tools); + } } From 8916c1e3ad9c27f36dbc8aee9a45c85eb90296ae Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:35:04 +0100 Subject: [PATCH 03/22] feat: headers for mcp --- src/toolsets/base.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/toolsets/base.ts b/src/toolsets/base.ts index e354ad8..c109e19 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -242,6 +242,7 @@ export abstract class ToolSet { async fetchTools(): Promise { await using clients = await createMCPClient({ baseUrl: `${this.baseUrl}/mcp`, + headers: this.headers, }); const listToolsResult = await clients.client.listTools(); From dd228583be1d7aabe4ac9687a25208d858818848 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:33:06 +0100 Subject: [PATCH 04/22] chore(deps): add stackone client sdk dependency --- bun.lock | 3 +++ package.json | 1 + 2 files changed, 4 insertions(+) diff --git a/bun.lock b/bun.lock index 8016526..7e73cfb 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", + "@stackone/stackone-client-ts": "^4.28.0", "json-schema": "^0.4.0", }, "devDependencies": { @@ -154,6 +155,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=="], diff --git a/package.json b/package.json index 1e1898b..16a3602 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", + "@stackone/stackone-client-ts": "^4.28.0", "json-schema": "^0.4.0" }, "devDependencies": { From 562581ad85a9667580b3bd98dff34a95342da6be Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:33:31 +0100 Subject: [PATCH 05/22] feat: execute stackone tools via actions rpc --- src/tool.ts | 60 ++++++++++---- src/toolsets/base.ts | 182 ++++++++++++++++++++++++++++++++++++++++++- src/types.ts | 14 ++++ 3 files changed, 238 insertions(+), 18 deletions(-) diff --git a/src/tool.ts b/src/tool.ts index e4a97f2..897db1f 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -8,6 +8,7 @@ import type { Experimental_PreExecuteFunction, Experimental_ToolCreationOptions, JsonDict, + ToolExecution, ToolParameters, } from './types'; import { StackOneError } from './utils/errors'; @@ -24,6 +25,18 @@ export class BaseTool { protected requestBuilder: RequestBuilder; protected experimental_preExecute?: Experimental_PreExecuteFunction; + private createExecutionMetadata(): ToolExecution { + return { + config: { + method: this.executeConfig.method, + url: this.executeConfig.url, + bodyType: this.executeConfig.bodyType, + params: this.executeConfig.params.map((param) => ({ ...param })), + }, + headers: this.getHeaders(), + }; + } + constructor( name: string, description: string, @@ -114,7 +127,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 +139,30 @@ export class BaseTool { additionalProperties: false, }; + const toolDefinition: Record = { + parameters: jsonSchema(schema), + description: this.description, + }; + + const executionOption = options.execution ?? this.createExecutionMetadata(); + + 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 +285,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; } diff --git a/src/toolsets/base.ts b/src/toolsets/base.ts index c109e19..f9c7f56 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -1,12 +1,17 @@ +import { StackOne } from '@stackone/stackone-client-ts'; import type { Arrayable } from 'type-fest'; import { createMCPClient } from '../mcp'; import { BaseTool, Tools } from '../tool'; import type { ExecuteConfig, + ExecuteOptions, Experimental_ToolCreationOptions, + JsonDict, JsonSchemaProperties, } from '../types'; +import { ParameterLocation } from '../types'; import { toArray } from '../utils/array'; +import { StackOneError } from '../utils/errors'; /** * Base exception for toolset errors @@ -59,6 +64,7 @@ export interface BaseToolSetConfig { authentication?: AuthenticationConfig; headers?: Record; _oasUrl?: string; + stackOneClient?: StackOne; } /** @@ -68,6 +74,7 @@ export abstract class ToolSet { protected baseUrl?: string; protected authentication?: AuthenticationConfig; protected headers: Record; + protected stackOneClient?: StackOne; protected tools: BaseTool[] = []; /** @@ -78,6 +85,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) { @@ -240,14 +248,54 @@ export abstract class ToolSet { * Fetch tool definitions from MCP (if applicable) */ async fetchTools(): Promise { + if (!this.baseUrl) { + throw new ToolSetConfigError('baseUrl is required to fetch MCP tools'); + } + 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 }) => { - return new BaseTool( + const executeConfig: ExecuteConfig = { + method: 'POST', + url: `${this.baseUrl}/actions/rpc`, + bodyType: 'json', + params: [ + { + name: 'action', + location: ParameterLocation.BODY, + type: 'string', + }, + { + name: 'body', + location: ParameterLocation.BODY, + type: 'object', + }, + { + name: 'headers', + location: ParameterLocation.BODY, + type: 'object', + }, + { + name: 'path', + location: ParameterLocation.BODY, + type: 'object', + }, + { + name: 'query', + location: ParameterLocation.BODY, + type: 'object', + }, + ], + }; + + const tool = new BaseTool( name, description ?? '', { @@ -256,11 +304,137 @@ export abstract class ToolSet { // properties are not well typed in MCP spec properties: inputSchema?.properties as JsonSchemaProperties, }, - - // TODO: Implement mcp client method - {} as ExecuteConfig + executeConfig, + this.headers ); + + 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; }); + return new Tools(tools); } + + private getActionsClient(): StackOne { + if (this.stackOneClient) { + return this.stackOneClient; + } + + if (this.authentication?.type === 'basic') { + const { username, password } = this.authentication.credentials ?? {}; + if (!username) { + throw new ToolSetConfigError( + 'StackOne API key is required to create an actions client. Provide stackOneClient or configure basic authentication credentials.' + ); + } + + this.stackOneClient = new StackOne({ + serverURL: this.baseUrl, + security: { + username, + password: password ?? '', + }, + }); + return this.stackOneClient; + } + + throw new ToolSetConfigError( + 'StackOne client not configured. Provide stackOneClient or basic authentication credentials.' + ); + } + + 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/types.ts b/src/types.ts index 714f930..0e20e2c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -96,6 +96,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 */ From aee92311034cb219ee69e45d2d6ea5fff9b01ef8 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:49:25 +0100 Subject: [PATCH 06/22] test: verify fetchTools against hono mcp --- bun.lock | 6 ++ package.json | 2 + src/toolsets/tests/mcp-fetch.spec.ts | 114 +++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/toolsets/tests/mcp-fetch.spec.ts diff --git a/bun.lock b/bun.lock index 7e73cfb..5209945 100644 --- a/bun.lock +++ b/bun.lock @@ -4,9 +4,11 @@ "": { "name": "stackone-ai-ts", "dependencies": { + "@hono/mcp": "^0.1.4", "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", "@stackone/stackone-client-ts": "^4.28.0", + "hono": "^4.9.10", "json-schema": "^0.4.0", }, "devDependencies": { @@ -83,6 +85,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=="], @@ -351,6 +355,8 @@ "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=="], diff --git a/package.json b/package.json index 16a3602..dc8ca54 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,11 @@ "format": "biome format --write ." }, "dependencies": { + "@hono/mcp": "^0.1.4", "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", "@stackone/stackone-client-ts": "^4.28.0", + "hono": "^4.9.10", "json-schema": "^0.4.0" }, "devDependencies": { diff --git a/src/toolsets/tests/mcp-fetch.spec.ts b/src/toolsets/tests/mcp-fetch.spec.ts new file mode 100644 index 0000000..7c252b4 --- /dev/null +++ b/src/toolsets/tests/mcp-fetch.spec.ts @@ -0,0 +1,114 @@ +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 with RPC execution metadata', 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'); + + const execution = aiToolDefinition.execution as { + config: { url: string }; + headers: Record; + }; + + expect(execution.config.url).toBe(`${origin}/actions/rpc`); + expect(execution.headers['x-account-id']).toBe('test-account'); + }); +}); From 0846baad4ce97385d068a3bf5d5758edc1bd4d6c Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:25:34 +0100 Subject: [PATCH 07/22] Refactor execute config handling --- src/modules/requestBuilder.ts | 6 +- src/modules/tests/requestBuilder.spec.ts | 1 + src/openapi/parser.ts | 49 +++-- src/tests/json-schema.spec.ts | 2 + src/tests/meta-tools.spec.ts | 6 + src/tests/tool.spec.ts | 26 +++ src/tool.ts | 87 ++++++-- src/toolsets/base.ts | 259 ++++++++++++----------- src/toolsets/tests/base.spec.ts | 2 + src/toolsets/tests/openapi.spec.ts | 1 + src/types.ts | 38 +++- 11 files changed, 307 insertions(+), 170 deletions(-) diff --git a/src/modules/requestBuilder.ts b/src/modules/requestBuilder.ts index 7969b56..cd912b3 100644 --- a/src/modules/requestBuilder.ts +++ b/src/modules/requestBuilder.ts @@ -1,6 +1,6 @@ import { - type ExecuteConfig, type ExecuteOptions, + type HttpExecuteConfig, type JsonDict, ParameterLocation, } from '../types'; @@ -25,10 +25,10 @@ export class RequestBuilder { private method: string; private url: string; private bodyType: 'json' | 'multipart-form' | 'form'; - private params: ExecuteConfig['params']; + 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; diff --git a/src/modules/tests/requestBuilder.spec.ts b/src/modules/tests/requestBuilder.spec.ts index 676fb8f..41c2d36 100644 --- a/src/modules/tests/requestBuilder.spec.ts +++ b/src/modules/tests/requestBuilder.spec.ts @@ -16,6 +16,7 @@ describe('RequestBuilder', () => { return recordedRequests; }; const mockConfig = { + kind: 'http' as const, method: 'GET', url: 'https://api.example.com/test/{pathParam}', bodyType: 'json' as const, diff --git a/src/openapi/parser.ts b/src/openapi/parser.ts index 7bd260d..3486282 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,22 @@ export class OpenAPIParser { return servers.length > 0 ? servers[0].url : 'https://api.stackone.com'; } + private normalizeBodyType(bodyType: string | null): HttpExecuteConfig['bodyType'] { + 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 +568,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 +591,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/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 897db1f..faeb895 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -7,7 +7,9 @@ import type { ExecuteOptions, Experimental_PreExecuteFunction, Experimental_ToolCreationOptions, + HttpExecuteConfig, JsonDict, + LocalExecuteConfig, ToolExecution, ToolParameters, } from './types'; @@ -22,17 +24,39 @@ 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 { - return { - config: { + let config: ExecuteConfig; + + if (this.executeConfig.kind === 'http') { + config = { + kind: 'http', method: this.executeConfig.method, url: this.executeConfig.url, bodyType: this.executeConfig.bodyType, params: this.executeConfig.params.map((param) => ({ ...param })), - }, + } satisfies HttpExecuteConfig; + } else if (this.executeConfig.kind === 'rpc') { + config = { + kind: 'rpc', + method: this.executeConfig.method, + url: this.executeConfig.url, + payloadKeys: { ...this.executeConfig.payloadKeys }, + }; + } else { + config = { + kind: 'local', + identifier: this.executeConfig.identifier, + description: this.executeConfig.description, + }; + } + + return { + config, headers: this.getHeaders(), }; } @@ -49,7 +73,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; } @@ -57,7 +84,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; } @@ -65,7 +95,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; } /** @@ -73,6 +116,11 @@ export class BaseTool { */ async execute(inputParams?: JsonDict | string, options?: ExecuteOptions): Promise { try { + if (!this.requestBuilder || this.executeConfig.kind !== 'http') { + 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 && @@ -144,7 +192,12 @@ export class BaseTool { description: this.description, }; - const executionOption = options.execution ?? this.createExecutionMetadata(); + const executionOption = + options.execution !== undefined + ? options.execution + : this.#exposeExecutionMetadata + ? this.createExecutionMetadata() + : false; if (executionOption !== false) { toolDefinition.execution = executionOption; @@ -437,11 +490,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 => { @@ -521,11 +573,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 f9c7f56..bd971c1 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -3,16 +3,20 @@ import type { Arrayable } from 'type-fest'; import { createMCPClient } from '../mcp'; import { BaseTool, Tools } from '../tool'; import type { - ExecuteConfig, ExecuteOptions, Experimental_ToolCreationOptions, JsonDict, JsonSchemaProperties, + RpcExecuteConfig, + ToolParameters, } from '../types'; -import { ParameterLocation } 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 */ @@ -261,132 +265,14 @@ export abstract class ToolSet { const listToolsResult = await clients.client.listTools(); const actionsClient = this.getActionsClient(); - const tools = listToolsResult.tools.map(({ name, description, inputSchema }) => { - const executeConfig: ExecuteConfig = { - method: 'POST', - url: `${this.baseUrl}/actions/rpc`, - bodyType: 'json', - params: [ - { - name: 'action', - location: ParameterLocation.BODY, - type: 'string', - }, - { - name: 'body', - location: ParameterLocation.BODY, - type: 'object', - }, - { - name: 'headers', - location: ParameterLocation.BODY, - type: 'object', - }, - { - name: 'path', - location: ParameterLocation.BODY, - type: 'object', - }, - { - name: 'query', - location: ParameterLocation.BODY, - type: 'object', - }, - ], - }; - - const tool = new BaseTool( + const tools = listToolsResult.tools.map(({ name, description, inputSchema }) => + this.createRpcBackedTool({ + actionsClient, name, - description ?? '', - { - ...inputSchema, - - // properties are not well typed in MCP spec - properties: inputSchema?.properties as JsonSchemaProperties, - }, - executeConfig, - this.headers - ); - - 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; - }); + description, + inputSchema, + }) + ); return new Tools(tools); } @@ -419,6 +305,125 @@ export abstract class ToolSet { ); } + 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; + + 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' 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/types.ts b/src/types.ts index 0e20e2c..9d62268 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,18 +56,42 @@ 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 interface HttpExecuteConfig { + kind: 'http'; 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. + params: HttpExecuteParameter[]; // full list of params used to execute. Comes straight from the OpenAPI spec. } +export interface RpcExecuteConfig { + kind: 'rpc'; + method: string; + url: string; + payloadKeys: { + action: string; + body?: string; + headers?: string; + path?: string; + query?: string; + }; +} + +export interface LocalExecuteConfig { + kind: 'local'; + identifier?: string; + description?: string; +} + +export type ExecuteConfig = HttpExecuteConfig | RpcExecuteConfig | LocalExecuteConfig; + /** * EXPERIMENTAL: Options for creating tools with schema overrides and preExecute functions */ From c1d41053ef7b01e1362c9d5774934a20cceb8401 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:27:46 +0100 Subject: [PATCH 08/22] test: move mock --- bun.lock | 4 ++-- package.json | 4 ++-- ...tch.spec.ts => stackone.mcp-fetch.spec.ts} | 19 ++++++++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) rename src/toolsets/tests/{mcp-fetch.spec.ts => stackone.mcp-fetch.spec.ts} (84%) diff --git a/bun.lock b/bun.lock index 5209945..4ed6e61 100644 --- a/bun.lock +++ b/bun.lock @@ -4,22 +4,22 @@ "": { "name": "stackone-ai-ts", "dependencies": { - "@hono/mcp": "^0.1.4", "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", "@stackone/stackone-client-ts": "^4.28.0", - "hono": "^4.9.10", "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", diff --git a/package.json b/package.json index dc8ca54..2492094 100644 --- a/package.json +++ b/package.json @@ -36,22 +36,22 @@ "format": "biome format --write ." }, "dependencies": { - "@hono/mcp": "^0.1.4", "@modelcontextprotocol/sdk": "^1.19.1", "@orama/orama": "^3.1.11", "@stackone/stackone-client-ts": "^4.28.0", - "hono": "^4.9.10", "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", diff --git a/src/toolsets/tests/mcp-fetch.spec.ts b/src/toolsets/tests/stackone.mcp-fetch.spec.ts similarity index 84% rename from src/toolsets/tests/mcp-fetch.spec.ts rename to src/toolsets/tests/stackone.mcp-fetch.spec.ts index 7c252b4..31609b8 100644 --- a/src/toolsets/tests/mcp-fetch.spec.ts +++ b/src/toolsets/tests/stackone.mcp-fetch.spec.ts @@ -76,7 +76,7 @@ describe('ToolSet.fetchTools (MCP + RPC integration)', () => { restoreMsw?.(); }); - it('creates tools from MCP catalog with RPC execution metadata', async () => { + it('creates tools from MCP catalog and wires RPC execution', async () => { const stackOneClient = { actions: { rpcAction: mock(async () => ({ actionsRpcResponse: { data: null } })), @@ -102,13 +102,18 @@ describe('ToolSet.fetchTools (MCP + RPC integration)', () => { expect(aiToolDefinition).toBeDefined(); expect(aiToolDefinition.description).toBe('Dummy tool'); expect(aiToolDefinition.parameters.jsonSchema.properties.foo.type).toBe('string'); + expect(aiToolDefinition.execution).toBeUndefined(); - const execution = aiToolDefinition.execution as { - config: { url: string }; - headers: Record; - }; + const executableTool = tool.toAISDK().dummy_action; + const result = await executableTool.execute({ foo: 'bar' }); - expect(execution.config.url).toBe(`${origin}/actions/rpc`); - expect(execution.headers['x-account-id']).toBe('test-account'); + 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 }); }); }); From 9ee7d3a9404acd8a8be7dac6d6c9158fb87fbeb9 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:28:23 +0100 Subject: [PATCH 09/22] test: update snapshot --- .../__snapshots__/openapi-parser.spec.ts.snap | 74 +++++++++++++++++++ .../tests/__snapshots__/stackone.spec.ts.snap | 74 +++++++++++++++++++ 2 files changed, 148 insertions(+) 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/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": [ { From a32e6c30ac59174128a1dc01fd631aedf81c1e11 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:29:16 +0100 Subject: [PATCH 10/22] Add inline comments for execution config changes --- src/modules/requestBuilder.ts | 2 +- src/openapi/parser.ts | 1 + src/tool.ts | 1 + src/toolsets/base.ts | 2 +- src/types.ts | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/requestBuilder.ts b/src/modules/requestBuilder.ts index cd912b3..3f48ee9 100644 --- a/src/modules/requestBuilder.ts +++ b/src/modules/requestBuilder.ts @@ -19,7 +19,7 @@ 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; diff --git a/src/openapi/parser.ts b/src/openapi/parser.ts index 3486282..11baed6 100644 --- a/src/openapi/parser.ts +++ b/src/openapi/parser.ts @@ -68,6 +68,7 @@ export class OpenAPIParser { } private normalizeBodyType(bodyType: string | null): HttpExecuteConfig['bodyType'] { + // Map OpenAPI content types into the narrower set supported by ExecuteConfig. if (!bodyType) { return 'json'; } diff --git a/src/tool.ts b/src/tool.ts index faeb895..c30638b 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -117,6 +117,7 @@ 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.' ); diff --git a/src/toolsets/base.ts b/src/toolsets/base.ts index bd971c1..abfff90 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -327,7 +327,7 @@ export abstract class ToolSet { path: 'path', query: 'query', }, - } as const satisfies RpcExecuteConfig; + } as const satisfies RpcExecuteConfig; // Mirrors StackOne RPC payload layout so metadata/debug stays in sync. const toolParameters = { ...inputSchema, diff --git a/src/types.ts b/src/types.ts index 9d62268..5a3a639 100644 --- a/src/types.ts +++ b/src/types.ts @@ -90,6 +90,7 @@ export interface LocalExecuteConfig { description?: string; } +// Discriminated union lets call sites branch on execution style without relying on nullable fields. export type ExecuteConfig = HttpExecuteConfig | RpcExecuteConfig | LocalExecuteConfig; /** From 7ef126d7e421abc7acf3804ff9489a7ed13ecb1b Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:30:47 +0100 Subject: [PATCH 11/22] Tighten test mock execute config typing --- src/modules/tests/requestBuilder.spec.ts | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/modules/tests/requestBuilder.spec.ts b/src/modules/tests/requestBuilder.spec.ts index 41c2d36..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,28 +16,28 @@ describe('RequestBuilder', () => { return recordedRequests; }; const mockConfig = { - kind: 'http' as const, + 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' }); From 90cdf039da6ed3daaf32c16ea83abeb80d793cc6 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:35:54 +0100 Subject: [PATCH 12/22] Document execute config union --- src/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 5a3a639..fec420a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -90,7 +90,9 @@ export interface LocalExecuteConfig { description?: string; } -// Discriminated union lets call sites branch on execution style without relying on nullable fields. +/** + * Discriminated union lets call sites branch on execution style without relying on nullable fields. + */ export type ExecuteConfig = HttpExecuteConfig | RpcExecuteConfig | LocalExecuteConfig; /** From b8d71ed403e5c800519e1145ed351fab90307fce Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:56:54 +0100 Subject: [PATCH 13/22] fix: fallback to env api key for actions client --- src/toolsets/base.ts | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/toolsets/base.ts b/src/toolsets/base.ts index abfff90..dfeb118 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -282,27 +282,32 @@ export abstract class ToolSet { return this.stackOneClient; } - if (this.authentication?.type === 'basic') { - const { username, password } = this.authentication.credentials ?? {}; - if (!username) { - throw new ToolSetConfigError( - 'StackOne API key is required to create an actions client. Provide stackOneClient or configure basic authentication credentials.' - ); - } - - this.stackOneClient = new StackOne({ - serverURL: this.baseUrl, - security: { - username, - password: password ?? '', - }, - }); - 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.' + ); } - throw new ToolSetConfigError( - 'StackOne client not configured. Provide stackOneClient or basic authentication credentials.' - ); + this.stackOneClient = new StackOne({ + serverURL: this.baseUrl, + security: { + username: apiKey, + password, + }, + }); + + return this.stackOneClient; } private createRpcBackedTool({ From 3e16bdb5da7c6dd72db4d739d03d6332bcdc98da Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:10:53 +0100 Subject: [PATCH 14/22] docs: document mcp-powered tools --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++ examples/README.md | 8 ++++++++ 2 files changed, 56 insertions(+) diff --git a/README.md b/README.md index 8ced2cd..a9ffadb 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ export STACKONE_API_KEY= or load from a .env file using your preferred environment variable library. +This API key is required for both statically generated tools and the MCP-backed dynamic tools described below. If it is missing, RPC execution will fail with a configuration error. + ### Account IDs StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels: @@ -125,6 +127,52 @@ const currentAccountId = tools.getAccountId(); // Get the current account ID [View full example](examples/account-id-usage.ts) +### Dynamic Tool Fetching (MCP) + +StackOne hosts a Model Context Protocol (MCP) service that exposes your account's actions catalog. You can fetch those tools on demand and wire them up to the StackOne RPC endpoint without maintaining OpenAPI specs locally: + +```typescript +class MyToolSet extends StackOneToolSet {} + +const toolset = new MyToolSet({ baseUrl: "https://api.stackone.com" }); +const tools = await toolset.fetchTools(); // Pulls the live catalog from /mcp and binds RPC execution + +const onboardingTool = tools.getTool("hris_create_employee"); +const result = await onboardingTool.execute({ + body: { + name: "Jane Doe", + email: "jane@example.com", + }, +}); +``` + +When calling `fetchTools`, the SDK automatically: + +- Connects to your project's MCP endpoint (`/mcp`) +- Reuses the current authentication headers (including `STACKONE_API_KEY`) +- Creates `BaseTool` instances backed by the `actions.rpcAction` client + +[View full example](examples/meta-tools.ts) + +### Meta Tools for Discovery (Beta) + +Every `Tools` collection can produce *meta tools* that make it easier for AI agents to search the catalog and invoke tools dynamically: + +- `meta_search_tools` lets you run natural-language queries (powered by Orama's BM25 ranking) to find relevant tools. +- `meta_execute_tool` takes a tool name plus parameters and runs it, so agents do not need to hardcode tool identifiers. + +```typescript +const toolset = new StackOneToolSet(); +const tools = toolset.getStackOneTools("hris_*", "your-account-id"); + +const metaTools = await tools.metaTools(); +const searchResult = await metaTools.getTool("meta_search_tools")?.execute({ + query: "create a time off request", +}); +``` + +[View full example](examples/meta-tools.ts) + ### File Upload The `StackOneToolSet` comes with built-in transformations for file uploads: diff --git a/examples/README.md b/examples/README.md index 0691be8..72e6ee6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -155,6 +155,14 @@ Shows how to implement human-in-the-loop workflows for validation. - **API Calls**: Conditional - **Key Features**: Manual approval workflows, UI integration patterns +#### [`meta-tools.ts`](./meta-tools.ts) - Dynamic Tool Discovery (Beta) + +Demonstrates MCP-backed tool discovery and meta tools that allow agents to search for and execute tools dynamically. + +- **Account ID**: HRIS +- **API Calls**: Yes (RPC via MCP actions) +- **Key Features**: MCP-powered catalog fetching, Orama BM25 search, AI agent integration + ### OpenAPI Toolset Examples #### [`openapi-toolset.ts`](./openapi-toolset.ts) - OpenAPI Integration From a73b8d088e21c424abab3eb0787f92c7dde8d3c6 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:15:41 +0100 Subject: [PATCH 15/22] Revert "docs: document mcp-powered tools" This reverts commit 0797d5e7caae99cc0c356415e112e6ad27466db5. --- README.md | 48 ---------------------------------------------- examples/README.md | 8 -------- 2 files changed, 56 deletions(-) diff --git a/README.md b/README.md index a9ffadb..8ced2cd 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,6 @@ export STACKONE_API_KEY= or load from a .env file using your preferred environment variable library. -This API key is required for both statically generated tools and the MCP-backed dynamic tools described below. If it is missing, RPC execution will fail with a configuration error. - ### Account IDs StackOne uses account IDs to identify different integrations. You can specify the account ID at different levels: @@ -127,52 +125,6 @@ const currentAccountId = tools.getAccountId(); // Get the current account ID [View full example](examples/account-id-usage.ts) -### Dynamic Tool Fetching (MCP) - -StackOne hosts a Model Context Protocol (MCP) service that exposes your account's actions catalog. You can fetch those tools on demand and wire them up to the StackOne RPC endpoint without maintaining OpenAPI specs locally: - -```typescript -class MyToolSet extends StackOneToolSet {} - -const toolset = new MyToolSet({ baseUrl: "https://api.stackone.com" }); -const tools = await toolset.fetchTools(); // Pulls the live catalog from /mcp and binds RPC execution - -const onboardingTool = tools.getTool("hris_create_employee"); -const result = await onboardingTool.execute({ - body: { - name: "Jane Doe", - email: "jane@example.com", - }, -}); -``` - -When calling `fetchTools`, the SDK automatically: - -- Connects to your project's MCP endpoint (`/mcp`) -- Reuses the current authentication headers (including `STACKONE_API_KEY`) -- Creates `BaseTool` instances backed by the `actions.rpcAction` client - -[View full example](examples/meta-tools.ts) - -### Meta Tools for Discovery (Beta) - -Every `Tools` collection can produce *meta tools* that make it easier for AI agents to search the catalog and invoke tools dynamically: - -- `meta_search_tools` lets you run natural-language queries (powered by Orama's BM25 ranking) to find relevant tools. -- `meta_execute_tool` takes a tool name plus parameters and runs it, so agents do not need to hardcode tool identifiers. - -```typescript -const toolset = new StackOneToolSet(); -const tools = toolset.getStackOneTools("hris_*", "your-account-id"); - -const metaTools = await tools.metaTools(); -const searchResult = await metaTools.getTool("meta_search_tools")?.execute({ - query: "create a time off request", -}); -``` - -[View full example](examples/meta-tools.ts) - ### File Upload The `StackOneToolSet` comes with built-in transformations for file uploads: diff --git a/examples/README.md b/examples/README.md index 72e6ee6..0691be8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -155,14 +155,6 @@ Shows how to implement human-in-the-loop workflows for validation. - **API Calls**: Conditional - **Key Features**: Manual approval workflows, UI integration patterns -#### [`meta-tools.ts`](./meta-tools.ts) - Dynamic Tool Discovery (Beta) - -Demonstrates MCP-backed tool discovery and meta tools that allow agents to search for and execute tools dynamically. - -- **Account ID**: HRIS -- **API Calls**: Yes (RPC via MCP actions) -- **Key Features**: MCP-powered catalog fetching, Orama BM25 search, AI agent integration - ### OpenAPI Toolset Examples #### [`openapi-toolset.ts`](./openapi-toolset.ts) - OpenAPI Integration From a30304a31fc6004ea12128b6dff94824ed16667e Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:24:08 +0100 Subject: [PATCH 16/22] docs: explain fetchTools usage --- README.md | 21 +++++++++++++++++++++ examples/README.md | 8 ++++++++ examples/fetch-tools.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 examples/fetch-tools.ts 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/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)); From 8e36061f54dff47498f08554cfbbc70247fac31c Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:32:00 +0100 Subject: [PATCH 17/22] test: add zod dependency for mcp tests --- bun.lock | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 4ed6e61..77d2284 100644 --- a/bun.lock +++ b/bun.lock @@ -29,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", diff --git a/package.json b/package.json index 2492094..328cf78 100644 --- a/package.json +++ b/package.json @@ -60,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", From 4ee774626ec093fae418bfa185413a9ee3d27fcc Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:44:49 +0100 Subject: [PATCH 18/22] fix: typesafe config --- src/tool.ts | 53 +++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/tool.ts b/src/tool.ts index c30638b..313ef24 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -10,6 +10,7 @@ import type { HttpExecuteConfig, JsonDict, LocalExecuteConfig, + RpcExecuteConfig, ToolExecution, ToolParameters, } from './types'; @@ -30,30 +31,34 @@ export class BaseTool { #headers: Record; private createExecutionMetadata(): ToolExecution { - let config: ExecuteConfig; - - if (this.executeConfig.kind === 'http') { - config = { - kind: 'http', - method: this.executeConfig.method, - url: this.executeConfig.url, - bodyType: this.executeConfig.bodyType, - params: this.executeConfig.params.map((param) => ({ ...param })), - } satisfies HttpExecuteConfig; - } else if (this.executeConfig.kind === 'rpc') { - config = { - kind: 'rpc', - method: this.executeConfig.method, - url: this.executeConfig.url, - payloadKeys: { ...this.executeConfig.payloadKeys }, - }; - } else { - config = { - kind: 'local', - identifier: this.executeConfig.identifier, - description: this.executeConfig.description, - }; - } + 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, From 185e5c836b2a1cb564925105d840fe1c6aad1d56 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:52:37 +0100 Subject: [PATCH 19/22] refactor: alias http body type and enforce exhaustive handling --- src/modules/requestBuilder.ts | 11 +++++++++-- src/types.ts | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/modules/requestBuilder.ts b/src/modules/requestBuilder.ts index 3f48ee9..eadb260 100644 --- a/src/modules/requestBuilder.ts +++ b/src/modules/requestBuilder.ts @@ -1,5 +1,6 @@ import { type ExecuteOptions, + type HttpBodyType, type HttpExecuteConfig, type JsonDict, ParameterLocation, @@ -24,7 +25,7 @@ class ParameterSerializationError extends Error { export class RequestBuilder { private method: string; private url: string; - private bodyType: 'json' | 'multipart-form' | 'form'; + private bodyType: HttpBodyType; private params: HttpExecuteConfig['params']; private headers: Record; @@ -114,7 +115,9 @@ export class RequestBuilder { // Handle different body types if (Object.keys(bodyParams).length > 0) { - switch (this.bodyType) { + const bodyType = this.bodyType; + + switch (bodyType) { case 'json': fetchOptions.headers = { ...fetchOptions.headers, @@ -144,6 +147,10 @@ export class RequestBuilder { // Don't set Content-Type for FormData, it will be set automatically with the boundary break; } + default: { + const _exhaustiveCheck: never = bodyType; + throw new Error(`Unsupported HTTP body type: ${String(_exhaustiveCheck)}`); + } } } diff --git a/src/types.ts b/src/types.ts index fec420a..1a55dc2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,11 +63,13 @@ export interface HttpExecuteParameter { 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: 'json' | 'multipart-form' | 'form'; + bodyType: HttpBodyType; params: HttpExecuteParameter[]; // full list of params used to execute. Comes straight from the OpenAPI spec. } From 9137d63425f6745ebe7a498250ae87c6e884316c Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:58:40 +0100 Subject: [PATCH 20/22] docs: note union exhaustiveness pattern --- CLAUDE.md | 26 +++++++++++++++++++++++++- src/modules/requestBuilder.ts | 8 +++----- 2 files changed, 28 insertions(+), 6 deletions(-) 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/src/modules/requestBuilder.ts b/src/modules/requestBuilder.ts index eadb260..9042310 100644 --- a/src/modules/requestBuilder.ts +++ b/src/modules/requestBuilder.ts @@ -115,9 +115,7 @@ export class RequestBuilder { // Handle different body types if (Object.keys(bodyParams).length > 0) { - const bodyType = this.bodyType; - - switch (bodyType) { + switch (this.bodyType) { case 'json': fetchOptions.headers = { ...fetchOptions.headers, @@ -148,8 +146,8 @@ export class RequestBuilder { break; } default: { - const _exhaustiveCheck: never = bodyType; - throw new Error(`Unsupported HTTP body type: ${String(_exhaustiveCheck)}`); + this.bodyType satisfies never; + throw new Error(`Unsupported HTTP body type: ${String(this.bodyType)}`); } } } From d66998f21588e4b02b84e38aa31fff8a4633a88b Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:02:16 +0100 Subject: [PATCH 21/22] fix: remove default mcp options --- src/mcp.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/mcp.ts b/src/mcp.ts index 26ba045..63cb055 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -18,11 +18,6 @@ interface MCPClient { [Symbol.asyncDispose](): Promise; } -const DEFAULT_MCP_CLIENT_OPTIONS = { - baseUrl: 'https://api.modelcontextprotocol.org', - headers: {}, -} as const satisfies MCPClientOptions; - /** * Create a Model Context Protocol (MCP) client. * @@ -38,10 +33,7 @@ const DEFAULT_MCP_CLIENT_OPTIONS = { * }); * ``` */ -export async function createMCPClient({ - baseUrl, - headers, -}: MCPClientOptions = DEFAULT_MCP_CLIENT_OPTIONS): Promise { +export async function createMCPClient({ baseUrl, headers }: MCPClientOptions): Promise { const transport = new StreamableHTTPClientTransport(new URL(baseUrl), { requestInit: { headers, From 08d159935e2541271538bfba3643cb6484a306bd Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:07:09 +0100 Subject: [PATCH 22/22] chore: note future fetchTools filters --- src/toolsets/base.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/toolsets/base.ts b/src/toolsets/base.ts index dfeb118..23ef4a1 100644 --- a/src/toolsets/base.ts +++ b/src/toolsets/base.ts @@ -256,6 +256,9 @@ export abstract class ToolSet { 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,