diff --git a/README.md b/README.md index 6b9afe4..b9470db 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,12 @@ Official Qveris MCP Server SDK — Dynamically search and execute tools via natu ## Overview -This SDK provides a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that enables LLMs to discover and execute third-party tools through the Qveris API. With just two simple tools, your AI assistant can: +This SDK provides a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that enables LLMs to discover and execute third-party tools through the Qveris API. With three simple tools, your AI assistant can: - **Search** for tools using natural language queries +- **Get** detailed information about specific tools by their IDs - **Execute** any discovered tool with the appropriate parameters -## Installation - -```bash -# Using npx (recommended for MCP) -npx @qverisai/sdk - -# Or install globally -npm add -g @qverisai/sdk -``` - ## Quick Start ### 1. Get Your API Key @@ -96,7 +87,7 @@ Search for available tools based on natural language queries. ```json { "query": "send email notification", - "limit": 5 + "limit": 10 } ``` @@ -110,18 +101,37 @@ Execute a discovered tool with specific parameters. | `search_id` | string | ✓ | Search ID from the search that found this tool | | `params_to_tool` | string | ✓ | JSON string of parameters to pass to the tool | | `session_id` | string | | Session identifier (auto-generated if omitted) | -| `max_data_size` | number | | Max response size in bytes (default: 20480) | +| `max_response_size` | number | | Max response size in bytes (default: 20480) | **Example:** ```json { - "tool_id": "openweathermap_current_weather", - "search_id": "abc123", + "tool_id": "openweathermap.weather.execute.v1", + "search_id": "abcd1234-ab12-ab12-ab12-abcdef123456", "params_to_tool": "{\"city\": \"London\", \"units\": \"metric\"}" } ``` +### `get_tools_by_ids` + +Get detailed descriptions of tools based on their tool IDs. Useful for retrieving information about specific tools when you already know their IDs from previous searches. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `tool_ids` | array | ✓ | Array of tool IDs to retrieve (at least one required) | +| `search_id` | string | | Search ID from the search that returned the tool(s) | +| `session_id` | string | | Session identifier (auto-generated if omitted) | + +**Example:** + +```json +{ + "tool_ids": ["openweathermap.weather.execute.v1", "worldbank_refined.search_indicators.v1"], + "search_id": "abcd1234-ab12-ab12-ab12-abcdef123456" +} +``` + ## Session Management Providing a consistent `session_id` in a same user session in any tool call enables: @@ -137,8 +147,8 @@ If not provided, the SDK automatically generates and maintains a session ID for ```json { - "execution_id": "exec-123", - "tool_id": "openweathermap_current_weather", + "execution_id": "abcd1234-ab12-ab12-ab12-abcdef123456", + "tool_id": "openweathermap.weather.execute.v1", "success": true, "result": { "data": { @@ -153,7 +163,7 @@ If not provided, the SDK automatically generates and maintains a session ID for ### Large Responses -When tool output exceeds `max_data_size`, you'll receive: +When tool output exceeds `max_response_size`, you'll receive: ```json { diff --git a/package-lock.json b/package-lock.json index b65f02c..3f3a976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@qverisai/sdk", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@qverisai/sdk", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", @@ -780,6 +780,7 @@ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1328,6 +1329,7 @@ "resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -2267,6 +2269,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -2453,6 +2456,7 @@ "resolved": "https://registry.npmmirror.com/zod/-/zod-4.1.13.tgz", "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 98e46e6..daf0cf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@qverisai/sdk", - "version": "0.1.1", + "version": "0.1.2", "description": "Official Qveris AI MCP Server SDK - Search and execute tools via natural language", "keywords": [ "qveris", diff --git a/src/api/client.test.ts b/src/api/client.test.ts index ffc06ac..a7a4e3a 100644 --- a/src/api/client.test.ts +++ b/src/api/client.test.ts @@ -131,6 +131,146 @@ describe('QverisClient', () => { }); }); + describe('getToolsByIds', () => { + let client: QverisClient; + let fetchMock: ReturnType; + + beforeEach(() => { + client = new QverisClient({ apiKey: 'test-api-key' }); + fetchMock = vi.fn(); + global.fetch = fetchMock; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should make POST request to /tools/by-ids endpoint', async () => { + const mockResponse = { + search_id: 'search-123', + results: [ + { + tool_id: 'weather-tool-1', + name: 'Weather API', + description: 'Get weather data', + }, + ], + total: 1, + }; + + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }); + + const result = await client.getToolsByIds({ + tool_ids: ['weather-tool-1'], + }); + + expect(fetchMock).toHaveBeenCalledWith( + 'https://qveris.ai/api/v1/tools/by-ids', + expect.objectContaining({ + method: 'POST', + headers: { + Authorization: 'Bearer test-api-key', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + tool_ids: ['weather-tool-1'], + }), + }) + ); + + expect(result).toEqual(mockResponse); + }); + + it('should include search_id and session_id when provided', async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + search_id: 'search-456', + results: [], + }), + }); + + await client.getToolsByIds({ + tool_ids: ['tool-1', 'tool-2'], + search_id: 'search-456', + session_id: 'session-abc', + }); + + expect(fetchMock).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + tool_ids: ['tool-1', 'tool-2'], + search_id: 'search-456', + session_id: 'session-abc', + }), + }) + ); + }); + + it('should handle multiple tool IDs', async () => { + const toolIds = ['tool-1', 'tool-2', 'tool-3']; + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + search_id: 'search-123', + results: toolIds.map((id) => ({ + tool_id: id, + name: `Tool ${id}`, + description: `Description for ${id}`, + })), + total: toolIds.length, + }), + }); + + const result = await client.getToolsByIds({ + tool_ids: toolIds, + }); + + expect(result.results).toHaveLength(3); + expect(result.total).toBe(3); + }); + + it('should throw ApiError on non-OK response', async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + status: 400, + statusText: 'Bad Request', + json: async () => ({ message: 'Invalid tool_ids' }), + }); + + await expect( + client.getToolsByIds({ tool_ids: [] }) + ).rejects.toEqual({ + status: 400, + message: 'Invalid tool_ids', + details: { message: 'Invalid tool_ids' }, + }); + }); + + it('should handle non-JSON error response', async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + json: async () => { + throw new Error('Not JSON'); + }, + }); + + await expect( + client.getToolsByIds({ tool_ids: ['tool-1'] }) + ).rejects.toEqual({ + status: 500, + message: 'Internal Server Error', + details: undefined, + }); + }); + }); + describe('executeTool', () => { let client: QverisClient; let fetchMock: ReturnType; @@ -202,7 +342,7 @@ describe('QverisClient', () => { ); }); - it('should include max_data_size when provided', async () => { + it('should include max_response_size when provided', async () => { fetchMock.mockResolvedValueOnce({ ok: true, json: async () => ({ @@ -217,7 +357,7 @@ describe('QverisClient', () => { await client.executeTool('tool-1', { search_id: 'search-123', parameters: {}, - max_data_size: 102400, + max_response_size: 102400, }); expect(fetchMock).toHaveBeenCalledWith( @@ -226,7 +366,7 @@ describe('QverisClient', () => { body: JSON.stringify({ search_id: 'search-123', parameters: {}, - max_data_size: 102400, + max_response_size: 102400, }), }) ); diff --git a/src/api/client.ts b/src/api/client.ts index 36a0a0b..359b726 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -10,6 +10,7 @@ import type { SearchRequest, SearchResponse, + GetToolsByIdsRequest, ExecuteRequest, ExecuteResponse, QverisClientConfig, @@ -126,7 +127,7 @@ export class QverisClient { * const result = await client.searchTools({ * query: 'send SMS message', * limit: 10, - * session_id: 'user-123' + * session_id: 'abcd1234-ab12-ab12-ab12-abcdef123456' * }); * * console.log(`Found ${result.total} tools`); @@ -139,6 +140,34 @@ export class QverisClient { return this.request('POST', '/search', request); } + /** + * Get tool descriptions by their IDs. + * + * Retrieves detailed information about specific tools when you already + * know their tool_ids. Useful for refreshing tool metadata or getting + * details for tools from previous searches. + * + * @param request - Request parameters with tool IDs + * @returns Tool information matching the provided IDs + * + * @example + * ```typescript + * const result = await client.getToolsByIds({ + * tool_ids: ['weather-tool-1', 'email-tool-2'], + * search_id: 'abcd1234-ab12-ab12-ab12-abcdef123456', + * session_id: 'abcd1234-ab12-ab12-ab12-abcdef123456' + * }); + * + * console.log(`Retrieved ${result.results.length} tools`); + * for (const tool of result.results) { + * console.log(`- ${tool.name}: ${tool.description}`); + * } + * ``` + */ + async getToolsByIds(request: GetToolsByIdsRequest): Promise { + return this.request('POST', '/tools/by-ids', request); + } + /** * Execute a tool with the specified parameters. * @@ -152,8 +181,8 @@ export class QverisClient { * @example * ```typescript * const result = await client.executeTool('openweathermap_current', { - * search_id: 'search-abc123', - * session_id: 'user-123', + * search_id: 'abcd1234-ab12-ab12-ab12-abcdef123456', + * session_id: 'abcd1234-ab12-ab12-ab12-abcdef123456', * parameters: { * city: 'Tokyo', * units: 'metric' diff --git a/src/index.ts b/src/index.ts index dfb4ac5..0034cd0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,11 @@ import { executeExecuteTool, type ExecuteToolInput, } from './tools/execute.js'; +import { + getToolsByIdsSchema, + executeGetToolsByIds, + type GetToolsByIdsInput, +} from './tools/get-by-ids.js'; import type { ApiError } from './types.js'; // ============================================================================ @@ -93,7 +98,7 @@ async function main(): Promise { /** * Lists available tools. - * Returns the search_tools and execute_tool definitions. + * Returns the search_tools, get_tools_by_ids, and execute_tool definitions. */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { @@ -106,6 +111,14 @@ async function main(): Promise { 'Use this to discover tools before executing them.', inputSchema: searchToolsSchema, }, + { + name: 'get_tools_by_ids', + description: + 'Get descriptions of tools based on their tool IDs. ' + + 'Useful for retrieving detailed information about specific tools ' + + 'when you already know their tool_ids from previous searches.', + inputSchema: getToolsByIdsSchema, + }, { name: 'execute_tool', description: @@ -157,6 +170,37 @@ async function main(): Promise { }; } + if (name === 'get_tools_by_ids') { + const input = args as unknown as GetToolsByIdsInput; + + // Validate required fields + if (!input.tool_ids || !Array.isArray(input.tool_ids) || input.tool_ids.length === 0) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'Missing or invalid required parameter: tool_ids', + hint: 'Provide an array of tool IDs (at least one) to retrieve tool information', + }), + }, + ], + isError: true, + }; + } + + const result = await executeGetToolsByIds(client, input, defaultSessionId); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } + if (name === 'execute_tool') { const input = args as unknown as ExecuteToolInput; @@ -200,7 +244,7 @@ async function main(): Promise { type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}`, - available_tools: ['search_tools', 'execute_tool'], + available_tools: ['search_tools', 'get_tools_by_ids', 'execute_tool'], }), }, ], @@ -224,13 +268,19 @@ async function main(): Promise { }; } - // Handle other errors + // Handle other errors (including fetch network errors) + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + const errorCause = error instanceof Error && error.cause instanceof Error + ? error.cause.message + : undefined; + return { content: [ { type: 'text', text: JSON.stringify({ - error: error instanceof Error ? error.message : 'Unknown error occurred', + error: errorMessage, + ...(errorCause && { cause: errorCause }), }), }, ], diff --git a/src/tools/execute.test.ts b/src/tools/execute.test.ts index 00d76ba..7d8562c 100644 --- a/src/tools/execute.test.ts +++ b/src/tools/execute.test.ts @@ -27,9 +27,9 @@ describe('execute_tool', () => { expect(executeToolSchema.properties.params_to_tool.type).toBe('string'); }); - it('should define max_data_size with default', () => { - expect(executeToolSchema.properties.max_data_size.type).toBe('number'); - expect(executeToolSchema.properties.max_data_size.default).toBe(20480); + it('should define max_response_size with default', () => { + expect(executeToolSchema.properties.max_response_size.type).toBe('number'); + expect(executeToolSchema.properties.max_response_size.default).toBe(20480); }); it('should define session_id as optional', () => { @@ -79,7 +79,7 @@ describe('execute_tool', () => { search_id: 'search-123', session_id: 'default-session', parameters: { city: 'Tokyo', units: 'metric' }, - max_data_size: undefined, + max_response_size: undefined, }); expect(result).toEqual(mockResponse); @@ -109,11 +109,11 @@ describe('execute_tool', () => { search_id: 'search-123', session_id: 'custom-session', parameters: {}, - max_data_size: undefined, + max_response_size: undefined, }); }); - it('should pass max_data_size when provided', async () => { + it('should pass max_response_size when provided', async () => { executeToolMock.mockResolvedValueOnce({ execution_id: 'exec-123', tool_id: 'tool-1', @@ -128,7 +128,7 @@ describe('execute_tool', () => { tool_id: 'tool-1', search_id: 'search-123', params_to_tool: '{}', - max_data_size: 102400, + max_response_size: 102400, }, 'default-session' ); @@ -137,7 +137,7 @@ describe('execute_tool', () => { search_id: 'search-123', session_id: 'default-session', parameters: {}, - max_data_size: 102400, + max_response_size: 102400, }); }); @@ -201,7 +201,7 @@ describe('execute_tool', () => { search_id: 'search-123', session_id: 'default-session', parameters: complexParams, - max_data_size: undefined, + max_response_size: undefined, }); }); diff --git a/src/tools/execute.ts b/src/tools/execute.ts index dcec8c4..9317fe6 100644 --- a/src/tools/execute.ts +++ b/src/tools/execute.ts @@ -49,7 +49,7 @@ export interface ExecuteToolInput { * @default 20480 (20KB) * @minimum -1 (-1 means no limit) */ - max_data_size?: number; + max_response_size?: number; } /** @@ -83,7 +83,7 @@ export const executeToolSchema = { 'Session identifier for tracking user sessions. ' + 'If not provided, an auto-generated session ID will be used.', }, - max_data_size: { + max_response_size: { type: 'number', description: 'Maximum size of response data in bytes. ' + @@ -124,7 +124,7 @@ export async function executeExecuteTool( search_id: input.search_id, session_id: input.session_id ?? defaultSessionId, parameters, - max_data_size: input.max_data_size, + max_response_size: input.max_response_size, }); return response; diff --git a/src/tools/get-by-ids.test.ts b/src/tools/get-by-ids.test.ts new file mode 100644 index 0000000..bde0189 --- /dev/null +++ b/src/tools/get-by-ids.test.ts @@ -0,0 +1,220 @@ +/** + * Unit tests for the get_tools_by_ids MCP tool + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { executeGetToolsByIds, getToolsByIdsSchema } from './get-by-ids.js'; +import { QverisClient } from '../api/client.js'; +import type { SearchResponse } from '../types.js'; + +describe('get_tools_by_ids', () => { + describe('getToolsByIdsSchema', () => { + it('should have tool_ids as required parameter', () => { + expect(getToolsByIdsSchema.required).toContain('tool_ids'); + }); + + it('should define tool_ids as array of strings', () => { + expect(getToolsByIdsSchema.properties.tool_ids.type).toBe('array'); + expect(getToolsByIdsSchema.properties.tool_ids.items.type).toBe('string'); + expect(getToolsByIdsSchema.properties.tool_ids.minItems).toBe(1); + }); + + it('should define search_id as optional string', () => { + expect(getToolsByIdsSchema.properties.search_id.type).toBe('string'); + expect(getToolsByIdsSchema.required).not.toContain('search_id'); + }); + + it('should define session_id as optional string', () => { + expect(getToolsByIdsSchema.properties.session_id.type).toBe('string'); + expect(getToolsByIdsSchema.required).not.toContain('session_id'); + }); + }); + + describe('executeGetToolsByIds', () => { + let mockClient: QverisClient; + let getToolsByIdsMock: ReturnType; + + beforeEach(() => { + getToolsByIdsMock = vi.fn(); + mockClient = { + getToolsByIds: getToolsByIdsMock, + } as unknown as QverisClient; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should call client.getToolsByIds with correct parameters', async () => { + const mockResponse: SearchResponse = { + search_id: 'search-123', + results: [ + { + tool_id: 'weather-tool-1', + name: 'Weather API', + description: 'Get weather data', + }, + { + tool_id: 'email-tool-2', + name: 'Email Service', + description: 'Send emails', + }, + ], + total: 2, + }; + + getToolsByIdsMock.mockResolvedValueOnce(mockResponse); + + const result = await executeGetToolsByIds( + mockClient, + { + tool_ids: ['weather-tool-1', 'email-tool-2'], + search_id: 'search-123', + }, + 'default-session' + ); + + expect(getToolsByIdsMock).toHaveBeenCalledWith({ + tool_ids: ['weather-tool-1', 'email-tool-2'], + search_id: 'search-123', + session_id: 'default-session', + }); + + expect(result).toEqual(mockResponse); + }); + + it('should use default session_id when not provided', async () => { + getToolsByIdsMock.mockResolvedValueOnce({ + search_id: 'search-123', + results: [ + { + tool_id: 'tool-1', + name: 'Tool 1', + description: 'Description 1', + }, + ], + }); + + await executeGetToolsByIds( + mockClient, + { + tool_ids: ['tool-1'], + }, + 'default-session' + ); + + expect(getToolsByIdsMock).toHaveBeenCalledWith({ + tool_ids: ['tool-1'], + search_id: undefined, + session_id: 'default-session', + }); + }); + + it('should use provided session_id over default', async () => { + getToolsByIdsMock.mockResolvedValueOnce({ + search_id: 'search-123', + results: [], + }); + + await executeGetToolsByIds( + mockClient, + { + tool_ids: ['tool-1'], + session_id: 'custom-session', + }, + 'default-session' + ); + + expect(getToolsByIdsMock).toHaveBeenCalledWith({ + tool_ids: ['tool-1'], + search_id: undefined, + session_id: 'custom-session', + }); + }); + + it('should include search_id when provided', async () => { + getToolsByIdsMock.mockResolvedValueOnce({ + search_id: 'search-456', + results: [], + }); + + await executeGetToolsByIds( + mockClient, + { + tool_ids: ['tool-1', 'tool-2'], + search_id: 'search-456', + }, + 'default-session' + ); + + expect(getToolsByIdsMock).toHaveBeenCalledWith({ + tool_ids: ['tool-1', 'tool-2'], + search_id: 'search-456', + session_id: 'default-session', + }); + }); + + it('should handle single tool ID', async () => { + getToolsByIdsMock.mockResolvedValueOnce({ + search_id: 'search-123', + results: [ + { + tool_id: 'single-tool', + name: 'Single Tool', + description: 'A single tool', + }, + ], + total: 1, + }); + + const result = await executeGetToolsByIds( + mockClient, + { + tool_ids: ['single-tool'], + }, + 'default-session' + ); + + expect(result.results).toHaveLength(1); + expect(result.results[0].tool_id).toBe('single-tool'); + }); + + it('should handle multiple tool IDs', async () => { + const toolIds = ['tool-1', 'tool-2', 'tool-3', 'tool-4', 'tool-5']; + getToolsByIdsMock.mockResolvedValueOnce({ + search_id: 'search-123', + results: toolIds.map((id) => ({ + tool_id: id, + name: `Tool ${id}`, + description: `Description for ${id}`, + })), + total: toolIds.length, + }); + + const result = await executeGetToolsByIds( + mockClient, + { + tool_ids: toolIds, + }, + 'default-session' + ); + + expect(result.results).toHaveLength(5); + expect(result.total).toBe(5); + }); + + it('should propagate errors from client', async () => { + const error = { status: 404, message: 'Tool not found' }; + getToolsByIdsMock.mockRejectedValueOnce(error); + + await expect( + executeGetToolsByIds( + mockClient, + { tool_ids: ['non-existent-tool'] }, + 'session' + ) + ).rejects.toEqual(error); + }); + }); +}); + diff --git a/src/tools/get-by-ids.ts b/src/tools/get-by-ids.ts new file mode 100644 index 0000000..0e18dce --- /dev/null +++ b/src/tools/get-by-ids.ts @@ -0,0 +1,93 @@ +/** + * get_tools_by_ids MCP Tool Implementation + * + * Retrieves tool descriptions based on tool IDs. + * Useful for getting detailed information about specific tools + * when you already know their tool_ids. + * + * @module tools/get-by-ids + */ + +import type { QverisClient } from '../api/client.js'; +import type { SearchResponse } from '../types.js'; + +/** + * Input parameters for the get_tools_by_ids tool. + */ +export interface GetToolsByIdsInput { + /** + * Array of tool IDs to retrieve information for. + * Must be obtained from previous search_tools results. + * + * @example ["weather-tool-1", "email-tool-2"] + */ + tool_ids: string[]; + + /** + * The search_id from the search_tools response that returned the tool(s). + * Optional but recommended for analytics and billing. + */ + search_id?: string; + + /** + * Session identifier for tracking user sessions. + * If not provided, the server will use an auto-generated session ID. + */ + session_id?: string; +} + +/** + * JSON Schema for the get_tools_by_ids tool input. + * Used by MCP to validate and document the tool parameters. + */ +export const getToolsByIdsSchema = { + type: 'object' as const, + properties: { + tool_ids: { + type: 'array', + items: { + type: 'string', + }, + description: + 'Array of tool IDs to retrieve information for. ' + + 'These IDs should come from previous search_tools results.', + minItems: 1, + }, + search_id: { + type: 'string', + description: + 'The search_id from the search_tools response that returned the tool(s). ' + + 'Optional but recommended for linking to the original search.', + }, + session_id: { + type: 'string', + description: + 'Session identifier for tracking user sessions. ' + + 'If not provided, an auto-generated session ID will be used.', + }, + }, + required: ['tool_ids'], +}; + +/** + * Executes the get_tools_by_ids operation. + * + * @param client - Initialized Qveris API client + * @param input - Request parameters with tool IDs + * @param defaultSessionId - Fallback session ID if not provided in input + * @returns Tool information for the requested IDs + */ +export async function executeGetToolsByIds( + client: QverisClient, + input: GetToolsByIdsInput, + defaultSessionId: string +): Promise { + const response = await client.getToolsByIds({ + tool_ids: input.tool_ids, + search_id: input.search_id, + session_id: input.session_id ?? defaultSessionId, + }); + + return response; +} + diff --git a/src/types.ts b/src/types.ts index 5105b90..0c064b2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,7 +20,7 @@ * const request: SearchRequest = { * query: "weather forecast API", * limit: 10, - * session_id: "user-session-123" + * session_id: "abcd1234-ab12-ab12-ab12-abcdef123456" * }; * ``` */ @@ -145,6 +145,41 @@ export interface SearchResponse { stats?: SearchStats; } +// ============================================================================ +// Get Tools by IDs API Types +// ============================================================================ + +/** + * Request body for the Get Tools by IDs API. + * + * @example + * ```typescript + * const request: GetToolsByIdsRequest = { + * tool_ids: ["tool-1", "tool-2"], + * search_id: "search-123", + * session_id: "abcd1234-ab12-ab12-ab12-abcdef123456" + * }; + * ``` + */ +export interface GetToolsByIdsRequest { + /** + * Array of tool IDs to retrieve information for. + */ + tool_ids: string[]; + + /** + * The search_id from the search that returned the tool(s). + * Optional but recommended for analytics and billing. + */ + search_id?: string; + + /** + * Session identifier for tracking user sessions. + * Same ID corresponds to the same user session. + */ + session_id?: string; +} + // ============================================================================ // Execute API Types // ============================================================================ @@ -155,10 +190,10 @@ export interface SearchResponse { * @example * ```typescript * const request: ExecuteRequest = { - * search_id: "abc123", - * session_id: "user-session-123", + * search_id: "abcd1234-ab12-ab12-ab12-abcdef123456", + * session_id: "abcd1234-ab12-ab12-ab12-abcdef123456", * parameters: { city: "London", units: "metric" }, - * max_data_size: 20480 + * max_response_size: 20480 * }; * ``` */ @@ -188,11 +223,11 @@ export interface ExecuteRequest { * @default 20480 (20KB) * @minimum -1 (-1 means no limit) */ - max_data_size?: number; + max_response_size?: number; } /** - * Result data when the response fits within max_data_size. + * Result data when the response fits within max_response_size. */ export interface ExecuteResultData { /** The actual result data from the tool execution */ @@ -200,7 +235,7 @@ export interface ExecuteResultData { } /** - * Result data when the response exceeds max_data_size. + * Result data when the response exceeds max_response_size. * Provides truncated content and a URL to download the full result. */ export interface ExecuteResultTruncated { @@ -214,7 +249,7 @@ export interface ExecuteResultTruncated { full_content_file_url: string; /** - * The initial portion of the response (max_data_size bytes). + * The initial portion of the response (max_response_size bytes). * Useful for previewing the data structure. */ truncated_content: string;