From bdfd369118542dad02cf8a0fae8713d0d8bea4eb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:35:26 +0000 Subject: [PATCH 01/44] chore(internal): use npm pack for build uploads --- scripts/utils/upload-artifact.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 0870aebc..e169cf10 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" From de606ba3b734389e1c52a9929dbf8487828822e0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:03:23 +0000 Subject: [PATCH 02/44] chore: extract some types in mcp docs --- .../tools/accounts/url-endpoints/list-accounts-url-endpoints.ts | 2 +- .../mcp-server/src/tools/accounts/usage/get-accounts-usage.ts | 2 +- packages/mcp-server/src/tools/assets/list-assets.ts | 2 +- .../src/tools/cache/invalidation/create-cache-invalidation.ts | 2 +- .../src/tools/cache/invalidation/get-cache-invalidation.ts | 2 +- .../custom-metadata-fields/delete-custom-metadata-fields.ts | 2 +- .../tools/custom-metadata-fields/list-custom-metadata-fields.ts | 2 +- packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts | 2 +- packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts | 2 +- .../src/tools/files/bulk/remove-ai-tags-files-bulk.ts | 2 +- .../mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts | 2 +- packages/mcp-server/src/tools/files/copy-files.ts | 2 +- packages/mcp-server/src/tools/files/move-files.ts | 2 +- packages/mcp-server/src/tools/files/rename-files.ts | 2 +- .../src/tools/files/versions/delete-files-versions.ts | 2 +- .../mcp-server/src/tools/files/versions/list-files-versions.ts | 2 +- packages/mcp-server/src/tools/folders/copy-folders.ts | 2 +- packages/mcp-server/src/tools/folders/create-folders.ts | 2 +- packages/mcp-server/src/tools/folders/delete-folders.ts | 2 +- packages/mcp-server/src/tools/folders/job/get-folders-job.ts | 2 +- packages/mcp-server/src/tools/folders/move-folders.ts | 2 +- packages/mcp-server/src/tools/folders/rename-folders.ts | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts index b565f8ad..5bb31895 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_accounts_url_endpoints', 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\n**Note:** This API is currently in beta. \nReturns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/url_endpoint_response'\n },\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\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\n**Note:** This API is currently in beta. \nReturns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_list_response',\n $defs: {\n url_endpoint_list_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/url_endpoint_response'\n }\n },\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts index 09f47703..d39f75c4 100644 --- a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts +++ b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'get_accounts_usage', 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\nGet the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n bandwidthBytes: {\n type: 'integer',\n description: 'Amount of bandwidth used in bytes.'\n },\n extensionUnitsCount: {\n type: 'integer',\n description: 'Number of extension units used.'\n },\n mediaLibraryStorageBytes: {\n type: 'integer',\n description: 'Storage used by media library in bytes.'\n },\n originalCacheStorageBytes: {\n type: 'integer',\n description: 'Storage used by the original cache in bytes.'\n },\n videoProcessingUnitsCount: {\n type: 'integer',\n description: 'Number of video processing units used.'\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 the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/usage_get_response',\n $defs: {\n usage_get_response: {\n type: 'object',\n properties: {\n bandwidthBytes: {\n type: 'integer',\n description: 'Amount of bandwidth used in bytes.'\n },\n extensionUnitsCount: {\n type: 'integer',\n description: 'Number of extension units used.'\n },\n mediaLibraryStorageBytes: {\n type: 'integer',\n description: 'Storage used by media library in bytes.'\n },\n originalCacheStorageBytes: {\n type: 'integer',\n description: 'Storage used by the original cache in bytes.'\n },\n videoProcessingUnitsCount: {\n type: 'integer',\n description: 'Number of video processing units used.'\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts index 6da6273a..180a2750 100644 --- a/packages/mcp-server/src/tools/assets/list-assets.ts +++ b/packages/mcp-server/src/tools/assets/list-assets.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_assets', 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\nThis API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/file'\n },\n {\n $ref: '#/$defs/folder'\n }\n ],\n description: 'Object containing details of a file or file version.'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n },\n folder: {\n type: 'object',\n title: 'Folder',\n properties: {\n createdAt: {\n type: 'string',\n description: 'Date and time when the folder was created. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n folderId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n folderPath: {\n type: 'string',\n description: 'Path of the folder. This is the path you would use in the URL to access the folder. For example, if the folder is at the root of the media library, the path will be /folder. If the folder is inside another folder named images, the path will be /images/folder.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'folder'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the folder was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n }\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\nThis API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/asset_list_response',\n $defs: {\n asset_list_response: {\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/file'\n },\n {\n $ref: '#/$defs/folder'\n }\n ],\n description: 'Object containing details of a file or file version.'\n }\n },\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n },\n folder: {\n type: 'object',\n title: 'Folder',\n properties: {\n createdAt: {\n type: 'string',\n description: 'Date and time when the folder was created. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n folderId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n folderPath: {\n type: 'string',\n description: 'Path of the folder. This is the path you would use in the URL to access the folder. For example, if the folder is at the root of the media library, the path will be /folder. If the folder is inside another folder named images, the path will be /images/folder.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'folder'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the folder was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts index 1e73f174..47d7e67d 100644 --- a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts +++ b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_cache_invalidation', 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\nThis API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n requestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\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\nThis API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/invalidation_create_response',\n $defs: {\n invalidation_create_response: {\n type: 'object',\n properties: {\n requestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts index a9a7afc4..78b3a46b 100644 --- a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts +++ b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'get_cache_invalidation', 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\nThis API returns the status of a purge cache request.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'Status of the purge request.',\n enum: [ 'Pending',\n 'Completed'\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\nThis API returns the status of a purge cache request.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/invalidation_get_response',\n $defs: {\n invalidation_get_response: {\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'Status of the purge request.',\n enum: [ 'Pending',\n 'Completed'\n ]\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts index f26b7b65..334361cf 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'delete_custom_metadata_fields', 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\nThis API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\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\nThis API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field_delete_response',\n $defs: {\n custom_metadata_field_delete_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts index 0367d7d1..9aa77973 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_custom_metadata_fields', 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\nThis API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response.\n\nYou can also filter results by a specific folder path to retrieve custom metadata fields applicable at that location. This path-specific filtering is useful when using the **Path policy** feature to determine which custom metadata fields are selected for a given path.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/custom_metadata_field'\n },\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Data type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\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\nThis API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response.\n\nYou can also filter results by a specific folder path to retrieve custom metadata fields applicable at that location. This path-specific filtering is useful when using the **Path policy** feature to determine which custom metadata fields are selected for a given path.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field_list_response',\n $defs: {\n custom_metadata_field_list_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/custom_metadata_field'\n }\n },\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Data type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts index 3aa9970c..3c163c97 100644 --- a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'add_tags_files_bulk', 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\nThis API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully added.\\n',\n items: {\n type: 'string'\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\nThis API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_add_tags_response',\n $defs: {\n bulk_add_tags_response: {\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully added.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts index 471b0c93..8527fb8d 100644 --- a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'delete_files_bulk', 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\nThis API deletes multiple files and all their file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n\nA maximum of 100 files can be deleted at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyDeletedFileIds: {\n type: 'array',\n description: 'An array of fileIds that were successfully deleted.\\n',\n items: {\n type: 'string'\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\nThis API deletes multiple files and all their file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n\nA maximum of 100 files can be deleted at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_delete_response',\n $defs: {\n bulk_delete_response: {\n type: 'object',\n properties: {\n successfullyDeletedFileIds: {\n type: 'array',\n description: 'An array of fileIds that were successfully deleted.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts index 28ea4b62..3632b0c1 100644 --- a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'remove_ai_tags_files_bulk', 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\nThis API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which AITags were successfully removed.\\n',\n items: {\n type: 'string'\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\nThis API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_remove_ai_tags_response',\n $defs: {\n bulk_remove_ai_tags_response: {\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which AITags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts index 2fd2661e..63efd0e0 100644 --- a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'remove_tags_files_bulk', 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\nThis API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully removed.\\n',\n items: {\n type: 'string'\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\nThis API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_remove_tags_response',\n $defs: {\n bulk_remove_tags_response: {\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts index 6dffab77..27fd670a 100644 --- a/packages/mcp-server/src/tools/files/copy-files.ts +++ b/packages/mcp-server/src/tools/files/copy-files.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'copy_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\nThis will copy a file from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\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\nThis will copy a file from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_copy_response',\n $defs: {\n file_copy_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts index 48c159a2..4539db6e 100644 --- a/packages/mcp-server/src/tools/files/move-files.ts +++ b/packages/mcp-server/src/tools/files/move-files.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'move_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\nThis will move a file and all its versions from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\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\nThis will move a file and all its versions from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_move_response',\n $defs: {\n file_move_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts index 2e76c956..97484634 100644 --- a/packages/mcp-server/src/tools/files/rename-files.ts +++ b/packages/mcp-server/src/tools/files/rename-files.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'rename_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\nYou can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. \n\nNote: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\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\nYou can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. \n\nNote: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_rename_response',\n $defs: {\n file_rename_response: {\n type: 'object',\n properties: {\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts index df4b1b92..17700066 100644 --- a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'delete_files_versions', 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\nThis API deletes a non-current file version permanently. The API returns an empty response.\n\nNote: If you want to delete all versions of a file, use the delete file API.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\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\nThis API deletes a non-current file version permanently. The API returns an empty response.\n\nNote: If you want to delete all versions of a file, use the delete file API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/version_delete_response',\n $defs: {\n version_delete_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts index 7ea542e2..b520c583 100644 --- a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_files_versions', 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\nThis API returns details of all versions of a file.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\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\nThis API returns details of all versions of a file.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/version_list_response',\n $defs: {\n version_list_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n }\n },\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts index b1efd5fe..8dd9e326 100644 --- a/packages/mcp-server/src/tools/folders/copy-folders.ts +++ b/packages/mcp-server/src/tools/folders/copy-folders.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'copy_folders', 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\nThis will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\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\nThis will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_copy_response',\n $defs: {\n folder_copy_response: {\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts index 365f3b02..70d49c57 100644 --- a/packages/mcp-server/src/tools/folders/create-folders.ts +++ b/packages/mcp-server/src/tools/folders/create-folders.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_folders', 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\nThis will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\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\nThis will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_create_response',\n $defs: {\n folder_create_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts index b58d0819..b8e4f32b 100644 --- a/packages/mcp-server/src/tools/folders/delete-folders.ts +++ b/packages/mcp-server/src/tools/folders/delete-folders.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'delete_folders', 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\nThis will delete a folder and all its contents permanently. The API returns an empty response.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\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\nThis will delete a folder and all its contents permanently. The API returns an empty response.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_delete_response',\n $defs: {\n folder_delete_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts index 19ca4ef2..a2704f98 100644 --- a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts +++ b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'get_folders_job', 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\nThis API returns the status of a bulk job like copy and move folder operations.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job.\\n'\n },\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This will be present only if `purgeCache` is set to `true` in the rename folder API request.\\n'\n },\n status: {\n type: 'string',\n description: 'Status of the bulk job.',\n enum: [ 'Pending',\n 'Completed'\n ]\n },\n type: {\n type: 'string',\n description: 'Type of the bulk job.',\n enum: [ 'COPY_FOLDER',\n 'MOVE_FOLDER',\n 'RENAME_FOLDER'\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\nThis API returns the status of a bulk job like copy and move folder operations.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_get_response',\n $defs: {\n job_get_response: {\n type: 'object',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job.\\n'\n },\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This will be present only if `purgeCache` is set to `true` in the rename folder API request.\\n'\n },\n status: {\n type: 'string',\n description: 'Status of the bulk job.',\n enum: [ 'Pending',\n 'Completed'\n ]\n },\n type: {\n type: 'string',\n description: 'Type of the bulk job.',\n enum: [ 'COPY_FOLDER',\n 'MOVE_FOLDER',\n 'RENAME_FOLDER'\n ]\n }\n }\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts index 9131e342..e315003a 100644 --- a/packages/mcp-server/src/tools/folders/move-folders.ts +++ b/packages/mcp-server/src/tools/folders/move-folders.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'move_folders', 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\nThis will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\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\nThis will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_move_response',\n $defs: {\n folder_move_response: {\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { diff --git a/packages/mcp-server/src/tools/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts index 83c54224..2822faac 100644 --- a/packages/mcp-server/src/tools/folders/rename-folders.ts +++ b/packages/mcp-server/src/tools/folders/rename-folders.ts @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'rename_folders', 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\nThis API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\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\nThis API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_rename_response',\n $defs: {\n folder_rename_response: {\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { From 9ea439a2d0a4c8300d14d4424dc72ab40a67c4d4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:46:15 +0000 Subject: [PATCH 03/44] feat(api): add GetImageAttributesOptions and ResponsiveImageAttributes schemas; update resource references in main.yaml; remove dummy endpoint --- .stats.yml | 6 ++-- api.md | 2 ++ src/client.ts | 2 ++ src/resources/shared.ts | 76 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index f035d349..27ad9e7e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-c7ad6f552b38f2145781847f8b390fa1ec43068d64e45a33012a97a9299edc10.yml -openapi_spec_hash: 50f281e91210ad5018ac7e4eee216f56 -config_hash: 74a8263b80c732a2b016177e7d56bb9c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0e4fa3c1f9d8cafecb9671fa76c0ff9156c643e05837804679e5e336bad8f4c1.yml +openapi_spec_hash: 4544b950730b721c252eb519358b8609 +config_hash: 3d7a0bc2844e9fb4797149b233e85770 diff --git a/api.md b/api.md index 86719ecf..fc87a76f 100644 --- a/api.md +++ b/api.md @@ -4,10 +4,12 @@ Types: - BaseOverlay - Extensions +- GetImageAttributesOptions - ImageOverlay - Overlay - OverlayPosition - OverlayTiming +- ResponsiveImageAttributes - SolidColorOverlay - SolidColorOverlayTransformation - SrcOptions diff --git a/src/client.ts b/src/client.ts index 1560e2bc..55424b8f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -911,10 +911,12 @@ export declare namespace ImageKit { export type BaseOverlay = API.BaseOverlay; export type Extensions = API.Extensions; + export type GetImageAttributesOptions = API.GetImageAttributesOptions; export type ImageOverlay = API.ImageOverlay; export type Overlay = API.Overlay; export type OverlayPosition = API.OverlayPosition; export type OverlayTiming = API.OverlayTiming; + export type ResponsiveImageAttributes = API.ResponsiveImageAttributes; export type SolidColorOverlay = API.SolidColorOverlay; export type SolidColorOverlayTransformation = API.SolidColorOverlayTransformation; export type SrcOptions = API.SrcOptions; diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 6d73d695..9ecb5fcf 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -78,6 +78,54 @@ export namespace Extensions { } } +/** + * Options for generating responsive image attributes including `src`, `srcSet`, + * and `sizes` for HTML `` elements. This schema extends `SrcOptions` to add + * support for responsive image generation with breakpoints. + */ +export interface GetImageAttributesOptions extends SrcOptions { + /** + * Custom list of **device-width breakpoints** in pixels. These define common + * screen widths for responsive image generation. + * + * Defaults to `[640, 750, 828, 1080, 1200, 1920, 2048, 3840]`. Sorted + * automatically. + */ + deviceBreakpoints?: Array; + + /** + * Custom list of **image-specific breakpoints** in pixels. Useful for generating + * small variants (e.g., placeholders or thumbnails). + * + * Merged with `deviceBreakpoints` before calculating `srcSet`. Defaults to + * `[16, 32, 48, 64, 96, 128, 256, 384]`. Sorted automatically. + */ + imageBreakpoints?: Array; + + /** + * The value for the HTML `sizes` attribute (e.g., `"100vw"` or + * `"(min-width:768px) 50vw, 100vw"`). + * + * - If it includes one or more `vw` units, breakpoints smaller than the + * corresponding percentage of the smallest device width are excluded. + * - If it contains no `vw` units, the full breakpoint list is used. + * + * Enables a width-based strategy and generates `w` descriptors in `srcSet`. + */ + sizes?: string; + + /** + * The intended display width of the image in pixels, used **only when the `sizes` + * attribute is not provided**. + * + * Triggers a DPR-based strategy (1x and 2x variants) and generates `x` descriptors + * in `srcSet`. + * + * Ignored if `sizes` is present. + */ + width?: number; +} + export interface ImageOverlay extends BaseOverlay { /** * Specifies the relative path to the image used as an overlay. @@ -175,6 +223,34 @@ export interface OverlayTiming { start?: number | string; } +/** + * Resulting set of attributes suitable for an HTML `` element. Useful for + * enabling responsive image loading with `srcSet` and `sizes`. + */ +export interface ResponsiveImageAttributes { + /** + * URL for the _largest_ candidate (assigned to plain `src`). + */ + src: string; + + /** + * `sizes` returned (or synthesised as `100vw`). The value for the HTML `sizes` + * attribute. + */ + sizes?: string; + + /** + * Candidate set with `w` or `x` descriptors. Multiple image URLs separated by + * commas, each with a descriptor. + */ + srcSet?: string; + + /** + * Width as a number (if `width` was provided in the input options). + */ + width?: number; +} + export interface SolidColorOverlay extends BaseOverlay { /** * Specifies the color of the block using an RGB hex code (e.g., `FF0000`), an RGBA From 34840d0b94cdbc5fed95e4f20b938ed3b3cfebc3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:14:22 +0000 Subject: [PATCH 04/44] codegen metadata --- .stats.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 27ad9e7e..e27f9d4f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0e4fa3c1f9d8cafecb9671fa76c0ff9156c643e05837804679e5e336bad8f4c1.yml -openapi_spec_hash: 4544b950730b721c252eb519358b8609 -config_hash: 3d7a0bc2844e9fb4797149b233e85770 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml +openapi_spec_hash: a9aa620376fce66532c84f9364209b0b +config_hash: bb7229ef61cee50cd6c1ac02a5a74e81 From e5bc430c7e00fa4b4911510884d9c9b03c384900 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:32:15 +0000 Subject: [PATCH 05/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index e27f9d4f..df4337df 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: bb7229ef61cee50cd6c1ac02a5a74e81 +config_hash: d7961ccc7e3cafdf8f56d6c89c3c7d62 From 94fff856ff130376b60654347baa19309a9165fc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:32:59 +0000 Subject: [PATCH 06/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index df4337df..43d92ddb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: d7961ccc7e3cafdf8f56d6c89c3c7d62 +config_hash: c2c1c0a5c85d678e5aa8996f3637b616 From b700dc8bbc018d15de3a6a99c234555cba64c363 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 05:45:35 +0000 Subject: [PATCH 07/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 43d92ddb..ae3aead6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: c2c1c0a5c85d678e5aa8996f3637b616 +config_hash: eb4cf65a4c6b26a2901076eff5810d5d From d81e22560aab772ee9b241fb44a50561b8837034 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:44:37 +0000 Subject: [PATCH 08/44] fix(mcpb): pin @anthropic-ai/mcpb version --- packages/mcp-server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 572eee54..084a41f2 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -47,7 +47,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", From 71e22a30017324842fed5ab08ee1efbb1eecb6d2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:11:40 +0000 Subject: [PATCH 09/44] chore(internal): grammar fix (it's -> its) --- packages/mcp-server/src/code-tool.ts | 4 ++-- packages/mcp-server/src/dynamic-tools.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 47d2064a..451a5f3e 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -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 TypeScript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts index 47d60e0d..53676da1 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. * From 451f306f6328e6c48374d0525dad9a0ddc6511d6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:59:18 +0000 Subject: [PATCH 10/44] chore: use structured error when code execution tool errors --- packages/mcp-server/src/code-tool.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 451a5f3e..67754b4b 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 ImageKit, { ClientOptions } from '@imagekit/nodejs'; -import { Endpoint, ContentBlock, Metadata } from './tools/types'; +import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; @@ -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: ImageKit, args: unknown) => { + const handler = async (client: ImageKit, args: unknown): Promise => { const baseURLHostname = new URL(client.baseURL).hostname; const { code } = args as { code: string }; @@ -99,7 +99,7 @@ export async function codeTool(): Promise { } satisfies WorkerInput); req.write(body, (err) => { - if (err !== null && err !== undefined) { + if (err != null) { reject(err); } }); @@ -110,12 +110,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 @@ -135,10 +135,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(); } From 6678ee13cc1eee5ce1ac51b672144be77c38e9ea Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:50:50 +0000 Subject: [PATCH 11/44] chore: mcp code tool explicit error message when missing a run function --- packages/mcp-server/package.json | 4 +- packages/mcp-server/src/code-tool-worker.ts | 47 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 084a41f2..a8f4bb17 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -38,6 +38,7 @@ "express": "^5.1.0", "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", "qs": "^6.14.0", + "typescript": "5.8.3", "yargs": "^17.7.2", "zod": "^3.25.20", "zod-to-json-schema": "^3.24.5", @@ -64,8 +65,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": { "@imagekit/api-mcp": ".", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 865c3928..d553a7aa 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,11 +1,58 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import util from 'node:util'; + +import ts from 'typescript'; + import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; import { ImageKit } from '@imagekit/nodejs'; +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 fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; + + 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 ImageKit({ ...opts, }); From cc68e38fa61078db6a5c961e7ed75dad342dc7e8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:20:42 +0000 Subject: [PATCH 12/44] feat(mcp): enable optional code execution tool on http mcp servers --- packages/mcp-server/src/options.ts | 13 ++++++++++--- packages/mcp-server/tests/options.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) 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/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', () => { From 636829d18a7dda730b65c0549298258afcea6341 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:17:30 +0000 Subject: [PATCH 13/44] chore(mcp): add friendlier MCP code tool errors on incorrect method invocations --- packages/mcp-server/package.json | 1 + packages/mcp-server/src/code-tool-worker.ts | 132 +++++++++++++++++++- packages/mcp-server/src/code-tool.ts | 2 +- 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index a8f4bb17..116b3dc4 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -36,6 +36,7 @@ "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", + "fuse.js": "^7.1.0", "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", "qs": "^6.14.0", "typescript": "5.8.3", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index d553a7aa..2e029c12 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -2,6 +2,7 @@ import util from 'node:util'; +import Fuse from 'fuse.js'; import ts from 'typescript'; import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; @@ -39,8 +40,137 @@ function getRunFunctionNode( return null; } +const fuse = new Fuse( + [ + 'client.customMetadataFields.create', + 'client.customMetadataFields.delete', + 'client.customMetadataFields.list', + 'client.customMetadataFields.update', + 'client.files.copy', + 'client.files.delete', + 'client.files.get', + 'client.files.move', + 'client.files.rename', + 'client.files.update', + 'client.files.upload', + 'client.files.bulk.addTags', + 'client.files.bulk.delete', + 'client.files.bulk.removeAITags', + 'client.files.bulk.removeTags', + 'client.files.versions.delete', + 'client.files.versions.get', + 'client.files.versions.list', + 'client.files.versions.restore', + 'client.files.metadata.get', + 'client.files.metadata.getFromURL', + 'client.assets.list', + 'client.cache.invalidation.create', + 'client.cache.invalidation.get', + 'client.folders.copy', + 'client.folders.create', + 'client.folders.delete', + 'client.folders.move', + 'client.folders.rename', + 'client.folders.job.get', + 'client.accounts.usage.get', + 'client.accounts.origins.create', + 'client.accounts.origins.delete', + 'client.accounts.origins.get', + 'client.accounts.origins.list', + 'client.accounts.origins.update', + 'client.accounts.urlEndpoints.create', + 'client.accounts.urlEndpoints.delete', + 'client.accounts.urlEndpoints.get', + 'client.accounts.urlEndpoints.list', + 'client.accounts.urlEndpoints.update', + 'client.beta.v2.files.upload', + 'client.webhooks.unsafeUnwrap', + 'client.webhooks.unwrap', + ], + { 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; +} + 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) { @@ -73,7 +203,7 @@ const fetch = async (req: Request): Promise => { ${code} run_ = run; `); - const result = await run_(client); + const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, logLines, diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 67754b4b..62f4d7fb 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -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 TypeScript 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' } } }, }; From 25e4e59f434c07c2d9afeef3f548dd99e250a7ab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:24:08 +0000 Subject: [PATCH 14/44] chore(mcp): add line numbers to code tool errors --- packages/mcp-server/src/code-tool-worker.ts | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 2e029c12..36f587e2 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -160,6 +160,25 @@ function makeSdkProxy(obj: T, { path, isBelievedBad = false }: 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) { @@ -199,10 +218,7 @@ const fetch = async (req: Request): Promise => { }; try { let run_ = async (client: any) => {}; - eval(` - ${code} - run_ = run; - `); + eval(`${code}\nrun_ = run;`); const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, @@ -210,10 +226,9 @@ const fetch = async (req: Request): Promise => { 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' }, ); From a7575d308e3038715964f7416284bb012e27b240 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:16:21 +0000 Subject: [PATCH 15/44] docs(mcp): add a README button for one-click add to Cursor --- packages/mcp-server/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 38260a76..69bf7e58 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -36,6 +36,13 @@ 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=@imagekit/api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBpbWFnZWtpdC9hcGktbWNwIl0sImVudiI6eyJJTUFHRUtJVF9QUklWQVRFX0tFWSI6IlNldCB5b3VyIElNQUdFS0lUX1BSSVZBVEVfS0VZIGhlcmUuIiwiT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIjoiU2V0IHlvdXIgT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIGhlcmUuIiwiSU1BR0VLSVRfV0VCSE9PS19TRUNSRVQiOiJTZXQgeW91ciBJTUFHRUtJVF9XRUJIT09LX1NFQ1JFVCBoZXJlLiJ9fQ) + ## Exposing endpoints to your MCP Client There are two ways to expose endpoints as tools in the MCP server: From 8c9026ace5d217264e5753c01309a02a2c09095e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 00:40:20 +0000 Subject: [PATCH 16/44] chore(internal): codegen related update --- packages/mcp-server/README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 69bf7e58..f408fab1 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -38,17 +38,18 @@ 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. +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=@imagekit/api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBpbWFnZWtpdC9hcGktbWNwIl0sImVudiI6eyJJTUFHRUtJVF9QUklWQVRFX0tFWSI6IlNldCB5b3VyIElNQUdFS0lUX1BSSVZBVEVfS0VZIGhlcmUuIiwiT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIjoiU2V0IHlvdXIgT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIGhlcmUuIiwiSU1BR0VLSVRfV0VCSE9PS19TRUNSRVQiOiJTZXQgeW91ciBJTUFHRUtJVF9XRUJIT09LX1NFQ1JFVCBoZXJlLiJ9fQ) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@imagekit/api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBpbWFnZWtpdC9hcGktbWNwIl0sImVudiI6eyJJTUFHRUtJVF9QUklWQVRFX0tFWSI6IlNldCB5b3VyIElNQUdFS0lUX1BSSVZBVEVfS0VZIGhlcmUuIiwiT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIjoiU2V0IHlvdXIgT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIGhlcmUuIiwiSU1BR0VLSVRfV0VCSE9PS19TRUNSRVQiOiJTZXQgeW91ciBJTUFHRUtJVF9XRUJIT09LX1NFQ1JFVCBoZXJlLiJ9fQ) ## 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 @@ -83,6 +84,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. From 2a90d2802e4814d97ec5bba77097eec2f0a5a718 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:41:53 +0000 Subject: [PATCH 17/44] docs(mcp): add a README link to add server to VS Code or Claude Code --- packages/mcp-server/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index f408fab1..2c75fe1f 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -43,6 +43,22 @@ in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > Ne [![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@imagekit/api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBpbWFnZWtpdC9hcGktbWNwIl0sImVudiI6eyJJTUFHRUtJVF9QUklWQVRFX0tFWSI6IlNldCB5b3VyIElNQUdFS0lUX1BSSVZBVEVfS0VZIGhlcmUuIiwiT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIjoiU2V0IHlvdXIgT1BUSU9OQUxfSU1BR0VLSVRfSUdOT1JFU19USElTIGhlcmUuIiwiSU1BR0VLSVRfV0VCSE9PS19TRUNSRVQiOiJTZXQgeW91ciBJTUFHRUtJVF9XRUJIT09LX1NFQ1JFVCBoZXJlLiJ9fQ) +### 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%22%40imagekit%2Fapi-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40imagekit%2Fapi-mcp%22%5D%2C%22env%22%3A%7B%22IMAGEKIT_PRIVATE_KEY%22%3A%22Set%20your%20IMAGEKIT_PRIVATE_KEY%20here.%22%2C%22OPTIONAL_IMAGEKIT_IGNORES_THIS%22%3A%22Set%20your%20OPTIONAL_IMAGEKIT_IGNORES_THIS%20here.%22%2C%22IMAGEKIT_WEBHOOK_SECRET%22%3A%22Set%20your%20IMAGEKIT_WEBHOOK_SECRET%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 imagekit_nodejs_api --env IMAGEKIT_PRIVATE_KEY="Your IMAGEKIT_PRIVATE_KEY here." OPTIONAL_IMAGEKIT_IGNORES_THIS="Your OPTIONAL_IMAGEKIT_IGNORES_THIS here." IMAGEKIT_WEBHOOK_SECRET="Your IMAGEKIT_WEBHOOK_SECRET here." -- npx -y @imagekit/api-mcp +``` + ## Exposing endpoints to your MCP Client There are three ways to expose endpoints as tools in the MCP server: From 662aa87091dc519bb811554438eaf978660d035e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:05:03 +0000 Subject: [PATCH 18/44] chore(internal): codegen related update --- packages/mcp-server/src/code-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 62f4d7fb..74b830ac 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -23,7 +23,7 @@ export async function codeTool(): Promise { const tool: Tool = { name: 'execute', description: - 'Runs TypeScript 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.', + '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' } } }, }; From 00789acfb9ceb07af0cd03b1951090f430691592 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:17:56 +0000 Subject: [PATCH 19/44] chore(mcp): clarify http auth error --- packages/mcp-server/src/headers.ts | 4 +++- packages/mcp-server/src/http.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts index 63e3abc9..40fd3090 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/headers.ts @@ -15,7 +15,9 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = password: rawValue.slice(rawValue.search(':') + 1), }; default: - throw new Error(`Unsupported authorization scheme`); + throw new Error( + 'Unsupported authorization scheme. Expected the "Authorization" header to be a supported scheme (Basic).', + ); } } 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; From d1949dbef79859b446ee9ee2c8a2d562568c1cca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:07:31 +0000 Subject: [PATCH 20/44] fix(mcp): return tool execution error on jq failure --- packages/mcp-server/src/filtering.ts | 4 ++++ .../create-accounts-url-endpoints.ts | 13 ++++++++++--- .../url-endpoints/get-accounts-url-endpoints.ts | 13 ++++++++++--- .../list-accounts-url-endpoints.ts | 13 ++++++++++--- .../update-accounts-url-endpoints.ts | 17 ++++++++++++----- .../tools/accounts/usage/get-accounts-usage.ts | 13 ++++++++++--- .../mcp-server/src/tools/assets/list-assets.ts | 13 ++++++++++--- .../invalidation/create-cache-invalidation.ts | 13 ++++++++++--- .../invalidation/get-cache-invalidation.ts | 13 ++++++++++--- .../create-custom-metadata-fields.ts | 13 ++++++++++--- .../delete-custom-metadata-fields.ts | 13 ++++++++++--- .../list-custom-metadata-fields.ts | 13 ++++++++++--- .../update-custom-metadata-fields.ts | 17 ++++++++++++----- .../src/tools/files/bulk/add-tags-files-bulk.ts | 13 ++++++++++--- .../src/tools/files/bulk/delete-files-bulk.ts | 13 ++++++++++--- .../files/bulk/remove-ai-tags-files-bulk.ts | 13 ++++++++++--- .../tools/files/bulk/remove-tags-files-bulk.ts | 13 ++++++++++--- .../mcp-server/src/tools/files/copy-files.ts | 13 ++++++++++--- .../mcp-server/src/tools/files/get-files.ts | 13 ++++++++++--- .../tools/files/metadata/get-files-metadata.ts | 13 ++++++++++--- .../metadata/get-from-url-files-metadata.ts | 13 ++++++++++--- .../mcp-server/src/tools/files/move-files.ts | 13 ++++++++++--- .../mcp-server/src/tools/files/rename-files.ts | 13 ++++++++++--- .../files/versions/delete-files-versions.ts | 17 ++++++++++++----- .../tools/files/versions/get-files-versions.ts | 15 ++++++++++++--- .../tools/files/versions/list-files-versions.ts | 13 ++++++++++--- .../files/versions/restore-files-versions.ts | 17 ++++++++++++----- .../src/tools/folders/copy-folders.ts | 13 ++++++++++--- .../src/tools/folders/create-folders.ts | 13 ++++++++++--- .../src/tools/folders/delete-folders.ts | 13 ++++++++++--- .../src/tools/folders/job/get-folders-job.ts | 13 ++++++++++--- .../src/tools/folders/move-folders.ts | 13 ++++++++++--- .../src/tools/folders/rename-folders.ts | 13 ++++++++++--- packages/mcp-server/src/tools/types.ts | 12 ++++++++++++ 34 files changed, 346 insertions(+), 104 deletions(-) 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/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts index a4152360..cbb6c21c 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -97,7 +97,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.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/accounts/url-endpoints/get-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts index 440628ad..e2dbdc8c 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -43,7 +43,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(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/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts index 5bb31895..eacc7b0c 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -38,7 +38,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.list())); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.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/accounts/url-endpoints/update-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts index 6e9969b2..71732222 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -104,9 +104,16 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(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/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts index d39f75c4..93c446e6 100644 --- a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts +++ b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -50,7 +50,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(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/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts index 180a2750..e62f9cb0 100644 --- a/packages/mcp-server/src/tools/assets/list-assets.ts +++ b/packages/mcp-server/src/tools/assets/list-assets.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -88,7 +88,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(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/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts index 47d7e67d..9f0a5f57 100644 --- a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts +++ b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -40,7 +40,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.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/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts index 78b3a46b..b07a90c9 100644 --- a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts +++ b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { requestId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts index 67cf2ec6..30c1b40f 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -148,7 +148,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.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/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts index 334361cf..1816da4b 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(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/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts index 9aa77973..7ce27b25 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -47,7 +47,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(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/custom-metadata-fields/update-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts index f49b10dd..942cca71 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -142,9 +142,16 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.customMetadataFields.update(id, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.customMetadataFields.update(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/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts index 3c163c97..20668c26 100644 --- a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -50,7 +50,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(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/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts index 8527fb8d..8e8f47ac 100644 --- a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -43,7 +43,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(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/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts index 3632b0c1..dcd48ff2 100644 --- a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -50,7 +50,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(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/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts index 63efd0e0..cdcec883 100644 --- a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -50,7 +50,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(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/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts index 27fd670a..365a8e34 100644 --- a/packages/mcp-server/src/tools/files/copy-files.ts +++ b/packages/mcp-server/src/tools/files/copy-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -49,7 +49,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(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/get-files.ts b/packages/mcp-server/src/tools/files/get-files.ts index 958ab961..6c1f6601 100644 --- a/packages/mcp-server/src/tools/files/get-files.ts +++ b/packages/mcp-server/src/tools/files/get-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { fileId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); + } 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/metadata/get-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts index 04cf9ad4..2ff4181f 100644 --- a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts +++ b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { fileId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); + } 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/metadata/get-from-url-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts index 08ec83c9..7b5a452f 100644 --- a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts +++ b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -42,7 +42,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(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/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts index 4539db6e..10c3b441 100644 --- a/packages/mcp-server/src/tools/files/move-files.ts +++ b/packages/mcp-server/src/tools/files/move-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(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/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts index 97484634..3ab38f22 100644 --- a/packages/mcp-server/src/tools/files/rename-files.ts +++ b/packages/mcp-server/src/tools/files/rename-files.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -52,7 +52,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(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/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts index 17700066..39186f46 100644 --- a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -44,9 +44,16 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { versionId, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.delete(versionId, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.delete(versionId, 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/versions/get-files-versions.ts b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts index bf05d7d1..388d74eb 100644 --- a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -44,7 +44,16 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { versionId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.get(versionId, body))); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.get(versionId, 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/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts index b520c583..4c852e1b 100644 --- a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { fileId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); + } 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/versions/restore-files-versions.ts b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts index fd9a04bf..f74cb7dc 100644 --- a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -44,9 +44,16 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { versionId, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.restore(versionId, body)), - ); + try { + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.restore(versionId, 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/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts index 8dd9e326..f53734a3 100644 --- a/packages/mcp-server/src/tools/folders/copy-folders.ts +++ b/packages/mcp-server/src/tools/folders/copy-folders.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -49,7 +49,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(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/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts index 70d49c57..cf8ec547 100644 --- a/packages/mcp-server/src/tools/folders/create-folders.ts +++ b/packages/mcp-server/src/tools/folders/create-folders.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -46,7 +46,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.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/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts index b8e4f32b..3229954e 100644 --- a/packages/mcp-server/src/tools/folders/delete-folders.ts +++ b/packages/mcp-server/src/tools/folders/delete-folders.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -42,7 +42,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(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/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts index a2704f98..3dc4d8eb 100644 --- a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts +++ b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jobId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts index e315003a..d5643f5f 100644 --- a/packages/mcp-server/src/tools/folders/move-folders.ts +++ b/packages/mcp-server/src/tools/folders/move-folders.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(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/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts index 2822faac..cb881ee2 100644 --- a/packages/mcp-server/src/tools/folders/rename-folders.ts +++ b/packages/mcp-server/src/tools/folders/rename-folders.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -50,7 +50,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(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 8106d499..715d3422 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 Date: Wed, 12 Nov 2025 20:50:27 +0000 Subject: [PATCH 21/44] chore(mcp): upgrade jq-web --- packages/mcp-server/package.json | 2 +- packages/mcp-server/yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 116b3dc4..98b9bb5b 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -37,7 +37,7 @@ "cors": "^2.8.5", "express": "^5.1.0", "fuse.js": "^7.1.0", - "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "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", 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" From f36d79523b0613a27afd45d56f4e1e906e6fdfe9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 18:09:22 +0000 Subject: [PATCH 22/44] feat(mcp): add detail field to docs search tool --- packages/mcp-server/src/docs-search-tool.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index f05cd9b2..e4225125 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'], From 6269318024cd320f3038def32c3d5bf1f1da77a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:38:36 +0000 Subject: [PATCH 23/44] chore(client): fix logger property type --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 55424b8f..54169b41 100644 --- a/src/client.ts +++ b/src/client.ts @@ -189,7 +189,7 @@ export class ImageKit { baseURL: string; maxRetries: number; timeout: number; - logger: Logger | undefined; + logger: Logger; logLevel: LogLevel | undefined; fetchOptions: MergedRequestInit | undefined; From 1e866f8e5254ecc305b3dfee53ec232455143cc4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:01:56 +0000 Subject: [PATCH 24/44] fix(mcp): return tool execution error on api error --- .../tools/accounts/origins/create-accounts-origins.ts | 11 +++++++++-- .../tools/accounts/origins/get-accounts-origins.ts | 11 +++++++++-- .../tools/accounts/origins/list-accounts-origins.ts | 11 +++++++++-- .../tools/accounts/origins/update-accounts-origins.ts | 11 +++++++++-- .../url-endpoints/create-accounts-url-endpoints.ts | 2 +- .../url-endpoints/get-accounts-url-endpoints.ts | 2 +- .../url-endpoints/list-accounts-url-endpoints.ts | 2 +- .../url-endpoints/update-accounts-url-endpoints.ts | 2 +- .../src/tools/accounts/usage/get-accounts-usage.ts | 2 +- packages/mcp-server/src/tools/assets/list-assets.ts | 2 +- .../src/tools/beta/v2/files/upload-v2-beta-files.ts | 11 +++++++++-- .../cache/invalidation/create-cache-invalidation.ts | 2 +- .../cache/invalidation/get-cache-invalidation.ts | 2 +- .../create-custom-metadata-fields.ts | 2 +- .../delete-custom-metadata-fields.ts | 2 +- .../list-custom-metadata-fields.ts | 2 +- .../update-custom-metadata-fields.ts | 2 +- .../src/tools/files/bulk/add-tags-files-bulk.ts | 2 +- .../src/tools/files/bulk/delete-files-bulk.ts | 2 +- .../src/tools/files/bulk/remove-ai-tags-files-bulk.ts | 2 +- .../src/tools/files/bulk/remove-tags-files-bulk.ts | 2 +- packages/mcp-server/src/tools/files/copy-files.ts | 2 +- packages/mcp-server/src/tools/files/get-files.ts | 2 +- .../src/tools/files/metadata/get-files-metadata.ts | 2 +- .../files/metadata/get-from-url-files-metadata.ts | 2 +- packages/mcp-server/src/tools/files/move-files.ts | 2 +- packages/mcp-server/src/tools/files/rename-files.ts | 2 +- packages/mcp-server/src/tools/files/update-files.ts | 11 +++++++++-- packages/mcp-server/src/tools/files/upload-files.ts | 11 +++++++++-- .../src/tools/files/versions/delete-files-versions.ts | 2 +- .../src/tools/files/versions/get-files-versions.ts | 2 +- .../src/tools/files/versions/list-files-versions.ts | 2 +- .../tools/files/versions/restore-files-versions.ts | 2 +- packages/mcp-server/src/tools/folders/copy-folders.ts | 2 +- .../mcp-server/src/tools/folders/create-folders.ts | 2 +- .../mcp-server/src/tools/folders/delete-folders.ts | 2 +- .../src/tools/folders/job/get-folders-job.ts | 2 +- packages/mcp-server/src/tools/folders/move-folders.ts | 2 +- .../mcp-server/src/tools/folders/rename-folders.ts | 2 +- 39 files changed, 95 insertions(+), 46 deletions(-) diff --git a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts index e46f5680..b802ca2a 100644 --- a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts +++ b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -312,7 +312,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const body = args as any; - return asTextContentResult(await client.accounts.origins.create(body)); + try { + return asTextContentResult(await client.accounts.origins.create(body)); + } catch (error) { + if (error instanceof ImageKit.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts index 86107b52..c699ff3c 100644 --- a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts +++ b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -35,7 +35,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { id, ...body } = args as any; - return asTextContentResult(await client.accounts.origins.get(id)); + try { + return asTextContentResult(await client.accounts.origins.get(id)); + } catch (error) { + if (error instanceof ImageKit.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts index 0510104b..e82908d6 100644 --- a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts +++ b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -29,7 +29,14 @@ export const tool: Tool = { }; export const handler = async (client: ImageKit, args: Record | undefined) => { - return asTextContentResult(await client.accounts.origins.list()); + try { + return asTextContentResult(await client.accounts.origins.list()); + } catch (error) { + if (error instanceof ImageKit.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts index 91019fb7..515081fd 100644 --- a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts +++ b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -354,7 +354,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { id, ...body } = args as any; - return asTextContentResult(await client.accounts.origins.update(id, body)); + try { + return asTextContentResult(await client.accounts.origins.update(id, body)); + } catch (error) { + if (error instanceof ImageKit.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts index cbb6c21c..4696ba95 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts @@ -100,7 +100,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts index e2dbdc8c..5199e01f 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts @@ -46,7 +46,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(id))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts index eacc7b0c..a8d6262f 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts @@ -41,7 +41,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.list())); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts index 71732222..a1d5f56f 100644 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts @@ -109,7 +109,7 @@ export const handler = async (client: ImageKit, args: Record | await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts index 93c446e6..dffb2647 100644 --- a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts +++ b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts @@ -53,7 +53,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts index e62f9cb0..007773e3 100644 --- a/packages/mcp-server/src/tools/assets/list-assets.ts +++ b/packages/mcp-server/src/tools/assets/list-assets.ts @@ -91,7 +91,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts index 9a45009d..533c6d33 100644 --- a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts +++ b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -310,7 +310,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const body = args as any; - return asTextContentResult(await client.beta.v2.files.upload(body)); + try { + return asTextContentResult(await client.beta.v2.files.upload(body)); + } catch (error) { + if (error instanceof ImageKit.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts index 9f0a5f57..d0a79cdc 100644 --- a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts +++ b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts @@ -43,7 +43,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts index b07a90c9..6fca57fe 100644 --- a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts +++ b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts @@ -44,7 +44,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts index 30c1b40f..666f950d 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts @@ -151,7 +151,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts index 1816da4b..75a37e22 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts @@ -44,7 +44,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(id))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts index 7ce27b25..a473365d 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts @@ -50,7 +50,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts index 942cca71..7bb0470f 100644 --- a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts +++ b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts @@ -147,7 +147,7 @@ export const handler = async (client: ImageKit, args: Record | await maybeFilter(jq_filter, await client.customMetadataFields.update(id, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts index 20668c26..efd2ad77 100644 --- a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts @@ -53,7 +53,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts index 8e8f47ac..3ba1f4c7 100644 --- a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts @@ -46,7 +46,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts index dcd48ff2..a2cdf3bf 100644 --- a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts @@ -53,7 +53,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts index cdcec883..62520e20 100644 --- a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts +++ b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts @@ -53,7 +53,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts index 365a8e34..e69f89ee 100644 --- a/packages/mcp-server/src/tools/files/copy-files.ts +++ b/packages/mcp-server/src/tools/files/copy-files.ts @@ -52,7 +52,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/get-files.ts b/packages/mcp-server/src/tools/files/get-files.ts index 6c1f6601..8606b93c 100644 --- a/packages/mcp-server/src/tools/files/get-files.ts +++ b/packages/mcp-server/src/tools/files/get-files.ts @@ -44,7 +44,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts index 2ff4181f..4bc2a401 100644 --- a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts +++ b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts @@ -44,7 +44,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts index 7b5a452f..f9aac486 100644 --- a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts +++ b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts @@ -45,7 +45,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts index 10c3b441..c044a04e 100644 --- a/packages/mcp-server/src/tools/files/move-files.ts +++ b/packages/mcp-server/src/tools/files/move-files.ts @@ -47,7 +47,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts index 3ab38f22..046afef5 100644 --- a/packages/mcp-server/src/tools/files/rename-files.ts +++ b/packages/mcp-server/src/tools/files/rename-files.ts @@ -55,7 +55,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/update-files.ts b/packages/mcp-server/src/tools/files/update-files.ts index e21194ca..99218ddf 100644 --- a/packages/mcp-server/src/tools/files/update-files.ts +++ b/packages/mcp-server/src/tools/files/update-files.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -190,7 +190,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const { fileId, ...body } = args as any; - return asTextContentResult(await client.files.update(fileId, body)); + try { + return asTextContentResult(await client.files.update(fileId, body)); + } catch (error) { + if (error instanceof ImageKit.APIError) { + 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 60e8fdda..d49e5a75 100644 --- a/packages/mcp-server/src/tools/files/upload-files.ts +++ b/packages/mcp-server/src/tools/files/upload-files.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; +import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import ImageKit from '@imagekit/nodejs'; @@ -326,7 +326,14 @@ export const tool: Tool = { export const handler = async (client: ImageKit, args: Record | undefined) => { const body = args as any; - return asTextContentResult(await client.files.upload(body)); + try { + return asTextContentResult(await client.files.upload(body)); + } catch (error) { + if (error instanceof ImageKit.APIError) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts index 39186f46..6903001d 100644 --- a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts @@ -49,7 +49,7 @@ export const handler = async (client: ImageKit, args: Record | await maybeFilter(jq_filter, await client.files.versions.delete(versionId, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts index 388d74eb..dfe562ab 100644 --- a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts @@ -49,7 +49,7 @@ export const handler = async (client: ImageKit, args: Record | await maybeFilter(jq_filter, await client.files.versions.get(versionId, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts index 4c852e1b..eb6b82c9 100644 --- a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts @@ -44,7 +44,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts index f74cb7dc..dc4f76c4 100644 --- a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts +++ b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts @@ -49,7 +49,7 @@ export const handler = async (client: ImageKit, args: Record | await maybeFilter(jq_filter, await client.files.versions.restore(versionId, body)), ); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts index f53734a3..f88e720a 100644 --- a/packages/mcp-server/src/tools/folders/copy-folders.ts +++ b/packages/mcp-server/src/tools/folders/copy-folders.ts @@ -52,7 +52,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts index cf8ec547..c8fa02c1 100644 --- a/packages/mcp-server/src/tools/folders/create-folders.ts +++ b/packages/mcp-server/src/tools/folders/create-folders.ts @@ -49,7 +49,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.folders.create(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts index 3229954e..231947a3 100644 --- a/packages/mcp-server/src/tools/folders/delete-folders.ts +++ b/packages/mcp-server/src/tools/folders/delete-folders.ts @@ -45,7 +45,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts index 3dc4d8eb..b3a149ab 100644 --- a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts +++ b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts @@ -44,7 +44,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts index d5643f5f..5589db62 100644 --- a/packages/mcp-server/src/tools/folders/move-folders.ts +++ b/packages/mcp-server/src/tools/folders/move-folders.ts @@ -47,7 +47,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; diff --git a/packages/mcp-server/src/tools/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts index cb881ee2..60c7c121 100644 --- a/packages/mcp-server/src/tools/folders/rename-folders.ts +++ b/packages/mcp-server/src/tools/folders/rename-folders.ts @@ -53,7 +53,7 @@ export const handler = async (client: ImageKit, args: Record | try { return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(body))); } catch (error) { - if (isJqError(error)) { + if (error instanceof ImageKit.APIError || isJqError(error)) { return asErrorResult(error.message); } throw error; From 6118fe4804be492482268ff25e0afc714bea7613 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:14:21 +0000 Subject: [PATCH 25/44] feat(mcp): return logs on code tool errors --- packages/mcp-server/src/code-tool-types.ts | 6 ++++- packages/mcp-server/src/code-tool-worker.ts | 6 +++++ packages/mcp-server/src/code-tool.ts | 25 +++++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts index 02e7e890..d3906768 100644 --- a/packages/mcp-server/src/code-tool-types.ts +++ b/packages/mcp-server/src/code-tool-types.ts @@ -11,4 +11,8 @@ export type WorkerSuccess = { logLines: string[]; errLines: string[]; }; -export type WorkerError = { message: string | undefined }; +export type WorkerError = { + message: string | undefined; + logLines: string[]; + errLines: string[]; +}; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 36f587e2..391d5388 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -186,6 +186,8 @@ const fetch = async (req: Request): Promise => { { 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```', + logLines: [], + errLines: [], } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); @@ -197,6 +199,8 @@ const fetch = async (req: Request): Promise => { { 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```', + logLines: [], + errLines: [], } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); @@ -229,6 +233,8 @@ const fetch = async (req: Request): Promise => { return Response.json( { message: parseError(code, e), + logLines, + errLines, } 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 74b830ac..248f4ee0 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -134,9 +134,30 @@ export async function codeTool(): Promise { content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), }; } else { - const { message } = (await resp.json()) as WorkerError; + const { message, logLines, errLines } = (await resp.json()) as WorkerError; + const messageOutput: ContentBlock | null = + message == null ? null : ( + { + type: 'text', + text: message, + } + ); + const logOutput: ContentBlock | null = + logLines.length === 0 ? + null + : { + type: 'text', + text: logLines.join('\n'), + }; + const errOutput: ContentBlock | null = + errLines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + errLines.join('\n'), + }; return { - content: message == null ? [] : [{ type: 'text', text: message }], + content: [messageOutput, logOutput, errOutput].filter((block) => block !== null), isError: true, }; } From 310bf0d0cc1d5b15d08a2cbb483a9969ebf4f11d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:02:51 +0000 Subject: [PATCH 26/44] chore(internal): upgrade eslint --- package.json | 2 +- yarn.lock | 150 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 89 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index efd98c94..111cd791 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@types/node": "^20.17.6", "@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/parser": "8.31.1", - "eslint": "^9.20.1", + "eslint": "^9.39.1", "eslint-plugin-prettier": "^5.4.1", "eslint-plugin-unused-imports": "^4.1.4", "iconv-lite": "^0.6.3", diff --git a/yarn.lock b/yarn.lock index 1935915b..2180b9e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -350,45 +350,52 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": +"@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/eslint-utils@^4.8.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.0": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== +"@eslint/config-array@^0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== dependencies: - "@eslint/object-schema" "^2.1.6" + "@eslint/object-schema" "^2.1.7" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" - integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== dependencies: - "@types/json-schema" "^7.0.15" + "@eslint/core" "^0.17.0" -"@eslint/core@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" - integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== +"@eslint/eslintrc@^3.3.1": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -396,26 +403,26 @@ globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.0" + js-yaml "^4.1.1" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.20.0": - version "9.20.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" - integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== +"@eslint/js@9.39.1": + version "9.39.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.1.tgz#0dd59c3a9f40e3f1882975c321470969243e0164" + integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw== -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== -"@eslint/plugin-kit@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" - integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== dependencies: - "@eslint/core" "^0.10.0" + "@eslint/core" "^0.17.0" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -441,10 +448,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== +"@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -1062,6 +1069,11 @@ acorn@^8.14.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + acorn@^8.4.1: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -1565,15 +1577,15 @@ eslint-plugin-unused-imports@^4.1.4: resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -1583,31 +1595,36 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.20.1: - version "9.20.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" - integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.39.1: + version "9.39.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.1.tgz#be8bf7c6de77dcc4252b5a8dcb31c2efff74a6e5" + integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.11.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.20.0" - "@eslint/plugin-kit" "^0.2.5" + "@eslint/config-array" "^0.21.1" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.39.1" + "@eslint/plugin-kit" "^0.4.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" + "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -1623,7 +1640,7 @@ eslint@^9.20.1: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: +espree@^10.0.1: version "10.3.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== @@ -1632,6 +1649,15 @@ espree@^10.0.1, espree@^10.3.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.0" +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -2450,10 +2476,10 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" From f7b9b4e55919b644d9445730c03c8972ecdfe0b7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:28:44 +0000 Subject: [PATCH 27/44] chore: use latest @modelcontextprotocol/sdk --- packages/mcp-server/cloudflare-worker/package.json | 2 +- packages/mcp-server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/cloudflare-worker/package.json b/packages/mcp-server/cloudflare-worker/package.json index 85f7956d..1067db1d 100644 --- a/packages/mcp-server/cloudflare-worker/package.json +++ b/packages/mcp-server/cloudflare-worker/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@cloudflare/workers-oauth-provider": "^0.0.5", - "@modelcontextprotocol/sdk": "^1.11.4", + "@modelcontextprotocol/sdk": "^1.24.0", "agents": "^0.0.88", "hono": "^4.7.9", "@imagekit/api-mcp": "latest", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 98b9bb5b..e51e3eb2 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -32,7 +32,7 @@ "dependencies": { "@imagekit/nodejs": "file:../../dist/", "@cloudflare/cabidela": "^0.2.4", - "@modelcontextprotocol/sdk": "^1.11.5", + "@modelcontextprotocol/sdk": "^1.24.0", "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", From 63ab735bf6c8458a53bd66c8c437b45a7aef60fe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:22:30 +0000 Subject: [PATCH 28/44] feat(mcp): add typescript check to code execution tool --- packages/mcp-server/src/code-tool-worker.ts | 107 ++++++++++++++++---- packages/mcp-server/src/code-tool.ts | 16 ++- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 391d5388..f6394239 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import path from 'node:path'; import util from 'node:util'; import Fuse from 'fuse.js'; @@ -8,30 +9,41 @@ import ts from 'typescript'; import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; import { ImageKit } from '@imagekit/nodejs'; -function getRunFunctionNode( - code: string, -): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { +function getRunFunctionSource(code: string): { + type: 'declaration' | 'expression'; + client: string | undefined; + code: string; +} | null { const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + const printer = ts.createPrinter(); for (const statement of sourceFile.statements) { // Check for top-level function declarations if (ts.isFunctionDeclaration(statement)) { if (statement.name?.text === 'run') { - return statement; + return { + type: 'declaration', + client: statement.parameters[0]?.name.getText(), + code: printer.printNode(ts.EmitHint.Unspecified, statement.body!, sourceFile), + }; } } // 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') { + 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; - } + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return { + type: 'expression', + client: declaration.initializer.parameters[0]?.name.getText(), + code: printer.printNode(ts.EmitHint.Unspecified, declaration.initializer, sourceFile), + }; } } } @@ -40,6 +52,61 @@ function getRunFunctionNode( return null; } +function getTSDiagnostics(code: string): string[] { + const functionSource = getRunFunctionSource(code)!; + const codeWithImport = [ + 'import { ImageKit } from "@imagekit/nodejs";', + functionSource.type === 'declaration' ? + `async function run(${functionSource.client}: ImageKit)` + : `const run: (${functionSource.client}: ImageKit) => Promise =`, + functionSource.code, + ].join('\n'); + const sourcePath = path.resolve('code.ts'); + const ast = ts.createSourceFile(sourcePath, codeWithImport, ts.ScriptTarget.Latest, true); + const options = ts.getDefaultCompilerOptions(); + options.target = ts.ScriptTarget.Latest; + options.module = ts.ModuleKind.NodeNext; + options.moduleResolution = ts.ModuleResolutionKind.NodeNext; + const host = ts.createCompilerHost(options, true); + const newHost: typeof host = { + ...host, + getSourceFile: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return ast; + } + return host.getSourceFile(...args); + }, + readFile: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return codeWithImport; + } + return host.readFile(...args); + }, + fileExists: (...args) => { + if (path.resolve(args[0]) === sourcePath) { + return true; + } + return host.fileExists(...args); + }, + }; + const program = ts.createProgram({ + options, + rootNames: [sourcePath], + host: newHost, + }); + const diagnostics = ts.getPreEmitDiagnostics(program, ast); + return diagnostics.map((d) => { + const message = ts.flattenDiagnosticMessageText(d.messageText, '\n'); + if (!d.file || !d.start) return `- ${message}`; + const { line: tsLine } = ts.getLineAndCharacterOfPosition(d.file, d.start); + // We add two lines in the beginning, for the client import and the function declaration. + // So the actual (zero-based) line number is tsLine - 2. + const lineNumber = tsLine - 2; + const line = code.split('\n').at(lineNumber)?.trim(); + return line ? `- ${message}\n at line ${lineNumber + 1}\n ${line}` : `- ${message}`; + }); +} + const fuse = new Fuse( [ 'client.customMetadataFields.create', @@ -181,11 +248,16 @@ function parseError(code: string, error: unknown): string | undefined { const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; - if (code == null) { + + const runFunctionSource = code ? getRunFunctionSource(code) : null; + if (!runFunctionSource) { + const message = + code ? + 'The code is missing a top-level `run` function.' + : 'The code argument is missing. Provide one containing a top-level `run` function.'; 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```', + message: `${message} Write code within this template:\n\n\`\`\`\nasync function run(client) {\n // Fill this out\n}\n\`\`\``, logLines: [], errLines: [], } satisfies WorkerError, @@ -193,12 +265,11 @@ const fetch = async (req: Request): Promise => { ); } - const runFunctionNode = getRunFunctionNode(code); - if (!runFunctionNode) { + const diagnostics = getTSDiagnostics(code); + if (diagnostics.length > 0) { 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```', + message: `The code contains TypeScript diagnostics:\n${diagnostics.join('\n')}`, logLines: [], errLines: [], } satisfies WorkerError, diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 248f4ee0..82d11938 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { dirname } from 'node:path'; -import { pathToFileURL } from 'node:url'; +import path from 'node:path'; +import url from 'node:url'; import ImageKit, { ClientOptions } from '@imagekit/nodejs'; import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; @@ -35,10 +35,16 @@ export async function codeTool(): Promise { const baseURLHostname = new URL(client.baseURL).hostname; const { code } = args as { code: string }; - const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { + const allowRead = [ + 'code-tool-worker.mjs', + `${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + path.resolve(path.dirname(workerPath), '..'), + ].join(','); + + const worker = await newDenoHTTPWorker(url.pathToFileURL(workerPath), { runFlags: [ `--node-modules-dir=manual`, - `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + `--allow-read=${allowRead}`, `--allow-net=${baseURLHostname}`, // Allow environment variables because instantiating the client will try to read from them, // even though they are not set. @@ -46,7 +52,7 @@ export async function codeTool(): Promise { ], printOutput: true, spawnOptions: { - cwd: dirname(workerPath), + cwd: path.dirname(workerPath), }, }); From eb22f0883fc74c94959fdd97e50d41d940e1aed6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:01:40 +0000 Subject: [PATCH 29/44] feat(mcp): handle code mode calls in the Stainless API Moves the code-mode execution to an endpoint in the Stainless API. --- packages/mcp-server/src/code-tool-paths.cts | 3 - packages/mcp-server/src/code-tool-worker.ts | 315 -------------------- packages/mcp-server/src/code-tool.ts | 186 +++--------- packages/mcp-server/src/docs-search-tool.ts | 7 + 4 files changed, 43 insertions(+), 468 deletions(-) delete mode 100644 packages/mcp-server/src/code-tool-paths.cts delete mode 100644 packages/mcp-server/src/code-tool-worker.ts diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts deleted file mode 100644 index 15ce7f55..00000000 --- a/packages/mcp-server/src/code-tool-paths.cts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export const workerPath = require.resolve('./code-tool-worker.mjs'); diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts deleted file mode 100644 index f6394239..00000000 --- a/packages/mcp-server/src/code-tool-worker.ts +++ /dev/null @@ -1,315 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import path from 'node:path'; -import util from 'node:util'; - -import Fuse from 'fuse.js'; -import ts from 'typescript'; - -import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; -import { ImageKit } from '@imagekit/nodejs'; - -function getRunFunctionSource(code: string): { - type: 'declaration' | 'expression'; - client: string | undefined; - code: string; -} | null { - const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); - const printer = ts.createPrinter(); - - for (const statement of sourceFile.statements) { - // Check for top-level function declarations - if (ts.isFunctionDeclaration(statement)) { - if (statement.name?.text === 'run') { - return { - type: 'declaration', - client: statement.parameters[0]?.name.getText(), - code: printer.printNode(ts.EmitHint.Unspecified, statement.body!, sourceFile), - }; - } - } - - // 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 - declaration.initializer && - (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) - ) { - return { - type: 'expression', - client: declaration.initializer.parameters[0]?.name.getText(), - code: printer.printNode(ts.EmitHint.Unspecified, declaration.initializer, sourceFile), - }; - } - } - } - } - - return null; -} - -function getTSDiagnostics(code: string): string[] { - const functionSource = getRunFunctionSource(code)!; - const codeWithImport = [ - 'import { ImageKit } from "@imagekit/nodejs";', - functionSource.type === 'declaration' ? - `async function run(${functionSource.client}: ImageKit)` - : `const run: (${functionSource.client}: ImageKit) => Promise =`, - functionSource.code, - ].join('\n'); - const sourcePath = path.resolve('code.ts'); - const ast = ts.createSourceFile(sourcePath, codeWithImport, ts.ScriptTarget.Latest, true); - const options = ts.getDefaultCompilerOptions(); - options.target = ts.ScriptTarget.Latest; - options.module = ts.ModuleKind.NodeNext; - options.moduleResolution = ts.ModuleResolutionKind.NodeNext; - const host = ts.createCompilerHost(options, true); - const newHost: typeof host = { - ...host, - getSourceFile: (...args) => { - if (path.resolve(args[0]) === sourcePath) { - return ast; - } - return host.getSourceFile(...args); - }, - readFile: (...args) => { - if (path.resolve(args[0]) === sourcePath) { - return codeWithImport; - } - return host.readFile(...args); - }, - fileExists: (...args) => { - if (path.resolve(args[0]) === sourcePath) { - return true; - } - return host.fileExists(...args); - }, - }; - const program = ts.createProgram({ - options, - rootNames: [sourcePath], - host: newHost, - }); - const diagnostics = ts.getPreEmitDiagnostics(program, ast); - return diagnostics.map((d) => { - const message = ts.flattenDiagnosticMessageText(d.messageText, '\n'); - if (!d.file || !d.start) return `- ${message}`; - const { line: tsLine } = ts.getLineAndCharacterOfPosition(d.file, d.start); - // We add two lines in the beginning, for the client import and the function declaration. - // So the actual (zero-based) line number is tsLine - 2. - const lineNumber = tsLine - 2; - const line = code.split('\n').at(lineNumber)?.trim(); - return line ? `- ${message}\n at line ${lineNumber + 1}\n ${line}` : `- ${message}`; - }); -} - -const fuse = new Fuse( - [ - 'client.customMetadataFields.create', - 'client.customMetadataFields.delete', - 'client.customMetadataFields.list', - 'client.customMetadataFields.update', - 'client.files.copy', - 'client.files.delete', - 'client.files.get', - 'client.files.move', - 'client.files.rename', - 'client.files.update', - 'client.files.upload', - 'client.files.bulk.addTags', - 'client.files.bulk.delete', - 'client.files.bulk.removeAITags', - 'client.files.bulk.removeTags', - 'client.files.versions.delete', - 'client.files.versions.get', - 'client.files.versions.list', - 'client.files.versions.restore', - 'client.files.metadata.get', - 'client.files.metadata.getFromURL', - 'client.assets.list', - 'client.cache.invalidation.create', - 'client.cache.invalidation.get', - 'client.folders.copy', - 'client.folders.create', - 'client.folders.delete', - 'client.folders.move', - 'client.folders.rename', - 'client.folders.job.get', - 'client.accounts.usage.get', - 'client.accounts.origins.create', - 'client.accounts.origins.delete', - 'client.accounts.origins.get', - 'client.accounts.origins.list', - 'client.accounts.origins.update', - 'client.accounts.urlEndpoints.create', - 'client.accounts.urlEndpoints.delete', - 'client.accounts.urlEndpoints.get', - 'client.accounts.urlEndpoints.list', - 'client.accounts.urlEndpoints.update', - 'client.beta.v2.files.upload', - 'client.webhooks.unsafeUnwrap', - 'client.webhooks.unwrap', - ], - { 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; - - const runFunctionSource = code ? getRunFunctionSource(code) : null; - if (!runFunctionSource) { - const message = - code ? - 'The code is missing a top-level `run` function.' - : 'The code argument is missing. Provide one containing a top-level `run` function.'; - return Response.json( - { - message: `${message} Write code within this template:\n\n\`\`\`\nasync function run(client) {\n // Fill this out\n}\n\`\`\``, - logLines: [], - errLines: [], - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } - - const diagnostics = getTSDiagnostics(code); - if (diagnostics.length > 0) { - return Response.json( - { - message: `The code contains TypeScript diagnostics:\n${diagnostics.join('\n')}`, - logLines: [], - errLines: [], - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } - - const client = new ImageKit({ - ...opts, - }); - - const logLines: string[] = []; - const errLines: string[] = []; - const console = { - log: (...args: unknown[]) => { - logLines.push(util.format(...args)); - }, - error: (...args: unknown[]) => { - errLines.push(util.format(...args)); - }, - }; - try { - let run_ = async (client: any) => {}; - eval(`${code}\nrun_ = run;`); - const result = await run_(makeSdkProxy(client, { path: ['client'] })); - return Response.json({ - result, - logLines, - errLines, - } satisfies WorkerSuccess); - } catch (e) { - return Response.json( - { - message: parseError(code, e), - logLines, - errLines, - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } -}; - -export default { fetch }; diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 82d11938..9f350600 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,14 +1,9 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import path from 'node:path'; -import url from 'node:url'; -import ImageKit, { ClientOptions } from '@imagekit/nodejs'; -import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; - +import { Metadata, ToolCallResult, asTextContentResult } from './tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; - -import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; - +import { readEnv } from './server'; +import { WorkerSuccess } from './code-tool-types'; /** * A tool that runs code against a copy of the SDK. * @@ -18,158 +13,49 @@ import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; * * @param endpoints - The endpoints to include in the list. */ -export async function codeTool(): Promise { +export async function codeTool() { const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; const tool: Tool = { name: 'execute', description: - '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.', + '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 SDK 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' } } }, }; - - // Import dynamically to avoid failing at import time in cases where the environment is not well-supported. - const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); - const { workerPath } = await import('./code-tool-paths.cjs'); - - const handler = async (client: ImageKit, args: unknown): Promise => { - const baseURLHostname = new URL(client.baseURL).hostname; - const { code } = args as { code: string }; - - const allowRead = [ - 'code-tool-worker.mjs', - `${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, - path.resolve(path.dirname(workerPath), '..'), - ].join(','); - - const worker = await newDenoHTTPWorker(url.pathToFileURL(workerPath), { - runFlags: [ - `--node-modules-dir=manual`, - `--allow-read=${allowRead}`, - `--allow-net=${baseURLHostname}`, - // Allow environment variables because instantiating the client will try to read from them, - // even though they are not set. - '--allow-env', - ], - printOutput: true, - spawnOptions: { - cwd: path.dirname(workerPath), + const handler = async (_: unknown, args: any): Promise => { + const code = args.code as string; + + // this is not required, but passing a Stainless API key for the matching project_name + // will allow you to run code-mode queries against non-published versions of your SDK. + const stainlessAPIKey = readEnv('STAINLESS_API_KEY'); + const codeModeEndpoint = + readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool/'; + + const res = await fetch(codeModeEndpoint, { + method: 'POST', + headers: { + ...(stainlessAPIKey && { Authorization: stainlessAPIKey }), + 'Content-Type': 'application/json', + client_envs: JSON.stringify({ + IMAGEKIT_PRIVATE_KEY: readEnv('IMAGEKIT_PRIVATE_KEY'), + OPTIONAL_IMAGEKIT_IGNORES_THIS: readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS'), + IMAGEKIT_WEBHOOK_SECRET: readEnv('IMAGEKIT_WEBHOOK_SECRET'), + }), }, + body: JSON.stringify({ + project_name: 'imagekit', + code, + }), }); - try { - const resp = await new Promise((resolve, reject) => { - worker.addEventListener('exit', (exitCode) => { - reject(new Error(`Worker exited with code ${exitCode}`)); - }); - - const opts: ClientOptions = { - baseURL: client.baseURL, - privateKey: client.privateKey, - password: client.password, - webhookSecret: client.webhookSecret, - defaultHeaders: { - 'X-Stainless-MCP': 'true', - }, - }; - - const req = worker.request( - 'http://localhost', - { - headers: { - 'content-type': 'application/json', - }, - method: 'POST', - }, - (resp) => { - const body: Uint8Array[] = []; - resp.on('error', (err) => { - reject(err); - }); - resp.on('data', (chunk) => { - body.push(chunk); - }); - resp.on('end', () => { - resolve( - new Response(Buffer.concat(body).toString(), { - status: resp.statusCode ?? 200, - headers: resp.headers as any, - }), - ); - }); - }, - ); - - const body = JSON.stringify({ - opts, - code, - } satisfies WorkerInput); - - req.write(body, (err) => { - if (err != null) { - reject(err); - } - }); - - req.end(); - }); - - if (resp.status === 200) { - const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; - const returnOutput: ContentBlock | null = - result == null ? null : ( - { - type: 'text', - text: typeof result === 'string' ? result : JSON.stringify(result), - } - ); - const logOutput: ContentBlock | null = - logLines.length === 0 ? - null - : { - type: 'text', - text: logLines.join('\n'), - }; - const errOutput: ContentBlock | null = - errLines.length === 0 ? - null - : { - type: 'text', - text: 'Error output:\n' + errLines.join('\n'), - }; - return { - content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), - }; - } else { - const { message, logLines, errLines } = (await resp.json()) as WorkerError; - const messageOutput: ContentBlock | null = - message == null ? null : ( - { - type: 'text', - text: message, - } - ); - const logOutput: ContentBlock | null = - logLines.length === 0 ? - null - : { - type: 'text', - text: logLines.join('\n'), - }; - const errOutput: ContentBlock | null = - errLines.length === 0 ? - null - : { - type: 'text', - text: 'Error output:\n' + errLines.join('\n'), - }; - return { - content: [messageOutput, logOutput, errOutput].filter((block) => block !== null), - isError: true, - }; - } - } finally { - worker.terminate(); + if (!res.ok) { + throw new Error( + `${res.status}: ${ + res.statusText + } error when trying to contact Code Tool server. Details: ${await res.text()}`, + ); } + + return asTextContentResult((await res.json()) as WorkerSuccess); }; return { metadata, tool, handler }; diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index e4225125..015a5ba1 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -46,6 +46,13 @@ export const handler = async (_: unknown, args: Record | undefi const body = args as any; const query = new URLSearchParams(body).toString(); const result = await fetch(`${docsSearchURL}?${query}`); + + if (!result.ok) { + throw new Error( + `${result.status}: ${result.statusText} when using doc search tool. Details: ${await result.text()}`, + ); + } + return asTextContentResult(await result.json()); }; From aa7ae07286cf492a7b1fecce34697006837beeef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:41:38 +0000 Subject: [PATCH 30/44] fix(mcp): return correct lines on typescript errors --- .devcontainer/Dockerfile | 23 ++ .eslintrc.js | 10 + jest.setup.ts | 0 packages/mcp-server/src/did-you-mean-proxy.ts | 356 ++++++++++++++++++ src/internal/polyfill/file.node.d.ts | 14 + src/internal/polyfill/file.node.mjs | 9 + tests/responses.test.ts | 24 ++ 7 files changed, 436 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .eslintrc.js create mode 100644 jest.setup.ts create mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts create mode 100644 src/internal/polyfill/file.node.d.ts create mode 100644 src/internal/polyfill/file.node.mjs create mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..8ea34be9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS stainless + +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + yarnpkg \ + && apt-get clean autoclean + +# Ensure UTF-8 encoding +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Yarn +RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn + +WORKDIR /workspace + +COPY package.json yarn.lock /workspace/ + +RUN yarn install + +COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..60f0e7a3 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], + rules: { + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + 'unused-imports/no-unused-imports': 'error', + }, + root: true, +}; diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts new file mode 100644 index 00000000..8c1380d7 --- /dev/null +++ b/packages/mcp-server/src/did-you-mean-proxy.ts @@ -0,0 +1,356 @@ +import Fuse from 'fuse.js'; + +const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; +type Kind = (typeof allKinds)[number]; + +const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); +const denoInspect = Symbol.for('Deno.customInspect'); + +const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); +const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); +const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); + +function getApplyKinds(name?: string | symbol): readonly Kind[] { + if (!name || name === nodeInspect || name === denoInspect) { + return allKinds; + } + + if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { + return ['string', 'number', 'boolean']; + } + + if (name === Symbol.iterator) { + return ['array']; + } + + if (typeof name !== 'string') { + return ['method']; + } + + const kinds: Kind[] = []; + if (stringMethods.has(name)) { + kinds.push('string'); + } else if (numberMethods.has(name)) { + kinds.push('number'); + } else if (arrayMethods.has(name)) { + kinds.push('array'); + } + + return kinds.length > 0 ? kinds : ['method']; +} + +type KindPaths = Record; + +function traverseKinds( + obj: object, + path: string = '', + result: KindPaths = { + string: [], + number: [], + boolean: [], + object: [], + array: [], + method: [], + constructor: [], + }, +): KindPaths { + while (obj !== null) { + for (const key of Reflect.ownKeys(obj)) { + if (typeof key !== 'string') { + continue; + } + + if (key === 'constructor') { + continue; + } + + if (!/^[a-zA-Z]/.test(key)) { + continue; + } + + const value = Reflect.get(obj, key); + let kind: Kind; + + switch (typeof value) { + case 'string': { + kind = 'string'; + break; + } + case 'number': + case 'bigint': { + kind = 'number'; + break; + } + case 'boolean': { + kind = 'boolean'; + break; + } + case 'object': { + if (value === null) { + continue; + } + kind = Array.isArray(value) ? 'array' : 'object'; + break; + } + case 'function': { + kind = + key === value.name && value.name === value.prototype?.constructor?.name ? + 'constructor' + : 'method'; + break; + } + default: { + continue; + } + } + + const fullKey = path ? `${path}.${key}` : key; + result[kind].push(fullKey); + + if (kind === 'object') { + traverseKinds(value, fullKey, result); + } else if (kind === 'array' && value.length > 0) { + traverseKinds(value[0], `${fullKey}[]`, result); + } + } + + obj = Object.getPrototypeOf(obj); + if (obj === Object.prototype || obj === Array.prototype) { + break; + } + } + + return result; +} + +export type MakeError = (props: { + expected: readonly Kind[]; + rootPath: (string | symbol)[]; + path: (string | symbol)[]; + suggestions: { item: string; score: number }[]; +}) => string; + +export type ProxyConfig = { + /** + * Whether to also proxy the return values. They will be proxied with + * the same config, except the root path will be blank. Defaults to true. + */ + proxyReturn?: boolean; + /** + * The path to the root object, prepended to path suggestions. For example, + * if this is set to ['client'], then suggestions will be 'client.repos.list', + * 'client.users.list', etc. + */ + rootPath?: (string | symbol)[]; + /** + * Customize the error message to be thrown. The root path will not be + * prepended to either the path or the suggestions. + */ + makeSuggestionError?: MakeError; +}; + +function shouldProxy(value: unknown): value is NonNullable { + return value !== null && (typeof value === 'object' || typeof value === 'function'); +} + +const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); + +type EmptyTarget = { + [emptyTargetSymbol]: { + getError: () => string; + }; +}; +type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; + +/** + * We use a special empty target so we can catch calls and constructions. + * Also useful for de-proxying in the end; if we get an empty target, we know + * we can throw an error. + */ +function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { + const emptyTarget = function () {} as any; + emptyTarget[nodeInspect] = () => { + throw info.getError(); + }; + emptyTarget[denoInspect] = () => { + throw info.getError(); + }; + emptyTarget[emptyTargetSymbol] = info; + return emptyTarget; +} + +function isEmptyTarget(value: unknown): value is EmptyTarget { + return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; +} + +export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { + const rootPathString = + rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; + const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; + + let header = `${pathString} does not exist.`; + if (expected.length === 1) { + const expectedType = + expected[0] === 'array' ? 'an array' + : expected[0] === 'object' ? 'an object' + : expected[0] === 'method' ? 'a function' + : `a ${expected[0]}`; + header = `${pathString} is not ${expectedType}.`; + } + + const suggestionStrings = suggestions + // TODO(sometime): thresholding? + .filter((suggestion) => suggestion.score < 1) + .slice(0, 5) + .map((suggestion) => `'${rootPathString}${suggestion.item}'`); + + let body = ''; + if (suggestionStrings.length === 1) { + body = `Did you mean ${suggestionStrings[0]}?`; + } else if (suggestionStrings.length > 1) { + const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); + body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; + } + + return body ? `${header} ${body}` : header; +}; + +export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { + return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} +${suggestions + .slice(0, 10) + .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) + .join('\n')} +`; +}; + +const proxyToObj = new WeakMap(); + +export function makeProxy(root: Root, config: ProxyConfig = {}): Root { + let kindPaths: KindPaths | null = null; + + config.proxyReturn ??= true; + config.rootPath ??= ['']; + config.makeSuggestionError ??= defaultMakeError; + + const { proxyReturn, rootPath, makeSuggestionError } = config; + const { rootPath: _, ...subconfig } = config; + + function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { + if (!kindPaths) { + kindPaths = traverseKinds(root); + } + + const fuse = new Fuse( + expected.flatMap((kind) => kindPaths![kind]), + { includeScore: true }, + ); + + const path = pathWithRoot.slice(rootPath.length); + const searchKey: string[] = []; + for (const key of path) { + // Convert array keys to []: + if (/^\d+$/.test(key.toString())) { + searchKey.push('[]'); + } else if (typeof key === 'string') { + searchKey.push('.'); + searchKey.push(key); + } + } + + const key = searchKey.join(''); + const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; + + return makeSuggestionError({ expected, rootPath, path, suggestions }); + } + + function subproxy(obj: T, path: (string | symbol)[]): T { + const handlers: ProxyHandler = { + get(target, prop, receiver) { + const newPath = [...path, prop]; + const value = Reflect.get(target, prop, receiver); + + if (value === undefined && !Reflect.has(target, prop)) { + // Some common special cases: + // - 'then' is called on a non-thenable. + // - 'toJSON' is called when it's not defined. + // In these cases, we actually want to return undefined, so we + // resolve to the top-level thing. + if (prop === 'then' || prop === 'toJSON') { + return undefined; + } + + return subproxy( + createEmptyTarget({ + getError: () => makeError(newPath, allKinds), + }), + newPath, + ); + } + + return shouldProxy(value) ? subproxy(value, newPath) : value; + }, + construct(target, args, newTarget) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, ['constructor'])); + } + + const result = Reflect.construct(target, args, newTarget); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + apply(target, thisArg, args) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); + } + + const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; + const proxiedArgs = + proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; + const result = Reflect.apply(target, correctThisArg, proxiedArgs); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + }; + + // All other traps demand a non-empty target: + for (const trap of [ + 'defineProperty', + 'has', + 'set', + 'deleteProperty', + 'ownKeys', + 'getPrototypeOf', + 'setPrototypeOf', + 'isExtensible', + 'preventExtensions', + 'getOwnPropertyDescriptor', + ] as const) { + handlers[trap] = function (target: any, ...args: any[]) { + if (isEmptyTarget(target)) { + throw new Error(makeError(path, allKinds)); + } + + return (Reflect[trap] as any)(target, ...args); + }; + } + + const proxy = new Proxy(obj, handlers); + proxyToObj.set(proxy, obj); + + return proxy; + } + + return subproxy(root, rootPath); +} + +export function deproxy(value: T): T { + // Primitives never get proxied, so these are safe: + if (typeof value !== 'object' && typeof value !== 'function') { + return value; + } + if (isEmptyTarget(value)) { + throw new Error(value[emptyTargetSymbol].getError()); + } + return proxyToObj.get(value) ?? value; +} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts new file mode 100644 index 00000000..c95276d8 --- /dev/null +++ b/src/internal/polyfill/file.node.d.ts @@ -0,0 +1,14 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +// @ts-ignore +type nodeBuffer = typeof import('node:buffer'); +declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] +: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] +: any; +export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs new file mode 100644 index 00000000..520dcb84 --- /dev/null +++ b/src/internal/polyfill/file.node.mjs @@ -0,0 +1,9 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts new file mode 100644 index 00000000..a65035f5 --- /dev/null +++ b/tests/responses.test.ts @@ -0,0 +1,24 @@ +import { createResponseHeaders } from '@imagekit/nodejs/internal/headers'; + +describe('response parsing', () => { + // TODO: test unicode characters + test('headers are case agnostic', async () => { + const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); + expect(headers['content-type']).toEqual('foo'); + expect(headers['Content-type']).toEqual('foo'); + expect(headers['Content-Type']).toEqual('foo'); + expect(headers['accept']).toEqual('text/plain'); + expect(headers['Accept']).toEqual('text/plain'); + expect(headers['Hello-World']).toBeUndefined(); + }); + + test('duplicate headers are concatenated', () => { + const headers = createResponseHeaders( + new Headers([ + ['Content-Type', 'text/xml'], + ['Content-Type', 'application/json'], + ]), + ); + expect(headers['content-type']).toBe('text/xml, application/json'); + }); +}); From 26acc3a9fe08c7c478eed956dd553333bd8cf210 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 21:17:15 +0000 Subject: [PATCH 31/44] chore(internal): codegen related update --- .devcontainer/Dockerfile | 23 -- .eslintrc.js | 10 - jest.setup.ts | 0 packages/mcp-server/src/did-you-mean-proxy.ts | 356 ------------------ src/internal/polyfill/file.node.d.ts | 14 - src/internal/polyfill/file.node.mjs | 9 - tests/responses.test.ts | 24 -- 7 files changed, 436 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .eslintrc.js delete mode 100644 jest.setup.ts delete mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts delete mode 100644 src/internal/polyfill/file.node.d.ts delete mode 100644 src/internal/polyfill/file.node.mjs delete mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 8ea34be9..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM debian:bookworm-slim AS stainless - -RUN apt-get update && apt-get install -y \ - nodejs \ - npm \ - yarnpkg \ - && apt-get clean autoclean - -# Ensure UTF-8 encoding -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Yarn -RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn - -WORKDIR /workspace - -COPY package.json yarn.lock /workspace/ - -RUN yarn install - -COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60f0e7a3..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - }, - root: true, -}; diff --git a/jest.setup.ts b/jest.setup.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts deleted file mode 100644 index 8c1380d7..00000000 --- a/packages/mcp-server/src/did-you-mean-proxy.ts +++ /dev/null @@ -1,356 +0,0 @@ -import Fuse from 'fuse.js'; - -const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; -type Kind = (typeof allKinds)[number]; - -const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); -const denoInspect = Symbol.for('Deno.customInspect'); - -const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); -const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); -const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); - -function getApplyKinds(name?: string | symbol): readonly Kind[] { - if (!name || name === nodeInspect || name === denoInspect) { - return allKinds; - } - - if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { - return ['string', 'number', 'boolean']; - } - - if (name === Symbol.iterator) { - return ['array']; - } - - if (typeof name !== 'string') { - return ['method']; - } - - const kinds: Kind[] = []; - if (stringMethods.has(name)) { - kinds.push('string'); - } else if (numberMethods.has(name)) { - kinds.push('number'); - } else if (arrayMethods.has(name)) { - kinds.push('array'); - } - - return kinds.length > 0 ? kinds : ['method']; -} - -type KindPaths = Record; - -function traverseKinds( - obj: object, - path: string = '', - result: KindPaths = { - string: [], - number: [], - boolean: [], - object: [], - array: [], - method: [], - constructor: [], - }, -): KindPaths { - while (obj !== null) { - for (const key of Reflect.ownKeys(obj)) { - if (typeof key !== 'string') { - continue; - } - - if (key === 'constructor') { - continue; - } - - if (!/^[a-zA-Z]/.test(key)) { - continue; - } - - const value = Reflect.get(obj, key); - let kind: Kind; - - switch (typeof value) { - case 'string': { - kind = 'string'; - break; - } - case 'number': - case 'bigint': { - kind = 'number'; - break; - } - case 'boolean': { - kind = 'boolean'; - break; - } - case 'object': { - if (value === null) { - continue; - } - kind = Array.isArray(value) ? 'array' : 'object'; - break; - } - case 'function': { - kind = - key === value.name && value.name === value.prototype?.constructor?.name ? - 'constructor' - : 'method'; - break; - } - default: { - continue; - } - } - - const fullKey = path ? `${path}.${key}` : key; - result[kind].push(fullKey); - - if (kind === 'object') { - traverseKinds(value, fullKey, result); - } else if (kind === 'array' && value.length > 0) { - traverseKinds(value[0], `${fullKey}[]`, result); - } - } - - obj = Object.getPrototypeOf(obj); - if (obj === Object.prototype || obj === Array.prototype) { - break; - } - } - - return result; -} - -export type MakeError = (props: { - expected: readonly Kind[]; - rootPath: (string | symbol)[]; - path: (string | symbol)[]; - suggestions: { item: string; score: number }[]; -}) => string; - -export type ProxyConfig = { - /** - * Whether to also proxy the return values. They will be proxied with - * the same config, except the root path will be blank. Defaults to true. - */ - proxyReturn?: boolean; - /** - * The path to the root object, prepended to path suggestions. For example, - * if this is set to ['client'], then suggestions will be 'client.repos.list', - * 'client.users.list', etc. - */ - rootPath?: (string | symbol)[]; - /** - * Customize the error message to be thrown. The root path will not be - * prepended to either the path or the suggestions. - */ - makeSuggestionError?: MakeError; -}; - -function shouldProxy(value: unknown): value is NonNullable { - return value !== null && (typeof value === 'object' || typeof value === 'function'); -} - -const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); - -type EmptyTarget = { - [emptyTargetSymbol]: { - getError: () => string; - }; -}; -type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; - -/** - * We use a special empty target so we can catch calls and constructions. - * Also useful for de-proxying in the end; if we get an empty target, we know - * we can throw an error. - */ -function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { - const emptyTarget = function () {} as any; - emptyTarget[nodeInspect] = () => { - throw info.getError(); - }; - emptyTarget[denoInspect] = () => { - throw info.getError(); - }; - emptyTarget[emptyTargetSymbol] = info; - return emptyTarget; -} - -function isEmptyTarget(value: unknown): value is EmptyTarget { - return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; -} - -export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { - const rootPathString = - rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; - const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; - - let header = `${pathString} does not exist.`; - if (expected.length === 1) { - const expectedType = - expected[0] === 'array' ? 'an array' - : expected[0] === 'object' ? 'an object' - : expected[0] === 'method' ? 'a function' - : `a ${expected[0]}`; - header = `${pathString} is not ${expectedType}.`; - } - - const suggestionStrings = suggestions - // TODO(sometime): thresholding? - .filter((suggestion) => suggestion.score < 1) - .slice(0, 5) - .map((suggestion) => `'${rootPathString}${suggestion.item}'`); - - let body = ''; - if (suggestionStrings.length === 1) { - body = `Did you mean ${suggestionStrings[0]}?`; - } else if (suggestionStrings.length > 1) { - const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); - body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; - } - - return body ? `${header} ${body}` : header; -}; - -export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { - return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} -${suggestions - .slice(0, 10) - .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) - .join('\n')} -`; -}; - -const proxyToObj = new WeakMap(); - -export function makeProxy(root: Root, config: ProxyConfig = {}): Root { - let kindPaths: KindPaths | null = null; - - config.proxyReturn ??= true; - config.rootPath ??= ['']; - config.makeSuggestionError ??= defaultMakeError; - - const { proxyReturn, rootPath, makeSuggestionError } = config; - const { rootPath: _, ...subconfig } = config; - - function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { - if (!kindPaths) { - kindPaths = traverseKinds(root); - } - - const fuse = new Fuse( - expected.flatMap((kind) => kindPaths![kind]), - { includeScore: true }, - ); - - const path = pathWithRoot.slice(rootPath.length); - const searchKey: string[] = []; - for (const key of path) { - // Convert array keys to []: - if (/^\d+$/.test(key.toString())) { - searchKey.push('[]'); - } else if (typeof key === 'string') { - searchKey.push('.'); - searchKey.push(key); - } - } - - const key = searchKey.join(''); - const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; - - return makeSuggestionError({ expected, rootPath, path, suggestions }); - } - - function subproxy(obj: T, path: (string | symbol)[]): T { - const handlers: ProxyHandler = { - get(target, prop, receiver) { - const newPath = [...path, prop]; - const value = Reflect.get(target, prop, receiver); - - if (value === undefined && !Reflect.has(target, prop)) { - // Some common special cases: - // - 'then' is called on a non-thenable. - // - 'toJSON' is called when it's not defined. - // In these cases, we actually want to return undefined, so we - // resolve to the top-level thing. - if (prop === 'then' || prop === 'toJSON') { - return undefined; - } - - return subproxy( - createEmptyTarget({ - getError: () => makeError(newPath, allKinds), - }), - newPath, - ); - } - - return shouldProxy(value) ? subproxy(value, newPath) : value; - }, - construct(target, args, newTarget) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, ['constructor'])); - } - - const result = Reflect.construct(target, args, newTarget); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - apply(target, thisArg, args) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); - } - - const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; - const proxiedArgs = - proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; - const result = Reflect.apply(target, correctThisArg, proxiedArgs); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - }; - - // All other traps demand a non-empty target: - for (const trap of [ - 'defineProperty', - 'has', - 'set', - 'deleteProperty', - 'ownKeys', - 'getPrototypeOf', - 'setPrototypeOf', - 'isExtensible', - 'preventExtensions', - 'getOwnPropertyDescriptor', - ] as const) { - handlers[trap] = function (target: any, ...args: any[]) { - if (isEmptyTarget(target)) { - throw new Error(makeError(path, allKinds)); - } - - return (Reflect[trap] as any)(target, ...args); - }; - } - - const proxy = new Proxy(obj, handlers); - proxyToObj.set(proxy, obj); - - return proxy; - } - - return subproxy(root, rootPath); -} - -export function deproxy(value: T): T { - // Primitives never get proxied, so these are safe: - if (typeof value !== 'object' && typeof value !== 'function') { - return value; - } - if (isEmptyTarget(value)) { - throw new Error(value[emptyTargetSymbol].getError()); - } - return proxyToObj.get(value) ?? value; -} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts deleted file mode 100644 index c95276d8..00000000 --- a/src/internal/polyfill/file.node.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -// @ts-ignore -type nodeBuffer = typeof import('node:buffer'); -declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] -: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] -: any; -export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs deleted file mode 100644 index 520dcb84..00000000 --- a/src/internal/polyfill/file.node.mjs +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts deleted file mode 100644 index a65035f5..00000000 --- a/tests/responses.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createResponseHeaders } from '@imagekit/nodejs/internal/headers'; - -describe('response parsing', () => { - // TODO: test unicode characters - test('headers are case agnostic', async () => { - const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); - expect(headers['content-type']).toEqual('foo'); - expect(headers['Content-type']).toEqual('foo'); - expect(headers['Content-Type']).toEqual('foo'); - expect(headers['accept']).toEqual('text/plain'); - expect(headers['Accept']).toEqual('text/plain'); - expect(headers['Hello-World']).toBeUndefined(); - }); - - test('duplicate headers are concatenated', () => { - const headers = createResponseHeaders( - new Headers([ - ['Content-Type', 'text/xml'], - ['Content-Type', 'application/json'], - ]), - ); - expect(headers['content-type']).toBe('text/xml, application/json'); - }); -}); From f4d2b6c9989e8cc6c69badba7de0abb57e6de398 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 23:52:47 +0000 Subject: [PATCH 32/44] fix(mcp): correct code tool API endpoint --- .devcontainer/Dockerfile | 23 ++ .eslintrc.js | 10 + jest.setup.ts | 0 packages/mcp-server/src/code-tool.ts | 2 +- packages/mcp-server/src/did-you-mean-proxy.ts | 356 ++++++++++++++++++ src/internal/polyfill/file.node.d.ts | 14 + src/internal/polyfill/file.node.mjs | 9 + tests/responses.test.ts | 24 ++ 8 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .eslintrc.js create mode 100644 jest.setup.ts create mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts create mode 100644 src/internal/polyfill/file.node.d.ts create mode 100644 src/internal/polyfill/file.node.mjs create mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..8ea34be9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS stainless + +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + yarnpkg \ + && apt-get clean autoclean + +# Ensure UTF-8 encoding +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Yarn +RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn + +WORKDIR /workspace + +COPY package.json yarn.lock /workspace/ + +RUN yarn install + +COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..60f0e7a3 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], + rules: { + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + 'unused-imports/no-unused-imports': 'error', + }, + root: true, +}; diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 9f350600..715c43cf 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -28,7 +28,7 @@ export async function codeTool() { // will allow you to run code-mode queries against non-published versions of your SDK. const stainlessAPIKey = readEnv('STAINLESS_API_KEY'); const codeModeEndpoint = - readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool/'; + readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool'; const res = await fetch(codeModeEndpoint, { method: 'POST', diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts new file mode 100644 index 00000000..8c1380d7 --- /dev/null +++ b/packages/mcp-server/src/did-you-mean-proxy.ts @@ -0,0 +1,356 @@ +import Fuse from 'fuse.js'; + +const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; +type Kind = (typeof allKinds)[number]; + +const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); +const denoInspect = Symbol.for('Deno.customInspect'); + +const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); +const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); +const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); + +function getApplyKinds(name?: string | symbol): readonly Kind[] { + if (!name || name === nodeInspect || name === denoInspect) { + return allKinds; + } + + if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { + return ['string', 'number', 'boolean']; + } + + if (name === Symbol.iterator) { + return ['array']; + } + + if (typeof name !== 'string') { + return ['method']; + } + + const kinds: Kind[] = []; + if (stringMethods.has(name)) { + kinds.push('string'); + } else if (numberMethods.has(name)) { + kinds.push('number'); + } else if (arrayMethods.has(name)) { + kinds.push('array'); + } + + return kinds.length > 0 ? kinds : ['method']; +} + +type KindPaths = Record; + +function traverseKinds( + obj: object, + path: string = '', + result: KindPaths = { + string: [], + number: [], + boolean: [], + object: [], + array: [], + method: [], + constructor: [], + }, +): KindPaths { + while (obj !== null) { + for (const key of Reflect.ownKeys(obj)) { + if (typeof key !== 'string') { + continue; + } + + if (key === 'constructor') { + continue; + } + + if (!/^[a-zA-Z]/.test(key)) { + continue; + } + + const value = Reflect.get(obj, key); + let kind: Kind; + + switch (typeof value) { + case 'string': { + kind = 'string'; + break; + } + case 'number': + case 'bigint': { + kind = 'number'; + break; + } + case 'boolean': { + kind = 'boolean'; + break; + } + case 'object': { + if (value === null) { + continue; + } + kind = Array.isArray(value) ? 'array' : 'object'; + break; + } + case 'function': { + kind = + key === value.name && value.name === value.prototype?.constructor?.name ? + 'constructor' + : 'method'; + break; + } + default: { + continue; + } + } + + const fullKey = path ? `${path}.${key}` : key; + result[kind].push(fullKey); + + if (kind === 'object') { + traverseKinds(value, fullKey, result); + } else if (kind === 'array' && value.length > 0) { + traverseKinds(value[0], `${fullKey}[]`, result); + } + } + + obj = Object.getPrototypeOf(obj); + if (obj === Object.prototype || obj === Array.prototype) { + break; + } + } + + return result; +} + +export type MakeError = (props: { + expected: readonly Kind[]; + rootPath: (string | symbol)[]; + path: (string | symbol)[]; + suggestions: { item: string; score: number }[]; +}) => string; + +export type ProxyConfig = { + /** + * Whether to also proxy the return values. They will be proxied with + * the same config, except the root path will be blank. Defaults to true. + */ + proxyReturn?: boolean; + /** + * The path to the root object, prepended to path suggestions. For example, + * if this is set to ['client'], then suggestions will be 'client.repos.list', + * 'client.users.list', etc. + */ + rootPath?: (string | symbol)[]; + /** + * Customize the error message to be thrown. The root path will not be + * prepended to either the path or the suggestions. + */ + makeSuggestionError?: MakeError; +}; + +function shouldProxy(value: unknown): value is NonNullable { + return value !== null && (typeof value === 'object' || typeof value === 'function'); +} + +const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); + +type EmptyTarget = { + [emptyTargetSymbol]: { + getError: () => string; + }; +}; +type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; + +/** + * We use a special empty target so we can catch calls and constructions. + * Also useful for de-proxying in the end; if we get an empty target, we know + * we can throw an error. + */ +function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { + const emptyTarget = function () {} as any; + emptyTarget[nodeInspect] = () => { + throw info.getError(); + }; + emptyTarget[denoInspect] = () => { + throw info.getError(); + }; + emptyTarget[emptyTargetSymbol] = info; + return emptyTarget; +} + +function isEmptyTarget(value: unknown): value is EmptyTarget { + return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; +} + +export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { + const rootPathString = + rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; + const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; + + let header = `${pathString} does not exist.`; + if (expected.length === 1) { + const expectedType = + expected[0] === 'array' ? 'an array' + : expected[0] === 'object' ? 'an object' + : expected[0] === 'method' ? 'a function' + : `a ${expected[0]}`; + header = `${pathString} is not ${expectedType}.`; + } + + const suggestionStrings = suggestions + // TODO(sometime): thresholding? + .filter((suggestion) => suggestion.score < 1) + .slice(0, 5) + .map((suggestion) => `'${rootPathString}${suggestion.item}'`); + + let body = ''; + if (suggestionStrings.length === 1) { + body = `Did you mean ${suggestionStrings[0]}?`; + } else if (suggestionStrings.length > 1) { + const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); + body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; + } + + return body ? `${header} ${body}` : header; +}; + +export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { + return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} +${suggestions + .slice(0, 10) + .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) + .join('\n')} +`; +}; + +const proxyToObj = new WeakMap(); + +export function makeProxy(root: Root, config: ProxyConfig = {}): Root { + let kindPaths: KindPaths | null = null; + + config.proxyReturn ??= true; + config.rootPath ??= ['']; + config.makeSuggestionError ??= defaultMakeError; + + const { proxyReturn, rootPath, makeSuggestionError } = config; + const { rootPath: _, ...subconfig } = config; + + function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { + if (!kindPaths) { + kindPaths = traverseKinds(root); + } + + const fuse = new Fuse( + expected.flatMap((kind) => kindPaths![kind]), + { includeScore: true }, + ); + + const path = pathWithRoot.slice(rootPath.length); + const searchKey: string[] = []; + for (const key of path) { + // Convert array keys to []: + if (/^\d+$/.test(key.toString())) { + searchKey.push('[]'); + } else if (typeof key === 'string') { + searchKey.push('.'); + searchKey.push(key); + } + } + + const key = searchKey.join(''); + const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; + + return makeSuggestionError({ expected, rootPath, path, suggestions }); + } + + function subproxy(obj: T, path: (string | symbol)[]): T { + const handlers: ProxyHandler = { + get(target, prop, receiver) { + const newPath = [...path, prop]; + const value = Reflect.get(target, prop, receiver); + + if (value === undefined && !Reflect.has(target, prop)) { + // Some common special cases: + // - 'then' is called on a non-thenable. + // - 'toJSON' is called when it's not defined. + // In these cases, we actually want to return undefined, so we + // resolve to the top-level thing. + if (prop === 'then' || prop === 'toJSON') { + return undefined; + } + + return subproxy( + createEmptyTarget({ + getError: () => makeError(newPath, allKinds), + }), + newPath, + ); + } + + return shouldProxy(value) ? subproxy(value, newPath) : value; + }, + construct(target, args, newTarget) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, ['constructor'])); + } + + const result = Reflect.construct(target, args, newTarget); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + apply(target, thisArg, args) { + if (isEmptyTarget(target) || typeof target !== 'function') { + throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); + } + + const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; + const proxiedArgs = + proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; + const result = Reflect.apply(target, correctThisArg, proxiedArgs); + + return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; + }, + }; + + // All other traps demand a non-empty target: + for (const trap of [ + 'defineProperty', + 'has', + 'set', + 'deleteProperty', + 'ownKeys', + 'getPrototypeOf', + 'setPrototypeOf', + 'isExtensible', + 'preventExtensions', + 'getOwnPropertyDescriptor', + ] as const) { + handlers[trap] = function (target: any, ...args: any[]) { + if (isEmptyTarget(target)) { + throw new Error(makeError(path, allKinds)); + } + + return (Reflect[trap] as any)(target, ...args); + }; + } + + const proxy = new Proxy(obj, handlers); + proxyToObj.set(proxy, obj); + + return proxy; + } + + return subproxy(root, rootPath); +} + +export function deproxy(value: T): T { + // Primitives never get proxied, so these are safe: + if (typeof value !== 'object' && typeof value !== 'function') { + return value; + } + if (isEmptyTarget(value)) { + throw new Error(value[emptyTargetSymbol].getError()); + } + return proxyToObj.get(value) ?? value; +} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts new file mode 100644 index 00000000..c95276d8 --- /dev/null +++ b/src/internal/polyfill/file.node.d.ts @@ -0,0 +1,14 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +// @ts-ignore +type nodeBuffer = typeof import('node:buffer'); +declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] +: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] +: any; +export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs new file mode 100644 index 00000000..520dcb84 --- /dev/null +++ b/src/internal/polyfill/file.node.mjs @@ -0,0 +1,9 @@ +/** + * This file polyfills the global `File` object for you if it's not already defined + * when running on Node.js + * + * This is only needed on Node.js v18 & v19. Newer versions already define `File` + * as a global. + */ + +import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts new file mode 100644 index 00000000..a65035f5 --- /dev/null +++ b/tests/responses.test.ts @@ -0,0 +1,24 @@ +import { createResponseHeaders } from '@imagekit/nodejs/internal/headers'; + +describe('response parsing', () => { + // TODO: test unicode characters + test('headers are case agnostic', async () => { + const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); + expect(headers['content-type']).toEqual('foo'); + expect(headers['Content-type']).toEqual('foo'); + expect(headers['Content-Type']).toEqual('foo'); + expect(headers['accept']).toEqual('text/plain'); + expect(headers['Accept']).toEqual('text/plain'); + expect(headers['Hello-World']).toBeUndefined(); + }); + + test('duplicate headers are concatenated', () => { + const headers = createResponseHeaders( + new Headers([ + ['Content-Type', 'text/xml'], + ['Content-Type', 'application/json'], + ]), + ); + expect(headers['content-type']).toBe('text/xml, application/json'); + }); +}); From b6b0d1a7d2f00d9ecaa9e0e630a012c25f6a00f4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 23:53:25 +0000 Subject: [PATCH 33/44] chore(internal): codegen related update --- .devcontainer/Dockerfile | 23 -- .eslintrc.js | 10 - jest.setup.ts | 0 packages/mcp-server/src/did-you-mean-proxy.ts | 356 ------------------ src/internal/polyfill/file.node.d.ts | 14 - src/internal/polyfill/file.node.mjs | 9 - tests/responses.test.ts | 24 -- 7 files changed, 436 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .eslintrc.js delete mode 100644 jest.setup.ts delete mode 100644 packages/mcp-server/src/did-you-mean-proxy.ts delete mode 100644 src/internal/polyfill/file.node.d.ts delete mode 100644 src/internal/polyfill/file.node.mjs delete mode 100644 tests/responses.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 8ea34be9..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM debian:bookworm-slim AS stainless - -RUN apt-get update && apt-get install -y \ - nodejs \ - npm \ - yarnpkg \ - && apt-get clean autoclean - -# Ensure UTF-8 encoding -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Yarn -RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn - -WORKDIR /workspace - -COPY package.json yarn.lock /workspace/ - -RUN yarn install - -COPY . /workspace diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60f0e7a3..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - }, - root: true, -}; diff --git a/jest.setup.ts b/jest.setup.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/mcp-server/src/did-you-mean-proxy.ts b/packages/mcp-server/src/did-you-mean-proxy.ts deleted file mode 100644 index 8c1380d7..00000000 --- a/packages/mcp-server/src/did-you-mean-proxy.ts +++ /dev/null @@ -1,356 +0,0 @@ -import Fuse from 'fuse.js'; - -const allKinds = ['string', 'number', 'boolean', 'object', 'array', 'method', 'constructor'] as const; -type Kind = (typeof allKinds)[number]; - -const nodeInspect = Symbol.for('nodejs.util.inspect.custom'); -const denoInspect = Symbol.for('Deno.customInspect'); - -const stringMethods = new Set(Reflect.ownKeys(String.prototype).filter((k) => typeof k === 'string')); -const numberMethods = new Set(Reflect.ownKeys(Number.prototype).filter((k) => typeof k === 'string')); -const arrayMethods = new Set(Reflect.ownKeys(Array.prototype).filter((k) => typeof k === 'string')); - -function getApplyKinds(name?: string | symbol): readonly Kind[] { - if (!name || name === nodeInspect || name === denoInspect) { - return allKinds; - } - - if (name === Symbol.toPrimitive || name === 'toString' || name === 'valueOf') { - return ['string', 'number', 'boolean']; - } - - if (name === Symbol.iterator) { - return ['array']; - } - - if (typeof name !== 'string') { - return ['method']; - } - - const kinds: Kind[] = []; - if (stringMethods.has(name)) { - kinds.push('string'); - } else if (numberMethods.has(name)) { - kinds.push('number'); - } else if (arrayMethods.has(name)) { - kinds.push('array'); - } - - return kinds.length > 0 ? kinds : ['method']; -} - -type KindPaths = Record; - -function traverseKinds( - obj: object, - path: string = '', - result: KindPaths = { - string: [], - number: [], - boolean: [], - object: [], - array: [], - method: [], - constructor: [], - }, -): KindPaths { - while (obj !== null) { - for (const key of Reflect.ownKeys(obj)) { - if (typeof key !== 'string') { - continue; - } - - if (key === 'constructor') { - continue; - } - - if (!/^[a-zA-Z]/.test(key)) { - continue; - } - - const value = Reflect.get(obj, key); - let kind: Kind; - - switch (typeof value) { - case 'string': { - kind = 'string'; - break; - } - case 'number': - case 'bigint': { - kind = 'number'; - break; - } - case 'boolean': { - kind = 'boolean'; - break; - } - case 'object': { - if (value === null) { - continue; - } - kind = Array.isArray(value) ? 'array' : 'object'; - break; - } - case 'function': { - kind = - key === value.name && value.name === value.prototype?.constructor?.name ? - 'constructor' - : 'method'; - break; - } - default: { - continue; - } - } - - const fullKey = path ? `${path}.${key}` : key; - result[kind].push(fullKey); - - if (kind === 'object') { - traverseKinds(value, fullKey, result); - } else if (kind === 'array' && value.length > 0) { - traverseKinds(value[0], `${fullKey}[]`, result); - } - } - - obj = Object.getPrototypeOf(obj); - if (obj === Object.prototype || obj === Array.prototype) { - break; - } - } - - return result; -} - -export type MakeError = (props: { - expected: readonly Kind[]; - rootPath: (string | symbol)[]; - path: (string | symbol)[]; - suggestions: { item: string; score: number }[]; -}) => string; - -export type ProxyConfig = { - /** - * Whether to also proxy the return values. They will be proxied with - * the same config, except the root path will be blank. Defaults to true. - */ - proxyReturn?: boolean; - /** - * The path to the root object, prepended to path suggestions. For example, - * if this is set to ['client'], then suggestions will be 'client.repos.list', - * 'client.users.list', etc. - */ - rootPath?: (string | symbol)[]; - /** - * Customize the error message to be thrown. The root path will not be - * prepended to either the path or the suggestions. - */ - makeSuggestionError?: MakeError; -}; - -function shouldProxy(value: unknown): value is NonNullable { - return value !== null && (typeof value === 'object' || typeof value === 'function'); -} - -const emptyTargetSymbol = Symbol.for('did-you-mean-proxy.emptyTargetPath'); - -type EmptyTarget = { - [emptyTargetSymbol]: { - getError: () => string; - }; -}; -type EmptyTargetInfo = EmptyTarget[typeof emptyTargetSymbol]; - -/** - * We use a special empty target so we can catch calls and constructions. - * Also useful for de-proxying in the end; if we get an empty target, we know - * we can throw an error. - */ -function createEmptyTarget(info: EmptyTargetInfo): EmptyTarget { - const emptyTarget = function () {} as any; - emptyTarget[nodeInspect] = () => { - throw info.getError(); - }; - emptyTarget[denoInspect] = () => { - throw info.getError(); - }; - emptyTarget[emptyTargetSymbol] = info; - return emptyTarget; -} - -function isEmptyTarget(value: unknown): value is EmptyTarget { - return typeof value === 'function' && (value as any)[emptyTargetSymbol] !== undefined; -} - -export const defaultMakeError: MakeError = function ({ expected, rootPath, path, suggestions }) { - const rootPathString = - rootPath.length > 0 ? `${rootPath.filter((p) => typeof p === 'string').join('.')}.` : ''; - const pathString = `'${rootPathString}${path.filter((p) => typeof p === 'string').join('.')}'`; - - let header = `${pathString} does not exist.`; - if (expected.length === 1) { - const expectedType = - expected[0] === 'array' ? 'an array' - : expected[0] === 'object' ? 'an object' - : expected[0] === 'method' ? 'a function' - : `a ${expected[0]}`; - header = `${pathString} is not ${expectedType}.`; - } - - const suggestionStrings = suggestions - // TODO(sometime): thresholding? - .filter((suggestion) => suggestion.score < 1) - .slice(0, 5) - .map((suggestion) => `'${rootPathString}${suggestion.item}'`); - - let body = ''; - if (suggestionStrings.length === 1) { - body = `Did you mean ${suggestionStrings[0]}?`; - } else if (suggestionStrings.length > 1) { - const commas = suggestionStrings.slice(0, suggestionStrings.length - 1).join(', '); - body = `Did you mean ${commas}, or ${suggestionStrings[suggestionStrings.length - 1]}?`; - } - - return body ? `${header} ${body}` : header; -}; - -export const debugMakeError: MakeError = function ({ expected, path, suggestions }) { - return `path ${path.filter((p) => typeof p === 'string').join('.')}; expected ${expected.join(', ')} -${suggestions - .slice(0, 10) - .map((suggestion) => ` - [${suggestion.score.toFixed(2)}] ${suggestion.item}`) - .join('\n')} -`; -}; - -const proxyToObj = new WeakMap(); - -export function makeProxy(root: Root, config: ProxyConfig = {}): Root { - let kindPaths: KindPaths | null = null; - - config.proxyReturn ??= true; - config.rootPath ??= ['']; - config.makeSuggestionError ??= defaultMakeError; - - const { proxyReturn, rootPath, makeSuggestionError } = config; - const { rootPath: _, ...subconfig } = config; - - function makeError(pathWithRoot: (string | symbol)[], expected: readonly Kind[]) { - if (!kindPaths) { - kindPaths = traverseKinds(root); - } - - const fuse = new Fuse( - expected.flatMap((kind) => kindPaths![kind]), - { includeScore: true }, - ); - - const path = pathWithRoot.slice(rootPath.length); - const searchKey: string[] = []; - for (const key of path) { - // Convert array keys to []: - if (/^\d+$/.test(key.toString())) { - searchKey.push('[]'); - } else if (typeof key === 'string') { - searchKey.push('.'); - searchKey.push(key); - } - } - - const key = searchKey.join(''); - const suggestions = fuse.search(key.slice(1)) as { item: string; score: number }[]; - - return makeSuggestionError({ expected, rootPath, path, suggestions }); - } - - function subproxy(obj: T, path: (string | symbol)[]): T { - const handlers: ProxyHandler = { - get(target, prop, receiver) { - const newPath = [...path, prop]; - const value = Reflect.get(target, prop, receiver); - - if (value === undefined && !Reflect.has(target, prop)) { - // Some common special cases: - // - 'then' is called on a non-thenable. - // - 'toJSON' is called when it's not defined. - // In these cases, we actually want to return undefined, so we - // resolve to the top-level thing. - if (prop === 'then' || prop === 'toJSON') { - return undefined; - } - - return subproxy( - createEmptyTarget({ - getError: () => makeError(newPath, allKinds), - }), - newPath, - ); - } - - return shouldProxy(value) ? subproxy(value, newPath) : value; - }, - construct(target, args, newTarget) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, ['constructor'])); - } - - const result = Reflect.construct(target, args, newTarget); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - apply(target, thisArg, args) { - if (isEmptyTarget(target) || typeof target !== 'function') { - throw new Error(makeError(path, getApplyKinds(path[path.length - 1]))); - } - - const correctThisArg = proxyToObj.get(thisArg) ?? thisArg; - const proxiedArgs = - proxyReturn ? args.map((arg) => (shouldProxy(arg) ? makeProxy(arg, subconfig) : arg)) : args; - const result = Reflect.apply(target, correctThisArg, proxiedArgs); - - return proxyReturn && shouldProxy(result) ? makeProxy(result, subconfig) : result; - }, - }; - - // All other traps demand a non-empty target: - for (const trap of [ - 'defineProperty', - 'has', - 'set', - 'deleteProperty', - 'ownKeys', - 'getPrototypeOf', - 'setPrototypeOf', - 'isExtensible', - 'preventExtensions', - 'getOwnPropertyDescriptor', - ] as const) { - handlers[trap] = function (target: any, ...args: any[]) { - if (isEmptyTarget(target)) { - throw new Error(makeError(path, allKinds)); - } - - return (Reflect[trap] as any)(target, ...args); - }; - } - - const proxy = new Proxy(obj, handlers); - proxyToObj.set(proxy, obj); - - return proxy; - } - - return subproxy(root, rootPath); -} - -export function deproxy(value: T): T { - // Primitives never get proxied, so these are safe: - if (typeof value !== 'object' && typeof value !== 'function') { - return value; - } - if (isEmptyTarget(value)) { - throw new Error(value[emptyTargetSymbol].getError()); - } - return proxyToObj.get(value) ?? value; -} diff --git a/src/internal/polyfill/file.node.d.ts b/src/internal/polyfill/file.node.d.ts deleted file mode 100644 index c95276d8..00000000 --- a/src/internal/polyfill/file.node.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -// @ts-ignore -type nodeBuffer = typeof import('node:buffer'); -declare const File: typeof globalThis extends { File: unknown } ? (typeof globalThis)['File'] -: nodeBuffer extends { File: unknown } ? nodeBuffer['File'] -: any; -export {}; diff --git a/src/internal/polyfill/file.node.mjs b/src/internal/polyfill/file.node.mjs deleted file mode 100644 index 520dcb84..00000000 --- a/src/internal/polyfill/file.node.mjs +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This file polyfills the global `File` object for you if it's not already defined - * when running on Node.js - * - * This is only needed on Node.js v18 & v19. Newer versions already define `File` - * as a global. - */ - -import './file.node.js'; diff --git a/tests/responses.test.ts b/tests/responses.test.ts deleted file mode 100644 index a65035f5..00000000 --- a/tests/responses.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createResponseHeaders } from '@imagekit/nodejs/internal/headers'; - -describe('response parsing', () => { - // TODO: test unicode characters - test('headers are case agnostic', async () => { - const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); - expect(headers['content-type']).toEqual('foo'); - expect(headers['Content-type']).toEqual('foo'); - expect(headers['Content-Type']).toEqual('foo'); - expect(headers['accept']).toEqual('text/plain'); - expect(headers['Accept']).toEqual('text/plain'); - expect(headers['Hello-World']).toBeUndefined(); - }); - - test('duplicate headers are concatenated', () => { - const headers = createResponseHeaders( - new Headers([ - ['Content-Type', 'text/xml'], - ['Content-Type', 'application/json'], - ]), - ); - expect(headers['content-type']).toBe('text/xml, application/json'); - }); -}); From 570f4c4e557f52deca7ea8f9848e6b68b9642b15 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:58:09 +0000 Subject: [PATCH 34/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ae3aead6..8577a4cc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: eb4cf65a4c6b26a2901076eff5810d5d +config_hash: 10b48f323ed534664483af1952174d52 From 08984d1f788488bf28821607b515f584c443e086 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:31:39 +0000 Subject: [PATCH 35/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 8577a4cc..6dfdc12a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: 10b48f323ed534664483af1952174d52 +config_hash: fd112bd17c0c8e9f81a50d0e15ea70d6 From 967c8d90503b2e0ed0576b919be09f3b924ec890 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:59:30 +0000 Subject: [PATCH 36/44] fix(mcp): add client instantiation options to code tool --- packages/mcp-server/src/code-tool.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 715c43cf..8923510e 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -43,6 +43,7 @@ export async function codeTool() { }, body: JSON.stringify({ project_name: 'imagekit', + client_opts: {}, code, }), }); From 07038271d5ec459516a8a936eb49f31490ccf6b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:15:43 +0000 Subject: [PATCH 37/44] chore(mcp): update lockfile --- packages/mcp-server/yarn.lock | 381 ++++++++++++++++++++++++++++++++-- 1 file changed, 364 insertions(+), 17 deletions(-) diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 2bb21c66..38be884f 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -10,6 +10,20 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@anthropic-ai/mcpb@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@anthropic-ai/mcpb/-/mcpb-1.1.0.tgz#1af18de2ab9499d321d6310d0be095f5fef5161b" + integrity sha512-nOnhG1eNpGKSIDv6lt3xsI3w2p2k0D/rPTMGXXugLovCEaJ7svh8XMfCe145vs8qo384t8wKbokWAvx9PkQMDA== + dependencies: + "@inquirer/prompts" "^6.0.1" + commander "^13.1.0" + fflate "^0.8.2" + galactus "^1.0.0" + ignore "^7.0.5" + node-forge "^1.3.1" + pretty-bytes "^5.6.0" + zod "^3.25.67" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" @@ -336,6 +350,144 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/checkbox@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-3.0.1.tgz#0a57f704265f78c36e17f07e421b98efb4b9867b" + integrity sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-4.0.1.tgz#9106d6bffa0b2fdd0e4f60319b6f04f2e06e6e25" + integrity sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-3.0.1.tgz#d109f21e050af6b960725388cb1c04214ed7c7bc" + integrity sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + external-editor "^3.1.0" + +"@inquirer/expand@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-3.0.1.tgz#aed9183cac4d12811be47a4a895ea8e82a17e22c" + integrity sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a" + integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== + +"@inquirer/input@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-3.0.1.tgz#de63d49e516487388508d42049deb70f2cb5f28e" + integrity sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/number@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-2.0.1.tgz#b9863080d02ab7dc2e56e16433d83abea0f2a980" + integrity sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/password@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-3.0.1.tgz#2a9a9143591088336bbd573bcb05d5bf080dbf87" + integrity sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-6.0.1.tgz#43f5c0ed35c5ebfe52f1d43d46da2d363d950071" + integrity sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A== + dependencies: + "@inquirer/checkbox" "^3.0.1" + "@inquirer/confirm" "^4.0.1" + "@inquirer/editor" "^3.0.1" + "@inquirer/expand" "^3.0.1" + "@inquirer/input" "^3.0.1" + "@inquirer/number" "^2.0.1" + "@inquirer/password" "^3.0.1" + "@inquirer/rawlist" "^3.0.1" + "@inquirer/search" "^2.0.1" + "@inquirer/select" "^3.0.1" + +"@inquirer/rawlist@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-3.0.1.tgz#729def358419cc929045f264131878ed379e0af3" + integrity sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-2.0.1.tgz#69b774a0a826de2e27b48981d01bc5ad81e73721" + integrity sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-3.0.1.tgz#1df9ed27fb85a5f526d559ac5ce7cc4e9dc4e7ec" + integrity sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -584,12 +736,13 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@modelcontextprotocol/sdk@^1.11.5": - version "1.17.3" - resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" - integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== +"@modelcontextprotocol/sdk@^1.24.0": + version "1.24.3" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz#81a3fcc919cb4ce8630e2bcecf59759176eb331a" + integrity sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw== dependencies: - ajv "^6.12.6" + ajv "^8.17.1" + ajv-formats "^3.0.1" content-type "^1.0.5" cors "^2.8.5" cross-spawn "^7.0.5" @@ -597,10 +750,11 @@ eventsource-parser "^3.0.0" express "^5.0.1" express-rate-limit "^7.5.0" + jose "^6.1.1" pkce-challenge "^5.0.0" raw-body "^3.0.0" - zod "^3.23.8" - zod-to-json-schema "^3.24.1" + zod "^3.25 || ^4.0" + zod-to-json-schema "^3.25.0" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -795,6 +949,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + "@types/node@*": version "22.15.17" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" @@ -802,6 +963,13 @@ dependencies: undici-types "~6.21.0" +"@types/node@^22.5.5": + version "22.19.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.2.tgz#2f0956fba46518aaf7578c84e37bddab55f85d01" + integrity sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw== + dependencies: + undici-types "~6.21.0" + "@types/qs@*", "@types/qs@^6.14.0": version "6.14.0" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" @@ -834,6 +1002,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -970,7 +1143,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.12.4, ajv@^6.12.6: +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + +ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -980,7 +1160,17 @@ ajv@^6.12.4, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: +ajv@^8.0.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -1222,6 +1412,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -1237,6 +1432,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1273,6 +1473,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1685,6 +1890,15 @@ express@^5.0.1, express@^5.1.0: type-is "^2.0.1" vary "^1.1.2" +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1716,6 +1930,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fastq@^1.6.0: version "1.19.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" @@ -1730,6 +1949,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1793,6 +2017,14 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flora-colossus@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-2.0.0.tgz#af1e85db0a8256ef05f3fb531c1235236c97220a" + integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA== + dependencies: + debug "^4.3.4" + fs-extra "^10.1.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1803,6 +2035,15 @@ fresh@^2.0.0: resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1818,6 +2059,20 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +fuse.js@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.1.0.tgz#306228b4befeee11e05b027087c2744158527d09" + integrity sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ== + +galactus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/galactus/-/galactus-1.0.0.tgz#c2615182afa0c6d0859b92e56ae36d052827db7e" + integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ== + dependencies: + debug "^4.3.4" + flora-colossus "^2.0.0" + fs-extra "^10.1.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1910,7 +2165,7 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.2.9: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1965,11 +2220,23 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -2494,6 +2761,11 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.7.0" +jose@^6.1.1: + version "6.1.3" + resolved "https://registry.yarnpkg.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5" + integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ== + "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" @@ -2538,6 +2810,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -2548,6 +2825,15 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -2721,6 +3007,11 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2731,6 +3022,11 @@ negotiator@^1.0.0: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== +node-forge@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751" + integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -2796,6 +3092,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-all@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" @@ -2939,6 +3240,11 @@ prettier@^3.0.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -3020,6 +3326,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -3086,7 +3397,7 @@ safe-buffer@5.2.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -3190,6 +3501,11 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -3334,6 +3650,13 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3474,6 +3797,11 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -3537,6 +3865,15 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3597,22 +3934,32 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + +zod-to-json-schema@^3.24.5: version "3.24.5" resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== +zod-to-json-schema@^3.25.0: + version "3.25.0" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz#df504c957c4fb0feff467c74d03e6aab0b013e1c" + integrity sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ== + zod-validation-error@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== -zod@^3.23.8: - version "3.24.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" - integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== +"zod@^3.25 || ^4.0": + version "4.1.13" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.13.tgz#93699a8afe937ba96badbb0ce8be6033c0a4b6b1" + integrity sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig== -zod@^3.25.20: +zod@^3.25.20, zod@^3.25.67: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== From a043056abd2f86bcb5a691ac8f5c7ccf8a17663c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:13:31 +0000 Subject: [PATCH 38/44] fix(docs): remove extraneous example object fields --- src/resources/custom-metadata-fields.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/resources/custom-metadata-fields.ts b/src/resources/custom-metadata-fields.ts index 7629d3f9..81081924 100644 --- a/src/resources/custom-metadata-fields.ts +++ b/src/resources/custom-metadata-fields.ts @@ -38,11 +38,7 @@ export class CustomMetadataFields extends APIResource { * const customMetadataField = * await client.customMetadataFields.update('id', { * label: 'price', - * schema: { - * type: 'Number', - * minValue: 1000, - * maxValue: 3000, - * }, + * schema: { minValue: 1000, maxValue: 3000 }, * }); * ``` */ From 08a69fefc66824f2dd604e1850a334ce6db299af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:53:56 +0000 Subject: [PATCH 39/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 6dfdc12a..2e7957ed 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: fd112bd17c0c8e9f81a50d0e15ea70d6 +config_hash: 2a4d7992f6d3a0db0e9a430d513d94e6 From 9bc02450a485b2d0123d717054fd8957bd61f2d5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:55:22 +0000 Subject: [PATCH 40/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 2e7957ed..f03cc097 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: 2a4d7992f6d3a0db0e9a430d513d94e6 +config_hash: b4f610d4f53fe5bb17b35cf77a7521ea From ebb95afcfde3e21fa793a4c4eb27c37236355f41 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:00:53 +0000 Subject: [PATCH 41/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f03cc097..e75a1bc7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml openapi_spec_hash: a9aa620376fce66532c84f9364209b0b -config_hash: b4f610d4f53fe5bb17b35cf77a7521ea +config_hash: 71cab8223bb5610c6c7ca6e9c4cc1f89 From 908fa874d20613761caa76cd4b2151524ef87606 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:28:12 +0000 Subject: [PATCH 42/44] fix(mcp): pass base url to code tool --- packages/mcp-server/src/code-tool.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 8923510e..0f7231ca 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -39,6 +39,7 @@ export async function codeTool() { IMAGEKIT_PRIVATE_KEY: readEnv('IMAGEKIT_PRIVATE_KEY'), OPTIONAL_IMAGEKIT_IGNORES_THIS: readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS'), IMAGEKIT_WEBHOOK_SECRET: readEnv('IMAGEKIT_WEBHOOK_SECRET'), + IMAGE_KIT_BASE_URL: readEnv('IMAGE_KIT_BASE_URL'), }), }, body: JSON.stringify({ From b1a0e607e55f27ffed93b2862e8f4466bc609809 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:00:42 +0000 Subject: [PATCH 43/44] chore(mcp)!: remove deprecated tool schemes This removes all tool schemes except for "code mode" tools. Specifically, this removes "all tools" and "dynamic tools" schemes. Additionally, this removes support for resource filtering, tags, jq filtering, and compatibility controls in MCP servers, as they are no longer necessary when using code mode. # Migration To migrate, simply modify the command used to invoke the MCP server. Currently, the only supported tool scheme is code mode. Now, starting the server with just `node /path/to/mcp/server` or `npx package-name` will invoke code tools: changing your command to one of these is likely all you will need to do. Specifically, you must remove all flags including things like --resources, --tags, --client, --tools=dynamic, etc from your invocation command. The only supported flags are now `--port`, `--transport`, `--socket`, and `--tools=docs` (specifically for "docs"). These are also the only options available for http servers. migration-effort: small --- packages/mcp-server/README.md | 309 +---- packages/mcp-server/src/code-tool.ts | 4 +- packages/mcp-server/src/compat.ts | 483 ------- packages/mcp-server/src/docs-search-tool.ts | 2 +- packages/mcp-server/src/dynamic-tools.ts | 153 --- packages/mcp-server/src/filtering.ts | 18 - packages/mcp-server/src/http.ts | 20 +- packages/mcp-server/src/index.ts | 57 +- packages/mcp-server/src/options.ts | 391 +----- packages/mcp-server/src/server.ts | 82 +- packages/mcp-server/src/stdio.ts | 5 +- packages/mcp-server/src/tools.ts | 1 - .../origins/create-accounts-origins.ts | 325 ----- .../origins/delete-accounts-origins.ts | 43 - .../accounts/origins/get-accounts-origins.ts | 48 - .../accounts/origins/list-accounts-origins.ts | 42 - .../origins/update-accounts-origins.ts | 367 ------ .../create-accounts-url-endpoints.ts | 110 -- .../delete-accounts-url-endpoints.ts | 43 - .../get-accounts-url-endpoints.ts | 56 - .../list-accounts-url-endpoints.ts | 51 - .../update-accounts-url-endpoints.ts | 119 -- .../accounts/usage/get-accounts-usage.ts | 63 - .../src/tools/assets/list-assets.ts | 101 -- .../beta/v2/files/upload-v2-beta-files.ts | 323 ----- .../invalidation/create-cache-invalidation.ts | 53 - .../invalidation/get-cache-invalidation.ts | 54 - .../create-custom-metadata-fields.ts | 161 --- .../delete-custom-metadata-fields.ts | 54 - .../list-custom-metadata-fields.ts | 60 - .../update-custom-metadata-fields.ts | 157 --- .../tools/files/bulk/add-tags-files-bulk.ts | 63 - .../src/tools/files/bulk/delete-files-bulk.ts | 56 - .../files/bulk/remove-ai-tags-files-bulk.ts | 63 - .../files/bulk/remove-tags-files-bulk.ts | 63 - .../mcp-server/src/tools/files/copy-files.ts | 62 - .../src/tools/files/delete-files.ts | 41 - .../mcp-server/src/tools/files/get-files.ts | 54 - .../files/metadata/get-files-metadata.ts | 54 - .../metadata/get-from-url-files-metadata.ts | 55 - .../mcp-server/src/tools/files/move-files.ts | 57 - .../src/tools/files/rename-files.ts | 65 - .../src/tools/files/update-files.ts | 203 --- .../src/tools/files/upload-files.ts | 339 ----- .../files/versions/delete-files-versions.ts | 59 - .../files/versions/get-files-versions.ts | 59 - .../files/versions/list-files-versions.ts | 54 - .../files/versions/restore-files-versions.ts | 59 - .../src/tools/folders/copy-folders.ts | 62 - .../src/tools/folders/create-folders.ts | 59 - .../src/tools/folders/delete-folders.ts | 55 - .../src/tools/folders/job/get-folders-job.ts | 54 - .../src/tools/folders/move-folders.ts | 57 - .../src/tools/folders/rename-folders.ts | 63 - packages/mcp-server/src/tools/index.ts | 153 --- packages/mcp-server/src/{tools => }/types.ts | 2 +- packages/mcp-server/tests/compat.test.ts | 1166 ----------------- .../mcp-server/tests/dynamic-tools.test.ts | 185 --- packages/mcp-server/tests/options.test.ts | 510 +------ packages/mcp-server/tests/tools.test.ts | 225 ---- 60 files changed, 51 insertions(+), 7701 deletions(-) delete mode 100644 packages/mcp-server/src/compat.ts delete mode 100644 packages/mcp-server/src/dynamic-tools.ts delete mode 100644 packages/mcp-server/src/filtering.ts delete mode 100644 packages/mcp-server/src/tools.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts delete mode 100644 packages/mcp-server/src/tools/assets/list-assets.ts delete mode 100644 packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts delete mode 100644 packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts delete mode 100644 packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/copy-files.ts delete mode 100644 packages/mcp-server/src/tools/files/delete-files.ts delete mode 100644 packages/mcp-server/src/tools/files/get-files.ts delete mode 100644 packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts delete mode 100644 packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts delete mode 100644 packages/mcp-server/src/tools/files/move-files.ts delete mode 100644 packages/mcp-server/src/tools/files/rename-files.ts delete mode 100644 packages/mcp-server/src/tools/files/update-files.ts delete mode 100644 packages/mcp-server/src/tools/files/upload-files.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/delete-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/get-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/list-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/restore-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/folders/copy-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/create-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/delete-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/job/get-folders-job.ts delete mode 100644 packages/mcp-server/src/tools/folders/move-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/rename-folders.ts delete mode 100644 packages/mcp-server/src/tools/index.ts rename packages/mcp-server/src/{tools => }/types.ts (98%) delete mode 100644 packages/mcp-server/tests/compat.test.ts delete mode 100644 packages/mcp-server/tests/dynamic-tools.test.ts delete mode 100644 packages/mcp-server/tests/tools.test.ts diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 2c75fe1f..d3562879 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -25,7 +25,7 @@ For clients with a configuration JSON, it might look something like this: "mcpServers": { "imagekit_nodejs_api": { "command": "npx", - "args": ["-y", "@imagekit/api-mcp", "--client=claude", "--tools=dynamic"], + "args": ["-y", "@imagekit/api-mcp"], "env": { "IMAGEKIT_PRIVATE_KEY": "My Private Key", "OPTIONAL_IMAGEKIT_IGNORES_THIS": "My Password", @@ -59,110 +59,22 @@ environment variables in Claude Code's `.claude.json`, which can be found in you claude mcp add --transport stdio imagekit_nodejs_api --env IMAGEKIT_PRIVATE_KEY="Your IMAGEKIT_PRIVATE_KEY here." OPTIONAL_IMAGEKIT_IGNORES_THIS="Your OPTIONAL_IMAGEKIT_IGNORES_THIS here." IMAGEKIT_WEBHOOK_SECRET="Your IMAGEKIT_WEBHOOK_SECRET here." -- npx -y @imagekit/api-mcp ``` -## Exposing endpoints to your MCP Client +## Code Mode -There are three ways to expose endpoints as tools in the MCP server: +This MCP server is built on the "Code Mode" tool scheme. In this MCP Server, +your agent will write code against the TypeScript SDK, which will then be executed in an +isolated sandbox. To accomplish this, the server will expose two tools to your agent: -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 +- The first tool is a docs search tool, which can be used to generically query for + documentation about your API/SDK. -### Filtering endpoints and tools +- The second tool is a code tool, where the agent can write code against the TypeScript SDK. + The code will be executed in a sandbox environment without web or filesystem access. Then, + anything the code returns or prints will be returned to the agent as the result of the + tool call. -You can run the package on the command line to discover and filter the set of tools that are exposed by the -MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's -context window. - -You can filter by multiple aspects: - -- `--tool` includes a specific tool by name -- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` -- `--operation` includes just read (get/list) or just write operations - -### Dynamic tools - -If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will -expose the following tools: - -1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query -2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint -3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters - -This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all -of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to -search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it -can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, -you can opt-in to explicit tools, the dynamic tools, or both. - -See more information with `--help`. - -All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). - -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. - -You can specify the client you are using with the `--client` argument, and the MCP server will automatically -serve tools and schemas that are more compatible with that client. - -- `--client=`: Set all capabilities based on a known MCP client - - - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` - - Example: `--client=cursor` - -Additionally, if you have a client not on the above list, or the client has gotten better -over time, you can manually enable or disable certain capabilities: - -- `--capability=`: Specify individual client capabilities - - Available capabilities: - - `top-level-unions`: Enable support for top-level unions in tool schemas - - `valid-json`: Enable JSON string parsing for arguments - - `refs`: Enable support for $ref pointers in schemas - - `unions`: Enable support for union types (anyOf) in schemas - - `formats`: Enable support for format validations in schemas (e.g. date-time, email) - - `tool-name-length=N`: Set maximum tool name length to N characters - - Example: `--capability=top-level-unions --capability=tool-name-length=40` - - Example: `--capability=top-level-unions,tool-name-length=40` - -### Examples - -1. Filter for read operations on cards: - -```bash ---resource=cards --operation=read -``` - -2. Exclude specific tools while including others: - -```bash ---resource=cards --no-tool=create_cards -``` - -3. Configure for Cursor client with custom max tool name length: - -```bash ---client=cursor --capability=tool-name-length=40 -``` - -4. Complex filtering with multiple criteria: - -```bash ---resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards -``` +Using this scheme, agents are capable of performing very complex tasks deterministically +and repeatably. ## Running remotely @@ -190,198 +102,3 @@ A configuration JSON for this server might look like this, assuming the server i } } ``` - -The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. -For example, to exclude specific tools while including others, use the URL: - -``` -http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards -``` - -Or, to configure for the Cursor client, with a custom max tool name length, use the URL: - -``` -http://localhost:3000?client=cursor&capability=tool-name-length%3D40 -``` - -## Importing the tools and server individually - -```js -// Import the server, generated endpoints, or the init function -import { server, endpoints, init } from "@imagekit/api-mcp/server"; - -// import a specific tool -import createCustomMetadataFields from "@imagekit/api-mcp/tools/custom-metadata-fields/create-custom-metadata-fields"; - -// initialize the server and all endpoints -init({ server, endpoints }); - -// manually start server -const transport = new StdioServerTransport(); -await server.connect(transport); - -// or initialize your own server with specific tools -const myServer = new McpServer(...); - -// define your own endpoint -const myCustomEndpoint = { - tool: { - name: 'my_custom_tool', - description: 'My custom tool', - inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), - }, - handler: async (client: client, args: any) => { - return { myResponse: 'Hello world!' }; - }) -}; - -// initialize the server with your custom endpoints -init({ server: myServer, endpoints: [createCustomMetadataFields, myCustomEndpoint] }); -``` - -## Available Tools - -The following tools are available in this MCP server. - -### Resource `customMetadataFields`: - -- `create_custom_metadata_fields` (`write`): This API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API. -- `update_custom_metadata_fields` (`write`): This API updates the label or schema of an existing custom metadata field. -- `list_custom_metadata_fields` (`read`): This API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response. - - You can also filter results by a specific folder path to retrieve custom metadata fields applicable at that location. This path-specific filtering is useful when using the **Path policy** feature to determine which custom metadata fields are selected for a given path. - -- `delete_custom_metadata_fields` (`write`): This API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name. - -### Resource `files`: - -- `update_files` (`write`): This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API. -- `delete_files` (`write`): This API deletes the file and all its file versions permanently. - - Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. - -- `copy_files` (`write`): This will copy a file from one folder to another. - - Note: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history. - -- `get_files` (`read`): This API returns an object with details or attributes about the current version of the file. -- `move_files` (`write`): This will move a file and all its versions from one folder to another. - - Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file. - -- `rename_files` (`write`): You can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. - - Note: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested. - -- `upload_files` (`write`): ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload. - - The [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT. - - **File size limit** \ - On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans. - - **Version limit** \ - A file can have a maximum of 100 versions. - - **Demo applications** - - - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. - - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. - -### Resource `files.bulk`: - -- `delete_files_bulk` (`write`): This API deletes multiple files and all their file versions permanently. - - Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. - - A maximum of 100 files can be deleted at a time. - -- `add_tags_files_bulk` (`write`): This API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time. -- `remove_ai_tags_files_bulk` (`write`): This API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time. -- `remove_tags_files_bulk` (`write`): This API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time. - -### Resource `files.versions`: - -- `list_files_versions` (`read`): This API returns details of all versions of a file. -- `delete_files_versions` (`write`): This API deletes a non-current file version permanently. The API returns an empty response. - - Note: If you want to delete all versions of a file, use the delete file API. - -- `get_files_versions` (`read`): This API returns an object with details or attributes of a file version. -- `restore_files_versions` (`write`): This API restores a file version as the current file version. - -### Resource `files.metadata`: - -- `get_files_metadata` (`read`): You can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API. - - You can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter. - -- `get_from_url_files_metadata` (`read`): Get image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API. - -### Resource `assets`: - -- `list_assets` (`read`): This API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`. - -### Resource `cache.invalidation`: - -- `create_cache_invalidation` (`write`): This API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes. -- `get_cache_invalidation` (`read`): This API returns the status of a purge cache request. - -### Resource `folders`: - -- `create_folders` (`write`): This will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created. -- `delete_folders` (`write`): This will delete a folder and all its contents permanently. The API returns an empty response. -- `copy_folders` (`write`): This will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. -- `move_folders` (`write`): This will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. -- `rename_folders` (`write`): This API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name. - -### Resource `folders.job`: - -- `get_folders_job` (`read`): This API returns the status of a bulk job like copy and move folder operations. - -### Resource `accounts.usage`: - -- `get_accounts_usage` (`read`): Get the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date. - -### Resource `accounts.origins`: - -- `create_accounts_origins` (`write`): **Note:** This API is currently in beta. - Creates a new origin and returns the origin object. -- `update_accounts_origins` (`write`): **Note:** This API is currently in beta. - Updates the origin identified by `id` and returns the updated origin object. -- `list_accounts_origins` (`read`): **Note:** This API is currently in beta. - Returns an array of all configured origins for the current account. -- `delete_accounts_origins` (`write`): **Note:** This API is currently in beta. - Permanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error. -- `get_accounts_origins` (`read`): **Note:** This API is currently in beta. - Retrieves the origin identified by `id`. - -### Resource `accounts.urlEndpoints`: - -- `create_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. - Creates a new URL‑endpoint and returns the resulting object. -- `update_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. - Updates the URL‑endpoint identified by `id` and returns the updated object. -- `list_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. - Returns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation. -- `delete_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. - Deletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation. -- `get_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. - Retrieves the URL‑endpoint identified by `id`. - -### Resource `beta.v2.files`: - -- `upload_v2_beta_files` (`write`): The V2 API enhances security by verifying the entire payload using JWT. This API is in beta. - - ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload. - - **File size limit** \ - On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans. - - **Version limit** \ - A file can have a maximum of 100 versions. - - **Demo applications** - - - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. - - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 0f7231ca..676cb460 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, ToolCallResult, asTextContentResult } from './tools/types'; +import { McpTool, Metadata, ToolCallResult, asTextContentResult } from './types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { readEnv } from './server'; import { WorkerSuccess } from './code-tool-types'; @@ -13,7 +13,7 @@ import { WorkerSuccess } from './code-tool-types'; * * @param endpoints - The endpoints to include in the list. */ -export async function codeTool() { +export function codeTool(): McpTool { const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; const tool: Tool = { name: 'execute', diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts deleted file mode 100644 index f84053c7..00000000 --- a/packages/mcp-server/src/compat.ts +++ /dev/null @@ -1,483 +0,0 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { z } from 'zod'; -import { Endpoint } from './tools'; - -export interface ClientCapabilities { - topLevelUnions: boolean; - validJson: boolean; - refs: boolean; - unions: boolean; - formats: boolean; - toolNameLength: number | undefined; -} - -export const defaultClientCapabilities: ClientCapabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, -}; - -export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); -export type ClientType = z.infer; - -// Client presets for compatibility -// Note that these could change over time as models get better, so this is -// a best effort. -export const knownClients: Record, ClientCapabilities> = { - 'openai-agents': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - claude: { - topLevelUnions: true, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - 'claude-code': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - cursor: { - topLevelUnions: false, - validJson: true, - refs: false, - unions: false, - formats: false, - toolNameLength: 50, - }, -}; - -/** - * Attempts to parse strings into JSON objects - */ -export function parseEmbeddedJSON(args: Record, schema: Record) { - let updated = false; - const newArgs: Record = Object.assign({}, args); - - for (const [key, value] of Object.entries(newArgs)) { - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value); - // Only parse if result is a plain object (not array, null, or primitive) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { - newArgs[key] = parsed; - updated = true; - } - } catch (e) { - // Not valid JSON, leave as is - } - } - } - - if (updated) { - return newArgs; - } - - return args; -} - -export type JSONSchema = { - type?: string; - properties?: Record; - required?: string[]; - anyOf?: JSONSchema[]; - $ref?: string; - $defs?: Record; - [key: string]: any; -}; - -/** - * Truncates tool names to the specified length while ensuring uniqueness. - * If truncation would cause duplicate names, appends a number to make them unique. - */ -export function truncateToolNames(names: string[], maxLength: number): Map { - if (maxLength <= 0) { - return new Map(); - } - - const renameMap = new Map(); - const usedNames = new Set(); - - const toTruncate = names.filter((name) => name.length > maxLength); - - if (toTruncate.length === 0) { - return renameMap; - } - - const willCollide = - new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; - - if (!willCollide) { - for (const name of toTruncate) { - const truncatedName = name.slice(0, maxLength); - renameMap.set(name, truncatedName); - } - } else { - const baseLength = maxLength - 1; - - for (const name of toTruncate) { - const baseName = name.slice(0, baseLength); - let counter = 1; - - while (usedNames.has(baseName + counter)) { - counter++; - } - - const finalName = baseName + counter; - renameMap.set(name, finalName); - usedNames.add(finalName); - } - } - - return renameMap; -} - -/** - * Removes top-level unions from a tool by splitting it into multiple tools, - * one for each variant in the union. - */ -export function removeTopLevelUnions(tool: Tool): Tool[] { - const inputSchema = tool.inputSchema as JSONSchema; - const variants = inputSchema.anyOf; - - if (!variants || !Array.isArray(variants) || variants.length === 0) { - return [tool]; - } - - const defs = inputSchema.$defs || {}; - - return variants.map((variant, index) => { - const variantSchema: JSONSchema = { - ...inputSchema, - ...variant, - type: 'object', - properties: { - ...(inputSchema.properties || {}), - ...(variant.properties || {}), - }, - }; - - delete variantSchema.anyOf; - - if (!variantSchema['description']) { - variantSchema['description'] = tool.description; - } - - const usedDefs = findUsedDefs(variant, defs); - if (Object.keys(usedDefs).length > 0) { - variantSchema.$defs = usedDefs; - } else { - delete variantSchema.$defs; - } - - return { - ...tool, - name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, - description: variant['description'] || tool.description, - inputSchema: variantSchema, - } as Tool; - }); -} - -function findUsedDefs( - schema: JSONSchema, - defs: Record, - visited: Set = new Set(), -): Record { - const usedDefs: Record = {}; - - if (typeof schema !== 'object' || schema === null) { - return usedDefs; - } - - if (schema.$ref) { - const refParts = schema.$ref.split('/'); - if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { - const defName = refParts[2]; - const def = defs[defName]; - if (def && !visited.has(schema.$ref)) { - usedDefs[defName] = def; - visited.add(schema.$ref); - Object.assign(usedDefs, findUsedDefs(def, defs, visited)); - visited.delete(schema.$ref); - } - } - return usedDefs; - } - - for (const key in schema) { - if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { - Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); - } - } - - return usedDefs; -} - -// Export for testing -export { findUsedDefs }; - -/** - * Inlines all $refs in a schema, eliminating $defs. - * If a circular reference is detected, the circular property is removed. - */ -export function inlineRefs(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - const clonedSchema = { ...schema }; - const defs: Record = schema.$defs || {}; - - delete clonedSchema.$defs; - - const result = inlineRefsRecursive(clonedSchema, defs, new Set()); - // The top level can never be null - return result === null ? {} : result; -} - -function inlineRefsRecursive( - schema: JSONSchema, - defs: Record, - refPath: Set, -): JSONSchema | null { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => { - const processed = inlineRefsRecursive(item, defs, refPath); - return processed === null ? {} : processed; - }) as JSONSchema; - } - - const result = { ...schema }; - - if ('$ref' in result && typeof result.$ref === 'string') { - if (result.$ref.startsWith('#/$defs/')) { - const refName = result.$ref.split('/').pop() as string; - const def = defs[refName]; - - // If we've already seen this ref in our path, we have a circular reference - if (refPath.has(result.$ref)) { - // For circular references, we completely remove the property - // by returning null. The parent will remove it. - return null; - } - - if (def) { - const newRefPath = new Set(refPath); - newRefPath.add(result.$ref); - - const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); - - if (inlinedDef === null) { - return { ...result }; - } - - // Merge the inlined definition with the original schema's properties - // but preserve things like description, etc. - const { $ref, ...rest } = result; - return { ...inlinedDef, ...rest }; - } - } - - // Keep external refs as-is - return result; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); - if (processed === null) { - // Remove properties that would cause circular references - delete result[key]; - } else { - result[key] = processed; - } - } - } - - return result; -} - -/** - * Removes anyOf fields from a schema, using only the first variant. - */ -export function removeAnyOf(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeAnyOf(item)) as JSONSchema; - } - - const result = { ...schema }; - - if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { - const firstVariant = result.anyOf[0]; - - if (firstVariant && typeof firstVariant === 'object') { - // Special handling for properties to ensure deep merge - if (firstVariant.properties && result.properties) { - result.properties = { - ...result.properties, - ...(firstVariant.properties as Record), - }; - } else if (firstVariant.properties) { - result.properties = { ...firstVariant.properties }; - } - - for (const key in firstVariant) { - if (key !== 'properties') { - result[key] = firstVariant[key]; - } - } - } - - delete result.anyOf; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeAnyOf(result[key] as JSONSchema); - } - } - - return result; -} - -/** - * Removes format fields from a schema and appends them to the description. - */ -export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { - if (formatsCapability) { - return schema; - } - - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; - } - - const result = { ...schema }; - - if ('format' in result && typeof result['format'] === 'string') { - const formatStr = `(format: "${result['format']}")`; - - if ('description' in result && typeof result['description'] === 'string') { - result['description'] = `${result['description']} ${formatStr}`; - } else { - result['description'] = formatStr; - } - - delete result['format']; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); - } - } - - return result; -} - -/** - * Applies all compatibility transformations to the endpoints based on the provided capabilities. - */ -export function applyCompatibilityTransformations( - endpoints: Endpoint[], - capabilities: ClientCapabilities, -): Endpoint[] { - let transformedEndpoints = [...endpoints]; - - // Handle top-level unions first as this changes tool names - if (!capabilities.topLevelUnions) { - const newEndpoints: Endpoint[] = []; - - for (const endpoint of transformedEndpoints) { - const variantTools = removeTopLevelUnions(endpoint.tool); - - if (variantTools.length === 1) { - newEndpoints.push(endpoint); - } else { - for (const variantTool of variantTools) { - newEndpoints.push({ - ...endpoint, - tool: variantTool, - }); - } - } - } - - transformedEndpoints = newEndpoints; - } - - if (capabilities.toolNameLength) { - const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); - const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); - - transformedEndpoints = transformedEndpoints.map((endpoint) => ({ - ...endpoint, - tool: { - ...endpoint.tool, - name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, - }, - })); - } - - if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { - transformedEndpoints = transformedEndpoints.map((endpoint) => { - let schema = endpoint.tool.inputSchema as JSONSchema; - - if (!capabilities.refs) { - schema = inlineRefs(schema); - } - - if (!capabilities.unions) { - schema = removeAnyOf(schema); - } - - if (!capabilities.formats) { - schema = removeFormats(schema, capabilities.formats); - } - - return { - ...endpoint, - tool: { - ...endpoint.tool, - inputSchema: schema as typeof endpoint.tool.inputSchema, - }, - }; - }); - } - - return transformedEndpoints; -} - -function toSnakeCase(str: string): string { - return str - .replace(/\s+/g, '_') - .replace(/([a-z])([A-Z])/g, '$1_$2') - .toLowerCase(); -} diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 015a5ba1..9af38ab5 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from './tools/types'; +import { Metadata, asTextContentResult } from './types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts deleted file mode 100644 index 53676da1..00000000 --- a/packages/mcp-server/src/dynamic-tools.ts +++ /dev/null @@ -1,153 +0,0 @@ -import ImageKit from '@imagekit/nodejs'; -import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; -import { zodToJsonSchema } from 'zod-to-json-schema'; -import { z } from 'zod'; -import { Cabidela } from '@cloudflare/cabidela'; - -function zodToInputSchema(schema: z.ZodSchema) { - return { - type: 'object' as const, - ...(zodToJsonSchema(schema) as any), - }; -} - -/** - * A list of tools that expose all the endpoints in the API dynamically. - * - * 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. - * - * @param endpoints - The endpoints to include in the list. - */ -export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { - const listEndpointsSchema = z.object({ - search_query: z - .string() - .optional() - .describe( - 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', - ), - }); - - const listEndpointsTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'list_api_endpoints', - description: 'List or search for all endpoints in the Image Kit TypeScript API', - inputSchema: zodToInputSchema(listEndpointsSchema), - }, - handler: async (client: ImageKit, args: Record | undefined): Promise => { - const query = args && listEndpointsSchema.parse(args).search_query?.trim(); - - const filteredEndpoints = - query && query.length > 0 ? - endpoints.filter((endpoint) => { - const fieldsToMatch = [ - endpoint.tool.name, - endpoint.tool.description, - endpoint.metadata.resource, - endpoint.metadata.operation, - ...endpoint.metadata.tags, - ]; - return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); - }) - : endpoints; - - return asTextContentResult({ - tools: filteredEndpoints.map(({ tool, metadata }) => ({ - name: tool.name, - description: tool.description, - resource: metadata.resource, - operation: metadata.operation, - tags: metadata.tags, - })), - }); - }, - }; - - const getEndpointSchema = z.object({ - endpoint: z.string().describe('The name of the endpoint to get the schema for.'), - }); - const getEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'get_api_endpoint_schema', - description: - 'Get the schema for an endpoint in the Image Kit TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', - inputSchema: zodToInputSchema(getEndpointSchema), - }, - handler: async (client: ImageKit, args: Record | undefined) => { - if (!args) { - throw new Error('No endpoint provided'); - } - const endpointName = getEndpointSchema.parse(args).endpoint; - - const endpoint = endpoints.find((e) => e.tool.name === endpointName); - if (!endpoint) { - throw new Error(`Endpoint ${endpointName} not found`); - } - return asTextContentResult(endpoint.tool); - }, - }; - - const invokeEndpointSchema = z.object({ - endpoint_name: z.string().describe('The name of the endpoint to invoke.'), - args: z - .record(z.string(), z.any()) - .describe( - 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', - ), - }); - - const invokeEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'write' as const, - tags: [], - }, - tool: { - name: 'invoke_api_endpoint', - description: - 'Invoke an endpoint in the Image Kit TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', - inputSchema: zodToInputSchema(invokeEndpointSchema), - }, - handler: async (client: ImageKit, args: Record | undefined): Promise => { - if (!args) { - throw new Error('No endpoint provided'); - } - const { success, data, error } = invokeEndpointSchema.safeParse(args); - if (!success) { - throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); - } - const { endpoint_name, args: endpointArgs } = data; - - const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); - if (!endpoint) { - throw new Error( - `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, - ); - } - - try { - // Try to validate the arguments for a better error message - const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); - cabidela.validate(endpointArgs); - } catch (error) { - throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); - } - - return await endpoint.handler(client, endpointArgs); - }, - }; - - return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; -} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts deleted file mode 100644 index eaae0fcf..00000000 --- a/packages/mcp-server/src/filtering.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-nocheck -import initJq from 'jq-web'; - -export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { - if (jqFilter && typeof jqFilter === 'string') { - return await jq(response, jqFilter); - } else { - return response; - } -} - -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/http.ts b/packages/mcp-server/src/http.ts index 84517003..2366d8f9 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -4,38 +4,21 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import express from 'express'; -import { fromError } from 'zod-validation-error/v3'; -import { McpOptions, parseQueryOptions } from './options'; +import { McpOptions } from './options'; import { ClientOptions, initMcpServer, newMcpServer } from './server'; import { parseAuthHeaders } from './headers'; const newServer = ({ clientOptions, - mcpOptions: defaultMcpOptions, req, res, }: { clientOptions: ClientOptions; - mcpOptions: McpOptions; req: express.Request; res: express.Response; }): McpServer | null => { const server = newMcpServer(); - let mcpOptions: McpOptions; - try { - mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); - } catch (error) { - res.status(400).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: `Invalid request: ${fromError(error)}`, - }, - }); - return null; - } - try { const authOptions = parseAuthHeaders(req); initMcpServer({ @@ -44,7 +27,6 @@ const newServer = ({ ...clientOptions, ...authOptions, }, - mcpOptions, }); } catch (error) { res.status(401).json({ diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 4850a0e2..0f6dd426 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,20 +1,15 @@ #!/usr/bin/env node import { selectTools } from './server'; -import { Endpoint, endpoints } from './tools'; import { McpOptions, parseCLIOptions } from './options'; import { launchStdioServer } from './stdio'; import { launchStreamableHTTPServer } from './http'; +import type { McpTool } from './types'; async function main() { const options = parseOptionsOrError(); - if (options.list) { - listAllTools(); - return; - } - - const selectedTools = await selectToolsOrError(endpoints, options); + const selectedTools = await selectToolsOrError(options); console.error( `MCP Server starting with ${selectedTools.length} tools:`, @@ -23,7 +18,7 @@ async function main() { switch (options.transport) { case 'stdio': - await launchStdioServer(options); + await launchStdioServer(); break; case 'http': await launchStreamableHTTPServer(options, options.port ?? options.socket); @@ -47,9 +42,9 @@ function parseOptionsOrError() { } } -async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise { +async function selectToolsOrError(options: McpOptions): Promise { try { - const includedTools = await selectTools(endpoints, options); + const includedTools = selectTools(options); if (includedTools.length === 0) { console.error('No tools match the provided filters.'); process.exit(1); @@ -64,45 +59,3 @@ async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): P process.exit(1); } } - -function listAllTools() { - if (endpoints.length === 0) { - console.log('No tools available.'); - return; - } - console.log('Available tools:\n'); - - // Group endpoints by resource - const resourceGroups = new Map(); - - for (const endpoint of endpoints) { - const resource = endpoint.metadata.resource; - if (!resourceGroups.has(resource)) { - resourceGroups.set(resource, []); - } - resourceGroups.get(resource)!.push(endpoint); - } - - // Sort resources alphabetically - const sortedResources = Array.from(resourceGroups.keys()).sort(); - - // Display hierarchically by resource - for (const resource of sortedResources) { - console.log(`Resource: ${resource}`); - - const resourceEndpoints = resourceGroups.get(resource)!; - // Sort endpoints by tool name - resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); - - for (const endpoint of resourceEndpoints) { - const { - tool, - metadata: { operation, tags }, - } = endpoint; - - console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); - console.log(` Description: ${tool.description}`); - } - console.log(''); - } -} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index b6ff5976..6c8bb8d1 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -2,140 +2,31 @@ import qs from 'qs'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import z from 'zod'; -import { endpoints, Filter } from './tools'; -import { ClientCapabilities, knownClients, ClientType } from './compat'; export type CLIOptions = McpOptions & { - list: boolean; transport: 'stdio' | 'http'; port: number | undefined; socket: string | undefined; }; export type McpOptions = { - client?: ClientType | undefined; - includeDynamicTools?: boolean | undefined; - includeAllTools?: boolean | undefined; - includeCodeTools?: boolean | undefined; includeDocsTools?: boolean | undefined; - filters?: Filter[] | undefined; - capabilities?: Partial | undefined; }; -const CAPABILITY_CHOICES = [ - 'top-level-unions', - 'valid-json', - 'refs', - 'unions', - 'formats', - 'tool-name-length', -] as const; - -type Capability = (typeof CAPABILITY_CHOICES)[number]; - -function parseCapabilityValue(cap: string): { name: Capability; value?: number } { - if (cap.startsWith('tool-name-length=')) { - const parts = cap.split('='); - if (parts.length === 2) { - const length = parseInt(parts[1]!, 10); - if (!isNaN(length)) { - return { name: 'tool-name-length', value: length }; - } - throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); - } - throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); - } - if (!CAPABILITY_CHOICES.includes(cap as Capability)) { - throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); - } - return { name: cap as Capability }; -} - export function parseCLIOptions(): CLIOptions { const opts = yargs(hideBin(process.argv)) .option('tools', { type: 'string', array: true, - choices: ['dynamic', 'all', 'code', 'docs'], + choices: ['code', 'docs'], description: 'Use dynamic tools or all tools', }) .option('no-tools', { type: 'string', array: true, - choices: ['dynamic', 'all', 'code', 'docs'], + choices: ['code', 'docs'], description: 'Do not use any dynamic or all tools', }) - .option('tool', { - type: 'string', - array: true, - description: 'Include tools matching the specified names', - }) - .option('resource', { - type: 'string', - array: true, - description: 'Include tools matching the specified resources', - }) - .option('operation', { - type: 'string', - array: true, - choices: ['read', 'write'], - description: 'Include tools matching the specified operations', - }) - .option('tag', { - type: 'string', - array: true, - description: 'Include tools with the specified tags', - }) - .option('no-tool', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified names', - }) - .option('no-resource', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified resources', - }) - .option('no-operation', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified operations', - }) - .option('no-tag', { - type: 'string', - array: true, - description: 'Exclude tools with the specified tags', - }) - .option('list', { - type: 'boolean', - description: 'List all tools and exit', - }) - .option('client', { - type: 'string', - choices: Object.keys(knownClients), - description: 'Specify the MCP client being used', - }) - .option('capability', { - type: 'string', - array: true, - description: 'Specify client capabilities', - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('no-capability', { - type: 'string', - array: true, - description: 'Unset client capabilities', - choices: CAPABILITY_CHOICES, - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('describe-capabilities', { - type: 'boolean', - description: 'Print detailed explanation of client capabilities and exit', - }) .option('transport', { type: 'string', choices: ['stdio', 'http'], @@ -152,122 +43,19 @@ export function parseCLIOptions(): CLIOptions { }) .help(); - for (const [command, desc] of examples()) { - opts.example(command, desc); - } - const argv = opts.parseSync(); - // Handle describe-capabilities flag - if (argv.describeCapabilities) { - console.log(getCapabilitiesExplanation()); - process.exit(0); - } - - const filters: Filter[] = []; - - // Helper function to support comma-separated values - const splitValues = (values: string[] | undefined): string[] => { - if (!values) return []; - return values.flatMap((v) => v.split(',')); - }; - - for (const tag of splitValues(argv.tag)) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - - for (const tag of splitValues(argv.noTag)) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - - for (const resource of splitValues(argv.resource)) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - - for (const resource of splitValues(argv.noResource)) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - - for (const tool of splitValues(argv.tool)) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - - for (const tool of splitValues(argv.noTool)) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - for (const operation of splitValues(argv.operation)) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - - for (const operation of splitValues(argv.noOperation)) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - - // Parse client capabilities - const clientCapabilities: Partial = {}; - - // Apply individual capability overrides - if (Array.isArray(argv.capability)) { - for (const cap of argv.capability) { - const parsedCap = parseCapabilityValue(cap); - if (parsedCap.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsedCap.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsedCap.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsedCap.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsedCap.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsedCap.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsedCap.value; - } - } - } - - // Handle no-capability options to unset capabilities - if (Array.isArray(argv.noCapability)) { - for (const cap of argv.noCapability) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - } - - const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code' | 'docs') => + const shouldIncludeToolType = (toolType: 'code' | 'docs') => argv.noTools?.includes(toolType) ? false : argv.tools?.includes(toolType) ? true : undefined; - const includeDynamicTools = shouldIncludeToolType('dynamic'); - const includeAllTools = shouldIncludeToolType('all'); - const includeCodeTools = shouldIncludeToolType('code'); const includeDocsTools = shouldIncludeToolType('docs'); const transport = argv.transport as 'stdio' | 'http'; - const client = argv.client as ClientType; return { - client: client && client !== 'infer' && knownClients[client] ? client : undefined, - includeDynamicTools, - includeAllTools, - includeCodeTools, includeDocsTools, - filters, - capabilities: clientCapabilities, - list: argv.list || false, transport, port: argv.port, socket: argv.socket, @@ -284,190 +72,21 @@ const coerceArray = (zodType: T) => ); const QueryOptions = z.object({ - 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.', - ), + tools: coerceArray(z.enum(['code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['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( - 'Include tools matching the specified operations', - ), - tag: coerceArray(z.string()).describe('Include tools with the specified tags'), - no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), - no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), - no_operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Exclude tools matching the specified operations', - ), - no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), - client: ClientType.optional().describe('Specify the MCP client being used'), - capability: coerceArray(z.string()).describe('Specify client capabilities'), - no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), }); export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { const queryObject = typeof query === 'string' ? qs.parse(query) : query; const queryOptions = QueryOptions.parse(queryObject); - const filters: Filter[] = [...(defaultOptions.filters ?? [])]; - - for (const resource of queryOptions.resource || []) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - for (const operation of queryOptions.operation || []) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - for (const tag of queryOptions.tag || []) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - for (const tool of queryOptions.tool || []) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - for (const resource of queryOptions.no_resource || []) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - for (const operation of queryOptions.no_operation || []) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - for (const tag of queryOptions.no_tag || []) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - for (const tool of queryOptions.no_tool || []) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - // Parse client capabilities - const clientCapabilities: Partial = { ...defaultOptions.capabilities }; - - for (const cap of queryOptions.capability || []) { - const parsed = parseCapabilityValue(cap); - if (parsed.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsed.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsed.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsed.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsed.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsed.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsed.value; - } - } - - for (const cap of queryOptions.no_capability || []) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - - let dynamicTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false - : queryOptions.tools?.includes('dynamic') ? true - : defaultOptions.includeDynamicTools; - - let allTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false - : queryOptions.tools?.includes('all') ? true - : defaultOptions.includeAllTools; - let docsTools: boolean | undefined = queryOptions.no_tools && queryOptions.no_tools?.includes('docs') ? false : queryOptions.tools?.includes('docs') ? true : defaultOptions.includeDocsTools; - 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: codeTools, includeDocsTools: docsTools, - filters, - capabilities: clientCapabilities, }; } - -function getCapabilitiesExplanation(): string { - return ` -Client Capabilities Explanation: - -Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. - -When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. - -Available Capabilities: - -# top-level-unions -Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. - -# refs -Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. - -# valid-json -Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. - -# unions -Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. - -# formats -Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. - -# tool-name-length=N -Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. - -Client Presets (--client): -Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. - -Current presets: -${JSON.stringify(knownClients, null, 2)} - `; -} - -function examples(): [string, string][] { - const firstEndpoint = endpoints[0]!; - const secondEndpoint = - endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; - const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; - const otherEndpoint = secondEndpoint || firstEndpoint; - - return [ - [ - `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, - 'Include tools by name', - ], - [ - `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, - 'Filter by resource and operation', - ], - [ - `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, - 'Use resource wildcards and exclusions', - ], - [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], - [ - `--capability="top-level-unions" --capability="tool-name-length=40"`, - 'Specify individual client capabilities', - ], - [ - `--client="cursor" --no-capability="tool-name-length"`, - 'Use cursor client preset but remove tool name length limit', - ], - ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), - ]; -} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 457cbb4b..40473d31 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -2,33 +2,20 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Endpoint, endpoints, HandlerFunction, query } from './tools'; import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, - Implementation, - Tool, } from '@modelcontextprotocol/sdk/types.js'; import { ClientOptions } from '@imagekit/nodejs'; import ImageKit from '@imagekit/nodejs'; -import { - applyCompatibilityTransformations, - ClientCapabilities, - defaultClientCapabilities, - knownClients, - parseEmbeddedJSON, -} from './compat'; -import { dynamicTools } from './dynamic-tools'; import { codeTool } from './code-tool'; import docsSearchTool from './docs-search-tool'; import { McpOptions } from './options'; +import { HandlerFunction, McpTool } from './types'; export { McpOptions } from './options'; -export { ClientType } from './compat'; -export { Filter } from './tools'; export { ClientOptions } from '@imagekit/nodejs'; -export { endpoints } from './tools'; export const newMcpServer = () => new McpServer( @@ -52,25 +39,6 @@ export function initMcpServer(params: { mcpOptions?: McpOptions; }) { const server = params.server instanceof McpServer ? params.server.server : params.server; - const mcpOptions = params.mcpOptions ?? {}; - - let providedEndpoints: Endpoint[] | null = null; - let endpointMap: Record | null = null; - - const initTools = async (implementation?: Implementation) => { - if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { - mcpOptions.client = - implementation.name.toLowerCase().includes('claude') ? 'claude' - : implementation.name.toLowerCase().includes('cursor') ? 'cursor' - : undefined; - mcpOptions.capabilities = { - ...(mcpOptions.client && knownClients[mcpOptions.client]), - ...mcpOptions.capabilities, - }; - } - providedEndpoints ??= await selectTools(endpoints, mcpOptions); - endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); - }; const logAtLevel = (level: 'debug' | 'info' | 'warning' | 'error') => @@ -96,26 +64,23 @@ export function initMcpServer(params: { }, }); + const providedTools = selectTools(params.mcpOptions); + const toolMap = Object.fromEntries(providedTools.map((mcpTool) => [mcpTool.tool.name, mcpTool])); + server.setRequestHandler(ListToolsRequestSchema, async () => { - if (providedEndpoints === null) { - await initTools(server.getClientVersion()); - } return { - tools: providedEndpoints!.map((endpoint) => endpoint.tool), + tools: providedTools.map((mcpTool) => mcpTool.tool), }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (endpointMap === null) { - await initTools(server.getClientVersion()); - } const { name, arguments: args } = request.params; - const endpoint = endpointMap![name]; - if (!endpoint) { + const mcpTool = toolMap[name]; + if (!mcpTool) { throw new Error(`Unknown tool: ${name}`); } - return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); + return executeHandler(mcpTool.handler, client, args); }); server.setRequestHandler(SetLevelRequestSchema, async (request) => { @@ -145,47 +110,22 @@ export function initMcpServer(params: { /** * Selects the tools to include in the MCP Server based on the provided options. */ -export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise { - const filteredEndpoints = query(options?.filters ?? [], endpoints); - - let includedTools = filteredEndpoints.slice(); - - if (includedTools.length > 0) { - if (options?.includeDynamicTools) { - includedTools = dynamicTools(includedTools); - } - } else { - if (options?.includeAllTools) { - includedTools = endpoints.slice(); - } else if (options?.includeDynamicTools) { - includedTools = dynamicTools(endpoints); - } else if (options?.includeCodeTools) { - includedTools = [await codeTool()]; - } else { - includedTools = endpoints.slice(); - } - } +export function selectTools(options?: McpOptions): McpTool[] { + const includedTools = [codeTool()]; if (options?.includeDocsTools ?? true) { includedTools.push(docsSearchTool); } - const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; - return applyCompatibilityTransformations(includedTools, capabilities); + return includedTools; } /** * Runs the provided handler with the given client and arguments. */ export async function executeHandler( - tool: Tool, handler: HandlerFunction, client: ImageKit, args: Record | undefined, - compatibilityOptions?: Partial, ) { - const options = { ...defaultClientCapabilities, ...compatibilityOptions }; - if (!options.validJson && args) { - args = parseEmbeddedJSON(args, tool.inputSchema); - } return await handler(client, args || {}); } diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts index d902a5bb..f07696f3 100644 --- a/packages/mcp-server/src/stdio.ts +++ b/packages/mcp-server/src/stdio.ts @@ -1,11 +1,10 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { initMcpServer, newMcpServer } from './server'; -import { McpOptions } from './options'; -export const launchStdioServer = async (options: McpOptions) => { +export const launchStdioServer = async () => { const server = newMcpServer(); - initMcpServer({ server, mcpOptions: options }); + initMcpServer({ server }); const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts deleted file mode 100644 index 7e516de7..00000000 --- a/packages/mcp-server/src/tools.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts deleted file mode 100644 index b802ca2a..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts +++ /dev/null @@ -1,325 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/accounts/origins', - operationId: 'create-origin', -}; - -export const tool: Tool = { - name: 'create_accounts_origins', - description: - '**Note:** This API is currently in beta. \nCreates a new origin and returns the origin object.\n', - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - endpoint: { - type: 'string', - description: 'Custom S3-compatible endpoint.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3_COMPATIBLE'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - s3ForcePathStyle: { - type: 'boolean', - description: 'Use path-style S3 URLs?', - }, - }, - required: ['accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['CLOUDINARY_BACKUP'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - properties: { - baseUrl: { - type: 'string', - description: 'Root URL for the web folder origin.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_FOLDER'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - forwardHostHeaderToOrigin: { - type: 'boolean', - description: 'Forward the Host header to origin?', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['baseUrl', 'name', 'type'], - }, - { - type: 'object', - properties: { - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_PROXY'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['name', 'type'], - }, - { - type: 'object', - properties: { - bucket: { - type: 'string', - }, - clientEmail: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - privateKey: { - type: 'string', - }, - type: { - type: 'string', - enum: ['GCS'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['bucket', 'clientEmail', 'name', 'privateKey', 'type'], - }, - { - type: 'object', - properties: { - accountName: { - type: 'string', - }, - container: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - sasToken: { - type: 'string', - }, - type: { - type: 'string', - enum: ['AZURE_BLOB'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['accountName', 'container', 'name', 'sasToken', 'type'], - }, - { - type: 'object', - properties: { - baseUrl: { - type: 'string', - description: 'Akeneo instance base URL.', - }, - clientId: { - type: 'string', - description: 'Akeneo API client ID.', - }, - clientSecret: { - type: 'string', - description: 'Akeneo API client secret.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - password: { - type: 'string', - description: 'Akeneo API password.', - }, - type: { - type: 'string', - enum: ['AKENEO_PIM'], - }, - username: { - type: 'string', - description: 'Akeneo API username.', - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], - }, - ], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const body = args as any; - try { - return asTextContentResult(await client.accounts.origins.create(body)); - } catch (error) { - if (error instanceof ImageKit.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts deleted file mode 100644 index 9cb4d97e..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts +++ /dev/null @@ -1,43 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/accounts/origins/{id}', - operationId: 'delete-origin', -}; - -export const tool: Tool = { - name: 'delete_accounts_origins', - description: - '**Note:** This API is currently in beta. \nPermanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - required: ['id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - const response = await client.accounts.origins.delete(id).asResponse(); - return asTextContentResult(await response.text()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts deleted file mode 100644 index c699ff3c..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts +++ /dev/null @@ -1,48 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/origins/{id}', - operationId: 'get-origin', -}; - -export const tool: Tool = { - name: 'get_accounts_origins', - description: '**Note:** This API is currently in beta. \nRetrieves the origin identified by `id`.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - try { - return asTextContentResult(await client.accounts.origins.get(id)); - } catch (error) { - if (error instanceof ImageKit.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts deleted file mode 100644 index e82908d6..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts +++ /dev/null @@ -1,42 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/origins', - operationId: 'list-origins', -}; - -export const tool: Tool = { - name: 'list_accounts_origins', - description: - '**Note:** This API is currently in beta. \nReturns an array of all configured origins for the current account.\n', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - try { - return asTextContentResult(await client.accounts.origins.list()); - } catch (error) { - if (error instanceof ImageKit.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts deleted file mode 100644 index 515081fd..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts +++ /dev/null @@ -1,367 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/accounts/origins/{id}', - operationId: 'update-origin', -}; - -export const tool: Tool = { - name: 'update_accounts_origins', - description: - '**Note:** This API is currently in beta. \nUpdates the origin identified by `id` and returns the updated origin object.\n', - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['id', 'accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - endpoint: { - type: 'string', - description: 'Custom S3-compatible endpoint.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3_COMPATIBLE'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - s3ForcePathStyle: { - type: 'boolean', - description: 'Use path-style S3 URLs?', - }, - }, - required: ['id', 'accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['CLOUDINARY_BACKUP'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['id', 'accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - baseUrl: { - type: 'string', - description: 'Root URL for the web folder origin.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_FOLDER'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - forwardHostHeaderToOrigin: { - type: 'boolean', - description: 'Forward the Host header to origin?', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['id', 'baseUrl', 'name', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_PROXY'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['id', 'name', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - bucket: { - type: 'string', - }, - clientEmail: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - privateKey: { - type: 'string', - }, - type: { - type: 'string', - enum: ['GCS'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['id', 'bucket', 'clientEmail', 'name', 'privateKey', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - accountName: { - type: 'string', - }, - container: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - sasToken: { - type: 'string', - }, - type: { - type: 'string', - enum: ['AZURE_BLOB'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['id', 'accountName', 'container', 'name', 'sasToken', 'type'], - }, - { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - baseUrl: { - type: 'string', - description: 'Akeneo instance base URL.', - }, - clientId: { - type: 'string', - description: 'Akeneo API client ID.', - }, - clientSecret: { - type: 'string', - description: 'Akeneo API client secret.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - password: { - type: 'string', - description: 'Akeneo API password.', - }, - type: { - type: 'string', - enum: ['AKENEO_PIM'], - }, - username: { - type: 'string', - description: 'Akeneo API username.', - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['id', 'baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], - }, - ], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - try { - return asTextContentResult(await client.accounts.origins.update(id, body)); - } catch (error) { - if (error instanceof ImageKit.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts deleted file mode 100644 index 4696ba95..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts +++ /dev/null @@ -1,110 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/accounts/url-endpoints', - operationId: 'create-url-endpoint', -}; - -export const tool: Tool = { - name: 'create_accounts_url_endpoints', - 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\n**Note:** This API is currently in beta. \nCreates a new URL‑endpoint and returns the resulting object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - description: { - type: 'string', - description: 'Description of the URL endpoint.', - }, - origins: { - type: 'array', - description: - 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', - items: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - urlPrefix: { - type: 'string', - description: - 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', - }, - urlRewriter: { - anyOf: [ - { - type: 'object', - title: 'Cloudinary URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['CLOUDINARY'], - }, - preserveAssetDeliveryTypes: { - type: 'boolean', - description: 'Whether to preserve `/` in the rewritten URL.', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Imgix URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['IMGIX'], - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Akamai URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['AKAMAI'], - }, - }, - required: ['type'], - }, - ], - description: 'Configuration for third-party URL rewriting.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['description'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.create(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts deleted file mode 100644 index f6894b47..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts +++ /dev/null @@ -1,43 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/accounts/url-endpoints/{id}', - operationId: 'delete-url-endpoint', -}; - -export const tool: Tool = { - name: 'delete_accounts_url_endpoints', - description: - '**Note:** This API is currently in beta. \nDeletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', - }, - }, - required: ['id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - const response = await client.accounts.urlEndpoints.delete(id).asResponse(); - return asTextContentResult(await response.text()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts deleted file mode 100644 index 5199e01f..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/url-endpoints/{id}', - operationId: 'get-url-endpoint', -}; - -export const tool: Tool = { - name: 'get_accounts_url_endpoints', - 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\n**Note:** This API is currently in beta. \nRetrieves the URL‑endpoint identified by `id`.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(id))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts deleted file mode 100644 index a8d6262f..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/url-endpoints', - operationId: 'list-url-endpoints', -}; - -export const tool: Tool = { - name: 'list_accounts_url_endpoints', - 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\n**Note:** This API is currently in beta. \nReturns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_list_response',\n $defs: {\n url_endpoint_list_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/url_endpoint_response'\n }\n },\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.list())); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts deleted file mode 100644 index a1d5f56f..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts +++ /dev/null @@ -1,119 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/accounts/url-endpoints/{id}', - operationId: 'update-url-endpoint', -}; - -export const tool: Tool = { - name: 'update_accounts_url_endpoints', - 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\n**Note:** This API is currently in beta. \nUpdates the URL‑endpoint identified by `id` and returns the updated object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', - }, - description: { - type: 'string', - description: 'Description of the URL endpoint.', - }, - origins: { - type: 'array', - description: - 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', - items: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - urlPrefix: { - type: 'string', - description: - 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', - }, - urlRewriter: { - anyOf: [ - { - type: 'object', - title: 'Cloudinary URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['CLOUDINARY'], - }, - preserveAssetDeliveryTypes: { - type: 'boolean', - description: 'Whether to preserve `/` in the rewritten URL.', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Imgix URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['IMGIX'], - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Akamai URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['AKAMAI'], - }, - }, - required: ['type'], - }, - ], - description: 'Configuration for third-party URL rewriting.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'description'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(id, body)), - ); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts deleted file mode 100644 index dffb2647..00000000 --- a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.usage', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/usage', - operationId: 'get-usage', -}; - -export const tool: Tool = { - name: 'get_accounts_usage', - 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\nGet the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/usage_get_response',\n $defs: {\n usage_get_response: {\n type: 'object',\n properties: {\n bandwidthBytes: {\n type: 'integer',\n description: 'Amount of bandwidth used in bytes.'\n },\n extensionUnitsCount: {\n type: 'integer',\n description: 'Number of extension units used.'\n },\n mediaLibraryStorageBytes: {\n type: 'integer',\n description: 'Storage used by media library in bytes.'\n },\n originalCacheStorageBytes: {\n type: 'integer',\n description: 'Storage used by the original cache in bytes.'\n },\n videoProcessingUnitsCount: {\n type: 'integer',\n description: 'Number of video processing units used.'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - endDate: { - type: 'string', - description: - 'Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. The difference between `startDate` and `endDate` should be less than 90 days.', - format: 'date', - }, - startDate: { - type: 'string', - description: - 'Specify a `startDate` in `YYYY-MM-DD` format. It should be before the `endDate`. The difference between `startDate` and `endDate` should be less than 90 days.', - format: 'date', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['endDate', 'startDate'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts deleted file mode 100644 index 007773e3..00000000 --- a/packages/mcp-server/src/tools/assets/list-assets.ts +++ /dev/null @@ -1,101 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'assets', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files', - operationId: 'list-and-search-assets', -}; - -export const tool: Tool = { - name: 'list_assets', - 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\nThis API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/asset_list_response',\n $defs: {\n asset_list_response: {\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/file'\n },\n {\n $ref: '#/$defs/folder'\n }\n ],\n description: 'Object containing details of a file or file version.'\n }\n },\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n },\n folder: {\n type: 'object',\n title: 'Folder',\n properties: {\n createdAt: {\n type: 'string',\n description: 'Date and time when the folder was created. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n folderId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n folderPath: {\n type: 'string',\n description: 'Path of the folder. This is the path you would use in the URL to access the folder. For example, if the folder is at the root of the media library, the path will be /folder. If the folder is inside another folder named images, the path will be /images/folder.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'folder'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the folder was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileType: { - type: 'string', - description: - 'Filter results by file type.\n\n- `all` — include all file types \n- `image` — include only image files \n- `non-image` — include only non-image files (e.g., JS, CSS, video)', - enum: ['all', 'image', 'non-image'], - }, - limit: { - type: 'integer', - description: 'The maximum number of results to return in response.\n', - }, - path: { - type: 'string', - description: - 'Folder path if you want to limit the search within a specific folder. For example, `/sales-banner/` will only search in folder sales-banner.\n\nNote : If your use case involves searching within a folder as well as its subfolders, you can use `path` parameter in `searchQuery` with appropriate operator.\nCheckout [Supported parameters](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#supported-parameters) for more information.\n', - }, - searchQuery: { - type: 'string', - description: - 'Query string in a Lucene-like query language e.g. `createdAt > "7d"`.\n\nNote : When the searchQuery parameter is present, the following query parameters will have no effect on the result:\n\n1. `tags`\n2. `type`\n3. `name`\n\n[Learn more](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#advanced-search-queries) from examples.\n', - }, - skip: { - type: 'integer', - description: 'The number of results to skip before returning results.\n', - }, - sort: { - type: 'string', - description: 'Sort the results by one of the supported fields in ascending or descending order.', - enum: [ - 'ASC_NAME', - 'DESC_NAME', - 'ASC_CREATED', - 'DESC_CREATED', - 'ASC_UPDATED', - 'DESC_UPDATED', - 'ASC_HEIGHT', - 'DESC_HEIGHT', - 'ASC_WIDTH', - 'DESC_WIDTH', - 'ASC_SIZE', - 'DESC_SIZE', - 'ASC_RELEVANCE', - 'DESC_RELEVANCE', - ], - }, - type: { - type: 'string', - description: - 'Filter results by asset type.\n\n- `file` — returns only files \n- `file-version` — returns specific file versions \n- `folder` — returns only folders \n- `all` — returns both files and folders (excludes `file-version`)', - enum: ['file', 'file-version', 'folder', 'all'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts deleted file mode 100644 index 533c6d33..00000000 --- a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts +++ /dev/null @@ -1,323 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'beta.v2.files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/api/v2/files/upload', - operationId: 'upload-file-v2', -}; - -export const tool: Tool = { - name: 'upload_v2_beta_files', - description: - 'The V2 API enhances security by verifying the entire payload using JWT. This API is in beta.\n\nImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', - inputSchema: { - type: 'object', - properties: { - file: { - type: 'string', - description: - 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', - }, - fileName: { - type: 'string', - description: 'The name with which the file has to be uploaded.\n', - }, - token: { - type: 'string', - description: - "This is the client-generated JSON Web Token (JWT). The ImageKit.io server uses it to authenticate and check that the upload request parameters have not been tampered with after the token has been generated. Learn how to create the token on the page below. This field is only required for authentication when uploading a file from the client side.\n\n\n**Note**: Sending a JWT that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new token.\n\n\n**⚠️Warning**: JWT must be generated on the server-side because it is generated using your account's private API key. This field is required for authentication when uploading a file from the client-side.\n", - }, - checks: { - type: 'string', - description: - 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file-v2#upload-api-checks).\n', - }, - customCoordinates: { - type: 'string', - description: - 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', - }, - customMetadata: { - type: 'object', - description: - 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', - additionalProperties: true, - }, - description: { - type: 'string', - description: 'Optional text to describe the contents of the file.\n', - }, - extensions: { - $ref: '#/$defs/extensions', - }, - folder: { - type: 'string', - description: - "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created. Using multiple `/` creates a nested folder.\n", - }, - isPrivateFile: { - type: 'boolean', - description: - 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', - }, - isPublished: { - type: 'boolean', - description: - 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', - }, - overwriteAITags: { - type: 'boolean', - description: - 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', - }, - overwriteCustomMetadata: { - type: 'boolean', - description: - 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', - }, - overwriteFile: { - type: 'boolean', - description: - 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', - }, - overwriteTags: { - type: 'boolean', - description: - 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', - }, - responseFields: { - type: 'array', - description: 'Array of response field keys to include in the API response body.\n', - items: { - type: 'string', - enum: [ - 'tags', - 'customCoordinates', - 'isPrivateFile', - 'embeddedMetadata', - 'isPublished', - 'customMetadata', - 'metadata', - 'selectedFieldsSchema', - ], - }, - }, - tags: { - type: 'array', - description: - 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', - items: { - type: 'string', - }, - }, - transformation: { - type: 'object', - description: - "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", - properties: { - post: { - type: 'array', - description: - 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Simple post-transformation', - properties: { - type: { - type: 'string', - description: 'Transformation type.', - enum: ['transformation'], - }, - value: { - type: 'string', - description: - 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', - }, - }, - required: ['type', 'value'], - }, - { - type: 'object', - title: 'Convert GIF to video', - properties: { - type: { - type: 'string', - description: 'Converts an animated GIF into an MP4.', - enum: ['gif-to-video'], - }, - value: { - type: 'string', - description: - 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Generate a thumbnail', - properties: { - type: { - type: 'string', - description: 'Generates a thumbnail image.', - enum: ['thumbnail'], - }, - value: { - type: 'string', - description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Adaptive Bitrate Streaming', - properties: { - protocol: { - type: 'string', - description: 'Streaming protocol to use (`hls` or `dash`).', - enum: ['hls', 'dash'], - }, - type: { - type: 'string', - description: 'Adaptive Bitrate Streaming (ABS) setup.', - enum: ['abs'], - }, - value: { - type: 'string', - description: - 'List of different representations you want to create separated by an underscore.\n', - }, - }, - required: ['protocol', 'type', 'value'], - }, - ], - }, - }, - pre: { - type: 'string', - description: - 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', - }, - }, - }, - useUniqueFileName: { - type: 'boolean', - description: - 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', - }, - webhookUrl: { - type: 'string', - description: - 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', - }, - }, - required: ['file', 'fileName'], - $defs: { - extensions: { - type: 'array', - title: 'Extensions Array', - description: - 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Remove background', - properties: { - name: { - type: 'string', - description: 'Specifies the background removal extension.', - enum: ['remove-bg'], - }, - options: { - type: 'object', - properties: { - add_shadow: { - type: 'boolean', - description: - 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', - }, - bg_color: { - type: 'string', - description: - 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', - }, - bg_image_url: { - type: 'string', - description: - 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', - }, - semitransparency: { - type: 'boolean', - description: - 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', - }, - }, - }, - }, - required: ['name'], - }, - { - type: 'object', - title: 'Auto tagging', - properties: { - maxTags: { - type: 'integer', - description: 'Maximum number of tags to attach to the asset.', - }, - minConfidence: { - type: 'integer', - description: 'Minimum confidence level for tags to be considered valid.', - }, - name: { - type: 'string', - description: 'Specifies the auto-tagging extension used.', - enum: ['google-auto-tagging', 'aws-auto-tagging'], - }, - }, - required: ['maxTags', 'minConfidence', 'name'], - }, - { - type: 'object', - title: 'Auto description', - properties: { - name: { - type: 'string', - description: 'Specifies the auto description extension.', - enum: ['ai-auto-description'], - }, - }, - required: ['name'], - }, - ], - }, - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const body = args as any; - try { - return asTextContentResult(await client.beta.v2.files.upload(body)); - } catch (error) { - if (error instanceof ImageKit.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts deleted file mode 100644 index d0a79cdc..00000000 --- a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts +++ /dev/null @@ -1,53 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'cache.invalidation', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/purge', - operationId: 'purge-cache', -}; - -export const tool: Tool = { - name: 'create_cache_invalidation', - 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\nThis API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/invalidation_create_response',\n $defs: {\n invalidation_create_response: {\n type: 'object',\n properties: {\n requestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - url: { - type: 'string', - description: 'The full URL of the file to be purged.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['url'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.create(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts deleted file mode 100644 index 6fca57fe..00000000 --- a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'cache.invalidation', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/purge/{requestId}', - operationId: 'purge-status', -}; - -export const tool: Tool = { - name: 'get_cache_invalidation', - 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\nThis API returns the status of a purge cache request.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/invalidation_get_response',\n $defs: {\n invalidation_get_response: {\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'Status of the purge request.',\n enum: [ 'Pending',\n 'Completed'\n ]\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - requestId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['requestId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { requestId, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts deleted file mode 100644 index 666f950d..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts +++ /dev/null @@ -1,161 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/customMetadataFields', - operationId: 'create-new-field', -}; - -export const tool: Tool = { - name: 'create_custom_metadata_fields', - 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\nThis API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Data type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - label: { - type: 'string', - description: - 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI.', - }, - name: { - type: 'string', - description: - 'API name of the custom metadata field. This should be unique across all (including deleted) custom metadata fields.', - }, - schema: { - type: 'object', - properties: { - type: { - type: 'string', - description: 'Type of the custom metadata field.', - enum: ['Text', 'Textarea', 'Number', 'Date', 'Boolean', 'SingleSelect', 'MultiSelect'], - }, - defaultValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - { - type: 'array', - title: 'Mixed', - description: - 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - ], - description: - 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', - }, - isValueRequired: { - type: 'boolean', - description: - 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', - }, - maxLength: { - type: 'number', - description: - 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - maxValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - minLength: { - type: 'number', - description: - 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - minValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - selectOptions: { - type: 'array', - description: - 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - }, - required: ['type'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['label', 'name', 'schema'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.create(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts deleted file mode 100644 index 75a37e22..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/customMetadataFields/{id}', - operationId: 'delete-a-field', -}; - -export const tool: Tool = { - name: 'delete_custom_metadata_fields', - 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\nThis API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field_delete_response',\n $defs: {\n custom_metadata_field_delete_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(id))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts deleted file mode 100644 index a473365d..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts +++ /dev/null @@ -1,60 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/customMetadataFields', - operationId: 'list-all-fields', -}; - -export const tool: Tool = { - name: 'list_custom_metadata_fields', - 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\nThis API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response.\n\nYou can also filter results by a specific folder path to retrieve custom metadata fields applicable at that location. This path-specific filtering is useful when using the **Path policy** feature to determine which custom metadata fields are selected for a given path.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field_list_response',\n $defs: {\n custom_metadata_field_list_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/custom_metadata_field'\n }\n },\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Data type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderPath: { - type: 'string', - description: - 'The folder path (e.g., `/path/to/folder`) for which to retrieve applicable custom metadata fields. Useful for determining path-specific field selections when the [Path policy](https://imagekit.io/docs/dam/path-policy) feature is in use.\n', - }, - includeDeleted: { - type: 'boolean', - description: 'Set it to `true` to include deleted field objects in the API response.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts deleted file mode 100644 index 7bb0470f..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts +++ /dev/null @@ -1,157 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'write', - tags: [], - httpMethod: 'patch', - httpPath: '/v1/customMetadataFields/{id}', - operationId: 'update-existing-field', -}; - -export const tool: Tool = { - name: 'update_custom_metadata_fields', - 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\nThis API updates the label or schema of an existing custom metadata field.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Data type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - label: { - type: 'string', - description: - 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI. This parameter is required if `schema` is not provided.', - }, - schema: { - type: 'object', - description: - 'An object that describes the rules for the custom metadata key. This parameter is required if `label` is not provided. Note: `type` cannot be updated and will be ignored if sent with the `schema`. The schema will be validated as per the existing `type`.\n', - properties: { - defaultValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - { - type: 'array', - title: 'Mixed', - description: - 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - ], - description: - 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', - }, - isValueRequired: { - type: 'boolean', - description: - 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', - }, - maxLength: { - type: 'number', - description: - 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - maxValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - minLength: { - type: 'number', - description: - 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - minValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - selectOptions: { - type: 'array', - description: - 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.customMetadataFields.update(id, body)), - ); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts deleted file mode 100644 index efd2ad77..00000000 --- a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/addTags', - operationId: 'add-tags-bulk', -}; - -export const tool: Tool = { - name: 'add_tags_files_bulk', - 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\nThis API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_add_tags_response',\n $defs: {\n bulk_add_tags_response: {\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully added.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileIds: { - type: 'array', - description: 'An array of fileIds to which you want to add tags.\n', - items: { - type: 'string', - }, - }, - tags: { - type: 'array', - description: 'An array of tags that you want to add to the files.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileIds', 'tags'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts deleted file mode 100644 index 3ba1f4c7..00000000 --- a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/batch/deleteByFileIds', - operationId: 'delete-multiple-files', -}; - -export const tool: Tool = { - name: 'delete_files_bulk', - 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\nThis API deletes multiple files and all their file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n\nA maximum of 100 files can be deleted at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_delete_response',\n $defs: {\n bulk_delete_response: {\n type: 'object',\n properties: {\n successfullyDeletedFileIds: {\n type: 'array',\n description: 'An array of fileIds that were successfully deleted.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileIds: { - type: 'array', - description: 'An array of fileIds which you want to delete.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileIds'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts deleted file mode 100644 index a2cdf3bf..00000000 --- a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/removeAITags', - operationId: 'remove-ai-tags-bulk', -}; - -export const tool: Tool = { - name: 'remove_ai_tags_files_bulk', - 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\nThis API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_remove_ai_tags_response',\n $defs: {\n bulk_remove_ai_tags_response: {\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which AITags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - AITags: { - type: 'array', - description: 'An array of AITags that you want to remove from the files.\n', - items: { - type: 'string', - }, - }, - fileIds: { - type: 'array', - description: 'An array of fileIds from which you want to remove AITags.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['AITags', 'fileIds'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts deleted file mode 100644 index 62520e20..00000000 --- a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/removeTags', - operationId: 'remove-tags-bulk', -}; - -export const tool: Tool = { - name: 'remove_tags_files_bulk', - 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\nThis API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/bulk_remove_tags_response',\n $defs: {\n bulk_remove_tags_response: {\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileIds: { - type: 'array', - description: 'An array of fileIds from which you want to remove tags.\n', - items: { - type: 'string', - }, - }, - tags: { - type: 'array', - description: 'An array of tags that you want to remove from the files.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileIds', 'tags'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts deleted file mode 100644 index e69f89ee..00000000 --- a/packages/mcp-server/src/tools/files/copy-files.ts +++ /dev/null @@ -1,62 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/copy', - operationId: 'copy-file', -}; - -export const tool: Tool = { - name: 'copy_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\nThis will copy a file from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_copy_response',\n $defs: {\n file_copy_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the folder you want to copy the above file into.\n', - }, - sourceFilePath: { - type: 'string', - description: 'The full path of the file you want to copy.\n', - }, - includeFileVersions: { - type: 'boolean', - description: - 'Option to copy all versions of a file. By default, only the current version of the file is copied. When set to true, all versions of the file will be copied. Default value - `false`.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFilePath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || 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 deleted file mode 100644 index dac67c16..00000000 --- a/packages/mcp-server/src/tools/files/delete-files.ts +++ /dev/null @@ -1,41 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/files/{fileId}', - operationId: 'delete-file', -}; - -export const tool: Tool = { - name: 'delete_files', - description: - 'This API deletes the file and all its file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n', - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - }, - required: ['fileId'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, ...body } = args as any; - const response = await client.files.delete(fileId).asResponse(); - return asTextContentResult(await response.text()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/get-files.ts b/packages/mcp-server/src/tools/files/get-files.ts deleted file mode 100644 index 8606b93c..00000000 --- a/packages/mcp-server/src/tools/files/get-files.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/details', - operationId: 'get-file-details', -}; - -export const tool: Tool = { - name: 'get_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\nThis API returns an object with details or attributes about the current version of the file.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts deleted file mode 100644 index 4bc2a401..00000000 --- a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.metadata', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/metadata', - operationId: 'get-uploaded-file-metadata', -}; - -export const tool: Tool = { - name: 'get_files_metadata', - 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\nYou can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API.\n\nYou can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts deleted file mode 100644 index f9aac486..00000000 --- a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.metadata', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/metadata', - operationId: 'get-metadata-from-url', -}; - -export const tool: Tool = { - name: 'get_from_url_files_metadata', - 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\nGet image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - url: { - type: 'string', - description: 'Should be a valid file URL. It should be accessible using your ImageKit.io account.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['url'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts deleted file mode 100644 index c044a04e..00000000 --- a/packages/mcp-server/src/tools/files/move-files.ts +++ /dev/null @@ -1,57 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/move', - operationId: 'move-file', -}; - -export const tool: Tool = { - name: 'move_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\nThis will move a file and all its versions from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_move_response',\n $defs: {\n file_move_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the folder you want to move the above file into.\n', - }, - sourceFilePath: { - type: 'string', - description: 'The full path of the file you want to move.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFilePath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts deleted file mode 100644 index 046afef5..00000000 --- a/packages/mcp-server/src/tools/files/rename-files.ts +++ /dev/null @@ -1,65 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/files/rename', - operationId: 'rename-file', -}; - -export const tool: Tool = { - name: 'rename_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\nYou can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. \n\nNote: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file_rename_response',\n $defs: {\n file_rename_response: {\n type: 'object',\n properties: {\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - filePath: { - type: 'string', - description: 'The full path of the file you want to rename.\n', - }, - newFileName: { - type: 'string', - description: - 'The new name of the file. A filename can contain:\n\nAlphanumeric Characters: `a-z`, `A-Z`, `0-9` (including Unicode letters, marks, and numerals in other languages).\nSpecial Characters: `.`, `_`, and `-`.\n\nAny other character, including space, will be replaced by `_`.\n', - }, - purgeCache: { - type: 'boolean', - description: - "Option to purge cache for the old file and its versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove cached content of old file and its versions. This purge request is counted against your monthly purge quota.\n\nNote: If the old file were accessible at `https://ik.imagekit.io/demo/old-filename.jpg`, a purge cache request would be issued against `https://ik.imagekit.io/demo/old-filename.jpg*` (with a wildcard at the end). It will remove the file and its versions' URLs and any transformations made using query parameters on this file or its versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\n\n\nDefault value - `false`\n", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['filePath', 'newFileName'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/update-files.ts b/packages/mcp-server/src/tools/files/update-files.ts deleted file mode 100644 index 99218ddf..00000000 --- a/packages/mcp-server/src/tools/files/update-files.ts +++ /dev/null @@ -1,203 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'patch', - httpPath: '/v1/files/{fileId}/details', - operationId: 'update-file-details', -}; - -export const tool: Tool = { - name: 'update_files', - description: - 'This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API.\n', - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - customCoordinates: { - type: 'string', - description: - 'Define an important area in the image in the format `x,y,width,height` e.g. `10,10,100,100`. Send `null` to unset this value.\n', - }, - customMetadata: { - type: 'object', - description: - 'A key-value data to be associated with the asset. To unset a key, send `null` value for that key. Before setting any custom metadata on an asset you have to create the field using custom metadata fields API.\n', - additionalProperties: true, - }, - description: { - type: 'string', - description: 'Optional text to describe the contents of the file.\n', - }, - extensions: { - $ref: '#/$defs/extensions', - }, - removeAITags: { - anyOf: [ - { - type: 'array', - items: { - type: 'string', - }, - }, - { - type: 'string', - enum: ['all'], - }, - ], - description: - 'An array of AITags associated with the file that you want to remove, e.g. `["car", "vehicle", "motorsports"]`. \n\nIf you want to remove all AITags associated with the file, send a string - "all".\n\nNote: The remove operation for `AITags` executes before any of the `extensions` are processed.\n', - }, - tags: { - type: 'array', - description: - 'An array of tags associated with the file, such as `["tag1", "tag2"]`. Send `null` to unset all tags associated with the file.\n', - items: { - type: 'string', - }, - }, - webhookUrl: { - type: 'string', - description: - 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', - }, - }, - required: ['fileId'], - }, - { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - publish: { - type: 'object', - description: 'Configure the publication status of a file and its versions.\n', - properties: { - isPublished: { - type: 'boolean', - description: 'Set to `true` to publish the file. Set to `false` to unpublish the file.\n', - }, - includeFileVersions: { - type: 'boolean', - description: - 'Set to `true` to publish/unpublish all versions of the file. Set to `false` to publish/unpublish only the current version of the file.\n', - }, - }, - required: ['isPublished'], - }, - }, - required: ['fileId'], - }, - ], - $defs: { - extensions: { - type: 'array', - title: 'Extensions Array', - description: - 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Remove background', - properties: { - name: { - type: 'string', - description: 'Specifies the background removal extension.', - enum: ['remove-bg'], - }, - options: { - type: 'object', - properties: { - add_shadow: { - type: 'boolean', - description: - 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', - }, - bg_color: { - type: 'string', - description: - 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', - }, - bg_image_url: { - type: 'string', - description: - 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', - }, - semitransparency: { - type: 'boolean', - description: - 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', - }, - }, - }, - }, - required: ['name'], - }, - { - type: 'object', - title: 'Auto tagging', - properties: { - maxTags: { - type: 'integer', - description: 'Maximum number of tags to attach to the asset.', - }, - minConfidence: { - type: 'integer', - description: 'Minimum confidence level for tags to be considered valid.', - }, - name: { - type: 'string', - description: 'Specifies the auto-tagging extension used.', - enum: ['google-auto-tagging', 'aws-auto-tagging'], - }, - }, - required: ['maxTags', 'minConfidence', 'name'], - }, - { - type: 'object', - title: 'Auto description', - properties: { - name: { - type: 'string', - description: 'Specifies the auto description extension.', - enum: ['ai-auto-description'], - }, - }, - required: ['name'], - }, - ], - }, - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, ...body } = args as any; - try { - return asTextContentResult(await client.files.update(fileId, body)); - } catch (error) { - if (error instanceof ImageKit.APIError) { - 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 deleted file mode 100644 index d49e5a75..00000000 --- a/packages/mcp-server/src/tools/files/upload-files.ts +++ /dev/null @@ -1,339 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/api/v1/files/upload', - operationId: 'upload-file', -}; - -export const tool: Tool = { - name: 'upload_files', - description: - 'ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload.\n\nThe [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', - inputSchema: { - type: 'object', - properties: { - file: { - type: 'string', - description: - 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', - }, - fileName: { - type: 'string', - description: - 'The name with which the file has to be uploaded.\nThe file name can contain:\n\n - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`.\n - Special Characters: `.`, `-`\n\nAny other character including space will be replaced by `_`\n', - }, - token: { - type: 'string', - description: - 'A unique value that the ImageKit.io server will use to recognize and prevent subsequent retries for the same request. We suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions. This field is only required for authentication when uploading a file from the client side.\n\n**Note**: Sending a value that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new value for this field.\n', - }, - checks: { - type: 'string', - description: - 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks).\n', - }, - customCoordinates: { - type: 'string', - description: - 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', - }, - customMetadata: { - type: 'object', - description: - 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', - additionalProperties: true, - }, - description: { - type: 'string', - description: 'Optional text to describe the contents of the file.\n', - }, - expire: { - type: 'integer', - description: - 'The time until your signature is valid. It must be a [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into the future. It should be in seconds. This field is only required for authentication when uploading a file from the client side.\n', - }, - extensions: { - $ref: '#/$defs/extensions', - }, - folder: { - type: 'string', - description: - "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created.\n\nThe folder name can contain:\n\n - Alphanumeric Characters: `a-z` , `A-Z` , `0-9`\n - Special Characters: `/` , `_` , `-`\n\nUsing multiple `/` creates a nested folder.\n", - }, - isPrivateFile: { - type: 'boolean', - description: - 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', - }, - isPublished: { - type: 'boolean', - description: - 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', - }, - overwriteAITags: { - type: 'boolean', - description: - 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', - }, - overwriteCustomMetadata: { - type: 'boolean', - description: - 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', - }, - overwriteFile: { - type: 'boolean', - description: - 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', - }, - overwriteTags: { - type: 'boolean', - description: - 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', - }, - publicKey: { - type: 'string', - description: - 'Your ImageKit.io public key. This field is only required for authentication when uploading a file from the client side.\n', - }, - responseFields: { - type: 'array', - description: 'Array of response field keys to include in the API response body.\n', - items: { - type: 'string', - enum: [ - 'tags', - 'customCoordinates', - 'isPrivateFile', - 'embeddedMetadata', - 'isPublished', - 'customMetadata', - 'metadata', - 'selectedFieldsSchema', - ], - }, - }, - signature: { - type: 'string', - description: - 'HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a key. Learn how to create a signature on the page below. This should be in lowercase.\n\nSignature must be calculated on the server-side. This field is only required for authentication when uploading a file from the client side.\n', - }, - tags: { - type: 'array', - description: - 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', - items: { - type: 'string', - }, - }, - transformation: { - type: 'object', - description: - "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", - properties: { - post: { - type: 'array', - description: - 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Simple post-transformation', - properties: { - type: { - type: 'string', - description: 'Transformation type.', - enum: ['transformation'], - }, - value: { - type: 'string', - description: - 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', - }, - }, - required: ['type', 'value'], - }, - { - type: 'object', - title: 'Convert GIF to video', - properties: { - type: { - type: 'string', - description: 'Converts an animated GIF into an MP4.', - enum: ['gif-to-video'], - }, - value: { - type: 'string', - description: - 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Generate a thumbnail', - properties: { - type: { - type: 'string', - description: 'Generates a thumbnail image.', - enum: ['thumbnail'], - }, - value: { - type: 'string', - description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Adaptive Bitrate Streaming', - properties: { - protocol: { - type: 'string', - description: 'Streaming protocol to use (`hls` or `dash`).', - enum: ['hls', 'dash'], - }, - type: { - type: 'string', - description: 'Adaptive Bitrate Streaming (ABS) setup.', - enum: ['abs'], - }, - value: { - type: 'string', - description: - 'List of different representations you want to create separated by an underscore.\n', - }, - }, - required: ['protocol', 'type', 'value'], - }, - ], - }, - }, - pre: { - type: 'string', - description: - 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', - }, - }, - }, - useUniqueFileName: { - type: 'boolean', - description: - 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', - }, - webhookUrl: { - type: 'string', - description: - 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', - }, - }, - required: ['file', 'fileName'], - $defs: { - extensions: { - type: 'array', - title: 'Extensions Array', - description: - 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Remove background', - properties: { - name: { - type: 'string', - description: 'Specifies the background removal extension.', - enum: ['remove-bg'], - }, - options: { - type: 'object', - properties: { - add_shadow: { - type: 'boolean', - description: - 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', - }, - bg_color: { - type: 'string', - description: - 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', - }, - bg_image_url: { - type: 'string', - description: - 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', - }, - semitransparency: { - type: 'boolean', - description: - 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', - }, - }, - }, - }, - required: ['name'], - }, - { - type: 'object', - title: 'Auto tagging', - properties: { - maxTags: { - type: 'integer', - description: 'Maximum number of tags to attach to the asset.', - }, - minConfidence: { - type: 'integer', - description: 'Minimum confidence level for tags to be considered valid.', - }, - name: { - type: 'string', - description: 'Specifies the auto-tagging extension used.', - enum: ['google-auto-tagging', 'aws-auto-tagging'], - }, - }, - required: ['maxTags', 'minConfidence', 'name'], - }, - { - type: 'object', - title: 'Auto description', - properties: { - name: { - type: 'string', - description: 'Specifies the auto description extension.', - enum: ['ai-auto-description'], - }, - }, - required: ['name'], - }, - ], - }, - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const body = args as any; - try { - return asTextContentResult(await client.files.upload(body)); - } catch (error) { - if (error instanceof ImageKit.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts deleted file mode 100644 index 6903001d..00000000 --- a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/files/{fileId}/versions/{versionId}', - operationId: 'delete-file-version', -}; - -export const tool: Tool = { - name: 'delete_files_versions', - 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\nThis API deletes a non-current file version permanently. The API returns an empty response.\n\nNote: If you want to delete all versions of a file, use the delete file API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/version_delete_response',\n $defs: {\n version_delete_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - versionId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId', 'versionId'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { versionId, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.delete(versionId, body)), - ); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts deleted file mode 100644 index dfe562ab..00000000 --- a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/versions/{versionId}', - operationId: 'get-file-version-details', -}; - -export const tool: Tool = { - name: 'get_files_versions', - 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\nThis API returns an object with details or attributes of a file version.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - versionId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId', 'versionId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { versionId, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.get(versionId, body)), - ); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts deleted file mode 100644 index eb6b82c9..00000000 --- a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/versions', - operationId: 'list-file-versions', -}; - -export const tool: Tool = { - name: 'list_files_versions', - 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\nThis API returns details of all versions of a file.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/version_list_response',\n $defs: {\n version_list_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n }\n },\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts deleted file mode 100644 index dc4f76c4..00000000 --- a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/files/{fileId}/versions/{versionId}/restore', - operationId: 'restore-file-version', -}; - -export const tool: Tool = { - name: 'restore_files_versions', - 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\nThis API restores a file version as the current file version.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n selectedFieldsSchema: {\n type: 'object',\n description: 'This field is included in the response only if the Path policy feature is available in the plan.\\nIt contains schema definitions for the custom metadata fields selected for the specified file path.\\nField selection can only be done when the Path policy feature is enabled.\\n\\nKeys are the names of the custom metadata fields; the value object has details about the custom metadata schema.\\n',\n additionalProperties: true\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - versionId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId', 'versionId'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { versionId, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.restore(versionId, body)), - ); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts deleted file mode 100644 index f88e720a..00000000 --- a/packages/mcp-server/src/tools/folders/copy-folders.ts +++ /dev/null @@ -1,62 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/bulkJobs/copyFolder', - operationId: 'copy-folder', -}; - -export const tool: Tool = { - name: 'copy_folders', - 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\nThis will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_copy_response',\n $defs: {\n folder_copy_response: {\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the destination folder where you want to copy the source folder into.\n', - }, - sourceFolderPath: { - type: 'string', - description: 'The full path to the source folder you want to copy.\n', - }, - includeVersions: { - type: 'boolean', - description: - 'Option to copy all versions of files that are nested inside the selected folder. By default, only the current version of each file will be copied. When set to true, all versions of each file will be copied. Default value - `false`.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFolderPath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts deleted file mode 100644 index c8fa02c1..00000000 --- a/packages/mcp-server/src/tools/folders/create-folders.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/folder', - operationId: 'create-folder', -}; - -export const tool: Tool = { - name: 'create_folders', - 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\nThis will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_create_response',\n $defs: {\n folder_create_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderName: { - type: 'string', - description: - 'The folder will be created with this name. \n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) will be replaced by an underscore i.e. `_`.\n', - }, - parentFolderPath: { - type: 'string', - description: - "The folder where the new folder should be created, for root use `/` else the path e.g. `containing/folder/`.\n\nNote: If any folder(s) is not present in the parentFolderPath parameter, it will be automatically created. For example, if you pass `/product/images/summer`, then `product`, `images`, and `summer` folders will be created if they don't already exist.\n", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['folderName', 'parentFolderPath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.create(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts deleted file mode 100644 index 231947a3..00000000 --- a/packages/mcp-server/src/tools/folders/delete-folders.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/folder', - operationId: 'delete-folder', -}; - -export const tool: Tool = { - name: 'delete_folders', - 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\nThis will delete a folder and all its contents permanently. The API returns an empty response.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_delete_response',\n $defs: {\n folder_delete_response: {\n type: 'object',\n properties: {}\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderPath: { - type: 'string', - description: 'Full path to the folder you want to delete. For example `/folder/to/delete/`.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['folderPath'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts deleted file mode 100644 index b3a149ab..00000000 --- a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders.job', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/bulkJobs/{jobId}', - operationId: 'bulk-job-status', -}; - -export const tool: Tool = { - name: 'get_folders_job', - 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\nThis API returns the status of a bulk job like copy and move folder operations.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_get_response',\n $defs: {\n job_get_response: {\n type: 'object',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job.\\n'\n },\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This will be present only if `purgeCache` is set to `true` in the rename folder API request.\\n'\n },\n status: {\n type: 'string',\n description: 'Status of the bulk job.',\n enum: [ 'Pending',\n 'Completed'\n ]\n },\n type: {\n type: 'string',\n description: 'Type of the bulk job.',\n enum: [ 'COPY_FOLDER',\n 'MOVE_FOLDER',\n 'RENAME_FOLDER'\n ]\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jobId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['jobId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jobId, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts deleted file mode 100644 index 5589db62..00000000 --- a/packages/mcp-server/src/tools/folders/move-folders.ts +++ /dev/null @@ -1,57 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/bulkJobs/moveFolder', - operationId: 'move-folder', -}; - -export const tool: Tool = { - name: 'move_folders', - 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\nThis will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_move_response',\n $defs: {\n folder_move_response: {\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the destination folder where you want to move the source folder into.\n', - }, - sourceFolderPath: { - type: 'string', - description: 'The full path to the source folder you want to move.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFolderPath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts deleted file mode 100644 index 60c7c121..00000000 --- a/packages/mcp-server/src/tools/folders/rename-folders.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@imagekit/api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@imagekit/api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/bulkJobs/renameFolder', - operationId: 'rename-folder', -}; - -export const tool: Tool = { - name: 'rename_folders', - 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\nThis API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/folder_rename_response',\n $defs: {\n folder_rename_response: {\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderPath: { - type: 'string', - description: 'The full path to the folder you want to rename.\n', - }, - newFolderName: { - type: 'string', - description: - 'The new name for the folder.\n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) and `-` will be replaced by an underscore i.e. `_`.\n', - }, - purgeCache: { - type: 'boolean', - description: - "Option to purge cache for the old nested files and their versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove the cached content of the old nested files and their versions. There will only be one purge request for all the nested files, which will be counted against your monthly purge quota.\n\nNote: A purge cache request will be issued against `https://ik.imagekit.io/old/folder/path*` (with a wildcard at the end). This will remove all nested files, their versions' URLs, and any transformations made using query parameters on these files or their versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\nDefault value - `false`\n", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['folderPath', 'newFolderName'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(body))); - } catch (error) { - if (error instanceof ImageKit.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts deleted file mode 100644 index ba7083d7..00000000 --- a/packages/mcp-server/src/tools/index.ts +++ /dev/null @@ -1,153 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, Endpoint, HandlerFunction } from './types'; - -export { Metadata, Endpoint, HandlerFunction }; - -import create_custom_metadata_fields from './custom-metadata-fields/create-custom-metadata-fields'; -import update_custom_metadata_fields from './custom-metadata-fields/update-custom-metadata-fields'; -import list_custom_metadata_fields from './custom-metadata-fields/list-custom-metadata-fields'; -import delete_custom_metadata_fields from './custom-metadata-fields/delete-custom-metadata-fields'; -import update_files from './files/update-files'; -import delete_files from './files/delete-files'; -import copy_files from './files/copy-files'; -import get_files from './files/get-files'; -import move_files from './files/move-files'; -import rename_files from './files/rename-files'; -import upload_files from './files/upload-files'; -import delete_files_bulk from './files/bulk/delete-files-bulk'; -import add_tags_files_bulk from './files/bulk/add-tags-files-bulk'; -import remove_ai_tags_files_bulk from './files/bulk/remove-ai-tags-files-bulk'; -import remove_tags_files_bulk from './files/bulk/remove-tags-files-bulk'; -import list_files_versions from './files/versions/list-files-versions'; -import delete_files_versions from './files/versions/delete-files-versions'; -import get_files_versions from './files/versions/get-files-versions'; -import restore_files_versions from './files/versions/restore-files-versions'; -import get_files_metadata from './files/metadata/get-files-metadata'; -import get_from_url_files_metadata from './files/metadata/get-from-url-files-metadata'; -import list_assets from './assets/list-assets'; -import create_cache_invalidation from './cache/invalidation/create-cache-invalidation'; -import get_cache_invalidation from './cache/invalidation/get-cache-invalidation'; -import create_folders from './folders/create-folders'; -import delete_folders from './folders/delete-folders'; -import copy_folders from './folders/copy-folders'; -import move_folders from './folders/move-folders'; -import rename_folders from './folders/rename-folders'; -import get_folders_job from './folders/job/get-folders-job'; -import get_accounts_usage from './accounts/usage/get-accounts-usage'; -import create_accounts_origins from './accounts/origins/create-accounts-origins'; -import update_accounts_origins from './accounts/origins/update-accounts-origins'; -import list_accounts_origins from './accounts/origins/list-accounts-origins'; -import delete_accounts_origins from './accounts/origins/delete-accounts-origins'; -import get_accounts_origins from './accounts/origins/get-accounts-origins'; -import create_accounts_url_endpoints from './accounts/url-endpoints/create-accounts-url-endpoints'; -import update_accounts_url_endpoints from './accounts/url-endpoints/update-accounts-url-endpoints'; -import list_accounts_url_endpoints from './accounts/url-endpoints/list-accounts-url-endpoints'; -import delete_accounts_url_endpoints from './accounts/url-endpoints/delete-accounts-url-endpoints'; -import get_accounts_url_endpoints from './accounts/url-endpoints/get-accounts-url-endpoints'; -import upload_v2_beta_files from './beta/v2/files/upload-v2-beta-files'; - -export const endpoints: Endpoint[] = []; - -function addEndpoint(endpoint: Endpoint) { - endpoints.push(endpoint); -} - -addEndpoint(create_custom_metadata_fields); -addEndpoint(update_custom_metadata_fields); -addEndpoint(list_custom_metadata_fields); -addEndpoint(delete_custom_metadata_fields); -addEndpoint(update_files); -addEndpoint(delete_files); -addEndpoint(copy_files); -addEndpoint(get_files); -addEndpoint(move_files); -addEndpoint(rename_files); -addEndpoint(upload_files); -addEndpoint(delete_files_bulk); -addEndpoint(add_tags_files_bulk); -addEndpoint(remove_ai_tags_files_bulk); -addEndpoint(remove_tags_files_bulk); -addEndpoint(list_files_versions); -addEndpoint(delete_files_versions); -addEndpoint(get_files_versions); -addEndpoint(restore_files_versions); -addEndpoint(get_files_metadata); -addEndpoint(get_from_url_files_metadata); -addEndpoint(list_assets); -addEndpoint(create_cache_invalidation); -addEndpoint(get_cache_invalidation); -addEndpoint(create_folders); -addEndpoint(delete_folders); -addEndpoint(copy_folders); -addEndpoint(move_folders); -addEndpoint(rename_folders); -addEndpoint(get_folders_job); -addEndpoint(get_accounts_usage); -addEndpoint(create_accounts_origins); -addEndpoint(update_accounts_origins); -addEndpoint(list_accounts_origins); -addEndpoint(delete_accounts_origins); -addEndpoint(get_accounts_origins); -addEndpoint(create_accounts_url_endpoints); -addEndpoint(update_accounts_url_endpoints); -addEndpoint(list_accounts_url_endpoints); -addEndpoint(delete_accounts_url_endpoints); -addEndpoint(get_accounts_url_endpoints); -addEndpoint(upload_v2_beta_files); - -export type Filter = { - type: 'resource' | 'operation' | 'tag' | 'tool'; - op: 'include' | 'exclude'; - value: string; -}; - -export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { - const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); - const unmatchedFilters = new Set(filters); - - const filtered = endpoints.filter((endpoint: Endpoint) => { - let included = false || allExcludes; - - for (const filter of filters) { - if (match(filter, endpoint)) { - unmatchedFilters.delete(filter); - included = filter.op === 'include'; - } - } - - return included; - }); - - // Check if any filters didn't match - const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); - if (unmatched.length > 0) { - throw new Error( - `The following filters did not match any endpoints: ${unmatched - .map((f) => `${f.type}=${f.value}`) - .join(', ')}`, - ); - } - - return filtered; -} - -function match({ type, value }: Filter, endpoint: Endpoint): boolean { - switch (type) { - case 'resource': { - const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; - const regex = new RegExp(regexStr); - return regex.test(normalizeResource(endpoint.metadata.resource)); - } - case 'operation': - return endpoint.metadata.operation === value; - case 'tag': - return endpoint.metadata.tags.includes(value); - case 'tool': - return endpoint.tool.name === value; - } -} - -function normalizeResource(resource: string): string { - return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); -} diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/types.ts similarity index 98% rename from packages/mcp-server/src/tools/types.ts rename to packages/mcp-server/src/types.ts index 715d3422..6b307bec 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/types.ts @@ -108,7 +108,7 @@ export type Metadata = { operationId?: string; }; -export type Endpoint = { +export type McpTool = { metadata: Metadata; tool: Tool; handler: HandlerFunction; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts deleted file mode 100644 index d6272f6c..00000000 --- a/packages/mcp-server/tests/compat.test.ts +++ /dev/null @@ -1,1166 +0,0 @@ -import { - truncateToolNames, - removeTopLevelUnions, - removeAnyOf, - inlineRefs, - applyCompatibilityTransformations, - removeFormats, - findUsedDefs, -} from '../src/compat'; -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { JSONSchema } from '../src/compat'; -import { Endpoint } from '../src/tools'; - -describe('truncateToolNames', () => { - it('should return original names when maxLength is 0 or negative', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 0)).toEqual(new Map()); - expect(truncateToolNames(names, -1)).toEqual(new Map()); - }); - - it('should return original names when all names are shorter than maxLength', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 10)).toEqual(new Map()); - }); - - it('should truncate names longer than maxLength', () => { - const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; - expect(truncateToolNames(names, 10)).toEqual( - new Map([ - ['very-long-tool-name', 'very-long-'], - ['another-long-tool-name', 'another-lo'], - ]), - ); - }); - - it('should handle duplicate truncated names by appending numbers', () => { - const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; - expect(truncateToolNames(names, 8)).toEqual( - new Map([ - ['tool-name-a', 'tool-na1'], - ['tool-name-b', 'tool-na2'], - ['tool-name-c', 'tool-na3'], - ]), - ); - }); -}); - -describe('removeTopLevelUnions', () => { - const createTestTool = (overrides = {}): Tool => ({ - name: 'test-tool', - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - it('should return the original tool if it has no anyOf at the top level', () => { - const tool = createTestTool({ - inputSchema: { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }, - }); - - expect(removeTopLevelUnions(tool)).toEqual([tool]); - }); - - it('should split a tool with top-level anyOf into multiple tools', () => { - const tool = createTestTool({ - name: 'union-tool', - description: 'A tool with unions', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - description: 'Its the first variant', - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'union-tool_first_variant', - description: 'Its the first variant', - inputSchema: { - type: 'object', - title: 'first variant', - description: 'Its the first variant', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - }, - { - name: 'union-tool_second_variant', - description: 'A tool with unions', - inputSchema: { - type: 'object', - title: 'second variant', - description: 'A tool with unions', - properties: { - common: { type: 'string' }, - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - }, - ]); - }); - - it('should handle $defs and only include those used by the variant', () => { - const tool = createTestTool({ - name: 'defs-tool', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - def2: { type: 'number', minimum: 0 }, - unused: { type: 'boolean' }, - }, - anyOf: [ - { - properties: { - email: { $ref: '#/$defs/def1' }, - }, - }, - { - properties: { - count: { $ref: '#/$defs/def2' }, - }, - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'defs-tool_variant1', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - email: { $ref: '#/$defs/def1' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - }, - }, - }, - { - name: 'defs-tool_variant2', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - count: { $ref: '#/$defs/def2' }, - }, - $defs: { - def2: { type: 'number', minimum: 0 }, - }, - }, - }, - ]); - }); -}); - -describe('removeAnyOf', () => { - it('should return original schema if it has no anyOf', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'number' }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(schema); - }); - - it('should remove anyOf field and use the first variant', () => { - const schema = { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }; - - const expected = { - type: 'object', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should recursively remove anyOf fields from nested properties', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - }, - anyOf: [ - { - properties: { - option1: { type: 'boolean' }, - }, - }, - { - properties: { - option2: { type: 'array' }, - }, - }, - ], - }, - }, - }; - - const expected = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - option1: { type: 'boolean' }, - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should handle arrays', () => { - const schema = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); -}); - -describe('findUsedDefs', () => { - it('should handle circular references without stack overflow', () => { - const defs = { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/person' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('person'); - }).not.toThrow(); - }); - - it('should handle indirect circular references without stack overflow', () => { - const defs = { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Indirect circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - root: { $ref: '#/$defs/node' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('node'); - expect(result).toHaveProperty('childNode'); - }).not.toThrow(); - }); - - it('should find all used definitions in non-circular schemas', () => { - const defs = { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - address: { $ref: '#/$defs/address' }, - }, - }, - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - }, - unused: { - type: 'object', - properties: { - data: { type: 'string' }, - }, - }, - }; - - const schema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/user' }, - }, - }; - - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('user'); - expect(result).toHaveProperty('address'); - expect(result).not.toHaveProperty('unused'); - }); -}); - -describe('inlineRefs', () => { - it('should return the original schema if it does not contain $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - }, - }; - - expect(inlineRefs(schema)).toEqual(schema); - }); - - it('should inline simple $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should inline nested $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - order: { $ref: '#/$defs/order' }, - }, - $defs: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { type: 'array', items: { $ref: '#/$defs/item' } }, - }, - }, - item: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { - type: 'array', - items: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle circular references by removing the circular part', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/person' }, - }, - $defs: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - // friend property is removed to break the circular reference - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle indirect circular references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - node: { $ref: '#/$defs/node' }, - }, - $defs: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Circular reference through childNode - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { - type: 'object', - properties: { - value: { type: 'string' }, - // parent property is removed to break the circular reference - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should preserve other properties when inlining references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - address: { $ref: '#/$defs/address', description: 'User address' }, - }, - $defs: { - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - address: { - type: 'object', - description: 'User address', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); -}); - -describe('removeFormats', () => { - it('should return original schema if formats capability is true', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - expect(removeFormats(schema, true)).toEqual(schema); - }); - - it('should move format to description when formats capability is false', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - email: { type: 'string', description: 'An email field (format: "email")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle properties without description', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', format: 'date' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: '(format: "date")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle nested properties', () => { - const schema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle arrays of objects', () => { - const schema = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date', format: 'date' }, - end: { type: 'string', description: 'End date', format: 'date' }, - }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date (format: "date")' }, - end: { type: 'string', description: 'End date (format: "date")' }, - }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle schemas with $defs', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field', - format: 'date-time', - }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field (format: "date-time")', - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); -}); - -describe('applyCompatibilityTransformations', () => { - const createTestTool = (name: string, overrides = {}): Tool => ({ - name, - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - const createTestEndpoint = (tool: Tool): Endpoint => ({ - tool, - handler: jest.fn(), - metadata: { - resource: 'test', - operation: 'read' as const, - tags: [], - }, - }); - - it('should not modify endpoints when all capabilities are enabled', () => { - const tool = createTestTool('test-tool'); - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed).toEqual(endpoints); - }); - - it('should split tools with top-level unions when topLevelUnions is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); - expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); - }); - - it('should handle variants without titles in removeTopLevelUnions', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - }, - { - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); - expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); - }); - - it('should truncate tool names when toolNameLength is set', () => { - const tools = [ - createTestTool('very-long-tool-name-that-exceeds-limit'), - createTestTool('another-long-tool-name-to-truncate'), - createTestTool('short-name'), - ]; - - const endpoints = tools.map(createTestEndpoint); - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); - expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); - expect(transformed[2]!.tool.name).toBe('short-name'); - }); - - it('should inline refs when refs capability is disabled', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - expect(schema.$defs).toBeUndefined(); - - if (schema.properties) { - expect(schema.properties['user']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }); - } - }); - - it('should preserve external refs when inlining', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - internal: { $ref: '#/$defs/internal' }, - external: { $ref: 'https://example.com/schemas/external.json' }, - }, - $defs: { - internal: { - type: 'object', - properties: { - name: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties) { - expect(schema.properties['internal']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - }, - }); - expect(schema.properties['external']).toEqual({ - $ref: 'https://example.com/schemas/external.json', - }); - } - }); - - it('should remove anyOf fields when unions capability is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - field: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['field']) { - const field = schema.properties['field']; - expect(field.anyOf).toBeUndefined(); - expect(field.type).toBe('string'); - } - }); - - it('should correctly combine topLevelUnions and toolNameLength transformations', () => { - const tool = createTestTool('very-long-union-tool-name', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - - // Both names should be truncated because they exceed 20 characters - expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); - expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); - }); - - it('should correctly combine refs and unions transformations', () => { - const tool = createTestTool('complex-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - preference: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - // Refs should be inlined - expect(schema.$defs).toBeUndefined(); - - // Safely access nested properties - if (schema.properties && schema.properties['user']) { - const user = schema.properties['user']; - // User should be inlined - expect(user.type).toBe('object'); - - // AnyOf in the inlined user.preference should be removed - if (user.properties && user.properties['preference']) { - const preference = user.properties['preference']; - expect(preference.anyOf).toBeUndefined(); - expect(preference.type).toBe('string'); - } - } - }); - - it('should handle formats capability being false', () => { - const tool = createTestTool('format-tool', { - inputSchema: { - type: 'object', - properties: { - date: { type: 'string', description: 'A date', format: 'date' }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: false, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['date']) { - const dateField = schema.properties['date']; - expect(dateField['format']).toBeUndefined(); - expect(dateField['description']).toBe('A date (format: "date")'); - } - }); -}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts deleted file mode 100644 index 08963af8..00000000 --- a/packages/mcp-server/tests/dynamic-tools.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { dynamicTools } from '../src/dynamic-tools'; -import { Endpoint } from '../src/tools'; - -describe('dynamicTools', () => { - const fakeClient = {} as any; - - const endpoints: Endpoint[] = [ - makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), - makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), - makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), - makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), - ]; - - const tools = dynamicTools(endpoints); - - const toolsMap = { - list_api_endpoints: toolOrError('list_api_endpoints'), - get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), - invoke_api_endpoint: toolOrError('invoke_api_endpoint'), - }; - - describe('list_api_endpoints', () => { - it('should return all endpoints when no search query is provided', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(endpoints.length); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); - }); - - it('should filter endpoints by name', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - - it('should filter endpoints by resource', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); - }); - - it('should filter endpoints by tag', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); - }); - - it('should be case insensitive in search', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.length).toBe(2); - result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { - expect( - tool.name.toLowerCase().includes('admin') || - tool.resource.toLowerCase().includes('admin') || - tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), - ).toBeTruthy(); - }); - }); - - it('should filter endpoints by description', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'Test endpoint for user_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); - }); - - it('should filter endpoints by partial description match', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'endpoint for user', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - }); - - describe('get_api_endpoint_schema', () => { - it('should return schema for existing endpoint', async () => { - const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { - endpoint: 'test_read_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result).toEqual(endpoints[0]?.tool); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), - ).rejects.toThrow('Endpoint non_existent_endpoint not found'); - }); - - it('should throw error when no endpoint provided', async () => { - await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - }); - - describe('invoke_api_endpoint', () => { - it('should successfully invoke endpoint with valid arguments', async () => { - const mockHandler = endpoints[0]?.handler as jest.Mock; - mockHandler.mockClear(); - - await toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { testParam: 'test value' }, - }); - - expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'non_existent_endpoint', - args: { testParam: 'test value' }, - }), - ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); - }); - - it('should throw error when no arguments provided', async () => { - await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - - it('should throw error for invalid argument schema', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { wrongParam: 'test value' }, // Missing required testParam - }), - ).rejects.toThrow(/Invalid arguments for endpoint/); - }); - }); - - function toolOrError(name: string) { - const tool = tools.find((tool) => tool.tool.name === name); - if (!tool) throw new Error(`Tool ${name} not found`); - return tool; - } -}); - -function makeEndpoint( - name: string, - resource: string, - operation: 'read' | 'write', - tags: string[] = [], -): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { - name, - description: `Test endpoint for ${name}`, - inputSchema: { - type: 'object', - properties: { - testParam: { type: 'string' }, - }, - required: ['testParam'], - }, - }, - handler: jest.fn().mockResolvedValue({ success: true }), - }; -} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index 4d9b60ca..532666a8 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -1,6 +1,4 @@ import { parseCLIOptions, parseQueryOptions } from '../src/options'; -import { Filter } from '../src/tools'; -import { parseEmbeddedJSON } from '../src/compat'; // Mock process.argv const mockArgv = (args: string[]) => { @@ -12,338 +10,35 @@ const mockArgv = (args: string[]) => { }; describe('parseCLIOptions', () => { - it('should parse basic filter options', () => { - const cleanup = mockArgv([ - '--tool=test-tool', - '--resource=test-resource', - '--operation=read', - '--tag=test-tag', - ]); + it('default parsing should be stdio', () => { + const cleanup = mockArgv([]); const result = parseCLIOptions(); - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - { type: 'operation', op: 'include', value: 'read' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - expect(result.list).toBe(false); + expect(result.transport).toBe('stdio'); cleanup(); }); - it('should parse exclusion filters', () => { - const cleanup = mockArgv([ - '--no-tool=exclude-tool', - '--no-resource=exclude-resource', - '--no-operation=write', - '--no-tag=exclude-tag', - ]); + it('using http transport with a port', () => { + const cleanup = mockArgv(['--transport=http', '--port=2222']); const result = parseCLIOptions(); - expect(result.filters).toEqual([ - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - cleanup(); - }); - - it('should parse client presets', () => { - const cleanup = mockArgv(['--client=openai-agents']); - - const result = parseCLIOptions(); - - expect(result.client).toEqual('openai-agents'); - - cleanup(); - }); - - it('should parse individual capabilities', () => { - const cleanup = mockArgv([ - '--capability=top-level-unions', - '--capability=valid-json', - '--capability=refs', - '--capability=unions', - '--capability=tool-name-length=40', - ]); - - const result = parseCLIOptions(); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - toolNameLength: 40, - }); - - cleanup(); - }); - - it('should handle list option', () => { - const cleanup = mockArgv(['--list']); - - const result = parseCLIOptions(); - - expect(result.list).toBe(true); - - cleanup(); - }); - - it('should handle multiple filters of the same type', () => { - const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - cleanup(); - }); - - it('should handle comma-separated values in array options', () => { - const cleanup = mockArgv([ - '--tool=tool1,tool2', - '--resource=res1,res2', - '--capability=top-level-unions,valid-json,unions', - ]); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - unions: true, - }); - - cleanup(); - }); - - it('should handle invalid tool-name-length format', () => { - const cleanup = mockArgv(['--capability=tool-name-length=invalid']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; - cleanup(); - }); - - it('should handle unknown capability', () => { - const cleanup = mockArgv(['--capability=unknown-capability']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; + expect(result.transport).toBe('http'); + expect(result.port).toBe('2222'); cleanup(); }); }); describe('parseQueryOptions', () => { - const defaultOptions = { - client: undefined, - includeDynamicTools: undefined, - includeCodeTools: undefined, - includeAllTools: undefined, - filters: [], - capabilities: { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - }; - - it('should parse basic filter options from query string', () => { - const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - ]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }); - }); - - it('should parse exclusion filters from query string', () => { - const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'operation', op: 'exclude', value: 'write' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - it('should parse client option from query string', () => { - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should parse client capabilities from query string', () => { - const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 40, - }); - }); - - it('should parse no-capability options from query string', () => { - const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: false, - validJson: true, - refs: false, - unions: true, - formats: false, - toolNameLength: undefined, - }); - }); - - it('should parse tools options from query string', () => { - const query = 'tools=dynamic&tools=all'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(true); - expect(result.includeAllTools).toBe(true); - }); - - it('should parse no-tools options from query string', () => { - const query = 'tools=dynamic&tools=all&no_tools=dynamic'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(false); - expect(result.includeAllTools).toBe(true); - }); - - it('should handle array values in query string', () => { - const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ]); - }); - - it('should merge with default options', () => { - const defaultWithFilters = { - ...defaultOptions, - filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], - client: 'cursor' as const, - includeDynamicTools: true, - }; - - const query = 'tool=new-tool&resource=new-resource'; - const result = parseQueryOptions(defaultWithFilters, query); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'existing-tag' }, - { type: 'resource', op: 'include', value: 'new-resource' }, - { type: 'tool', op: 'include', value: 'new-tool' }, - ]); - - expect(result.client).toBe('cursor'); - expect(result.includeDynamicTools).toBe(true); - }); - - it('should override client from default options', () => { - const defaultWithClient = { - ...defaultOptions, - client: 'cursor' as const, - }; + const defaultOptions = {}; - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultWithClient, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should merge capabilities with default options', () => { - const defaultWithCapabilities = { - ...defaultOptions, - capabilities: { - topLevelUnions: false, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: 30, - }, - }; - - const query = 'capability=top-level-unions&no_capability=refs'; - const result = parseQueryOptions(defaultWithCapabilities, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: false, - refs: false, - unions: true, - formats: true, - toolNameLength: 30, - }); - }); - - it('should handle empty query string', () => { + it('default parsing should be empty', () => { const query = ''; const result = parseQueryOptions(defaultOptions, query); - expect(result).toEqual(defaultOptions); + expect(result).toBe({}); }); it('should handle invalid query string gracefully', () => { @@ -352,189 +47,4 @@ describe('parseQueryOptions', () => { // Should throw due to Zod validation for invalid operation expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); }); - - it('should preserve default undefined values when not specified', () => { - const defaultWithUndefined = { - ...defaultOptions, - client: undefined, - includeDynamicTools: undefined, - includeAllTools: undefined, - }; - - const query = 'tool=test-tool'; - const result = parseQueryOptions(defaultWithUndefined, query); - - expect(result.client).toBeUndefined(); - expect(result.includeDynamicTools).toBeFalsy(); - expect(result.includeAllTools).toBeFalsy(); - }); - - it('should handle complex query with mixed include and exclude filters', () => { - const query = - 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'include-res' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'include-tag' }, - { type: 'tool', op: 'include', value: 'include-tool' }, - { type: 'resource', op: 'exclude', value: 'exclude-res' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - 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', () => { - it('should not change non-string values', () => { - const args = { - numberProp: 42, - booleanProp: true, - objectProp: { nested: 'value' }, - arrayProp: [1, 2, 3], - nullProp: null, - undefinedProp: undefined, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberProp']).toBe(42); - expect(result['booleanProp']).toBe(true); - expect(result['objectProp']).toEqual({ nested: 'value' }); - expect(result['arrayProp']).toEqual([1, 2, 3]); - expect(result['nullProp']).toBe(null); - expect(result['undefinedProp']).toBe(undefined); - }); - - it('should parse valid JSON objects in string properties', () => { - const args = { - jsonObjectString: '{"key": "value", "number": 123}', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since changes were made - expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); - expect(result['regularString']).toBe('not json'); - }); - - it('should leave invalid JSON in string properties unchanged', () => { - const args = { - invalidJson1: '{"key": value}', // Missing quotes around value - invalidJson2: '{key: "value"}', // Missing quotes around key - invalidJson3: '{"key": "value",}', // Trailing comma - invalidJson4: 'just a regular string', - emptyString: '', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['invalidJson1']).toBe('{"key": value}'); - expect(result['invalidJson2']).toBe('{key: "value"}'); - expect(result['invalidJson3']).toBe('{"key": "value",}'); - expect(result['invalidJson4']).toBe('just a regular string'); - expect(result['emptyString']).toBe(''); - }); - - it('should not parse JSON primitives in string properties', () => { - const args = { - numberString: '123', - floatString: '45.67', - negativeNumberString: '-89', - booleanTrueString: 'true', - booleanFalseString: 'false', - nullString: 'null', - jsonArrayString: '[1, 2, 3, "test"]', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberString']).toBe('123'); - expect(result['floatString']).toBe('45.67'); - expect(result['negativeNumberString']).toBe('-89'); - expect(result['booleanTrueString']).toBe('true'); - expect(result['booleanFalseString']).toBe('false'); - expect(result['nullString']).toBe('null'); - expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); - expect(result['regularString']).toBe('not json'); - }); - - it('should handle mixed valid objects and other JSON types', () => { - const args = { - validObject: '{"success": true}', - invalidObject: '{"missing": quote}', - validNumber: '42', - validArray: '[1, 2, 3]', - keepAsString: 'hello world', - nonString: 123, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since some changes were made - expect(result['validObject']).toEqual({ success: true }); - expect(result['invalidObject']).toBe('{"missing": quote}'); - expect(result['validNumber']).toBe('42'); // Not parsed, remains string - expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string - expect(result['keepAsString']).toBe('hello world'); - expect(result['nonString']).toBe(123); - }); - - it('should return original object when no strings are present', () => { - const args = { - number: 42, - boolean: true, - object: { key: 'value' }, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); - - it('should return original object when all strings are invalid JSON', () => { - const args = { - string1: 'hello', - string2: 'world', - string3: 'not json at all', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); }); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts deleted file mode 100644 index cfff24a2..00000000 --- a/packages/mcp-server/tests/tools.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { Endpoint, Filter, Metadata, query } from '../src/tools'; - -describe('Endpoint filtering', () => { - const endpoints: Endpoint[] = [ - endpoint({ - resource: 'user', - operation: 'read', - tags: ['admin'], - toolName: 'retrieve_user', - }), - endpoint({ - resource: 'user.profile', - operation: 'write', - tags: [], - toolName: 'create_user_profile', - }), - endpoint({ - resource: 'user.profile', - operation: 'read', - tags: [], - toolName: 'get_user_profile', - }), - endpoint({ - resource: 'user.roles.permissions', - operation: 'write', - tags: ['admin', 'security'], - toolName: 'update_user_role_permissions', - }), - endpoint({ - resource: 'documents.metadata.tags', - operation: 'write', - tags: ['taxonomy', 'metadata'], - toolName: 'create_document_metadata_tags', - }), - endpoint({ - resource: 'organization.settings', - operation: 'read', - tags: ['admin', 'configuration'], - toolName: 'get_organization_settings', - }), - ]; - - const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ - { - name: 'match none', - filters: [], - expected: [], - }, - - // Resource tests - { - name: 'simple resource', - filters: [{ type: 'resource', op: 'include', value: 'user' }], - expected: ['retrieve_user'], - }, - { - name: 'exclude resource', - filters: [{ type: 'resource', op: 'exclude', value: 'user' }], - expected: [ - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'create_document_metadata_tags', - 'get_organization_settings', - ], - }, - { - name: 'resource and subresources', - filters: [{ type: 'resource', op: 'include', value: 'user*' }], - expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'just subresources', - filters: [{ type: 'resource', op: 'include', value: 'user.*' }], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'specific subresource', - filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], - expected: ['update_user_role_permissions'], - }, - { - name: 'deep wildcard match', - filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], - expected: ['create_document_metadata_tags'], - }, - - // Operation tests - { - name: 'read operation', - filters: [{ type: 'operation', op: 'include', value: 'read' }], - expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], - }, - { - name: 'write operation', - filters: [{ type: 'operation', op: 'include', value: 'write' }], - expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], - }, - { - name: 'resource and operation combined', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ], - expected: ['get_user_profile'], - }, - - // Tag tests - { - name: 'admin tag', - filters: [{ type: 'tag', op: 'include', value: 'admin' }], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'taxonomy tag', - filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], - expected: ['create_document_metadata_tags'], - }, - { - name: 'multiple tags (OR logic)', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'include', value: 'security' }, - ], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'excluding a tag', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'exclude', value: 'security' }, - ], - expected: ['retrieve_user', 'get_organization_settings'], - }, - - // Tool name tests - { - name: 'tool name match', - filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], - expected: ['get_organization_settings'], - }, - { - name: 'two tools match', - filters: [ - { type: 'tool', op: 'include', value: 'get_organization_settings' }, - { type: 'tool', op: 'include', value: 'create_user_profile' }, - ], - expected: ['create_user_profile', 'get_organization_settings'], - }, - { - name: 'excluding tool by name', - filters: [ - { type: 'resource', op: 'include', value: 'user*' }, - { type: 'tool', op: 'exclude', value: 'retrieve_user' }, - ], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - - // Complex combinations - { - name: 'complex filter: read operations with admin tag', - filters: [ - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - { - name: 'complex filter: user resources with no tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'exclude', value: 'admin' }, - ], - expected: ['create_user_profile', 'get_user_profile'], - }, - { - name: 'complex filter: user resources and tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - ]; - - tests.forEach((test) => { - it(`filters by ${test.name}`, () => { - const filtered = query(test.filters, endpoints); - expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); - }); - }); -}); - -function endpoint({ - resource, - operation, - tags, - toolName, -}: { - resource: string; - operation: Metadata['operation']; - tags: string[]; - toolName: string; -}): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, - handler: jest.fn(), - }; -} From ec8cc7e48ae11803dff315e69c49bcab702a6558 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:01:43 +0000 Subject: [PATCH 44/44] release: 8.0.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 58 +++++++++++++++++++++++++++++++ package.json | 2 +- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/server.ts | 2 +- src/version.ts | 2 +- 6 files changed, 63 insertions(+), 5 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4e9d6c7e..32ac6588 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.1.1" + ".": "8.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0617e7cc..66269322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # Changelog +## 8.0.0 (2025-12-18) + +Full Changelog: [v7.1.1...v8.0.0](https://github.com/imagekit-developer/imagekit-nodejs/compare/v7.1.1...v8.0.0) + +### ⚠ BREAKING CHANGES + +* **mcp:** remove deprecated tool schemes +* **mcp:** **Migration:** To migrate, simply modify the command used to invoke the MCP server. Currently, the only supported tool scheme is code mode. Now, starting the server with just `node /path/to/mcp/server` or `npx package-name` will invoke code tools: changing your command to one of these is likely all you will need to do. + +### Features + +* **api:** add GetImageAttributesOptions and ResponsiveImageAttributes schemas; update resource references in main.yaml; remove dummy endpoint ([9ea439a](https://github.com/imagekit-developer/imagekit-nodejs/commit/9ea439a2d0a4c8300d14d4424dc72ab40a67c4d4)) +* **mcp:** add detail field to docs search tool ([f36d795](https://github.com/imagekit-developer/imagekit-nodejs/commit/f36d79523b0613a27afd45d56f4e1e906e6fdfe9)) +* **mcp:** add typescript check to code execution tool ([63ab735](https://github.com/imagekit-developer/imagekit-nodejs/commit/63ab735bf6c8458a53bd66c8c437b45a7aef60fe)) +* **mcp:** enable optional code execution tool on http mcp servers ([cc68e38](https://github.com/imagekit-developer/imagekit-nodejs/commit/cc68e38fa61078db6a5c961e7ed75dad342dc7e8)) +* **mcp:** handle code mode calls in the Stainless API ([eb22f08](https://github.com/imagekit-developer/imagekit-nodejs/commit/eb22f0883fc74c94959fdd97e50d41d940e1aed6)) +* **mcp:** return logs on code tool errors ([6118fe4](https://github.com/imagekit-developer/imagekit-nodejs/commit/6118fe4804be492482268ff25e0afc714bea7613)) + + +### Bug Fixes + +* **docs:** remove extraneous example object fields ([a043056](https://github.com/imagekit-developer/imagekit-nodejs/commit/a043056abd2f86bcb5a691ac8f5c7ccf8a17663c)) +* **mcp:** add client instantiation options to code tool ([967c8d9](https://github.com/imagekit-developer/imagekit-nodejs/commit/967c8d90503b2e0ed0576b919be09f3b924ec890)) +* **mcpb:** pin @anthropic-ai/mcpb version ([d81e225](https://github.com/imagekit-developer/imagekit-nodejs/commit/d81e22560aab772ee9b241fb44a50561b8837034)) +* **mcp:** correct code tool API endpoint ([f4d2b6c](https://github.com/imagekit-developer/imagekit-nodejs/commit/f4d2b6c9989e8cc6c69badba7de0abb57e6de398)) +* **mcp:** pass base url to code tool ([908fa87](https://github.com/imagekit-developer/imagekit-nodejs/commit/908fa874d20613761caa76cd4b2151524ef87606)) +* **mcp:** return correct lines on typescript errors ([aa7ae07](https://github.com/imagekit-developer/imagekit-nodejs/commit/aa7ae07286cf492a7b1fecce34697006837beeef)) +* **mcp:** return tool execution error on api error ([1e866f8](https://github.com/imagekit-developer/imagekit-nodejs/commit/1e866f8e5254ecc305b3dfee53ec232455143cc4)) +* **mcp:** return tool execution error on jq failure ([d1949db](https://github.com/imagekit-developer/imagekit-nodejs/commit/d1949dbef79859b446ee9ee2c8a2d562568c1cca)) + + +### Chores + +* **client:** fix logger property type ([6269318](https://github.com/imagekit-developer/imagekit-nodejs/commit/6269318024cd320f3038def32c3d5bf1f1da77a1)) +* extract some types in mcp docs ([de606ba](https://github.com/imagekit-developer/imagekit-nodejs/commit/de606ba3b734389e1c52a9929dbf8487828822e0)) +* **internal:** codegen related update ([b6b0d1a](https://github.com/imagekit-developer/imagekit-nodejs/commit/b6b0d1a7d2f00d9ecaa9e0e630a012c25f6a00f4)) +* **internal:** codegen related update ([26acc3a](https://github.com/imagekit-developer/imagekit-nodejs/commit/26acc3a9fe08c7c478eed956dd553333bd8cf210)) +* **internal:** codegen related update ([662aa87](https://github.com/imagekit-developer/imagekit-nodejs/commit/662aa87091dc519bb811554438eaf978660d035e)) +* **internal:** codegen related update ([8c9026a](https://github.com/imagekit-developer/imagekit-nodejs/commit/8c9026ace5d217264e5753c01309a02a2c09095e)) +* **internal:** grammar fix (it's -> its) ([71e22a3](https://github.com/imagekit-developer/imagekit-nodejs/commit/71e22a30017324842fed5ab08ee1efbb1eecb6d2)) +* **internal:** upgrade eslint ([310bf0d](https://github.com/imagekit-developer/imagekit-nodejs/commit/310bf0d0cc1d5b15d08a2cbb483a9969ebf4f11d)) +* **internal:** use npm pack for build uploads ([bdfd369](https://github.com/imagekit-developer/imagekit-nodejs/commit/bdfd369118542dad02cf8a0fae8713d0d8bea4eb)) +* mcp code tool explicit error message when missing a run function ([6678ee1](https://github.com/imagekit-developer/imagekit-nodejs/commit/6678ee13cc1eee5ce1ac51b672144be77c38e9ea)) +* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([636829d](https://github.com/imagekit-developer/imagekit-nodejs/commit/636829d18a7dda730b65c0549298258afcea6341)) +* **mcp:** add line numbers to code tool errors ([25e4e59](https://github.com/imagekit-developer/imagekit-nodejs/commit/25e4e59f434c07c2d9afeef3f548dd99e250a7ab)) +* **mcp:** clarify http auth error ([00789ac](https://github.com/imagekit-developer/imagekit-nodejs/commit/00789acfb9ceb07af0cd03b1951090f430691592)) +* **mcp:** remove deprecated tool schemes ([b1a0e60](https://github.com/imagekit-developer/imagekit-nodejs/commit/b1a0e607e55f27ffed93b2862e8f4466bc609809)) +* **mcp:** update lockfile ([0703827](https://github.com/imagekit-developer/imagekit-nodejs/commit/07038271d5ec459516a8a936eb49f31490ccf6b0)) +* **mcp:** upgrade jq-web ([0750770](https://github.com/imagekit-developer/imagekit-nodejs/commit/075077081fc5826a4502a76a2abc40788f7915a3)) +* use latest @modelcontextprotocol/sdk ([f7b9b4e](https://github.com/imagekit-developer/imagekit-nodejs/commit/f7b9b4e55919b644d9445730c03c8972ecdfe0b7)) +* use structured error when code execution tool errors ([451f306](https://github.com/imagekit-developer/imagekit-nodejs/commit/451f306f6328e6c48374d0525dad9a0ddc6511d6)) + + +### Documentation + +* **mcp:** add a README button for one-click add to Cursor ([a7575d3](https://github.com/imagekit-developer/imagekit-nodejs/commit/a7575d308e3038715964f7416284bb012e27b240)) +* **mcp:** add a README link to add server to VS Code or Claude Code ([2a90d28](https://github.com/imagekit-developer/imagekit-nodejs/commit/2a90d2802e4814d97ec5bba77097eec2f0a5a718)) + ## 7.1.1 (2025-10-06) Full Changelog: [v7.1.0...v7.1.1](https://github.com/imagekit-developer/imagekit-nodejs/compare/v7.1.0...v7.1.1) diff --git a/package.json b/package.json index 111cd791..9fd98223 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@imagekit/nodejs", - "version": "7.1.1", + "version": "8.0.0", "description": "Offical NodeJS SDK for ImageKit.io integration", "author": "Image Kit ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index e51e3eb2..ec5e4337 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@imagekit/api-mcp", - "version": "7.1.1", + "version": "8.0.0", "description": "The official MCP Server for the Image Kit API", "author": "Image Kit ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 40473d31..1a471df0 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -21,7 +21,7 @@ export const newMcpServer = () => new McpServer( { name: 'imagekit_nodejs_api', - version: '7.1.1', + version: '8.0.0', }, { capabilities: { tools: {}, logging: {} } }, ); diff --git a/src/version.ts b/src/version.ts index 60c81603..ee13ebb3 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '7.1.1'; // x-release-please-version +export const VERSION = '8.0.0'; // x-release-please-version