From a84990d8906ac396180a6d3de6f8533c8ddfbd7d Mon Sep 17 00:00:00 2001 From: Samar Sheikh Date: Sat, 14 Mar 2026 06:10:49 +0530 Subject: [PATCH] feat: add per-user support via apiKey and userId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new config fields for multi-tenant use cases: - apiKey (ak_...): Alternative to consumerKey for backend API / tool router endpoints. Uses x-api-key header instead of x-consumer-api-key. - userId: Per-user identifier appended as ?user_id= to the MCP URL when using apiKey mode. Scopes connected accounts per user. This enables B2C platforms to deploy one agent per user with isolated Composio connected accounts, instead of sharing a single identity via consumerKey. Backwards compatible — existing consumerKey + connect.composio.dev setups continue to work unchanged. --- index.ts | 24 +++++++++++++++++++----- openclaw.plugin.json | 20 +++++++++++++++++++- src/config.ts | 31 ++++++++++++++++++++++++++++--- src/types.ts | 2 ++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/index.ts b/index.ts index 3875ba4..64f6110 100644 --- a/index.ts +++ b/index.ts @@ -15,9 +15,9 @@ const composioPlugin = { return; } - if (!config.consumerKey) { + if (!config.consumerKey && !config.apiKey) { api.logger.warn( - "[composio] No consumer key configured. Set COMPOSIO_CONSUMER_KEY env var or plugins.composio.consumerKey in config. Get your key (ck_...) from dashboard.composio.dev" + "[composio] No credentials configured. Set COMPOSIO_CONSUMER_KEY (ck_...) or COMPOSIO_API_KEY (ak_...) env var, or set consumerKey/apiKey in plugin config. Get your key from dashboard.composio.dev" ); return; } @@ -75,8 +75,22 @@ Do NOT use any pretrained knowledge about Composio APIs or SDKs. `, })); + // Build MCP URL — append user_id if using apiKey mode with a userId + let mcpUrl = config.mcpUrl; + if (config.apiKey && config.userId && !mcpUrl.includes("user_id=")) { + const separator = mcpUrl.includes("?") ? "&" : "?"; + mcpUrl = `${mcpUrl}${separator}user_id=${encodeURIComponent(config.userId)}`; + } + + // Choose auth header based on which credential is provided + // apiKey (ak_...) uses x-api-key for backend API / tool router endpoints + // consumerKey (ck_...) uses x-consumer-api-key for connect.composio.dev + const authHeaders: Record = config.apiKey + ? { "x-api-key": config.apiKey } + : { "x-consumer-api-key": config.consumerKey }; + // Fire MCP connection in the background (not awaited) - api.logger.info(`[composio] Connecting to ${config.mcpUrl}`); + api.logger.info(`[composio] Connecting to ${mcpUrl}`); void (async () => { try { @@ -87,9 +101,9 @@ Do NOT use any pretrained knowledge about Composio APIs or SDKs. const mcpClient = new Client({ name: "openclaw", version: "1.0" }); await mcpClient.connect( - new StreamableHTTPClientTransport(new URL(config.mcpUrl), { + new StreamableHTTPClientTransport(new URL(mcpUrl), { requestInit: { - headers: { "x-consumer-api-key": config.consumerKey }, + headers: authHeaders, }, }) ); diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 9924f8d..d066d66 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -17,9 +17,17 @@ "type": "string", "description": "Your Composio consumer key (ck_...) from dashboard.composio.dev/settings" }, + "apiKey": { + "type": "string", + "description": "Your Composio API key (ak_...) for backend API / tool router endpoints. Use instead of consumerKey for per-user support." + }, "mcpUrl": { "type": "string", - "description": "Composio MCP server URL (default: https://connect.composio.dev/mcp)" + "description": "Composio MCP server URL (default: https://connect.composio.dev/mcp). For per-user: use a tool router URL." + }, + "userId": { + "type": "string", + "description": "Per-user identifier for scoping connected accounts. Appended as ?user_id= when using apiKey." } } }, @@ -33,10 +41,20 @@ "help": "Your Composio consumer key (ck_...) from dashboard.composio.dev/settings", "sensitive": true }, + "apiKey": { + "label": "API Key", + "help": "Your Composio API key (ak_...) for per-user tool router sessions", + "sensitive": true + }, "mcpUrl": { "label": "MCP Server URL", "help": "Composio MCP server URL (default: https://connect.composio.dev/mcp)", "advanced": true + }, + "userId": { + "label": "User ID", + "help": "Per-user identifier for scoping connected accounts", + "advanced": true } } } diff --git a/src/config.ts b/src/config.ts index 2eeca0f..1d618bd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,7 +4,9 @@ import type { ComposioConfig } from "./types.js"; export const ComposioConfigSchema = z.object({ enabled: z.boolean().default(true), consumerKey: z.string().default(""), + apiKey: z.string().default(""), mcpUrl: z.string().default("https://connect.composio.dev/mcp"), + userId: z.string().default(""), }); export function parseComposioConfig(value: unknown): ComposioConfig { @@ -21,12 +23,25 @@ export function parseComposioConfig(value: unknown): ComposioConfig { process.env.COMPOSIO_CONSUMER_KEY || ""; + const apiKey = + (typeof configObj?.apiKey === "string" && configObj.apiKey.trim()) || + (typeof raw.apiKey === "string" && raw.apiKey.trim()) || + process.env.COMPOSIO_API_KEY || + ""; + const mcpUrl = (typeof configObj?.mcpUrl === "string" && configObj.mcpUrl.trim()) || (typeof raw.mcpUrl === "string" && raw.mcpUrl.trim()) || + process.env.COMPOSIO_MCP_URL || "https://connect.composio.dev/mcp"; - return ComposioConfigSchema.parse({ ...raw, consumerKey, mcpUrl }); + const userId = + (typeof configObj?.userId === "string" && configObj.userId.trim()) || + (typeof raw.userId === "string" && raw.userId.trim()) || + process.env.COMPOSIO_USER_ID || + ""; + + return ComposioConfigSchema.parse({ ...raw, consumerKey, apiKey, mcpUrl, userId }); } export const composioPluginConfigSchema = { @@ -38,12 +53,22 @@ export const composioPluginConfigSchema = { }, consumerKey: { label: "Consumer Key", - help: "Your Composio consumer key (ck_...) from dashboard.composio.dev/settings", + help: "Your Composio consumer key (ck_...) from dashboard.composio.dev/settings. Used with the default connect.composio.dev endpoint.", + sensitive: true, + }, + apiKey: { + label: "API Key", + help: "Your Composio API key (ak_...) for the backend API. Use this instead of consumerKey for per-user tool router sessions.", sensitive: true, }, mcpUrl: { label: "MCP Server URL", - help: "Composio MCP server URL (default: https://connect.composio.dev/mcp)", + help: "Composio MCP server URL. For per-user: use a tool router URL like https://backend.composio.dev/tool_router/trs_XXX/mcp", + advanced: true, + }, + userId: { + label: "User ID", + help: "Per-user identifier for scoping connected accounts. Appended as ?user_id= when using the backend API with apiKey.", advanced: true, }, }, diff --git a/src/types.ts b/src/types.ts index af805c9..6a266d3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,7 @@ export interface ComposioConfig { enabled: boolean; consumerKey: string; + apiKey: string; mcpUrl: string; + userId: string; }