composio-claude-agent-sdk 0.10.5__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ from composio_claude_agent_sdk.provider import ClaudeAgentSDKProvider
2
+
3
+ __all__ = ("ClaudeAgentSDKProvider",)
@@ -0,0 +1,187 @@
1
+ """
2
+ Claude Agent SDK Provider for Composio
3
+
4
+ This provider enables integration with Claude Agent SDK,
5
+ allowing Composio tools to be used as MCP tools within Claude agents.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import typing as t
11
+
12
+ from claude_agent_sdk import (
13
+ McpSdkServerConfig,
14
+ SdkMcpTool,
15
+ create_sdk_mcp_server,
16
+ tool as sdk_tool,
17
+ )
18
+
19
+ from composio.core.provider import AgenticProvider
20
+ from composio.core.provider.agentic import AgenticProviderExecuteFn
21
+ from composio.types import Tool
22
+
23
+
24
+ class ClaudeAgentSDKProvider(
25
+ AgenticProvider[SdkMcpTool, list[SdkMcpTool]],
26
+ name="claude_agent_sdk",
27
+ ):
28
+ """
29
+ Composio provider for Claude Agent SDK.
30
+
31
+ This provider wraps Composio tools as MCP tools that can be used with
32
+ the Claude Agent SDK's `query()` function via the `mcp_servers` option.
33
+
34
+ Example:
35
+ ```python
36
+ import asyncio
37
+ from composio import Composio
38
+ from composio_claude_agent_sdk import ClaudeAgentSDKProvider
39
+ from claude_agent_sdk import query, ClaudeAgentOptions
40
+
41
+ composio = Composio(provider=ClaudeAgentSDKProvider())
42
+
43
+ async def main():
44
+ tools = composio.tools.get(user_id="default", toolkits=["gmail"])
45
+ mcp_server = composio.provider.create_mcp_server(tools)
46
+
47
+ async for message in query(
48
+ prompt="Fetch my latest email",
49
+ options=ClaudeAgentOptions(
50
+ mcp_servers={"composio": mcp_server},
51
+ permission_mode="bypassPermissions",
52
+ ),
53
+ ):
54
+ print(message)
55
+
56
+ asyncio.run(main())
57
+ ```
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ server_name: str = "composio",
63
+ server_version: str = "1.0.0",
64
+ ) -> None:
65
+ """
66
+ Initialize the Claude Agent SDK provider.
67
+
68
+ Args:
69
+ server_name: Name for the MCP server (default: "composio")
70
+ server_version: Version for the MCP server (default: "1.0.0")
71
+ """
72
+ super().__init__()
73
+ self.server_name = server_name
74
+ self.server_version = server_version
75
+
76
+ def wrap_tool(
77
+ self,
78
+ tool: Tool,
79
+ execute_tool: AgenticProviderExecuteFn,
80
+ ) -> SdkMcpTool:
81
+ """
82
+ Wrap a Composio tool as a Claude Agent SDK MCP tool.
83
+
84
+ Args:
85
+ tool: The Composio tool to wrap
86
+ execute_tool: Function to execute the tool
87
+
88
+ Returns:
89
+ A Claude Agent SDK MCP tool definition
90
+ """
91
+ input_schema = tool.input_parameters or {}
92
+
93
+ @sdk_tool(
94
+ tool.slug,
95
+ tool.description or f"Execute {tool.slug}",
96
+ input_schema,
97
+ )
98
+ async def tool_handler(args: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
99
+ """Execute the Composio tool with the given arguments."""
100
+ try:
101
+ # Run the synchronous execute_tool in a thread
102
+ result = await asyncio.to_thread(
103
+ execute_tool,
104
+ tool.slug,
105
+ args,
106
+ )
107
+ # Format the result for Claude Agent SDK
108
+ result_text = result if isinstance(result, str) else json.dumps(result)
109
+ return {
110
+ "content": [
111
+ {
112
+ "type": "text",
113
+ "text": result_text,
114
+ }
115
+ ]
116
+ }
117
+ except Exception as e:
118
+ return {
119
+ "content": [
120
+ {
121
+ "type": "text",
122
+ "text": json.dumps(
123
+ {
124
+ "successful": False,
125
+ "error": str(e),
126
+ "data": None,
127
+ }
128
+ ),
129
+ }
130
+ ]
131
+ }
132
+
133
+ return tool_handler
134
+
135
+ def wrap_tools(
136
+ self,
137
+ tools: t.Sequence[Tool],
138
+ execute_tool: AgenticProviderExecuteFn,
139
+ ) -> list[SdkMcpTool]:
140
+ """
141
+ Wrap multiple Composio tools as Claude Agent SDK MCP tools.
142
+
143
+ Args:
144
+ tools: Sequence of Composio tools to wrap
145
+ execute_tool: Function to execute the tools
146
+
147
+ Returns:
148
+ List of Claude Agent SDK MCP tool definitions
149
+ """
150
+ return [self.wrap_tool(tool, execute_tool) for tool in tools]
151
+
152
+ def create_mcp_server(
153
+ self,
154
+ wrapped_tools: list[SdkMcpTool],
155
+ ) -> McpSdkServerConfig:
156
+ """
157
+ Create an MCP server configuration for use with Claude Agent SDK.
158
+
159
+ This is the primary method for integrating Composio tools with Claude agents.
160
+ The returned configuration can be passed directly to the `mcp_servers` option.
161
+
162
+ Args:
163
+ wrapped_tools: List of wrapped Claude Agent SDK MCP tools
164
+ (from composio.tools.get())
165
+
166
+ Returns:
167
+ MCP server configuration for Claude Agent SDK
168
+
169
+ Example:
170
+ ```python
171
+ tools = composio.tools.get(user_id="default", toolkits=["gmail"])
172
+ mcp_server = composio.provider.create_mcp_server(tools)
173
+
174
+ async for message in query(
175
+ prompt="Send an email",
176
+ options=ClaudeAgentOptions(
177
+ mcp_servers={"composio": mcp_server},
178
+ ),
179
+ ):
180
+ print(message)
181
+ ```
182
+ """
183
+ return create_sdk_mcp_server(
184
+ name=self.server_name,
185
+ version=self.server_version,
186
+ tools=wrapped_tools,
187
+ )
File without changes
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: composio-claude-agent-sdk
3
+ Version: 0.10.5
4
+ Summary: Use Composio to get an array of tools with Claude Code Agents SDK.
5
+ Home-page: https://github.com/ComposioHQ/composio
6
+ Author: Composio
7
+ Author-email: Composio <tech@composio.dev>
8
+ Project-URL: Homepage, https://github.com/ComposioHQ/composio
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10,<4
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: claude-agent-sdk>=0.1.0
15
+ Requires-Dist: composio
16
+ Dynamic: author
17
+ Dynamic: home-page
18
+ Dynamic: requires-python
19
+
20
+ # Composio Claude Code Agents Provider
21
+
22
+ Use Composio tools with the Claude Code Agents SDK.
23
+
24
+ ## Installation
25
+
26
+ ### Prerequisites
27
+
28
+ 1. **Claude Code CLI**: The Claude Agent SDK requires Claude Code to be installed:
29
+
30
+ ```bash
31
+ # macOS/Linux/WSL
32
+ curl -fsSL https://claude.ai/install.sh | bash
33
+
34
+ # or via Homebrew
35
+ brew install --cask claude-code
36
+
37
+ # or via npm
38
+ npm install -g @anthropic-ai/claude-code
39
+ ```
40
+
41
+ 2. **Anthropic API Key**: Set your API key as an environment variable:
42
+ ```bash
43
+ export ANTHROPIC_API_KEY="your-api-key"
44
+ ```
45
+
46
+ ### Install the package
47
+
48
+ ```bash
49
+ pip install composio-claude-agent-sdk
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```python
55
+ import asyncio
56
+ from composio import Composio
57
+ from composio_claude_agent_sdk import ClaudeAgentSDKProvider
58
+ from claude_agent_sdk import query, ClaudeAgentOptions
59
+
60
+ # Initialize Composio with the Claude Code Agents provider
61
+ composio = Composio(provider=ClaudeAgentSDKProvider())
62
+
63
+ async def main():
64
+ # Get tools from Composio
65
+ tools = composio.tools.get(
66
+ user_id="default",
67
+ toolkits=["gmail"],
68
+ )
69
+
70
+ # Create an MCP server configuration with the tools
71
+ mcp_server = composio.provider.create_mcp_server(tools)
72
+
73
+ # Run a Claude agent with access to Composio tools
74
+ async for message in query(
75
+ prompt="Fetch my latest email from Gmail",
76
+ options=ClaudeAgentOptions(
77
+ mcp_servers={"composio": mcp_server},
78
+ permission_mode="bypassPermissions",
79
+ ),
80
+ ):
81
+ if message.type == "assistant":
82
+ print(message.message)
83
+
84
+ asyncio.run(main())
85
+ ```
86
+
87
+ ## API Reference
88
+
89
+ ### ClaudeCodeAgentsProvider
90
+
91
+ The main provider class for integrating Composio tools with Claude Code Agents SDK.
92
+
93
+ #### Constructor Options
94
+
95
+ ```python
96
+ ClaudeCodeAgentsProvider(
97
+ server_name: str = "composio", # Name for the MCP server
98
+ server_version: str = "1.0.0", # Version for the MCP server
99
+ )
100
+ ```
101
+
102
+ #### Methods
103
+
104
+ - `wrap_tool(tool, execute_tool)` - Wraps a single Composio tool as a Claude Agent SDK MCP tool
105
+ - `wrap_tools(tools, execute_tool)` - Wraps multiple Composio tools
106
+ - `create_mcp_server(wrapped_tools)` - Creates an MCP server configuration from wrapped tools
107
+
108
+ ## Environment Variables
109
+
110
+ - `COMPOSIO_API_KEY` - Your Composio API key (get one at https://app.composio.dev)
111
+ - `ANTHROPIC_API_KEY` - Your Anthropic API key (get one at https://console.anthropic.com)
112
+
113
+ ## Links
114
+
115
+ - [Composio Documentation](https://docs.composio.dev)
116
+ - [Claude Agent SDK Documentation](https://platform.claude.com/docs/en/agent-sdk/python)
117
+ - [GitHub Repository](https://github.com/ComposioHQ/composio)
@@ -0,0 +1,9 @@
1
+ composio_claude_agent_sdk/__init__.py,sha256=5jU62AJuNHgNf4b8skPmOBqU53hs-zThc8lhUZtCQzs,109
2
+ composio_claude_agent_sdk/provider.py,sha256=hLYnVVkfILqjVoxvThmVoNABpdjMgWJUz00dtbYbUkY,5741
3
+ composio_claude_agent_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ tests/test_provider.py,sha256=TJXJ9XJhuSR_8GVtKFRuUeRc6Tc6dQJjA799yx5nPPM,10694
6
+ composio_claude_agent_sdk-0.10.5.dist-info/METADATA,sha256=tb5f4nnBQuBHef5gkghotsdJgI1aAPxIz1oUvwxQU54,3262
7
+ composio_claude_agent_sdk-0.10.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ composio_claude_agent_sdk-0.10.5.dist-info/top_level.txt,sha256=J2UB7SfZ_56iY7K_cRyIH1pqTYttMFKrj2AfN9qih48,32
9
+ composio_claude_agent_sdk-0.10.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ composio_claude_agent_sdk
2
+ tests
tests/__init__.py ADDED
File without changes
tests/test_provider.py ADDED
@@ -0,0 +1,313 @@
1
+ """
2
+ Tests for Claude Code Agents Provider
3
+ """
4
+
5
+ import json
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pytest
9
+
10
+ from composio_claude_agent_sdk.provider import ClaudeAgentSDKProvider
11
+
12
+
13
+ @pytest.fixture
14
+ def provider():
15
+ """Create a ClaudeAgentSDKProvider instance."""
16
+ return ClaudeAgentSDKProvider()
17
+
18
+
19
+ @pytest.fixture
20
+ def custom_provider():
21
+ """Create a ClaudeAgentSDKProvider with custom options."""
22
+ return ClaudeAgentSDKProvider(
23
+ server_name="custom-server",
24
+ server_version="2.0.0",
25
+ )
26
+
27
+
28
+ @pytest.fixture
29
+ def mock_tool():
30
+ """Create a mock Composio tool."""
31
+ return MagicMock(
32
+ slug="GMAIL_SEND_EMAIL",
33
+ name="Gmail Send Email",
34
+ description="Send an email via Gmail",
35
+ input_parameters={
36
+ "type": "object",
37
+ "properties": {
38
+ "to": {
39
+ "type": "string",
40
+ "description": "Recipient email address",
41
+ },
42
+ "subject": {
43
+ "type": "string",
44
+ "description": "Email subject",
45
+ },
46
+ "body": {
47
+ "type": "string",
48
+ "description": "Email body content",
49
+ },
50
+ },
51
+ "required": ["to", "subject", "body"],
52
+ },
53
+ )
54
+
55
+
56
+ @pytest.fixture
57
+ def mock_execute_tool():
58
+ """Create a mock execute_tool function."""
59
+ return MagicMock(
60
+ return_value={
61
+ "data": {"result": "success"},
62
+ "error": None,
63
+ "successful": True,
64
+ }
65
+ )
66
+
67
+
68
+ class TestClaudeAgentSDKProviderInit:
69
+ """Tests for provider initialization."""
70
+
71
+ def test_default_options(self, provider):
72
+ """Test provider initializes with default options."""
73
+ assert provider.server_name == "composio"
74
+ assert provider.server_version == "1.0.0"
75
+
76
+ def test_custom_options(self, custom_provider):
77
+ """Test provider initializes with custom options."""
78
+ assert custom_provider.server_name == "custom-server"
79
+ assert custom_provider.server_version == "2.0.0"
80
+
81
+ def test_name_property(self, provider):
82
+ """Test provider has correct name."""
83
+ assert provider.name == "claude_agent_sdk"
84
+
85
+
86
+ class TestJsonSchemaConversion:
87
+ """Tests for JSON Schema conversion."""
88
+
89
+ def test_simple_schema_passthrough(self, provider):
90
+ """Test that JSON Schema is passed through unchanged."""
91
+ schema = {
92
+ "type": "object",
93
+ "properties": {
94
+ "name": {"type": "string"},
95
+ "count": {"type": "integer"},
96
+ },
97
+ }
98
+ result = provider._json_schema_to_simple_schema(schema)
99
+ assert result == schema
100
+
101
+ def test_empty_schema(self, provider):
102
+ """Test handling of empty schema."""
103
+ result = provider._json_schema_to_simple_schema({})
104
+ assert result == {}
105
+
106
+
107
+ class TestWrapTool:
108
+ """Tests for wrap_tool method."""
109
+
110
+ def test_wrap_tool_returns_sdk_mcp_tool(
111
+ self, provider, mock_tool, mock_execute_tool
112
+ ):
113
+ """Test that wrap_tool returns an SdkMcpTool."""
114
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
115
+ mock_sdk_tool.return_value = lambda fn: fn
116
+ provider.wrap_tool(mock_tool, mock_execute_tool)
117
+
118
+ # Verify sdk_tool was called with correct arguments
119
+ mock_sdk_tool.assert_called_once_with(
120
+ mock_tool.slug,
121
+ mock_tool.description,
122
+ mock_tool.input_parameters,
123
+ )
124
+
125
+ def test_wrap_tool_without_description(self, provider, mock_execute_tool):
126
+ """Test wrapping a tool without description."""
127
+ tool_without_desc = MagicMock(
128
+ slug="TEST_TOOL",
129
+ description=None,
130
+ input_parameters={"type": "object", "properties": {}},
131
+ )
132
+
133
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
134
+ mock_sdk_tool.return_value = lambda fn: fn
135
+ provider.wrap_tool(tool_without_desc, mock_execute_tool)
136
+
137
+ # Should use default description
138
+ mock_sdk_tool.assert_called_once()
139
+ call_args = mock_sdk_tool.call_args[0]
140
+ assert call_args[1] == "Execute TEST_TOOL"
141
+
142
+ def test_wrap_tool_without_input_parameters(self, provider, mock_execute_tool):
143
+ """Test wrapping a tool without input parameters."""
144
+ tool_without_params = MagicMock(
145
+ slug="TEST_TOOL",
146
+ description="Test tool",
147
+ input_parameters=None,
148
+ )
149
+
150
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
151
+ mock_sdk_tool.return_value = lambda fn: fn
152
+ provider.wrap_tool(tool_without_params, mock_execute_tool)
153
+
154
+ # Should use empty schema
155
+ mock_sdk_tool.assert_called_once()
156
+ call_args = mock_sdk_tool.call_args[0]
157
+ assert call_args[2] == {}
158
+
159
+
160
+ class TestWrapTools:
161
+ """Tests for wrap_tools method."""
162
+
163
+ def test_wrap_multiple_tools(self, provider, mock_tool, mock_execute_tool):
164
+ """Test wrapping multiple tools."""
165
+ another_tool = MagicMock(
166
+ slug="SLACK_POST_MESSAGE",
167
+ description="Post a message to Slack",
168
+ input_parameters={"type": "object", "properties": {}},
169
+ )
170
+ tools = [mock_tool, another_tool]
171
+
172
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
173
+ mock_sdk_tool.return_value = lambda fn: fn
174
+ wrapped = provider.wrap_tools(tools, mock_execute_tool)
175
+
176
+ assert len(wrapped) == 2
177
+ assert mock_sdk_tool.call_count == 2
178
+
179
+ def test_wrap_empty_tools_list(self, provider, mock_execute_tool):
180
+ """Test wrapping an empty tools list."""
181
+ wrapped = provider.wrap_tools([], mock_execute_tool)
182
+ assert wrapped == []
183
+
184
+
185
+ class TestCreateMcpServer:
186
+ """Tests for create_mcp_server method."""
187
+
188
+ def test_create_mcp_server_with_default_options(self, provider):
189
+ """Test creating MCP server with default options."""
190
+ mock_tools = [MagicMock(), MagicMock()]
191
+
192
+ with patch(
193
+ "composio_claude_agent_sdk.provider.create_sdk_mcp_server"
194
+ ) as mock_create:
195
+ mock_create.return_value = {"type": "sdk", "name": "composio"}
196
+ provider.create_mcp_server(mock_tools)
197
+
198
+ mock_create.assert_called_once_with(
199
+ name="composio",
200
+ version="1.0.0",
201
+ tools=mock_tools,
202
+ )
203
+
204
+ def test_create_mcp_server_with_custom_options(self, custom_provider):
205
+ """Test creating MCP server with custom options."""
206
+ mock_tools = [MagicMock()]
207
+
208
+ with patch(
209
+ "composio_claude_agent_sdk.provider.create_sdk_mcp_server"
210
+ ) as mock_create:
211
+ mock_create.return_value = {"type": "sdk", "name": "custom-server"}
212
+ custom_provider.create_mcp_server(mock_tools)
213
+
214
+ mock_create.assert_called_once_with(
215
+ name="custom-server",
216
+ version="2.0.0",
217
+ tools=mock_tools,
218
+ )
219
+
220
+ def test_create_mcp_server_with_empty_tools(self, provider):
221
+ """Test creating MCP server with empty tools list."""
222
+ with patch(
223
+ "composio_claude_agent_sdk.provider.create_sdk_mcp_server"
224
+ ) as mock_create:
225
+ mock_create.return_value = {"type": "sdk", "name": "composio"}
226
+ provider.create_mcp_server([])
227
+
228
+ mock_create.assert_called_once_with(
229
+ name="composio",
230
+ version="1.0.0",
231
+ tools=[],
232
+ )
233
+
234
+
235
+ class TestToolHandlerExecution:
236
+ """Tests for tool handler execution."""
237
+
238
+ @pytest.mark.asyncio
239
+ async def test_tool_handler_success(self, provider, mock_tool):
240
+ """Test successful tool execution."""
241
+ mock_execute = MagicMock(return_value={"data": "success", "successful": True})
242
+
243
+ # Create a real wrapped tool to test the handler
244
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
245
+ # Capture the decorated function
246
+ captured_handler = None
247
+
248
+ def capture_decorator(name, desc, schema):
249
+ def decorator(fn):
250
+ nonlocal captured_handler
251
+ captured_handler = fn
252
+ return fn
253
+
254
+ return decorator
255
+
256
+ mock_sdk_tool.side_effect = capture_decorator
257
+ provider.wrap_tool(mock_tool, mock_execute)
258
+
259
+ # Execute the captured handler
260
+ result = await captured_handler({"to": "test@example.com"})
261
+
262
+ assert result["content"][0]["type"] == "text"
263
+ assert "success" in result["content"][0]["text"]
264
+
265
+ @pytest.mark.asyncio
266
+ async def test_tool_handler_error(self, provider, mock_tool):
267
+ """Test tool execution with error."""
268
+ mock_execute = MagicMock(side_effect=Exception("Test error"))
269
+
270
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
271
+ captured_handler = None
272
+
273
+ def capture_decorator(name, desc, schema):
274
+ def decorator(fn):
275
+ nonlocal captured_handler
276
+ captured_handler = fn
277
+ return fn
278
+
279
+ return decorator
280
+
281
+ mock_sdk_tool.side_effect = capture_decorator
282
+ provider.wrap_tool(mock_tool, mock_execute)
283
+
284
+ result = await captured_handler({"to": "test@example.com"})
285
+
286
+ assert result["content"][0]["type"] == "text"
287
+ response = json.loads(result["content"][0]["text"])
288
+ assert response["successful"] is False
289
+ assert "Test error" in response["error"]
290
+
291
+ @pytest.mark.asyncio
292
+ async def test_tool_handler_string_result(self, provider, mock_tool):
293
+ """Test tool execution returning string result."""
294
+ mock_execute = MagicMock(return_value="Simple string result")
295
+
296
+ with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
297
+ captured_handler = None
298
+
299
+ def capture_decorator(name, desc, schema):
300
+ def decorator(fn):
301
+ nonlocal captured_handler
302
+ captured_handler = fn
303
+ return fn
304
+
305
+ return decorator
306
+
307
+ mock_sdk_tool.side_effect = capture_decorator
308
+ provider.wrap_tool(mock_tool, mock_execute)
309
+
310
+ result = await captured_handler({})
311
+
312
+ assert result["content"][0]["type"] == "text"
313
+ assert result["content"][0]["text"] == "Simple string result"