@customclaw/composio 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ import { composioPluginConfigSchema, parseComposioConfig } from "./config.js";
2
+ import { createComposioClient } from "./client.js";
3
+ import { createComposioSearchTool } from "./tools/search.js";
4
+ import { createComposioExecuteTool } from "./tools/execute.js";
5
+ import { createComposioMultiExecuteTool } from "./tools/multi-execute.js";
6
+ import { createComposioConnectionsTool } from "./tools/connections.js";
7
+ import { createComposioWorkbenchTool } from "./tools/workbench.js";
8
+ import { createCompositoBashTool } from "./tools/bash.js";
9
+ import { registerComposioCli } from "./cli.js";
10
+ /**
11
+ * Composio Tool Router Plugin for OpenClaw
12
+ *
13
+ * Provides access to 1000+ third-party tools through Composio's unified interface.
14
+ * Tools include: Gmail, Slack, GitHub, Notion, Linear, Jira, and many more.
15
+ *
16
+ * Configuration (in openclaw config):
17
+ * ```json
18
+ * {
19
+ * "plugins": {
20
+ * "composio": {
21
+ * "enabled": true,
22
+ * "apiKey": "your-composio-api-key"
23
+ * }
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * Or set COMPOSIO_API_KEY environment variable.
29
+ */
30
+ const composioPlugin = {
31
+ id: "composio",
32
+ name: "Composio Tool Router",
33
+ description: "Access 1000+ third-party tools via Composio Tool Router. " +
34
+ "Search, authenticate, and execute tools for Gmail, Slack, GitHub, Notion, and more.",
35
+ configSchema: composioPluginConfigSchema,
36
+ register(api) {
37
+ const config = parseComposioConfig(api.pluginConfig);
38
+ if (!config.enabled) {
39
+ api.logger.debug("[composio] Plugin disabled in config");
40
+ return;
41
+ }
42
+ if (!config.apiKey) {
43
+ api.logger.warn("[composio] No API key configured. Set COMPOSIO_API_KEY env var or plugins.composio.apiKey in config.");
44
+ return;
45
+ }
46
+ let client = null;
47
+ const ensureClient = () => {
48
+ if (!client) {
49
+ client = createComposioClient(config);
50
+ }
51
+ return client;
52
+ };
53
+ // Register tools (lazily create client on first use)
54
+ api.registerTool({
55
+ ...createComposioSearchTool(ensureClient(), config),
56
+ execute: async (toolCallId, params) => {
57
+ return createComposioSearchTool(ensureClient(), config).execute(toolCallId, params);
58
+ },
59
+ });
60
+ api.registerTool({
61
+ ...createComposioExecuteTool(ensureClient(), config),
62
+ execute: async (toolCallId, params) => {
63
+ return createComposioExecuteTool(ensureClient(), config).execute(toolCallId, params);
64
+ },
65
+ });
66
+ api.registerTool({
67
+ ...createComposioMultiExecuteTool(ensureClient(), config),
68
+ execute: async (toolCallId, params) => {
69
+ return createComposioMultiExecuteTool(ensureClient(), config).execute(toolCallId, params);
70
+ },
71
+ });
72
+ api.registerTool({
73
+ ...createComposioConnectionsTool(ensureClient(), config),
74
+ execute: async (toolCallId, params) => {
75
+ return createComposioConnectionsTool(ensureClient(), config).execute(toolCallId, params);
76
+ },
77
+ });
78
+ api.registerTool({
79
+ ...createComposioWorkbenchTool(ensureClient(), config),
80
+ execute: async (toolCallId, params) => {
81
+ return createComposioWorkbenchTool(ensureClient(), config).execute(toolCallId, params);
82
+ },
83
+ });
84
+ api.registerTool({
85
+ ...createCompositoBashTool(ensureClient(), config),
86
+ execute: async (toolCallId, params) => {
87
+ return createCompositoBashTool(ensureClient(), config).execute(toolCallId, params);
88
+ },
89
+ });
90
+ // Register CLI commands
91
+ api.registerCli(({ program }) => registerComposioCli({
92
+ program,
93
+ client: ensureClient(),
94
+ config,
95
+ logger: api.logger,
96
+ }), { commands: ["composio"] });
97
+ // Inject agent instructions via before_agent_start hook
98
+ api.on("before_agent_start", () => {
99
+ return {
100
+ prependContext: `<composio-tools>
101
+ You have access to Composio Tool Router, which provides 1000+ third-party integrations (Gmail, Slack, GitHub, Notion, Linear, Jira, HubSpot, Google Drive, etc.).
102
+
103
+ ## How to use Composio tools
104
+
105
+ 1. **Search first**: Use \`composio_search_tools\` to find tools matching the user's task. Search by describing what you want to do (e.g., "send email", "create github issue").
106
+
107
+ 2. **Check connections**: Before executing, use \`composio_manage_connections\` with action="status" to verify the required toolkit is connected. If not connected, use action="create" to generate an auth URL for the user.
108
+
109
+ 3. **Execute tools**: Use \`composio_execute_tool\` with the tool_slug from search results and arguments matching the tool's schema. For multiple operations, use \`composio_multi_execute\` to run up to 50 tools in parallel.
110
+
111
+ 4. **Remote processing**: For large responses or bulk operations, use \`composio_workbench\` to run Python code in a remote Jupyter sandbox with helpers like run_composio_tool(), invoke_llm(), etc. Use \`composio_bash\` for shell commands in the remote sandbox.
112
+
113
+ ## Important notes
114
+ - Tool slugs are uppercase (e.g., GMAIL_SEND_EMAIL, GITHUB_CREATE_ISSUE)
115
+ - Always use exact tool_slug values from search results - do not invent slugs
116
+ - Check the parameters schema from search results before executing
117
+ - If a tool fails with auth errors, prompt the user to connect the toolkit
118
+ - Use workbench/bash tools when processing data stored in remote files or scripting bulk operations
119
+ </composio-tools>`,
120
+ };
121
+ });
122
+ api.logger.info("[composio] Plugin registered with 6 tools and CLI commands");
123
+ },
124
+ };
125
+ export default composioPlugin;
@@ -0,0 +1,42 @@
1
+ import type { ComposioClient } from "../client.js";
2
+ import type { ComposioConfig } from "../types.js";
3
+ /**
4
+ * Tool parameters for composio_bash
5
+ */
6
+ export declare const CompositoBashSchema: import("@sinclair/typebox").TObject<{
7
+ command: import("@sinclair/typebox").TString;
8
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
+ }>;
10
+ /**
11
+ * Create the composio_bash tool
12
+ */
13
+ export declare function createCompositoBashTool(client: ComposioClient, _config: ComposioConfig): {
14
+ name: string;
15
+ label: string;
16
+ description: string;
17
+ parameters: import("@sinclair/typebox").TObject<{
18
+ command: import("@sinclair/typebox").TString;
19
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
20
+ }>;
21
+ execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
22
+ content: {
23
+ type: string;
24
+ text: string;
25
+ }[];
26
+ details: {
27
+ error: string;
28
+ };
29
+ } | {
30
+ content: {
31
+ type: string;
32
+ text: string;
33
+ }[];
34
+ details: {
35
+ output: unknown;
36
+ success: boolean;
37
+ } | {
38
+ error: string | undefined;
39
+ success: boolean;
40
+ };
41
+ }>;
42
+ };
@@ -0,0 +1,59 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ /**
3
+ * Tool parameters for composio_bash
4
+ */
5
+ export const CompositoBashSchema = Type.Object({
6
+ command: Type.String({
7
+ description: "Bash command to execute in the remote sandbox. " +
8
+ "Use for file operations, data processing with jq/awk/sed/grep, " +
9
+ "or handling large tool responses saved to remote files. " +
10
+ "Commands run from /home/user directory by default.",
11
+ }),
12
+ user_id: Type.Optional(Type.String({
13
+ description: "User ID for session scoping (uses default if not provided)",
14
+ })),
15
+ });
16
+ /**
17
+ * Create the composio_bash tool
18
+ */
19
+ export function createCompositoBashTool(client, _config) {
20
+ return {
21
+ name: "composio_bash",
22
+ label: "Composio Remote Bash",
23
+ description: "Execute bash commands in a remote sandbox for file operations, data processing, " +
24
+ "and system tasks. Essential for handling large tool responses saved to remote files. " +
25
+ "Use shell tools like jq, awk, sed, grep for data extraction.",
26
+ parameters: CompositoBashSchema,
27
+ async execute(_toolCallId, params) {
28
+ const command = String(params.command || "").trim();
29
+ if (!command) {
30
+ return {
31
+ content: [{ type: "text", text: JSON.stringify({ error: "command is required" }, null, 2) }],
32
+ details: { error: "command is required" },
33
+ };
34
+ }
35
+ const userId = typeof params.user_id === "string" ? params.user_id : undefined;
36
+ try {
37
+ const result = await client.executeBash(command, userId);
38
+ const response = {
39
+ success: result.success,
40
+ ...(result.success ? { output: result.output } : { error: result.error }),
41
+ };
42
+ return {
43
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
44
+ details: response,
45
+ };
46
+ }
47
+ catch (err) {
48
+ const errorResponse = {
49
+ success: false,
50
+ error: err instanceof Error ? err.message : String(err),
51
+ };
52
+ return {
53
+ content: [{ type: "text", text: JSON.stringify(errorResponse, null, 2) }],
54
+ details: errorResponse,
55
+ };
56
+ }
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,89 @@
1
+ import type { ComposioClient } from "../client.js";
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">]>;
8
+ toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
+ toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
10
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
11
+ }>;
12
+ /**
13
+ * Create the composio_manage_connections tool
14
+ */
15
+ export declare function createComposioConnectionsTool(client: ComposioClient, _config: ComposioConfig): {
16
+ name: string;
17
+ label: string;
18
+ description: string;
19
+ parameters: import("@sinclair/typebox").TObject<{
20
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">]>;
21
+ toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
22
+ toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
23
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
24
+ }>;
25
+ execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
26
+ content: {
27
+ type: string;
28
+ text: string;
29
+ }[];
30
+ details: {
31
+ action: string;
32
+ count: number;
33
+ toolkits: string[];
34
+ };
35
+ } | {
36
+ content: {
37
+ type: string;
38
+ text: string;
39
+ }[];
40
+ details: {
41
+ error: string;
42
+ action?: undefined;
43
+ toolkit?: undefined;
44
+ };
45
+ } | {
46
+ content: {
47
+ type: string;
48
+ text: string;
49
+ }[];
50
+ details: {
51
+ action: string;
52
+ toolkit: string;
53
+ error: string;
54
+ };
55
+ } | {
56
+ content: {
57
+ type: string;
58
+ text: string;
59
+ }[];
60
+ details: {
61
+ action: string;
62
+ toolkit: string;
63
+ authUrl: string;
64
+ instructions: string;
65
+ };
66
+ } | {
67
+ content: {
68
+ type: string;
69
+ text: string;
70
+ }[];
71
+ details: {
72
+ action: string;
73
+ count: number;
74
+ connections: {
75
+ toolkit: string;
76
+ connected: boolean;
77
+ }[];
78
+ };
79
+ } | {
80
+ content: {
81
+ type: string;
82
+ text: string;
83
+ }[];
84
+ details: {
85
+ action: string;
86
+ error: string;
87
+ };
88
+ }>;
89
+ };
@@ -0,0 +1,112 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ /**
3
+ * Tool parameters for composio_manage_connections
4
+ */
5
+ export const ComposioManageConnectionsToolSchema = Type.Object({
6
+ action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list")], {
7
+ description: "Action to perform: 'status' to check connections, 'create' to initiate auth, 'list' to list toolkits",
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 (uses default if not provided)",
17
+ })),
18
+ });
19
+ /**
20
+ * Create the composio_manage_connections tool
21
+ */
22
+ export function createComposioConnectionsTool(client, _config) {
23
+ return {
24
+ name: "composio_manage_connections",
25
+ label: "Composio Manage Connections",
26
+ description: "Manage Composio toolkit connections. Check connection status, create new auth links, " +
27
+ "or list available toolkits. Use this before executing tools to ensure authentication.",
28
+ parameters: ComposioManageConnectionsToolSchema,
29
+ async execute(_toolCallId, params) {
30
+ const action = String(params.action || "status");
31
+ const userId = typeof params.user_id === "string" ? params.user_id : undefined;
32
+ try {
33
+ switch (action) {
34
+ case "list": {
35
+ const toolkits = await client.listToolkits();
36
+ const response = {
37
+ action: "list",
38
+ count: toolkits.length,
39
+ toolkits,
40
+ };
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
43
+ details: response,
44
+ };
45
+ }
46
+ case "create": {
47
+ const toolkit = String(params.toolkit || "").trim();
48
+ if (!toolkit) {
49
+ return {
50
+ content: [
51
+ { type: "text", text: JSON.stringify({ error: "toolkit is required for 'create' action" }, null, 2) },
52
+ ],
53
+ details: { error: "toolkit is required for 'create' action" },
54
+ };
55
+ }
56
+ const result = await client.createConnection(toolkit, userId);
57
+ if ("error" in result) {
58
+ return {
59
+ content: [{ type: "text", text: JSON.stringify({ action: "create", toolkit, error: result.error }, null, 2) }],
60
+ details: { action: "create", toolkit, error: result.error },
61
+ };
62
+ }
63
+ const response = {
64
+ action: "create",
65
+ toolkit,
66
+ authUrl: result.authUrl,
67
+ instructions: `Open the auth URL to connect ${toolkit}. After authentication, the connection will be active.`,
68
+ };
69
+ return {
70
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
71
+ details: response,
72
+ };
73
+ }
74
+ case "status":
75
+ default: {
76
+ // Collect toolkits to check
77
+ let toolkitsToCheck;
78
+ if (typeof params.toolkit === "string" && params.toolkit.trim()) {
79
+ toolkitsToCheck = [params.toolkit.trim()];
80
+ }
81
+ else if (Array.isArray(params.toolkits)) {
82
+ toolkitsToCheck = params.toolkits.filter((t) => typeof t === "string" && t.trim() !== "");
83
+ }
84
+ const statuses = await client.getConnectionStatus(toolkitsToCheck, userId);
85
+ const response = {
86
+ action: "status",
87
+ count: statuses.length,
88
+ connections: statuses.map((s) => ({
89
+ toolkit: s.toolkit,
90
+ connected: s.connected,
91
+ })),
92
+ };
93
+ return {
94
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
95
+ details: response,
96
+ };
97
+ }
98
+ }
99
+ }
100
+ catch (err) {
101
+ const errorResponse = {
102
+ action,
103
+ error: err instanceof Error ? err.message : String(err),
104
+ };
105
+ return {
106
+ content: [{ type: "text", text: JSON.stringify(errorResponse, null, 2) }],
107
+ details: errorResponse,
108
+ };
109
+ }
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,46 @@
1
+ import type { ComposioClient } from "../client.js";
2
+ import type { ComposioConfig } from "../types.js";
3
+ /**
4
+ * Tool parameters for composio_execute_tool
5
+ */
6
+ export declare const ComposioExecuteToolSchema: import("@sinclair/typebox").TObject<{
7
+ tool_slug: import("@sinclair/typebox").TString;
8
+ arguments: import("@sinclair/typebox").TUnknown;
9
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
10
+ }>;
11
+ /**
12
+ * Create the composio_execute_tool tool
13
+ */
14
+ export declare function createComposioExecuteTool(client: ComposioClient, _config: ComposioConfig): {
15
+ name: string;
16
+ label: string;
17
+ description: string;
18
+ parameters: import("@sinclair/typebox").TObject<{
19
+ tool_slug: import("@sinclair/typebox").TString;
20
+ arguments: import("@sinclair/typebox").TUnknown;
21
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
22
+ }>;
23
+ execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
24
+ content: {
25
+ type: string;
26
+ text: string;
27
+ }[];
28
+ details: {
29
+ error: string;
30
+ };
31
+ } | {
32
+ content: {
33
+ type: string;
34
+ text: string;
35
+ }[];
36
+ details: {
37
+ data: unknown;
38
+ tool_slug: string;
39
+ success: boolean;
40
+ } | {
41
+ error: string | undefined;
42
+ tool_slug: string;
43
+ success: boolean;
44
+ };
45
+ }>;
46
+ };
@@ -0,0 +1,63 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ /**
3
+ * Tool parameters for composio_execute_tool
4
+ */
5
+ export const ComposioExecuteToolSchema = Type.Object({
6
+ tool_slug: Type.String({
7
+ description: "Tool slug from composio_search_tools results (e.g., 'GMAIL_SEND_EMAIL')",
8
+ }),
9
+ arguments: Type.Unknown({
10
+ description: "Tool arguments matching the tool's parameter schema",
11
+ }),
12
+ user_id: Type.Optional(Type.String({
13
+ description: "User ID for session scoping (uses default if not provided)",
14
+ })),
15
+ });
16
+ /**
17
+ * Create the composio_execute_tool tool
18
+ */
19
+ export function createComposioExecuteTool(client, _config) {
20
+ return {
21
+ name: "composio_execute_tool",
22
+ label: "Composio Execute Tool",
23
+ description: "Execute a single Composio tool. Use composio_search_tools first to find the tool slug " +
24
+ "and parameter schema. The tool must be connected (use composio_manage_connections to check).",
25
+ parameters: ComposioExecuteToolSchema,
26
+ async execute(_toolCallId, params) {
27
+ const toolSlug = String(params.tool_slug || "").trim();
28
+ if (!toolSlug) {
29
+ return {
30
+ content: [{ type: "text", text: JSON.stringify({ error: "tool_slug is required" }, null, 2) }],
31
+ details: { error: "tool_slug is required" },
32
+ };
33
+ }
34
+ const args = params.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments)
35
+ ? params.arguments
36
+ : {};
37
+ const userId = typeof params.user_id === "string" ? params.user_id : undefined;
38
+ try {
39
+ const result = await client.executeTool(toolSlug, args, userId);
40
+ const response = {
41
+ tool_slug: toolSlug,
42
+ success: result.success,
43
+ ...(result.success ? { data: result.data } : { error: result.error }),
44
+ };
45
+ return {
46
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
47
+ details: response,
48
+ };
49
+ }
50
+ catch (err) {
51
+ const errorResponse = {
52
+ tool_slug: toolSlug,
53
+ success: false,
54
+ error: err instanceof Error ? err.message : String(err),
55
+ };
56
+ return {
57
+ content: [{ type: "text", text: JSON.stringify(errorResponse, null, 2) }],
58
+ details: errorResponse,
59
+ };
60
+ }
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,52 @@
1
+ import type { ComposioClient } from "../client.js";
2
+ import type { ComposioConfig } from "../types.js";
3
+ /**
4
+ * Tool parameters for composio_multi_execute
5
+ */
6
+ export declare const ComposioMultiExecuteToolSchema: import("@sinclair/typebox").TObject<{
7
+ executions: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
8
+ tool_slug: import("@sinclair/typebox").TString;
9
+ arguments: import("@sinclair/typebox").TUnknown;
10
+ }>>;
11
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
12
+ }>;
13
+ /**
14
+ * Create the composio_multi_execute tool
15
+ */
16
+ export declare function createComposioMultiExecuteTool(client: ComposioClient, _config: ComposioConfig): {
17
+ name: string;
18
+ label: string;
19
+ description: string;
20
+ parameters: import("@sinclair/typebox").TObject<{
21
+ executions: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
22
+ tool_slug: import("@sinclair/typebox").TString;
23
+ arguments: import("@sinclair/typebox").TUnknown;
24
+ }>>;
25
+ user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
26
+ }>;
27
+ execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
28
+ content: {
29
+ type: string;
30
+ text: string;
31
+ }[];
32
+ details: {
33
+ error: string;
34
+ };
35
+ } | {
36
+ content: {
37
+ type: string;
38
+ text: string;
39
+ }[];
40
+ details: {
41
+ total: number;
42
+ succeeded: number;
43
+ failed: number;
44
+ results: {
45
+ tool_slug: string;
46
+ success: boolean;
47
+ data?: unknown;
48
+ error?: string;
49
+ }[];
50
+ };
51
+ }>;
52
+ };
@@ -0,0 +1,87 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ /**
3
+ * Tool parameters for composio_multi_execute
4
+ */
5
+ export const ComposioMultiExecuteToolSchema = Type.Object({
6
+ executions: Type.Array(Type.Object({
7
+ tool_slug: Type.String({
8
+ description: "Tool slug from composio_search_tools results",
9
+ }),
10
+ arguments: Type.Unknown({
11
+ description: "Tool arguments matching the tool's parameter schema",
12
+ }),
13
+ }), {
14
+ description: "Array of tool executions to run in parallel (max 50)",
15
+ maxItems: 50,
16
+ }),
17
+ user_id: Type.Optional(Type.String({
18
+ description: "User ID for session scoping (uses default if not provided)",
19
+ })),
20
+ });
21
+ /**
22
+ * Create the composio_multi_execute tool
23
+ */
24
+ export function createComposioMultiExecuteTool(client, _config) {
25
+ return {
26
+ name: "composio_multi_execute",
27
+ label: "Composio Multi Execute",
28
+ description: "Execute multiple Composio tools in parallel (up to 50). Use composio_search_tools first " +
29
+ "to find tool slugs and parameter schemas. All tools must be connected.",
30
+ parameters: ComposioMultiExecuteToolSchema,
31
+ async execute(_toolCallId, params) {
32
+ const executions = Array.isArray(params.executions) ? params.executions : [];
33
+ if (executions.length === 0) {
34
+ return {
35
+ content: [
36
+ { type: "text", text: JSON.stringify({ error: "executions array is required and cannot be empty" }, null, 2) },
37
+ ],
38
+ details: { error: "executions array is required and cannot be empty" },
39
+ };
40
+ }
41
+ // Validate and normalize executions
42
+ const normalizedExecutions = executions
43
+ .slice(0, 50)
44
+ .filter((exec) => exec &&
45
+ typeof exec === "object" &&
46
+ typeof exec.tool_slug === "string" &&
47
+ exec.tool_slug.trim() !== "")
48
+ .map((exec) => ({
49
+ tool_slug: exec.tool_slug.trim(),
50
+ arguments: exec.arguments && typeof exec.arguments === "object" && !Array.isArray(exec.arguments)
51
+ ? exec.arguments
52
+ : {},
53
+ }));
54
+ if (normalizedExecutions.length === 0) {
55
+ return {
56
+ content: [
57
+ { type: "text", text: JSON.stringify({ error: "No valid executions provided" }, null, 2) },
58
+ ],
59
+ details: { error: "No valid executions provided" },
60
+ };
61
+ }
62
+ const userId = typeof params.user_id === "string" ? params.user_id : undefined;
63
+ try {
64
+ const result = await client.multiExecute(normalizedExecutions, userId);
65
+ const response = {
66
+ total: normalizedExecutions.length,
67
+ succeeded: result.results.filter((r) => r.success).length,
68
+ failed: result.results.filter((r) => !r.success).length,
69
+ results: result.results,
70
+ };
71
+ return {
72
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
73
+ details: response,
74
+ };
75
+ }
76
+ catch (err) {
77
+ const errorResponse = {
78
+ error: err instanceof Error ? err.message : String(err),
79
+ };
80
+ return {
81
+ content: [{ type: "text", text: JSON.stringify(errorResponse, null, 2) }],
82
+ details: errorResponse,
83
+ };
84
+ }
85
+ },
86
+ };
87
+ }