diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a6a0050..c867a95 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,7 +24,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
- name: Bootstrap
run: ./scripts/bootstrap
@@ -46,7 +46,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
- name: Bootstrap
run: ./scripts/bootstrap
@@ -68,6 +68,15 @@ jobs:
AUTH: ${{ steps.github-oidc.outputs.github_token }}
SHA: ${{ github.sha }}
run: ./scripts/utils/upload-artifact.sh
+
+ - name: Upload MCP Server tarball
+ if: github.repository == 'stainless-sdks/isaacus-typescript'
+ env:
+ URL: https://pkg.stainless.com/s?subpackage=mcp-server
+ AUTH: ${{ steps.github-oidc.outputs.github_token }}
+ SHA: ${{ github.sha }}
+ BASE_PATH: packages/mcp-server
+ run: ./scripts/utils/upload-artifact.sh
test:
timeout-minutes: 10
name: test
diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml
index 4459c5c..d82e723 100644
--- a/.github/workflows/publish-npm.yml
+++ b/.github/workflows/publish-npm.yml
@@ -16,6 +16,8 @@ jobs:
publish:
name: publish
runs-on: ubuntu-latest
+ permissions:
+ contents: write
steps:
- uses: actions/checkout@v4
@@ -39,3 +41,10 @@ jobs:
yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }"
env:
NPM_TOKEN: ${{ secrets.ISAACUS_NPM_TOKEN || secrets.NPM_TOKEN }}
+
+ - name: Upload MCP Server DXT GitHub release asset
+ run: |
+ gh release upload ${{ github.event.release.tag_name }} \
+ packages/mcp-server/isaacus_api.mcpb
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 0ba42e3..ac1184b 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -19,3 +19,4 @@ jobs:
bash ./bin/check-release-environment
env:
NPM_TOKEN: ${{ secrets.ISAACUS_NPM_TOKEN || secrets.NPM_TOKEN }}
+
diff --git a/.gitignore b/.gitignore
index d98d51a..d62bea5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,6 @@ dist
dist-deno
/*.tgz
.idea/
-
+.eslintcache
+dist-bundle
+*.mcpb
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 78e7f27..ddfa3e3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.11.0"
+ ".": "0.11.1"
}
diff --git a/.stats.yml b/.stats.yml
index b8132f0..0987f00 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 3
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-a0aa3bcfef3af964f7172cecc6e969193a4ca96b26f8c47e7f50d852b13ef356.yml
-openapi_spec_hash: e243aed52e8a3c6dad6254c57408fdc4
-config_hash: bfe30148ec88e8bbbf4a348a9fdfc00a
+configured_endpoints: 4
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-ee884a4336559147aacf9a927a540f21e9760f00d2d5588af00fa8a25e2707d9.yml
+openapi_spec_hash: 2ba78bd360942c63a7d08dba791f00d2
+config_hash: a85580968a69d8d6fadf96e5e2d6870e
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70dd1ad..03155e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,89 @@
# Changelog
+## 0.11.1 (2025-10-14)
+
+Full Changelog: [v0.11.0...v0.11.1](https://github.com/isaacus-dev/isaacus-typescript/compare/v0.11.0...v0.11.1)
+
+### ⚠ BREAKING CHANGES
+
+* **api:** reduce max length of embeddings input
+* **sdk:** add `_response` to response models to finally fix duplicated names
+
+### Features
+
+* **api:** added embedding endpoint ([0d8d83a](https://github.com/isaacus-dev/isaacus-typescript/commit/0d8d83a611df5252c68050071d06d3fb4685d534))
+* **api:** reduce max length of embeddings input ([8dfea48](https://github.com/isaacus-dev/isaacus-typescript/commit/8dfea484dd6333868978b30e74666a8dbb85b669))
+* **api:** rename embedding -> embeddings ([b0f92ff](https://github.com/isaacus-dev/isaacus-typescript/commit/b0f92ff7ed78d27692e3fe380d4756074eb54d84))
+* **api:** revert embedding -> embeddings ([8255684](https://github.com/isaacus-dev/isaacus-typescript/commit/82556847327ac00dac205e1876b6482099fb1c31))
+* **mcp:** add code execution tool ([a1cbb00](https://github.com/isaacus-dev/isaacus-typescript/commit/a1cbb008f3d7addd94730652490c4b157bd554f2))
+* **mcp:** add docs search tool ([879786d](https://github.com/isaacus-dev/isaacus-typescript/commit/879786deb453dc2211105151425e5a767e8a52da))
+* **mcp:** add logging when environment variable is set ([782e9fa](https://github.com/isaacus-dev/isaacus-typescript/commit/782e9fae8939787ac7f362712395d1ddc988f9db))
+* **mcp:** add option for including docs tools ([d75738d](https://github.com/isaacus-dev/isaacus-typescript/commit/d75738ddec8b9f1527a3ec985e7937267c81194a))
+* **mcp:** add option to infer mcp client ([5e57506](https://github.com/isaacus-dev/isaacus-typescript/commit/5e5750630aeff57d48479c8ceef76df5f9682120))
+* **mcp:** add unix socket option for remote MCP ([29456e4](https://github.com/isaacus-dev/isaacus-typescript/commit/29456e482b1a330cdbf2b3bc9d8a736cde34dd39))
+* **mcp:** allow setting logging level ([7c287d1](https://github.com/isaacus-dev/isaacus-typescript/commit/7c287d1650b60ee197658475558ff98c9b3ba253))
+* **mcp:** enable experimental docs search tool ([0077ea5](https://github.com/isaacus-dev/isaacus-typescript/commit/0077ea5876adea43d1a9026545fda774c0ef0c02))
+* **mcp:** expose client options in `streamableHTTPApp` ([e757029](https://github.com/isaacus-dev/isaacus-typescript/commit/e7570299b08070cfd932e0cf2310bd3178037377))
+* **mcp:** parse query string as mcp client options in mcp server ([d57ad3c](https://github.com/isaacus-dev/isaacus-typescript/commit/d57ad3c5a57de9c28c16929e41fad2f3a68c5068))
+* **mcp:** remote server with passthru auth ([98e619a](https://github.com/isaacus-dev/isaacus-typescript/commit/98e619a1edbc4adb4c30fbb7dddcd111a05893aa))
+* **sdk:** add embeddings endpoint ([402f641](https://github.com/isaacus-dev/isaacus-typescript/commit/402f641b04680e4ebd24242a57a7098ca5433fef))
+* **sdk:** toggle to force regen ([9c9ef78](https://github.com/isaacus-dev/isaacus-typescript/commit/9c9ef78f125a67ddb2a016d4eb57517e5b97c015))
+* **sdk:** untoggle to force regen ([c4755bc](https://github.com/isaacus-dev/isaacus-typescript/commit/c4755bc35f60f6094ba6a124b42f269e273794de))
+
+
+### Bug Fixes
+
+* **api:** avoid stainless SDK `NameError` ([865bc85](https://github.com/isaacus-dev/isaacus-typescript/commit/865bc856ef06c536332b9af0de7fd50592f4f237))
+* **api:** typo ([eab8690](https://github.com/isaacus-dev/isaacus-typescript/commit/eab869049c7e8f04023ece405445604de31a2985))
+* **ci:** set permissions for DXT publish action ([ce5e62e](https://github.com/isaacus-dev/isaacus-typescript/commit/ce5e62ee9c698a85d3eeb6a2e998ee896f497473))
+* coerce nullable values to undefined ([4afa2c2](https://github.com/isaacus-dev/isaacus-typescript/commit/4afa2c265b899d29b60c823d27d00c08d23afdc9))
+* **mcp:** avoid sending `jq_filter` to base API ([850f0f6](https://github.com/isaacus-dev/isaacus-typescript/commit/850f0f6f5e4c1fb13a7159ccc6d357854992cf5f))
+* **mcp:** fix bug in header handling ([27f12ba](https://github.com/isaacus-dev/isaacus-typescript/commit/27f12baeae30bf127d54f43b580291108422b555))
+* **mcp:** fix query options parsing ([0ec3eb3](https://github.com/isaacus-dev/isaacus-typescript/commit/0ec3eb32004668ab9dd3d29f74ad01d546c6a323))
+* **mcp:** fix uploading dxt release assets ([8c3db47](https://github.com/isaacus-dev/isaacus-typescript/commit/8c3db47ddf574faa5f2ca7b3dacb18530c75012c))
+* **mcp:** reverse validJson capability option and limit scope ([6d04008](https://github.com/isaacus-dev/isaacus-typescript/commit/6d040082ad4456a697ef550d9c176cad753afb7a))
+* **sdk:** add `_response` to response models to finally fix duplicated names ([14a9df4](https://github.com/isaacus-dev/isaacus-typescript/commit/14a9df456e70d8bdd152e3ff9710e519f870e463))
+
+
+### Chores
+
+* add package to package.json ([c4984c4](https://github.com/isaacus-dev/isaacus-typescript/commit/c4984c444562500d2f2f36e0090c1f415600f0a1))
+* **client:** qualify global Blob ([07703e2](https://github.com/isaacus-dev/isaacus-typescript/commit/07703e2299ade5e7987c7058a90fae4b39e66038))
+* **codegen:** internal codegen update ([5712a83](https://github.com/isaacus-dev/isaacus-typescript/commit/5712a833b930d92c94d2d3f0c5e426100786ddf0))
+* **deps:** update dependency @types/node to v20.17.58 ([7e69826](https://github.com/isaacus-dev/isaacus-typescript/commit/7e698263849b5a53b1098792a5cdc1e27fa76c70))
+* do not install brew dependencies in ./scripts/bootstrap by default ([39b5c60](https://github.com/isaacus-dev/isaacus-typescript/commit/39b5c60b0837ff81d984341f52f9a5399d83e029))
+* improve example values ([14a1d22](https://github.com/isaacus-dev/isaacus-typescript/commit/14a1d226a5494f3ad876361b78de554f223c6554))
+* **internal:** codegen related update ([eb7fa2d](https://github.com/isaacus-dev/isaacus-typescript/commit/eb7fa2dc18f0189867748891dee527dcd2d5d9f1))
+* **internal:** codegen related update ([dd992b1](https://github.com/isaacus-dev/isaacus-typescript/commit/dd992b122975393f2d3350b9a7371e1a29f0c329))
+* **internal:** codegen related update ([00165b1](https://github.com/isaacus-dev/isaacus-typescript/commit/00165b13517d9af29d038d9c9c1ad0e358732c91))
+* **internal:** codegen related update ([d77461b](https://github.com/isaacus-dev/isaacus-typescript/commit/d77461b18e5fca06485b3fa4f02ff832f16faf14))
+* **internal:** codegen related update ([7cd4ae0](https://github.com/isaacus-dev/isaacus-typescript/commit/7cd4ae0a330c4703cc543ad4e5cf211816cbbc4d))
+* **internal:** codegen related update ([91d9f96](https://github.com/isaacus-dev/isaacus-typescript/commit/91d9f969277f809196b0e76bf69d3e2b1e734ad5))
+* **internal:** formatting change ([5f7ef32](https://github.com/isaacus-dev/isaacus-typescript/commit/5f7ef32bae84995296074c99eca4e9c557b54431))
+* **internal:** gitignore .mcpb files ([e541193](https://github.com/isaacus-dev/isaacus-typescript/commit/e5411936faad14e1b9417c2c6ba10f663ddb3694))
+* **internal:** make mcp-server publishing public by defaut ([117c3f6](https://github.com/isaacus-dev/isaacus-typescript/commit/117c3f67f3cc2b038d8e08626ecdcd76d309788f))
+* **internal:** move publish config ([3f2772e](https://github.com/isaacus-dev/isaacus-typescript/commit/3f2772efe40aa46f0d6c2fc7157d5aef4c4087f1))
+* **internal:** refactor array check ([3fc9cc8](https://github.com/isaacus-dev/isaacus-typescript/commit/3fc9cc8aaecea7abe226c9dc9bdc00d971cc2732))
+* **internal:** remove redundant imports config ([b44ad38](https://github.com/isaacus-dev/isaacus-typescript/commit/b44ad387603fbbc6d04af5e43788342798e28c73))
+* **internal:** update comment in script ([c5c42a4](https://github.com/isaacus-dev/isaacus-typescript/commit/c5c42a4ab8d47a0d4de42df8ccacd87d88c83f0b))
+* **internal:** update global Error reference ([fe4dddb](https://github.com/isaacus-dev/isaacus-typescript/commit/fe4dddb7ff5d44bf609edd23d1b97a8d10f6b33a))
+* **mcp:** add cors to oauth metadata route ([736876e](https://github.com/isaacus-dev/isaacus-typescript/commit/736876e0b49b6d3c42130b161bd52ba16bd05f7b))
+* **mcp:** document remote server in README.md ([fbbcc43](https://github.com/isaacus-dev/isaacus-typescript/commit/fbbcc43335cb6562ba7609bd29206ebcd35ade80))
+* **mcp:** minor cleanup of types and package.json ([b2a894d](https://github.com/isaacus-dev/isaacus-typescript/commit/b2a894dc78fca32896b7f0beb29656f681c75923))
+* **mcp:** refactor streamable http transport ([16e027e](https://github.com/isaacus-dev/isaacus-typescript/commit/16e027eb2536ed2d452173df0cf22686bd78c75b))
+* **mcp:** rename dxt to mcpb ([2859f3c](https://github.com/isaacus-dev/isaacus-typescript/commit/2859f3c4473df3e0df28ac91df1dc393510f70a2))
+* **mcp:** update package.json ([9f2e9e3](https://github.com/isaacus-dev/isaacus-typescript/commit/9f2e9e31fe589d24fce942348053c3fd3c0c239d))
+* **mcp:** update README ([4eb94b3](https://github.com/isaacus-dev/isaacus-typescript/commit/4eb94b3367ad0c1071edb1c4fe215446dbe19aa9))
+* **mcp:** update types ([12563a9](https://github.com/isaacus-dev/isaacus-typescript/commit/12563a9b5596bea758f319118b43748a121d4f65))
+* **mcp:** upload dxt as release asset ([9ccb2af](https://github.com/isaacus-dev/isaacus-typescript/commit/9ccb2af27350bf9eca2c27045af6161bc5168cc6))
+* update @stainless-api/prism-cli to v5.15.0 ([cd4e751](https://github.com/isaacus-dev/isaacus-typescript/commit/cd4e7513d64584f424ed25bd156218993acaf6b2))
+* update CI script ([1674d7e](https://github.com/isaacus-dev/isaacus-typescript/commit/1674d7efca5a5e4137f9fddfa5b00ae03e7b9d86))
+
+
+### Documentation
+
+* **sdk:** make embeddings example first ([3f1e94b](https://github.com/isaacus-dev/isaacus-typescript/commit/3f1e94bef779f5b0d45eba325fdc2947c0f10728))
+
## 0.11.0 (2025-07-24)
Full Changelog: [v0.10.0...v0.11.0](https://github.com/isaacus-dev/isaacus-typescript/compare/v0.10.0...v0.11.0)
diff --git a/README.md b/README.md
index 2eea75c..5b38b5f 100644
--- a/README.md
+++ b/README.md
@@ -26,13 +26,12 @@ const client = new Isaacus({
apiKey: process.env['ISAACUS_API_KEY'], // This is the default and can be omitted
});
-const universalClassification = await client.classifications.universal.create({
- model: 'kanon-universal-classifier',
- query: 'This is a confidentiality clause.',
- texts: ['I agree not to tell anyone about the document.'],
+const embeddingResponse = await client.embeddings.create({
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
});
-console.log(universalClassification.classifications);
+console.log(embeddingResponse.embeddings);
```
### Request & Response types
@@ -47,13 +46,11 @@ const client = new Isaacus({
apiKey: process.env['ISAACUS_API_KEY'], // This is the default and can be omitted
});
-const params: Isaacus.Classifications.UniversalCreateParams = {
- model: 'kanon-universal-classifier',
- query: 'This is a confidentiality clause.',
- texts: ['I agree not to tell anyone about the document.'],
+const params: Isaacus.EmbeddingCreateParams = {
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
};
-const universalClassification: Isaacus.Classifications.UniversalClassification =
- await client.classifications.universal.create(params);
+const embeddingResponse: Isaacus.EmbeddingResponse = await client.embeddings.create(params);
```
Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors.
@@ -66,11 +63,10 @@ a subclass of `APIError` will be thrown:
```ts
-const universalClassification = await client.classifications.universal
+const embeddingResponse = await client.embeddings
.create({
- model: 'kanon-universal-classifier',
- query: 'This is a confidentiality clause.',
- texts: ['I agree not to tell anyone about the document.'],
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
})
.catch(async (err) => {
if (err instanceof Isaacus.APIError) {
@@ -112,7 +108,7 @@ const client = new Isaacus({
});
// Or, configure per-request:
-await client.classifications.universal.create({ model: 'kanon-universal-classifier', query: 'This is a confidentiality clause.', texts: ['I agree not to tell anyone about the document.'] }, {
+await client.embeddings.create({ model: 'kanon-2-embedder', texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'] }, {
maxRetries: 5,
});
```
@@ -129,7 +125,7 @@ const client = new Isaacus({
});
// Override per-request:
-await client.classifications.universal.create({ model: 'kanon-universal-classifier', query: 'This is a confidentiality clause.', texts: ['I agree not to tell anyone about the document.'] }, {
+await client.embeddings.create({ model: 'kanon-2-embedder', texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'] }, {
timeout: 5 * 1000,
});
```
@@ -152,25 +148,23 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse
```ts
const client = new Isaacus();
-const response = await client.classifications.universal
+const response = await client.embeddings
.create({
- model: 'kanon-universal-classifier',
- query: 'This is a confidentiality clause.',
- texts: ['I agree not to tell anyone about the document.'],
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
})
.asResponse();
console.log(response.headers.get('X-My-Header'));
console.log(response.statusText); // access the underlying Response object
-const { data: universalClassification, response: raw } = await client.classifications.universal
+const { data: embeddingResponse, response: raw } = await client.embeddings
.create({
- model: 'kanon-universal-classifier',
- query: 'This is a confidentiality clause.',
- texts: ['I agree not to tell anyone about the document.'],
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
})
.withResponse();
console.log(raw.headers.get('X-My-Header'));
-console.log(universalClassification.classifications);
+console.log(embeddingResponse.embeddings);
```
### Logging
@@ -250,7 +244,7 @@ parameter. This library doesn't validate at runtime that the request matches the
send will be sent as-is.
```ts
-client.classifications.universal.create({
+client.embeddings.create({
// ...
// @ts-expect-error baz is not yet public
baz: 'undocumented option',
diff --git a/api.md b/api.md
index 9cfaf10..8cdef8b 100644
--- a/api.md
+++ b/api.md
@@ -1,24 +1,34 @@
+# Embeddings
+
+Types:
+
+- EmbeddingResponse
+
+Methods:
+
+- client.embeddings.create({ ...params }) -> EmbeddingResponse
+
# Classifications
## Universal
Types:
-- UniversalClassification
+- UniversalClassificationResponse
Methods:
-- client.classifications.universal.create({ ...params }) -> UniversalClassification
+- client.classifications.universal.create({ ...params }) -> UniversalClassificationResponse
# Rerankings
Types:
-- Reranking
+- RerankingResponse
Methods:
-- client.rerankings.create({ ...params }) -> Reranking
+- client.rerankings.create({ ...params }) -> RerankingResponse
# Extractions
@@ -26,8 +36,8 @@ Methods:
Types:
-- AnswerExtraction
+- AnswerExtractionResponse
Methods:
-- client.extractions.qa.create({ ...params }) -> AnswerExtraction
+- client.extractions.qa.create({ ...params }) -> AnswerExtractionResponse
diff --git a/bin/publish-npm b/bin/publish-npm
index fa2243d..45e8aa8 100644
--- a/bin/publish-npm
+++ b/bin/publish-npm
@@ -58,4 +58,4 @@ else
fi
# Publish with the appropriate tag
-yarn publish --access public --tag "$TAG"
+yarn publish --tag "$TAG"
diff --git a/package.json b/package.json
index f29f3e4..a9d0106 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "isaacus",
- "version": "0.11.0",
+ "version": "0.11.1",
"description": "The official TypeScript library for the Isaacus API",
"author": "Isaacus ",
"types": "dist/index.d.ts",
@@ -13,6 +13,9 @@
"**/*"
],
"private": false,
+ "publishConfig": {
+ "access": "public"
+ },
"scripts": {
"test": "./scripts/test",
"build": "./scripts/build",
@@ -41,15 +44,12 @@
"publint": "^0.2.12",
"ts-jest": "^29.1.0",
"ts-node": "^10.5.0",
- "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz",
+ "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
"tsconfig-paths": "^4.0.0",
+ "tslib": "^2.8.1",
"typescript": "5.8.3",
"typescript-eslint": "8.31.1"
},
- "imports": {
- "isaacus": ".",
- "isaacus/*": "./src/*"
- },
"exports": {
".": {
"import": "./dist/index.mjs",
diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md
index eba51aa..9c5476e 100644
--- a/packages/mcp-server/README.md
+++ b/packages/mcp-server/README.md
@@ -126,6 +126,45 @@ over time, you can manually enable or disable certain capabilities:
--resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards
```
+## Running remotely
+
+Launching the client with `--transport=http` launches the server as a remote server using Streamable HTTP transport. The `--port` setting can choose the port it will run on, and the `--socket` setting allows it to run on a Unix socket.
+
+Authorization can be provided via the `Authorization` header using the Bearer scheme.
+
+Additionally, authorization can be provided via the following headers:
+| Header | Equivalent client option | Security scheme |
+| ------------------- | ------------------------ | --------------------- |
+| `x-isaacus-api-key` | `apiKey` | APIKeyBearerTokenAuth |
+
+A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`:
+
+```json
+{
+ "mcpServers": {
+ "isaacus_api": {
+ "url": "http://localhost:3000",
+ "headers": {
+ "Authorization": "Bearer "
+ }
+ }
+ }
+}
+```
+
+The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL.
+For example, to exclude specific tools while including others, use the URL:
+
+```
+http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards
+```
+
+Or, to configure for the Cursor client, with a custom max tool name length, use the URL:
+
+```
+http://localhost:3000?client=cursor&capability=tool-name-length%3D40
+```
+
## Importing the tools and server individually
```js
@@ -133,7 +172,7 @@ over time, you can manually enable or disable certain capabilities:
import { server, endpoints, init } from "isaacus-mcp/server";
// import a specific tool
-import createClassificationsUniversal from "isaacus-mcp/tools/classifications/universal/create-classifications-universal";
+import createEmbeddings from "isaacus-mcp/tools/embeddings/create-embeddings";
// initialize the server and all endpoints
init({ server, endpoints });
@@ -158,13 +197,17 @@ const myCustomEndpoint = {
};
// initialize the server with your custom endpoints
-init({ server: myServer, endpoints: [createClassificationsUniversal, myCustomEndpoint] });
+init({ server: myServer, endpoints: [createEmbeddings, myCustomEndpoint] });
```
## Available Tools
The following tools are available in this MCP server.
+### Resource `embeddings`:
+
+- `create_embeddings` (`write`): Embed legal texts with an Isaacus legal AI embedder.
+
### Resource `classifications.universal`:
- `create_classifications_universal` (`write`): Classify the relevance of legal documents to a query with an Isaacus universal legal AI classifier.
diff --git a/packages/mcp-server/build b/packages/mcp-server/build
index df7429e..fdc1237 100644
--- a/packages/mcp-server/build
+++ b/packages/mcp-server/build
@@ -30,3 +30,27 @@ cp tsconfig.dist-src.json dist/src/tsconfig.json
chmod +x dist/index.js
DIST_PATH=./dist PKG_IMPORT_PATH=isaacus-mcp/ node ../../scripts/utils/postprocess-files.cjs
+
+# mcp bundle
+rm -rf dist-bundle isaacus_api.mcpb; mkdir dist-bundle
+
+# copy package.json
+PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist-bundle/package.json
+
+# copy files
+node scripts/copy-bundle-files.cjs
+
+# install runtime deps
+cd dist-bundle
+npm install
+cd ..
+
+# pack bundle
+cp manifest.json dist-bundle
+
+npx mcpb pack dist-bundle isaacus_api.mcpb
+
+npx mcpb sign isaacus_api.mcpb --self-signed
+
+# clean up
+rm -rf dist-bundle
diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json
new file mode 100644
index 0000000..3406bea
--- /dev/null
+++ b/packages/mcp-server/manifest.json
@@ -0,0 +1,43 @@
+{
+ "dxt_version": "0.2",
+ "name": "isaacus-mcp",
+ "version": "0.11.0",
+ "description": "The official MCP Server for the Isaacus API",
+ "author": {
+ "name": "Isaacus",
+ "email": "support@isaacus.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/isaacus-dev/isaacus-typescript.git"
+ },
+ "homepage": "https://github.com/isaacus-dev/isaacus-typescript/tree/main/packages/mcp-server#readme",
+ "documentation": "https://docs.isaacus.com",
+ "server": {
+ "type": "node",
+ "entry_point": "index.js",
+ "mcp_config": {
+ "command": "node",
+ "args": ["${__dirname}/index.js"],
+ "env": {
+ "ISAACUS_API_KEY": "${user_config.ISAACUS_API_KEY}"
+ }
+ }
+ },
+ "user_config": {
+ "ISAACUS_API_KEY": {
+ "title": "api_key",
+ "description": "An Isaacus-issued API key passed as a bearer token via the `Authorization` header.",
+ "required": true,
+ "type": "string"
+ }
+ },
+ "tools": [],
+ "tools_generated": true,
+ "compatibility": {
+ "runtimes": {
+ "node": ">=18.0.0"
+ }
+ },
+ "keywords": ["api"]
+}
diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json
index 6ea8617..bfd570a 100644
--- a/packages/mcp-server/package.json
+++ b/packages/mcp-server/package.json
@@ -1,6 +1,6 @@
{
"name": "isaacus-mcp",
- "version": "0.11.0",
+ "version": "0.11.1",
"description": "The official MCP Server for the Isaacus API",
"author": "Isaacus ",
"types": "dist/index.d.ts",
@@ -15,6 +15,9 @@
"license": "Apache-2.0",
"packageManager": "yarn@1.22.22",
"private": false,
+ "publishConfig": {
+ "access": "public"
+ },
"scripts": {
"test": "jest",
"build": "bash ./build",
@@ -28,18 +31,28 @@
},
"dependencies": {
"isaacus": "file:../../dist/",
+ "@cloudflare/cabidela": "^0.2.4",
"@modelcontextprotocol/sdk": "^1.11.5",
- "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.2/jq-web.tar.gz",
+ "@valtown/deno-http-worker": "^0.0.21",
+ "cors": "^2.8.5",
+ "express": "^5.1.0",
+ "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz",
+ "qs": "^6.14.0",
"yargs": "^17.7.2",
- "@cloudflare/cabidela": "^0.2.4",
"zod": "^3.25.20",
- "zod-to-json-schema": "^3.24.5"
+ "zod-to-json-schema": "^3.24.5",
+ "zod-validation-error": "^4.0.1"
},
"bin": {
"mcp-server": "dist/index.js"
},
"devDependencies": {
+ "@anthropic-ai/mcpb": "^1.1.0",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.3",
"@types/jest": "^29.4.0",
+ "@types/qs": "^6.14.0",
+ "@types/yargs": "^17.0.8",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"eslint": "^8.49.0",
@@ -50,7 +63,7 @@
"ts-jest": "^29.1.0",
"ts-morph": "^19.0.0",
"ts-node": "^10.5.0",
- "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz",
+ "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
"tsconfig-paths": "^4.0.0",
"typescript": "5.8.3"
},
diff --git a/packages/mcp-server/scripts/copy-bundle-files.cjs b/packages/mcp-server/scripts/copy-bundle-files.cjs
new file mode 100644
index 0000000..a4bbcbd
--- /dev/null
+++ b/packages/mcp-server/scripts/copy-bundle-files.cjs
@@ -0,0 +1,36 @@
+const fs = require('fs');
+const path = require('path');
+const pkgJson = require('../dist-bundle/package.json');
+
+const distDir = path.resolve(__dirname, '..', 'dist');
+const distBundleDir = path.resolve(__dirname, '..', 'dist-bundle');
+const distBundlePkgJson = path.join(distBundleDir, 'package.json');
+
+async function* walk(dir) {
+ for await (const d of await fs.promises.opendir(dir)) {
+ const entry = path.join(dir, d.name);
+ if (d.isDirectory()) yield* walk(entry);
+ else if (d.isFile()) yield entry;
+ }
+}
+
+async function copyFiles() {
+ // copy runtime files
+ for await (const file of walk(distDir)) {
+ if (!/[cm]?js$/.test(file)) continue;
+ const dest = path.join(distBundleDir, path.relative(distDir, file));
+ await fs.promises.mkdir(path.dirname(dest), { recursive: true });
+ await fs.promises.copyFile(file, dest);
+ }
+
+ // replace package.json reference with local reference
+ for (const dep in pkgJson.dependencies) {
+ if (dep === 'isaacus') {
+ pkgJson.dependencies[dep] = 'file:../../../dist/';
+ }
+ }
+
+ await fs.promises.writeFile(distBundlePkgJson, JSON.stringify(pkgJson, null, 2));
+}
+
+copyFiles();
diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts
new file mode 100644
index 0000000..15ce7f5
--- /dev/null
+++ b/packages/mcp-server/src/code-tool-paths.cts
@@ -0,0 +1,3 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+export const workerPath = require.resolve('./code-tool-worker.mjs');
diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts
new file mode 100644
index 0000000..c7d8173
--- /dev/null
+++ b/packages/mcp-server/src/code-tool-types.ts
@@ -0,0 +1,14 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { ClientOptions } from 'isaacus';
+
+export type WorkerInput = {
+ opts: ClientOptions;
+ code: string;
+};
+export type WorkerSuccess = {
+ result: unknown | null;
+ logLines: string[];
+ errLines: string[];
+};
+export type WorkerError = { message: string | undefined };
diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts
new file mode 100644
index 0000000..032da42
--- /dev/null
+++ b/packages/mcp-server/src/code-tool-worker.ts
@@ -0,0 +1,46 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import util from 'node:util';
+import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types';
+import { Isaacus } from 'isaacus';
+
+const fetch = async (req: Request): Promise => {
+ const { opts, code } = (await req.json()) as WorkerInput;
+ const client = new Isaacus({
+ ...opts,
+ });
+
+ const logLines: string[] = [];
+ const errLines: string[] = [];
+ const console = {
+ log: (...args: unknown[]) => {
+ logLines.push(util.format(...args));
+ },
+ error: (...args: unknown[]) => {
+ errLines.push(util.format(...args));
+ },
+ };
+ try {
+ let run_ = async (client: any) => {};
+ eval(`
+ ${code}
+ run_ = run;
+ `);
+ const result = await run_(client);
+ return Response.json({
+ result,
+ logLines,
+ errLines,
+ } satisfies WorkerSuccess);
+ } catch (e) {
+ const message = e instanceof Error ? e.message : undefined;
+ return Response.json(
+ {
+ message,
+ } satisfies WorkerError,
+ { status: 400, statusText: 'Code execution error' },
+ );
+ }
+};
+
+export default { fetch };
diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts
new file mode 100644
index 0000000..61049d9
--- /dev/null
+++ b/packages/mcp-server/src/code-tool.ts
@@ -0,0 +1,146 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { dirname } from 'node:path';
+import { pathToFileURL } from 'node:url';
+import Isaacus, { ClientOptions } from 'isaacus';
+import { Endpoint, ContentBlock, Metadata } from './tools/types';
+
+import { Tool } from '@modelcontextprotocol/sdk/types.js';
+
+import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types';
+
+/**
+ * A tool that runs code against a copy of the SDK.
+ *
+ * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once,
+ * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then
+ * a generic endpoint that can be used to invoke any endpoint with the provided arguments.
+ *
+ * @param endpoints - The endpoints to include in the list.
+ */
+export async function codeTool(): Promise {
+ const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] };
+ const tool: Tool = {
+ name: 'execute',
+ description:
+ 'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.',
+ inputSchema: { type: 'object', properties: { code: { type: 'string' } } },
+ };
+
+ // Import dynamically to avoid failing at import time in cases where the environment is not well-supported.
+ const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker');
+ const { workerPath } = await import('./code-tool-paths.cjs');
+
+ const handler = async (client: Isaacus, args: unknown) => {
+ const baseURLHostname = new URL(client.baseURL).hostname;
+ const { code } = args as { code: string };
+
+ const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), {
+ runFlags: [
+ `--node-modules-dir=manual`,
+ `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`,
+ `--allow-net=${baseURLHostname}`,
+ // Allow environment variables because instantiating the client will try to read from them,
+ // even though they are not set.
+ '--allow-env',
+ ],
+ printOutput: true,
+ spawnOptions: {
+ cwd: dirname(workerPath),
+ },
+ });
+
+ try {
+ const resp = await new Promise((resolve, reject) => {
+ worker.addEventListener('exit', (exitCode) => {
+ reject(new Error(`Worker exited with code ${exitCode}`));
+ });
+
+ const opts: ClientOptions = {
+ baseURL: client.baseURL,
+ apiKey: client.apiKey,
+ defaultHeaders: {
+ 'X-Stainless-MCP': 'true',
+ },
+ };
+
+ const req = worker.request(
+ 'http://localhost',
+ {
+ headers: {
+ 'content-type': 'application/json',
+ },
+ method: 'POST',
+ },
+ (resp) => {
+ const body: Uint8Array[] = [];
+ resp.on('error', (err) => {
+ reject(err);
+ });
+ resp.on('data', (chunk) => {
+ body.push(chunk);
+ });
+ resp.on('end', () => {
+ resolve(
+ new Response(Buffer.concat(body).toString(), {
+ status: resp.statusCode ?? 200,
+ headers: resp.headers as any,
+ }),
+ );
+ });
+ },
+ );
+
+ const body = JSON.stringify({
+ opts,
+ code,
+ } satisfies WorkerInput);
+
+ req.write(body, (err) => {
+ if (err !== null && err !== undefined) {
+ reject(err);
+ }
+ });
+
+ req.end();
+ });
+
+ if (resp.status === 200) {
+ const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess;
+ const returnOutput: ContentBlock | null =
+ result === null ? null
+ : result === undefined ? null
+ : {
+ type: 'text',
+ text: typeof result === 'string' ? (result as string) : JSON.stringify(result),
+ };
+ const logOutput: ContentBlock | null =
+ logLines.length === 0 ?
+ null
+ : {
+ type: 'text',
+ text: logLines.join('\n'),
+ };
+ const errOutput: ContentBlock | null =
+ errLines.length === 0 ?
+ null
+ : {
+ type: 'text',
+ text: 'Error output:\n' + errLines.join('\n'),
+ };
+ return {
+ content: [returnOutput, logOutput, errOutput].filter((block) => block !== null),
+ };
+ } else {
+ const { message } = (await resp.json()) as WorkerError;
+ throw new Error(message);
+ }
+ } catch (e) {
+ throw e;
+ } finally {
+ worker.terminate();
+ }
+ };
+
+ return { metadata, tool, handler };
+}
diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts
index ff0d6d4..f84053c 100644
--- a/packages/mcp-server/src/compat.ts
+++ b/packages/mcp-server/src/compat.ts
@@ -1,4 +1,5 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
+import { z } from 'zod';
import { Endpoint } from './tools';
export interface ClientCapabilities {
@@ -19,12 +20,13 @@ export const defaultClientCapabilities: ClientCapabilities = {
toolNameLength: undefined,
};
-export type ClientType = 'openai-agents' | 'claude' | 'claude-code' | 'cursor';
+export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']);
+export type ClientType = z.infer;
// Client presets for compatibility
// Note that these could change over time as models get better, so this is
// a best effort.
-export const knownClients: Record = {
+export const knownClients: Record, ClientCapabilities> = {
'openai-agents': {
topLevelUnions: false,
validJson: true,
@@ -70,8 +72,11 @@ export function parseEmbeddedJSON(args: Record, schema: Record<
if (typeof value === 'string') {
try {
const parsed = JSON.parse(value);
- newArgs[key] = parsed;
- updated = true;
+ // Only parse if result is a plain object (not array, null, or primitive)
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
+ newArgs[key] = parsed;
+ updated = true;
+ }
} catch (e) {
// Not valid JSON, leave as is
}
diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts
new file mode 100644
index 0000000..e28e583
--- /dev/null
+++ b/packages/mcp-server/src/docs-search-tool.ts
@@ -0,0 +1,48 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { Metadata, asTextContentResult } from './tools/types';
+
+import { Tool } from '@modelcontextprotocol/sdk/types.js';
+
+export const metadata: Metadata = {
+ resource: 'all',
+ operation: 'read',
+ tags: [],
+ httpMethod: 'get',
+};
+
+export const tool: Tool = {
+ name: 'search_docs',
+ description:
+ 'Search for documentation for how to use the client to interact with the API.\nThe tool will return an array of Markdown-formatted documentation pages.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ query: {
+ type: 'string',
+ description: 'The query to search for.',
+ },
+ language: {
+ type: 'string',
+ description: 'The language for the SDK to search for.',
+ enum: ['http', 'python', 'go', 'typescript', 'terraform', 'ruby', 'java', 'kotlin'],
+ },
+ },
+ required: ['query', 'language'],
+ },
+ annotations: {
+ readOnlyHint: true,
+ },
+};
+
+const docsSearchURL =
+ process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/isaacus/docs/search';
+
+export const handler = async (_: unknown, args: Record | undefined) => {
+ const body = args as any;
+ const query = new URLSearchParams(body).toString();
+ const result = await fetch(`${docsSearchURL}?${query}`);
+ return asTextContentResult(await result.json());
+};
+
+export default { metadata, tool, handler };
diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts
index 87eab2d..1aa9a40 100644
--- a/packages/mcp-server/src/filtering.ts
+++ b/packages/mcp-server/src/filtering.ts
@@ -1,8 +1,7 @@
// @ts-nocheck
import initJq from 'jq-web';
-export async function maybeFilter(args: Record | undefined, response: any): Promise {
- const jqFilter = args?.['jq_filter'];
+export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise {
if (jqFilter && typeof jqFilter === 'string') {
return await jq(response, jqFilter);
} else {
diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts
new file mode 100644
index 0000000..c536d7b
--- /dev/null
+++ b/packages/mcp-server/src/headers.ts
@@ -0,0 +1,23 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { IncomingMessage } from 'node:http';
+import { ClientOptions } from 'isaacus';
+
+export const parseAuthHeaders = (req: IncomingMessage): Partial => {
+ if (req.headers.authorization) {
+ const scheme = req.headers.authorization.split(' ')[0]!;
+ const value = req.headers.authorization.slice(scheme.length + 1);
+ switch (scheme) {
+ case 'Bearer':
+ return { apiKey: req.headers.authorization.slice('Bearer '.length) };
+ default:
+ throw new Error(`Unsupported authorization scheme`);
+ }
+ }
+
+ const apiKey =
+ Array.isArray(req.headers['x-isaacus-api-key']) ?
+ req.headers['x-isaacus-api-key'][0]
+ : req.headers['x-isaacus-api-key'];
+ return { apiKey };
+};
diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts
new file mode 100644
index 0000000..ec34ab4
--- /dev/null
+++ b/packages/mcp-server/src/http.ts
@@ -0,0 +1,127 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
+import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
+
+import express from 'express';
+import { fromError } from 'zod-validation-error/v3';
+import { McpOptions, parseQueryOptions } from './options';
+import { ClientOptions, initMcpServer, newMcpServer } from './server';
+import { parseAuthHeaders } from './headers';
+
+const newServer = ({
+ clientOptions,
+ mcpOptions: defaultMcpOptions,
+ req,
+ res,
+}: {
+ clientOptions: ClientOptions;
+ mcpOptions: McpOptions;
+ req: express.Request;
+ res: express.Response;
+}): McpServer | null => {
+ const server = newMcpServer();
+
+ let mcpOptions: McpOptions;
+ try {
+ mcpOptions = parseQueryOptions(defaultMcpOptions, req.query);
+ } catch (error) {
+ res.status(400).json({
+ jsonrpc: '2.0',
+ error: {
+ code: -32000,
+ message: `Invalid request: ${fromError(error)}`,
+ },
+ });
+ return null;
+ }
+
+ try {
+ const authOptions = parseAuthHeaders(req);
+ initMcpServer({
+ server: server,
+ clientOptions: {
+ ...clientOptions,
+ ...authOptions,
+ },
+ mcpOptions,
+ });
+ } catch {
+ res.status(401).json({
+ jsonrpc: '2.0',
+ error: {
+ code: -32000,
+ message: 'Unauthorized',
+ },
+ });
+ return null;
+ }
+
+ return server;
+};
+
+const post =
+ (options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) =>
+ async (req: express.Request, res: express.Response) => {
+ const server = newServer({ ...options, req, res });
+ // If we return null, we already set the authorization error.
+ if (server === null) return;
+ const transport = new StreamableHTTPServerTransport({
+ // Stateless server
+ sessionIdGenerator: undefined,
+ });
+ await server.connect(transport);
+ await transport.handleRequest(req, res, req.body);
+ };
+
+const get = async (req: express.Request, res: express.Response) => {
+ res.status(405).json({
+ jsonrpc: '2.0',
+ error: {
+ code: -32000,
+ message: 'Method not supported',
+ },
+ });
+};
+
+const del = async (req: express.Request, res: express.Response) => {
+ res.status(405).json({
+ jsonrpc: '2.0',
+ error: {
+ code: -32000,
+ message: 'Method not supported',
+ },
+ });
+};
+
+export const streamableHTTPApp = ({
+ clientOptions = {},
+ mcpOptions = {},
+}: {
+ clientOptions?: ClientOptions;
+ mcpOptions?: McpOptions;
+}): express.Express => {
+ const app = express();
+ app.set('query parser', 'extended');
+ app.use(express.json());
+
+ app.get('/', get);
+ app.post('/', post({ clientOptions, mcpOptions }));
+ app.delete('/', del);
+
+ return app;
+};
+
+export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => {
+ const app = streamableHTTPApp({ mcpOptions: options });
+ const server = app.listen(port);
+ const address = server.address();
+
+ if (typeof address === 'string') {
+ console.error(`MCP Server running on streamable HTTP at ${address}`);
+ } else if (address !== null) {
+ console.error(`MCP Server running on streamable HTTP on port ${address.port}`);
+ } else {
+ console.error(`MCP Server running on streamable HTTP on port ${port}`);
+ }
+};
diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts
index 0621357..4850a0e 100644
--- a/packages/mcp-server/src/index.ts
+++ b/packages/mcp-server/src/index.ts
@@ -1,9 +1,10 @@
#!/usr/bin/env node
-import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
-import { init, selectTools, server } from './server';
+import { selectTools } from './server';
import { Endpoint, endpoints } from './tools';
-import { McpOptions, parseOptions } from './options';
+import { McpOptions, parseCLIOptions } from './options';
+import { launchStdioServer } from './stdio';
+import { launchStreamableHTTPServer } from './http';
async function main() {
const options = parseOptionsOrError();
@@ -13,18 +14,21 @@ async function main() {
return;
}
- const includedTools = selectToolsOrError(endpoints, options);
+ const selectedTools = await selectToolsOrError(endpoints, options);
console.error(
- `MCP Server starting with ${includedTools.length} tools:`,
- includedTools.map((e) => e.tool.name),
+ `MCP Server starting with ${selectedTools.length} tools:`,
+ selectedTools.map((e) => e.tool.name),
);
- init({ server, endpoints: includedTools });
-
- const transport = new StdioServerTransport();
- await server.connect(transport);
- console.error('MCP Server running on stdio');
+ switch (options.transport) {
+ case 'stdio':
+ await launchStdioServer(options);
+ break;
+ case 'http':
+ await launchStreamableHTTPServer(options, options.port ?? options.socket);
+ break;
+ }
}
if (require.main === module) {
@@ -36,16 +40,16 @@ if (require.main === module) {
function parseOptionsOrError() {
try {
- return parseOptions();
+ return parseCLIOptions();
} catch (error) {
console.error('Error parsing options:', error);
process.exit(1);
}
}
-function selectToolsOrError(endpoints: Endpoint[], options: McpOptions) {
+async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise {
try {
- const includedTools = selectTools(endpoints, options);
+ const includedTools = await selectTools(endpoints, options);
if (includedTools.length === 0) {
console.error('No tools match the provided filters.');
process.exit(1);
diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts
index c075101..4fe3b98 100644
--- a/packages/mcp-server/src/options.ts
+++ b/packages/mcp-server/src/options.ts
@@ -1,18 +1,25 @@
+import qs from 'qs';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
+import z from 'zod';
import { endpoints, Filter } from './tools';
import { ClientCapabilities, knownClients, ClientType } from './compat';
export type CLIOptions = McpOptions & {
list: boolean;
+ transport: 'stdio' | 'http';
+ port: number | undefined;
+ socket: string | undefined;
};
export type McpOptions = {
- client: ClientType | undefined;
- includeDynamicTools: boolean | undefined;
- includeAllTools: boolean | undefined;
- filters: Filter[];
- capabilities?: Partial;
+ client?: ClientType | undefined;
+ includeDynamicTools?: boolean | undefined;
+ includeAllTools?: boolean | undefined;
+ includeCodeTools?: boolean | undefined;
+ includeDocsTools?: boolean | undefined;
+ filters?: Filter[] | undefined;
+ capabilities?: Partial | undefined;
};
const CAPABILITY_CHOICES = [
@@ -44,18 +51,18 @@ function parseCapabilityValue(cap: string): { name: Capability; value?: number }
return { name: cap as Capability };
}
-export function parseOptions(): CLIOptions {
+export function parseCLIOptions(): CLIOptions {
const opts = yargs(hideBin(process.argv))
.option('tools', {
type: 'string',
array: true,
- choices: ['dynamic', 'all'],
+ choices: ['dynamic', 'all', 'code', 'docs'],
description: 'Use dynamic tools or all tools',
})
.option('no-tools', {
type: 'string',
array: true,
- choices: ['dynamic', 'all'],
+ choices: ['dynamic', 'all', 'code', 'docs'],
description: 'Do not use any dynamic or all tools',
})
.option('tool', {
@@ -129,6 +136,20 @@ export function parseOptions(): CLIOptions {
type: 'boolean',
description: 'Print detailed explanation of client capabilities and exit',
})
+ .option('transport', {
+ type: 'string',
+ choices: ['stdio', 'http'],
+ default: 'stdio',
+ description: 'What transport to use; stdio for local servers or http for remote servers',
+ })
+ .option('port', {
+ type: 'number',
+ description: 'Port to serve on if using http transport',
+ })
+ .option('socket', {
+ type: 'string',
+ description: 'Unix socket to serve on if using http transport',
+ })
.help();
for (const [command, desc] of examples()) {
@@ -184,14 +205,7 @@ export function parseOptions(): CLIOptions {
}
// Parse client capabilities
- const clientCapabilities: ClientCapabilities = {
- topLevelUnions: true,
- validJson: true,
- refs: true,
- unions: true,
- formats: true,
- toolNameLength: undefined,
- };
+ const clientCapabilities: Partial = {};
// Apply individual capability overrides
if (Array.isArray(argv.capability)) {
@@ -232,20 +246,153 @@ export function parseOptions(): CLIOptions {
}
}
- const explicitTools = Boolean(argv.tools || argv.noTools);
- const includeDynamicTools =
- explicitTools ? argv.tools?.includes('dynamic') && !argv.noTools?.includes('dynamic') : undefined;
- const includeAllTools =
- explicitTools ? argv.tools?.includes('all') && !argv.noTools?.includes('all') : undefined;
+ const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code' | 'docs') =>
+ argv.noTools?.includes(toolType) ? false
+ : argv.tools?.includes(toolType) ? true
+ : undefined;
+
+ const includeDynamicTools = shouldIncludeToolType('dynamic');
+ const includeAllTools = shouldIncludeToolType('all');
+ const includeCodeTools = shouldIncludeToolType('code');
+ const includeDocsTools = shouldIncludeToolType('docs');
+
+ const transport = argv.transport as 'stdio' | 'http';
const client = argv.client as ClientType;
return {
- client: client && knownClients[client] ? client : undefined,
+ client: client && client !== 'infer' && knownClients[client] ? client : undefined,
includeDynamicTools,
includeAllTools,
+ includeCodeTools,
+ includeDocsTools,
filters,
capabilities: clientCapabilities,
list: argv.list || false,
+ transport,
+ port: argv.port,
+ socket: argv.socket,
+ };
+}
+
+const coerceArray = (zodType: T) =>
+ z.preprocess(
+ (val) =>
+ Array.isArray(val) ? val
+ : val ? [val]
+ : val,
+ z.array(zodType).optional(),
+ );
+
+const QueryOptions = z.object({
+ tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Use dynamic tools or all tools'),
+ no_tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Do not use dynamic tools or all tools'),
+ tool: coerceArray(z.string()).describe('Include tools matching the specified names'),
+ resource: coerceArray(z.string()).describe('Include tools matching the specified resources'),
+ operation: coerceArray(z.enum(['read', 'write'])).describe(
+ 'Include tools matching the specified operations',
+ ),
+ tag: coerceArray(z.string()).describe('Include tools with the specified tags'),
+ no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'),
+ no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'),
+ no_operation: coerceArray(z.enum(['read', 'write'])).describe(
+ 'Exclude tools matching the specified operations',
+ ),
+ no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'),
+ client: ClientType.optional().describe('Specify the MCP client being used'),
+ capability: coerceArray(z.string()).describe('Specify client capabilities'),
+ no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'),
+});
+
+export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions {
+ const queryObject = typeof query === 'string' ? qs.parse(query) : query;
+ const queryOptions = QueryOptions.parse(queryObject);
+
+ const filters: Filter[] = [...(defaultOptions.filters ?? [])];
+
+ for (const resource of queryOptions.resource || []) {
+ filters.push({ type: 'resource', op: 'include', value: resource });
+ }
+ for (const operation of queryOptions.operation || []) {
+ filters.push({ type: 'operation', op: 'include', value: operation });
+ }
+ for (const tag of queryOptions.tag || []) {
+ filters.push({ type: 'tag', op: 'include', value: tag });
+ }
+ for (const tool of queryOptions.tool || []) {
+ filters.push({ type: 'tool', op: 'include', value: tool });
+ }
+ for (const resource of queryOptions.no_resource || []) {
+ filters.push({ type: 'resource', op: 'exclude', value: resource });
+ }
+ for (const operation of queryOptions.no_operation || []) {
+ filters.push({ type: 'operation', op: 'exclude', value: operation });
+ }
+ for (const tag of queryOptions.no_tag || []) {
+ filters.push({ type: 'tag', op: 'exclude', value: tag });
+ }
+ for (const tool of queryOptions.no_tool || []) {
+ filters.push({ type: 'tool', op: 'exclude', value: tool });
+ }
+
+ // Parse client capabilities
+ const clientCapabilities: Partial = { ...defaultOptions.capabilities };
+
+ for (const cap of queryOptions.capability || []) {
+ const parsed = parseCapabilityValue(cap);
+ if (parsed.name === 'top-level-unions') {
+ clientCapabilities.topLevelUnions = true;
+ } else if (parsed.name === 'valid-json') {
+ clientCapabilities.validJson = true;
+ } else if (parsed.name === 'refs') {
+ clientCapabilities.refs = true;
+ } else if (parsed.name === 'unions') {
+ clientCapabilities.unions = true;
+ } else if (parsed.name === 'formats') {
+ clientCapabilities.formats = true;
+ } else if (parsed.name === 'tool-name-length') {
+ clientCapabilities.toolNameLength = parsed.value;
+ }
+ }
+
+ for (const cap of queryOptions.no_capability || []) {
+ if (cap === 'top-level-unions') {
+ clientCapabilities.topLevelUnions = false;
+ } else if (cap === 'valid-json') {
+ clientCapabilities.validJson = false;
+ } else if (cap === 'refs') {
+ clientCapabilities.refs = false;
+ } else if (cap === 'unions') {
+ clientCapabilities.unions = false;
+ } else if (cap === 'formats') {
+ clientCapabilities.formats = false;
+ } else if (cap === 'tool-name-length') {
+ clientCapabilities.toolNameLength = undefined;
+ }
+ }
+
+ let dynamicTools: boolean | undefined =
+ queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false
+ : queryOptions.tools?.includes('dynamic') ? true
+ : defaultOptions.includeDynamicTools;
+
+ let allTools: boolean | undefined =
+ queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false
+ : queryOptions.tools?.includes('all') ? true
+ : defaultOptions.includeAllTools;
+
+ let docsTools: boolean | undefined =
+ queryOptions.no_tools && queryOptions.no_tools?.includes('docs') ? false
+ : queryOptions.tools?.includes('docs') ? true
+ : defaultOptions.includeDocsTools;
+
+ return {
+ client: queryOptions.client ?? defaultOptions.client,
+ includeDynamicTools: dynamicTools,
+ includeAllTools: allTools,
+ includeCodeTools: undefined,
+ includeDocsTools: docsTools,
+ filters,
+ capabilities: clientCapabilities,
};
}
diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts
index ae9b363..bef0e7d 100644
--- a/packages/mcp-server/src/server.ts
+++ b/packages/mcp-server/src/server.ts
@@ -3,7 +3,13 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Endpoint, endpoints, HandlerFunction, query } from './tools';
-import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
+import {
+ CallToolRequestSchema,
+ ListToolsRequestSchema,
+ SetLevelRequestSchema,
+ Implementation,
+ Tool,
+} from '@modelcontextprotocol/sdk/types.js';
import { ClientOptions } from 'isaacus';
import Isaacus from 'isaacus';
import {
@@ -14,6 +20,8 @@ import {
parseEmbeddedJSON,
} from './compat';
import { dynamicTools } from './dynamic-tools';
+import { codeTool } from './code-tool';
+import docsSearchTool from './docs-search-tool';
import { McpOptions } from './options';
export { McpOptions } from './options';
@@ -22,14 +30,17 @@ export { Filter } from './tools';
export { ClientOptions } from 'isaacus';
export { endpoints } from './tools';
+export const newMcpServer = () =>
+ new McpServer(
+ {
+ name: 'isaacus_api',
+ version: '0.11.1',
+ },
+ { capabilities: { tools: {}, logging: {} } },
+ );
+
// Create server instance
-export const server = new McpServer(
- {
- name: 'isaacus_api',
- version: '0.11.0',
- },
- { capabilities: { tools: {} } },
-);
+export const server = newMcpServer();
/**
* Initializes the provided MCP Server with the given tools and handlers.
@@ -37,72 +48,127 @@ export const server = new McpServer(
*/
export function initMcpServer(params: {
server: Server | McpServer;
- clientOptions: ClientOptions;
- mcpOptions: McpOptions;
- endpoints?: { tool: Tool; handler: HandlerFunction }[];
-}) {
- const transformedEndpoints = selectTools(endpoints, params.mcpOptions);
- const client = new Isaacus(params.clientOptions);
- const capabilities = {
- ...defaultClientCapabilities,
- ...(params.mcpOptions.client ? knownClients[params.mcpOptions.client] : params.mcpOptions.capabilities),
- };
- init({ server: params.server, client, endpoints: transformedEndpoints, capabilities });
-}
-
-export function init(params: {
- server: Server | McpServer;
- client?: Isaacus;
- endpoints?: { tool: Tool; handler: HandlerFunction }[];
- capabilities?: Partial;
+ clientOptions?: ClientOptions;
+ mcpOptions?: McpOptions;
}) {
const server = params.server instanceof McpServer ? params.server.server : params.server;
- const providedEndpoints = params.endpoints || endpoints;
+ const mcpOptions = params.mcpOptions ?? {};
+
+ let providedEndpoints: Endpoint[] | null = null;
+ let endpointMap: Record | null = null;
+
+ const initTools = async (implementation?: Implementation) => {
+ if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) {
+ mcpOptions.client =
+ implementation.name.toLowerCase().includes('claude') ? 'claude'
+ : implementation.name.toLowerCase().includes('cursor') ? 'cursor'
+ : undefined;
+ mcpOptions.capabilities = {
+ ...(mcpOptions.client && knownClients[mcpOptions.client]),
+ ...mcpOptions.capabilities,
+ };
+ }
+ providedEndpoints ??= await selectTools(endpoints, mcpOptions);
+ endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint]));
+ };
- const endpointMap = Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint]));
+ const logAtLevel =
+ (level: 'debug' | 'info' | 'warning' | 'error') =>
+ (message: string, ...rest: unknown[]) => {
+ void server.sendLoggingMessage({
+ level,
+ data: { message, rest },
+ });
+ };
+ const logger = {
+ debug: logAtLevel('debug'),
+ info: logAtLevel('info'),
+ warn: logAtLevel('warning'),
+ error: logAtLevel('error'),
+ };
- const client = params.client || new Isaacus({ defaultHeaders: { 'X-Stainless-MCP': 'true' } });
+ let client = new Isaacus({
+ logger,
+ ...params.clientOptions,
+ defaultHeaders: {
+ ...params.clientOptions?.defaultHeaders,
+ 'X-Stainless-MCP': 'true',
+ },
+ });
server.setRequestHandler(ListToolsRequestSchema, async () => {
+ if (providedEndpoints === null) {
+ await initTools(server.getClientVersion());
+ }
return {
- tools: providedEndpoints.map((endpoint) => endpoint.tool),
+ tools: providedEndpoints!.map((endpoint) => endpoint.tool),
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (endpointMap === null) {
+ await initTools(server.getClientVersion());
+ }
const { name, arguments: args } = request.params;
- const endpoint = endpointMap[name];
+ const endpoint = endpointMap![name];
if (!endpoint) {
throw new Error(`Unknown tool: ${name}`);
}
- return executeHandler(endpoint.tool, endpoint.handler, client, args, params.capabilities);
+ return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities);
+ });
+
+ server.setRequestHandler(SetLevelRequestSchema, async (request) => {
+ const { level } = request.params;
+ switch (level) {
+ case 'debug':
+ client = client.withOptions({ logLevel: 'debug' });
+ break;
+ case 'info':
+ client = client.withOptions({ logLevel: 'info' });
+ break;
+ case 'notice':
+ case 'warning':
+ client = client.withOptions({ logLevel: 'warn' });
+ break;
+ case 'error':
+ client = client.withOptions({ logLevel: 'error' });
+ break;
+ default:
+ client = client.withOptions({ logLevel: 'off' });
+ break;
+ }
+ return {};
});
}
/**
* Selects the tools to include in the MCP Server based on the provided options.
*/
-export function selectTools(endpoints: Endpoint[], options: McpOptions) {
- const filteredEndpoints = query(options.filters, endpoints);
+export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise {
+ const filteredEndpoints = query(options?.filters ?? [], endpoints);
- let includedTools = filteredEndpoints;
+ let includedTools = filteredEndpoints.slice();
if (includedTools.length > 0) {
- if (options.includeDynamicTools) {
+ if (options?.includeDynamicTools) {
includedTools = dynamicTools(includedTools);
}
} else {
- if (options.includeAllTools) {
- includedTools = endpoints;
- } else if (options.includeDynamicTools) {
+ if (options?.includeAllTools) {
+ includedTools = endpoints.slice();
+ } else if (options?.includeDynamicTools) {
includedTools = dynamicTools(endpoints);
+ } else if (options?.includeCodeTools) {
+ includedTools = [await codeTool()];
} else {
- includedTools = endpoints;
+ includedTools = endpoints.slice();
}
}
-
- const capabilities = { ...defaultClientCapabilities, ...options.capabilities };
+ if (options?.includeDocsTools ?? true) {
+ includedTools.push(docsSearchTool);
+ }
+ const capabilities = { ...defaultClientCapabilities, ...options?.capabilities };
return applyCompatibilityTransformations(includedTools, capabilities);
}
@@ -117,7 +183,7 @@ export async function executeHandler(
compatibilityOptions?: Partial,
) {
const options = { ...defaultClientCapabilities, ...compatibilityOptions };
- if (options.validJson && args) {
+ if (!options.validJson && args) {
args = parseEmbeddedJSON(args, tool.inputSchema);
}
return await handler(client, args || {});
diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts
new file mode 100644
index 0000000..d902a5b
--- /dev/null
+++ b/packages/mcp-server/src/stdio.ts
@@ -0,0 +1,13 @@
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { initMcpServer, newMcpServer } from './server';
+import { McpOptions } from './options';
+
+export const launchStdioServer = async (options: McpOptions) => {
+ const server = newMcpServer();
+
+ initMcpServer({ server, mcpOptions: options });
+
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error('MCP Server running on stdio');
+};
diff --git a/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts b/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts
index dbaabbd..fe35998 100644
--- a/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts
+++ b/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts
@@ -18,7 +18,7 @@ export const metadata: Metadata = {
export const tool: Tool = {
name: 'create_classifications_universal',
description:
- "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nClassify the relevance of legal documents to a query with an Isaacus universal legal AI classifier.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/universal_classification',\n $defs: {\n universal_classification: {\n type: 'object',\n title: 'Universal classification response',\n description: 'Classifications of the relevance of legal documents to a query produced by an Isaacus universal legal AI classifier.',\n properties: {\n classifications: {\n type: 'array',\n description: 'The classifications of the texts, by relevance to the query, in order from highest to lowest relevance score.',\n items: {\n type: 'object',\n title: 'Universal classification',\n properties: {\n chunks: {\n type: 'array',\n description: 'The text as broken into chunks by [semchunk](https://github.com/isaacus-dev/semchunk), each chunk with its own confidence score, ordered from highest to lowest score.\\n\\nIf no chunking occurred, this will be `null`.',\n items: {\n type: 'object',\n title: 'Universal classification chunk',\n properties: {\n end: {\n type: 'integer',\n description: 'The index of the character immediately after the last character of the chunk in the original text, beginning from `0` (such that, in Python, the chunk is equivalent to `text[start:end]`).'\n },\n index: {\n type: 'integer',\n description: 'The original position of the chunk in the outputted list of chunks before sorting, starting from `0` (and, therefore, ending at the number of chunks minus `1`).'\n },\n score: {\n type: 'number',\n description: 'The model\\'s score of the likelihood that the query expressed about the chunk is supported by the chunk.\\n\\nA score greater than `0.5` indicates that the chunk supports the query, while a score less than `0.5` indicates that the chunk does not support the query.'\n },\n start: {\n type: 'integer',\n description: 'The index of the character in the original text where the chunk starts, beginning from `0`.'\n },\n text: {\n type: 'string',\n description: 'The text of the chunk.'\n }\n },\n required: [ 'end',\n 'index',\n 'score',\n 'start',\n 'text'\n ]\n }\n },\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n score: {\n type: 'number',\n description: 'A score of the likelihood that the query expressed about the text is supported by the text.\\n\\nA score greater than `0.5` indicates that the text supports the query, while a score less than `0.5` indicates that the text does not support the query.'\n }\n },\n required: [ 'chunks',\n 'index',\n 'score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Universal classification usage',\n description: 'Statistics about the usage of resources in the process of classifying the text.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'classifications',\n 'usage'\n ]\n }\n }\n}\n```",
+ "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nClassify the relevance of legal documents to a query with an Isaacus universal legal AI classifier.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/universal_classification_response',\n $defs: {\n universal_classification_response: {\n type: 'object',\n title: 'Universal classification response',\n description: 'Classifications of the relevance of legal documents to a query produced by an Isaacus universal legal AI classifier.',\n properties: {\n classifications: {\n type: 'array',\n description: 'The classifications of the texts, by relevance to the query, in order from highest to lowest relevance score.',\n items: {\n type: 'object',\n title: 'Universal classification',\n properties: {\n chunks: {\n type: 'array',\n description: 'The text as broken into chunks by [semchunk](https://github.com/isaacus-dev/semchunk), each chunk with its own confidence score, ordered from highest to lowest score.\\n\\nIf no chunking occurred, this will be `null`.',\n items: {\n type: 'object',\n title: 'Universal classification chunk',\n properties: {\n end: {\n type: 'integer',\n description: 'The index of the character immediately after the last character of the chunk in the original text, beginning from `0` (such that, in Python, the chunk is equivalent to `text[start:end]`).'\n },\n index: {\n type: 'integer',\n description: 'The original position of the chunk in the outputted list of chunks before sorting, starting from `0` (and, therefore, ending at the number of chunks minus `1`).'\n },\n score: {\n type: 'number',\n description: 'The model\\'s score of the likelihood that the query expressed about the chunk is supported by the chunk.\\n\\nA score greater than `0.5` indicates that the chunk supports the query, while a score less than `0.5` indicates that the chunk does not support the query.'\n },\n start: {\n type: 'integer',\n description: 'The index of the character in the original text where the chunk starts, beginning from `0`.'\n },\n text: {\n type: 'string',\n description: 'The text of the chunk.'\n }\n },\n required: [ 'end',\n 'index',\n 'score',\n 'start',\n 'text'\n ]\n }\n },\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n score: {\n type: 'number',\n description: 'A score of the likelihood that the query expressed about the text is supported by the text.\\n\\nA score greater than `0.5` indicates that the text supports the query, while a score less than `0.5` indicates that the text does not support the query.'\n }\n },\n required: [ 'chunks',\n 'index',\n 'score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Universal classification usage',\n description: 'Statistics about the usage of resources in the process of classifying the text.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'classifications',\n 'usage'\n ]\n }\n }\n}\n```",
inputSchema: {
type: 'object',
properties: {
@@ -88,8 +88,10 @@ export const tool: Tool = {
};
export const handler = async (client: Isaacus, args: Record | undefined) => {
- const body = args as any;
- return asTextContentResult(await maybeFilter(args, await client.classifications.universal.create(body)));
+ const { jq_filter, ...body } = args as any;
+ return asTextContentResult(
+ await maybeFilter(jq_filter, await client.classifications.universal.create(body)),
+ );
};
export default { metadata, tool, handler };
diff --git a/packages/mcp-server/src/tools/embeddings/create-embeddings.ts b/packages/mcp-server/src/tools/embeddings/create-embeddings.ts
new file mode 100644
index 0000000..0c2bcb3
--- /dev/null
+++ b/packages/mcp-server/src/tools/embeddings/create-embeddings.ts
@@ -0,0 +1,81 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { maybeFilter } from 'isaacus-mcp/filtering';
+import { Metadata, asTextContentResult } from 'isaacus-mcp/tools/types';
+
+import { Tool } from '@modelcontextprotocol/sdk/types.js';
+import Isaacus from 'isaacus';
+
+export const metadata: Metadata = {
+ resource: 'embeddings',
+ operation: 'write',
+ tags: [],
+ httpMethod: 'post',
+ httpPath: '/embeddings',
+ operationId: 'CreateEmbeddings',
+};
+
+export const tool: Tool = {
+ name: 'create_embeddings',
+ description:
+ "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nEmbed legal texts with an Isaacus legal AI embedder.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/embedding_response',\n $defs: {\n embedding_response: {\n type: 'object',\n title: 'Embedding response',\n description: 'Embeddings of legal texts produced by an Isaacus legal AI embedder.',\n properties: {\n embeddings: {\n type: 'array',\n description: 'The embeddings of the inputs.',\n items: {\n type: 'object',\n title: 'Content embedding',\n properties: {\n embedding: {\n type: 'array',\n description: 'The embedding of the content represented as an array of floating point numbers.',\n items: {\n type: 'number'\n }\n },\n index: {\n type: 'integer',\n description: 'The position of the content in the input array of contents, starting from `0` (and, therefore, ending at the number of contents minus `1`).'\n }\n },\n required: [ 'embedding',\n 'index'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Embedding usage',\n description: 'Statistics about the usage of resources in the process of embedding the inputs.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'embeddings',\n 'usage'\n ]\n }\n }\n}\n```",
+ inputSchema: {
+ type: 'object',
+ properties: {
+ model: {
+ type: 'string',
+ description: 'The ID of the [model](https://docs.isaacus.com/models#embedding) to use for embedding.',
+ enum: ['kanon-2-embedder'],
+ },
+ texts: {
+ anyOf: [
+ {
+ type: 'array',
+ items: {
+ type: 'string',
+ title: 'Non-blank string',
+ },
+ },
+ {
+ type: 'string',
+ title: 'Non-blank string',
+ },
+ ],
+ description:
+ 'The text or array of texts to embed.\n\nEach text must contain at least one non-whitespace character.\n\nNo more than 128 texts can be embedded in a single request.',
+ },
+ dimensions: {
+ type: 'integer',
+ title: 'Positive integer',
+ description: 'A whole number greater than or equal to 1.',
+ },
+ overflow_strategy: {
+ type: 'string',
+ description:
+ "The strategy to employ when content exceeds the model's maximum input length.\n\n`drop_end`, which is the default setting, drops tokens from the end of the content exceeding the limit.\n\nIf `null`, an error will be raised if any content exceeds the model's maximum input length.",
+ enum: ['drop_end'],
+ },
+ task: {
+ type: 'string',
+ description:
+ 'The task the embeddings will be used for.\n\n`retrieval/query` is meant for queries and statements, and `retrieval/document` is meant for anything to be retrieved using query embeddings.\n\nIf `null`, which is the default setting, embeddings will not be optimized for any particular task.',
+ enum: ['retrieval/query', 'retrieval/document'],
+ },
+ jq_filter: {
+ type: 'string',
+ title: 'jq Filter',
+ description:
+ 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
+ },
+ },
+ required: ['model', 'texts'],
+ },
+ annotations: {},
+};
+
+export const handler = async (client: Isaacus, args: Record | undefined) => {
+ const { jq_filter, ...body } = args as any;
+ return asTextContentResult(await maybeFilter(jq_filter, await client.embeddings.create(body)));
+};
+
+export default { metadata, tool, handler };
diff --git a/packages/mcp-server/src/tools/extractions/qa/create-extractions-qa.ts b/packages/mcp-server/src/tools/extractions/qa/create-extractions-qa.ts
index 581a8e0..3bb9f63 100644
--- a/packages/mcp-server/src/tools/extractions/qa/create-extractions-qa.ts
+++ b/packages/mcp-server/src/tools/extractions/qa/create-extractions-qa.ts
@@ -18,14 +18,14 @@ export const metadata: Metadata = {
export const tool: Tool = {
name: 'create_extractions_qa',
description:
- "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nExtract answers to questions from legal documents with an Isaacus legal AI answer extractor.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/answer_extraction',\n $defs: {\n answer_extraction: {\n type: 'object',\n title: 'Answer extraction response',\n description: 'The results of extracting answers from texts.',\n properties: {\n extractions: {\n type: 'array',\n description: 'The results of extracting answers from the texts, ordered from highest to lowest answer confidence score (or else lowest to highest inextractability score if there are no answers for a text).',\n items: {\n type: 'object',\n title: 'Answer extraction',\n description: 'The result of extracting answers from a text.',\n properties: {\n answers: {\n type: 'array',\n description: 'Answers extracted from the text, ordered from highest to lowest score.',\n items: {\n type: 'object',\n title: 'Answer',\n description: 'An answer extracted from a text.',\n properties: {\n end: {\n type: 'integer',\n description: 'The index of the character immediately after the last character of the answer in the text, starting from `0` (such that, in Python, the answer is equivalent to `text[start:end]`).'\n },\n score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the strength of the answer.'\n },\n start: {\n type: 'integer',\n description: 'The index of the first character of the answer in the text, starting from `0` (and, therefore, ending at the number of characters in the text minus `1`).'\n },\n text: {\n type: 'string',\n description: 'The text of the answer.'\n }\n },\n required: [ 'end',\n 'score',\n 'start',\n 'text'\n ]\n }\n },\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts that this result represents, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n inextractability_score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the likelihood that an answer can not be extracted from the text.\\n\\nWhere this score is greater than the highest score of all possible answers, the text should be regarded as not having an extractable answer to the query. If that is the case and `ignore_inextractability` is `false`, no answers will be returned.'\n }\n },\n required: [ 'answers',\n 'index',\n 'inextractability_score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Answer extraction usage',\n description: 'Statistics about the usage of resources in the process of extracting answers from the texts.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'extractions',\n 'usage'\n ]\n }\n }\n}\n```",
+ "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nExtract answers to questions from legal documents with an Isaacus legal AI answer extractor.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/answer_extraction_response',\n $defs: {\n answer_extraction_response: {\n type: 'object',\n title: 'Answer extraction response',\n description: 'The results of extracting answers from texts.',\n properties: {\n extractions: {\n type: 'array',\n description: 'The results of extracting answers from the texts, ordered from highest to lowest answer confidence score (or else lowest to highest inextractability score if there are no answers for a text).',\n items: {\n type: 'object',\n title: 'Answer extraction',\n description: 'The result of extracting answers from a text.',\n properties: {\n answers: {\n type: 'array',\n description: 'Answers extracted from the text, ordered from highest to lowest score.',\n items: {\n type: 'object',\n title: 'Answer',\n description: 'An answer extracted from a text.',\n properties: {\n end: {\n type: 'integer',\n description: 'The index of the character immediately after the last character of the answer in the text, starting from `0` (such that, in Python, the answer is equivalent to `text[start:end]`).'\n },\n score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the strength of the answer.'\n },\n start: {\n type: 'integer',\n description: 'The index of the first character of the answer in the text, starting from `0` (and, therefore, ending at the number of characters in the text minus `1`).'\n },\n text: {\n type: 'string',\n description: 'The text of the answer.'\n }\n },\n required: [ 'end',\n 'score',\n 'start',\n 'text'\n ]\n }\n },\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts that this result represents, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n inextractability_score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the likelihood that an answer can not be extracted from the text.\\n\\nWhere this score is greater than the highest score of all possible answers, the text should be regarded as not having an extractable answer to the query. If that is the case and `ignore_inextractability` is `false`, no answers will be returned.'\n }\n },\n required: [ 'answers',\n 'index',\n 'inextractability_score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Answer extraction usage',\n description: 'Statistics about the usage of resources in the process of extracting answers from the texts.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'extractions',\n 'usage'\n ]\n }\n }\n}\n```",
inputSchema: {
type: 'object',
properties: {
model: {
type: 'string',
description:
- 'The ID of the [model](https://docs.isaacus.com/models#extractive-qa) to use for extractive question answering.',
+ 'The ID of the [model](https://docs.isaacus.com/models#extractive-question-answering) to use for extractive question answering.',
enum: ['kanon-answer-extractor', 'kanon-answer-extractor-mini'],
},
query: {
@@ -87,8 +87,8 @@ export const tool: Tool = {
};
export const handler = async (client: Isaacus, args: Record | undefined) => {
- const body = args as any;
- return asTextContentResult(await maybeFilter(args, await client.extractions.qa.create(body)));
+ const { jq_filter, ...body } = args as any;
+ return asTextContentResult(await maybeFilter(jq_filter, await client.extractions.qa.create(body)));
};
export default { metadata, tool, handler };
diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts
index 4b2f082..d9a927e 100644
--- a/packages/mcp-server/src/tools/index.ts
+++ b/packages/mcp-server/src/tools/index.ts
@@ -4,6 +4,7 @@ import { Metadata, Endpoint, HandlerFunction } from './types';
export { Metadata, Endpoint, HandlerFunction };
+import create_embeddings from './embeddings/create-embeddings';
import create_classifications_universal from './classifications/universal/create-classifications-universal';
import create_rerankings from './rerankings/create-rerankings';
import create_extractions_qa from './extractions/qa/create-extractions-qa';
@@ -14,6 +15,7 @@ function addEndpoint(endpoint: Endpoint) {
endpoints.push(endpoint);
}
+addEndpoint(create_embeddings);
addEndpoint(create_classifications_universal);
addEndpoint(create_rerankings);
addEndpoint(create_extractions_qa);
diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts
index 13b5782..3a0ee04 100644
--- a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts
+++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts
@@ -18,7 +18,7 @@ export const metadata: Metadata = {
export const tool: Tool = {
name: 'create_rerankings',
description:
- "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRerank legal documents by their relevance to a query with an Isaacus legal AI reranker.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/reranking',\n $defs: {\n reranking: {\n type: 'object',\n title: 'Reranking response',\n description: 'The reranking of texts, by relevance to a query, out of an input array of texts.',\n properties: {\n results: {\n type: 'array',\n description: 'The rerankings of the texts, by relevance to the query, in order from highest to lowest relevance score.',\n items: {\n type: 'object',\n title: 'Reranking result',\n properties: {\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the relevance of the text to the query.'\n }\n },\n required: [ 'index',\n 'score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Reranking usage',\n description: 'Statistics about the usage of resources in the process of reranking the texts.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'results',\n 'usage'\n ]\n }\n }\n}\n```",
+ "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRerank legal documents by their relevance to a query with an Isaacus legal AI reranker.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/reranking_response',\n $defs: {\n reranking_response: {\n type: 'object',\n title: 'Reranking response',\n description: 'The reranking of texts, by relevance to a query, out of an input array of texts.',\n properties: {\n results: {\n type: 'array',\n description: 'The rerankings of the texts, by relevance to the query, in order from highest to lowest relevance score.',\n items: {\n type: 'object',\n title: 'Reranking result',\n properties: {\n index: {\n type: 'integer',\n description: 'The index of the text in the input array of texts, starting from `0` (and, therefore, ending at the number of texts minus `1`).'\n },\n score: {\n type: 'number',\n description: 'A score between `0` and `1`, inclusive, representing the relevance of the text to the query.'\n }\n },\n required: [ 'index',\n 'score'\n ]\n }\n },\n usage: {\n type: 'object',\n title: 'Reranking usage',\n description: 'Statistics about the usage of resources in the process of reranking the texts.',\n properties: {\n input_tokens: {\n type: 'integer',\n description: 'The number of tokens inputted to the model.'\n }\n },\n required: [ 'input_tokens'\n ]\n }\n },\n required: [ 'results',\n 'usage'\n ]\n }\n }\n}\n```",
inputSchema: {
type: 'object',
properties: {
@@ -92,8 +92,8 @@ export const tool: Tool = {
};
export const handler = async (client: Isaacus, args: Record | undefined) => {
- const body = args as any;
- return asTextContentResult(await maybeFilter(args, await client.rerankings.create(body)));
+ const { jq_filter, ...body } = args as any;
+ return asTextContentResult(await maybeFilter(jq_filter, await client.rerankings.create(body)));
};
export default { metadata, tool, handler };
diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts
index f7661d6..a8a5b81 100644
--- a/packages/mcp-server/tests/options.test.ts
+++ b/packages/mcp-server/tests/options.test.ts
@@ -1,5 +1,6 @@
-import { parseOptions } from '../src/options';
+import { parseCLIOptions, parseQueryOptions } from '../src/options';
import { Filter } from '../src/tools';
+import { parseEmbeddedJSON } from '../src/compat';
// Mock process.argv
const mockArgv = (args: string[]) => {
@@ -10,7 +11,7 @@ const mockArgv = (args: string[]) => {
};
};
-describe('parseOptions', () => {
+describe('parseCLIOptions', () => {
it('should parse basic filter options', () => {
const cleanup = mockArgv([
'--tool=test-tool',
@@ -19,7 +20,7 @@ describe('parseOptions', () => {
'--tag=test-tag',
]);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.filters).toEqual([
{ type: 'tag', op: 'include', value: 'test-tag' },
@@ -28,15 +29,7 @@ describe('parseOptions', () => {
{ type: 'operation', op: 'include', value: 'read' },
] as Filter[]);
- // Default client capabilities
- expect(result.capabilities).toEqual({
- topLevelUnions: true,
- validJson: true,
- refs: true,
- unions: true,
- formats: true,
- toolNameLength: undefined,
- });
+ expect(result.capabilities).toEqual({});
expect(result.list).toBe(false);
@@ -51,7 +44,7 @@ describe('parseOptions', () => {
'--no-tag=exclude-tag',
]);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.filters).toEqual([
{ type: 'tag', op: 'exclude', value: 'exclude-tag' },
@@ -60,14 +53,7 @@ describe('parseOptions', () => {
{ type: 'operation', op: 'exclude', value: 'write' },
] as Filter[]);
- expect(result.capabilities).toEqual({
- topLevelUnions: true,
- validJson: true,
- refs: true,
- unions: true,
- formats: true,
- toolNameLength: undefined,
- });
+ expect(result.capabilities).toEqual({});
cleanup();
});
@@ -75,7 +61,7 @@ describe('parseOptions', () => {
it('should parse client presets', () => {
const cleanup = mockArgv(['--client=openai-agents']);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.client).toEqual('openai-agents');
@@ -91,14 +77,13 @@ describe('parseOptions', () => {
'--capability=tool-name-length=40',
]);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.capabilities).toEqual({
topLevelUnions: true,
validJson: true,
refs: true,
unions: true,
- formats: true,
toolNameLength: 40,
});
@@ -108,7 +93,7 @@ describe('parseOptions', () => {
it('should handle list option', () => {
const cleanup = mockArgv(['--list']);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.list).toBe(true);
@@ -118,7 +103,7 @@ describe('parseOptions', () => {
it('should handle multiple filters of the same type', () => {
const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.filters).toEqual([
{ type: 'resource', op: 'include', value: 'res1' },
@@ -137,7 +122,7 @@ describe('parseOptions', () => {
'--capability=top-level-unions,valid-json,unions',
]);
- const result = parseOptions();
+ const result = parseCLIOptions();
expect(result.filters).toEqual([
{ type: 'resource', op: 'include', value: 'res1' },
@@ -149,10 +134,7 @@ describe('parseOptions', () => {
expect(result.capabilities).toEqual({
topLevelUnions: true,
validJson: true,
- refs: true,
unions: true,
- formats: true,
- toolNameLength: undefined,
});
cleanup();
@@ -165,7 +147,7 @@ describe('parseOptions', () => {
const originalError = console.error;
console.error = jest.fn();
- expect(() => parseOptions()).toThrow();
+ expect(() => parseCLIOptions()).toThrow();
console.error = originalError;
cleanup();
@@ -178,9 +160,359 @@ describe('parseOptions', () => {
const originalError = console.error;
console.error = jest.fn();
- expect(() => parseOptions()).toThrow();
+ expect(() => parseCLIOptions()).toThrow();
console.error = originalError;
cleanup();
});
});
+
+describe('parseQueryOptions', () => {
+ const defaultOptions = {
+ client: undefined,
+ includeDynamicTools: undefined,
+ includeAllTools: undefined,
+ filters: [],
+ capabilities: {
+ topLevelUnions: true,
+ validJson: true,
+ refs: true,
+ unions: true,
+ formats: true,
+ toolNameLength: undefined,
+ },
+ };
+
+ it('should parse basic filter options from query string', () => {
+ const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.filters).toEqual([
+ { type: 'resource', op: 'include', value: 'test-resource' },
+ { type: 'operation', op: 'include', value: 'read' },
+ { type: 'tag', op: 'include', value: 'test-tag' },
+ { type: 'tool', op: 'include', value: 'test-tool' },
+ ]);
+
+ expect(result.capabilities).toEqual({
+ topLevelUnions: true,
+ validJson: true,
+ refs: true,
+ unions: true,
+ formats: true,
+ toolNameLength: undefined,
+ });
+ });
+
+ it('should parse exclusion filters from query string', () => {
+ const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.filters).toEqual([
+ { type: 'resource', op: 'exclude', value: 'exclude-resource' },
+ { type: 'operation', op: 'exclude', value: 'write' },
+ { type: 'tag', op: 'exclude', value: 'exclude-tag' },
+ { type: 'tool', op: 'exclude', value: 'exclude-tool' },
+ ]);
+ });
+
+ it('should parse client option from query string', () => {
+ const query = 'client=openai-agents';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.client).toBe('openai-agents');
+ });
+
+ it('should parse client capabilities from query string', () => {
+ const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.capabilities).toEqual({
+ topLevelUnions: true,
+ validJson: true,
+ refs: true,
+ unions: true,
+ formats: true,
+ toolNameLength: 40,
+ });
+ });
+
+ it('should parse no-capability options from query string', () => {
+ const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.capabilities).toEqual({
+ topLevelUnions: false,
+ validJson: true,
+ refs: false,
+ unions: true,
+ formats: false,
+ toolNameLength: undefined,
+ });
+ });
+
+ it('should parse tools options from query string', () => {
+ const query = 'tools=dynamic&tools=all';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.includeDynamicTools).toBe(true);
+ expect(result.includeAllTools).toBe(true);
+ });
+
+ it('should parse no-tools options from query string', () => {
+ const query = 'tools=dynamic&tools=all&no_tools=dynamic';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.includeDynamicTools).toBe(false);
+ expect(result.includeAllTools).toBe(true);
+ });
+
+ it('should handle array values in query string', () => {
+ const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.filters).toEqual([
+ { type: 'resource', op: 'include', value: 'res1' },
+ { type: 'resource', op: 'include', value: 'res2' },
+ { type: 'tool', op: 'include', value: 'tool1' },
+ { type: 'tool', op: 'include', value: 'tool2' },
+ ]);
+ });
+
+ it('should merge with default options', () => {
+ const defaultWithFilters = {
+ ...defaultOptions,
+ filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }],
+ client: 'cursor' as const,
+ includeDynamicTools: true,
+ };
+
+ const query = 'tool=new-tool&resource=new-resource';
+ const result = parseQueryOptions(defaultWithFilters, query);
+
+ expect(result.filters).toEqual([
+ { type: 'tag', op: 'include', value: 'existing-tag' },
+ { type: 'resource', op: 'include', value: 'new-resource' },
+ { type: 'tool', op: 'include', value: 'new-tool' },
+ ]);
+
+ expect(result.client).toBe('cursor');
+ expect(result.includeDynamicTools).toBe(true);
+ });
+
+ it('should override client from default options', () => {
+ const defaultWithClient = {
+ ...defaultOptions,
+ client: 'cursor' as const,
+ };
+
+ const query = 'client=openai-agents';
+ const result = parseQueryOptions(defaultWithClient, query);
+
+ expect(result.client).toBe('openai-agents');
+ });
+
+ it('should merge capabilities with default options', () => {
+ const defaultWithCapabilities = {
+ ...defaultOptions,
+ capabilities: {
+ topLevelUnions: false,
+ validJson: false,
+ refs: true,
+ unions: true,
+ formats: true,
+ toolNameLength: 30,
+ },
+ };
+
+ const query = 'capability=top-level-unions&no_capability=refs';
+ const result = parseQueryOptions(defaultWithCapabilities, query);
+
+ expect(result.capabilities).toEqual({
+ topLevelUnions: true,
+ validJson: false,
+ refs: false,
+ unions: true,
+ formats: true,
+ toolNameLength: 30,
+ });
+ });
+
+ it('should handle empty query string', () => {
+ const query = '';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result).toEqual(defaultOptions);
+ });
+
+ it('should handle invalid query string gracefully', () => {
+ const query = 'invalid=value&operation=invalid-operation';
+
+ // Should throw due to Zod validation for invalid operation
+ expect(() => parseQueryOptions(defaultOptions, query)).toThrow();
+ });
+
+ it('should preserve default undefined values when not specified', () => {
+ const defaultWithUndefined = {
+ ...defaultOptions,
+ client: undefined,
+ includeDynamicTools: undefined,
+ includeAllTools: undefined,
+ };
+
+ const query = 'tool=test-tool';
+ const result = parseQueryOptions(defaultWithUndefined, query);
+
+ expect(result.client).toBeUndefined();
+ expect(result.includeDynamicTools).toBeFalsy();
+ expect(result.includeAllTools).toBeFalsy();
+ });
+
+ it('should handle complex query with mixed include and exclude filters', () => {
+ const query =
+ 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag';
+ const result = parseQueryOptions(defaultOptions, query);
+
+ expect(result.filters).toEqual([
+ { type: 'resource', op: 'include', value: 'include-res' },
+ { type: 'operation', op: 'include', value: 'read' },
+ { type: 'tag', op: 'include', value: 'include-tag' },
+ { type: 'tool', op: 'include', value: 'include-tool' },
+ { type: 'resource', op: 'exclude', value: 'exclude-res' },
+ { type: 'tag', op: 'exclude', value: 'exclude-tag' },
+ { type: 'tool', op: 'exclude', value: 'exclude-tool' },
+ ]);
+ });
+});
+
+describe('parseEmbeddedJSON', () => {
+ it('should not change non-string values', () => {
+ const args = {
+ numberProp: 42,
+ booleanProp: true,
+ objectProp: { nested: 'value' },
+ arrayProp: [1, 2, 3],
+ nullProp: null,
+ undefinedProp: undefined,
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).toBe(args); // Should return original object since no changes made
+ expect(result['numberProp']).toBe(42);
+ expect(result['booleanProp']).toBe(true);
+ expect(result['objectProp']).toEqual({ nested: 'value' });
+ expect(result['arrayProp']).toEqual([1, 2, 3]);
+ expect(result['nullProp']).toBe(null);
+ expect(result['undefinedProp']).toBe(undefined);
+ });
+
+ it('should parse valid JSON objects in string properties', () => {
+ const args = {
+ jsonObjectString: '{"key": "value", "number": 123}',
+ regularString: 'not json',
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).not.toBe(args); // Should return new object since changes were made
+ expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 });
+ expect(result['regularString']).toBe('not json');
+ });
+
+ it('should leave invalid JSON in string properties unchanged', () => {
+ const args = {
+ invalidJson1: '{"key": value}', // Missing quotes around value
+ invalidJson2: '{key: "value"}', // Missing quotes around key
+ invalidJson3: '{"key": "value",}', // Trailing comma
+ invalidJson4: 'just a regular string',
+ emptyString: '',
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).toBe(args); // Should return original object since no changes made
+ expect(result['invalidJson1']).toBe('{"key": value}');
+ expect(result['invalidJson2']).toBe('{key: "value"}');
+ expect(result['invalidJson3']).toBe('{"key": "value",}');
+ expect(result['invalidJson4']).toBe('just a regular string');
+ expect(result['emptyString']).toBe('');
+ });
+
+ it('should not parse JSON primitives in string properties', () => {
+ const args = {
+ numberString: '123',
+ floatString: '45.67',
+ negativeNumberString: '-89',
+ booleanTrueString: 'true',
+ booleanFalseString: 'false',
+ nullString: 'null',
+ jsonArrayString: '[1, 2, 3, "test"]',
+ regularString: 'not json',
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).toBe(args); // Should return original object since no changes made
+ expect(result['numberString']).toBe('123');
+ expect(result['floatString']).toBe('45.67');
+ expect(result['negativeNumberString']).toBe('-89');
+ expect(result['booleanTrueString']).toBe('true');
+ expect(result['booleanFalseString']).toBe('false');
+ expect(result['nullString']).toBe('null');
+ expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]');
+ expect(result['regularString']).toBe('not json');
+ });
+
+ it('should handle mixed valid objects and other JSON types', () => {
+ const args = {
+ validObject: '{"success": true}',
+ invalidObject: '{"missing": quote}',
+ validNumber: '42',
+ validArray: '[1, 2, 3]',
+ keepAsString: 'hello world',
+ nonString: 123,
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).not.toBe(args); // Should return new object since some changes were made
+ expect(result['validObject']).toEqual({ success: true });
+ expect(result['invalidObject']).toBe('{"missing": quote}');
+ expect(result['validNumber']).toBe('42'); // Not parsed, remains string
+ expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string
+ expect(result['keepAsString']).toBe('hello world');
+ expect(result['nonString']).toBe(123);
+ });
+
+ it('should return original object when no strings are present', () => {
+ const args = {
+ number: 42,
+ boolean: true,
+ object: { key: 'value' },
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).toBe(args); // Should return original object since no changes made
+ });
+
+ it('should return original object when all strings are invalid JSON', () => {
+ const args = {
+ string1: 'hello',
+ string2: 'world',
+ string3: 'not json at all',
+ };
+ const schema = {};
+
+ const result = parseEmbeddedJSON(args, schema);
+
+ expect(result).toBe(args); // Should return original object since no changes made
+ });
+});
diff --git a/packages/mcp-server/tsconfig.build.json b/packages/mcp-server/tsconfig.build.json
index 9cd534a..c7135f0 100644
--- a/packages/mcp-server/tsconfig.build.json
+++ b/packages/mcp-server/tsconfig.build.json
@@ -5,8 +5,8 @@
"compilerOptions": {
"rootDir": "./dist/src",
"paths": {
- "isaacus-mcp/*": ["dist/src/*"],
- "isaacus-mcp": ["dist/src/index.ts"]
+ "isaacus-mcp/*": ["./dist/src/*"],
+ "isaacus-mcp": ["./dist/src/index.ts"]
},
"noEmit": false,
"declaration": true,
diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json
index 1cf9ca3..61e9994 100644
--- a/packages/mcp-server/tsconfig.json
+++ b/packages/mcp-server/tsconfig.json
@@ -7,10 +7,9 @@
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
- "baseUrl": "./",
"paths": {
- "isaacus-mcp/*": ["src/*"],
- "isaacus-mcp": ["src/index.ts"]
+ "isaacus-mcp/*": ["./src/*"],
+ "isaacus-mcp": ["./src/index.ts"]
},
"noEmit": true,
diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock
index 9970ec3..966d057 100644
--- a/packages/mcp-server/yarn.lock
+++ b/packages/mcp-server/yarn.lock
@@ -584,15 +584,17 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
-"@modelcontextprotocol/sdk@^1.6.1":
- version "1.11.1"
- resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz#c7f4a1432872ef10130f5d9b0072060c17a3946b"
- integrity sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==
+"@modelcontextprotocol/sdk@^1.11.5":
+ version "1.17.3"
+ resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae"
+ integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==
dependencies:
+ ajv "^6.12.6"
content-type "^1.0.5"
cors "^2.8.5"
- cross-spawn "^7.0.3"
+ 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"
@@ -708,6 +710,47 @@
dependencies:
"@babel/types" "^7.20.7"
+"@types/body-parser@*":
+ version "1.19.6"
+ resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474"
+ integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==
+ dependencies:
+ "@types/connect" "*"
+ "@types/node" "*"
+
+"@types/connect@*":
+ version "3.4.38"
+ resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
+ integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
+ dependencies:
+ "@types/node" "*"
+
+"@types/cors@^2.8.19":
+ version "2.8.19"
+ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342"
+ integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/express-serve-static-core@^5.0.0":
+ version "5.0.7"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6"
+ integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+ "@types/send" "*"
+
+"@types/express@^5.0.3":
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956"
+ integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^5.0.0"
+ "@types/serve-static" "*"
+
"@types/graceful-fs@^4.1.3":
version "4.1.9"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4"
@@ -715,6 +758,11 @@
dependencies:
"@types/node" "*"
+"@types/http-errors@*":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472"
+ integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7"
@@ -742,6 +790,11 @@
expect "^29.0.0"
pretty-format "^29.0.0"
+"@types/mime@^1":
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
+ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
+
"@types/node@*":
version "22.15.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055"
@@ -749,6 +802,33 @@
dependencies:
undici-types "~6.21.0"
+"@types/qs@*", "@types/qs@^6.14.0":
+ version "6.14.0"
+ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
+ integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==
+
+"@types/range-parser@*":
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
+ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
+
+"@types/send@*":
+ version "0.17.5"
+ resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74"
+ integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==
+ dependencies:
+ "@types/mime" "^1"
+ "@types/node" "*"
+
+"@types/serve-static@*":
+ version "1.15.8"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877"
+ integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==
+ dependencies:
+ "@types/http-errors" "*"
+ "@types/node" "*"
+ "@types/send" "*"
+
"@types/stack-utils@^2.0.0":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
@@ -852,6 +932,11 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+"@valtown/deno-http-worker@^0.0.21":
+ version "0.0.21"
+ resolved "https://registry.yarnpkg.com/@valtown/deno-http-worker/-/deno-http-worker-0.0.21.tgz#9ce3b5c1d0db211fe7ea8297881fe551838474ad"
+ integrity sha512-16kFuUykann75lNytnXXIQlmpzreZjzdyT27ebT3yNGCS3kKaS1iZYWHc3Si9An54Cphwr4qEcviChQkEeJBlA==
+
accepts@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
@@ -885,7 +970,7 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
-ajv@^6.12.4:
+ajv@^6.12.4, ajv@^6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -1246,7 +1331,7 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
-cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -1514,6 +1599,11 @@ etag@^1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+eventsource-parser@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.3.tgz#e9af1d40b77e6268cdcbc767321e8b9f066adea8"
+ integrity sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==
+
eventsource-parser@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd"
@@ -1562,7 +1652,7 @@ express-rate-limit@^7.5.0:
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f"
integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==
-express@^5.0.1:
+express@^5.0.1, express@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9"
integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==
@@ -2404,6 +2494,10 @@ jest@^29.4.0:
import-local "^3.0.2"
jest-cli "^29.7.0"
+"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz":
+ version "0.8.6"
+ resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f"
+
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3305,9 +3399,9 @@ ts-node@^10.5.0:
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
-"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.7/tsc-multi.tgz":
- version "1.1.7"
- resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.7/tsc-multi.tgz#52f40adf8b808bd0b633346d11cc4a8aeea465cd"
+"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz":
+ version "1.1.9"
+ resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd"
dependencies:
debug "^4.3.7"
fast-glob "^3.3.2"
@@ -3508,7 +3602,17 @@ zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5:
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3"
integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==
-zod@^3.23.8, zod@^3.24.4:
+zod-validation-error@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1"
+ integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog==
+
+zod@^3.23.8:
version "3.24.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f"
integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==
+
+zod@^3.25.20:
+ version "3.25.76"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34"
+ integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
diff --git a/scripts/bootstrap b/scripts/bootstrap
index 0af58e2..a8b69ff 100755
--- a/scripts/bootstrap
+++ b/scripts/bootstrap
@@ -4,10 +4,18 @@ set -e
cd "$(dirname "$0")/.."
-if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then
+if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
- echo "==> Installing Homebrew dependencies…"
- brew bundle
+ echo -n "==> Install Homebrew dependencies? (y/N): "
+ read -r response
+ case "$response" in
+ [yY][eE][sS]|[yY])
+ brew bundle
+ ;;
+ *)
+ ;;
+ esac
+ echo
}
fi
@@ -15,4 +23,4 @@ echo "==> Installing Node dependencies…"
PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm")
-$PACKAGE_MANAGER install
+$PACKAGE_MANAGER install "$@"
diff --git a/scripts/fast-format b/scripts/fast-format
new file mode 100755
index 0000000..53721ac
--- /dev/null
+++ b/scripts/fast-format
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+echo "Script started with $# arguments"
+echo "Arguments: $*"
+echo "Script location: $(dirname "$0")"
+
+cd "$(dirname "$0")/.."
+echo "Changed to directory: $(pwd)"
+
+if [ $# -eq 0 ]; then
+ echo "Usage: $0 [additional-formatter-args...]"
+ echo "The file should contain one file path per line"
+ exit 1
+fi
+
+FILE_LIST="$1"
+
+echo "Looking for file: $FILE_LIST"
+
+if [ ! -f "$FILE_LIST" ]; then
+ echo "Error: File '$FILE_LIST' not found"
+ exit 1
+fi
+
+echo "==> Running eslint --fix"
+ESLINT_FILES="$(grep '\.ts$' "$FILE_LIST" || true)"
+if ! [ -z "$ESLINT_FILES" ]; then
+ echo "$ESLINT_FILES" | xargs ./node_modules/.bin/eslint --cache --fix
+fi
+
+echo "==> Running prettier --write"
+# format things eslint didn't
+PRETTIER_FILES="$(grep '\.\(js\|json\)$' "$FILE_LIST" || true)"
+if ! [ -z "$PRETTIER_FILES" ]; then
+ echo "$PRETTIER_FILES" | xargs ./node_modules/.bin/prettier \
+ --write --cache --cache-strategy metadata --no-error-on-unmatched-pattern \
+ '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs'
+fi
diff --git a/scripts/mock b/scripts/mock
index d2814ae..0b28f6e 100755
--- a/scripts/mock
+++ b/scripts/mock
@@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}"
# Run prism mock on the given spec
if [ "$1" == "--daemon" ]; then
- npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log &
+ npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log &
# Wait for server to come online
echo -n "Waiting for server"
@@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then
echo
else
- npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL"
+ npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL"
fi
diff --git a/scripts/test b/scripts/test
index 2049e31..7bce051 100755
--- a/scripts/test
+++ b/scripts/test
@@ -43,7 +43,7 @@ elif ! prism_is_running ; then
echo -e "To run the server, pass in the path or url of your OpenAPI"
echo -e "spec to the prism command:"
echo
- echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
+ echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}"
echo
exit 1
diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh
index cdec3e4..6b3c5d8 100755
--- a/scripts/utils/upload-artifact.sh
+++ b/scripts/utils/upload-artifact.sh
@@ -12,9 +12,11 @@ if [[ "$SIGNED_URL" == "null" ]]; then
exit 1
fi
-UPLOAD_RESPONSE=$(tar -cz dist | curl -v -X PUT \
+TARBALL=$(cd dist && npm pack --silent)
+
+UPLOAD_RESPONSE=$(curl -v -X PUT \
-H "Content-Type: application/gzip" \
- --data-binary @- "$SIGNED_URL" 2>&1)
+ --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1)
if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
diff --git a/src/client.ts b/src/client.ts
index 8104cfc..4ac2a40 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -16,7 +16,8 @@ import * as Errors from './core/error';
import * as Uploads from './core/uploads';
import * as API from './resources/index';
import { APIPromise } from './core/api-promise';
-import { Reranking, RerankingCreateParams, Rerankings } from './resources/rerankings';
+import { EmbeddingCreateParams, EmbeddingResponse, Embeddings } from './resources/embeddings';
+import { RerankingCreateParams, RerankingResponse, Rerankings } from './resources/rerankings';
import { Classifications } from './resources/classifications/classifications';
import { Extractions } from './resources/extractions/extractions';
import { type Fetch } from './internal/builtin-types';
@@ -370,7 +371,7 @@ export class Isaacus {
const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError);
const headersTime = Date.now();
- if (response instanceof Error) {
+ if (response instanceof globalThis.Error) {
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
@@ -677,7 +678,7 @@ export class Isaacus {
// Preserve legacy string encoding behavior for now
headers.values.has('content-type')) ||
// `Blob` is superset of `File`
- body instanceof Blob ||
+ ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) ||
// `FormData` -> `multipart/form-data`
body instanceof FormData ||
// `URLSearchParams` -> `application/x-www-form-urlencoded`
@@ -716,21 +717,31 @@ export class Isaacus {
static toFile = Uploads.toFile;
+ embeddings: API.Embeddings = new API.Embeddings(this);
classifications: API.Classifications = new API.Classifications(this);
rerankings: API.Rerankings = new API.Rerankings(this);
extractions: API.Extractions = new API.Extractions(this);
}
+
+Isaacus.Embeddings = Embeddings;
Isaacus.Classifications = Classifications;
Isaacus.Rerankings = Rerankings;
Isaacus.Extractions = Extractions;
+
export declare namespace Isaacus {
export type RequestOptions = Opts.RequestOptions;
+ export {
+ Embeddings as Embeddings,
+ type EmbeddingResponse as EmbeddingResponse,
+ type EmbeddingCreateParams as EmbeddingCreateParams,
+ };
+
export { Classifications as Classifications };
export {
Rerankings as Rerankings,
- type Reranking as Reranking,
+ type RerankingResponse as RerankingResponse,
type RerankingCreateParams as RerankingCreateParams,
};
diff --git a/src/internal/to-file.ts b/src/internal/to-file.ts
index 245e849..30eada3 100644
--- a/src/internal/to-file.ts
+++ b/src/internal/to-file.ts
@@ -73,7 +73,7 @@ export type ToFileInput =
/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
- * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s
+ * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts
* @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param {Object=} options additional properties
* @param {string=} options.type the MIME type of the content
diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts
index 9b13307..b609e25 100644
--- a/src/internal/utils/values.ts
+++ b/src/internal/utils/values.ts
@@ -76,21 +76,21 @@ export const coerceBoolean = (value: unknown): boolean => {
};
export const maybeCoerceInteger = (value: unknown): number | undefined => {
- if (value === undefined) {
+ if (value == null) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value: unknown): number | undefined => {
- if (value === undefined) {
+ if (value == null) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value: unknown): boolean | undefined => {
- if (value === undefined) {
+ if (value == null) {
return undefined;
}
return coerceBoolean(value);
diff --git a/src/resources/classifications/classifications.ts b/src/resources/classifications/classifications.ts
index acda901..8568098 100644
--- a/src/resources/classifications/classifications.ts
+++ b/src/resources/classifications/classifications.ts
@@ -2,7 +2,7 @@
import { APIResource } from '../../core/resource';
import * as UniversalAPI from './universal';
-import { Universal, UniversalClassification, UniversalCreateParams } from './universal';
+import { Universal, UniversalClassificationResponse, UniversalCreateParams } from './universal';
export class Classifications extends APIResource {
universal: UniversalAPI.Universal = new UniversalAPI.Universal(this._client);
@@ -13,7 +13,7 @@ Classifications.Universal = Universal;
export declare namespace Classifications {
export {
Universal as Universal,
- type UniversalClassification as UniversalClassification,
+ type UniversalClassificationResponse as UniversalClassificationResponse,
type UniversalCreateParams as UniversalCreateParams,
};
}
diff --git a/src/resources/classifications/index.ts b/src/resources/classifications/index.ts
index 3ae64d0..72b07f3 100644
--- a/src/resources/classifications/index.ts
+++ b/src/resources/classifications/index.ts
@@ -1,4 +1,4 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export { Classifications } from './classifications';
-export { Universal, type UniversalClassification, type UniversalCreateParams } from './universal';
+export { Universal, type UniversalClassificationResponse, type UniversalCreateParams } from './universal';
diff --git a/src/resources/classifications/universal.ts b/src/resources/classifications/universal.ts
index fd0cf29..b4343a3 100644
--- a/src/resources/classifications/universal.ts
+++ b/src/resources/classifications/universal.ts
@@ -11,7 +11,7 @@ export class Universal extends APIResource {
*
* @example
* ```ts
- * const universalClassification =
+ * const universalClassificationResponse =
* await client.classifications.universal.create({
* model: 'kanon-universal-classifier',
* query: 'This is a confidentiality clause.',
@@ -21,7 +21,7 @@ export class Universal extends APIResource {
* });
* ```
*/
- create(body: UniversalCreateParams, options?: RequestOptions): APIPromise {
+ create(body: UniversalCreateParams, options?: RequestOptions): APIPromise {
return this._client.post('/classifications/universal', { body, ...options });
}
}
@@ -30,20 +30,20 @@ export class Universal extends APIResource {
* Classifications of the relevance of legal documents to a query produced by an
* Isaacus universal legal AI classifier.
*/
-export interface UniversalClassification {
+export interface UniversalClassificationResponse {
/**
* The classifications of the texts, by relevance to the query, in order from
* highest to lowest relevance score.
*/
- classifications: Array;
+ classifications: Array;
/**
* Statistics about the usage of resources in the process of classifying the text.
*/
- usage: UniversalClassification.Usage;
+ usage: UniversalClassificationResponse.Usage;
}
-export namespace UniversalClassification {
+export namespace UniversalClassificationResponse {
export interface Classification {
/**
* The text as broken into chunks by
@@ -195,7 +195,7 @@ export namespace UniversalCreateParams {
export declare namespace Universal {
export {
- type UniversalClassification as UniversalClassification,
+ type UniversalClassificationResponse as UniversalClassificationResponse,
type UniversalCreateParams as UniversalCreateParams,
};
}
diff --git a/src/resources/embeddings.ts b/src/resources/embeddings.ts
new file mode 100644
index 0000000..9e2c8ca
--- /dev/null
+++ b/src/resources/embeddings.ts
@@ -0,0 +1,113 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { APIResource } from '../core/resource';
+import { APIPromise } from '../core/api-promise';
+import { RequestOptions } from '../internal/request-options';
+
+export class Embeddings extends APIResource {
+ /**
+ * Embed legal texts with an Isaacus legal AI embedder.
+ *
+ * @example
+ * ```ts
+ * const embeddingResponse = await client.embeddings.create({
+ * model: 'kanon-2-embedder',
+ * texts: [
+ * 'Are restraints of trade enforceable under English law?',
+ * 'What is a non-compete clause?',
+ * ],
+ * });
+ * ```
+ */
+ create(body: EmbeddingCreateParams, options?: RequestOptions): APIPromise {
+ return this._client.post('/embeddings', { body, ...options });
+ }
+}
+
+/**
+ * Embeddings of legal texts produced by an Isaacus legal AI embedder.
+ */
+export interface EmbeddingResponse {
+ /**
+ * The embeddings of the inputs.
+ */
+ embeddings: Array;
+
+ /**
+ * Statistics about the usage of resources in the process of embedding the inputs.
+ */
+ usage: EmbeddingResponse.Usage;
+}
+
+export namespace EmbeddingResponse {
+ export interface Embedding {
+ /**
+ * The embedding of the content represented as an array of floating point numbers.
+ */
+ embedding: Array;
+
+ /**
+ * The position of the content in the input array of contents, starting from `0`
+ * (and, therefore, ending at the number of contents minus `1`).
+ */
+ index: number;
+ }
+
+ /**
+ * Statistics about the usage of resources in the process of embedding the inputs.
+ */
+ export interface Usage {
+ /**
+ * The number of tokens inputted to the model.
+ */
+ input_tokens: number;
+ }
+}
+
+export interface EmbeddingCreateParams {
+ /**
+ * The ID of the [model](https://docs.isaacus.com/models#embedding) to use for
+ * embedding.
+ */
+ model: 'kanon-2-embedder';
+
+ /**
+ * The text or array of texts to embed.
+ *
+ * Each text must contain at least one non-whitespace character.
+ *
+ * No more than 128 texts can be embedded in a single request.
+ */
+ texts: Array | string;
+
+ /**
+ * A whole number greater than or equal to 1.
+ */
+ dimensions?: number | null;
+
+ /**
+ * The strategy to employ when content exceeds the model's maximum input length.
+ *
+ * `drop_end`, which is the default setting, drops tokens from the end of the
+ * content exceeding the limit.
+ *
+ * If `null`, an error will be raised if any content exceeds the model's maximum
+ * input length.
+ */
+ overflow_strategy?: 'drop_end' | null;
+
+ /**
+ * The task the embeddings will be used for.
+ *
+ * `retrieval/query` is meant for queries and statements, and `retrieval/document`
+ * is meant for anything to be retrieved using query embeddings.
+ *
+ * If `null`, which is the default setting, embeddings will not be optimized for
+ * any particular task.
+ */
+ task?: 'retrieval/query' | 'retrieval/document' | null;
+}
+
+export declare namespace Embeddings {
+ export { type EmbeddingResponse as EmbeddingResponse, type EmbeddingCreateParams as EmbeddingCreateParams };
+}
diff --git a/src/resources/extractions/extractions.ts b/src/resources/extractions/extractions.ts
index 12368cd..f65bb08 100644
--- a/src/resources/extractions/extractions.ts
+++ b/src/resources/extractions/extractions.ts
@@ -2,7 +2,7 @@
import { APIResource } from '../../core/resource';
import * as QaAPI from './qa';
-import { AnswerExtraction, Qa, QaCreateParams } from './qa';
+import { AnswerExtractionResponse, Qa, QaCreateParams } from './qa';
export class Extractions extends APIResource {
qa: QaAPI.Qa = new QaAPI.Qa(this._client);
@@ -11,5 +11,9 @@ export class Extractions extends APIResource {
Extractions.Qa = Qa;
export declare namespace Extractions {
- export { Qa as Qa, type AnswerExtraction as AnswerExtraction, type QaCreateParams as QaCreateParams };
+ export {
+ Qa as Qa,
+ type AnswerExtractionResponse as AnswerExtractionResponse,
+ type QaCreateParams as QaCreateParams,
+ };
}
diff --git a/src/resources/extractions/index.ts b/src/resources/extractions/index.ts
index 53bba39..1012743 100644
--- a/src/resources/extractions/index.ts
+++ b/src/resources/extractions/index.ts
@@ -1,4 +1,4 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export { Extractions } from './extractions';
-export { Qa, type AnswerExtraction, type QaCreateParams } from './qa';
+export { Qa, type AnswerExtractionResponse, type QaCreateParams } from './qa';
diff --git a/src/resources/extractions/qa.ts b/src/resources/extractions/qa.ts
index 7abaa3e..3d66c92 100644
--- a/src/resources/extractions/qa.ts
+++ b/src/resources/extractions/qa.ts
@@ -11,18 +11,17 @@ export class Qa extends APIResource {
*
* @example
* ```ts
- * const answerExtraction = await client.extractions.qa.create(
- * {
+ * const answerExtractionResponse =
+ * await client.extractions.qa.create({
* model: 'kanon-answer-extractor',
* query: 'What is the punishment for murder in Victoria?',
* texts: [
* 'The standard sentence for murder in the State of Victoria is 30 years if the person murdered was a police officer and 25 years in any other case.',
* ],
- * },
- * );
+ * });
* ```
*/
- create(body: QaCreateParams, options?: RequestOptions): APIPromise {
+ create(body: QaCreateParams, options?: RequestOptions): APIPromise {
return this._client.post('/extractions/qa', { body, ...options });
}
}
@@ -30,22 +29,22 @@ export class Qa extends APIResource {
/**
* The results of extracting answers from texts.
*/
-export interface AnswerExtraction {
+export interface AnswerExtractionResponse {
/**
* The results of extracting answers from the texts, ordered from highest to lowest
* answer confidence score (or else lowest to highest inextractability score if
* there are no answers for a text).
*/
- extractions: Array;
+ extractions: Array;
/**
* Statistics about the usage of resources in the process of extracting answers
* from the texts.
*/
- usage: AnswerExtraction.Usage;
+ usage: AnswerExtractionResponse.Usage;
}
-export namespace AnswerExtraction {
+export namespace AnswerExtractionResponse {
/**
* The result of extracting answers from a text.
*/
@@ -117,8 +116,9 @@ export namespace AnswerExtraction {
export interface QaCreateParams {
/**
- * The ID of the [model](https://docs.isaacus.com/models#extractive-qa) to use for
- * extractive question answering.
+ * The ID of the
+ * [model](https://docs.isaacus.com/models#extractive-question-answering) to use
+ * for extractive question answering.
*/
model: 'kanon-answer-extractor' | 'kanon-answer-extractor-mini';
@@ -188,5 +188,5 @@ export namespace QaCreateParams {
}
export declare namespace Qa {
- export { type AnswerExtraction as AnswerExtraction, type QaCreateParams as QaCreateParams };
+ export { type AnswerExtractionResponse as AnswerExtractionResponse, type QaCreateParams as QaCreateParams };
}
diff --git a/src/resources/index.ts b/src/resources/index.ts
index d4ad369..36888da 100644
--- a/src/resources/index.ts
+++ b/src/resources/index.ts
@@ -1,5 +1,6 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export { Classifications } from './classifications/classifications';
+export { Embeddings, type EmbeddingResponse, type EmbeddingCreateParams } from './embeddings';
export { Extractions } from './extractions/extractions';
-export { Rerankings, type Reranking, type RerankingCreateParams } from './rerankings';
+export { Rerankings, type RerankingResponse, type RerankingCreateParams } from './rerankings';
diff --git a/src/resources/rerankings.ts b/src/resources/rerankings.ts
index e94182d..aabb71b 100644
--- a/src/resources/rerankings.ts
+++ b/src/resources/rerankings.ts
@@ -11,7 +11,7 @@ export class Rerankings extends APIResource {
*
* @example
* ```ts
- * const reranking = await client.rerankings.create({
+ * const rerankingResponse = await client.rerankings.create({
* model: 'kanon-universal-classifier',
* query:
* 'What are the essential elements required to establish a negligence claim?',
@@ -25,7 +25,7 @@ export class Rerankings extends APIResource {
* });
* ```
*/
- create(body: RerankingCreateParams, options?: RequestOptions): APIPromise {
+ create(body: RerankingCreateParams, options?: RequestOptions): APIPromise {
return this._client.post('/rerankings', { body, ...options });
}
}
@@ -33,20 +33,20 @@ export class Rerankings extends APIResource {
/**
* The reranking of texts, by relevance to a query, out of an input array of texts.
*/
-export interface Reranking {
+export interface RerankingResponse {
/**
* The rerankings of the texts, by relevance to the query, in order from highest to
* lowest relevance score.
*/
- results: Array;
+ results: Array;
/**
* Statistics about the usage of resources in the process of reranking the texts.
*/
- usage: Reranking.Usage;
+ usage: RerankingResponse.Usage;
}
-export namespace Reranking {
+export namespace RerankingResponse {
export interface Result {
/**
* The index of the text in the input array of texts, starting from `0` (and,
@@ -158,5 +158,5 @@ export namespace RerankingCreateParams {
}
export declare namespace Rerankings {
- export { type Reranking as Reranking, type RerankingCreateParams as RerankingCreateParams };
+ export { type RerankingResponse as RerankingResponse, type RerankingCreateParams as RerankingCreateParams };
}
diff --git a/src/version.ts b/src/version.ts
index 9085e9d..945825f 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const VERSION = '0.11.0'; // x-release-please-version
+export const VERSION = '0.11.1'; // x-release-please-version
diff --git a/tests/api-resources/classifications/universal.test.ts b/tests/api-resources/classifications/universal.test.ts
index 7ab9fc5..ec25404 100644
--- a/tests/api-resources/classifications/universal.test.ts
+++ b/tests/api-resources/classifications/universal.test.ts
@@ -8,7 +8,7 @@ const client = new Isaacus({
});
describe('resource universal', () => {
- // skipped: tests are disabled for the time being
+ // Prism tests are disabled
test.skip('create: only required params', async () => {
const responsePromise = client.classifications.universal.create({
model: 'kanon-universal-classifier',
@@ -24,13 +24,13 @@ describe('resource universal', () => {
expect(dataAndResponse.response).toBe(rawResponse);
});
- // skipped: tests are disabled for the time being
+ // Prism tests are disabled
test.skip('create: required and optional params', async () => {
const response = await client.classifications.universal.create({
model: 'kanon-universal-classifier',
query: 'This is a confidentiality clause.',
texts: ['I agree not to tell anyone about the document.'],
- chunking_options: { overlap_ratio: 0.1, overlap_tokens: 0, size: 512 },
+ chunking_options: { overlap_ratio: 0.1, overlap_tokens: 10, size: 512 },
is_iql: true,
scoring_method: 'auto',
});
diff --git a/tests/api-resources/embeddings.test.ts b/tests/api-resources/embeddings.test.ts
new file mode 100644
index 0000000..97f2090
--- /dev/null
+++ b/tests/api-resources/embeddings.test.ts
@@ -0,0 +1,36 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import Isaacus from 'isaacus';
+
+const client = new Isaacus({
+ apiKey: 'My API Key',
+ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
+});
+
+describe('resource embeddings', () => {
+ // Prism tests are disabled
+ test.skip('create: only required params', async () => {
+ const responsePromise = client.embeddings.create({
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
+ });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ // Prism tests are disabled
+ test.skip('create: required and optional params', async () => {
+ const response = await client.embeddings.create({
+ model: 'kanon-2-embedder',
+ texts: ['Are restraints of trade enforceable under English law?', 'What is a non-compete clause?'],
+ dimensions: 1,
+ overflow_strategy: 'drop_end',
+ task: 'retrieval/query',
+ });
+ });
+});
diff --git a/tests/api-resources/extractions/qa.test.ts b/tests/api-resources/extractions/qa.test.ts
index f8c6162..c807108 100644
--- a/tests/api-resources/extractions/qa.test.ts
+++ b/tests/api-resources/extractions/qa.test.ts
@@ -8,7 +8,7 @@ const client = new Isaacus({
});
describe('resource qa', () => {
- // skipped: tests are disabled for the time being
+ // Prism tests are disabled
test.skip('create: only required params', async () => {
const responsePromise = client.extractions.qa.create({
model: 'kanon-answer-extractor',
@@ -26,7 +26,7 @@ describe('resource qa', () => {
expect(dataAndResponse.response).toBe(rawResponse);
});
- // skipped: tests are disabled for the time being
+ // Prism tests are disabled
test.skip('create: required and optional params', async () => {
const response = await client.extractions.qa.create({
model: 'kanon-answer-extractor',
@@ -34,7 +34,7 @@ describe('resource qa', () => {
texts: [
'The standard sentence for murder in the State of Victoria is 30 years if the person murdered was a police officer and 25 years in any other case.',
],
- chunking_options: { overlap_ratio: 0.1, overlap_tokens: 0, size: 512 },
+ chunking_options: { overlap_ratio: 0.1, overlap_tokens: 10, size: 512 },
ignore_inextractability: false,
top_k: 1,
});
diff --git a/tests/api-resources/rerankings.test.ts b/tests/api-resources/rerankings.test.ts
index d87fd69..328d26d 100644
--- a/tests/api-resources/rerankings.test.ts
+++ b/tests/api-resources/rerankings.test.ts
@@ -8,7 +8,7 @@ const client = new Isaacus({
});
describe('resource rerankings', () => {
- // skipped: tests are disabled for the time being
+ // Prism tests are disabled
test.skip('create: only required params', async () => {
const responsePromise = client.rerankings.create({
model: 'kanon-universal-classifier',
@@ -30,7 +30,7 @@ describe('resource rerankings', () => {
expect(dataAndResponse.response).toBe(rawResponse);
});
- // skipped: tests are disabled for the time being
+ // Prism tests are disabled
test.skip('create: required and optional params', async () => {
const response = await client.rerankings.create({
model: 'kanon-universal-classifier',
@@ -42,7 +42,7 @@ describe('resource rerankings', () => {
'Negligence in tort law requires establishing a duty of care that the defendant owed to the plaintiff.',
'The concept of negligence is central to tort law, with courts assessing whether a breach of duty caused harm.',
],
- chunking_options: { overlap_ratio: 0.1, overlap_tokens: 0, size: 512 },
+ chunking_options: { overlap_ratio: 0.1, overlap_tokens: 10, size: 512 },
is_iql: false,
scoring_method: 'auto',
top_n: 1,
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 8859136..572688c 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -5,8 +5,8 @@
"compilerOptions": {
"rootDir": "./dist/src",
"paths": {
- "isaacus/*": ["dist/src/*"],
- "isaacus": ["dist/src/index.ts"]
+ "isaacus/*": ["./dist/src/*"],
+ "isaacus": ["./dist/src/index.ts"]
},
"noEmit": false,
"declaration": true,
diff --git a/tsconfig.json b/tsconfig.json
index 045dec1..d43f4e8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,10 +7,9 @@
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
- "baseUrl": "./",
"paths": {
- "isaacus/*": ["src/*"],
- "isaacus": ["src/index.ts"]
+ "isaacus/*": ["./src/*"],
+ "isaacus": ["./src/index.ts"]
},
"noEmit": true,
diff --git a/yarn.lock b/yarn.lock
index 58c08d5..8311caf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -938,11 +938,11 @@
undici-types "~5.26.4"
"@types/node@^20.17.6":
- version "20.17.6"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.6.tgz#6e4073230c180d3579e8c60141f99efdf5df0081"
- integrity sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==
+ version "20.19.11"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4"
+ integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==
dependencies:
- undici-types "~6.19.2"
+ undici-types "~6.21.0"
"@types/stack-utils@^2.0.0":
version "2.0.3"
@@ -3283,9 +3283,9 @@ ts-node@^10.5.0:
v8-compile-cache-lib "^3.0.0"
yn "3.1.1"
-"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz":
- version "1.1.8"
- resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz#f544b359b8f05e607771ffacc280e58201476b04"
+"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz":
+ version "1.1.9"
+ resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd"
dependencies:
debug "^4.3.7"
fast-glob "^3.3.2"
@@ -3353,10 +3353,10 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
-undici-types@~6.19.2:
- version "6.19.8"
- resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
- integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+undici-types@~6.21.0:
+ version "6.21.0"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
+ integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
unicode-emoji-modifier-base@^1.0.0:
version "1.0.0"