From ceb995ad055f3bd5458234a8e2b44ae063ef52bf Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 21 Jun 2026 19:06:37 +0000 Subject: [PATCH 1/3] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/xlabtg/teleton-plugins/issues/196 --- .gitkeep | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitkeep b/.gitkeep index 48587b8..aedc8fe 100644 --- a/.gitkeep +++ b/.gitkeep @@ -3,4 +3,5 @@ # Updated: 2026-04-05T19:20:26.278Z # Updated: 2026-04-09T18:02:32.277Z # Updated: 2026-06-14T10:40:42.180Z -# Updated: 2026-06-14T10:41:34.176Z \ No newline at end of file +# Updated: 2026-06-14T10:41:34.176Z +# Updated: 2026-06-21T19:06:37.695Z \ No newline at end of file From f260149c9a66450e47b3e75397188d471d02691c Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 21 Jun 2026 19:50:56 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=D0=BD=D0=B5=20=D1=81=D1=87=D0=B8?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=8C=20403=20Composio=20auth=5Frequired?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/composio-direct/GUIDE.md | 2 + plugins/composio-direct/README.md | 1 + plugins/composio-direct/index.js | 60 ++++++++++++++++--- plugins/composio-direct/manifest.json | 2 +- plugins/composio-direct/package.json | 2 +- .../test/integration/composio-api.test.js | 8 +-- .../test/unit/composio-direct.test.js | 11 +++- plugins/composio-direct/tests/index.test.js | 33 +++++++++- 8 files changed, 101 insertions(+), 18 deletions(-) diff --git a/plugins/composio-direct/GUIDE.md b/plugins/composio-direct/GUIDE.md index 3ebf499..1d9c19c 100644 --- a/plugins/composio-direct/GUIDE.md +++ b/plugins/composio-direct/GUIDE.md @@ -185,6 +185,8 @@ The plugin returns structured results: For `auth_required`, do not retry blindly. Generate or surface a connection link, wait for user confirmation, then retry. For validation errors, fetch the schema again and correct the parameters. For transient network or 5xx failures, the plugin already retries three times with exponential backoff. +HTTP 401/403 from Composio indicates API key authentication or permission failure, not a missing external app connection. Do not call `composio_auth_link` for those errors; verify the `composio_api_key` project key, its endpoint permissions, and any Composio IP allowlist. + ## Security Rules - Never ask the user to paste OAuth tokens or Composio API keys into chat. diff --git a/plugins/composio-direct/README.md b/plugins/composio-direct/README.md index 123bcca..58b247d 100644 --- a/plugins/composio-direct/README.md +++ b/plugins/composio-direct/README.md @@ -470,3 +470,4 @@ node --test plugins/composio-direct/test/unit/composio-direct.test.js \ - Added Triggers API coverage through trigger type discovery, active trigger listing, trigger upsert, enable/disable, and delete endpoints. - Added Webhooks API coverage through event type discovery and webhook subscription CRUD/secret rotation endpoints. - Meta-tool alignment: `composio_search_tools`, `composio_get_tool_schemas`, `composio_multi_execute`, connection/auth tools, `composio_manage_connections`, `composio_remote_bash`, and `composio_remote_workbench` cover the practical `search_tools`, `get_tool_schemas`, `multi_execute_tool`, `manage_connections`, `remote_bash_tool`, and `remote_workbench` flows for Teleton. +- HTTP 401/403 responses are reported as Composio API key access failures, not as `auth_required` service authorization. Check the project API key permissions and any Composio IP allowlist before retrying. diff --git a/plugins/composio-direct/index.js b/plugins/composio-direct/index.js index 7ef3084..75eb369 100644 --- a/plugins/composio-direct/index.js +++ b/plugins/composio-direct/index.js @@ -64,7 +64,7 @@ const COMPOSIO_EXECUTION_GUIDANCE = { export const manifest = { name: "composio-direct", - version: "1.9.1", + version: "1.9.2", sdkVersion: ">=1.0.0", description: "Direct access to 1000+ Composio automation tools plus v3.1 toolkits, files, triggers, webhooks, connection reuse, and meta-tools without MCP transport", @@ -187,12 +187,18 @@ async function fetchWithRetry({ url, method, headers, body, timeoutMs, log }) { * @returns {boolean} */ function isAuthError(response) { - if (response.status === 401 || response.status === 403) return true; if (isAuthRequiredPayload(response.data)) return true; + + // Composio reserves HTTP 401/403 for API-key authentication and permission + // failures. Toolkit OAuth/account authorization is returned through explicit + // auth_required payloads or connected-account messages instead. + if (response.status === 401 || response.status === 403) return false; + const msg = getComposioMessage(response.data).toLowerCase(); if (msg) { return ( - msg.includes("auth") || + msg.includes("auth_required") || + msg.includes("authorization required") || msg.includes("connect") || msg.includes("connection") || msg.includes("not connected") || @@ -203,6 +209,37 @@ function isAuthError(response) { return false; } +/** + * Detect Composio API-key authentication/permission errors. + * @param {{ status: number; data: unknown }} response + * @returns {boolean} + */ +function isComposioApiAccessError(response) { + return response.status === 401 || response.status === 403; +} + +/** + * Format Composio API-key authentication/permission failures with actionable + * guidance and without exposing the key. + * @param {{ status: number; data: unknown }} response + * @returns {string} + */ +function formatComposioApiAccessError(response) { + const message = getComposioMessage(response.data) || `HTTP ${response.status}`; + if (response.status === 403) { + return ( + "Composio API key permission denied. Check that composio_api_key is a " + + "project API key with permissions for this endpoint and that any Composio " + + `IP allowlist includes this runtime. Composio response: ${message}` + ); + } + + return ( + "Composio API key was rejected. Check that composio_api_key is a valid " + + `project API key. Composio response: ${message}` + ); +} + /** * Detect if a Composio response payload contains an auth_required flag, * even when the HTTP status is 200 and successful may be true. @@ -840,7 +877,9 @@ export const tools = (sdk) => { function failedResponse(action, response) { return { success: false, - error: getComposioMessage(response.data) || `${action}: HTTP ${response.status}`, + error: isComposioApiAccessError(response) + ? formatComposioApiAccessError(response) + : (getComposioMessage(response.data) || `${action}: HTTP ${response.status}`), data: { status: response.status, }, @@ -1023,7 +1062,9 @@ export const tools = (sdk) => { sdk.log.debug(`composio_search_tools: HTTP ${response.status}`); return { success: false, - error: `Composio API returned HTTP ${response.status}. Please check your API key and try again.`, + error: isComposioApiAccessError(response) + ? formatComposioApiAccessError(response) + : `Composio API returned HTTP ${response.status}. Please check your API key and try again.`, }; } @@ -1310,8 +1351,9 @@ export const tools = (sdk) => { if (response.status !== 200) { const errMsg = - getComposioMessage(response.data) || - `HTTP ${response.status}`; + isComposioApiAccessError(response) + ? formatComposioApiAccessError(response) + : (getComposioMessage(response.data) || `HTTP ${response.status}`); sdk.log.debug(`composio_execute_tool: error response ${response.status}`); return { success: false, error: `Tool execution failed: ${errMsg}` }; } @@ -1509,7 +1551,9 @@ export const tools = (sdk) => { } if (response.status !== 200) { - const errMsg = getComposioMessage(response.data) || `HTTP ${response.status}`; + const errMsg = isComposioApiAccessError(response) + ? formatComposioApiAccessError(response) + : (getComposioMessage(response.data) || `HTTP ${response.status}`); results[globalIdx] = { tool_slug: normalizedSlug, success: false, diff --git a/plugins/composio-direct/manifest.json b/plugins/composio-direct/manifest.json index 456ddde..10f17ff 100644 --- a/plugins/composio-direct/manifest.json +++ b/plugins/composio-direct/manifest.json @@ -1,7 +1,7 @@ { "id": "composio-direct", "name": "Composio Direct", - "version": "1.9.1", + "version": "1.9.2", "description": "Direct access to 1000+ Composio automation tools plus v3.1 toolkits, files, triggers, webhooks, connection reuse, and meta-tools without MCP transport", "author": { "name": "xlabtg", diff --git a/plugins/composio-direct/package.json b/plugins/composio-direct/package.json index 5aa7242..e40e766 100644 --- a/plugins/composio-direct/package.json +++ b/plugins/composio-direct/package.json @@ -1,7 +1,7 @@ { "name": "teleton-plugin-composio-direct", "type": "module", - "version": "1.9.1", + "version": "1.9.2", "private": true, "description": "Teleton plugin for direct Composio API access" } diff --git a/plugins/composio-direct/test/integration/composio-api.test.js b/plugins/composio-direct/test/integration/composio-api.test.js index 62960f5..c1a9097 100644 --- a/plugins/composio-direct/test/integration/composio-api.test.js +++ b/plugins/composio-direct/test/integration/composio-api.test.js @@ -121,10 +121,10 @@ describe("rate limit handling", () => { // --------------------------------------------------------------------------- describe("auth error flow", () => { - it("returns structured auth error with connect_url on 401", async () => { + it("returns structured auth error with connect_url when Composio reports a missing connected account", async () => { const restore = mockFetchFactory(async () => ({ - status: 401, - data: { message: "Not authenticated. Connect your GitHub account." }, + status: 400, + data: { message: "No connected account found. Connect your GitHub account." }, })); try { @@ -207,7 +207,7 @@ describe("multi-execute batching", () => { }); it("stops after first failure with fail_fast=true", async () => { - // Return 401 (auth error) which is NOT retried, so the first tool definitely fails + // Return 401 (API-key error) which is NOT retried, so the first tool definitely fails let callCount = 0; const restore = mockFetchFactory(async () => { callCount++; diff --git a/plugins/composio-direct/test/unit/composio-direct.test.js b/plugins/composio-direct/test/unit/composio-direct.test.js index 2e30075..da09899 100644 --- a/plugins/composio-direct/test/unit/composio-direct.test.js +++ b/plugins/composio-direct/test/unit/composio-direct.test.js @@ -149,7 +149,7 @@ describe("manifest", () => { assert.ok(manifest.name, "manifest.name is set"); assert.ok(manifest.version, "manifest.version is set"); assert.ok(manifest.secrets?.composio_api_key, "secret composio_api_key declared"); - assert.equal(manifest.version, "1.9.1"); + assert.equal(manifest.version, "1.9.2"); assert.equal(manifest.defaultConfig?.base_url, "https://backend.composio.dev/api/v3.1"); }); }); @@ -296,8 +296,13 @@ describe("composio_execute_tool", () => { } }); - it("returns structured auth error when 401", async () => { - const restore = mockFetch([{ status: 401, data: { message: "Unauthorized" } }]); + it("returns structured auth error when Composio reports a missing connected account", async () => { + const restore = mockFetch([ + { + status: 400, + data: { message: "No connected account found. Connect your GitHub account." }, + }, + ]); try { const sdk = makeSdk(); diff --git a/plugins/composio-direct/tests/index.test.js b/plugins/composio-direct/tests/index.test.js index fb168e3..1ec17c7 100644 --- a/plugins/composio-direct/tests/index.test.js +++ b/plugins/composio-direct/tests/index.test.js @@ -109,7 +109,7 @@ describe("composio-direct Teleton integration", () => { const sdk = makeSdk(); const toolList = toolsFactory(sdk); - assert.equal(manifest.version, "1.9.1"); + assert.equal(manifest.version, "1.9.2"); assert.equal(manifest.defaultConfig.base_url, "https://backend.composio.dev/api/v3.1"); assert.deepEqual( toolList.map((tool) => tool.name).sort(), @@ -430,6 +430,37 @@ describe("composio-direct Teleton integration", () => { } }); + it("does not treat Composio API key permission failures as toolkit auth_required", async () => { + const { restore } = mockFetch(() => ({ + status: 403, + data: { + error: { + message: "The API key doesn't have permissions to perform the request.", + slug: "HTTP_Forbidden", + status: 403, + request_id: "req_permission_denied", + suggested_fix: "Check the API key permissions or IP allowlist.", + }, + }, + })); + + try { + const executeTool = toolsFactory(makeSdk()).find((tool) => tool.name === "composio_execute_tool"); + const result = await executeTool.execute( + { tool_slug: "github_list_repos", parameters: {} }, + makeContext() + ); + + assert.equal(result.success, false); + assert.notEqual(result.error, "auth_required"); + assert.equal(result.auth, undefined); + assert.match(result.error, /API key/i); + assert.match(result.error, /permissions/i); + } finally { + restore(); + } + }); + it("passes connected_account_id in multi_execute HTTP body", async () => { const { calls, restore } = mockFetch(() => ({ status: 200, From 761fd20dbca6533ad7145af3e9311797da289cc3 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 21 Jun 2026 19:58:54 +0000 Subject: [PATCH 3/3] Revert "Initial commit with task details" This reverts commit ceb995ad055f3bd5458234a8e2b44ae063ef52bf. --- .gitkeep | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitkeep b/.gitkeep index aedc8fe..48587b8 100644 --- a/.gitkeep +++ b/.gitkeep @@ -3,5 +3,4 @@ # Updated: 2026-04-05T19:20:26.278Z # Updated: 2026-04-09T18:02:32.277Z # Updated: 2026-06-14T10:40:42.180Z -# Updated: 2026-06-14T10:41:34.176Z -# Updated: 2026-06-21T19:06:37.695Z \ No newline at end of file +# Updated: 2026-06-14T10:41:34.176Z \ No newline at end of file