diff --git a/CITATION.cff b/CITATION.cff index 1376e183..c64c34d0 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,7 +13,7 @@ abstract: > server that turns REST/OpenAPI, SOAP/WSDL and GraphQL APIs, SQL/NoSQL databases and other MCP servers into tools for AI clients such as Claude, ChatGPT, Google Gemini, GitHub Copilot and Cursor, without writing code. - It ships 331 pre-built adapters, with a focus on ERP and e-commerce + It ships 332 pre-built adapters, with a focus on ERP and e-commerce systems (SAP Business One, Odoo, Xentral, JTL-Wawi, weclapp, Shopware, WooCommerce, Amazon Seller, Kaufland, OTTO, etc.), a per-workspace knowledge graph served over MCP, per-tool response mapping, diff --git a/README.de.md b/README.de.md index 8a761733..afb42e70 100644 --- a/README.de.md +++ b/README.de.md @@ -1,5 +1,5 @@

- AnythingMCP macht ERP-, E-Commerce-, REST-, SOAP- und SQL-Systeme zu MCP-Tools für Claude und ChatGPT: 331 Connectors, 17 davon ohne API-Schlüssel. + AnythingMCP macht ERP-, E-Commerce-, REST-, SOAP- und SQL-Systeme zu MCP-Tools für Claude und ChatGPT: 332 Connectors, 18 davon ohne API-Schlüssel.

AnythingMCP: selbst gehostetes MCP-Gateway

@@ -19,7 +19,7 @@

AnythingMCP ist ein quelloffenes, selbst gehostetes MCP-Gateway, das jedes REST-/OpenAPI-, SOAP-, GraphQL-, OData- oder SQL-System in MCP-Tools für Claude, ChatGPT und Copilot verwandelt, ohne dass du einen MCP-Server programmierst.
- Es bringt 331 fertige Adapter mit, darunter SAP, Etsy, weclapp und Amazon Seller; 17 davon kommen ohne API-Schlüssel aus. + Es bringt 332 fertige Adapter mit, darunter SAP, Etsy, weclapp und Amazon Seller; 18 davon kommen ohne API-Schlüssel aus.

@@ -111,7 +111,7 @@ Tools werden zur Laufzeit registriert, ohne Neustart. `{{VAR}}`-Werte pro Connec ## Connector-Katalog -331 Adapter mit über 2.400 Tools. Zu jedem gibt es eine Einrichtungsanleitung auf [anythingmcp.com/de/guides](https://anythingmcp.com/de/guides), in sieben Sprachen. +332 Adapter mit über 2.400 Tools. Zu jedem gibt es eine Einrichtungsanleitung auf [anythingmcp.com/de/guides](https://anythingmcp.com/de/guides), in sieben Sprachen. | Kategorie | Beispiele | |---|---| diff --git a/README.ja.md b/README.ja.md index 1ef47386..16991d2d 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,5 +1,5 @@

- AnythingMCP は ERP、E コマース、REST、SOAP、SQL の各システムを Claude と ChatGPT 用の MCP ツールに変換します。331 のコネクター、うち 17 は API キー不要。 + AnythingMCP は ERP、E コマース、REST、SOAP、SQL の各システムを Claude と ChatGPT 用の MCP ツールに変換します。332 のコネクター、うち 18 は API キー不要。

AnythingMCP:セルフホスト型 MCP ゲートウェイ

@@ -19,7 +19,7 @@

AnythingMCP は、オープンソースのセルフホスト型 MCP ゲートウェイです。MCP サーバーを書かずに、REST/OpenAPI、SOAP、GraphQL、OData、SQL のあらゆるシステムを Claude、ChatGPT、Copilot 用の MCP ツールに変換します。
- SAP、Etsy、weclapp、Amazon Seller などを含む 331 種類の既製アダプターを同梱しており、うち 17 は API キー不要です。 + SAP、Etsy、weclapp、Amazon Seller などを含む 332 種類の既製アダプターを同梱しており、うち 18 は API キー不要です。

