From 63d895384acdadbeee292f951d524df90508c9b2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 03:58:56 +0000 Subject: [PATCH 01/29] chore(docs): use top-level-await in example snippets --- README.md | 68 +++++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 6a787186..0d5e3766 100644 --- a/README.md +++ b/README.md @@ -26,17 +26,13 @@ const client = new Isaacus({ apiKey: process.env['ISAACUS_API_KEY'], // This is the default and can be omitted }); -async function main() { - 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.'], - }); - - console.log(universalClassification.classifications); -} +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.'], +}); -main(); +console.log(universalClassification.classifications); ``` ### Request & Response types @@ -51,17 +47,13 @@ const client = new Isaacus({ apiKey: process.env['ISAACUS_API_KEY'], // This is the default and can be omitted }); -async function main() { - 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 universalClassification: Isaacus.Classifications.UniversalClassification = - await client.classifications.universal.create(params); -} - -main(); +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 universalClassification: Isaacus.Classifications.UniversalClassification = + await client.classifications.universal.create(params); ``` Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. @@ -74,25 +66,21 @@ a subclass of `APIError` will be thrown: ```ts -async function main() { - 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.'], - }) - .catch(async (err) => { - if (err instanceof Isaacus.APIError) { - console.log(err.status); // 400 - console.log(err.name); // BadRequestError - console.log(err.headers); // {server: 'nginx', ...} - } else { - throw err; - } - }); -} - -main(); +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.'], + }) + .catch(async (err) => { + if (err instanceof Isaacus.APIError) { + console.log(err.status); // 400 + console.log(err.name); // BadRequestError + console.log(err.headers); // {server: 'nginx', ...} + } else { + throw err; + } + }); ``` Error codes are as follows: From 604d380c9062489e279a76750b8cbf3707f16ecd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 04:28:53 +0000 Subject: [PATCH 02/29] chore(internal): fix readablestream types in node 20 --- scripts/build | 2 -- src/internal/shim-types.d.ts | 28 ---------------------------- src/internal/shim-types.ts | 26 ++++++++++++++++++++++++++ src/internal/shims.ts | 4 ++-- 4 files changed, 28 insertions(+), 32 deletions(-) delete mode 100644 src/internal/shim-types.d.ts create mode 100644 src/internal/shim-types.ts diff --git a/scripts/build b/scripts/build index 0ba3ec99..2fb46206 100755 --- a/scripts/build +++ b/scripts/build @@ -37,8 +37,6 @@ npm exec tsc-multi # when building .mjs node scripts/utils/fix-index-exports.cjs cp tsconfig.dist-src.json dist/src/tsconfig.json -cp src/internal/shim-types.d.ts dist/internal/shim-types.d.ts -cp src/internal/shim-types.d.ts dist/internal/shim-types.d.mts node scripts/utils/postprocess-files.cjs diff --git a/src/internal/shim-types.d.ts b/src/internal/shim-types.d.ts deleted file mode 100644 index fe48144f..00000000 --- a/src/internal/shim-types.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * Shims for types that we can't always rely on being available globally. - * - * Note: these only exist at the type-level, there is no corresponding runtime - * version for any of these symbols. - */ - -/** - * In order to properly access the global `NodeJS` type, if it's available, we - * need to make use of declaration shadowing. Without this, any checks for the - * presence of `NodeJS.ReadableStream` will fail. - */ -declare namespace NodeJS { - interface ReadableStream {} -} - -type HasProperties = keyof T extends never ? false : true; - -// @ts-ignore -type _ReadableStream = - // @ts-ignore - HasProperties extends true ? NodeJS.ReadableStream : ReadableStream; - -// @ts-ignore -declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; -export { _ReadableStream as ReadableStream }; diff --git a/src/internal/shim-types.ts b/src/internal/shim-types.ts new file mode 100644 index 00000000..8ddf7b0a --- /dev/null +++ b/src/internal/shim-types.ts @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Shims for types that we can't always rely on being available globally. + * + * Note: these only exist at the type-level, there is no corresponding runtime + * version for any of these symbols. + */ + +type NeverToAny = T extends never ? any : T; + +/** @ts-ignore */ +type _DOMReadableStream = globalThis.ReadableStream; + +/** @ts-ignore */ +type _NodeReadableStream = import('stream/web').ReadableStream; + +type _ConditionalNodeReadableStream = + typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; + +type _ReadableStream = NeverToAny< + | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) + | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) +>; + +export type { _ReadableStream as ReadableStream }; diff --git a/src/internal/shims.ts b/src/internal/shims.ts index 4f2a60eb..f46b894c 100644 --- a/src/internal/shims.ts +++ b/src/internal/shims.ts @@ -7,8 +7,8 @@ * messages in cases where an environment isn't fully supported. */ -import { type Fetch } from './builtin-types'; -import { type ReadableStream } from './shim-types'; +import type { Fetch } from './builtin-types'; +import type { ReadableStream } from './shim-types'; export function getDefaultFetch(): Fetch { if (typeof fetch !== 'undefined') { From bec8beac1c9e9187aa3cbeb6ba49764763530277 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:46:10 +0000 Subject: [PATCH 03/29] feat(mcp): implement support for binary responses --- packages/mcp-server/src/dynamic-tools.ts | 14 +-- packages/mcp-server/src/server.ts | 10 +- .../create-classifications-universal.ts | 6 +- .../extractions/qa/create-extractions-qa.ts | 6 +- packages/mcp-server/src/tools/index.ts | 23 +--- .../src/tools/rerankings/create-rerankings.ts | 6 +- packages/mcp-server/src/tools/types.ts | 104 ++++++++++++++++++ .../mcp-server/tests/dynamic-tools.test.ts | 24 ++-- 8 files changed, 143 insertions(+), 50 deletions(-) create mode 100644 packages/mcp-server/src/tools/types.ts diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts index 552a9b70..63f31f6e 100644 --- a/packages/mcp-server/src/dynamic-tools.ts +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -1,5 +1,5 @@ import Isaacus from 'isaacus'; -import { Endpoint } from './tools'; +import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { z } from 'zod'; import { Cabidela } from '@cloudflare/cabidela'; @@ -41,7 +41,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { description: 'List or search for all endpoints in the Isaacus TypeScript API', inputSchema: zodToInputSchema(listEndpointsSchema), }, - handler: async (client: Isaacus, args: Record | undefined) => { + handler: async (client: Isaacus, args: Record | undefined): Promise => { const query = args && listEndpointsSchema.parse(args).search_query?.trim(); const filteredEndpoints = @@ -58,7 +58,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { }) : endpoints; - return { + return asTextContentResult({ tools: filteredEndpoints.map(({ tool, metadata }) => ({ name: tool.name, description: tool.description, @@ -66,7 +66,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { operation: metadata.operation, tags: metadata.tags, })), - }; + }); }, }; @@ -95,7 +95,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { if (!endpoint) { throw new Error(`Endpoint ${endpointName} not found`); } - return endpoint.tool; + return asTextContentResult(endpoint.tool); }, }; @@ -120,7 +120,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { 'Invoke an endpoint in the Isaacus TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', inputSchema: zodToInputSchema(invokeEndpointSchema), }, - handler: async (client: Isaacus, args: Record | undefined) => { + handler: async (client: Isaacus, args: Record | undefined): Promise => { if (!args) { throw new Error('No endpoint provided'); } @@ -145,7 +145,7 @@ export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); } - return endpoint.handler(client, endpointArgs); + return await endpoint.handler(client, endpointArgs); }, }; diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 2278c399..24db3535 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -99,15 +99,7 @@ export async function executeHandler( if (options.validJson && args) { args = parseEmbeddedJSON(args, tool.inputSchema); } - const result = await handler(client, args || {}); - return { - content: [ - { - type: 'text', - text: JSON.stringify(result, null, 2), - }, - ], - }; + return await handler(client, args || {}); } export const readEnv = (env: string): string | undefined => { 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 1ba4df85..b5ae7170 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 @@ -1,5 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { asTextContentResult } from 'isaacus-mcp/tools/types'; + import { Tool } from '@modelcontextprotocol/sdk/types.js'; import type { Metadata } from '../../'; import Isaacus from 'isaacus'; @@ -78,9 +80,9 @@ export const tool: Tool = { }, }; -export const handler = (client: Isaacus, args: Record | undefined) => { +export const handler = async (client: Isaacus, args: Record | undefined) => { const body = args as any; - return client.classifications.universal.create(body); + return asTextContentResult(await client.classifications.universal.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 cc2223ac..7e42c254 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 @@ -1,5 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { asTextContentResult } from 'isaacus-mcp/tools/types'; + import { Tool } from '@modelcontextprotocol/sdk/types.js'; import type { Metadata } from '../../'; import Isaacus from 'isaacus'; @@ -76,9 +78,9 @@ export const tool: Tool = { }, }; -export const handler = (client: Isaacus, args: Record | undefined) => { +export const handler = async (client: Isaacus, args: Record | undefined) => { const body = args as any; - return client.extractions.qa.create(body); + return asTextContentResult(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 afc8f994..e95314a8 100644 --- a/packages/mcp-server/src/tools/index.ts +++ b/packages/mcp-server/src/tools/index.ts @@ -1,30 +1,13 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import Isaacus from 'isaacus'; -import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { Metadata, Endpoint, HandlerFunction } from './types'; + +export { Metadata, Endpoint, HandlerFunction }; 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'; -export type HandlerFunction = (client: Isaacus, args: Record | undefined) => Promise; - -export type Metadata = { - resource: string; - operation: 'read' | 'write'; - tags: string[]; - - httpMethod?: string; - httpPath?: string; - operationId?: string; -}; - -export type Endpoint = { - metadata: Metadata; - tool: Tool; - handler: HandlerFunction; -}; - export const endpoints: Endpoint[] = []; function addEndpoint(endpoint: Endpoint) { diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts index d45e8cde..f6d80dc1 100644 --- a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts +++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts @@ -1,5 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { asTextContentResult } from 'isaacus-mcp/tools/types'; + import { Tool } from '@modelcontextprotocol/sdk/types.js'; import type { Metadata } from '../'; import Isaacus from 'isaacus'; @@ -81,9 +83,9 @@ export const tool: Tool = { }, }; -export const handler = (client: Isaacus, args: Record | undefined) => { +export const handler = async (client: Isaacus, args: Record | undefined) => { const body = args as any; - return client.rerankings.create(body); + return asTextContentResult(await client.rerankings.create(body)); }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts new file mode 100644 index 00000000..5b69487e --- /dev/null +++ b/packages/mcp-server/src/tools/types.ts @@ -0,0 +1,104 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Isaacus from 'isaacus'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +type TextContentBlock = { + type: 'text'; + text: string; +}; + +type ImageContentBlock = { + type: 'image'; + data: string; + mimeType: string; +}; + +type AudioContentBlock = { + type: 'audio'; + data: string; + mimeType: string; +}; + +type ResourceContentBlock = { + type: 'resource'; + resource: + | { + uri: string; + mimeType: string; + text: string; + } + | { + uri: string; + mimeType: string; + blob: string; + }; +}; + +export type ContentBlock = TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock; + +export type ToolCallResult = { + content: ContentBlock[]; + isError?: boolean; +}; + +export type HandlerFunction = ( + client: Isaacus, + args: Record | undefined, +) => Promise; + +export function asTextContentResult(result: Object): ToolCallResult { + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +} + +export async function asBinaryContentResult(response: Response): Promise { + const blob = await response.blob(); + const mimeType = blob.type; + const data = Buffer.from(await blob.arrayBuffer()).toString('base64'); + if (mimeType.startsWith('image/')) { + return { + content: [{ type: 'image', mimeType, data }], + }; + } else if (mimeType.startsWith('audio/')) { + return { + content: [{ type: 'audio', mimeType, data }], + }; + } else { + return { + content: [ + { + type: 'resource', + resource: { + // We must give a URI, even though this isn't actually an MCP resource. + uri: 'resource://tool-response', + mimeType, + blob: data, + }, + }, + ], + }; + } +} + +export type Metadata = { + resource: string; + operation: 'read' | 'write'; + tags: string[]; + + httpMethod?: string; + httpPath?: string; + operationId?: string; +}; + +export type Endpoint = { + metadata: Metadata; + tool: Tool; + handler: HandlerFunction; +}; diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts index e2da6bd8..08963af8 100644 --- a/packages/mcp-server/tests/dynamic-tools.test.ts +++ b/packages/mcp-server/tests/dynamic-tools.test.ts @@ -21,7 +21,8 @@ describe('dynamicTools', () => { describe('list_api_endpoints', () => { it('should return all endpoints when no search query is provided', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, {}); + const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); + const result = JSON.parse(content.content[0].text); expect(result.tools).toHaveLength(endpoints.length); expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); @@ -31,26 +32,30 @@ describe('dynamicTools', () => { }); it('should filter endpoints by name', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); + const result = JSON.parse(content.content[0].text); expect(result.tools).toHaveLength(1); expect(result.tools[0].name).toBe('user_endpoint'); }); it('should filter endpoints by resource', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); }); it('should filter endpoints by tag', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); }); it('should be case insensitive in search', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); + const result = JSON.parse(content.content[0].text); expect(result.tools.length).toBe(2); result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { @@ -63,9 +68,10 @@ describe('dynamicTools', () => { }); it('should filter endpoints by description', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'Test endpoint for user_endpoint', }); + const result = JSON.parse(content.content[0].text); expect(result.tools).toHaveLength(1); expect(result.tools[0].name).toBe('user_endpoint'); @@ -73,9 +79,10 @@ describe('dynamicTools', () => { }); it('should filter endpoints by partial description match', async () => { - const result = await toolsMap.list_api_endpoints.handler(fakeClient, { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'endpoint for user', }); + const result = JSON.parse(content.content[0].text); expect(result.tools).toHaveLength(1); expect(result.tools[0].name).toBe('user_endpoint'); @@ -84,9 +91,10 @@ describe('dynamicTools', () => { describe('get_api_endpoint_schema', () => { it('should return schema for existing endpoint', async () => { - const result = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { + const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'test_read_endpoint', }); + const result = JSON.parse(content.content[0].text); expect(result).toEqual(endpoints[0]?.tool); }); From 8a167970c0102371267194262318788f07dafc1d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 03:32:06 +0000 Subject: [PATCH 04/29] chore: avoid type error in certain environments --- src/internal/uploads.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts index 454c3172..eabf67ac 100644 --- a/src/internal/uploads.ts +++ b/src/internal/uploads.ts @@ -138,7 +138,7 @@ export const createForm = async >( // We check for Blob not File because Bun.File doesn't inherit from File, // but they both inherit from Blob and have a `name` property at runtime. -const isNamedBlob = (value: object) => value instanceof Blob && 'name' in value; +const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; const isUploadable = (value: unknown) => typeof value === 'object' && From 43d44461cf592c7a93277cc6b939ffaf23da2342 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 02:15:21 +0000 Subject: [PATCH 05/29] chore(mcp): provides high-level initMcpServer function and exports known clients --- packages/mcp-server/package.json | 4 +- packages/mcp-server/src/compat.ts | 40 ++++++++++++++ packages/mcp-server/src/index.ts | 4 +- packages/mcp-server/src/options.ts | 66 +++++------------------ packages/mcp-server/src/server.ts | 53 +++++++++++++----- packages/mcp-server/src/tools/index.ts | 5 +- packages/mcp-server/src/tools/types.ts | 1 - packages/mcp-server/tests/options.test.ts | 9 +--- 8 files changed, 99 insertions(+), 83 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 347a6186..a1a6dbe2 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -28,10 +28,10 @@ }, "dependencies": { "isaacus": "file:../../dist/", - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.11.5", "yargs": "^17.7.2", "@cloudflare/cabidela": "^0.2.4", - "zod": "^3.24.4", + "zod": "^3.25.20", "zod-to-json-schema": "^3.24.5" }, "bin": { diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts index 2cf24a0d..ff0d6d40 100644 --- a/packages/mcp-server/src/compat.ts +++ b/packages/mcp-server/src/compat.ts @@ -19,6 +19,46 @@ export const defaultClientCapabilities: ClientCapabilities = { toolNameLength: undefined, }; +export type ClientType = 'openai-agents' | 'claude' | 'claude-code' | 'cursor'; + +// 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 = { + 'openai-agents': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + claude: { + topLevelUnions: true, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + 'claude-code': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + cursor: { + topLevelUnions: false, + validJson: true, + refs: false, + unions: false, + formats: false, + toolNameLength: 50, + }, +}; + /** * Attempts to parse strings into JSON objects */ diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 17e31ce6..7704b9bc 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,7 +1,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { init, selectTools, server } from './server'; import { Endpoint, endpoints } from './tools'; -import { ParsedOptions, parseOptions } from './options'; +import { McpOptions, parseOptions } from './options'; async function main() { const options = parseOptionsOrError(); @@ -41,7 +41,7 @@ function parseOptionsOrError() { } } -function selectToolsOrError(endpoints: Endpoint[], options: ParsedOptions) { +function selectToolsOrError(endpoints: Endpoint[], options: McpOptions) { try { const includedTools = selectTools(endpoints, options); if (includedTools.length === 0) { diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 4a2d1669..c0751018 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -1,55 +1,19 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { endpoints, Filter } from './tools'; -import { ClientCapabilities } from './compat'; +import { ClientCapabilities, knownClients, ClientType } from './compat'; -type ClientType = 'openai-agents' | 'claude' | 'claude-code' | 'cursor'; - -// Client presets for compatibility -// Note that these could change over time as models get better, so this is -// a best effort. -const CLIENT_PRESETS: Record = { - 'openai-agents': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - claude: { - topLevelUnions: true, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - 'claude-code': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - cursor: { - topLevelUnions: false, - validJson: true, - refs: false, - unions: false, - formats: false, - toolNameLength: 50, - }, +export type CLIOptions = McpOptions & { + list: boolean; }; -export interface ParsedOptions { +export type McpOptions = { + client: ClientType | undefined; includeDynamicTools: boolean | undefined; includeAllTools: boolean | undefined; filters: Filter[]; - capabilities: ClientCapabilities; - list: boolean; -} + capabilities?: Partial; +}; const CAPABILITY_CHOICES = [ 'top-level-unions', @@ -80,7 +44,7 @@ function parseCapabilityValue(cap: string): { name: Capability; value?: number } return { name: cap as Capability }; } -export function parseOptions(): ParsedOptions { +export function parseOptions(): CLIOptions { const opts = yargs(hideBin(process.argv)) .option('tools', { type: 'string', @@ -141,7 +105,7 @@ export function parseOptions(): ParsedOptions { }) .option('client', { type: 'string', - choices: Object.keys(CLIENT_PRESETS), + choices: Object.keys(knownClients), description: 'Specify the MCP client being used', }) .option('capability', { @@ -229,14 +193,6 @@ export function parseOptions(): ParsedOptions { toolNameLength: undefined, }; - // Apply client preset if specified - if (typeof argv.client === 'string') { - const clientKey = argv.client as ClientType; - if (CLIENT_PRESETS[clientKey]) { - Object.assign(clientCapabilities, CLIENT_PRESETS[clientKey]); - } - } - // Apply individual capability overrides if (Array.isArray(argv.capability)) { for (const cap of argv.capability) { @@ -282,7 +238,9 @@ export function parseOptions(): ParsedOptions { const includeAllTools = explicitTools ? argv.tools?.includes('all') && !argv.noTools?.includes('all') : undefined; + const client = argv.client as ClientType; return { + client: client && knownClients[client] ? client : undefined, includeDynamicTools, includeAllTools, filters, @@ -323,7 +281,7 @@ Client Presets (--client): Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. Current presets: -${JSON.stringify(CLIENT_PRESETS, null, 2)} +${JSON.stringify(knownClients, null, 2)} `; } diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 24db3535..d14a826d 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -4,15 +4,22 @@ 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 { ClientOptions } from 'isaacus'; import Isaacus from 'isaacus'; import { applyCompatibilityTransformations, ClientCapabilities, defaultClientCapabilities, + knownClients, parseEmbeddedJSON, } from './compat'; import { dynamicTools } from './dynamic-tools'; -import { ParsedOptions } from './options'; +import { McpOptions } from './options'; + +export { McpOptions } from './options'; +export { ClientType } from './compat'; +export { Filter } from './tools'; +export { ClientOptions } from 'isaacus'; export { endpoints } from './tools'; // Create server instance @@ -32,6 +39,21 @@ export const server = new McpServer( * Initializes the provided MCP Server with the given tools and handlers. * If not provided, the default client, tools and handlers will be used. */ +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; @@ -65,24 +87,27 @@ export function init(params: { /** * Selects the tools to include in the MCP Server based on the provided options. */ -export function selectTools(endpoints: Endpoint[], options: ParsedOptions) { +export function selectTools(endpoints: Endpoint[], options: McpOptions) { const filteredEndpoints = query(options.filters, endpoints); - const includedTools = filteredEndpoints; + let includedTools = filteredEndpoints; - if (options.includeAllTools && includedTools.length === 0) { - includedTools.push(...endpoints); - } - - if (options.includeDynamicTools) { - includedTools.push(...dynamicTools(endpoints)); - } - - if (includedTools.length === 0) { - includedTools.push(...endpoints); + if (includedTools.length > 0) { + if (options.includeDynamicTools) { + includedTools = dynamicTools(includedTools); + } + } else { + if (options.includeAllTools) { + includedTools = endpoints; + } else if (options.includeDynamicTools) { + includedTools = dynamicTools(endpoints); + } else { + includedTools = endpoints; + } } - return applyCompatibilityTransformations(includedTools, options.capabilities); + const capabilities = { ...defaultClientCapabilities, ...options.capabilities }; + return applyCompatibilityTransformations(includedTools, capabilities); } /** diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts index e95314a8..4b2f0827 100644 --- a/packages/mcp-server/src/tools/index.ts +++ b/packages/mcp-server/src/tools/index.ts @@ -42,9 +42,10 @@ export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { }); // Check if any filters didn't match - if (unmatchedFilters.size > 0) { + const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); + if (unmatched.length > 0) { throw new Error( - `The following filters did not match any endpoints: ${[...unmatchedFilters] + `The following filters did not match any endpoints: ${unmatched .map((f) => `${f.type}=${f.value}`) .join(', ')}`, ); diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts index 5b69487e..49a6f7c5 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/tools/types.ts @@ -91,7 +91,6 @@ export type Metadata = { resource: string; operation: 'read' | 'write'; tags: string[]; - httpMethod?: string; httpPath?: string; operationId?: string; diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index 9e2b2f8c..f7661d68 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -77,14 +77,7 @@ describe('parseOptions', () => { const result = parseOptions(); - expect(result.capabilities).toEqual({ - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }); + expect(result.client).toEqual('openai-agents'); cleanup(); }); From 6805e78dc985503dd1f84a8de227aedd5dddf99d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 02:28:29 +0000 Subject: [PATCH 06/29] =?UTF-8?q?fix:=20publish=20script=20=E2=80=94=20han?= =?UTF-8?q?dle=20NPM=20errors=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/publish-npm | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/bin/publish-npm b/bin/publish-npm index 2505deca..fa2243d2 100644 --- a/bin/publish-npm +++ b/bin/publish-npm @@ -7,15 +7,35 @@ npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" yarn build cd dist +# Get package name and version from package.json +PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" +VERSION="$(jq -r -e '.version' ./package.json)" + # Get latest version from npm # -# If the package doesn't exist, yarn will return -# {"type":"error","data":"Received invalid response from npm."} -# where .data.version doesn't exist so LAST_VERSION will be an empty string. -LAST_VERSION="$(yarn info --json 2> /dev/null | jq -r '.data.version')" - -# Get current version from package.json -VERSION="$(node -p "require('./package.json').version")" +# If the package doesn't exist, npm will return: +# { +# "error": { +# "code": "E404", +# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", +# "detail": "'the_package' is not in this registry..." +# } +# } +NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" + +# Check if we got an E404 error +if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then + # Package doesn't exist yet, no last version + LAST_VERSION="" +elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then + # Report other errors + echo "ERROR: npm returned unexpected data:" + echo "$NPM_INFO" + exit 1 +else + # Success - get the version + LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes +fi # Check if current version is pre-release (e.g. alpha / beta / rc) CURRENT_IS_PRERELEASE=false From 03358ec382f8c487656f80e5679a73aa54e8a867 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 02:42:27 +0000 Subject: [PATCH 07/29] chore(internal): add pure annotations, make base APIResource abstract --- package.json | 2 +- packages/mcp-server/build | 6 +- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/index.ts | 2 + packages/mcp-server/yarn.lock | 162 ++----------------------------- scripts/build | 2 +- src/core/resource.ts | 2 +- src/internal/headers.ts | 10 +- src/internal/uploads.ts | 2 +- src/internal/utils/log.ts | 2 +- src/internal/utils/path.ts | 2 +- src/internal/utils/values.ts | 3 + tsc-multi.json | 12 ++- yarn.lock | 6 +- 14 files changed, 37 insertions(+), 178 deletions(-) diff --git a/package.json b/package.json index 1c41f608..2080e156 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "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.4/tsc-multi-1.1.4.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.7/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "typescript": "5.8.3" }, diff --git a/packages/mcp-server/build b/packages/mcp-server/build index c1de500e..df7429e9 100644 --- a/packages/mcp-server/build +++ b/packages/mcp-server/build @@ -23,14 +23,10 @@ PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/ma node scripts/postprocess-dist-package-json.cjs # build to .js/.mjs/.d.ts files -npm exec tsc-multi +./node_modules/.bin/tsc-multi cp tsconfig.dist-src.json dist/src/tsconfig.json -# Add proper Node.js shebang to the top of the file -sed -i.bak '1s;^;#!/usr/bin/env node\n;' dist/index.js -rm dist/index.js.bak - chmod +x dist/index.js DIST_PATH=./dist PKG_IMPORT_PATH=isaacus-mcp/ node ../../scripts/utils/postprocess-files.cjs diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index a1a6dbe2..5a9d2ac4 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -49,7 +49,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.4/tsc-multi-1.1.4.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.7/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "typescript": "5.8.3" }, diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 7704b9bc..06213572 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,3 +1,5 @@ +#!/usr/bin/env node + import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { init, selectTools, server } from './server'; import { Endpoint, endpoints } from './tools'; diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 08682046..9970ec32 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -742,14 +742,6 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/node-fetch@^2.6.4": - version "2.6.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" - integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== - dependencies: - "@types/node" "*" - form-data "^4.0.0" - "@types/node@*": version "22.15.17" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" @@ -757,13 +749,6 @@ dependencies: undici-types "~6.21.0" -"@types/node@^18.11.18": - version "18.19.100" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.100.tgz#7f3aefbb6911099ab7e0902a1f373b1a4d2c1947" - integrity sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA== - dependencies: - undici-types "~5.26.4" - "@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" @@ -867,13 +852,6 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - accepts@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" @@ -899,13 +877,6 @@ acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== -agentkeepalive@^4.2.1: - version "4.6.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" - integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== - dependencies: - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -978,11 +949,6 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -1222,13 +1188,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1318,11 +1277,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - depd@2.0.0, depd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -1415,16 +1369,6 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -1570,11 +1514,6 @@ etag@^1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventsource-parser@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" @@ -1764,29 +1703,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - -form-data@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" - integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - mime-types "^2.1.12" - -formdata-node@^4.3.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" - integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.3" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1822,7 +1738,7 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -1919,18 +1835,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.3, has-symbols@^1.1.0: +has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -1959,13 +1868,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -2665,23 +2567,11 @@ micromatch@^4.0.4, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - mime-db@^1.54.0: version "1.54.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime-types@^3.0.0, mime-types@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" @@ -2732,7 +2622,7 @@ mkdirp@^2.1.6: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== -ms@^2.0.0, ms@^2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2747,18 +2637,6 @@ negotiator@^1.0.0: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -3379,11 +3257,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - ts-api-utils@^2.0.1: version "2.1.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" @@ -3432,9 +3305,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.4/tsc-multi-1.1.4.tgz": - version "1.1.4" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.4/tsc-multi-1.1.4.tgz#cbed459a9e902f5295ec3daaf1c7aa3b10427e55" +"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" dependencies: debug "^4.3.7" fast-glob "^3.3.2" @@ -3502,11 +3375,6 @@ typescript@5.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -3563,24 +3431,6 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web-streams-polyfill@4.0.0-beta.3: - version "4.0.0-beta.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" - integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" diff --git a/scripts/build b/scripts/build index 2fb46206..099cedf5 100755 --- a/scripts/build +++ b/scripts/build @@ -31,7 +31,7 @@ fi node scripts/utils/make-dist-package-json.cjs > dist/package.json # build to .js/.mjs/.d.ts files -npm exec tsc-multi +./node_modules/.bin/tsc-multi # we need to patch index.js so that `new module.exports()` works for cjs backwards # compat. No way to get that from index.ts because it would cause compile errors # when building .mjs diff --git a/src/core/resource.ts b/src/core/resource.ts index a963b780..2dad649c 100644 --- a/src/core/resource.ts +++ b/src/core/resource.ts @@ -2,7 +2,7 @@ import type { Isaacus } from '../client'; -export class APIResource { +export abstract class APIResource { protected _client: Isaacus; constructor(client: Isaacus) { diff --git a/src/internal/headers.ts b/src/internal/headers.ts index 5cc03ce3..c724a9d2 100644 --- a/src/internal/headers.ts +++ b/src/internal/headers.ts @@ -1,5 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { isReadonlyArray } from './utils/values'; + type HeaderValue = string | undefined | null; export type HeadersLike = | Headers @@ -9,7 +11,7 @@ export type HeadersLike = | null | NullableHeaders; -const brand_privateNullableHeaders = Symbol('brand.privateNullableHeaders'); +const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); /** * @internal @@ -25,8 +27,6 @@ export type NullableHeaders = { nulls: Set; }; -const isArray = Array.isArray as (val: unknown) => val is readonly unknown[]; - function* iterateHeaders(headers: HeadersLike): IterableIterator { if (!headers) return; @@ -43,7 +43,7 @@ function* iterateHeaders(headers: HeadersLike): IterableIterator; if (headers instanceof Headers) { iter = headers.entries(); - } else if (isArray(headers)) { + } else if (isReadonlyArray(headers)) { iter = headers; } else { shouldClear = true; @@ -52,7 +52,7 @@ function* iterateHeaders(headers: HeadersLike): IterableIterator>(); +const supportsFormDataMap = /** @__PURE__ */ new WeakMap>(); /** * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts index 1570446b..1a8835c1 100644 --- a/src/internal/utils/log.ts +++ b/src/internal/utils/log.ts @@ -58,7 +58,7 @@ const noopLogger = { debug: noop, }; -let cachedLoggers = new WeakMap(); +let cachedLoggers = /** @__PURE__ */ new WeakMap(); export function loggerFor(client: Isaacus): Logger { const logger = client.logger; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts index 5bbdde6a..824c12d5 100644 --- a/src/internal/utils/path.ts +++ b/src/internal/utils/path.ts @@ -60,4 +60,4 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) => /** * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. */ -export const path = createPathTagFunction(encodeURIPath); +export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts index ec076e82..9b133076 100644 --- a/src/internal/utils/values.ts +++ b/src/internal/utils/values.ts @@ -9,6 +9,9 @@ export const isAbsoluteURL = (url: string): boolean => { return startsWithSchemeRegexp.test(url); }; +export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); +export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; + /** Returns an object if the given value isn't an object, otherwise returns as-is */ export function maybeObj(x: unknown): object { if (typeof x !== 'object') { diff --git a/tsc-multi.json b/tsc-multi.json index 170bac7a..384ddac5 100644 --- a/tsc-multi.json +++ b/tsc-multi.json @@ -1,7 +1,15 @@ { "targets": [ - { "extname": ".js", "module": "commonjs", "shareHelpers": "internal/tslib.js" }, - { "extname": ".mjs", "module": "esnext", "shareHelpers": "internal/tslib.mjs" } + { + "extname": ".js", + "module": "commonjs", + "shareHelpers": "internal/tslib.js" + }, + { + "extname": ".mjs", + "module": "esnext", + "shareHelpers": "internal/tslib.mjs" + } ], "projects": ["tsconfig.build.json"] } diff --git a/yarn.lock b/yarn.lock index 49d3eb8f..c611c53b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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.4/tsc-multi-1.1.4.tgz": - version "1.1.4" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.4/tsc-multi-1.1.4.tgz#cbed459a9e902f5295ec3daaf1c7aa3b10427e55" +"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" dependencies: debug "^4.3.7" fast-glob "^3.3.2" From db4bb4f568df5883c148250104123900466b2f05 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 03:09:15 +0000 Subject: [PATCH 08/29] feat(mcp): set X-Stainless-MCP header --- packages/mcp-server/src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index d14a826d..8502607d 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -65,7 +65,7 @@ export function init(params: { const endpointMap = Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); - const client = params.client || new Isaacus({}); + const client = params.client || new Isaacus({ defaultHeaders: { 'X-Stainless-MCP': 'true' } }); server.setRequestHandler(ListToolsRequestSchema, async () => { return { From 301c34137a10f732d62218fedf0614a068acd997 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 03:36:12 +0000 Subject: [PATCH 09/29] chore(client): refactor imports --- src/client.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/client.ts b/src/client.ts index 5fb5fc96..d5befc6a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5,7 +5,6 @@ import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestIni import { uuid4 } from './internal/utils/uuid'; import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; import { sleep } from './internal/utils/sleep'; -import { type Logger, type LogLevel, parseLogLevel } from './internal/utils/log'; export type { Logger, LogLevel } from './internal/utils/log'; import { castToError, isAbortError } from './internal/errors'; import type { APIResponseProps } from './internal/parse'; @@ -17,15 +16,21 @@ 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 { Classifications } from './resources/classifications/classifications'; +import { Extractions } from './resources/extractions/extractions'; import { type Fetch } from './internal/builtin-types'; import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; import { FinalRequestOptions, RequestOptions } from './internal/request-options'; -import { Reranking, RerankingCreateParams, Rerankings } from './resources/rerankings'; import { readEnv } from './internal/utils/env'; -import { formatRequestDetails, loggerFor } from './internal/utils/log'; +import { + type LogLevel, + type Logger, + formatRequestDetails, + loggerFor, + parseLogLevel, +} from './internal/utils/log'; import { isEmptyObj } from './internal/utils/values'; -import { Classifications } from './resources/classifications/classifications'; -import { Extractions } from './resources/extractions/extractions'; export interface ClientOptions { /** From 5f69f71f60b25ce1559a244ab666042b944533b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:14:40 +0000 Subject: [PATCH 10/29] feat(client): add support for endpoint-specific base URLs --- package.json | 2 +- packages/mcp-server/package.json | 2 +- src/client.ts | 20 ++++++++++++++++---- src/internal/request-options.ts | 1 + tests/index.test.ts | 22 ++++++++++++++++++++++ yarn.lock | 6 +++--- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 2080e156..34d255e1 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "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.7/tsc-multi.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "typescript": "5.8.3" }, diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 5a9d2ac4..395e48ff 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -49,7 +49,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.7/tsc-multi.tgz", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", "typescript": "5.8.3" }, diff --git a/src/client.ts b/src/client.ts index d5befc6a..dd244cc6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -189,6 +189,13 @@ export class Isaacus { }); } + /** + * Check whether the base URL is set to its default. + */ + #baseURLOverridden(): boolean { + return this.baseURL !== 'https://api.isaacus.com/v1'; + } + protected defaultQuery(): Record | undefined { return this._options.defaultQuery; } @@ -238,11 +245,16 @@ export class Isaacus { return Errors.APIError.generate(status, error, message, headers); } - buildURL(path: string, query: Record | null | undefined): string { + buildURL( + path: string, + query: Record | null | undefined, + defaultBaseURL?: string | undefined, + ): string { + const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; const url = isAbsoluteURL(path) ? new URL(path) - : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); if (!isEmptyObj(defaultQuery)) { @@ -583,9 +595,9 @@ export class Isaacus { { retryCount = 0 }: { retryCount?: number } = {}, ): { req: FinalizedRequestInit; url: string; timeout: number } { const options = { ...inputOptions }; - const { method, path, query } = options; + const { method, path, query, defaultBaseURL } = options; - const url = this.buildURL(path!, query as Record); + const url = this.buildURL(path!, query as Record, defaultBaseURL); if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const { bodyHeaders, body } = this.buildBody({ options }); diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts index d2ade9ec..7de032f4 100644 --- a/src/internal/request-options.ts +++ b/src/internal/request-options.ts @@ -20,6 +20,7 @@ export type RequestOptions = { fetchOptions?: MergedRequestInit; signal?: AbortSignal | undefined | null; idempotencyKey?: string; + defaultBaseURL?: string | undefined; __binaryResponse?: boolean | undefined; }; diff --git a/tests/index.test.ts b/tests/index.test.ts index a91dae65..bdaf450a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -310,6 +310,28 @@ describe('instantiate client', () => { const client = new Isaacus({ apiKey: 'My API Key' }); expect(client.baseURL).toEqual('https://api.isaacus.com/v1'); }); + + test('in request options', () => { + const client = new Isaacus({ apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/option/foo', + ); + }); + + test('in request options overridden by client options', () => { + const client = new Isaacus({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/client/foo', + ); + }); + + test('in request options overridden by env variable', () => { + process.env['ISAACUS_BASE_URL'] = 'http://localhost:5000/env'; + const client = new Isaacus({ apiKey: 'My API Key' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/env/foo', + ); + }); }); test('maxRetries option is correctly set', () => { diff --git a/yarn.lock b/yarn.lock index c611c53b..58c08d5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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.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.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" dependencies: debug "^4.3.7" fast-glob "^3.3.2" From 840289ede6ef0e034cf1b72a4d3a324d467cf43e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 07:00:54 +0000 Subject: [PATCH 11/29] chore(ci): enable for pull requests --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 932afb7b..544ce737 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: From 2413a0e5a09e1aff4490aef3f27eff77fb1e5c0b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 02:16:24 +0000 Subject: [PATCH 12/29] chore(readme): update badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d5e3766..82cf8fa8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Isaacus TypeScript API Library -[![NPM version](https://img.shields.io/npm/v/isaacus.svg)](https://npmjs.org/package/isaacus) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/isaacus) +[![NPM version]()](https://npmjs.org/package/isaacus) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/isaacus) This library provides convenient access to the Isaacus REST API from server-side TypeScript or JavaScript. From 35f22a6f4288fe6736ea39e20870b59adfc91e58 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:40:37 +0000 Subject: [PATCH 13/29] chore(readme): use better example snippet for undocumented params --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 82cf8fa8..2eea75c5 100644 --- a/README.md +++ b/README.md @@ -250,9 +250,8 @@ parameter. This library doesn't validate at runtime that the request matches the send will be sent as-is. ```ts -client.foo.create({ - foo: 'my_param', - bar: 12, +client.classifications.universal.create({ + // ... // @ts-expect-error baz is not yet public baz: 'undocumented option', }); From 01129895de3a6d135186e75817f75c8512084985 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 02:19:39 +0000 Subject: [PATCH 14/29] fix(client): explicitly copy fetch in withOptions --- src/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.ts b/src/client.ts index dd244cc6..9ec2cfbd 100644 --- a/src/client.ts +++ b/src/client.ts @@ -183,6 +183,7 @@ export class Isaacus { timeout: this.timeout, logger: this.logger, logLevel: this.logLevel, + fetch: this.fetch, fetchOptions: this.fetchOptions, apiKey: this.apiKey, ...options, From 02f5170723293eda6ae0995673136e3528c39f8c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 02:38:05 +0000 Subject: [PATCH 15/29] =?UTF-8?q?fix(ci):=20release-doctor=20=E2=80=94=20r?= =?UTF-8?q?eport=20correct=20token=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/check-release-environment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/check-release-environment b/bin/check-release-environment index b877e5f2..e4b6d58e 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${NPM_TOKEN}" ]; then - errors+=("The ISAACUS_NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") + errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") fi lenErrors=${#errors[@]} From d2e9558832362fff02e3ffd5f5a950b609bdbd0c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 05:41:43 +0000 Subject: [PATCH 16/29] fix(client): get fetchOptions type more reliably --- src/internal/types.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/internal/types.ts b/src/internal/types.ts index d7928cd3..b668dfc0 100644 --- a/src/internal/types.ts +++ b/src/internal/types.ts @@ -7,7 +7,7 @@ export type KeysEnum = { [P in keyof Required]: true }; export type FinalizedRequestInit = RequestInit & { headers: Headers }; -type NotAny = [unknown] extends [T] ? never : T; +type NotAny = [0] extends [1 & T] ? never : T; /** * Some environments overload the global fetch function, and Parameters only gets the last signature. @@ -64,13 +64,15 @@ type OverloadedParameters = * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition */ /** @ts-ignore For users with \@types/node */ -type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; /** @ts-ignore For users with undici */ -type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; /** @ts-ignore For users with \@types/bun */ type BunRequestInit = globalThis.FetchRequestInit; -/** @ts-ignore For users with node-fetch */ -type NodeFetchRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with node-fetch@2 */ +type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ +type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; /** @ts-ignore For users who use Deno */ type FetchRequestInit = NonNullable[1]>; /* eslint-enable */ @@ -79,7 +81,8 @@ type RequestInits = | NotAny | NotAny | NotAny - | NotAny + | NotAny + | NotAny | NotAny | NotAny; From cf544d1ae1cc37bb2cfd13189652bb2b0a2ab92e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:48:55 +0000 Subject: [PATCH 17/29] chore(ci): only run for pushes and fork pull requests --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 544ce737..a6a00501 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/isaacus-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -35,6 +36,7 @@ jobs: timeout-minutes: 5 name: build runs-on: ${{ github.repository == 'stainless-sdks/isaacus-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork permissions: contents: read id-token: write @@ -70,6 +72,7 @@ jobs: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/isaacus-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 0c8d6f0123f808d8bc950936c45fbf7dfbe7ce07 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 02:15:59 +0000 Subject: [PATCH 18/29] chore(client): improve path param validation --- src/internal/uploads.ts | 2 +- src/internal/utils/log.ts | 2 +- src/internal/utils/path.ts | 39 +++++++-- tests/path.test.ts | 158 +++++++++++++++++++++++++++++++++++-- 4 files changed, 185 insertions(+), 16 deletions(-) diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts index c03d140f..1596db68 100644 --- a/src/internal/uploads.ts +++ b/src/internal/uploads.ts @@ -90,7 +90,7 @@ export const multipartFormRequestOptions = async ( return { ...opts, body: await createForm(opts.body, fetch) }; }; -const supportsFormDataMap = /** @__PURE__ */ new WeakMap>(); +const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); /** * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts index 1a8835c1..3ec1eada 100644 --- a/src/internal/utils/log.ts +++ b/src/internal/utils/log.ts @@ -58,7 +58,7 @@ const noopLogger = { debug: noop, }; -let cachedLoggers = /** @__PURE__ */ new WeakMap(); +let cachedLoggers = /* @__PURE__ */ new WeakMap(); export function loggerFor(client: Isaacus): Logger { const logger = client.logger; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts index 824c12d5..56b63daf 100644 --- a/src/internal/utils/path.ts +++ b/src/internal/utils/path.ts @@ -12,25 +12,43 @@ export function encodeURIPath(str: string) { return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); } +const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); + export const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics: readonly string[], ...params: readonly unknown[]): string { // If there are no params, no processing is needed. if (statics.length === 1) return statics[0]!; let postPath = false; + const invalidSegments = []; const path = statics.reduce((previousValue, currentValue, index) => { if (/[?#]/.test(currentValue)) { postPath = true; } - return ( - previousValue + - currentValue + - (index === params.length ? '' : (postPath ? encodeURIComponent : pathEncoder)(String(params[index]))) - ); + const value = params[index]; + let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value); + if ( + index !== params.length && + (value == null || + (typeof value === 'object' && + // handle values from other realms + value.toString === + Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) + ?.toString)) + ) { + encoded = value + ''; + invalidSegments.push({ + start: previousValue.length + currentValue.length, + length: encoded.length, + error: `Value of type ${Object.prototype.toString + .call(value) + .slice(8, -1)} is not a valid path parameter`, + }); + } + return previousValue + currentValue + (index === params.length ? '' : encoded); }, ''); const pathOnly = path.split(/[?#]/, 1)[0]!; - const invalidSegments = []; const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; let match; @@ -39,9 +57,12 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) => invalidSegments.push({ start: match.index, length: match[0].length, + error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, }); } + invalidSegments.sort((a, b) => a.start - b.start); + if (invalidSegments.length > 0) { let lastEnd = 0; const underline = invalidSegments.reduce((acc, segment) => { @@ -51,7 +72,11 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) => return acc + spaces + arrows; }, ''); - throw new IsaacusError(`Path parameters result in path with invalid segments:\n${path}\n${underline}`); + throw new IsaacusError( + `Path parameters result in path with invalid segments:\n${invalidSegments + .map((e) => e.error) + .join('\n')}\n${path}\n${underline}`, + ); } return path; diff --git a/tests/path.test.ts b/tests/path.test.ts index caccd698..1b8c89d2 100644 --- a/tests/path.test.ts +++ b/tests/path.test.ts @@ -1,5 +1,6 @@ import { createPathTagFunction, encodeURIPath } from 'isaacus/internal/utils/path'; import { inspect } from 'node:util'; +import { runInNewContext } from 'node:vm'; describe('path template tag function', () => { test('validates input', () => { @@ -32,9 +33,114 @@ describe('path template tag function', () => { return testParams.flatMap((e) => rest.map((r) => [e, ...r])); } - // we need to test how %2E is handled so we use a custom encoder that does no escaping + // We need to test how %2E is handled, so we use a custom encoder that does no escaping. const rawPath = createPathTagFunction((s) => s); + const emptyObject = {}; + const mathObject = Math; + const numberObject = new Number(); + const stringObject = new String(); + const basicClass = new (class {})(); + const classWithToString = new (class { + toString() { + return 'ok'; + } + })(); + + // Invalid values + expect(() => rawPath`/a/${null}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Null is not a valid path parameter\n' + + '/a/null/b\n' + + ' ^^^^', + ); + expect(() => rawPath`/a/${undefined}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Undefined is not a valid path parameter\n' + + '/a/undefined/b\n' + + ' ^^^^^^^^^', + ); + expect(() => rawPath`/a/${emptyObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${mathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${basicClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^', + ); + expect(() => rawPath`/../${''}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/../\n' + + ' ^^', + ); + expect(() => rawPath`/../${{}}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + 'Value of type Object is not a valid path parameter\n' + + '/../[object Object]\n' + + ' ^^ ^^^^^^^^^^^^^^', + ); + + // Valid values + expect(rawPath`/${0}`).toBe('/0'); + expect(rawPath`/${''}`).toBe('/'); + expect(rawPath`/${numberObject}`).toBe('/0'); + expect(rawPath`${stringObject}/`).toBe('/'); + expect(rawPath`/${classWithToString}`).toBe('/ok'); + + // We need to check what happens with cross-realm values, which we might get from + // Jest or other frames in a browser. + + const newRealm = runInNewContext('globalThis'); + expect(newRealm.Object).not.toBe(Object); + + const crossRealmObject = newRealm.Object(); + const crossRealmMathObject = newRealm.Math; + const crossRealmNumber = new newRealm.Number(); + const crossRealmString = new newRealm.String(); + const crossRealmClass = new (class extends newRealm.Object {})(); + const crossRealmClassWithToString = new (class extends newRealm.Object { + toString() { + return 'ok'; + } + })(); + + // Invalid cross-realm values + expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${crossRealmMathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${crossRealmClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^^', + ); + + // Valid cross-realm values + expect(rawPath`/${crossRealmNumber}`).toBe('/0'); + expect(rawPath`${crossRealmString}/`).toBe('/'); + expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok'); + const results: { [pathParts: string]: { [params: string]: { valid: boolean; result?: string; error?: string }; @@ -85,6 +191,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + '/path_params/%2E%2e/a\n' + ' ^^^^^^', }, @@ -92,6 +199,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + '/path_params/%2E/a\n' + ' ^^^', }, @@ -103,6 +211,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + '/path_params/%2e%2E/\n' + ' ^^^^^^', }, @@ -110,6 +219,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + '/path_params/%2e/\n' + ' ^^^', }, @@ -121,6 +231,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + '/path_params/%2E\n' + ' ^^^', }, @@ -128,6 +239,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + '/path_params/%2E%2e\n' + ' ^^^^^^', }, @@ -137,11 +249,17 @@ describe('path template tag function', () => { '["x"]': { valid: true, result: 'x/a' }, '["%2E"]': { valid: false, - error: 'Error: Path parameters result in path with invalid segments:\n%2E/a\n^^^', + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^', }, '["%2e%2E"]': { valid: false, - error: 'Error: Path parameters result in path with invalid segments:\n' + '%2e%2E/a\n' + '^^^^^^', + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '%2e%2E/a\n' + + '^^^^^^', }, }, '["","/"]': { @@ -149,11 +267,18 @@ describe('path template tag function', () => { '[""]': { valid: true, result: '/' }, '["%2E%2e"]': { valid: false, - error: 'Error: Path parameters result in path with invalid segments:\n' + '%2E%2e/\n' + '^^^^^^', + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '%2E%2e/\n' + + '^^^^^^', }, '["."]': { valid: false, - error: 'Error: Path parameters result in path with invalid segments:\n./\n^', + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + './\n^', }, }, '["",""]': { @@ -161,11 +286,17 @@ describe('path template tag function', () => { '["x"]': { valid: true, result: 'x' }, '[".."]': { valid: false, - error: 'Error: Path parameters result in path with invalid segments:\n..\n^^', + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '..\n^^', }, '["."]': { valid: false, - error: 'Error: Path parameters result in path with invalid segments:\n.\n^', + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '.\n^', }, }, '["a"]': {}, @@ -185,6 +316,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' + '/path_params/%2E%2E?beta=true\n' + ' ^^^^^^', }, @@ -192,6 +324,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + '/path_params/%2e%2E?beta=true\n' + ' ^^^^^^', }, @@ -203,6 +336,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + '/path_params/.?beta=true\n' + ' ^', }, @@ -210,6 +344,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e." can\'t be safely passed as a path parameter\n' + '/path_params/%2e.?beta=true\n' + ' ^^^^', }, @@ -221,6 +356,8 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + '/path_params/./%2e/download\n' + ' ^ ^^^', }, @@ -228,6 +365,8 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + '/path_params/%2E%2e/%2e/download\n' + ' ^^^^^^ ^^^', }, @@ -243,6 +382,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + '/path_params/%2E/download\n' + ' ^^^', }, @@ -250,6 +390,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E." can\'t be safely passed as a path parameter\n' + '/path_params/%2E./download\n' + ' ^^^^', }, @@ -261,6 +402,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + '/path_params/./download\n' + ' ^', }, @@ -268,6 +410,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + '/path_params/../download\n' + ' ^^', }, @@ -279,6 +422,7 @@ describe('path template tag function', () => { valid: false, error: 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + '/path_params/../download\n' + ' ^^', }, From 16b7822028f977b12a1c262e6b0a6602fb1ce8dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 03:57:30 +0000 Subject: [PATCH 19/29] chore: add docs to RequestOptions type --- src/client.ts | 2 ++ src/internal/request-options.ts | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/client.ts b/src/client.ts index 9ec2cfbd..6acff171 100644 --- a/src/client.ts +++ b/src/client.ts @@ -51,6 +51,8 @@ export interface ClientOptions { * * Note that request timeouts are retried by default, so in a worst-case scenario you may wait * much longer than this timeout before the promise succeeds or fails. + * + * @unit milliseconds */ timeout?: number | undefined; /** diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts index 7de032f4..2aabf9aa 100644 --- a/src/internal/request-options.ts +++ b/src/internal/request-options.ts @@ -9,17 +9,70 @@ import { type HeadersLike } from './headers'; export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; export type RequestOptions = { + /** + * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). + */ method?: HTTPMethod; + + /** + * The URL path for the request. + * + * @example "/v1/foo" + */ path?: string; + + /** + * Query parameters to include in the request URL. + */ query?: object | undefined | null; + + /** + * The request body. Can be a string, JSON object, FormData, or other supported types. + */ body?: unknown; + + /** + * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. + */ headers?: HeadersLike; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ maxRetries?: number; + stream?: boolean | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * @unit milliseconds + */ timeout?: number; + + /** + * Additional `RequestInit` options to be passed to the underlying `fetch` call. + * These options will be merged with the client's default fetch options. + */ fetchOptions?: MergedRequestInit; + + /** + * An AbortSignal that can be used to cancel the request. + */ signal?: AbortSignal | undefined | null; + + /** + * A unique key for this request to enable idempotency. + */ idempotencyKey?: string; + + /** + * Override the default base URL for this specific request. + */ defaultBaseURL?: string | undefined; __binaryResponse?: boolean | undefined; From d76316597b31c533754260474bee660e515fbffc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 04:43:09 +0000 Subject: [PATCH 20/29] chore: make some internal functions async --- src/client.ts | 25 ++++++++++++++----------- tests/index.test.ts | 30 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/client.ts b/src/client.ts index 6acff171..8104cfc0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -178,7 +178,7 @@ export class Isaacus { * Create a new client instance re-using the same options given to the current client with optional overriding. */ withOptions(options: Partial): this { - return new (this.constructor as any as new (props: ClientOptions) => typeof this)({ + const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ ...this._options, baseURL: this.baseURL, maxRetries: this.maxRetries, @@ -190,6 +190,7 @@ export class Isaacus { apiKey: this.apiKey, ...options, }); + return client; } /** @@ -207,7 +208,7 @@ export class Isaacus { return; } - protected authHeaders(opts: FinalRequestOptions): NullableHeaders | undefined { + protected async authHeaders(opts: FinalRequestOptions): Promise { return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]); } @@ -339,7 +340,9 @@ export class Isaacus { await this.prepareOptions(options); - const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining }); + const { req, url, timeout } = await this.buildRequest(options, { + retryCount: maxRetries - retriesRemaining, + }); await this.prepareRequest(req, { url, options }); @@ -417,7 +420,7 @@ export class Isaacus { } with status ${response.status} in ${headersTime - startTime}ms`; if (!response.ok) { - const shouldRetry = this.shouldRetry(response); + const shouldRetry = await this.shouldRetry(response); if (retriesRemaining && shouldRetry) { const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; @@ -516,7 +519,7 @@ export class Isaacus { } } - private shouldRetry(response: Response): boolean { + private async shouldRetry(response: Response): Promise { // Note this is not a standard header. const shouldRetryHeader = response.headers.get('x-should-retry'); @@ -593,10 +596,10 @@ export class Isaacus { return sleepSeconds * jitter * 1000; } - buildRequest( + async buildRequest( inputOptions: FinalRequestOptions, { retryCount = 0 }: { retryCount?: number } = {}, - ): { req: FinalizedRequestInit; url: string; timeout: number } { + ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { const options = { ...inputOptions }; const { method, path, query, defaultBaseURL } = options; @@ -604,7 +607,7 @@ export class Isaacus { if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const { bodyHeaders, body } = this.buildBody({ options }); - const reqHeaders = this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); + const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); const req: FinalizedRequestInit = { method, @@ -620,7 +623,7 @@ export class Isaacus { return { req, url, timeout: options.timeout }; } - private buildHeaders({ + private async buildHeaders({ options, method, bodyHeaders, @@ -630,7 +633,7 @@ export class Isaacus { method: HTTPMethod; bodyHeaders: HeadersLike; retryCount: number; - }): Headers { + }): Promise { let idempotencyHeaders: HeadersLike = {}; if (this.idempotencyHeader && method !== 'get') { if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); @@ -646,7 +649,7 @@ export class Isaacus { ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), ...getPlatformHeaders(), }, - this.authHeaders(options), + await this.authHeaders(options), this._options.defaultHeaders, bodyHeaders, options.headers, diff --git a/tests/index.test.ts b/tests/index.test.ts index bdaf450a..fda9d031 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -26,13 +26,13 @@ describe('instantiate client', () => { apiKey: 'My API Key', }); - test('they are used in the request', () => { - const { req } = client.buildRequest({ path: '/foo', method: 'post' }); + test('they are used in the request', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); expect(req.headers.get('x-my-default-header')).toEqual('2'); }); - test('can ignore `undefined` and leave the default', () => { - const { req } = client.buildRequest({ + test('can ignore `undefined` and leave the default', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', headers: { 'X-My-Default-Header': undefined }, @@ -40,8 +40,8 @@ describe('instantiate client', () => { expect(req.headers.get('x-my-default-header')).toEqual('2'); }); - test('can be removed with `null`', () => { - const { req } = client.buildRequest({ + test('can be removed with `null`', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', headers: { 'X-My-Default-Header': null }, @@ -344,7 +344,7 @@ describe('instantiate client', () => { }); describe('withOptions', () => { - test('creates a new client with overridden options', () => { + test('creates a new client with overridden options', async () => { const client = new Isaacus({ baseURL: 'http://localhost:5000/', maxRetries: 3, apiKey: 'My API Key' }); const newClient = client.withOptions({ @@ -365,7 +365,7 @@ describe('instantiate client', () => { expect(newClient.constructor).toBe(client.constructor); }); - test('inherits options from the parent client', () => { + test('inherits options from the parent client', async () => { const client = new Isaacus({ baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-Test-Header': 'test-value' }, @@ -380,7 +380,7 @@ describe('instantiate client', () => { // Test inherited options remain the same expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); - const { req } = newClient.buildRequest({ path: '/foo', method: 'get' }); + const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); expect(req.headers.get('x-test-header')).toEqual('test-value'); }); @@ -430,8 +430,8 @@ describe('request building', () => { const client = new Isaacus({ apiKey: 'My API Key' }); describe('custom headers', () => { - test('handles undefined', () => { - const { req } = client.buildRequest({ + test('handles undefined', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: { value: 'hello' }, @@ -466,8 +466,8 @@ describe('default encoder', () => { } } for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { - test(`serializes ${util.inspect(jsonValue)} as json`, () => { - const { req } = client.buildRequest({ + test(`serializes ${util.inspect(jsonValue)} as json`, async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: jsonValue, @@ -490,7 +490,7 @@ describe('default encoder', () => { asyncIterable, ]) { test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { - const { req } = client.buildRequest({ + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: streamValue, @@ -503,7 +503,7 @@ describe('default encoder', () => { } test(`can set content-type for ReadableStream`, async () => { - const { req } = client.buildRequest({ + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: new Response('a\nb\nc\n').body, From 8d0556f619b94473c24c362e72cbc4eaba644374 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:13:39 +0000 Subject: [PATCH 21/29] feat(mcp): support filtering tool results by a jq expression --- packages/mcp-server/package.json | 1 + packages/mcp-server/src/filtering.ts | 13 +++++++++++++ .../universal/create-classifications-universal.ts | 11 +++++++++-- .../tools/extractions/qa/create-extractions-qa.ts | 12 ++++++++++-- .../src/tools/rerankings/create-rerankings.ts | 12 ++++++++++-- 5 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 packages/mcp-server/src/filtering.ts diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 395e48ff..d21993d5 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -29,6 +29,7 @@ "dependencies": { "isaacus": "file:../../dist/", "@modelcontextprotocol/sdk": "^1.11.5", + "jq-web": "^0.6.2", "yargs": "^17.7.2", "@cloudflare/cabidela": "^0.2.4", "zod": "^3.25.20", diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts new file mode 100644 index 00000000..e560736f --- /dev/null +++ b/packages/mcp-server/src/filtering.ts @@ -0,0 +1,13 @@ +export async function maybeFilter(args: Record | undefined, response: any): Promise { + const jqFilter = args?.['jq_filter']; + if (jqFilter && typeof jqFilter === 'string') { + return await jq(response, jqFilter); + } else { + return response; + } +} + +var jqWeb = require('jq-web'); +async function jq(json: any, jqFilter: string) { + return (await jqWeb).json(json, jqFilter); +} 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 b5ae7170..b36eda91 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 @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { maybeFilter } from 'isaacus-mcp/filtering'; import { asTextContentResult } from 'isaacus-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; @@ -18,7 +19,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_classifications_universal', description: - 'Classify the relevance of legal documents to a query with an Isaacus universal legal AI classifier.', + "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```", inputSchema: { type: 'object', properties: { @@ -76,13 +77,19 @@ export const tool: Tool = { "The method to use for producing an overall confidence score.\n\n`auto` is the default scoring method and is recommended for most use cases. Currently, it is equivalent to `chunk_max`. In the future, it will automatically select the best method based on the model and inputs.\n\n`chunk_max` uses the highest confidence score of all of the texts' chunks.\n\n`chunk_avg` averages the confidence scores of all of the texts' chunks.\n\n`chunk_min` uses the lowest confidence score of all of the texts' chunks.", enum: ['auto', 'chunk_max', 'chunk_avg', 'chunk_min'], }, + 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/).', + }, }, }, }; export const handler = async (client: Isaacus, args: Record | undefined) => { const body = args as any; - return asTextContentResult(await client.classifications.universal.create(body)); + return asTextContentResult(await maybeFilter(args, await client.classifications.universal.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 7e42c254..bbf12030 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 @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { maybeFilter } from 'isaacus-mcp/filtering'; import { asTextContentResult } from 'isaacus-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; @@ -17,7 +18,8 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_extractions_qa', - description: 'Extract answers to questions from legal documents with an Isaacus legal AI answer extractor.', + 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```", inputSchema: { type: 'object', properties: { @@ -74,13 +76,19 @@ export const tool: Tool = { description: 'The number of highest scoring answers to return.\n\nIf `null`, which is the default, all answers will be returned.', }, + 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/).', + }, }, }, }; export const handler = async (client: Isaacus, args: Record | undefined) => { const body = args as any; - return asTextContentResult(await client.extractions.qa.create(body)); + return asTextContentResult(await maybeFilter(args, await client.extractions.qa.create(body))); }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts index f6d80dc1..1cf43313 100644 --- a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts +++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import { maybeFilter } from 'isaacus-mcp/filtering'; import { asTextContentResult } from 'isaacus-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; @@ -17,7 +18,8 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_rerankings', - description: 'Rerank legal documents by their relevance to a query with an Isaacus legal AI reranker.', + 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```", inputSchema: { type: 'object', properties: { @@ -79,13 +81,19 @@ export const tool: Tool = { title: 'Positive integer', description: 'A whole number greater than or equal to 1.', }, + 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/).', + }, }, }, }; export const handler = async (client: Isaacus, args: Record | undefined) => { const body = args as any; - return asTextContentResult(await client.rerankings.create(body)); + return asTextContentResult(await maybeFilter(args, await client.rerankings.create(body))); }; export default { metadata, tool, handler }; From 1627904fcb3474757ba2a29f940b5a4ce067b053 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 03:46:53 +0000 Subject: [PATCH 22/29] fix(mcp): relax input type for asTextContextResult --- packages/mcp-server/src/tools/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts index 49a6f7c5..5b7c5ba7 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/tools/types.ts @@ -47,7 +47,7 @@ export type HandlerFunction = ( args: Record | undefined, ) => Promise; -export function asTextContentResult(result: Object): ToolCallResult { +export function asTextContentResult(result: unknown): ToolCallResult { return { content: [ { From 391d8252818ab34f67f7ca02119a6660cdbe5520 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 03:29:29 +0000 Subject: [PATCH 23/29] fix(mcp): support jq filtering on cloudflare workers --- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/filtering.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index d21993d5..5e71756d 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -29,7 +29,7 @@ "dependencies": { "isaacus": "file:../../dist/", "@modelcontextprotocol/sdk": "^1.11.5", - "jq-web": "^0.6.2", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.2/jq-web.tar.gz", "yargs": "^17.7.2", "@cloudflare/cabidela": "^0.2.4", "zod": "^3.25.20", diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts index e560736f..87eab2de 100644 --- a/packages/mcp-server/src/filtering.ts +++ b/packages/mcp-server/src/filtering.ts @@ -1,3 +1,6 @@ +// @ts-nocheck +import initJq from 'jq-web'; + export async function maybeFilter(args: Record | undefined, response: any): Promise { const jqFilter = args?.['jq_filter']; if (jqFilter && typeof jqFilter === 'string') { @@ -7,7 +10,6 @@ export async function maybeFilter(args: Record | undefined, res } } -var jqWeb = require('jq-web'); async function jq(json: any, jqFilter: string) { - return (await jqWeb).json(json, jqFilter); + return (await initJq).json(json, jqFilter); } From 6d55f27f2157499c3ba7e5e137f821909269d5df Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 03:54:10 +0000 Subject: [PATCH 24/29] chore(mcp): rework imports in tools --- .../universal/create-classifications-universal.ts | 3 +-- .../src/tools/extractions/qa/create-extractions-qa.ts | 3 +-- packages/mcp-server/src/tools/rerankings/create-rerankings.ts | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) 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 b36eda91..681aaaf1 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 @@ -1,10 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { maybeFilter } from 'isaacus-mcp/filtering'; -import { asTextContentResult } from 'isaacus-mcp/tools/types'; +import { Metadata, asTextContentResult } from 'isaacus-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import type { Metadata } from '../../'; import Isaacus from 'isaacus'; export const metadata: Metadata = { 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 bbf12030..47d8d7eb 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 @@ -1,10 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { maybeFilter } from 'isaacus-mcp/filtering'; -import { asTextContentResult } from 'isaacus-mcp/tools/types'; +import { Metadata, asTextContentResult } from 'isaacus-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import type { Metadata } from '../../'; import Isaacus from 'isaacus'; export const metadata: Metadata = { diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts index 1cf43313..dc2bb6ab 100644 --- a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts +++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts @@ -1,10 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { maybeFilter } from 'isaacus-mcp/filtering'; -import { asTextContentResult } from 'isaacus-mcp/tools/types'; +import { Metadata, asTextContentResult } from 'isaacus-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import type { Metadata } from '../'; import Isaacus from 'isaacus'; export const metadata: Metadata = { From d0b6815a17366c5f67372cd44c4e43a4977cc3dc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 03:06:32 +0000 Subject: [PATCH 25/29] chore(ts): reorder package.json imports --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 34d255e1..1b8228fa 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@swc/jest": "^0.2.29", "@types/jest": "^29.4.0", "@types/node": "^20.17.6", - "typescript-eslint": "8.31.1", "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", "eslint": "^9.20.1", @@ -44,7 +43,8 @@ "ts-node": "^10.5.0", "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz", "tsconfig-paths": "^4.0.0", - "typescript": "5.8.3" + "typescript": "5.8.3", + "typescript-eslint": "8.31.1" }, "imports": { "isaacus": ".", From 62ef41ef9a61df100c684bb7cc694c299a84daf9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 03:27:05 +0000 Subject: [PATCH 26/29] chore(mcp): formatting --- packages/mcp-server/src/server.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 8502607d..31ac37bd 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -28,11 +28,7 @@ export const server = new McpServer( name: 'isaacus_api', version: '0.10.0', }, - { - capabilities: { - tools: {}, - }, - }, + { capabilities: { tools: {} } }, ); /** From d7e791bb7a3afbe7d8bf6a2c48a010fd091ada8d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 03:05:50 +0000 Subject: [PATCH 27/29] fix(mcp): include required section for top-level properties and support naming transformations --- .../universal/create-classifications-universal.ts | 2 +- .../src/tools/extractions/qa/create-extractions-qa.ts | 2 +- packages/mcp-server/src/tools/rerankings/create-rerankings.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 681aaaf1..60a6b474 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 @@ -63,7 +63,6 @@ export const tool: Tool = { description: 'A whole number greater than or equal to 1.', }, }, - required: [], }, is_iql: { type: 'boolean', @@ -83,6 +82,7 @@ export const tool: Tool = { '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', 'query', 'texts'], }, }; 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 47d8d7eb..7f4698ee 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 @@ -63,7 +63,6 @@ export const tool: Tool = { description: 'A whole number greater than or equal to 1.', }, }, - required: [], }, ignore_inextractability: { type: 'boolean', @@ -82,6 +81,7 @@ export const tool: Tool = { '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', 'query', 'texts'], }, }; diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts index dc2bb6ab..a05db3c9 100644 --- a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts +++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts @@ -62,7 +62,6 @@ export const tool: Tool = { description: 'A whole number greater than or equal to 1.', }, }, - required: [], }, is_iql: { type: 'boolean', @@ -87,6 +86,7 @@ export const tool: Tool = { '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', 'query', 'texts'], }, }; From bda771936be7dd0db83239b898a9fb8e8fd4a6ce Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 03:49:11 +0000 Subject: [PATCH 28/29] chore(internal): codegen related update --- .../universal/create-classifications-universal.ts | 1 + .../mcp-server/src/tools/extractions/qa/create-extractions-qa.ts | 1 + packages/mcp-server/src/tools/rerankings/create-rerankings.ts | 1 + 3 files changed, 3 insertions(+) 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 60a6b474..dbaabbd4 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 @@ -84,6 +84,7 @@ export const tool: Tool = { }, required: ['model', 'query', 'texts'], }, + annotations: {}, }; export const handler = async (client: Isaacus, args: Record | undefined) => { 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 7f4698ee..581a8e0d 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 @@ -83,6 +83,7 @@ export const tool: Tool = { }, required: ['model', 'query', 'texts'], }, + annotations: {}, }; export const handler = async (client: Isaacus, args: Record | undefined) => { diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts index a05db3c9..13b57821 100644 --- a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts +++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts @@ -88,6 +88,7 @@ export const tool: Tool = { }, required: ['model', 'query', 'texts'], }, + annotations: {}, }; export const handler = async (client: Isaacus, args: Record | undefined) => { From 94bd11ec6240fa2973251de537f5152662b3aea5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 03:49:40 +0000 Subject: [PATCH 29/29] release: 0.11.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++ package.json | 2 +- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/server.ts | 2 +- src/version.ts | 2 +- 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7d9b009d..78e7f271 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.10.0" + ".": "0.11.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3e4303..70dd1ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## 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) + +### Features + +* **client:** add support for endpoint-specific base URLs ([5f69f71](https://github.com/isaacus-dev/isaacus-typescript/commit/5f69f71f60b25ce1559a244ab666042b944533b0)) +* **mcp:** implement support for binary responses ([bec8bea](https://github.com/isaacus-dev/isaacus-typescript/commit/bec8beac1c9e9187aa3cbeb6ba49764763530277)) +* **mcp:** set X-Stainless-MCP header ([db4bb4f](https://github.com/isaacus-dev/isaacus-typescript/commit/db4bb4f568df5883c148250104123900466b2f05)) +* **mcp:** support filtering tool results by a jq expression ([8d0556f](https://github.com/isaacus-dev/isaacus-typescript/commit/8d0556f619b94473c24c362e72cbc4eaba644374)) + + +### Bug Fixes + +* **ci:** release-doctor — report correct token name ([02f5170](https://github.com/isaacus-dev/isaacus-typescript/commit/02f5170723293eda6ae0995673136e3528c39f8c)) +* **client:** explicitly copy fetch in withOptions ([0112989](https://github.com/isaacus-dev/isaacus-typescript/commit/01129895de3a6d135186e75817f75c8512084985)) +* **client:** get fetchOptions type more reliably ([d2e9558](https://github.com/isaacus-dev/isaacus-typescript/commit/d2e9558832362fff02e3ffd5f5a950b609bdbd0c)) +* **mcp:** include required section for top-level properties and support naming transformations ([d7e791b](https://github.com/isaacus-dev/isaacus-typescript/commit/d7e791bb7a3afbe7d8bf6a2c48a010fd091ada8d)) +* **mcp:** relax input type for asTextContextResult ([1627904](https://github.com/isaacus-dev/isaacus-typescript/commit/1627904fcb3474757ba2a29f940b5a4ce067b053)) +* **mcp:** support jq filtering on cloudflare workers ([391d825](https://github.com/isaacus-dev/isaacus-typescript/commit/391d8252818ab34f67f7ca02119a6660cdbe5520)) +* publish script — handle NPM errors correctly ([6805e78](https://github.com/isaacus-dev/isaacus-typescript/commit/6805e78dc985503dd1f84a8de227aedd5dddf99d)) + + +### Chores + +* add docs to RequestOptions type ([16b7822](https://github.com/isaacus-dev/isaacus-typescript/commit/16b7822028f977b12a1c262e6b0a6602fb1ce8dd)) +* avoid type error in certain environments ([8a16797](https://github.com/isaacus-dev/isaacus-typescript/commit/8a167970c0102371267194262318788f07dafc1d)) +* **ci:** enable for pull requests ([840289e](https://github.com/isaacus-dev/isaacus-typescript/commit/840289ede6ef0e034cf1b72a4d3a324d467cf43e)) +* **ci:** only run for pushes and fork pull requests ([cf544d1](https://github.com/isaacus-dev/isaacus-typescript/commit/cf544d1ae1cc37bb2cfd13189652bb2b0a2ab92e)) +* **client:** improve path param validation ([0c8d6f0](https://github.com/isaacus-dev/isaacus-typescript/commit/0c8d6f0123f808d8bc950936c45fbf7dfbe7ce07)) +* **client:** refactor imports ([301c341](https://github.com/isaacus-dev/isaacus-typescript/commit/301c34137a10f732d62218fedf0614a068acd997)) +* **docs:** use top-level-await in example snippets ([63d8953](https://github.com/isaacus-dev/isaacus-typescript/commit/63d895384acdadbeee292f951d524df90508c9b2)) +* **internal:** add pure annotations, make base APIResource abstract ([03358ec](https://github.com/isaacus-dev/isaacus-typescript/commit/03358ec382f8c487656f80e5679a73aa54e8a867)) +* **internal:** codegen related update ([bda7719](https://github.com/isaacus-dev/isaacus-typescript/commit/bda771936be7dd0db83239b898a9fb8e8fd4a6ce)) +* **internal:** fix readablestream types in node 20 ([604d380](https://github.com/isaacus-dev/isaacus-typescript/commit/604d380c9062489e279a76750b8cbf3707f16ecd)) +* make some internal functions async ([d763165](https://github.com/isaacus-dev/isaacus-typescript/commit/d76316597b31c533754260474bee660e515fbffc)) +* **mcp:** formatting ([62ef41e](https://github.com/isaacus-dev/isaacus-typescript/commit/62ef41ef9a61df100c684bb7cc694c299a84daf9)) +* **mcp:** provides high-level initMcpServer function and exports known clients ([43d4446](https://github.com/isaacus-dev/isaacus-typescript/commit/43d44461cf592c7a93277cc6b939ffaf23da2342)) +* **mcp:** rework imports in tools ([6d55f27](https://github.com/isaacus-dev/isaacus-typescript/commit/6d55f27f2157499c3ba7e5e137f821909269d5df)) +* **readme:** update badges ([2413a0e](https://github.com/isaacus-dev/isaacus-typescript/commit/2413a0e5a09e1aff4490aef3f27eff77fb1e5c0b)) +* **readme:** use better example snippet for undocumented params ([35f22a6](https://github.com/isaacus-dev/isaacus-typescript/commit/35f22a6f4288fe6736ea39e20870b59adfc91e58)) +* **ts:** reorder package.json imports ([d0b6815](https://github.com/isaacus-dev/isaacus-typescript/commit/d0b6815a17366c5f67372cd44c4e43a4977cc3dc)) + ## 0.10.0 (2025-06-03) Full Changelog: [v0.9.0...v0.10.0](https://github.com/isaacus-dev/isaacus-typescript/compare/v0.9.0...v0.10.0) diff --git a/package.json b/package.json index 1b8228fa..f29f3e42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "isaacus", - "version": "0.10.0", + "version": "0.11.0", "description": "The official TypeScript library for the Isaacus API", "author": "Isaacus ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 5e71756d..6ea8617f 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "isaacus-mcp", - "version": "0.10.0", + "version": "0.11.0", "description": "The official MCP Server for the Isaacus API", "author": "Isaacus ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 31ac37bd..ae9b363c 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -26,7 +26,7 @@ export { endpoints } from './tools'; export const server = new McpServer( { name: 'isaacus_api', - version: '0.10.0', + version: '0.11.0', }, { capabilities: { tools: {} } }, ); diff --git a/src/version.ts b/src/version.ts index c2e5b969..9085e9de 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.10.0'; // x-release-please-version +export const VERSION = '0.11.0'; // x-release-please-version