@customclaw/composio 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,11 @@ OpenClaw plugin that connects your agent to Gmail, Sentry, and other services th
8
8
  openclaw plugins install @customclaw/composio
9
9
  ```
10
10
 
11
+ ## Identifiers
12
+
13
+ - Install spec: `@customclaw/composio`
14
+ - Plugin id: `composio` (used in `plugins.entries.*` and `plugins update`)
15
+
11
16
  ## Setup
12
17
 
13
18
  1. Get an API key from [platform.composio.dev/settings](https://platform.composio.dev/settings)
@@ -28,7 +33,6 @@ Or add manually to `~/.openclaw/openclaw.json`:
28
33
  "enabled": true,
29
34
  "config": {
30
35
  "apiKey": "your-api-key",
31
- "defaultUserId": "my-app-user-123",
32
36
  "allowedToolkits": ["gmail", "sentry"]
33
37
  }
34
38
  }
@@ -58,7 +62,8 @@ The plugin gives your agent three tools:
58
62
  - `composio_execute_tool` — runs a Composio action (e.g. `GMAIL_FETCH_EMAILS`, `SENTRY_LIST_ISSUES`)
59
63
  - `composio_manage_connections` — checks connection status and generates OAuth links when a toolkit isn't connected yet
60
64
 
61
- `composio_execute_tool` also accepts optional `user_id` and `connected_account_id` fields for deterministic account selection when multiple accounts exist.
65
+ `composio_search_tools` and `composio_execute_tool` require `user_id` so every action is explicitly scoped.
66
+ `composio_execute_tool` also accepts optional `connected_account_id` for deterministic account selection when multiple accounts exist.
62
67
 
63
68
  The agent handles the rest. Ask it to "check my latest emails" and it will call the right tool, prompt you to connect Gmail if needed, and fetch the results.
64
69
 
@@ -69,6 +74,7 @@ openclaw composio setup # interactive setup for
69
74
  openclaw composio list --user-id user-123 # list available toolkits for a user scope
70
75
  openclaw composio status [toolkit] --user-id user-123 # check connection status in a user scope
71
76
  openclaw composio accounts [toolkit] # inspect connected accounts (id/user_id/status)
77
+ openclaw composio accounts [toolkit] --user-id user-123 # optional filter for one user scope
72
78
  openclaw composio connect gmail --user-id user-123 # open OAuth link for a specific user scope
73
79
  openclaw composio disconnect gmail --user-id user-123 # remove a connection in that user scope
74
80
  openclaw composio search "send email" --user-id user-123
@@ -79,7 +85,6 @@ openclaw composio search "send email" --user-id user-123
79
85
  | Key | Description |
80
86
  |-----|-------------|
81
87
  | `apiKey` | Composio API key (required) |
82
- | `defaultUserId` | Default Composio `user_id` scope when `--user-id` is not provided |
83
88
  | `allowedToolkits` | Only allow these toolkits (e.g. `["gmail", "sentry"]`) |
84
89
  | `blockedToolkits` | Block specific toolkits |
85
90
  | `readOnlyMode` | Blocks likely-destructive actions by token matching (delete/update/create/send/etc.); use `allowedToolSlugs` to override safe exceptions |
@@ -95,14 +100,13 @@ may look disconnected.
95
100
 
96
101
  Tips:
97
102
 
98
- - Set `defaultUserId` in plugin config for your app's primary identity.
99
- - Prefer passing `user_id`/`--user-id` explicitly when checking status, connecting, disconnecting, or executing tools.
103
+ - Always pass `user_id`/`--user-id` explicitly when searching, checking status, connecting, disconnecting, or executing tools.
100
104
  - Use `openclaw composio accounts <toolkit>` to discover which `user_id` owns active connections.
101
105
 
102
106
  ## Updating
103
107
 
104
108
  ```bash
105
- openclaw plugins update @customclaw/composio
109
+ openclaw plugins update composio
106
110
  openclaw gateway restart