@@ -111,7 +111,7 @@ amd64 ではイメージの取得に約 30 秒、その 24 秒後に API が利 ## コネクターカタログ -331 個のアダプターで 2,400 以上のツールを提供しています。どのアダプターにも [anythingmcp.com/ja/guides](https://anythingmcp.com/ja/guides) に 7 言語の設定ガイドがあります。 +332 個のアダプターで 2,400 以上のツールを提供しています。どのアダプターにも [anythingmcp.com/ja/guides](https://anythingmcp.com/ja/guides) に 7 言語の設定ガイドがあります。 | カテゴリー | 例 | |---|---| diff --git a/README.md b/README.md index 0ce5eb00..850cc37e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- AnythingMCP turns ERP, e-commerce, REST, SOAP and SQL systems into MCP tools for Claude and ChatGPT: 331 connectors, 17 of them with no API key. + AnythingMCP turns ERP, e-commerce, REST, SOAP and SQL systems into MCP tools for Claude and ChatGPT: 332 connectors, 17 of them with no API key.

AnythingMCP: self-hosted MCP gateway

@@ -19,7 +19,7 @@

AnythingMCP is an open-source, self-hosted MCP gateway that turns any REST/OpenAPI, SOAP, GraphQL, OData or SQL system into MCP tools for Claude, ChatGPT and Copilot, without writing an MCP server.
- It ships 331 ready connectors, among them SAP, Etsy, weclapp and Amazon Seller, and 17 of them need no API key. + It ships 332 ready connectors, among them SAP, Etsy, weclapp and Amazon Seller, and 18 of them need no API key.

@@ -103,7 +103,7 @@ Tools register at runtime, without a restart. Per-connector `{{VAR}}` values are ## Connector catalog -331 adapters, exposing 2,400+ tools. Every one has a setup guide on [anythingmcp.com/guides](https://anythingmcp.com/guides), in seven languages. +332 adapters, exposing 2,400+ tools. Every one has a setup guide on [anythingmcp.com/guides](https://anythingmcp.com/guides), in seven languages. | Category | Examples | |---|---| diff --git a/README.zh-CN.md b/README.zh-CN.md index 7062f8ab..99923fbd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,5 +1,5 @@

- AnythingMCP 将 ERP、电子商务、REST、SOAP 和 SQL 系统转化为 Claude 和 ChatGPT 可用的 MCP 工具:331 个连接器,其中 17 个无需 API 密钥。 + AnythingMCP 将 ERP、电子商务、REST、SOAP 和 SQL 系统转化为 Claude 和 ChatGPT 可用的 MCP 工具:332 个连接器,其中 18 个无需 API 密钥。

AnythingMCP:自行托管的 MCP 网关

@@ -19,7 +19,7 @@

AnythingMCP 是一个开源、可自行托管的 MCP 网关,无需编写 MCP 服务器,即可将任意 REST/OpenAPI、SOAP、GraphQL、OData 或 SQL 系统转化为 Claude、ChatGPT 和 Copilot 可用的 MCP 工具。
- 它自带 331 个现成适配器,涵盖 SAP、Etsy、weclapp 和 Amazon Seller 等,其中 17 个无需 API 密钥。 + 它自带 332 个现成适配器,涵盖 SAP、Etsy、weclapp 和 Amazon Seller 等,其中 18 个无需 API 密钥。

