diff --git a/.eslintcache b/.eslintcache deleted file mode 100644 index fa0627a5..00000000 --- a/.eslintcache +++ /dev/null @@ -1 +0,0 @@ -[{"/home/tempuser-2eudnk/run/codegen-output/writerai/writer-typescript/src/resources/tools/comprehend.ts":"1","/home/tempuser-2eudnk/run/codegen-output/writerai/writer-typescript/src/resources/tools/tools.ts":"2","/home/tempuser-2eudnk/run/codegen-output/writerai/writer-typescript/src/resources/translation.ts":"3"},{"size":2779,"mtime":1759270331968},{"size":9476,"mtime":1759270331968},{"size":5973,"mtime":1759270331968}] \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8d17a8e5..f42cb631 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.3.2" + ".": "2.3.3-rc.1" } diff --git a/.stats.yml b/.stats.yml index b3d54722..33ea12a4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 33 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-4ec783072dd7f57c6e021a746df7650fb8d7a164d8ec25c7d5cab06c33bc114f.yml -openapi_spec_hash: ceab065d515f3681b0c33137da308968 -config_hash: 089fd5502b9cf91247887b19117f1ca2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-ea6ec4b34f6b7fdecc564f59b2e31482eee05830bf8dc1f389461b158de1548e.yml +openapi_spec_hash: ea89c1faed473908be2740efe6da255f +config_hash: 886645f89dc98f04b8931eaf02854e5f diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f765c36..4ac567af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 2.3.3-rc.1 (2025-12-01) + +Full Changelog: [v2.3.2...v2.3.3-rc.1](https://github.com/writer/writer-node/compare/v2.3.2...v2.3.3-rc.1) + +### Features + +* **api:** Improve MCP tool descriptions ([95aa714](https://github.com/writer/writer-node/commit/95aa71460549717420896fd47fcebbd603739743)) +* **mcp:** add detail field to docs search tool ([0c975f7](https://github.com/writer/writer-node/commit/0c975f793c10f8f047c477ecde35f8e358b95e91)) +* **mcp:** enable optional code execution tool on http mcp servers ([cad37b7](https://github.com/writer/writer-node/commit/cad37b70c93d26bdafdfdf6047e9591a1b27376e)) + + +### Bug Fixes + +* **client:** incorrect offset pagination check ([b7847ab](https://github.com/writer/writer-node/commit/b7847abd4a65c07090855fd674c6bd4ff15b9760)) +* Fix linting errors. ([#238](https://github.com/writer/writer-node/issues/238)) ([12f49e6](https://github.com/writer/writer-node/commit/12f49e62a666d57a934537bf8f25cef1338b3e27)) +* **mcpb:** pin @anthropic-ai/mcpb version ([43acbce](https://github.com/writer/writer-node/commit/43acbcefa5cdbabe670ef4a5687ef871a3acf9f6)) +* **mcp:** return tool execution error on jq failure ([f833b8f](https://github.com/writer/writer-node/commit/f833b8ffb35c9921f66939cafab7ddc1a6948096)) +* **mcp:** use raw responses for binary content ([54da4f9](https://github.com/writer/writer-node/commit/54da4f964cae97a224763a29e851c4c63273953e)) + + +### Chores + +* **client:** fix logger property type ([20209b8](https://github.com/writer/writer-node/commit/20209b8c6037de89117b4f9b0e690721380e2dbc)) +* extract some types in mcp docs ([de1e150](https://github.com/writer/writer-node/commit/de1e15096bb097df00cdee211b752487d364d977)) +* **internal:** codegen related update ([35b1a8f](https://github.com/writer/writer-node/commit/35b1a8fd3128b36cb3a99025547b3484e57ae318)) +* **internal:** codegen related update ([5e30270](https://github.com/writer/writer-node/commit/5e3027034d15d23909742ac7a65d5e1db6709631)) +* **internal:** grammar fix (it's -> its) ([7dfbe17](https://github.com/writer/writer-node/commit/7dfbe171aa29752dd3e42e15d045fde008d6e96c)) +* **internal:** remove .eslintcache ([f75e301](https://github.com/writer/writer-node/commit/f75e3010fb346dde47038f0e876549bc6eba301f)) +* **internal:** use npm pack for build uploads ([38c37a1](https://github.com/writer/writer-node/commit/38c37a1cfc2af919b9833344716ff5172a5076b3)) +* **jsdoc:** fix [@link](https://github.com/link) annotations to refer only to parts of the packageā€˜s public interface ([d227de6](https://github.com/writer/writer-node/commit/d227de61034910da49481c6ff488436fcfed61fd)) +* mcp code tool explicit error message when missing a run function ([16c1a44](https://github.com/writer/writer-node/commit/16c1a446e5f1cf853582afe76bbc21739855ca70)) +* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([e6a4a0c](https://github.com/writer/writer-node/commit/e6a4a0c0936a215c9aed87365108b3bcf59303fd)) +* **mcp:** add line numbers to code tool errors ([00af445](https://github.com/writer/writer-node/commit/00af445b63b9a0db2fb59202f7ed3c4d34887990)) +* **mcp:** clarify http auth error ([bd123e0](https://github.com/writer/writer-node/commit/bd123e075e8bd0b179f95d8f8513a53414f86ac6)) +* **mcp:** upgrade jq-web ([9a0ac58](https://github.com/writer/writer-node/commit/9a0ac581162b2c59792d493e17cbe9d9cb0b5f60)) +* use structured error when code execution tool errors ([e140047](https://github.com/writer/writer-node/commit/e140047e470588b4c18bccbdb752078f198d8374)) + + +### Documentation + +* **api:** updates to API spec ([69b36c9](https://github.com/writer/writer-node/commit/69b36c97a708452f05522c9b81f0855fea0daadb)) +* **mcp:** add a README button for one-click add to Cursor ([062b7d7](https://github.com/writer/writer-node/commit/062b7d73c91530ea083872b67036a77265db9a8d)) +* **mcp:** add a README link to add server to VS Code or Claude Code ([bb1991a](https://github.com/writer/writer-node/commit/bb1991af52bb810f9b69ec7d9d761d8b79aa6b15)) + ## 2.3.2 (2025-10-03) Full Changelog: [v2.3.2-rc.2...v2.3.2](https://github.com/writer/writer-node/compare/v2.3.2-rc.2...v2.3.2) diff --git a/package.json b/package.json index d8129e9d..33f406b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "writer-sdk", - "version": "2.3.2", + "version": "2.3.3-rc.1", "description": "The official TypeScript library for the Writer API", "author": "Writer ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 1f3c8de9..b212d1a2 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -34,12 +34,36 @@ For clients with a configuration JSON, it might look something like this: } ``` +### Cursor + +If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables +in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=writer-sdk-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIndyaXRlci1zZGstbWNwIl0sImVudiI6eyJXUklURVJfQVBJX0tFWSI6IlNldCB5b3VyIFdSSVRFUl9BUElfS0VZIGhlcmUuIn19) + +### VS Code + +If you use MCP, you can install the MCP server by clicking the link below. You will need to set your environment variables +in VS Code's `mcp.json`, which can be found via Command Palette > MCP: Open User Configuration. + +[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22writer-sdk-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22writer-sdk-mcp%22%5D%2C%22env%22%3A%7B%22WRITER_API_KEY%22%3A%22Set%20your%20WRITER_API_KEY%20here.%22%7D%7D) + +### Claude Code + +If you use Claude Code, you can install the MCP server by running the command below in your terminal. You will need to set your +environment variables in Claude Code's `.claude.json`, which can be found in your home directory. + +``` +claude mcp add --transport stdio writer_sdk_api --env WRITER_API_KEY="Your WRITER_API_KEY here." -- npx -y writer-sdk-mcp +``` + ## Exposing endpoints to your MCP Client -There are two ways to expose endpoints as tools in the MCP server: +There are three ways to expose endpoints as tools in the MCP server: 1. Exposing one tool per endpoint, and filtering as necessary 2. Exposing a set of tools to dynamically discover and invoke endpoints from the API +3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client ### Filtering endpoints and tools @@ -74,6 +98,18 @@ All of these command-line options can be repeated, combined together, and have c Use `--list` to see the list of available tools, or see below. +### Code execution + +If you specify `--tools=code` to the MCP server, it will expose just two tools: + +- `search_docs` - Searches the API documentation and returns a list of markdown results +- `execute` - Runs code against the TypeScript client + +This allows the LLM to implement more complex logic by chaining together many API calls without loading +intermediary results into its context window. + +The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API. + ### Specifying the MCP Client Different clients have varying abilities to handle arbitrary tools and schemas. @@ -207,8 +243,8 @@ The following tools are available in this MCP server. ### Resource `applications`: - `retrieve_applications` (`read`): Retrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status. -- `list_applications` (`read`): Retrieves a paginated list of no-code agents (formerly called no-code applications) with optional filtering and sorting capabilities. -- `generate_content_applications` (`write`): Generate content from an existing no-code agent (formerly called no-code applications) with inputs. +- `list_applications` (`read`): Get all available no-code agents (applications) in your account. No-code agents are pre-configured AI workflows built in Writer's AI Studio. Use this to discover which agents are available before generating content from them. +- `generate_content_applications` (`write`): Generate content using a pre-configured no-code agent. No-code agents are custom AI workflows you've built in AI Studio with specific prompts, models, and settings. Provide the application ID and required inputs to get tailored content. Useful for consistent, repeatable AI tasks like content generation, data extraction, or custom workflows. ### Resource `applications.jobs`: @@ -224,11 +260,11 @@ The following tools are available in this MCP server. ### Resource `chat`: -- `chat_chat` (`write`): Generate a chat completion based on the provided messages. The response shown below is for non-streaming. To learn about streaming responses, see the [chat completion guide](https://dev.writer.com/home/chat-completion). +- `chat_chat` (`write`): Generate AI responses for conversational interactions. Use this for chat-based tasks, Q&A, content generation, and any natural language processing. Supports tools like Knowledge Graphs, web search, translation, and vision. Choose from models like palmyra-x5, palmyra-x4, palmyra-creative, palmyra-med, or palmyra-fin depending on the task. ### Resource `completions`: -- `create_completions` (`write`): Generate text completions using the specified model and prompt. This endpoint is useful for text generation tasks that don't require conversational context. +- `create_completions` (`write`): Generate text completions from a single prompt without conversational context. Best for straightforward text generation tasks like article writing, summaries, or creative content. For interactive conversations or multi-turn dialogues, use generate-chat-completion instead. ### Resource `models`: @@ -236,23 +272,23 @@ The following tools are available in this MCP server. ### Resource `graphs`: -- `create_graphs` (`write`): Create a new Knowledge Graph. -- `retrieve_graphs` (`read`): Retrieve a Knowledge Graph. +- `create_graphs` (`write`): Create a new Knowledge Graph to organize and query documents. Knowledge Graphs are containers for files that enable AI-powered search and question answering. After creation, add files to the graph using add-file-to-graph, then query it using query-knowledge-graph. +- `retrieve_graphs` (`read`): Get detailed information about a specific Knowledge Graph by its ID. Returns the graph name, description, creation date, file processing status, and associated URLs (for web-based graphs). Use this to check processing status or get graph metadata. - `update_graphs` (`write`): Update the name and description of a Knowledge Graph. -- `list_graphs` (`read`): Retrieve a list of Knowledge Graphs. +- `list_graphs` (`read`): Get all available Knowledge Graphs in your account. Knowledge Graphs are collections of documents and files that can be queried using AI. Use this to discover which knowledge bases are available before querying them. - `delete_graphs` (`write`): Delete a Knowledge Graph. -- `add_file_to_graph_graphs` (`write`): Add a file to a Knowledge Graph. -- `question_graphs` (`write`): Ask a question to specified Knowledge Graphs. +- `add_file_to_graph_graphs` (`write`): Add an uploaded file to a Knowledge Graph to make it queryable. The file must already be uploaded using upload-file. Once added, the file's content becomes searchable when querying the Knowledge Graph. Files are processed asynchronously - check status using get-file-info. +- `question_graphs` (`write`): Ask questions and get AI-generated answers based on your Knowledge Graph content. Queries your uploaded documents, PDFs, and files to retrieve accurate, source-cited information. Returns answers with supporting snippets and file references. Ideal for RAG (Retrieval-Augmented Generation) applications and knowledge base queries. - `remove_file_from_graph_graphs` (`write`): Remove a file from a Knowledge Graph. ### Resource `files`: -- `retrieve_files` (`read`): Retrieve detailed information about a specific file, including its metadata, status, and associated graphs. -- `list_files` (`read`): Retrieve a paginated list of files with optional filtering by status, graph association, and file type. +- `retrieve_files` (`read`): Get metadata and status information for a specific file by its ID. Returns file name, creation date, processing status, and associated Knowledge Graph IDs. Use this to check if a file has finished processing or to find which Knowledge Graphs contain a specific file. +- `list_files` (`read`): Get a paginated list of all uploaded files. Filter by processing status (in_progress, completed, failed), Knowledge Graph association, or file type. Use this to discover available files, monitor processing status, or find files to add to Knowledge Graphs. - `delete_files` (`write`): Permanently delete a file from the system. This action cannot be undone. - `download_files` (`read`): Download the binary content of a file. The response will contain the file data in the appropriate MIME type. - `retry_files` (`write`): Retry processing of files that previously failed to process. This will re-attempt the processing of the specified files. -- `upload_files` (`write`): Upload a new file to the system. Supports various file formats including PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, and XLSX. +- `upload_files` (`write`): Upload documents and files to Writer. Supports PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, XLSX, MP3, and MP4 formats. Once uploaded, files can be added to Knowledge Graphs for querying or used with Vision API for image analysis. Returns a file ID for subsequent operations. ### Resource `tools`: @@ -271,4 +307,4 @@ The following tools are available in this MCP server. ### Resource `vision`: -- `analyze_vision` (`write`): Submit images and a prompt to generate an analysis of the images. +- `analyze_vision` (`write`): Submit images and documents with a prompt to generate an analysis. Supports JPG, PNG, PDF, and TXT files up to 7MB each. diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 73f08781..f081e3c6 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "writer-sdk-mcp", - "version": "2.3.2", + "version": "2.3.3-rc.1", "description": "The official MCP Server for the Writer API", "author": "Writer ", "types": "dist/index.d.ts", @@ -36,8 +36,10 @@ "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", - "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "fuse.js": "^7.1.0", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz", "qs": "^6.14.0", + "typescript": "5.8.3", "yargs": "^17.7.2", "zod": "^3.25.20", "zod-to-json-schema": "^3.24.5", @@ -47,7 +49,7 @@ "mcp-server": "dist/index.js" }, "devDependencies": { - "@anthropic-ai/mcpb": "^1.1.0", + "@anthropic-ai/mcpb": "1.1.0", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/jest": "^29.4.0", @@ -64,8 +66,7 @@ "ts-morph": "^19.0.0", "ts-node": "^10.5.0", "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "typescript": "5.8.3" + "tsconfig-paths": "^4.0.0" }, "imports": { "writer-sdk-mcp": ".", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 2995c75c..1c458202 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,11 +1,196 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import util from 'node:util'; + +import Fuse from 'fuse.js'; +import ts from 'typescript'; + import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; import { Writer } from 'writer-sdk'; +function getRunFunctionNode( + code: string, +): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { + const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + + for (const statement of sourceFile.statements) { + // Check for top-level function declarations + if (ts.isFunctionDeclaration(statement)) { + if (statement.name?.text === 'run') { + return statement; + } + } + + // Check for variable declarations: const run = () => {} or const run = function() {} + if (ts.isVariableStatement(statement)) { + for (const declaration of statement.declarationList.declarations) { + if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') { + // Check if it's initialized with a function + if ( + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return declaration.initializer; + } + } + } + } + } + + return null; +} + +const fuse = new Fuse( + [ + 'client.applications.generateContent', + 'client.applications.list', + 'client.applications.retrieve', + 'client.applications.jobs.create', + 'client.applications.jobs.list', + 'client.applications.jobs.retrieve', + 'client.applications.jobs.retry', + 'client.applications.graphs.list', + 'client.applications.graphs.update', + 'client.chat.chat', + 'client.completions.create', + 'client.models.list', + 'client.graphs.addFileToGraph', + 'client.graphs.create', + 'client.graphs.delete', + 'client.graphs.list', + 'client.graphs.question', + 'client.graphs.removeFileFromGraph', + 'client.graphs.retrieve', + 'client.graphs.update', + 'client.files.delete', + 'client.files.download', + 'client.files.list', + 'client.files.retrieve', + 'client.files.retry', + 'client.files.upload', + 'client.tools.aiDetect', + 'client.tools.contextAwareSplitting', + 'client.tools.parsePdf', + 'client.tools.webSearch', + 'client.tools.comprehend.medical', + 'client.translation.translate', + 'client.vision.analyze', + ], + { threshold: 1, shouldSort: true }, +); + +function getMethodSuggestions(fullyQualifiedMethodName: string): string[] { + return fuse + .search(fullyQualifiedMethodName) + .map(({ item }) => item) + .slice(0, 5); +} + +const proxyToObj = new WeakMap(); +const objToProxy = new WeakMap(); + +type ClientProxyConfig = { + path: string[]; + isBelievedBad?: boolean; +}; + +function makeSdkProxy(obj: T, { path, isBelievedBad = false }: ClientProxyConfig): T { + let proxy: T = objToProxy.get(obj); + + if (!proxy) { + proxy = new Proxy(obj, { + get(target, prop, receiver) { + const propPath = [...path, String(prop)]; + const value = Reflect.get(target, prop, receiver); + + if (isBelievedBad || (!(prop in target) && value === undefined)) { + // If we're accessing a path that doesn't exist, it will probably eventually error. + // Let's proxy it and mark it bad so that we can control the error message. + // We proxy an empty class so that an invocation or construction attempt is possible. + return makeSdkProxy(class {}, { path: propPath, isBelievedBad: true }); + } + + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + return makeSdkProxy(value, { path: propPath, isBelievedBad }); + } + + return value; + }, + + apply(target, thisArg, args) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a function. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.apply(target, proxyToObj.get(thisArg) ?? thisArg, args); + }, + + construct(target, args, newTarget) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a constructor. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.construct(target, args, newTarget); + }, + }); + + objToProxy.set(obj, proxy); + proxyToObj.set(proxy, obj); + } + + return proxy; +} + +function parseError(code: string, error: unknown): string | undefined { + if (!(error instanceof Error)) return; + const message = error.name ? `${error.name}: ${error.message}` : error.message; + try { + // Deno uses V8; the first ":LINE:COLUMN" is the top of stack. + const lineNumber = error.stack?.match(/:([0-9]+):[0-9]+/)?.[1]; + // -1 for the zero-based indexing + const line = + lineNumber && + code + .split('\n') + .at(parseInt(lineNumber, 10) - 1) + ?.trim(); + return line ? `${message}\n at line ${lineNumber}\n ${line}` : message; + } catch { + return message; + } +} + const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; + if (code == null) { + return Response.json( + { + message: + 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + + const runFunctionNode = getRunFunctionNode(code); + if (!runFunctionNode) { + return Response.json( + { + message: + 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + const client = new Writer({ ...opts, }); @@ -22,21 +207,17 @@ const fetch = async (req: Request): Promise => { }; try { let run_ = async (client: any) => {}; - eval(` - ${code} - run_ = run; - `); - const result = await run_(client); + eval(`${code}\nrun_ = run;`); + const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, logLines, errLines, } satisfies WorkerSuccess); } catch (e) { - const message = e instanceof Error ? e.message : undefined; return Response.json( { - message, + message: parseError(code, e), } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 3b16d71c..d7cd783d 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -3,7 +3,7 @@ import { dirname } from 'node:path'; import { pathToFileURL } from 'node:url'; import Writer, { ClientOptions } from 'writer-sdk'; -import { Endpoint, ContentBlock, Metadata } from './tools/types'; +import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; @@ -12,7 +12,7 @@ import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; /** * A tool that runs code against a copy of the SDK. * - * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then * a generic endpoint that can be used to invoke any endpoint with the provided arguments. * @@ -23,7 +23,7 @@ export async function codeTool(): Promise { const tool: Tool = { name: 'execute', description: - 'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; @@ -31,7 +31,7 @@ export async function codeTool(): Promise { const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); const { workerPath } = await import('./code-tool-paths.cjs'); - const handler = async (client: Writer, args: unknown) => { + const handler = async (client: Writer, args: unknown): Promise => { const baseURLHostname = new URL(client.baseURL).hostname; const { code } = args as { code: string }; @@ -97,7 +97,7 @@ export async function codeTool(): Promise { } satisfies WorkerInput); req.write(body, (err) => { - if (err !== null && err !== undefined) { + if (err != null) { reject(err); } }); @@ -108,12 +108,12 @@ export async function codeTool(): Promise { if (resp.status === 200) { const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; const returnOutput: ContentBlock | null = - result === null ? null - : result === undefined ? null - : { + result == null ? null : ( + { type: 'text', - text: typeof result === 'string' ? (result as string) : JSON.stringify(result), - }; + text: typeof result === 'string' ? result : JSON.stringify(result), + } + ); const logOutput: ContentBlock | null = logLines.length === 0 ? null @@ -133,10 +133,11 @@ export async function codeTool(): Promise { }; } else { const { message } = (await resp.json()) as WorkerError; - throw new Error(message); + return { + content: message == null ? [] : [{ type: 'text', text: message }], + isError: true, + }; } - } catch (e) { - throw e; } finally { worker.terminate(); } diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 4ec0c2c8..5bded3fb 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -13,8 +13,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'search_docs', - description: - 'Search for documentation for how to use the client to interact with the API.\nThe tool will return an array of Markdown-formatted documentation pages.', + description: 'Search for documentation for how to use the client to interact with the API.', inputSchema: { type: 'object', properties: { @@ -25,7 +24,12 @@ export const tool: Tool = { language: { type: 'string', description: 'The language for the SDK to search for.', - enum: ['http', 'python', 'go', 'typescript', 'terraform', 'ruby', 'java', 'kotlin'], + enum: ['http', 'python', 'go', 'typescript', 'javascript', 'terraform', 'ruby', 'java', 'kotlin'], + }, + detail: { + type: 'string', + description: 'The amount of detail to return.', + enum: ['default', 'verbose'], }, }, required: ['query', 'language'], diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts index 88303f0d..a35da358 100644 --- a/packages/mcp-server/src/dynamic-tools.ts +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -14,7 +14,7 @@ function zodToInputSchema(schema: z.ZodSchema) { /** * A list of tools that expose all the endpoints in the API dynamically. * - * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then * a generic endpoint that can be used to invoke any endpoint with the provided arguments. * diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts index 1aa9a40c..eaae0fcf 100644 --- a/packages/mcp-server/src/filtering.ts +++ b/packages/mcp-server/src/filtering.ts @@ -12,3 +12,7 @@ export async function maybeFilter(jqFilter: unknown | undefined, response: any): async function jq(json: any, jqFilter: string) { return (await initJq).json(json, jqFilter); } + +export function isJqError(error: any): error is Error { + return error instanceof Error && 'stderr' in error; +} diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts index db114003..555715b7 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/headers.ts @@ -11,7 +11,9 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = case 'Bearer': return { apiKey: req.headers.authorization.slice('Bearer '.length) }; default: - throw new Error(`Unsupported authorization scheme`); + throw new Error( + 'Unsupported authorization scheme. Expected the "Authorization" header to be a supported scheme (Bearer).', + ); } } diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index ec34ab47..84517003 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -46,12 +46,12 @@ const newServer = ({ }, mcpOptions, }); - } catch { + } catch (error) { res.status(401).json({ jsonrpc: '2.0', error: { code: -32000, - message: 'Unauthorized', + message: `Unauthorized: ${error instanceof Error ? error.message : error}`, }, }); return null; diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 4fe3b987..b6ff5976 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -284,8 +284,10 @@ const coerceArray = (zodType: T) => ); const QueryOptions = z.object({ - tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Use dynamic tools or all tools'), - no_tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Do not use dynamic tools or all tools'), + tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe( + 'Specify which MCP tools to not use.', + ), tool: coerceArray(z.string()).describe('Include tools matching the specified names'), resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), operation: coerceArray(z.enum(['read', 'write'])).describe( @@ -385,11 +387,16 @@ export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): M : queryOptions.tools?.includes('docs') ? true : defaultOptions.includeDocsTools; + let codeTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false + : queryOptions.tools?.includes('code') && defaultOptions.includeCodeTools ? true + : defaultOptions.includeCodeTools; + return { client: queryOptions.client ?? defaultOptions.client, includeDynamicTools: dynamicTools, includeAllTools: allTools, - includeCodeTools: undefined, + includeCodeTools: codeTools, includeDocsTools: docsTools, filters, capabilities: clientCapabilities, diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 42e71b12..4ec8edfa 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -34,7 +34,7 @@ export const newMcpServer = () => new McpServer( { name: 'writer_sdk_api', - version: '2.3.2', + version: '2.3.3-rc.1', }, { capabilities: { tools: {}, logging: {} } }, ); diff --git a/packages/mcp-server/src/tools/applications/generate-content-applications.ts b/packages/mcp-server/src/tools/applications/generate-content-applications.ts index 96d103bc..6fd4a72b 100644 --- a/packages/mcp-server/src/tools/applications/generate-content-applications.ts +++ b/packages/mcp-server/src/tools/applications/generate-content-applications.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'generate_content_applications', 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\nGenerate content from an existing no-code agent (formerly called no-code applications) with inputs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_generate_content_response',\n $defs: {\n application_generate_content_response: {\n type: 'object',\n title: 'generate_application_response',\n properties: {\n suggestion: {\n type: 'string',\n description: 'The response from the model specified in the application.'\n },\n title: {\n type: 'string',\n description: 'The name of the output field.'\n }\n },\n required: [ 'suggestion'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGenerate content using a pre-configured no-code agent. No-code agents are custom AI workflows you've built in AI Studio with specific prompts, models, and settings. Provide the application ID and required inputs to get tailored content. Useful for consistent, repeatable AI tasks like content generation, data extraction, or custom workflows.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_generate_content_response',\n $defs: {\n application_generate_content_response: {\n type: 'object',\n title: 'generate_application_response',\n properties: {\n suggestion: {\n type: 'string',\n description: 'The response from the model specified in the application.'\n },\n title: {\n type: 'string',\n description: 'The name of the output field.'\n }\n },\n required: [ 'suggestion'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', anyOf: [ @@ -113,9 +113,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { application_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.generateContent(application_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.applications.generateContent(application_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts b/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts index bb39401e..d0543c5f 100644 --- a/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts +++ b/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -40,9 +40,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { application_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.graphs.list(application_id)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.applications.graphs.list(application_id)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts b/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts index 4af1d43c..72625bcd 100644 --- a/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts +++ b/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -48,9 +48,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { application_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.graphs.update(application_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.applications.graphs.update(application_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts index af7c4a48..cd5a192b 100644 --- a/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_applications_jobs', 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\nGenerate content asynchronously from an existing no-code agent (formerly called no-code applications) with inputs.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'generate_application_async_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the async job created.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n }\n },\n required: [ 'id',\n 'created_at',\n 'status'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGenerate content asynchronously from an existing no-code agent (formerly called no-code applications) with inputs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_create_response',\n $defs: {\n job_create_response: {\n type: 'object',\n title: 'generate_application_async_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the async job created.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n }\n },\n required: [ 'id',\n 'created_at',\n 'status'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -62,9 +62,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { application_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.jobs.create(application_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.applications.jobs.create(application_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts index fe57f862..a04bcc9e 100644 --- a/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -55,7 +55,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { application_id, jq_filter, ...body } = args as any; const response = await client.applications.jobs.list(application_id, body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts index 44ae4d79..bc431b5d 100644 --- a/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -40,7 +40,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { job_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.applications.jobs.retrieve(job_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.applications.jobs.retrieve(job_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts index 26448256..f9b6f099 100644 --- a/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'retry_applications_jobs', 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\nRe-triggers the async execution of a single job previously created via the Async api and terminated in error.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'generate_application_async_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the async job created.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n }\n },\n required: [ 'id',\n 'created_at',\n 'status'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRe-triggers the async execution of a single job previously created via the Async api and terminated in error.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_retry_response',\n $defs: {\n job_retry_response: {\n type: 'object',\n title: 'generate_application_async_response',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier for the async job created.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the job was created.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n title: 'api_job_status',\n description: 'The status of the job.',\n enum: [ 'in_progress',\n 'failed',\n 'completed'\n ]\n }\n },\n required: [ 'id',\n 'created_at',\n 'status'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -38,7 +38,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { job_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.applications.jobs.retry(job_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.applications.jobs.retry(job_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/list-applications.ts b/packages/mcp-server/src/tools/applications/list-applications.ts index b768f6ee..9a2f628a 100644 --- a/packages/mcp-server/src/tools/applications/list-applications.ts +++ b/packages/mcp-server/src/tools/applications/list-applications.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_applications', 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\nRetrieves a paginated list of no-code agents (formerly called no-code applications) with optional filtering and sorting capabilities.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'get_applications_response',\n description: 'Response object containing a paginated list of applications.',\n properties: {\n data: {\n type: 'array',\n description: 'List of application objects with their configurations.',\n items: {\n type: 'object',\n title: 'application_with_inputs',\n description: 'Detailed application object including its input configuration.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the application.'\n },\n created_at: {\n type: 'string',\n description: 'Timestamp when the application was created.',\n format: 'date-time'\n },\n inputs: {\n type: 'array',\n description: 'List of input configurations for the application.',\n items: {\n type: 'object',\n title: 'application_input',\n description: 'Configuration for an individual input field in the application.',\n properties: {\n input_type: {\n type: 'string',\n title: 'application_input_type',\n description: 'Type of input field determining its behavior and validation rules.',\n enum: [ 'text',\n 'dropdown',\n 'file',\n 'media'\n ]\n },\n name: {\n type: 'string',\n description: 'Identifier for the input field.'\n },\n required: {\n type: 'boolean',\n description: 'Indicates if this input field is mandatory.'\n },\n description: {\n type: 'string',\n description: 'Human-readable description of the input field\\'s purpose.'\n },\n options: {\n anyOf: [ {\n type: 'object',\n title: 'Dropdown',\n description: 'Configuration options specific to dropdown-type input fields.',\n properties: {\n list: {\n type: 'array',\n description: 'List of available options in the dropdown menu.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'list'\n ]\n },\n {\n type: 'object',\n title: 'File',\n description: 'Configuration options specific to file upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed file extensions.',\n items: {\n type: 'string'\n }\n },\n max_file_size_mb: {\n type: 'integer',\n description: 'Maximum file size allowed in megabytes.'\n },\n max_files: {\n type: 'integer',\n description: 'Maximum number of files that can be uploaded.'\n },\n max_word_count: {\n type: 'integer',\n description: 'Maximum number of words allowed in text files.'\n },\n upload_types: {\n type: 'array',\n description: 'List of allowed upload types for file inputs.',\n items: {\n type: 'string',\n title: 'file_upload_type',\n description: 'Type of file upload method supported by the application.',\n enum: [ 'url',\n 'file_id'\n ]\n }\n }\n },\n required: [ 'file_types',\n 'max_file_size_mb',\n 'max_files',\n 'max_word_count',\n 'upload_types'\n ]\n },\n {\n type: 'object',\n title: 'Media',\n description: 'Configuration options specific to media upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed media file types.',\n items: {\n type: 'string'\n }\n },\n max_image_size_mb: {\n type: 'integer',\n description: 'Maximum media file size allowed in megabytes.'\n }\n },\n required: [ 'file_types',\n 'max_image_size_mb'\n ]\n },\n {\n type: 'object',\n title: 'Text',\n description: 'Configuration options specific to text input fields.',\n properties: {\n max_fields: {\n type: 'integer',\n description: 'Maximum number of text fields allowed.'\n },\n min_fields: {\n type: 'integer',\n description: 'Minimum number of text fields required.'\n }\n },\n required: [ 'max_fields',\n 'min_fields'\n ]\n }\n ],\n description: 'Type-specific configuration options for input fields.'\n }\n },\n required: [ 'input_type',\n 'name',\n 'required'\n ]\n }\n },\n name: {\n type: 'string',\n description: 'Display name of the application.'\n },\n status: {\n type: 'string',\n title: 'application_status',\n description: 'Current deployment status of the application. Note: currently only `deployed` applications are returned.',\n enum: [ 'deployed',\n 'draft'\n ]\n },\n type: {\n type: 'string',\n title: 'application_type',\n description: 'The type of no-code application.',\n enum: [ 'generation'\n ]\n },\n updated_at: {\n type: 'string',\n description: 'Timestamp when the application was last updated.',\n format: 'date-time'\n },\n last_deployed_at: {\n type: 'string',\n description: 'Timestamp when the application was last deployed.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'created_at',\n 'inputs',\n 'name',\n 'status',\n 'type',\n 'updated_at'\n ]\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more results available in subsequent pages.'\n },\n first_id: {\n type: 'string',\n description: 'UUID of the first application in the current page.'\n },\n last_id: {\n type: 'string',\n description: 'UUID of the last application in the current page.'\n }\n },\n required: [ 'data',\n 'has_more'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet all available no-code agents (applications) in your account. No-code agents are pre-configured AI workflows built in Writer's AI Studio. Use this to discover which agents are available before generating content from them.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'get_applications_response',\n description: 'Response object containing a paginated list of applications.',\n properties: {\n data: {\n type: 'array',\n description: 'List of application objects with their configurations.',\n items: {\n $ref: '#/$defs/application_list_response'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more results available in subsequent pages.'\n },\n first_id: {\n type: 'string',\n description: 'UUID of the first application in the current page.'\n },\n last_id: {\n type: 'string',\n description: 'UUID of the last application in the current page.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n application_list_response: {\n type: 'object',\n title: 'application_with_inputs',\n description: 'Detailed application object including its input configuration.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the application.'\n },\n created_at: {\n type: 'string',\n description: 'Timestamp when the application was created.',\n format: 'date-time'\n },\n inputs: {\n type: 'array',\n description: 'List of input configurations for the application.',\n items: {\n type: 'object',\n title: 'application_input',\n description: 'Configuration for an individual input field in the application.',\n properties: {\n input_type: {\n type: 'string',\n title: 'application_input_type',\n description: 'Type of input field determining its behavior and validation rules.',\n enum: [ 'text',\n 'dropdown',\n 'file',\n 'media'\n ]\n },\n name: {\n type: 'string',\n description: 'Identifier for the input field.'\n },\n required: {\n type: 'boolean',\n description: 'Indicates if this input field is mandatory.'\n },\n description: {\n type: 'string',\n description: 'Human-readable description of the input field\\'s purpose.'\n },\n options: {\n anyOf: [ {\n type: 'object',\n title: 'Dropdown',\n description: 'Configuration options specific to dropdown-type input fields.',\n properties: {\n list: {\n type: 'array',\n description: 'List of available options in the dropdown menu.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'list'\n ]\n },\n {\n type: 'object',\n title: 'File',\n description: 'Configuration options specific to file upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed file extensions.',\n items: {\n type: 'string'\n }\n },\n max_file_size_mb: {\n type: 'integer',\n description: 'Maximum file size allowed in megabytes.'\n },\n max_files: {\n type: 'integer',\n description: 'Maximum number of files that can be uploaded.'\n },\n max_word_count: {\n type: 'integer',\n description: 'Maximum number of words allowed in text files.'\n },\n upload_types: {\n type: 'array',\n description: 'List of allowed upload types for file inputs.',\n items: {\n type: 'string',\n title: 'file_upload_type',\n description: 'Type of file upload method supported by the application.',\n enum: [ 'url',\n 'file_id'\n ]\n }\n }\n },\n required: [ 'file_types',\n 'max_file_size_mb',\n 'max_files',\n 'max_word_count',\n 'upload_types'\n ]\n },\n {\n type: 'object',\n title: 'Media',\n description: 'Configuration options specific to media upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed media file types.',\n items: {\n type: 'string'\n }\n },\n max_image_size_mb: {\n type: 'integer',\n description: 'Maximum media file size allowed in megabytes.'\n }\n },\n required: [ 'file_types',\n 'max_image_size_mb'\n ]\n },\n {\n type: 'object',\n title: 'Text',\n description: 'Configuration options specific to text input fields.',\n properties: {\n max_fields: {\n type: 'integer',\n description: 'Maximum number of text fields allowed.'\n },\n min_fields: {\n type: 'integer',\n description: 'Minimum number of text fields required.'\n }\n },\n required: [ 'max_fields',\n 'min_fields'\n ]\n }\n ],\n description: 'Type-specific configuration options for input fields.'\n }\n },\n required: [ 'input_type',\n 'name',\n 'required'\n ]\n }\n },\n name: {\n type: 'string',\n description: 'Display name of the application.'\n },\n status: {\n type: 'string',\n title: 'application_status',\n description: 'Current deployment status of the application. Note: currently only `deployed` applications are returned.',\n enum: [ 'deployed',\n 'draft'\n ]\n },\n type: {\n type: 'string',\n title: 'application_type',\n description: 'The type of no-code application.',\n enum: [ 'generation'\n ]\n },\n updated_at: {\n type: 'string',\n description: 'Timestamp when the application was last updated.',\n format: 'date-time'\n },\n last_deployed_at: {\n type: 'string',\n description: 'Timestamp when the application was last deployed.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'created_at',\n 'inputs',\n 'name',\n 'status',\n 'type',\n 'updated_at'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -61,7 +61,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.applications.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/retrieve-applications.ts b/packages/mcp-server/src/tools/applications/retrieve-applications.ts index b8585cc4..a85120ec 100644 --- a/packages/mcp-server/src/tools/applications/retrieve-applications.ts +++ b/packages/mcp-server/src/tools/applications/retrieve-applications.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'retrieve_applications', 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\nRetrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'application_with_inputs',\n description: 'Detailed application object including its input configuration.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the application.'\n },\n created_at: {\n type: 'string',\n description: 'Timestamp when the application was created.',\n format: 'date-time'\n },\n inputs: {\n type: 'array',\n description: 'List of input configurations for the application.',\n items: {\n type: 'object',\n title: 'application_input',\n description: 'Configuration for an individual input field in the application.',\n properties: {\n input_type: {\n type: 'string',\n title: 'application_input_type',\n description: 'Type of input field determining its behavior and validation rules.',\n enum: [ 'text',\n 'dropdown',\n 'file',\n 'media'\n ]\n },\n name: {\n type: 'string',\n description: 'Identifier for the input field.'\n },\n required: {\n type: 'boolean',\n description: 'Indicates if this input field is mandatory.'\n },\n description: {\n type: 'string',\n description: 'Human-readable description of the input field\\'s purpose.'\n },\n options: {\n anyOf: [ {\n type: 'object',\n title: 'Dropdown',\n description: 'Configuration options specific to dropdown-type input fields.',\n properties: {\n list: {\n type: 'array',\n description: 'List of available options in the dropdown menu.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'list'\n ]\n },\n {\n type: 'object',\n title: 'File',\n description: 'Configuration options specific to file upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed file extensions.',\n items: {\n type: 'string'\n }\n },\n max_file_size_mb: {\n type: 'integer',\n description: 'Maximum file size allowed in megabytes.'\n },\n max_files: {\n type: 'integer',\n description: 'Maximum number of files that can be uploaded.'\n },\n max_word_count: {\n type: 'integer',\n description: 'Maximum number of words allowed in text files.'\n },\n upload_types: {\n type: 'array',\n description: 'List of allowed upload types for file inputs.',\n items: {\n type: 'string',\n title: 'file_upload_type',\n description: 'Type of file upload method supported by the application.',\n enum: [ 'url',\n 'file_id'\n ]\n }\n }\n },\n required: [ 'file_types',\n 'max_file_size_mb',\n 'max_files',\n 'max_word_count',\n 'upload_types'\n ]\n },\n {\n type: 'object',\n title: 'Media',\n description: 'Configuration options specific to media upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed media file types.',\n items: {\n type: 'string'\n }\n },\n max_image_size_mb: {\n type: 'integer',\n description: 'Maximum media file size allowed in megabytes.'\n }\n },\n required: [ 'file_types',\n 'max_image_size_mb'\n ]\n },\n {\n type: 'object',\n title: 'Text',\n description: 'Configuration options specific to text input fields.',\n properties: {\n max_fields: {\n type: 'integer',\n description: 'Maximum number of text fields allowed.'\n },\n min_fields: {\n type: 'integer',\n description: 'Minimum number of text fields required.'\n }\n },\n required: [ 'max_fields',\n 'min_fields'\n ]\n }\n ],\n description: 'Type-specific configuration options for input fields.'\n }\n },\n required: [ 'input_type',\n 'name',\n 'required'\n ]\n }\n },\n name: {\n type: 'string',\n description: 'Display name of the application.'\n },\n status: {\n type: 'string',\n title: 'application_status',\n description: 'Current deployment status of the application. Note: currently only `deployed` applications are returned.',\n enum: [ 'deployed',\n 'draft'\n ]\n },\n type: {\n type: 'string',\n title: 'application_type',\n description: 'The type of no-code application.',\n enum: [ 'generation'\n ]\n },\n updated_at: {\n type: 'string',\n description: 'Timestamp when the application was last updated.',\n format: 'date-time'\n },\n last_deployed_at: {\n type: 'string',\n description: 'Timestamp when the application was last deployed.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'created_at',\n 'inputs',\n 'name',\n 'status',\n 'type',\n 'updated_at'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieves detailed information for a specific no-code agent (formerly called no-code applications), including its configuration and current status.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/application_retrieve_response',\n $defs: {\n application_retrieve_response: {\n type: 'object',\n title: 'application_with_inputs',\n description: 'Detailed application object including its input configuration.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the application.'\n },\n created_at: {\n type: 'string',\n description: 'Timestamp when the application was created.',\n format: 'date-time'\n },\n inputs: {\n type: 'array',\n description: 'List of input configurations for the application.',\n items: {\n type: 'object',\n title: 'application_input',\n description: 'Configuration for an individual input field in the application.',\n properties: {\n input_type: {\n type: 'string',\n title: 'application_input_type',\n description: 'Type of input field determining its behavior and validation rules.',\n enum: [ 'text',\n 'dropdown',\n 'file',\n 'media'\n ]\n },\n name: {\n type: 'string',\n description: 'Identifier for the input field.'\n },\n required: {\n type: 'boolean',\n description: 'Indicates if this input field is mandatory.'\n },\n description: {\n type: 'string',\n description: 'Human-readable description of the input field\\'s purpose.'\n },\n options: {\n anyOf: [ {\n type: 'object',\n title: 'Dropdown',\n description: 'Configuration options specific to dropdown-type input fields.',\n properties: {\n list: {\n type: 'array',\n description: 'List of available options in the dropdown menu.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'list'\n ]\n },\n {\n type: 'object',\n title: 'File',\n description: 'Configuration options specific to file upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed file extensions.',\n items: {\n type: 'string'\n }\n },\n max_file_size_mb: {\n type: 'integer',\n description: 'Maximum file size allowed in megabytes.'\n },\n max_files: {\n type: 'integer',\n description: 'Maximum number of files that can be uploaded.'\n },\n max_word_count: {\n type: 'integer',\n description: 'Maximum number of words allowed in text files.'\n },\n upload_types: {\n type: 'array',\n description: 'List of allowed upload types for file inputs.',\n items: {\n type: 'string',\n title: 'file_upload_type',\n description: 'Type of file upload method supported by the application.',\n enum: [ 'url',\n 'file_id'\n ]\n }\n }\n },\n required: [ 'file_types',\n 'max_file_size_mb',\n 'max_files',\n 'max_word_count',\n 'upload_types'\n ]\n },\n {\n type: 'object',\n title: 'Media',\n description: 'Configuration options specific to media upload input fields.',\n properties: {\n file_types: {\n type: 'array',\n description: 'List of allowed media file types.',\n items: {\n type: 'string'\n }\n },\n max_image_size_mb: {\n type: 'integer',\n description: 'Maximum media file size allowed in megabytes.'\n }\n },\n required: [ 'file_types',\n 'max_image_size_mb'\n ]\n },\n {\n type: 'object',\n title: 'Text',\n description: 'Configuration options specific to text input fields.',\n properties: {\n max_fields: {\n type: 'integer',\n description: 'Maximum number of text fields allowed.'\n },\n min_fields: {\n type: 'integer',\n description: 'Minimum number of text fields required.'\n }\n },\n required: [ 'max_fields',\n 'min_fields'\n ]\n }\n ],\n description: 'Type-specific configuration options for input fields.'\n }\n },\n required: [ 'input_type',\n 'name',\n 'required'\n ]\n }\n },\n name: {\n type: 'string',\n description: 'Display name of the application.'\n },\n status: {\n type: 'string',\n title: 'application_status',\n description: 'Current deployment status of the application. Note: currently only `deployed` applications are returned.',\n enum: [ 'deployed',\n 'draft'\n ]\n },\n type: {\n type: 'string',\n title: 'application_type',\n description: 'The type of no-code application.',\n enum: [ 'generation'\n ]\n },\n updated_at: {\n type: 'string',\n description: 'Timestamp when the application was last updated.',\n format: 'date-time'\n },\n last_deployed_at: {\n type: 'string',\n description: 'Timestamp when the application was last deployed.',\n format: 'date-time'\n }\n },\n required: [ 'id',\n 'created_at',\n 'inputs',\n 'name',\n 'status',\n 'type',\n 'updated_at'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -40,9 +40,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { application_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.applications.retrieve(application_id)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.applications.retrieve(application_id)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/chat/chat-chat.ts b/packages/mcp-server/src/tools/chat/chat-chat.ts index a89af792..42b3164b 100644 --- a/packages/mcp-server/src/tools/chat/chat-chat.ts +++ b/packages/mcp-server/src/tools/chat/chat-chat.ts @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'chat_chat', description: - 'Generate a chat completion based on the provided messages. The response shown below is for non-streaming. To learn about streaming responses, see the [chat completion guide](https://dev.writer.com/home/chat-completion).', + 'Generate AI responses for conversational interactions. Use this for chat-based tasks, Q&A, content generation, and any natural language processing. Supports tools like Knowledge Graphs, web search, translation, and vision. Choose from models like palmyra-x5, palmyra-x4, palmyra-creative, palmyra-med, or palmyra-fin depending on the task.', inputSchema: { type: 'object', anyOf: [ @@ -823,7 +823,8 @@ export const tool: Tool = { function: { type: 'object', title: 'Vision function', - description: 'A tool that uses Palmyra Vision to analyze images.', + description: + 'A tool that uses Palmyra Vision to analyze images and documents. Supports JPG, PNG, PDF, and TXT files up to 7MB each.', properties: { model: { type: 'string', @@ -839,7 +840,7 @@ export const tool: Tool = { file_id: { type: 'string', description: - 'The File ID of the image to analyze. The file must be uploaded to the Writer platform before you use it with the Vision tool. The maximum allowed file size is 7MB.', + 'The File ID of the file to analyze. The file must be uploaded to the Writer platform before you use it with the Vision tool. Supported file types: JPG, PNG, PDF, TXT. The maximum allowed file size is 7MB.', }, name: { type: 'string', diff --git a/packages/mcp-server/src/tools/completions/create-completions.ts b/packages/mcp-server/src/tools/completions/create-completions.ts index 6b9085d2..9b9a4cad 100644 --- a/packages/mcp-server/src/tools/completions/create-completions.ts +++ b/packages/mcp-server/src/tools/completions/create-completions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_completions', 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\nGenerate text completions using the specified model and prompt. This endpoint is useful for text generation tasks that don't require conversational context.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/completion',\n $defs: {\n completion: {\n type: 'object',\n properties: {\n choices: {\n type: 'array',\n description: 'A list of choices generated by the model, each containing the text of the completion and associated metadata such as log probabilities.',\n items: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The generated text output from the model, which forms the main content of the response.'\n },\n log_probs: {\n $ref: '#/$defs/logprobs'\n }\n },\n required: [ 'text'\n ]\n }\n },\n model: {\n type: 'string',\n description: 'The identifier of the model that was used to generate the responses in the \\'choices\\' array.'\n }\n },\n required: [ 'choices'\n ]\n },\n logprobs: {\n type: 'object',\n properties: {\n content: {\n type: 'array',\n items: {\n $ref: '#/$defs/logprobs_token'\n }\n },\n refusal: {\n type: 'array',\n items: {\n $ref: '#/$defs/logprobs_token'\n }\n }\n },\n required: [ 'content',\n 'refusal'\n ]\n },\n logprobs_token: {\n type: 'object',\n title: 'logprobs_token',\n properties: {\n token: {\n type: 'string'\n },\n logprob: {\n type: 'number'\n },\n top_logprobs: {\n type: 'array',\n items: {\n type: 'object',\n title: 'top_log_prob',\n description: 'An array of mappings for each token to its top log probabilities, showing detailed prediction probabilities.',\n properties: {\n token: {\n type: 'string'\n },\n logprob: {\n type: 'number'\n },\n bytes: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n },\n required: [ 'token',\n 'logprob'\n ]\n }\n },\n bytes: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n },\n required: [ 'token',\n 'logprob',\n 'top_logprobs'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGenerate text completions from a single prompt without conversational context. Best for straightforward text generation tasks like article writing, summaries, or creative content. For interactive conversations or multi-turn dialogues, use generate-chat-completion instead.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/completion',\n $defs: {\n completion: {\n type: 'object',\n properties: {\n choices: {\n type: 'array',\n description: 'A list of choices generated by the model, each containing the text of the completion and associated metadata such as log probabilities.',\n items: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The generated text output from the model, which forms the main content of the response.'\n },\n log_probs: {\n $ref: '#/$defs/logprobs'\n }\n },\n required: [ 'text'\n ]\n }\n },\n model: {\n type: 'string',\n description: 'The identifier of the model that was used to generate the responses in the \\'choices\\' array.'\n }\n },\n required: [ 'choices'\n ]\n },\n logprobs: {\n type: 'object',\n properties: {\n content: {\n type: 'array',\n items: {\n $ref: '#/$defs/logprobs_token'\n }\n },\n refusal: {\n type: 'array',\n items: {\n $ref: '#/$defs/logprobs_token'\n }\n }\n },\n required: [ 'content',\n 'refusal'\n ]\n },\n logprobs_token: {\n type: 'object',\n title: 'logprobs_token',\n properties: {\n token: {\n type: 'string'\n },\n logprob: {\n type: 'number'\n },\n top_logprobs: {\n type: 'array',\n items: {\n type: 'object',\n title: 'top_log_prob',\n description: 'An array of mappings for each token to its top log probabilities, showing detailed prediction probabilities.',\n properties: {\n token: {\n type: 'string'\n },\n logprob: {\n type: 'number'\n },\n bytes: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n },\n required: [ 'token',\n 'logprob'\n ]\n }\n },\n bytes: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n },\n required: [ 'token',\n 'logprob',\n 'top_logprobs'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', anyOf: [ @@ -157,7 +157,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.completions.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.completions.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/delete-files.ts b/packages/mcp-server/src/tools/files/delete-files.ts index d7af8d32..43050c44 100644 --- a/packages/mcp-server/src/tools/files/delete-files.ts +++ b/packages/mcp-server/src/tools/files/delete-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'delete_files', 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\nPermanently delete a file from the system. This action cannot be undone.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'delete_file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted file.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the file was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nPermanently delete a file from the system. This action cannot be undone.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_delete_response',\n $defs: {\n file_delete_response: {\n type: 'object',\n title: 'delete_file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted file.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the file was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { file_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.delete(file_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.delete(file_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/download-files.ts b/packages/mcp-server/src/tools/files/download-files.ts index cf220c23..0afb4831 100644 --- a/packages/mcp-server/src/tools/files/download-files.ts +++ b/packages/mcp-server/src/tools/files/download-files.ts @@ -34,7 +34,7 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { file_id, ...body } = args as any; - return asBinaryContentResult(await client.files.download(file_id)); + return asBinaryContentResult(await client.files.download(file_id).asResponse()); }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/list-files.ts b/packages/mcp-server/src/tools/files/list-files.ts index ffd1593d..ce78108e 100644 --- a/packages/mcp-server/src/tools/files/list-files.ts +++ b/packages/mcp-server/src/tools/files/list-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_files', 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\nRetrieve a paginated list of files with optional filtering by status, graph association, and file type.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'files_response',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more files available beyond the current page.'\n },\n first_id: {\n type: 'string',\n description: 'The ID of the first file in the current response.'\n },\n last_id: {\n type: 'string',\n description: 'The ID of the last file in the current response.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet a paginated list of all uploaded files. Filter by processing status (in_progress, completed, failed), Knowledge Graph association, or file type. Use this to discover available files, monitor processing status, or find files to add to Knowledge Graphs.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'files_response',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more files available beyond the current page.'\n },\n first_id: {\n type: 'string',\n description: 'The ID of the first file in the current response.'\n },\n last_id: {\n type: 'string',\n description: 'The ID of the last file in the current response.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -75,7 +75,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.files.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/retrieve-files.ts b/packages/mcp-server/src/tools/files/retrieve-files.ts index 2dbd0e6b..2621dd27 100644 --- a/packages/mcp-server/src/tools/files/retrieve-files.ts +++ b/packages/mcp-server/src/tools/files/retrieve-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'retrieve_files', 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\nRetrieve detailed information about a specific file, including its metadata, status, and associated graphs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet metadata and status information for a specific file by its ID. Returns file name, creation date, processing status, and associated Knowledge Graph IDs. Use this to check if a file has finished processing or to find which Knowledge Graphs contain a specific file.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { file_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.retrieve(file_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.retrieve(file_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/retry-files.ts b/packages/mcp-server/src/tools/files/retry-files.ts index 868ae1e1..cc62a08b 100644 --- a/packages/mcp-server/src/tools/files/retry-files.ts +++ b/packages/mcp-server/src/tools/files/retry-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'retry_files', 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\nRetry processing of files that previously failed to process. This will re-attempt the processing of the specified files.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'retry_files_response',\n properties: {\n success: {\n type: 'boolean',\n description: 'Indicates whether the retry operation was successful.'\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetry processing of files that previously failed to process. This will re-attempt the processing of the specified files.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_retry_response',\n $defs: {\n file_retry_response: {\n type: 'object',\n title: 'retry_files_response',\n properties: {\n success: {\n type: 'boolean',\n description: 'Indicates whether the retry operation was successful.'\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -43,7 +43,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.retry(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.retry(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/upload-files.ts b/packages/mcp-server/src/tools/files/upload-files.ts index ae6ffc33..4e9c7356 100644 --- a/packages/mcp-server/src/tools/files/upload-files.ts +++ b/packages/mcp-server/src/tools/files/upload-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'upload_files', 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\nUpload a new file to the system. Supports various file formats including PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, and XLSX.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpload documents and files to Writer. Supports PDF, DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, XLSX, MP3, and MP4 formats. Once uploaded, files can be added to Knowledge Graphs for querying or used with Vision API for image analysis. Returns a file ID for subsequent operations.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -28,6 +28,11 @@ export const tool: Tool = { 'Content-Disposition': { type: 'string', }, + graphId: { + type: 'string', + description: + 'The unique identifier of the Knowledge Graph to associate the uploaded file with.\n\nNote: The response from the upload endpoint does not include the `graphId` field, but the association will be visible when you retrieve the file using the file retrieval endpoint.', + }, jq_filter: { type: 'string', title: 'jq Filter', @@ -42,7 +47,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.upload(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.upload(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts b/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts index daf1d456..a25705e1 100644 --- a/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'add_file_to_graph_graphs', 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\nAdd a file to a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nAdd an uploaded file to a Knowledge Graph to make it queryable. The file must already be uploaded using upload-file. Once added, the file's content becomes searchable when querying the Knowledge Graph. Files are processed asynchronously - check status using get-file-info.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the file.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the file was uploaded.',\n format: 'date-time'\n },\n graph_ids: {\n type: 'array',\n description: 'A list of Knowledge Graph IDs that the file is associated with.\\n\\nIf you provided a `graphId` during upload, the file is associated with that Knowledge Graph. However, the `graph_ids` field in the upload response is an empty list. The association will be visible in the `graph_ids` list when you retrieve the file using the file retrieval endpoint.',\n items: {\n type: 'string'\n }\n },\n name: {\n type: 'string',\n description: 'The name of the file.'\n },\n status: {\n type: 'string',\n description: 'The processing status of the file.'\n }\n },\n required: [ 'id',\n 'created_at',\n 'graph_ids',\n 'name',\n 'status'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -43,9 +43,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { graph_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.graphs.addFileToGraph(graph_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.graphs.addFileToGraph(graph_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/create-graphs.ts b/packages/mcp-server/src/tools/graphs/create-graphs.ts index 7d7bbd88..b24f68aa 100644 --- a/packages/mcp-server/src/tools/graphs/create-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/create-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_graphs', 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\nCreate a new Knowledge Graph.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph (max 255 characters).'\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph (max 255 characters).'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'name'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate a new Knowledge Graph to organize and query documents. Knowledge Graphs are containers for files that enable AI-powered search and question answering. After creation, add files to the graph using add-file-to-graph, then query it using query-knowledge-graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_create_response',\n $defs: {\n graph_create_response: {\n type: 'object',\n title: 'graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph (max 255 characters).'\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph (max 255 characters).'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'name'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/delete-graphs.ts b/packages/mcp-server/src/tools/graphs/delete-graphs.ts index aa2d12a4..f49d8e37 100644 --- a/packages/mcp-server/src/tools/graphs/delete-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/delete-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'delete_graphs', 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\nDelete a Knowledge Graph.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'delete_graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted Knowledge Graph.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the Knowledge Graph was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nDelete a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_delete_response',\n $defs: {\n graph_delete_response: {\n type: 'object',\n title: 'delete_graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted Knowledge Graph.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the Knowledge Graph was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { graph_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.delete(graph_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.delete(graph_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/list-graphs.ts b/packages/mcp-server/src/tools/graphs/list-graphs.ts index e4db238f..c9ed0dcc 100644 --- a/packages/mcp-server/src/tools/graphs/list-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/list-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_graphs', 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\nRetrieve a list of Knowledge Graphs.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'graphs_response',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/graph'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more Knowledge Graphs available beyond the current page.'\n },\n first_id: {\n type: 'string',\n description: 'The ID of the first Knowledge Graph in the current response.'\n },\n last_id: {\n type: 'string',\n description: 'The ID of the last Knowledge Graph in the current response.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n graph: {\n type: 'object',\n title: 'graph',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n file_status: {\n type: 'object',\n title: 'graph_file_status',\n description: 'The processing status of files in the Knowledge Graph.',\n properties: {\n completed: {\n type: 'integer',\n description: 'The number of files that have been successfully processed.'\n },\n failed: {\n type: 'integer',\n description: 'The number of files that failed to process.'\n },\n in_progress: {\n type: 'integer',\n description: 'The number of files currently being processed.'\n },\n total: {\n type: 'integer',\n description: 'The total number of files associated with the Knowledge Graph.'\n }\n },\n required: [ 'completed',\n 'failed',\n 'in_progress',\n 'total'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph.'\n },\n type: {\n type: 'string',\n title: 'graph_type',\n description: 'The type of Knowledge Graph.\\n\\n- `manual`: files are uploaded via UI or API\\n- `connector`: files are uploaded via a data connector such as Google Drive or Confluence\\n- `web`: URLs are connected to the Knowledge Graph',\n enum: [ 'manual',\n 'connector',\n 'web'\n ]\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph.'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'file_status',\n 'name',\n 'type'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet all available Knowledge Graphs in your account. Knowledge Graphs are collections of documents and files that can be queried using AI. Use this to discover which knowledge bases are available before querying them.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'graphs_response',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/graph'\n }\n },\n has_more: {\n type: 'boolean',\n description: 'Indicates if there are more Knowledge Graphs available beyond the current page.'\n },\n first_id: {\n type: 'string',\n description: 'The ID of the first Knowledge Graph in the current response.'\n },\n last_id: {\n type: 'string',\n description: 'The ID of the last Knowledge Graph in the current response.'\n }\n },\n required: [ 'data',\n 'has_more'\n ],\n $defs: {\n graph: {\n type: 'object',\n title: 'graph',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n file_status: {\n type: 'object',\n title: 'graph_file_status',\n description: 'The processing status of files in the Knowledge Graph.',\n properties: {\n completed: {\n type: 'integer',\n description: 'The number of files that have been successfully processed.'\n },\n failed: {\n type: 'integer',\n description: 'The number of files that failed to process.'\n },\n in_progress: {\n type: 'integer',\n description: 'The number of files currently being processed.'\n },\n total: {\n type: 'integer',\n description: 'The total number of files associated with the Knowledge Graph.'\n }\n },\n required: [ 'completed',\n 'failed',\n 'in_progress',\n 'total'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph.'\n },\n type: {\n type: 'string',\n title: 'graph_type',\n description: 'The type of Knowledge Graph.\\n\\n- `manual`: files are uploaded via UI or API\\n- `connector`: files are uploaded via a data connector such as Google Drive or Confluence\\n- `web`: URLs are connected to the Knowledge Graph',\n enum: [ 'manual',\n 'connector',\n 'web'\n ]\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph.'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'file_status',\n 'name',\n 'type'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -60,7 +60,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; const response = await client.graphs.list(body).asResponse(); - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await response.json())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/question-graphs.ts b/packages/mcp-server/src/tools/graphs/question-graphs.ts index b73facf1..b8b11c61 100644 --- a/packages/mcp-server/src/tools/graphs/question-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/question-graphs.ts @@ -16,7 +16,8 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'question_graphs', - description: 'Ask a question to specified Knowledge Graphs.', + description: + 'Ask questions and get AI-generated answers based on your Knowledge Graph content. Queries your uploaded documents, PDFs, and files to retrieve accurate, source-cited information. Returns answers with supporting snippets and file references. Ideal for RAG (Retrieval-Augmented Generation) applications and knowledge base queries.', inputSchema: { type: 'object', anyOf: [ diff --git a/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts b/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts index 2e049669..6a7ed9cd 100644 --- a/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'remove_file_from_graph_graphs', 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\nRemove a file from a Knowledge Graph.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'delete_file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted file.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the file was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRemove a file from a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_remove_file_from_graph_response',\n $defs: {\n graph_remove_file_from_graph_response: {\n type: 'object',\n title: 'delete_file_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the deleted file.'\n },\n deleted: {\n type: 'boolean',\n description: 'Indicates whether the file was successfully deleted.'\n }\n },\n required: [ 'id',\n 'deleted'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -44,9 +44,16 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { file_id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.graphs.removeFileFromGraph(file_id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.graphs.removeFileFromGraph(file_id, body)), + ); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts b/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts index b38ee70d..a9641fe2 100644 --- a/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'retrieve_graphs', 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\nRetrieve a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph',\n $defs: {\n graph: {\n type: 'object',\n title: 'graph',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n file_status: {\n type: 'object',\n title: 'graph_file_status',\n description: 'The processing status of files in the Knowledge Graph.',\n properties: {\n completed: {\n type: 'integer',\n description: 'The number of files that have been successfully processed.'\n },\n failed: {\n type: 'integer',\n description: 'The number of files that failed to process.'\n },\n in_progress: {\n type: 'integer',\n description: 'The number of files currently being processed.'\n },\n total: {\n type: 'integer',\n description: 'The total number of files associated with the Knowledge Graph.'\n }\n },\n required: [ 'completed',\n 'failed',\n 'in_progress',\n 'total'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph.'\n },\n type: {\n type: 'string',\n title: 'graph_type',\n description: 'The type of Knowledge Graph.\\n\\n- `manual`: files are uploaded via UI or API\\n- `connector`: files are uploaded via a data connector such as Google Drive or Confluence\\n- `web`: URLs are connected to the Knowledge Graph',\n enum: [ 'manual',\n 'connector',\n 'web'\n ]\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph.'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'file_status',\n 'name',\n 'type'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet detailed information about a specific Knowledge Graph by its ID. Returns the graph name, description, creation date, file processing status, and associated URLs (for web-based graphs). Use this to check processing status or get graph metadata.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph',\n $defs: {\n graph: {\n type: 'object',\n title: 'graph',\n properties: {\n id: {\n type: 'string',\n description: 'The unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n file_status: {\n type: 'object',\n title: 'graph_file_status',\n description: 'The processing status of files in the Knowledge Graph.',\n properties: {\n completed: {\n type: 'integer',\n description: 'The number of files that have been successfully processed.'\n },\n failed: {\n type: 'integer',\n description: 'The number of files that failed to process.'\n },\n in_progress: {\n type: 'integer',\n description: 'The number of files currently being processed.'\n },\n total: {\n type: 'integer',\n description: 'The total number of files associated with the Knowledge Graph.'\n }\n },\n required: [ 'completed',\n 'failed',\n 'in_progress',\n 'total'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph.'\n },\n type: {\n type: 'string',\n title: 'graph_type',\n description: 'The type of Knowledge Graph.\\n\\n- `manual`: files are uploaded via UI or API\\n- `connector`: files are uploaded via a data connector such as Google Drive or Confluence\\n- `web`: URLs are connected to the Knowledge Graph',\n enum: [ 'manual',\n 'connector',\n 'web'\n ]\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph.'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'file_status',\n 'name',\n 'type'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { graph_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.retrieve(graph_id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.retrieve(graph_id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/update-graphs.ts b/packages/mcp-server/src/tools/graphs/update-graphs.ts index 1657182e..52c02654 100644 --- a/packages/mcp-server/src/tools/graphs/update-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/update-graphs.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'update_graphs', 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\nUpdate the name and description of a Knowledge Graph.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph (max 255 characters).'\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph (max 255 characters).'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'name'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdate the name and description of a Knowledge Graph.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/graph_update_response',\n $defs: {\n graph_update_response: {\n type: 'object',\n title: 'graph_response',\n properties: {\n id: {\n type: 'string',\n description: 'A unique identifier of the Knowledge Graph.'\n },\n created_at: {\n type: 'string',\n description: 'The timestamp when the Knowledge Graph was created.',\n format: 'date-time'\n },\n name: {\n type: 'string',\n description: 'The name of the Knowledge Graph (max 255 characters).'\n },\n description: {\n type: 'string',\n description: 'A description of the Knowledge Graph (max 255 characters).'\n },\n urls: {\n type: 'array',\n description: 'An array of web connector URLs associated with this Knowledge Graph.',\n items: {\n type: 'object',\n title: 'web_connector_url',\n properties: {\n status: {\n type: 'object',\n title: 'web_connector_url_state',\n description: 'The current status of the URL processing.',\n properties: {\n status: {\n type: 'string',\n title: 'web_connector_url_status',\n description: 'The current status of the URL processing.',\n enum: [ 'validating',\n 'success',\n 'error'\n ]\n },\n error_type: {\n type: 'string',\n title: 'web_connector_url_error_type',\n description: 'The type of error that occurred during processing, if any.',\n enum: [ 'invalid_url',\n 'not_searchable',\n 'not_found',\n 'paywall_or_login_page',\n 'unexpected_error'\n ]\n }\n },\n required: [ 'status'\n ]\n },\n type: {\n type: 'string',\n title: 'web_connector_url_type',\n description: 'The type of web connector processing for this URL.',\n enum: [ 'single_page',\n 'sub_pages'\n ]\n },\n url: {\n type: 'string',\n description: 'The URL to be processed by the web connector.'\n },\n exclude_urls: {\n type: 'array',\n description: 'An array of URLs to exclude from processing within this web connector.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'status',\n 'type',\n 'url'\n ]\n }\n }\n },\n required: [ 'id',\n 'created_at',\n 'name'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -80,7 +80,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { graph_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.update(graph_id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.graphs.update(graph_id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/models/list-models.ts b/packages/mcp-server/src/tools/models/list-models.ts index 11288956..50163670 100644 --- a/packages/mcp-server/src/tools/models/list-models.ts +++ b/packages/mcp-server/src/tools/models/list-models.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_models', 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\nRetrieve a list of available models that can be used for text generation, chat completions, and other AI tasks.\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n models: {\n type: 'array',\n description: 'The [ID of the model](https://dev.writer.com/home/models) to use for processing the request.',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The ID of the particular LLM that you want to use'\n },\n name: {\n type: 'string',\n description: 'The name of the particular LLM that you want to use.'\n }\n },\n required: [ 'id',\n 'name'\n ]\n }\n }\n },\n required: [ 'models'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nRetrieve a list of available models that can be used for text generation, chat completions, and other AI tasks.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/model_list_response',\n $defs: {\n model_list_response: {\n type: 'object',\n properties: {\n models: {\n type: 'array',\n description: 'The [ID of the model](https://dev.writer.com/home/models) to use for processing the request.',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The ID of the particular LLM that you want to use'\n },\n name: {\n type: 'string',\n description: 'The name of the particular LLM that you want to use.'\n }\n },\n required: [ 'id',\n 'name'\n ]\n }\n }\n },\n required: [ 'models'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -38,7 +38,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.models.list())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.models.list())); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/ai-detect-tools.ts b/packages/mcp-server/src/tools/tools/ai-detect-tools.ts index f9d3aabb..d64dfbc7 100644 --- a/packages/mcp-server/src/tools/tools/ai-detect-tools.ts +++ b/packages/mcp-server/src/tools/tools/ai-detect-tools.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'ai_detect_tools', 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\nDetects if content is AI- or human-generated, with a confidence score. Content must have at least 350 characters\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'ai_detection_response',\n properties: {\n label: {\n type: 'string',\n enum: [ 'fake',\n 'real'\n ]\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'label',\n 'score'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nDetects if content is AI- or human-generated, with a confidence score. Content must have at least 350 characters\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_ai_detect_response',\n $defs: {\n tool_ai_detect_response: {\n type: 'object',\n title: 'ai_detection_response',\n properties: {\n label: {\n type: 'string',\n enum: [ 'fake',\n 'real'\n ]\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'label',\n 'score'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -40,7 +40,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.aiDetect(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.tools.aiDetect(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts b/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts index 1daf29e4..b5fe3146 100644 --- a/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts +++ b/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'medical_tools_comprehend', 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\nAnalyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'medical_comprehend_response',\n properties: {\n entities: {\n type: 'array',\n description: 'An array of medical entities extracted from the input text.',\n items: {\n type: 'object',\n title: 'medical_comprehend_entity',\n properties: {\n attributes: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_attribute',\n properties: {\n begin_offset: {\n type: 'integer'\n },\n concepts: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_concept',\n properties: {\n code: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'code',\n 'description',\n 'score'\n ]\n }\n },\n end_offset: {\n type: 'integer'\n },\n relationship_score: {\n type: 'number'\n },\n score: {\n type: 'number'\n },\n text: {\n type: 'string'\n },\n traits: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_trait',\n properties: {\n name: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'name',\n 'score'\n ]\n }\n },\n type: {\n type: 'string'\n },\n category: {\n type: 'string'\n },\n relationship_type: {\n type: 'string'\n }\n },\n required: [ 'begin_offset',\n 'concepts',\n 'end_offset',\n 'relationship_score',\n 'score',\n 'text',\n 'traits',\n 'type'\n ]\n }\n },\n begin_offset: {\n type: 'integer'\n },\n category: {\n type: 'string'\n },\n concepts: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_concept',\n properties: {\n code: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'code',\n 'description',\n 'score'\n ]\n }\n },\n end_offset: {\n type: 'integer'\n },\n score: {\n type: 'number'\n },\n text: {\n type: 'string'\n },\n traits: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_trait',\n properties: {\n name: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'name',\n 'score'\n ]\n }\n },\n type: {\n type: 'string'\n }\n },\n required: [ 'attributes',\n 'begin_offset',\n 'category',\n 'concepts',\n 'end_offset',\n 'score',\n 'text',\n 'traits',\n 'type'\n ]\n }\n }\n },\n required: [ 'entities'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nAnalyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/comprehend_medical_response',\n $defs: {\n comprehend_medical_response: {\n type: 'object',\n title: 'medical_comprehend_response',\n properties: {\n entities: {\n type: 'array',\n description: 'An array of medical entities extracted from the input text.',\n items: {\n type: 'object',\n title: 'medical_comprehend_entity',\n properties: {\n attributes: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_attribute',\n properties: {\n begin_offset: {\n type: 'integer'\n },\n concepts: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_concept',\n properties: {\n code: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'code',\n 'description',\n 'score'\n ]\n }\n },\n end_offset: {\n type: 'integer'\n },\n relationship_score: {\n type: 'number'\n },\n score: {\n type: 'number'\n },\n text: {\n type: 'string'\n },\n traits: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_trait',\n properties: {\n name: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'name',\n 'score'\n ]\n }\n },\n type: {\n type: 'string'\n },\n category: {\n type: 'string'\n },\n relationship_type: {\n type: 'string'\n }\n },\n required: [ 'begin_offset',\n 'concepts',\n 'end_offset',\n 'relationship_score',\n 'score',\n 'text',\n 'traits',\n 'type'\n ]\n }\n },\n begin_offset: {\n type: 'integer'\n },\n category: {\n type: 'string'\n },\n concepts: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_concept',\n properties: {\n code: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'code',\n 'description',\n 'score'\n ]\n }\n },\n end_offset: {\n type: 'integer'\n },\n score: {\n type: 'number'\n },\n text: {\n type: 'string'\n },\n traits: {\n type: 'array',\n items: {\n type: 'object',\n title: 'medical_comprehend_trait',\n properties: {\n name: {\n type: 'string'\n },\n score: {\n type: 'number'\n }\n },\n required: [ 'name',\n 'score'\n ]\n }\n },\n type: {\n type: 'string'\n }\n },\n required: [ 'attributes',\n 'begin_offset',\n 'category',\n 'concepts',\n 'end_offset',\n 'score',\n 'text',\n 'traits',\n 'type'\n ]\n }\n }\n },\n required: [ 'entities'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.comprehend.medical(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.tools.comprehend.medical(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts b/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts index b79d9615..18c7cc96 100644 --- a/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts +++ b/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'context_aware_splitting_tools', 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\nSplits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'context_aware_splitting_response',\n properties: {\n chunks: {\n type: 'array',\n description: 'An array of text chunks generated by splitting the input text based on the specified strategy.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'chunks'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nSplits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_context_aware_splitting_response',\n $defs: {\n tool_context_aware_splitting_response: {\n type: 'object',\n title: 'context_aware_splitting_response',\n properties: {\n chunks: {\n type: 'array',\n description: 'An array of text chunks generated by splitting the input text based on the specified strategy.',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'chunks'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.contextAwareSplitting(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.tools.contextAwareSplitting(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts b/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts index 38d6797f..a23ac88d 100644 --- a/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts +++ b/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'parse_pdf_tools', 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\nParse PDF to other formats.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'parse_pdf_response',\n properties: {\n content: {\n type: 'string',\n description: 'The extracted content from the PDF file, converted to the specified format.'\n }\n },\n required: [ 'content'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nParse PDF to other formats.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_parse_pdf_response',\n $defs: {\n tool_parse_pdf_response: {\n type: 'object',\n title: 'parse_pdf_response',\n properties: {\n content: {\n type: 'string',\n description: 'The extracted content from the PDF file, converted to the specified format.'\n }\n },\n required: [ 'content'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { file_id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.parsePdf(file_id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.tools.parsePdf(file_id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/web-search-tools.ts b/packages/mcp-server/src/tools/tools/web-search-tools.ts index 983b70d6..d8ae2a66 100644 --- a/packages/mcp-server/src/tools/tools/web-search-tools.ts +++ b/packages/mcp-server/src/tools/tools/web-search-tools.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'web_search_tools', 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\nSearch the web for information about a given query and return relevant results with source URLs.\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'web_search_response',\n properties: {\n query: {\n type: 'string',\n description: 'The search query that was submitted.'\n },\n sources: {\n type: 'array',\n description: 'The search results found.',\n items: {\n type: 'object',\n properties: {\n raw_content: {\n type: 'string',\n description: 'Raw content from the source URL. Not included if `include_raw_content` is `false`.'\n },\n url: {\n type: 'string',\n description: 'URL of the search result.'\n }\n }\n }\n },\n answer: {\n type: 'string',\n description: 'Generated answer based on the search results. Not included if `include_answer` is `false`.'\n }\n },\n required: [ 'query',\n 'sources'\n ]\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nSearch the web for information about a given query and return relevant results with source URLs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/tool_web_search_response',\n $defs: {\n tool_web_search_response: {\n type: 'object',\n title: 'web_search_response',\n properties: {\n query: {\n type: 'string',\n description: 'The search query that was submitted.'\n },\n sources: {\n type: 'array',\n description: 'The search results found.',\n items: {\n type: 'object',\n properties: {\n raw_content: {\n type: 'string',\n description: 'Raw content from the source URL. Not included if `include_raw_content` is `false`.'\n },\n url: {\n type: 'string',\n description: 'URL of the search result.'\n }\n }\n }\n },\n answer: {\n type: 'string',\n description: 'Generated answer based on the search results. Not included if `include_answer` is `false`.'\n }\n },\n required: [ 'query',\n 'sources'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -279,7 +279,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.tools.webSearch(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.tools.webSearch(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/translation/translate-translation.ts b/packages/mcp-server/src/tools/translation/translate-translation.ts index 6c0832f4..353055c8 100644 --- a/packages/mcp-server/src/tools/translation/translate-translation.ts +++ b/packages/mcp-server/src/tools/translation/translate-translation.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from 'writer-sdk-mcp/filtering'; -import { Metadata, asTextContentResult } from 'writer-sdk-mcp/tools/types'; +import { isJqError, maybeFilter } from 'writer-sdk-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from 'writer-sdk-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; @@ -77,7 +77,14 @@ export const tool: Tool = { export const handler = async (client: Writer, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.translation.translate(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.translation.translate(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts index 17f01978..9993753a 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/tools/types.ts @@ -87,6 +87,18 @@ export async function asBinaryContentResult(response: Response): Promise | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.vision.analyze(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.vision.analyze(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index a8a5b81a..4d9b60ca 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -171,6 +171,7 @@ describe('parseQueryOptions', () => { const defaultOptions = { client: undefined, includeDynamicTools: undefined, + includeCodeTools: undefined, includeAllTools: undefined, filters: [], capabilities: { @@ -383,6 +384,27 @@ describe('parseQueryOptions', () => { { type: 'tool', op: 'exclude', value: 'exclude-tool' }, ]); }); + + it('code tools are enabled on http servers with default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: true }, query); + + expect(result.includeCodeTools).toBe(true); + }); + + it('code tools are prevented on http servers when no default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeCodeTools).toBe(undefined); + }); + + it('code tools are prevented on http servers when default option is explicitly false', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: false }, query); + + expect(result.includeCodeTools).toBe(false); + }); }); describe('parseEmbeddedJSON', () => { diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 966d0575..2bb21c66 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -2494,9 +2494,9 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.7.0" -"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz": - version "0.8.6" - resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f" +"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz": + version "0.8.8" + resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz#7849ef64bdfc28f70cbfc9888f886860e96da10d" js-tokens@^4.0.0: version "4.0.0" diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 7d8dd272..a90e5b8f 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -12,9 +12,11 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar "${BASE_PATH:+-C$BASE_PATH}" -cz "${ARTIFACT_PATH:-dist}" | curl -v -X PUT \ +TARBALL=$(cd dist && npm pack --silent) + +UPLOAD_RESPONSE=$(curl -v -X PUT \ -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) + --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1) if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" diff --git a/src/client.ts b/src/client.ts index b945426a..6cfe26e2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -202,7 +202,7 @@ export class Writer { baseURL: string; maxRetries: number; timeout: number; - logger: Logger | undefined; + logger: Logger; logLevel: LogLevel | undefined; fetchOptions: MergedRequestInit | undefined; diff --git a/src/core/pagination.ts b/src/core/pagination.ts index ef93f02a..383535e1 100644 --- a/src/core/pagination.ts +++ b/src/core/pagination.ts @@ -245,11 +245,7 @@ export class ApplicationJobsOffset } nextPageRequestOptions(): PageRequestOptions | null { - const offset = this.pagination?.offset; - if (!offset) { - return null; - } - + const offset = this.pagination?.offset ?? 0; const length = this.getPaginatedItems().length; const currentCount = offset + length; diff --git a/src/internal/to-file.ts b/src/internal/to-file.ts index 245e8493..30eada32 100644 --- a/src/internal/to-file.ts +++ b/src/internal/to-file.ts @@ -73,7 +73,7 @@ export type ToFileInput = /** * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats - * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s + * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible * @param {Object=} options additional properties * @param {string=} options.type the MIME type of the content diff --git a/src/resources/files.ts b/src/resources/files.ts index c1c5227b..3f22a12a 100644 --- a/src/resources/files.ts +++ b/src/resources/files.ts @@ -60,8 +60,14 @@ export class Files extends APIResource { * DOC, DOCX, PPT, PPTX, JPG, PNG, EML, HTML, SRT, CSV, XLS, and XLSX. */ upload(params: FileUploadParams, options?: RequestOptions): APIPromise { - const { content, 'Content-Disposition': contentDisposition, 'Content-Type': contentType } = params; + const { + content, + 'Content-Disposition': contentDisposition, + 'Content-Type': contentType, + graphId, + } = params; return this._client.post('/v1/files', { + query: { graphId }, body: content, ...options, headers: { @@ -88,6 +94,11 @@ export interface File { /** * A list of Knowledge Graph IDs that the file is associated with. + * + * If you provided a `graphId` during upload, the file is associated with that + * Knowledge Graph. However, the `graph_ids` field in the upload response is an + * empty list. The association will be visible in the `graph_ids` list when you + * retrieve the file using the file retrieval endpoint. */ graph_ids: Array; @@ -176,6 +187,16 @@ export interface FileUploadParams { * Header param: The content type of the file. */ 'Content-Type': string; + + /** + * Query param: The unique identifier of the Knowledge Graph to associate the + * uploaded file with. + * + * Note: The response from the upload endpoint does not include the `graphId` + * field, but the association will be visible when you retrieve the file using the + * file retrieval endpoint. + */ + graphId?: string; } export declare namespace Files { diff --git a/src/resources/shared.ts b/src/resources/shared.ts index ca578942..c852af4e 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -492,7 +492,8 @@ export namespace ToolParam { export interface VisionTool { /** - * A tool that uses Palmyra Vision to analyze images. + * A tool that uses Palmyra Vision to analyze images and documents. Supports JPG, + * PNG, PDF, and TXT files up to 7MB each. */ function: VisionTool.Function; @@ -504,7 +505,8 @@ export namespace ToolParam { export namespace VisionTool { /** - * A tool that uses Palmyra Vision to analyze images. + * A tool that uses Palmyra Vision to analyze images and documents. Supports JPG, + * PNG, PDF, and TXT files up to 7MB each. */ export interface Function { /** @@ -518,9 +520,9 @@ export namespace ToolParam { export namespace Function { export interface Variable { /** - * The File ID of the image to analyze. The file must be uploaded to the Writer - * platform before you use it with the Vision tool. The maximum allowed file size - * is 7MB. + * The File ID of the file to analyze. The file must be uploaded to the Writer + * platform before you use it with the Vision tool. Supported file types: JPG, PNG, + * PDF, TXT. The maximum allowed file size is 7MB. */ file_id: string; diff --git a/src/resources/vision.ts b/src/resources/vision.ts index 01ac6fa1..87544c76 100644 --- a/src/resources/vision.ts +++ b/src/resources/vision.ts @@ -6,7 +6,8 @@ import { RequestOptions } from '../internal/request-options'; export class Vision extends APIResource { /** - * Submit images and a prompt to generate an analysis of the images. + * Submit images and documents with a prompt to generate an analysis. Supports JPG, + * PNG, PDF, and TXT files up to 7MB each. * * @example * ```ts @@ -44,17 +45,19 @@ export interface VisionRequest { export namespace VisionRequest { /** - * An array of file variables required for the analysis. The image files must be - * uploaded to the Writer platform before they can be used in a vision request. - * Learn how to upload files using the + * An array of file variables required for the analysis. The files must be uploaded + * to the Writer platform before they can be used in a vision request. Learn how to + * upload files using the * [Files API](https://dev.writer.com/api-reference/file-api/upload-files). * - * The maximum allowed file size for each image is 7MB. + * Supported file types: JPG, PNG, PDF, TXT. The maximum allowed file size for each + * file is 7MB. */ export interface Variable { /** - * The File ID of the image to analyze. The file must be uploaded to the Writer - * platform before it can be used in a vision request. + * The File ID of the file to analyze. The file must be uploaded to the Writer + * platform before it can be used in a vision request. Supported file types: JPG, + * PNG, PDF, TXT (max 7MB each). */ file_id: string; @@ -92,17 +95,19 @@ export interface VisionAnalyzeParams { export namespace VisionAnalyzeParams { /** - * An array of file variables required for the analysis. The image files must be - * uploaded to the Writer platform before they can be used in a vision request. - * Learn how to upload files using the + * An array of file variables required for the analysis. The files must be uploaded + * to the Writer platform before they can be used in a vision request. Learn how to + * upload files using the * [Files API](https://dev.writer.com/api-reference/file-api/upload-files). * - * The maximum allowed file size for each image is 7MB. + * Supported file types: JPG, PNG, PDF, TXT. The maximum allowed file size for each + * file is 7MB. */ export interface Variable { /** - * The File ID of the image to analyze. The file must be uploaded to the Writer - * platform before it can be used in a vision request. + * The File ID of the file to analyze. The file must be uploaded to the Writer + * platform before it can be used in a vision request. Supported file types: JPG, + * PNG, PDF, TXT (max 7MB each). */ file_id: string; diff --git a/src/version.ts b/src/version.ts index c6fe86de..539014d7 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '2.3.2'; // x-release-please-version +export const VERSION = '2.3.3-rc.1'; // x-release-please-version diff --git a/tests/api-resources/files.test.ts b/tests/api-resources/files.test.ts index 9ba8965c..fd4b19c2 100644 --- a/tests/api-resources/files.test.ts +++ b/tests/api-resources/files.test.ts @@ -96,6 +96,7 @@ describe('resource files', () => { content: await toFile(Buffer.from('# my file contents'), 'README.md'), 'Content-Disposition': 'Content-Disposition', 'Content-Type': 'Content-Type', + graphId: '182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e', }); }); });