hammad-python 0.0.30__py3-none-any.whl → 0.0.32__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.
Files changed (137) hide show
  1. ham/__init__.py +200 -0
  2. {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/METADATA +6 -32
  3. hammad_python-0.0.32.dist-info/RECORD +6 -0
  4. hammad/__init__.py +0 -84
  5. hammad/_internal.py +0 -256
  6. hammad/_main.py +0 -226
  7. hammad/cache/__init__.py +0 -40
  8. hammad/cache/base_cache.py +0 -181
  9. hammad/cache/cache.py +0 -169
  10. hammad/cache/decorators.py +0 -261
  11. hammad/cache/file_cache.py +0 -80
  12. hammad/cache/ttl_cache.py +0 -74
  13. hammad/cli/__init__.py +0 -33
  14. hammad/cli/animations.py +0 -573
  15. hammad/cli/plugins.py +0 -867
  16. hammad/cli/styles/__init__.py +0 -55
  17. hammad/cli/styles/settings.py +0 -139
  18. hammad/cli/styles/types.py +0 -358
  19. hammad/cli/styles/utils.py +0 -634
  20. hammad/data/__init__.py +0 -90
  21. hammad/data/collections/__init__.py +0 -49
  22. hammad/data/collections/collection.py +0 -326
  23. hammad/data/collections/indexes/__init__.py +0 -37
  24. hammad/data/collections/indexes/qdrant/__init__.py +0 -1
  25. hammad/data/collections/indexes/qdrant/index.py +0 -723
  26. hammad/data/collections/indexes/qdrant/settings.py +0 -94
  27. hammad/data/collections/indexes/qdrant/utils.py +0 -210
  28. hammad/data/collections/indexes/tantivy/__init__.py +0 -1
  29. hammad/data/collections/indexes/tantivy/index.py +0 -426
  30. hammad/data/collections/indexes/tantivy/settings.py +0 -40
  31. hammad/data/collections/indexes/tantivy/utils.py +0 -176
  32. hammad/data/configurations/__init__.py +0 -35
  33. hammad/data/configurations/configuration.py +0 -564
  34. hammad/data/models/__init__.py +0 -50
  35. hammad/data/models/extensions/__init__.py +0 -4
  36. hammad/data/models/extensions/pydantic/__init__.py +0 -42
  37. hammad/data/models/extensions/pydantic/converters.py +0 -759
  38. hammad/data/models/fields.py +0 -546
  39. hammad/data/models/model.py +0 -1078
  40. hammad/data/models/utils.py +0 -280
  41. hammad/data/sql/__init__.py +0 -24
  42. hammad/data/sql/database.py +0 -576
  43. hammad/data/sql/types.py +0 -127
  44. hammad/data/types/__init__.py +0 -75
  45. hammad/data/types/file.py +0 -431
  46. hammad/data/types/multimodal/__init__.py +0 -36
  47. hammad/data/types/multimodal/audio.py +0 -200
  48. hammad/data/types/multimodal/image.py +0 -182
  49. hammad/data/types/text.py +0 -1308
  50. hammad/formatting/__init__.py +0 -33
  51. hammad/formatting/json/__init__.py +0 -27
  52. hammad/formatting/json/converters.py +0 -158
  53. hammad/formatting/text/__init__.py +0 -63
  54. hammad/formatting/text/converters.py +0 -723
  55. hammad/formatting/text/markdown.py +0 -131
  56. hammad/formatting/yaml/__init__.py +0 -26
  57. hammad/formatting/yaml/converters.py +0 -5
  58. hammad/genai/__init__.py +0 -217
  59. hammad/genai/a2a/__init__.py +0 -32
  60. hammad/genai/a2a/workers.py +0 -552
  61. hammad/genai/agents/__init__.py +0 -59
  62. hammad/genai/agents/agent.py +0 -1973
  63. hammad/genai/agents/run.py +0 -1024
  64. hammad/genai/agents/types/__init__.py +0 -42
  65. hammad/genai/agents/types/agent_context.py +0 -13
  66. hammad/genai/agents/types/agent_event.py +0 -128
  67. hammad/genai/agents/types/agent_hooks.py +0 -220
  68. hammad/genai/agents/types/agent_messages.py +0 -31
  69. hammad/genai/agents/types/agent_response.py +0 -125
  70. hammad/genai/agents/types/agent_stream.py +0 -327
  71. hammad/genai/graphs/__init__.py +0 -125
  72. hammad/genai/graphs/_utils.py +0 -190
  73. hammad/genai/graphs/base.py +0 -1828
  74. hammad/genai/graphs/plugins.py +0 -316
  75. hammad/genai/graphs/types.py +0 -638
  76. hammad/genai/models/__init__.py +0 -1
  77. hammad/genai/models/embeddings/__init__.py +0 -43
  78. hammad/genai/models/embeddings/model.py +0 -226
  79. hammad/genai/models/embeddings/run.py +0 -163
  80. hammad/genai/models/embeddings/types/__init__.py +0 -37
  81. hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
  82. hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
  83. hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
  84. hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
  85. hammad/genai/models/language/__init__.py +0 -57
  86. hammad/genai/models/language/model.py +0 -1098
  87. hammad/genai/models/language/run.py +0 -878
  88. hammad/genai/models/language/types/__init__.py +0 -40
  89. hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
  90. hammad/genai/models/language/types/language_model_messages.py +0 -28
  91. hammad/genai/models/language/types/language_model_name.py +0 -239
  92. hammad/genai/models/language/types/language_model_request.py +0 -127
  93. hammad/genai/models/language/types/language_model_response.py +0 -217
  94. hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
  95. hammad/genai/models/language/types/language_model_settings.py +0 -89
  96. hammad/genai/models/language/types/language_model_stream.py +0 -600
  97. hammad/genai/models/language/utils/__init__.py +0 -28
  98. hammad/genai/models/language/utils/requests.py +0 -421
  99. hammad/genai/models/language/utils/structured_outputs.py +0 -135
  100. hammad/genai/models/model_provider.py +0 -4
  101. hammad/genai/models/multimodal.py +0 -47
  102. hammad/genai/models/reranking.py +0 -26
  103. hammad/genai/types/__init__.py +0 -1
  104. hammad/genai/types/base.py +0 -215
  105. hammad/genai/types/history.py +0 -290
  106. hammad/genai/types/tools.py +0 -507
  107. hammad/logging/__init__.py +0 -35
  108. hammad/logging/decorators.py +0 -834
  109. hammad/logging/logger.py +0 -1018
  110. hammad/mcp/__init__.py +0 -53
  111. hammad/mcp/client/__init__.py +0 -35
  112. hammad/mcp/client/client.py +0 -624
  113. hammad/mcp/client/client_service.py +0 -400
  114. hammad/mcp/client/settings.py +0 -178
  115. hammad/mcp/servers/__init__.py +0 -26
  116. hammad/mcp/servers/launcher.py +0 -1161
  117. hammad/runtime/__init__.py +0 -32
  118. hammad/runtime/decorators.py +0 -142
  119. hammad/runtime/run.py +0 -299
  120. hammad/service/__init__.py +0 -49
  121. hammad/service/create.py +0 -527
  122. hammad/service/decorators.py +0 -283
  123. hammad/types.py +0 -288
  124. hammad/typing/__init__.py +0 -435
  125. hammad/web/__init__.py +0 -43
  126. hammad/web/http/__init__.py +0 -1
  127. hammad/web/http/client.py +0 -944
  128. hammad/web/models.py +0 -275
  129. hammad/web/openapi/__init__.py +0 -1
  130. hammad/web/openapi/client.py +0 -740
  131. hammad/web/search/__init__.py +0 -1
  132. hammad/web/search/client.py +0 -1023
  133. hammad/web/utils.py +0 -472
  134. hammad_python-0.0.30.dist-info/RECORD +0 -135
  135. {hammad → ham}/py.typed +0 -0
  136. {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/WHEEL +0 -0
  137. {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/licenses/LICENSE +0 -0
hammad/mcp/__init__.py DELETED
@@ -1,53 +0,0 @@
1
- """
2
- hammad.mcp
3
- """
4
-
5
- from typing import TYPE_CHECKING
6
- from .._internal import create_getattr_importer
7
-
8
- if TYPE_CHECKING:
9
- from mcp.server.fastmcp import FastMCP
10
- from .client.client import (
11
- convert_mcp_tool_to_openai_tool,
12
- MCPClient,
13
- MCPClientService,
14
- )
15
- from .client.settings import (
16
- MCPClientStdioSettings,
17
- MCPClientSseSettings,
18
- MCPClientStreamableHttpSettings,
19
- )
20
- from .servers.launcher import (
21
- launch_mcp_servers,
22
- MCPServerService,
23
- MCPServerStdioSettings,
24
- MCPServerSseSettings,
25
- MCPServerStreamableHttpSettings,
26
- )
27
-
28
-
29
- __all__ = (
30
- # fastmcp
31
- "FastMCP",
32
- # hammad.mcp.client
33
- "MCPClient",
34
- "MCPClientService",
35
- "convert_mcp_tool_to_openai_tool",
36
- # hammad.mcp.client.settings
37
- "MCPClientStdioSettings",
38
- "MCPClientSseSettings",
39
- "MCPClientStreamableHttpSettings",
40
- # hammad.mcp.servers.launcher
41
- "launch_mcp_servers",
42
- "MCPServerService",
43
- "MCPServerStdioSettings",
44
- "MCPServerSseSettings",
45
- "MCPServerStreamableHttpSettings",
46
- )
47
-
48
-
49
- __getattr__ = create_getattr_importer(__all__)
50
-
51
-
52
- def __dir__() -> list[str]:
53
- return list(__all__)
@@ -1,35 +0,0 @@
1
- """hammad.mcp.client"""
2
-
3
- from typing import TYPE_CHECKING
4
- from ..._internal import create_getattr_importer
5
-
6
- if TYPE_CHECKING:
7
- from .client import (
8
- MCPClient,
9
- MCPClientService,
10
- )
11
- from .settings import (
12
- MCPClientSettings,
13
- MCPClientSseSettings,
14
- MCPClientStreamableHttpSettings,
15
- MCPClientStdioSettings,
16
- )
17
-
18
- __all__ = (
19
- # hammad.mcp.client
20
- "MCPClient",
21
- # hammad.mcp.client.client_service
22
- "MCPClientService",
23
- # hammad.mcp.client.settings
24
- "MCPClientSettings",
25
- "MCPClientSseSettings",
26
- "MCPClientStreamableHttpSettings",
27
- "MCPClientStdioSettings",
28
- )
29
-
30
- __getattr__ = create_getattr_importer(__all__)
31
-
32
-
33
- def __dir__() -> list[str]:
34
- """Get the attributes of the client module."""
35
- return list(__all__)
@@ -1,624 +0,0 @@
1
- """hammad.mcp.client.client
2
-
3
- Contains the `MCPClient` class.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- import asyncio
9
- from dataclasses import dataclass, field
10
- from pathlib import Path
11
- from typing import Any, Callable, Literal, overload
12
- import threading
13
- import concurrent.futures
14
- import inspect
15
-
16
- try:
17
- from mcp.types import CallToolResult, Tool as MCPTool
18
- from openai.types.chat.chat_completion_tool_param import (
19
- ChatCompletionToolParam as OpenAITool,
20
- )
21
- from openai.types.shared import FunctionDefinition as Function
22
- except ImportError:
23
- CallToolResult = Any
24
- MCPTool = Any
25
- OpenAITool = Any
26
- Function = Any
27
-
28
- from .client_service import (
29
- MCPClientService,
30
- MCPClientServiceSse,
31
- MCPClientServiceStdio,
32
- MCPClientServiceStreamableHttp,
33
- )
34
- from .settings import (
35
- MCPClientSettings,
36
- )
37
-
38
- __all__ = (
39
- "MCPClient",
40
- "MCPToolWrapper",
41
- "convert_mcp_tool_to_openai_tool",
42
- )
43
-
44
-
45
- # -----------------------------------------------------------------------------
46
- # Client
47
- # -----------------------------------------------------------------------------
48
-
49
-
50
- def convert_mcp_tool_to_openai_tool(mcp_tool: MCPTool) -> OpenAITool:
51
- return OpenAITool(
52
- type="function",
53
- function=Function(
54
- name=mcp_tool.name,
55
- description=mcp_tool.description,
56
- parameters=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
57
- ),
58
- )
59
-
60
-
61
- @dataclass
62
- class MCPToolWrapper:
63
- """
64
- Wrapper class that provides a runnable method and tool definitions
65
- for an MCP tool.
66
- """
67
-
68
- server_name: str
69
- tool_name: str
70
- tool_description: str
71
- tool_args: dict[str, Any]
72
- mcp_tool: MCPTool
73
- openai_tool: OpenAITool
74
- function: Callable[..., Any]
75
-
76
-
77
- @dataclass
78
- class MCPClient:
79
- """
80
- High-level interface for connecting to MCP servers using different transports.
81
-
82
- This class provides both synchronous and asynchronous methods for interacting
83
- with MCP servers, wrapping the lower-level client service implementations.
84
- """
85
-
86
- client_service: MCPClientService
87
- _connected: bool = False
88
- _sync_loop: asyncio.AbstractEventLoop = field(default=None, init=False)
89
- _sync_thread: threading.Thread = field(default=None, init=False)
90
- _executor: concurrent.futures.ThreadPoolExecutor = field(default=None, init=False)
91
-
92
- @classmethod
93
- def from_settings(
94
- cls,
95
- settings: MCPClientSettings,
96
- cache_tools_list: bool = False,
97
- name: str | None = None,
98
- client_session_timeout_seconds: float | None = 5,
99
- ) -> MCPClient:
100
- """Create an MCPClient from a settings object.
101
-
102
- Args:
103
- settings: The MCP client settings object.
104
- cache_tools_list: Whether to cache the tools list.
105
- name: A readable name for the client.
106
- client_session_timeout_seconds: The read timeout for the MCP ClientSession.
107
-
108
- Returns:
109
- An MCPClient instance.
110
- """
111
- if settings.type == "stdio":
112
- client_service = MCPClientServiceStdio(
113
- settings=settings.settings,
114
- cache_tools_list=cache_tools_list,
115
- name=name,
116
- client_session_timeout_seconds=client_session_timeout_seconds,
117
- )
118
- elif settings.type == "sse":
119
- client_service = MCPClientServiceSse(
120
- settings=settings.settings,
121
- cache_tools_list=cache_tools_list,
122
- name=name,
123
- client_session_timeout_seconds=client_session_timeout_seconds,
124
- )
125
- elif settings.type == "streamable_http":
126
- client_service = MCPClientServiceStreamableHttp(
127
- settings=settings.settings,
128
- cache_tools_list=cache_tools_list,
129
- name=name,
130
- client_session_timeout_seconds=client_session_timeout_seconds,
131
- )
132
- else:
133
- raise ValueError(f"Unsupported client type: {settings.type}")
134
-
135
- return cls(client_service=client_service)
136
-
137
- @classmethod
138
- def stdio(
139
- cls,
140
- command: str,
141
- args: list[str] | None = None,
142
- env: dict[str, str] | None = None,
143
- cwd: str | Path | None = None,
144
- encoding: str | None = None,
145
- encoding_error_handler: Literal["strict", "ignore", "replace"] | None = None,
146
- cache_tools_list: bool = False,
147
- name: str | None = None,
148
- client_session_timeout_seconds: float | None = 5,
149
- ) -> MCPClient:
150
- """Create an MCPClient using the stdio transport.
151
-
152
- Args:
153
- command: The executable to run to start the server.
154
- args: Command line args to pass to the executable.
155
- env: The environment variables to set for the server.
156
- cwd: The working directory to use when spawning the process.
157
- encoding: The text encoding used when sending/receiving messages.
158
- encoding_error_handler: The text encoding error handler.
159
- cache_tools_list: Whether to cache the tools list.
160
- name: A readable name for the client.
161
- client_session_timeout_seconds: The read timeout for the MCP ClientSession.
162
-
163
- Returns:
164
- An MCPClient instance.
165
- """
166
- settings = MCPClientSettings.stdio(
167
- command=command,
168
- args=args,
169
- env=env,
170
- cwd=cwd,
171
- encoding=encoding,
172
- encoding_error_handler=encoding_error_handler,
173
- )
174
-
175
- return cls.from_settings(
176
- settings=settings,
177
- cache_tools_list=cache_tools_list,
178
- name=name,
179
- client_session_timeout_seconds=client_session_timeout_seconds,
180
- )
181
-
182
- @classmethod
183
- def sse(
184
- cls,
185
- url: str,
186
- headers: dict[str, str] | None = None,
187
- timeout: float | None = None,
188
- sse_read_timeout: float | None = None,
189
- cache_tools_list: bool = False,
190
- name: str | None = None,
191
- client_session_timeout_seconds: float | None = 5,
192
- ) -> MCPClient:
193
- """Create an MCPClient using the SSE transport.
194
-
195
- Args:
196
- url: The URL to connect to the server.
197
- headers: The HTTP headers to send with the request.
198
- timeout: The timeout for the request in seconds.
199
- sse_read_timeout: The timeout for the SSE event reads in seconds.
200
- cache_tools_list: Whether to cache the tools list.
201
- name: A readable name for the client.
202
- client_session_timeout_seconds: The read timeout for the MCP ClientSession.
203
-
204
- Returns:
205
- An MCPClient instance.
206
- """
207
- settings = MCPClientSettings.sse(
208
- url=url,
209
- headers=headers,
210
- timeout=timeout,
211
- sse_read_timeout=sse_read_timeout,
212
- )
213
-
214
- return cls.from_settings(
215
- settings=settings,
216
- cache_tools_list=cache_tools_list,
217
- name=name,
218
- client_session_timeout_seconds=client_session_timeout_seconds,
219
- )
220
-
221
- @classmethod
222
- def streamable_http(
223
- cls,
224
- url: str,
225
- headers: dict[str, str] | None = None,
226
- timeout: float | None = None,
227
- sse_read_timeout: float | None = None,
228
- terminate_on_close: bool | None = None,
229
- cache_tools_list: bool = False,
230
- name: str | None = None,
231
- client_session_timeout_seconds: float | None = 5,
232
- ) -> MCPClient:
233
- """Create an MCPClient using the streamable HTTP transport.
234
-
235
- Args:
236
- url: The URL to connect to the server.
237
- headers: The HTTP headers to send with the request.
238
- timeout: The timeout for the request in seconds.
239
- sse_read_timeout: The timeout for the SSE event reads in seconds.
240
- terminate_on_close: Whether to terminate the connection on close.
241
- cache_tools_list: Whether to cache the tools list.
242
- name: A readable name for the client.
243
- client_session_timeout_seconds: The read timeout for the MCP ClientSession.
244
-
245
- Returns:
246
- An MCPClient instance.
247
- """
248
- settings = MCPClientSettings.streamable_http(
249
- url=url,
250
- headers=headers,
251
- timeout=timeout,
252
- sse_read_timeout=sse_read_timeout,
253
- terminate_on_close=terminate_on_close,
254
- )
255
-
256
- return cls.from_settings(
257
- settings=settings,
258
- cache_tools_list=cache_tools_list,
259
- name=name,
260
- client_session_timeout_seconds=client_session_timeout_seconds,
261
- )
262
-
263
- @property
264
- def name(self) -> str:
265
- """A readable name for the client."""
266
- return self.client_service.name
267
-
268
- def _ensure_sync_context(self):
269
- """Ensure we have a persistent async context for sync operations."""
270
- if self._sync_loop is None or self._sync_loop.is_closed():
271
- self._create_sync_context()
272
-
273
- def _create_sync_context(self):
274
- """Create a persistent async context for sync operations."""
275
- if self._executor:
276
- self._executor.shutdown(wait=False)
277
-
278
- self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
279
-
280
- def run_loop():
281
- loop = asyncio.new_event_loop()
282
- asyncio.set_event_loop(loop)
283
- self._sync_loop = loop
284
- try:
285
- loop.run_forever()
286
- finally:
287
- # Clean up when the loop stops
288
- try:
289
- if self._connected:
290
- loop.run_until_complete(self.async_cleanup())
291
- except Exception:
292
- pass # Ignore cleanup errors
293
- loop.close()
294
-
295
- self._sync_thread = threading.Thread(target=run_loop, daemon=True)
296
- self._sync_thread.start()
297
-
298
- # Wait for the loop to be ready
299
- import time
300
-
301
- timeout = 5.0
302
- start_time = time.time()
303
- while (self._sync_loop is None or not self._sync_loop.is_running()) and (
304
- time.time() - start_time
305
- ) < timeout:
306
- time.sleep(0.01)
307
-
308
- if self._sync_loop is None or not self._sync_loop.is_running():
309
- raise RuntimeError("Failed to start sync event loop")
310
-
311
- def _run_in_sync_context(self, coro):
312
- """Run a coroutine in the persistent sync context."""
313
- self._ensure_sync_context()
314
-
315
- future = asyncio.run_coroutine_threadsafe(coro, self._sync_loop)
316
- return future.result()
317
-
318
- def connect(self) -> None:
319
- """Connect to the MCP server synchronously."""
320
- self._run_in_sync_context(self.async_connect())
321
-
322
- async def async_connect(self) -> None:
323
- """Connect to the MCP server asynchronously."""
324
- if self._connected:
325
- return
326
- await self.client_service.connect()
327
- self._connected = True
328
-
329
- def cleanup(self) -> None:
330
- """Cleanup the client connection synchronously."""
331
- try:
332
- if self._connected:
333
- self._run_in_sync_context(self.async_cleanup())
334
- finally:
335
- # Clean up the sync context
336
- if self._sync_loop and not self._sync_loop.is_closed():
337
- self._sync_loop.call_soon_threadsafe(self._sync_loop.stop)
338
- if self._executor:
339
- self._executor.shutdown(wait=True)
340
- self._executor = None
341
- self._sync_loop = None
342
- self._sync_thread = None
343
-
344
- async def async_cleanup(self) -> None:
345
- """Cleanup the client connection asynchronously."""
346
- if not self._connected:
347
- return
348
- await self.client_service.cleanup()
349
- self._connected = False
350
-
351
- def list_tools(self) -> list[MCPTool]:
352
- """List the tools available on the server synchronously.
353
-
354
- Returns:
355
- A list of available MCP tools.
356
- """
357
- return self._run_in_sync_context(self.async_list_tools())
358
-
359
- def list_wrapped_tools(self) -> list[MCPToolWrapper]:
360
- """List the tools available on the server as wrapped tools with OpenAI compatibility.
361
-
362
- Returns:
363
- A list of MCPToolWrapper objects that include both MCP and OpenAI tool formats,
364
- plus callable functions for each tool.
365
- """
366
- # Get the raw MCP tools
367
- mcp_tools = self.list_tools()
368
-
369
- wrapped_tools = []
370
- for mcp_tool in mcp_tools:
371
- # Convert to OpenAI tool format
372
- openai_tool = convert_mcp_tool_to_openai_tool(mcp_tool)
373
-
374
- # Create a callable function for this tool
375
- def create_tool_function(tool_name: str):
376
- def tool_function(**kwargs) -> Any:
377
- """Dynamically created function that calls the MCP tool."""
378
- return self.call_tool(tool_name, kwargs if kwargs else None)
379
-
380
- # Set function metadata
381
- tool_function.__name__ = tool_name
382
- tool_function.__doc__ = f"MCP tool: {mcp_tool.description}"
383
-
384
- return tool_function
385
-
386
- # Extract tool arguments from input schema
387
- tool_args = {}
388
- if mcp_tool.inputSchema and isinstance(mcp_tool.inputSchema, dict):
389
- properties = mcp_tool.inputSchema.get("properties", {})
390
- for prop_name, prop_info in properties.items():
391
- if isinstance(prop_info, dict):
392
- tool_args[prop_name] = prop_info.get("type", "any")
393
- else:
394
- tool_args[prop_name] = "any"
395
-
396
- # Create the wrapper
397
- wrapper = MCPToolWrapper(
398
- server_name=self.name,
399
- tool_name=mcp_tool.name,
400
- tool_description=mcp_tool.description or "",
401
- tool_args=tool_args,
402
- mcp_tool=mcp_tool,
403
- openai_tool=openai_tool,
404
- function=create_tool_function(mcp_tool.name),
405
- )
406
-
407
- wrapped_tools.append(wrapper)
408
-
409
- return wrapped_tools
410
-
411
- async def async_list_tools(self) -> list[MCPTool]:
412
- """List the tools available on the server asynchronously.
413
-
414
- Returns:
415
- A list of available MCP tools.
416
- """
417
- if not self._connected:
418
- await self.async_connect()
419
- return await self.client_service.list_tools()
420
-
421
- def call_tool(
422
- self, tool_name: str, arguments: dict[str, Any] | None = None
423
- ) -> CallToolResult:
424
- """Invoke a tool on the server synchronously.
425
-
426
- Args:
427
- tool_name: The name of the tool to call.
428
- arguments: The arguments to pass to the tool.
429
-
430
- Returns:
431
- The result of the tool call.
432
- """
433
- return self._run_in_sync_context(self.async_call_tool(tool_name, arguments))
434
-
435
- async def async_call_tool(
436
- self, tool_name: str, arguments: dict[str, Any] | None = None
437
- ) -> CallToolResult:
438
- """Invoke a tool on the server asynchronously.
439
-
440
- Args:
441
- tool_name: The name of the tool to call.
442
- arguments: The arguments to pass to the tool.
443
-
444
- Returns:
445
- The result of the tool call.
446
- """
447
- if not self._connected:
448
- await self.async_connect()
449
- return await self.client_service.call_tool(tool_name, arguments)
450
-
451
- def as_tool(
452
- self, tool_name: str, func: Callable[..., Any] | None = None
453
- ) -> Callable[..., Any]:
454
- """Decorator to convert a function into an MCP tool call.
455
-
456
- This decorator allows you to use a function as if it were a local function,
457
- but it will actually call the corresponding MCP tool.
458
-
459
- Args:
460
- tool_name: The name of the MCP tool to call.
461
- func: The function to decorate (optional, for decorator factory pattern).
462
-
463
- Returns:
464
- A decorated function that calls the MCP tool.
465
-
466
- Usage:
467
- @client.as_tool("my_tool")
468
- def my_function(arg1, arg2):
469
- pass
470
-
471
- # Or as a factory:
472
- my_function = client.as_tool("my_tool")
473
- """
474
-
475
- def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
476
- def wrapper(*args, **kwargs) -> Any:
477
- # Get the function signature to map arguments properly
478
- sig = inspect.signature(f)
479
- parameters = list(sig.parameters.keys())
480
-
481
- # Create a dictionary mapping positional args to parameter names
482
- arguments = {}
483
-
484
- # Map positional arguments to parameter names
485
- for i, arg in enumerate(args):
486
- if i < len(parameters):
487
- arguments[parameters[i]] = arg
488
- else:
489
- # If there are more positional args than parameters, use generic names
490
- arguments[f"arg_{i}"] = arg
491
-
492
- # Add keyword arguments (these override positional if there's a conflict)
493
- arguments.update(kwargs)
494
-
495
- # Call the MCP tool
496
- result = self.call_tool(tool_name, arguments if arguments else None)
497
- return result
498
-
499
- return wrapper
500
-
501
- if func is None:
502
- # Used as @client.as_tool("tool_name")
503
- return decorator
504
- else:
505
- # Used as @client.as_tool("tool_name", func)
506
- return decorator(func)
507
-
508
- def __enter__(self) -> MCPClient:
509
- """Context manager entry."""
510
- self.connect()
511
- return self
512
-
513
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
514
- """Context manager exit."""
515
- self.cleanup()
516
-
517
- async def __aenter__(self) -> MCPClient:
518
- """Async context manager entry."""
519
- await self.async_connect()
520
- return self
521
-
522
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
523
- """Async context manager exit."""
524
- await self.async_cleanup()
525
-
526
-
527
- # -----------------------------------------------------------------------------
528
- # Factory Function
529
- # -----------------------------------------------------------------------------
530
-
531
-
532
- @overload
533
- def create_mcp_client(
534
- type: Literal["stdio"],
535
- *,
536
- command: str,
537
- args: list[str] | None = None,
538
- env: dict[str, str] | None = None,
539
- cwd: Path | str | None = None,
540
- timeout: float = 30.0,
541
- ) -> MCPClient:
542
- """Create an MCP client with stdio transport."""
543
- ...
544
-
545
-
546
- @overload
547
- def create_mcp_client(
548
- type: Literal["sse"],
549
- *,
550
- url: str,
551
- timeout: float = 30.0,
552
- ) -> MCPClient:
553
- """Create an MCP client with SSE transport."""
554
- ...
555
-
556
-
557
- @overload
558
- def create_mcp_client(
559
- type: Literal["http"],
560
- *,
561
- url: str,
562
- timeout: float = 30.0,
563
- ) -> MCPClient:
564
- """Create an MCP client with HTTP transport."""
565
- ...
566
-
567
-
568
- def create_mcp_client(
569
- type: Literal["stdio", "sse", "http"],
570
- *,
571
- command: str | None = None,
572
- args: list[str] | None = None,
573
- env: dict[str, str] | None = None,
574
- cwd: Path | str | None = None,
575
- url: str | None = None,
576
- timeout: float = 30.0,
577
- ) -> MCPClient:
578
- """Create an MCP client with the specified transport type.
579
-
580
- Args:
581
- service_type: The type of transport to use ("stdio", "sse", or "http").
582
- command: Command to run for stdio transport.
583
- args: Arguments for the command (stdio only).
584
- env: Environment variables for the command (stdio only).
585
- cwd: Working directory for the command (stdio only).
586
- url: URL for SSE or HTTP transport.
587
- timeout: Connection timeout in seconds.
588
-
589
- Returns:
590
- A configured MCPClient instance.
591
-
592
- Raises:
593
- ValueError: If required parameters for the transport type are missing.
594
- """
595
- service_type = type
596
-
597
- if service_type == "stdio":
598
- if command is None:
599
- raise ValueError("command is required for stdio transport")
600
-
601
- service = MCPClientServiceStdio(
602
- command=command,
603
- args=args or [],
604
- env=env or {},
605
- cwd=Path(cwd) if cwd else None,
606
- )
607
-
608
- elif service_type == "sse":
609
- if url is None:
610
- raise ValueError("url is required for SSE transport")
611
-
612
- service = MCPClientServiceSse(url=url)
613
-
614
- elif service_type == "http":
615
- if url is None:
616
- raise ValueError("url is required for HTTP transport")
617
-
618
- service = MCPClientServiceStreamableHttp(url=url)
619
-
620
- else:
621
- raise ValueError(f"Unsupported service_type: {service_type}")
622
-
623
- settings = MCPClientSettings(timeout=timeout)
624
- return MCPClient(service=service, settings=settings)