107
111
  ```
108
112
 
package/dist/cli.js CHANGED
@@ -30,6 +30,13 @@ function normalizePluginIdList(value) {
30
30
  .filter(Boolean);
31
31
  return Array.from(new Set(normalized));
32
32
  }
33
+ function requireUserId(rawUserId, logger, commandUsage) {
34
+ const userId = String(rawUserId || "").trim();
35
+ if (userId)
36
+ return userId;
37
+ logger.error(`--user-id is required. Usage: ${commandUsage}`);
38
+ return null;
39
+ }
33
40
  async function readOpenClawConfig(configPath) {
34
41
  try {
35
42
  const raw = await readFile(configPath, "utf8");
@@ -71,7 +78,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
71
78
  .description("Create or update Composio config in ~/.openclaw/openclaw.json")
72
79
  .option("-c, --config-path <path>", "OpenClaw config file path", DEFAULT_OPENCLAW_CONFIG_PATH)
73
80
  .option("--api-key <apiKey>", "Composio API key")
74
- .option("--default-user-id <userId>", "Default user ID for Composio user scoping")
75
81
  .option("--allowed-toolkits <toolkits>", "Comma-separated allowed toolkit slugs")
76
82
  .option("--blocked-toolkits <toolkits>", "Comma-separated blocked toolkit slugs")
77
83
  .option("--read-only <enabled>", "Enable read-only mode (true/false)")
@@ -120,9 +126,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
120
126
  String(existingComposioConfig.apiKey || "").trim() ||
121
127
  String(config.apiKey || "").trim() ||
122
128
  String(process.env.COMPOSIO_API_KEY || "").trim();
123
- let defaultUserId = String(options.defaultUserId || "").trim() ||
124
- String(existingComposioConfig.defaultUserId || "").trim() ||
125
- String(config.defaultUserId || "").trim();
126
129
  let allowedToolkits = parseCsvToolkits(options.allowedToolkits) ||
127
130
  (Array.isArray(existingComposioConfig.allowedToolkits)
128
131
  ? normalizeToolkitList(existingComposioConfig.allowedToolkits)
@@ -148,9 +151,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
148
151
  const apiKeyPrompt = await rl.question(`Composio API key${apiKey ? " [configured]" : ""}: `);
149
152
  if (apiKeyPrompt.trim())
150
153
  apiKey = apiKeyPrompt.trim();
151
- const defaultUserPrompt = await rl.question(`Default user ID${defaultUserId ? ` [${defaultUserId}]` : " (optional)"}: `);
152
- if (defaultUserPrompt.trim())
153
- defaultUserId = defaultUserPrompt.trim();
154
154
  const allowedDefault = allowedToolkits && allowedToolkits.length > 0
155
155
  ? ` [${allowedToolkits.join(",")}]`
156
156
  : " (optional)";
@@ -187,12 +187,7 @@ export function registerComposioCli({ program, getClient, config, logger }) {
187
187
  apiKey,
188
188
  readOnlyMode,
189
189
  };
190
- if (defaultUserId) {
191
- mergedComposioConfig.defaultUserId = defaultUserId;
192
- }
193
- else {
194
- delete mergedComposioConfig.defaultUserId;
195
- }
190
+ delete mergedComposioConfig.defaultUserId;
196
191
  if (allowedToolkits && allowedToolkits.length > 0) {
197
192
  mergedComposioConfig.allowedToolkits = allowedToolkits;
198
193
  }
@@ -217,7 +212,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
217
212
  console.log("\nComposio setup saved.");
218
213
  console.log("─".repeat(40));
219
214
  console.log(`Config: ${configPath}`);
220
- console.log(`defaultUserId: ${defaultUserId || "default"}`);
221
215
  console.log(`readOnlyMode: ${readOnlyMode ? "enabled" : "disabled"}`);
222
216
  if (updatedPluginSystemEnabled) {
223
217
  console.log("plugins.enabled: set to true");
@@ -241,13 +235,16 @@ export function registerComposioCli({ program, getClient, config, logger }) {
241
235
  composio
242
236
  .command("list")
243
237
  .description("List available Composio toolkits")
244
- .option("-u, --user-id <userId>", "User ID for session scoping")
238
+ .option("-u, --user-id <userId>", "Required user ID for session scoping")
245
239
  .action(async (options) => {
246
240
  const composioClient = requireClient();
247
241
  if (!composioClient)
248
242
  return;
243
+ const userId = requireUserId(options.userId, logger, "openclaw composio list --user-id <user-id>");
244
+ if (!userId)
245
+ return;
249
246
  try {
250
- const toolkits = await composioClient.listToolkits(options.userId);
247
+ const toolkits = await composioClient.listToolkits(userId);
251
248
  console.log("\nAvailable Composio Toolkits:");
252
249
  console.log("─".repeat(40));
253
250
  for (const toolkit of toolkits.sort()) {
@@ -263,19 +260,21 @@ export function registerComposioCli({ program, getClient, config, logger }) {
263
260
  composio
264
261
  .command("status [toolkit]")
265
262
  .description("Check connection status for toolkits")
266
- .option("-u, --user-id <userId>", "User ID for session scoping")
263
+ .option("-u, --user-id <userId>", "Required user ID for session scoping")
267
264
  .action(async (toolkit, options) => {
268
265
  const composioClient = requireClient();
269
266
  if (!composioClient)
270
267
  return;
268
+ const userId = requireUserId(options.userId, logger, "openclaw composio status [toolkit] --user-id <user-id>");
269
+ if (!userId)
270
+ return;
271
271
  try {
272
272
  const toolkitSlug = toolkit ? normalizeToolkitSlug(toolkit) : undefined;
273
273
  const toolkits = toolkitSlug ? [toolkitSlug] : undefined;
274
- const currentUserId = options.userId || config.defaultUserId || "default";
275
- const statuses = await composioClient.getConnectionStatus(toolkits, options.userId);
274
+ const statuses = await composioClient.getConnectionStatus(toolkits, userId);
276
275
  console.log("\nComposio Connection Status:");
277
276
  console.log("─".repeat(40));
278
- console.log(` Scope user_id: ${currentUserId}${options.userId ? " (explicit)" : " (default)"}`);
277
+ console.log(` Scope user_id: ${userId}`);
279
278
  if (statuses.length === 0) {
280
279
  console.log(" No connections found");
281
280
  }
@@ -288,7 +287,7 @@ export function registerComposioCli({ program, getClient, config, logger }) {
288
287
  }
289
288
  if (toolkitSlug && statuses.length === 1 && !statuses[0]?.connected) {
290
289
  const activeUserIds = await composioClient.findActiveUserIdsForToolkit(toolkitSlug);
291
- const otherUserIds = activeUserIds.filter((uid) => uid !== currentUserId);
290
+ const otherUserIds = activeUserIds.filter((uid) => uid !== userId);
292
291
  if (otherUserIds.length > 0) {
293
292
  console.log(`\n Hint: '${toolkitSlug}' is connected under other user_id(s): ${otherUserIds.join(", ")}`);
294
293
  console.log(` Try: openclaw composio status ${toolkitSlug} --user-id <user-id>`);
@@ -341,17 +340,19 @@ export function registerComposioCli({ program, getClient, config, logger }) {
341
340
  composio
342
341
  .command("connect <toolkit>")
343
342
  .description("Connect to a Composio toolkit (opens auth URL)")
344
- .option("-u, --user-id <userId>", "User ID for session scoping")
343
+ .option("-u, --user-id <userId>", "Required user ID for session scoping")
345
344
  .action(async (toolkit, options) => {
346
345
  const composioClient = requireClient();
347
346
  if (!composioClient)
348
347
  return;
348
+ const userId = requireUserId(options.userId, logger, "openclaw composio connect <toolkit> --user-id <user-id>");
349
+ if (!userId)
350
+ return;
349
351
  try {
350
352
  const toolkitSlug = normalizeToolkitSlug(toolkit);
351
- const currentUserId = options.userId || config.defaultUserId || "default";
352
353
  console.log(`\nInitiating connection to ${toolkitSlug}...`);
353
- console.log(`Using user_id: ${currentUserId}`);
354
- const result = await composioClient.createConnection(toolkitSlug, options.userId);
354
+ console.log(`Using user_id: ${userId}`);
355
+ const result = await composioClient.createConnection(toolkitSlug, userId);
355
356
  if ("error" in result) {
356
357
  logger.error(`Failed to create connection: ${result.error}`);
357
358
  return;
@@ -360,7 +361,7 @@ export function registerComposioCli({ program, getClient, config, logger }) {
360
361
  console.log("─".repeat(40));
361
362
  console.log(result.authUrl);
362
363
  console.log("\nOpen this URL in your browser to authenticate.");
363
- console.log(`After authentication, run 'openclaw composio status ${toolkitSlug} --user-id ${currentUserId}' to verify.\n`);
364
+ console.log(`After authentication, run 'openclaw composio status ${toolkitSlug} --user-id ${userId}' to verify.\n`);
364
365
  // Try to open URL in browser
365
366
  try {
366
367
  const { exec } = await import("node:child_process");
@@ -384,15 +385,18 @@ export function registerComposioCli({ program, getClient, config, logger }) {
384
385
  composio
385
386
  .command("disconnect <toolkit>")
386
387
  .description("Disconnect from a Composio toolkit")
387
- .option("-u, --user-id <userId>", "User ID for session scoping")
388
+ .option("-u, --user-id <userId>", "Required user ID for session scoping")
388
389
  .action(async (toolkit, options) => {
389
390
  const composioClient = requireClient();
390
391
  if (!composioClient)
391
392
  return;
393
+ const userId = requireUserId(options.userId, logger, "openclaw composio disconnect <toolkit> --user-id <user-id>");
394
+ if (!userId)
395
+ return;
392
396
  try {
393
397
  const toolkitSlug = normalizeToolkitSlug(toolkit);
394
398
  console.log(`\nDisconnecting from ${toolkitSlug}...`);
395
- const result = await composioClient.disconnectToolkit(toolkitSlug, options.userId);
399
+ const result = await composioClient.disconnectToolkit(toolkitSlug, userId);
396
400
  if (result.success) {
397
401
  console.log(`Successfully disconnected from ${toolkitSlug}\n`);
398
402
  }
@@ -410,18 +414,21 @@ export function registerComposioCli({ program, getClient, config, logger }) {
410
414
  .description("Search for tools matching a query")
411
415
  .option("-t, --toolkit <toolkit>", "Limit search to a specific toolkit")
412
416
  .option("-l, --limit <limit>", "Maximum results", "10")
413
- .option("-u, --user-id <userId>", "User ID for session scoping")
417
+ .option("-u, --user-id <userId>", "Required user ID for session scoping")
414
418
  .action(async (query, options) => {
415
419
  const composioClient = requireClient();
416
420
  if (!composioClient)
417
421
  return;
422
+ const userId = requireUserId(options.userId, logger, "openclaw composio search <query> --user-id <user-id>");
423
+ if (!userId)
424
+ return;
418
425
  try {
419
426
  const limit = parseInt(options.limit, 10) || 10;
420
427
  const toolkits = options.toolkit ? [normalizeToolkitSlug(options.toolkit)] : undefined;
421
428
  const results = await composioClient.searchTools(query, {
422
429
  toolkits,
423
430
  limit,
424
- userId: options.userId,
431
+ userId,
425
432
  });
426
433
  console.log(`\nSearch results for "${query}":`);
427
434
  console.log("─".repeat(60));
package/dist/client.d.ts CHANGED
@@ -8,7 +8,8 @@ export declare class ComposioClient {
8
8
  private sessionCache;
9
9
  constructor(config: ComposioConfig);
10
10
  /**
11
- * Get the user ID to use for API calls
11
+ * Resolve user ID for API calls.
12
+ * This plugin requires explicit user_id scoping to avoid accidental cross-user access.
12
13
  */
13
14
  private getUserId;
14
15
  /**
@@ -19,7 +20,6 @@ export declare class ComposioClient {
19
20
  private buildToolRouterBlockedToolsConfig;
20
21
  private buildSessionConfig;
21
22
  private getSession;
22
- private shouldRetrySessionWithoutToolkitFilters;
23
23
  private clearUserSessionCache;
24
24
  /**
25
25
  * Check if a toolkit is allowed based on config
@@ -43,18 +43,8 @@ export declare class ComposioClient {
43
43
  * Execute a single tool using COMPOSIO_MULTI_EXECUTE_TOOL
44
44
  */
45
45
  executeTool(toolSlug: string, args: Record<string, unknown>, userId?: string, connectedAccountId?: string): Promise<ToolExecutionResult>;
46
- private tryExecutionRecovery;
47
- private tryDirectExecutionFallback;
48
- private executeDirectTool;
49
- private tryHintedIdentifierRetry;
50
- private shouldFallbackToDirectExecution;
51
- private shouldRetryFromServerHint;
52
- private extractServerHintLiteral;
53
- private buildRetryArgsFromHint;
54
- private extractSingleMissingField;
55
- private buildCombinedErrorText;
56
- private extractNestedMetaError;
57
46
  private resolveConnectedAccountForExecution;
47
+ private isConnectedAccountActiveForUser;
58
48
  /**
59
49
  * Get connection status for toolkits using session.toolkits()
60
50
  */
package/dist/client.js CHANGED
@@ -58,10 +58,15 @@ export class ComposioClient {
58
58
  this.client = new Composio({ apiKey: config.apiKey });
59
59
  }
60
60
  /**
61
- * Get the user ID to use for API calls
61
+ * Resolve user ID for API calls.
62
+ * This plugin requires explicit user_id scoping to avoid accidental cross-user access.
62
63
  */
63
64
  getUserId(overrideUserId) {
64
- return overrideUserId || this.config.defaultUserId || "default";
65
+ const userId = String(overrideUserId || "").trim();
66
+ if (!userId) {
67
+ throw new Error("user_id is required. Pass user_id explicitly.");
68
+ }
69
+ return userId;
65
70
  }
66
71
  /**
67
72
  * Get or create a Tool Router session for a user
@@ -141,32 +146,10 @@ export class ComposioClient {
141
146
  return this.sessionCache.get(key);
142
147
  }
143
148
  const sessionConfig = this.buildSessionConfig(normalizedConnectedAccounts);
144
- let session;
145
- try {
146
- session = await this.client.toolRouter.create(userId, sessionConfig);
147
- }
148
- catch (err) {
149
- if (!this.shouldRetrySessionWithoutToolkitFilters(err, sessionConfig)) {
150
- throw err;
151
- }
152
- const { toolkits: _removedToolkits, ...retryWithoutToolkits } = sessionConfig ?? {};
153
- const retryConfig = Object.keys(retryWithoutToolkits).length > 0
154
- ? retryWithoutToolkits
155
- : undefined;
156
- session = await this.client.toolRouter.create(userId, retryConfig);
157
- }
149
+ const session = await this.client.toolRouter.create(userId, sessionConfig);
158
150
  this.sessionCache.set(key, session);
159
151
  return session;
160
152
  }
161
- shouldRetrySessionWithoutToolkitFilters(err, sessionConfig) {
162
- const enabledToolkits = sessionConfig?.toolkits?.enable;
163
- if (!Array.isArray(enabledToolkits) || enabledToolkits.length === 0) {
164
- return false;
165
- }
166
- const message = String(err instanceof Error ? err.message : err || "").toLowerCase();
167
- return (message.includes("require auth configs but none exist") &&
168
- message.includes("please specify them in auth_configs"));
169
- }
170
153
  clearUserSessionCache(userId) {
171
154
  const prefix = `uid:${userId}`;
172
155
  for (const key of this.sessionCache.keys()) {
@@ -322,16 +305,6 @@ export class ComposioClient {
322
305
  sync_response_to_workbench: false,
323
306
  });
324
307
  if (!response.successful) {
325
- const recovered = await this.tryExecutionRecovery({
326
- uid: effectiveUid,
327
- toolSlug: normalizedToolSlug,
328
- args,
329
- connectedAccountId: accountResolution.connectedAccountId,
330
- metaError: response.error,
331
- metaData: response.data,
332
- });
333
- if (recovered)
334
- return recovered;
335
308
  return { success: false, error: response.error || "Execution failed" };
336
309
  }
337
310
  const results = response.data?.results || [];
@@ -341,21 +314,10 @@ export class ComposioClient {
341
314
  }
342
315
  // Response data is nested under result.response
343
316
  const toolResponse = result.response;
344
- if (!toolResponse.successful) {
345
- const recovered = await this.tryExecutionRecovery({
346
- uid: effectiveUid,
347
- toolSlug: normalizedToolSlug,
348
- args,
349
- connectedAccountId: accountResolution.connectedAccountId,
350
- metaError: toolResponse.error ?? undefined,
351
- metaData: response.data,
352
- });
353
- if (recovered)
354
- return recovered;
355
- }
317
+ const toolData = toolResponse.data ?? toolResponse.data_preview;
356
318
  return {
357
319
  success: toolResponse.successful,
358
- data: toolResponse.data,
320
+ data: toolData,
359
321
  error: toolResponse.error ?? undefined,
360
322
  };
361
323
  }
@@ -366,139 +328,13 @@ export class ComposioClient {
366
328
  };
367
329
  }
368
330
  }
369
- async tryExecutionRecovery(params) {
370
- const directFallback = await this.tryDirectExecutionFallback(params);
371
- if (directFallback?.success)
372
- return directFallback;
373
- const hintedRetry = await this.tryHintedIdentifierRetry({
374
- ...params,
375
- additionalError: directFallback?.error,
376
- });
377
- if (hintedRetry)
378
- return hintedRetry;
379
- return directFallback;
380
- }
381
- async tryDirectExecutionFallback(params) {
382
- if (!this.shouldFallbackToDirectExecution(params.uid, params.metaError, params.metaData)) {
383
- return null;
384
- }
385
- return this.executeDirectTool(params.toolSlug, params.uid, params.args, params.connectedAccountId);
386
- }
387
- async executeDirectTool(toolSlug, userId, args, connectedAccountId) {
388
- try {
389
- const response = await this.client.tools.execute(toolSlug, {
390
- userId,
391
- connectedAccountId,
392
- arguments: args,
393
- dangerouslySkipVersionCheck: true,
394
- });
395
- return {
396
- success: Boolean(response?.successful),
397
- data: response?.data,
398
- error: response?.error ?? undefined,
399
- };
400
- }
401
- catch (err) {
402
- return {
403
- success: false,
404
- error: err instanceof Error ? err.message : String(err),
405
- };
406
- }
407
- }
408
- async tryHintedIdentifierRetry(params) {
409
- const combined = this.buildCombinedErrorText(params.metaError, params.metaData, params.additionalError);
410
- if (!this.shouldRetryFromServerHint(combined))
411
- return null;
412
- const hint = this.extractServerHintLiteral(combined);
413
- if (!hint)
414
- return null;
415
- const retryArgs = this.buildRetryArgsFromHint(params.args, combined, hint);
416
- if (!retryArgs)
417
- return null;
418
- return this.executeDirectTool(params.toolSlug, params.uid, retryArgs, params.connectedAccountId);
419
- }
420
- shouldFallbackToDirectExecution(uid, metaError, metaData) {
421
- if (uid === "default")
422
- return false;
423
- const combined = this.buildCombinedErrorText(metaError, metaData).toLowerCase();
424
- return combined.includes("no connected account found for entity id default");
425
- }
426
- shouldRetryFromServerHint(errorText) {
427
- const lower = errorText.toLowerCase();
428
- return (lower.includes("only allowed to access") ||
429
- lower.includes("allowed to access the"));
430
- }
431
- extractServerHintLiteral(errorText) {
432
- const matches = errorText.match(/`([^`]+)`/);
433
- if (!matches?.[1])
434
- return undefined;
435
- const literal = matches[1].trim();
436
- if (!literal)
437
- return undefined;
438
- if (literal.length > 64)
439
- return undefined;
440
- if (/\s/.test(literal))
441
- return undefined;
442
- return literal;
443
- }
444
- buildRetryArgsFromHint(args, errorText, hint) {
445
- const stringEntries = Object.entries(args).filter(([, value]) => typeof value === "string");
446
- if (stringEntries.length === 1) {
447
- const [field, current] = stringEntries[0];
448
- if (current === hint)
449
- return null;
450
- return { ...args, [field]: hint };
451
- }
452
- if (stringEntries.length === 0) {
453
- const missing = this.extractSingleMissingField(errorText);
454
- if (!missing)
455
- return null;
456
- return { ...args, [missing]: hint };
457
- }
458
- return null;
459
- }
460
- extractSingleMissingField(errorText) {
461
- const match = errorText.match(/following fields are missing:\s*\{([^}]+)\}/i);
462
- const raw = match?.[1];
463
- if (!raw)
464
- return undefined;
465
- const fields = raw
466
- .split(",")
467
- .map(part => part.trim().replace(/^['"]|['"]$/g, ""))
468
- .filter(Boolean);
469
- return fields.length === 1 ? fields[0] : undefined;
470
- }
471
- buildCombinedErrorText(metaError, metaData, additionalError) {
472
- return [metaError, this.extractNestedMetaError(metaData), additionalError]
473
- .map(v => String(v || "").trim())
474
- .filter(Boolean)
475
- .join("\n");
476
- }
477
- extractNestedMetaError(metaData) {
478
- const results = metaData?.results || [];
479
- const first = results[0];
480
- return String(first?.error || "");
481
- }
482
331
  async resolveConnectedAccountForExecution(params) {
483
332
  const toolkit = normalizeToolkitSlug(params.toolkit);
484
333
  const { userId } = params;
485
334
  const explicitId = params.connectedAccountId?.trim();
486
335
  if (explicitId) {
487
336
  try {
488
- let rawAccount;
489
- const retrieve = this.client?.client?.connectedAccounts?.retrieve;
490
- if (typeof retrieve === "function") {
491
- try {
492
- rawAccount = await retrieve.call(this.client.client.connectedAccounts, explicitId);
493
- }
494
- catch {
495
- // Best-effort: fall through to SDK get() which may omit user_id.
496
- }
497
- }
498
- if (!rawAccount) {
499
- rawAccount = await this.client.connectedAccounts.get(explicitId);
500
- }
501
- const account = rawAccount;
337
+ const account = await this.client.connectedAccounts.get(explicitId);
502
338
  const accountToolkit = normalizeToolkitSlug(String(account?.toolkit?.slug || ""));
503
339
  const accountStatus = String(account?.status || "").toUpperCase();
504
340
  const accountUserId = String(account?.user_id || account?.userId || "").trim();
@@ -518,7 +354,18 @@ export class ComposioClient {
518
354
  `but '${userId}' was requested. Use matching user_id or omit user_id when providing connected_account_id.`,
519
355
  };
520
356
  }
521
- return { connectedAccountId: explicitId, userId: accountUserId || undefined };
357
+ if (!accountUserId) {
358
+ // Fail closed: when owner is omitted by API, verify this account is ACTIVE in requested user scope.
359
+ const accountMatchesRequestedUser = await this.isConnectedAccountActiveForUser(toolkit, userId, explicitId);
360
+ if (!accountMatchesRequestedUser) {
361
+ return {
362
+ error: `Connected account '${explicitId}' ownership could not be verified for user_id '${userId}'. ` +
363
+ "Use a connected_account_id that belongs to this user_id and is ACTIVE.",
364
+ };
365
+ }
366
+ return { connectedAccountId: explicitId, userId };
367
+ }
368
+ return { connectedAccountId: explicitId, userId: accountUserId };
522
369
  }
523
370
  catch (err) {
524
371
  return {
@@ -540,6 +387,14 @@ export class ComposioClient {
540
387
  "Please provide connected_account_id to choose one explicitly.",
541
388
  };
542
389
  }
390
+ async isConnectedAccountActiveForUser(toolkit, userId, connectedAccountId) {
391
+ const activeAccounts = await this.listConnectedAccounts({
392
+ toolkits: [toolkit],
393
+ userIds: [userId],
394
+ statuses: ["ACTIVE"],
395
+ });
396
+ return activeAccounts.some((account) => account.id === connectedAccountId);
397
+ }
543
398
  /**
544
399
  * Get connection status for toolkits using session.toolkits()
545
400
  */
@@ -708,11 +563,15 @@ export class ComposioClient {
708
563
  return Array.from(userIds).sort();
709
564
  }
710
565
  async listConnectedAccountsRaw(options) {
566
+ const rawList = this.client?.client?.connectedAccounts?.list;
567
+ if (typeof rawList !== "function") {
568
+ throw new Error("Raw connected accounts list API unavailable");
569
+ }
711
570
  const accounts = [];
712
571
  let cursor;
713
572
  const seenCursors = new Set();
714
573
  do {
715
- const response = await this.client.client.connectedAccounts.list({
574
+ const response = await rawList.call(this.client.client.connectedAccounts, {
716
575
  ...(options?.toolkits && options.toolkits.length > 0 ? { toolkit_slugs: options.toolkits } : {}),
717
576
  ...(options?.userIds && options.userIds.length > 0 ? { user_ids: options.userIds } : {}),
718
577
  ...(options?.statuses && options.statuses.length > 0 ? { statuses: options.statuses } : {}),
@@ -731,19 +590,31 @@ export class ComposioClient {
731
590
  accounts.push({
732
591
  id: String(item.id || ""),
733
592
  toolkit: toolkitSlug,
734
- userId: typeof item.user_id === "string" ? item.user_id : undefined,
593
+ userId: typeof item.user_id === "string"
594
+ ? item.user_id
595
+ : (typeof item.userId === "string" ? item.userId : undefined),
735
596
  status: typeof item.status === "string" ? item.status : undefined,
736
597
  authConfigId: typeof item.auth_config?.id === "string"
737
598
  ? item.auth_config.id
738
- : undefined,
739
- isDisabled: typeof item.is_disabled === "boolean" ? item.is_disabled : undefined,
740
- createdAt: typeof item.created_at === "string" ? item.created_at : undefined,
741
- updatedAt: typeof item.updated_at === "string" ? item.updated_at : undefined,
599
+ : (typeof item.authConfig?.id === "string"
600
+ ? item.authConfig.id
601
+ : undefined),
602
+ isDisabled: typeof item.is_disabled === "boolean"
603
+ ? item.is_disabled
604
+ : (typeof item.isDisabled === "boolean" ? item.isDisabled : undefined),
605
+ createdAt: typeof item.created_at === "string"
606
+ ? item.created_at
607
+ : (typeof item.createdAt === "string" ? item.createdAt : undefined),
608
+ updatedAt: typeof item.updated_at === "string"
609
+ ? item.updated_at
610
+ : (typeof item.updatedAt === "string" ? item.updatedAt : undefined),
742
611
  });
743
612
  }
744
613
  cursor = Array.isArray(response)
745
614
  ? null
746
- : (response?.next_cursor ?? null);
615
+ : ((response?.next_cursor)
616
+ ?? (response?.nextCursor)
617
+ ?? null);
747
618
  if (!cursor)
748
619
  break;
749
620
  if (seenCursors.has(cursor))
@@ -776,6 +647,9 @@ export class ComposioClient {
776
647
  accounts.push({
777
648
  id: String(item.id || ""),
778
649
  toolkit: toolkitSlug,
650
+ userId: typeof item.userId === "string"
651
+ ? item.userId
652
+ : (typeof item.user_id === "string" ? item.user_id : undefined),
779
653
  status: typeof item.status === "string" ? item.status : undefined,
780
654
  authConfigId: typeof item.authConfig?.id === "string"
781
655
  ? item.authConfig.id
@@ -811,7 +685,11 @@ export class ComposioClient {
811
685
  try {
812
686
  const session = await this.getSession(uid);
813
687
  const result = await session.authorize(toolkitSlug);
814
- return { authUrl: result.redirectUrl || result.url || "" };
688
+ const authUrl = String(result.redirectUrl || result.url || "").trim();
689
+ if (!authUrl) {
690
+ return { error: "Auth URL was not returned by provider" };
691
+ }
692
+ return { authUrl };
815
693
  }
816
694
  catch (err) {
817
695
  return {
package/dist/config.d.ts CHANGED
@@ -6,7 +6,6 @@ import type { ComposioConfig } from "./types.js";
6
6
  export declare const ComposioConfigSchema: z.ZodObject<{
7
7
  enabled: z.ZodDefault<z.ZodBoolean>;
8
8
  apiKey: z.ZodOptional<z.ZodString>;
9
- defaultUserId: z.ZodOptional<z.ZodString>;
10
9
  allowedToolkits: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
10
  blockedToolkits: z.ZodOptional<z.ZodArray<z.ZodString>>;
12
11
  readOnlyMode: z.ZodDefault<z.ZodBoolean>;
@@ -36,10 +35,6 @@ export declare const composioConfigUiHints: {
36
35
  help: string;
37
36
  sensitive: boolean;
38
37
  };
39
- defaultUserId: {
40
- label: string;
41
- help: string;
42
- };
43
38
  allowedToolkits: {
44
39
  label: string;
45
40
  help: string;
@@ -86,10 +81,6 @@ export declare const composioPluginConfigSchema: {
86
81
  help: string;
87
82
  sensitive: boolean;
88
83
  };
89
- defaultUserId: {
90
- label: string;
91
- help: string;
92
- };
93
84
  allowedToolkits: {
94
85
  label: string;
95
86
  help: string;
package/dist/config.js CHANGED
@@ -6,7 +6,6 @@ import { LEGACY_ENTRY_FLAT_CONFIG_KEYS, LEGACY_SHAPE_ERROR, SESSION_TAGS, isReco
6
6
  export const ComposioConfigSchema = z.object({
7
7
  enabled: z.boolean().default(true),
8
8
  apiKey: z.string().optional(),
9
- defaultUserId: z.string().optional(),
10
9
  allowedToolkits: z.array(z.string()).optional(),
11
10
  blockedToolkits: z.array(z.string()).optional(),
12
11
  readOnlyMode: z.boolean().default(false),
@@ -35,7 +34,6 @@ export function parseComposioConfig(value) {
35
34
  return ComposioConfigSchema.parse({
36
35
  enabled,
37
36
  apiKey,
38
- defaultUserId: typeof source.defaultUserId === "string" ? source.defaultUserId : undefined,
39
37
  allowedToolkits: normalizeToolkitList(Array.isArray(source.allowedToolkits) ? source.allowedToolkits : undefined),
40
38
  blockedToolkits: normalizeToolkitList(Array.isArray(source.blockedToolkits) ? source.blockedToolkits : undefined),
41
39
  readOnlyMode,
@@ -57,10 +55,6 @@ export const composioConfigUiHints = {
57
55
  help: "Composio API key from platform.composio.dev/settings",
58
56
  sensitive: true,
59
57
  },
60
- defaultUserId: {
61
- label: "Default User ID",
62
- help: "Default user ID for session scoping (optional)",
63
- },
64
58
  allowedToolkits: {
65
59
  label: "Allowed Toolkits",
66
60
  help: "Restrict to specific toolkits (e.g., github, gmail)",
package/dist/index.d.ts CHANGED
@@ -35,10 +35,6 @@ declare const composioPlugin: {
35
35
  help: string;
36
36
  sensitive: boolean;
37
37
  };
38
- defaultUserId: {
39
- label: string;
40
- help: string;
41
- };
42
38
  allowedToolkits: {
43
39
  label: string;
44
40
  help: string;
@@ -1,15 +1,24 @@
1
1
  import type { ComposioClient } from "../client.js";
2
2
  import type { ComposioConfig } from "../types.js";
3
- /**
4
- * Tool parameters for composio_manage_connections
5
- */
6
- export declare const ComposioManageConnectionsToolSchema: import("@sinclair/typebox").TObject<{
7
- action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">, import("@sinclair/typebox").TLiteral<"accounts">]>;
3
+ export declare const ComposioManageConnectionsToolSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
4
+ action: import("@sinclair/typebox").TLiteral<"list">;
5
+ user_id: import("@sinclair/typebox").TString;
6
+ }>, import("@sinclair/typebox").TObject<{
7
+ action: import("@sinclair/typebox").TLiteral<"create">;
8
+ toolkit: import("@sinclair/typebox").TString;
9
+ user_id: import("@sinclair/typebox").TString;
10
+ }>, import("@sinclair/typebox").TObject<{
11
+ action: import("@sinclair/typebox").TLiteral<"status">;
12
+ toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
13
+ toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
14
+ user_id: import("@sinclair/typebox").TString;
15
+ }>, import("@sinclair/typebox").TObject<{
16
+ action: import("@sinclair/typebox").TLiteral<"accounts">;
8
17
  toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
18
  toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
10
19
  user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
11
20
  statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
12
- }>;
21
+ }>]>;
13
22
  /**
14
23
  * Create the composio_manage_connections tool
15
24
  */
@@ -17,14 +26,35 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
17
26
  name: string;
18
27
  label: string;
19
28
  description: string;
20
- parameters: import("@sinclair/typebox").TObject<{
21
- action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">, import("@sinclair/typebox").TLiteral<"accounts">]>;
29
+ parameters: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
30
+ action: import("@sinclair/typebox").TLiteral<"list">;
31
+ user_id: import("@sinclair/typebox").TString;
32
+ }>, import("@sinclair/typebox").TObject<{
33
+ action: import("@sinclair/typebox").TLiteral<"create">;
34
+ toolkit: import("@sinclair/typebox").TString;
35
+ user_id: import("@sinclair/typebox").TString;
36
+ }>, import("@sinclair/typebox").TObject<{
37
+ action: import("@sinclair/typebox").TLiteral<"status">;
38
+ toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
39
+ toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
40
+ user_id: import("@sinclair/typebox").TString;
41
+ }>, import("@sinclair/typebox").TObject<{
42
+ action: import("@sinclair/typebox").TLiteral<"accounts">;
22
43
  toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
23
44
  toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
24
45
  user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
25
46
  statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
26
- }>;
47
+ }>]>;
27
48
  execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
49
+ content: {
50
+ type: string;
51
+ text: string;
52
+ }[];
53
+ details: {
54
+ action: string;
55
+ error: string;
56
+ };
57
+ } | {
28
58
  content: {
29
59
  type: string;
30
60
  text: string;
@@ -93,22 +123,12 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
93
123
  message: string;
94
124
  }[] | undefined;
95
125
  action: string;
96
- checked_user_id: string | undefined;
97
- user_id_explicit: boolean;
126
+ checked_user_id: string;
98
127
  count: number;
99
128
  connections: {
100
129
  toolkit: string;
101
130
  connected: boolean;
102
131
  }[];
103
132
  };
104
- } | {
105
- content: {
106
- type: string;
107
- text: string;
108
- }[];
109
- details: {
110
- action: string;
111
- error: string;
112
- };
113
133
  }>;
114
134
  };
@@ -2,23 +2,48 @@ import { Type } from "@sinclair/typebox";
2
2
  /**
3
3
  * Tool parameters for composio_manage_connections
4
4
  */
5
- export const ComposioManageConnectionsToolSchema = Type.Object({
6
- action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list"), Type.Literal("accounts")], {
7
- description: "Action to perform: 'status' to check connections, 'create' to initiate auth, 'list' to list toolkits, 'accounts' to inspect connected accounts",
8
- }),
9
- toolkit: Type.Optional(Type.String({
10
- description: "Toolkit name for 'status' or 'create' actions (e.g., 'github', 'gmail')",
11
- })),
12
- toolkits: Type.Optional(Type.Array(Type.String(), {
13
- description: "Multiple toolkits to check status for",
14
- })),
15
- user_id: Type.Optional(Type.String({
16
- description: "User ID for session scoping. Strongly recommended to avoid checking the wrong scope.",
17
- })),
18
- statuses: Type.Optional(Type.Array(Type.String(), {
19
- description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
20
- })),
5
+ const ActionDescription = "Action to perform: 'status' to check connections, 'create' to initiate auth, " +
6
+ "'list' to list toolkits, 'accounts' to inspect connected accounts";
7
+ const UserIdRequiredField = Type.String({
8
+ description: "Required user ID for session scoping.",
21
9
  });
10
+ const UserIdOptionalField = Type.Optional(Type.String({
11
+ description: "Optional user ID filter for accounts lookup.",
12
+ }));
13
+ const ToolkitField = Type.Optional(Type.String({
14
+ description: "Toolkit name (e.g., 'github', 'gmail')",
15
+ }));
16
+ const ToolkitsField = Type.Optional(Type.Array(Type.String(), {
17
+ description: "Multiple toolkits to check status for",
18
+ }));
19
+ export const ComposioManageConnectionsToolSchema = Type.Union([
20
+ Type.Object({
21
+ action: Type.Literal("list", { description: ActionDescription }),
22
+ user_id: UserIdRequiredField,
23
+ }),
24
+ Type.Object({
25
+ action: Type.Literal("create", { description: ActionDescription }),
26
+ toolkit: Type.String({
27
+ description: "Toolkit name for 'create' action (e.g., 'github', 'gmail')",
28
+ }),
29
+ user_id: UserIdRequiredField,
30
+ }),
31
+ Type.Object({
32
+ action: Type.Literal("status", { description: ActionDescription }),
33
+ toolkit: ToolkitField,
34
+ toolkits: ToolkitsField,
35
+ user_id: UserIdRequiredField,
36
+ }),
37
+ Type.Object({
38
+ action: Type.Literal("accounts", { description: ActionDescription }),
39
+ toolkit: ToolkitField,
40
+ toolkits: ToolkitsField,
41
+ user_id: UserIdOptionalField,
42
+ statuses: Type.Optional(Type.Array(Type.String(), {
43
+ description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
44
+ })),
45
+ }),
46
+ ]);
22
47
  /**
23
48
  * Create the composio_manage_connections tool
24
49
  */
@@ -33,8 +58,18 @@ export function createComposioConnectionsTool(client, _config) {
33
58
  parameters: ComposioManageConnectionsToolSchema,
34
59
  async execute(_toolCallId, params) {
35
60
  const action = String(params.action || "status");
36
- const userId = typeof params.user_id === "string" ? params.user_id : undefined;
37
- const userIdWasExplicit = typeof params.user_id === "string" && params.user_id.trim().length > 0;
61
+ const userId = typeof params.user_id === "string" ? params.user_id.trim() : "";
62
+ const requiresUserId = action === "list" || action === "status" || action === "create";
63
+ if (requiresUserId && !userId) {
64
+ const errorResponse = {
65
+ action,
66
+ error: "user_id is required for this action",
67
+ };
68
+ return {
69
+ content: [{ type: "text", text: JSON.stringify(errorResponse, null, 2) }],
70
+ details: errorResponse,
71
+ };
72
+ }
38
73
  try {
39
74
  switch (action) {
40
75
  case "list": {
@@ -122,24 +157,21 @@ export function createComposioConnectionsTool(client, _config) {
122
157
  const statuses = await client.getConnectionStatus(toolkitsToCheck, userId);
123
158
  const disconnectedToolkits = statuses.filter((s) => !s.connected).map((s) => s.toolkit);
124
159
  const hints = [];
125
- if (!userIdWasExplicit) {
126
- for (const toolkit of disconnectedToolkits) {
127
- const activeUserIds = await client.findActiveUserIdsForToolkit(toolkit);
128
- if (activeUserIds.length === 0)
129
- continue;
130
- hints.push({
131
- toolkit,
132
- connected_user_ids: activeUserIds,
133
- message: `No user_id was provided, so status checked the default scope. ` +
134
- `'${toolkit}' has ACTIVE accounts under: ${activeUserIds.join(", ")}. ` +
135
- "Pass user_id explicitly for deterministic results.",
136
- });
137
- }
160
+ for (const toolkit of disconnectedToolkits) {
161
+ const activeUserIds = await client.findActiveUserIdsForToolkit(toolkit);
162
+ const otherUserIds = activeUserIds.filter((uid) => uid !== userId);
163
+ if (otherUserIds.length === 0)
164
+ continue;
165
+ hints.push({
166
+ toolkit,
167
+ connected_user_ids: otherUserIds,
168
+ message: `'${toolkit}' has ACTIVE accounts under other user_id(s): ${otherUserIds.join(", ")}. ` +
169
+ "Use a matching user_id to check that scope.",
170
+ });
138
171
  }
139
172
  const response = {
140
173
  action: "status",
141
174
  checked_user_id: statuses[0]?.userId,
142
- user_id_explicit: userIdWasExplicit,
143
175
  count: statuses.length,
144
176
  connections: statuses.map((s) => ({
145
177
  toolkit: s.toolkit,
@@ -6,7 +6,7 @@ import type { ComposioConfig } from "../types.js";
6
6
  export declare const ComposioExecuteToolSchema: import("@sinclair/typebox").TObject<{
7
7
  tool_slug: import("@sinclair/typebox").TString;
8
8
  arguments: import("@sinclair/typebox").TUnknown;
9
- user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
+ user_id: import("@sinclair/typebox").TString;
10
10
  connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
11
11
  }>;
12
12
  /**
@@ -19,7 +19,7 @@ export declare function createComposioExecuteTool(client: ComposioClient, _confi
19
19
  parameters: import("@sinclair/typebox").TObject<{
20
20
  tool_slug: import("@sinclair/typebox").TString;
21
21
  arguments: import("@sinclair/typebox").TUnknown;
22
- user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
22
+ user_id: import("@sinclair/typebox").TString;
23
23
  connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
24
24
  }>;
25
25
  execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
@@ -9,9 +9,9 @@ export const ComposioExecuteToolSchema = Type.Object({
9
9
  arguments: Type.Unknown({
10
10
  description: "Tool arguments matching the tool's parameter schema",
11
11
  }),
12
- user_id: Type.Optional(Type.String({
13
- description: "User ID for session scoping. Strongly recommended to avoid executing in the wrong scope.",
14
- })),
12
+ user_id: Type.String({
13
+ description: "Required user ID for session scoping.",
14
+ }),
15
15
  connected_account_id: Type.Optional(Type.String({
16
16
  description: "Optional connected account ID to pin execution to a specific account when multiple are connected",
17
17
  })),
@@ -45,7 +45,13 @@ export function createComposioExecuteTool(client, _config) {
45
45
  const args = rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)
46
46
  ? rawArgs
47
47
  : {};
48
- const userId = typeof params.user_id === "string" ? params.user_id : undefined;
48
+ const userId = typeof params.user_id === "string" ? params.user_id.trim() : "";
49
+ if (!userId) {
50
+ return {
51
+ content: [{ type: "text", text: JSON.stringify({ error: "user_id is required" }, null, 2) }],
52
+ details: { error: "user_id is required" },
53
+ };
54
+ }
49
55
  const connectedAccountId = typeof params.connected_account_id === "string" ? params.connected_account_id : undefined;
50
56
  try {
51
57
  const result = await client.executeTool(toolSlug, args, userId, connectedAccountId);
@@ -7,7 +7,7 @@ export declare const ComposioSearchToolSchema: import("@sinclair/typebox").TObje
7
7
  query: import("@sinclair/typebox").TString;
8
8
  toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
9
9
  limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
10
- user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
10
+ user_id: import("@sinclair/typebox").TString;
11
11
  }>;
12
12
  /**
13
13
  * Create the composio_search_tools tool
@@ -20,7 +20,7 @@ export declare function createComposioSearchTool(client: ComposioClient, _config
20
20
  query: import("@sinclair/typebox").TString;
21
21
  toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
22
22
  limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
23
- user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
23
+ user_id: import("@sinclair/typebox").TString;
24
24
  }>;
25
25
  execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
26
26
  content: {
@@ -12,9 +12,9 @@ export const ComposioSearchToolSchema = Type.Object({
12
12
  limit: Type.Optional(Type.Number({
13
13
  description: "Maximum number of results to return (default: 10, max: 50)",
14
14
  })),
15
- user_id: Type.Optional(Type.String({
16
- description: "User ID for session scoping (uses default if not provided)",
17
- })),
15
+ user_id: Type.String({
16
+ description: "Required user ID for session scoping.",
17
+ }),
18
18
  });
19
19
  /**
20
20
  * Create the composio_search_tools tool
@@ -39,7 +39,13 @@ export function createComposioSearchTool(client, _config) {
39
39
  ? params.toolkits.filter((t) => typeof t === "string")
40
40
  : undefined;
41
41
  const limit = Math.min(typeof params.limit === "number" && params.limit > 0 ? params.limit : 10, 50);
42
- const userId = typeof params.user_id === "string" ? params.user_id : undefined;
42
+ const userId = typeof params.user_id === "string" ? params.user_id.trim() : "";
43
+ if (!userId) {
44
+ return {
45
+ content: [{ type: "text", text: JSON.stringify({ error: "user_id is required" }, null, 2) }],
46
+ details: { error: "user_id is required" },
47
+ };
48
+ }
43
49
  try {
44
50
  const results = await client.searchTools(query, { toolkits, limit, userId });
45
51
  const response = {
package/dist/types.d.ts CHANGED
@@ -5,7 +5,6 @@ export type ComposioSessionTag = "readOnlyHint" | "destructiveHint" | "idempoten
5
5
  export interface ComposioConfig {
6
6
  enabled: boolean;
7
7
  apiKey?: string;
8
- defaultUserId?: string;
9
8
  allowedToolkits?: string[];
10
9
  blockedToolkits?: string[];
11
10
  readOnlyMode?: boolean;
@@ -28,7 +27,7 @@ export interface ToolExecutionResult {
28
27
  export interface ConnectionStatus {
29
28
  toolkit: string;
30
29
  connected: boolean;
31
- userId?: string;
30
+ userId: string;
32
31
  authUrl?: string;
33
32
  }
34
33
  export interface ConnectedAccountSummary {
@@ -14,10 +14,6 @@
14
14
  "type": "string",
15
15
  "description": "Composio API key (or set COMPOSIO_API_KEY env var)"
16
16
  },
17
- "defaultUserId": {
18
- "type": "string",
19
- "description": "Default user ID for session scoping"
20
- },
21
17
  "allowedToolkits": {
22
18
  "type": "array",
23
19
  "items": { "type": "string" },
@@ -59,10 +55,6 @@
59
55
  "help": "Composio API key from platform.composio.dev/settings",
60
56
  "sensitive": true
61
57
  },
62
- "defaultUserId": {
63
- "label": "Default User ID",
64
- "help": "Default user ID for session scoping (optional)"
65
- },
66
58
  "allowedToolkits": {
67
59
  "label": "Allowed Toolkits",
68
60
  "help": "Restrict to specific toolkits (e.g., github, gmail)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customclaw/composio",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "description": "Composio Tool Router plugin for OpenClaw — access 1000+ third-party integrations",
6
6
  "main": "dist/index.js",