@@ -111,7 +111,7 @@ docker compose up -d ## 连接器目录 -共 331 个适配器,提供 2,400 多个工具。每个适配器都在 [anythingmcp.com/zh/guides](https://anythingmcp.com/zh/guides) 上提供七种语言的配置指南。 +共 332 个适配器,提供 2,400 多个工具。每个适配器都在 [anythingmcp.com/zh/guides](https://anythingmcp.com/zh/guides) 上提供七种语言的配置指南。 | 类别 | 示例 | |---|---| diff --git a/glama.json b/glama.json index 3e2d9e25..9c0ca949 100644 --- a/glama.json +++ b/glama.json @@ -3,5 +3,5 @@ "maintainers": [ "keysersoft" ], - "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 331 pre-built adapters for ERP and e-commerce (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, weclapp, Shopware, WooCommerce, Amazon Seller, Kaufland, OTTO\u2026) and more. Self-hosted, knowledge graph, per-tool response mapping, OAuth2/RBAC/SSO/audit. Open source under AGPL-3.0." + "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 332 pre-built adapters for ERP and e-commerce (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, weclapp, Shopware, WooCommerce, Amazon Seller, Kaufland, OTTO\u2026) and more. Self-hosted, knowledge graph, per-tool response mapping, OAuth2/RBAC/SSO/audit. Open source under AGPL-3.0." } diff --git a/package.json b/package.json index b0402699..4e9f40e9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "anythingmcp", "version": "0.20.0", - "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 331 pre-built adapters for ERP, e-commerce and more (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, Shopware, WooCommerce, Amazon Seller). Self-hosted, open source (AGPL-3.0).", + "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 332 pre-built adapters for ERP, e-commerce and more (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, Shopware, WooCommerce, Amazon Seller). Self-hosted, open source (AGPL-3.0).", "private": true, "license": "AGPL-3.0-only", "engines": { diff --git a/packages/backend/src/adapters/catalog.ts b/packages/backend/src/adapters/catalog.ts index af0bb66d..ac2a8523 100644 --- a/packages/backend/src/adapters/catalog.ts +++ b/packages/backend/src/adapters/catalog.ts @@ -109,6 +109,7 @@ import * as clickup from './intl/clickup.json'; import * as clockify from './intl/clockify.json'; import * as close from './intl/close.json'; import * as coda from './intl/coda.json'; +import * as cohesivity from './intl/cohesivity.json'; import * as coingecko from './intl/coingecko.json'; import * as convertkit from './intl/convertkit.json'; import * as copper from './intl/copper.json'; @@ -653,6 +654,7 @@ const RAW_ADAPTERS: AdapterDefinition[] = [ clockify as unknown as AdapterDefinition, close as unknown as AdapterDefinition, coda as unknown as AdapterDefinition, + cohesivity as unknown as AdapterDefinition, coingecko as unknown as AdapterDefinition, convertkit as unknown as AdapterDefinition, copper as unknown as AdapterDefinition, diff --git a/packages/backend/src/adapters/intl/cohesivity.json b/packages/backend/src/adapters/intl/cohesivity.json new file mode 100644 index 00000000..f344d4a1 --- /dev/null +++ b/packages/backend/src/adapters/intl/cohesivity.json @@ -0,0 +1,748 @@ +{ + "slug": "cohesivity", + "name": "Cohesivity", + "description": "Set up a backend for the project an AI agent is building: Postgres, hosting, email, storage and more, from Claude, ChatGPT or Copilot through Cohesivity's hosted MCP server. No account or API key needed.", + "instructions": "This connector bridges **Cohesivity's hosted MCP server** (`https://cohesivity.ai/mcp`). Cohesivity gives an AI agent a backend for the project it is building: Postgres, Redis, object and vector storage, hosting, an email inbox, auth and social login, realtime, cron, and AI model APIs, all behind one tenant. The server needs no account or API key, so the connector installs with nothing to fill in.\n\n**The tools come from the server.** At install AnythingMCP asks Cohesivity's server which tools it has. When Cohesivity adds tools, open the connector and click **Discover tools** to pick them up.\n\n**Tenants**: `create_tenant` creates a new ephemeral tenant on every confirmed call and returns its `tenant_id`, a management key and an application key. Every other tool takes the `tenant_id` and the management key. An ephemeral tenant expires after 72 hours; `claim_tenant` returns an approval link a person opens to keep it in their Cohesivity account. Tenant creation is limited to 10 calls per 60 seconds per IP address, and every user of this connector shares the gateway's address, so reuse a tenant instead of creating one per conversation.\n\n**Safety**:\n- The tenant keys pass through the conversation as tool results and arguments, so they are stored in AnythingMCP's audit log with the rest of each call. Limit who can use this connector and read its audit log with roles, and claim the tenant before relying on it.\n- An ephemeral tenant runs under fixed usage caps and pauses when it reaches one. A claimed tenant moves to its owner's Cohesivity plan and wallet, where provisioned services can cost money.\n- `give_feedback` installs switched off, because its text goes to Cohesivity.\n\n**Working with it**: start with `get_cohesivity_documentation` (`offerings`, then `offering` for the service you need), then `create_tenant` once, then `provision_resource` for each service and `tenant_status` to check what is provisioned. The application code then calls the provisioned services over HTTPS with the application key, as the offering documents describe.", + "region": "intl", + "category": "infrastructure", + "icon": "cohesivity", + "docsUrl": "https://cohesivity.ai/docs?ref=gh-anythingmcp", + "requiredEnvVars": [], + "prerequisites": "- Nothing to set up for a first tenant: Cohesivity's server needs no account or API key.\n- A Cohesivity account to keep a tenant past 72 hours; `claim_tenant` returns the link that adds it to one.\n- Network access from the AnythingMCP server to cohesivity.ai over HTTPS.", + "connector": { + "name": "Cohesivity", + "type": "MCP", + "baseUrl": "https://cohesivity.ai/mcp", + "authType": "NONE", + "config": { + "mcpToolPrefix": "cohesivity_" + } + }, + "tools": [ + { + "name": "cohesivity_get_cohesivity_documentation", + "description": "Read a current Cohesivity overview, reference, onboarding guide, pricing guide, offerings catalog, or individual offering document.", + "parameters": { + "type": "object", + "required": [ + "document" + ], + "properties": { + "document": { + "type": "string", + "enum": [ + "docs", + "quick-reference", + "full-reference", + "onboarding", + "pricing", + "offerings", + "offering" + ], + "description": "Which document to read: `offerings` lists every service, `offering` reads one service named in `offering`, and `docs`, `quick-reference`, `full-reference`, `onboarding` and `pricing` cover the platform." + }, + "offering": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^[a-z0-9][a-z0-9-]*$", + "description": "Offering slug. Required only when document is \"offering\"." + } + }, + "additionalProperties": false + }, + "endpointMapping": { + "method": "get_cohesivity_documentation", + "path": "/mcp" + }, + "annotations": { + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "cohesivity_create_tenant", + "description": "Create an ephemeral Cohesivity tenant without login. Call this first when the project has no .cohesivity file. Each confirmed call creates a new tenant; do not retry automatically when the outcome is unknown. Write credentials_file.content verbatim to a private (0600), gitignored .cohesivity file. Never overwrite a different tenant without approval, print the contents, or commit them. Use its tenant_id and coh_management_key for follow-up tools. If the client cannot write files, say so. This response contains project secrets and may be retained in client tool history. Sign in before creating new tenants to have them owned by your account without a separate claim.", + "parameters": { + "type": "object", + "properties": { + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + } + }, + "additionalProperties": false, + "required": [ + "confirmed" + ] + }, + "endpointMapping": { + "method": "create_tenant", + "path": "/mcp" + }, + "annotations": { + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "cohesivity_provision_resource", + "description": "Provision one resource with resource/configuration, or several with resources/configurations. Requires an existing tenant: pass its tenant_id, and if the project has no .cohesivity file, call create_tenant first. Fetch every requested offering's live documentation and obtain any required user consent before calling.", + "parameters": { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "tenant_id", + "resource", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resource": { + "type": "string", + "enum": [ + "openweather-api", + "google-geocoding-api", + "openai-api", + "ai-gateway", + "deepgram-api", + "exa-api", + "steel-browser", + "redis", + "object-storage", + "railway-hosting", + "cloudflare-workers" + ] + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tenant_id", + "resource", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resource": { + "type": "string", + "const": "postgres" + }, + "configuration": { + "type": "object", + "properties": { + "region": { + "type": "string", + "enum": [ + "apac", + "aws-us-east-1", + "aws-us-east-2", + "aws-us-west-2", + "aws-eu-central-1", + "aws-eu-west-2", + "aws-ap-southeast-1", + "aws-ap-southeast-2", + "aws-sa-east-1" + ] + } + }, + "additionalProperties": false + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tenant_id", + "resource", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resource": { + "type": "string", + "const": "inbox" + }, + "configuration": { + "type": "object", + "properties": { + "webhook_url": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "additionalProperties": false + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tenant_id", + "resource", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resource": { + "type": "string", + "const": "realtime" + }, + "configuration": { + "type": "object", + "properties": { + "write_region": { + "type": "string", + "enum": [ + "wnam", + "enam", + "weur", + "eeur", + "apac", + "oc" + ] + } + }, + "additionalProperties": false + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tenant_id", + "resource", + "configuration", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resource": { + "type": "string", + "const": "social-login" + }, + "configuration": { + "type": "object", + "required": [ + "callback_urls" + ], + "properties": { + "callback_urls": { + "type": "array", + "minItems": 1, + "maxItems": 50, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + } + }, + "additionalProperties": false + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tenant_id", + "resource", + "configuration", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resource": { + "type": "string", + "const": "vector-database" + }, + "configuration": { + "type": "object", + "required": [ + "dimensions" + ], + "properties": { + "dimensions": { + "type": "integer", + "enum": [ + 384, + 768, + 1024, + 1536, + 3072 + ] + }, + "metric": { + "type": "string", + "enum": [ + "cosine", + "euclidean", + "dotproduct" + ] + } + }, + "additionalProperties": false + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tenant_id", + "resources", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "resources": { + "type": "array", + "minItems": 1, + "maxItems": 16, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "openweather-api", + "google-geocoding-api", + "openai-api", + "ai-gateway", + "deepgram-api", + "exa-api", + "steel-browser", + "inbox", + "postgres", + "redis", + "object-storage", + "vector-database", + "railway-hosting", + "cloudflare-workers", + "social-login", + "realtime" + ] + } + }, + "configurations": { + "type": "object", + "properties": { + "postgres": { + "type": "object", + "properties": { + "region": { + "type": "string", + "enum": [ + "apac", + "aws-us-east-1", + "aws-us-east-2", + "aws-us-west-2", + "aws-eu-central-1", + "aws-eu-west-2", + "aws-ap-southeast-1", + "aws-ap-southeast-2", + "aws-sa-east-1" + ] + } + }, + "additionalProperties": false + }, + "inbox": { + "type": "object", + "properties": { + "webhook_url": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + }, + "additionalProperties": false + }, + "realtime": { + "type": "object", + "properties": { + "write_region": { + "type": "string", + "enum": [ + "wnam", + "enam", + "weur", + "eeur", + "apac", + "oc" + ] + } + }, + "additionalProperties": false + }, + "social-login": { + "type": "object", + "required": [ + "callback_urls" + ], + "properties": { + "callback_urls": { + "type": "array", + "minItems": 1, + "maxItems": 50, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + } + }, + "additionalProperties": false + }, + "vector-database": { + "type": "object", + "required": [ + "dimensions" + ], + "properties": { + "dimensions": { + "type": "integer", + "enum": [ + 384, + 768, + 1024, + 1536, + 3072 + ] + }, + "metric": { + "type": "string", + "enum": [ + "cosine", + "euclidean", + "dotproduct" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + } + ] + }, + "endpointMapping": { + "method": "provision_resource", + "path": "/mcp" + }, + "annotations": { + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "cohesivity_tenant_status", + "description": "Read current lifecycle, limits, resources, runtime state, and notifications for a tenant you can manage.", + "parameters": { + "type": "object", + "required": [ + "tenant_id", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + "endpointMapping": { + "method": "tenant_status", + "path": "/mcp" + }, + "annotations": { + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "cohesivity_claim_tenant", + "description": "Start the claim handoff for an ephemeral tenant you can manage. Claiming is a consent gate; call only after explicit user approval.", + "parameters": { + "type": "object", + "required": [ + "tenant_id", + "confirmed", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "confirmed": { + "type": "boolean", + "enum": [ + true + ], + "description": "Set to true only when the current user request explicitly authorized this exact mutation." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + "endpointMapping": { + "method": "claim_tenant", + "path": "/mcp" + }, + "annotations": { + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + } + }, + { + "name": "cohesivity_give_feedback", + "description": "Submit feedback on Cohesivity and its services anytime; no user confirmation is needed. Exclude personal information and secrets.", + "parameters": { + "type": "object", + "required": [ + "tenant_id", + "feedback", + "coh_management_key" + ], + "properties": { + "tenant_id": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^[A-Za-z0-9][A-Za-z0-9-]*$", + "description": "Required explicit tenant id. Use the tenant_id that create_tenant returned, or the one in the project .cohesivity file. Account connections are not bound to a tenant; access is checked for every call." + }, + "feedback": { + "type": "string", + "minLength": 1, + "maxLength": 20000, + "pattern": "\\S", + "description": "The feedback text, sent to Cohesivity. Leave out personal information and secrets." + }, + "coh_management_key": { + "type": "string", + "minLength": 9, + "maxLength": 256, + "pattern": "^coh_man_[A-Za-z0-9_-]+$", + "writeOnly": true, + "description": "Secret management key from the tenant .cohesivity file. Authorizes this tenant only. Never print it in chat or logs or commit it." + } + }, + "additionalProperties": false + }, + "endpointMapping": { + "method": "give_feedback", + "path": "/mcp" + }, + "enabled": false, + "annotations": { + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + } + } + ] +} diff --git a/packages/backend/src/adapters/intl/cohesivity.live.spec.ts b/packages/backend/src/adapters/intl/cohesivity.live.spec.ts new file mode 100644 index 00000000..05241361 --- /dev/null +++ b/packages/backend/src/adapters/intl/cohesivity.live.spec.ts @@ -0,0 +1,81 @@ +import * as adapter from './cohesivity.json'; +import { localMcpToolName } from '../../connectors/mcp-connector-config.util'; +import { McpClientEngine } from '../../connectors/engines/mcp-client.engine'; +import { OAuth2TokenService } from '../../connectors/engines/oauth2-token.service'; + +/** Live: RUN_COHESIVITY_LIVE=1 npx jest src/adapters/intl/cohesivity.live.spec.ts */ + +type Tool = { + name: string; + enabled?: boolean; + endpointMapping: { method: string; path: string }; +}; +const a = adapter as unknown as { + instructions: string; + category: string; + requiredEnvVars: string[]; + connector: { type: string; baseUrl: string; authType: string; config: { mcpToolPrefix: string } }; + tools: Tool[]; +}; +const remoteNames = [ + 'get_cohesivity_documentation', + 'create_tenant', + 'provision_resource', + 'tenant_status', + 'claim_tenant', + 'give_feedback', +]; + +describe('cohesivity adapter: static spec conformance', () => { + it("bridges Cohesivity's hosted MCP server with no credentials", () => { + expect(a.connector.type).toBe('MCP'); + expect(a.connector.baseUrl).toBe('https://cohesivity.ai/mcp'); + expect(a.connector.authType).toBe('NONE'); + expect(a.connector.config).toEqual({ mcpToolPrefix: 'cohesivity_' }); + expect(a.requiredEnvVars).toEqual([]); + expect(a.category).toBe('infrastructure'); + }); + + it('names each tool with the prefix and calls it by its remote name on /mcp', () => { + for (const t of a.tools) { + expect(t.name).toBe(localMcpToolName(t.endpointMapping.method, a.connector.config.mcpToolPrefix)); + expect(t.name.length).toBeLessThanOrEqual(64); + expect(t.endpointMapping.path).toBe('/mcp'); + } + expect(a.tools.map((t) => t.endpointMapping.method).sort()).toEqual([...remoteNames].sort()); + }); + + it('ships the feedback tool switched off', () => { + expect(a.tools.filter((t) => t.enabled === false).map((t) => t.endpointMapping.method)).toEqual(['give_feedback']); + }); + + it('documents the tenant keys, the audit log and the creation rate limit in the instructions', () => { + expect(a.instructions).toContain('audit log'); + expect(a.instructions).toContain('72 hours'); + expect(a.instructions).toContain('10 calls per 60 seconds'); + }); + + it('uses no em or en dashes', () => { + expect(JSON.stringify(adapter)).not.toMatch(/[–—]/); + }); +}); + +// Read-only on purpose: every create_tenant call creates a tenant and counts +// against the per-IP creation limit. +const maybe = process.env.RUN_COHESIVITY_LIVE ? describe : describe.skip; +maybe('cohesivity adapter - live', () => { + const engine = new McpClientEngine({} as OAuth2TokenService); + const config = { baseUrl: a.connector.baseUrl, authType: 'NONE' }; + + it('lists every tool of the snapshot without credentials', async () => { + const tools = await engine.listTools(config); + expect(tools.map((t) => t.name)).toEqual(expect.arrayContaining(remoteNames)); + }, 30000); + + it('reads the offerings catalog without a tenant', async () => { + const doc = a.tools.find((t) => t.endpointMapping.method === 'get_cohesivity_documentation')!; + const res: any = await engine.execute(config, doc.endpointMapping, { document: 'offerings' }); + expect(res.isError).toBeFalsy(); + expect(res.content[0].text).toContain('postgres'); + }, 30000); +}); diff --git a/packages/backend/src/mcp-server/mcp-server.service.ts b/packages/backend/src/mcp-server/mcp-server.service.ts index a139dc0b..f69174a8 100644 --- a/packages/backend/src/mcp-server/mcp-server.service.ts +++ b/packages/backend/src/mcp-server/mcp-server.service.ts @@ -9,6 +9,7 @@ import { decrypt } from '../common/crypto/encryption.util'; import { getRequiredSecret } from '../common/secrets.util'; import { ToolRegistry } from './tool-registry'; import { DynamicMcpTools } from './dynamic-mcp-tools'; +import { toolSchemaProperties } from './tool-schema.util'; import { RolesService } from '../roles/roles.service'; import { McpServersService } from '../mcp-servers/mcp-servers.service'; import { McpSessionManager } from '../mcp-servers/mcp-session.manager'; @@ -583,10 +584,9 @@ export class McpServerService implements OnModuleInit { * validation signal where it matters. */ private jsonSchemaToZod(schema: Record): any { - const properties = schema?.properties as Record | undefined; + const { properties, required } = toolSchemaProperties(schema); if (!properties) return z.object({}); - const required = (schema?.required as string[]) || []; const shape: Record = {}; for (const [key, prop] of Object.entries(properties)) { diff --git a/packages/backend/src/mcp-server/tool-schema.util.spec.ts b/packages/backend/src/mcp-server/tool-schema.util.spec.ts new file mode 100644 index 00000000..4e995d3d --- /dev/null +++ b/packages/backend/src/mcp-server/tool-schema.util.spec.ts @@ -0,0 +1,82 @@ +import { z } from 'zod'; +import { jsonSchemaToZodShape, toolSchemaProperties } from './tool-schema.util'; + +// One resource from a list, one with its own configuration, or several at once: +// the shape of a vendor MCP server that puts its alternatives under `oneOf`. +const provision = { + type: 'object', + oneOf: [ + { + type: 'object', + required: ['tenant_id', 'resource', 'confirmed'], + properties: { + tenant_id: { type: 'string', description: 'Tenant id.' }, + resource: { type: 'string', enum: ['redis', 'object-storage'] }, + confirmed: { type: 'boolean' }, + }, + }, + { + type: 'object', + required: ['tenant_id', 'resource', 'confirmed'], + properties: { + tenant_id: { type: 'string', description: 'Tenant id.' }, + resource: { type: 'string', const: 'postgres' }, + configuration: { type: 'object' }, + confirmed: { type: 'boolean' }, + }, + }, + { + type: 'object', + required: ['tenant_id', 'resources', 'confirmed'], + properties: { + tenant_id: { type: 'string', description: 'Tenant id.' }, + resources: { type: 'array', items: { type: 'string' } }, + confirmed: { type: 'boolean' }, + }, + }, + ], +}; + +describe('toolSchemaProperties', () => { + it('returns the properties of a plain object schema unchanged', () => { + const schema = { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] }; + expect(toolSchemaProperties(schema)).toEqual({ properties: schema.properties, required: ['q'] }); + }); + + it('merges the branches of a top-level oneOf', () => { + const { properties, required } = toolSchemaProperties(provision); + expect(Object.keys(properties!).sort()).toEqual( + ['configuration', 'confirmed', 'resource', 'resources', 'tenant_id'].sort(), + ); + expect(properties!.resource.enum).toEqual(['redis', 'object-storage', 'postgres']); + expect(properties!.tenant_id.description).toBe('Tenant id.'); + expect(required.sort()).toEqual(['confirmed', 'tenant_id']); + }); + + it('drops the value list when one branch leaves the property open', () => { + const { properties } = toolSchemaProperties({ + anyOf: [ + { properties: { mode: { type: 'string', enum: ['a'] } } }, + { properties: { mode: { type: 'string' } } }, + ], + }); + expect(properties!.mode).toEqual({ type: 'string' }); + }); +}); + +describe('jsonSchemaToZodShape with a top-level oneOf', () => { + it('keeps the arguments of every branch instead of stripping them', () => { + const parsed = z.object(jsonSchemaToZodShape(provision)).parse({ + tenant_id: 't-1', + resource: 'postgres', + configuration: { region: 'eu' }, + confirmed: true, + }); + expect(parsed).toEqual({ + tenant_id: 't-1', + resource: 'postgres', + configuration: { region: 'eu' }, + confirmed: true, + }); + }); +}); diff --git a/packages/backend/src/mcp-server/tool-schema.util.ts b/packages/backend/src/mcp-server/tool-schema.util.ts index 7351699b..9df9bb9b 100644 --- a/packages/backend/src/mcp-server/tool-schema.util.ts +++ b/packages/backend/src/mcp-server/tool-schema.util.ts @@ -1,5 +1,44 @@ import { z } from 'zod'; +/** + * The properties a tool accepts and the ones it requires. A schema with no + * `properties` of its own that lists alternatives under a top-level `oneOf` or + * `anyOf` (as some vendors' MCP servers do) accepts every branch's properties; + * one is required only when every branch requires it. Without this the tool was + * advertised with no parameters and every argument was stripped. The upstream + * server still checks which combination is valid. + */ +export function toolSchemaProperties(schema: Record): { + properties?: Record; + required: string[]; +} { + const properties = schema?.properties as Record | undefined; + const branches = (schema?.oneOf ?? schema?.anyOf) as Record[] | undefined; + if (properties || !Array.isArray(branches) || branches.length === 0) { + return { properties, required: (schema?.required as string[]) || [] }; + } + + const merged: Record = {}; + for (const branch of branches) { + for (const [key, prop] of Object.entries((branch?.properties ?? {}) as Record)) { + const seen = merged[key]; + if (!seen) { + merged[key] = prop; + continue; + } + // Keep a value list only when every branch restricts the property to one. + const values = (p: any) => p?.enum ?? (p?.const !== undefined ? [p.const] : undefined); + const [a, b] = [values(seen), values(prop)]; + const { const: _const, enum: _enum, ...rest } = seen; + merged[key] = a && b ? { ...rest, enum: [...new Set([...a, ...b])] } : rest; + } + } + const required = Object.keys(merged).filter((key) => + branches.every((b) => ((b?.required as string[]) ?? []).includes(key)), + ); + return { properties: Object.keys(merged).length ? merged : undefined, required }; +} + /** * Convert a tool's JSON Schema to a Zod raw shape, for McpServer registration * and for validating the arguments of a tool run through the shared endpoint. @@ -7,10 +46,9 @@ import { z } from 'zod'; export function jsonSchemaToZodShape( schema: Record, ): Record { - const properties = schema?.properties as Record | undefined; + const { properties, required } = toolSchemaProperties(schema); if (!properties) return {}; - const required = (schema?.required as string[]) || []; const shape: Record = {}; for (const [key, prop] of Object.entries(properties)) { diff --git a/packages/frontend/public/logos/connectors/cohesivity.svg b/packages/frontend/public/logos/connectors/cohesivity.svg new file mode 100644 index 00000000..364cf8b9 --- /dev/null +++ b/packages/frontend/public/logos/connectors/cohesivity.svg @@ -0,0 +1 @@ +Cohesivity diff --git a/server.json b/server.json index 726ce45b..d8453d0b 100644 --- a/server.json +++ b/server.json @@ -2,7 +2,7 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.HelpCode-ai/anythingmcp", "title": "AnythingMCP", - "description": "Any REST/SOAP/GraphQL/OData/SQL API as MCP tools for Claude & ChatGPT. 331 connectors: SAP, ERP.", + "description": "Any REST/SOAP/GraphQL/OData/SQL API as MCP tools for Claude & ChatGPT. 332 connectors: SAP, ERP.", "repository": { "url": "https://github.com/HelpCode-ai/anythingmcp", "source": "github"