langchain-mcp-tools 0.1.10__py3-none-any.whl → 0.2.0__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.
@@ -2,4 +2,5 @@
2
2
  from .langchain_mcp_tools import (
3
3
  convert_mcp_to_langchain_tools,
4
4
  McpServerCleanupFn,
5
+ McpServersConfig,
5
6
  )
@@ -1,8 +1,4 @@
1
1
  # Standard library imports
2
- from anyio.streams.memory import (
3
- MemoryObjectReceiveStream,
4
- MemoryObjectSendStream,
5
- )
6
2
  import logging
7
3
  import os
8
4
  import sys
@@ -11,21 +7,27 @@ from typing import (
11
7
  Any,
12
8
  Awaitable,
13
9
  Callable,
14
- Dict,
15
- List,
10
+ cast,
16
11
  NoReturn,
17
- Optional,
18
- Tuple,
19
- Type,
12
+ NotRequired,
13
+ TextIO,
20
14
  TypeAlias,
15
+ TypedDict,
21
16
  )
17
+ from urllib.parse import urlparse
22
18
 
23
19
  # Third-party imports
24
20
  try:
21
+ from anyio.streams.memory import (
22
+ MemoryObjectReceiveStream,
23
+ MemoryObjectSendStream,
24
+ )
25
25
  from jsonschema_pydantic import jsonschema_to_pydantic # type: ignore
26
26
  from langchain_core.tools import BaseTool, ToolException
27
- from mcp import ClientSession, StdioServerParameters
28
- from mcp.client.stdio import stdio_client
27
+ from mcp import ClientSession
28
+ from mcp.client.sse import sse_client
29
+ from mcp.client.stdio import stdio_client, StdioServerParameters
30
+ from mcp.client.websocket import websocket_client
29
31
  import mcp.types as mcp_types
30
32
  from pydantic import BaseModel
31
33
  # from pydantic_core import to_json
@@ -35,6 +37,27 @@ except ImportError as e:
35
37
  sys.exit(1)
36
38
 
37
39
 
40
+ class McpServerCommandBasedConfig(TypedDict):
41
+ command: str
42
+ args: NotRequired[list[str] | None]
43
+ env: NotRequired[dict[str, str] | None]
44
+ cwd: NotRequired[str | None]
45
+ errlog: NotRequired[TextIO | None]
46
+
47
+
48
+ class McpServerUrlBasedConfig(TypedDict):
49
+ url: str
50
+ args: NotRequired[list[str] | None]
51
+ env: NotRequired[dict[str, str] | None]
52
+ cwd: NotRequired[str | None]
53
+ errlog: NotRequired[TextIO | None]
54
+
55
+
56
+ McpServerConfig = McpServerCommandBasedConfig | McpServerUrlBasedConfig
57
+
58
+ McpServersConfig = dict[str, McpServerConfig]
59
+
60
+
38
61
  def fix_schema(schema: dict) -> dict:
39
62
  """Converts JSON Schema 'type': ['string', 'null'] to 'anyOf' format"""
40
63
  if isinstance(schema, dict):
@@ -48,7 +71,7 @@ def fix_schema(schema: dict) -> dict:
48
71
 
49
72
  # Type alias for the bidirectional communication channels with the MCP server
50
73
  # FIXME: not defined in mcp.types, really?
51
- StdioTransport: TypeAlias = tuple[
74
+ Transport: TypeAlias = tuple[
52
75
  MemoryObjectReceiveStream[mcp_types.JSONRPCMessage | Exception],
53
76
  MemoryObjectSendStream[mcp_types.JSONRPCMessage]
54
77
  ]
@@ -56,10 +79,10 @@ StdioTransport: TypeAlias = tuple[
56
79
 
57
80
  async def spawn_mcp_server_and_get_transport(
58
81
  server_name: str,
59
- server_config: Dict[str, Any],
82
+ server_config: McpServerConfig,
60
83
  exit_stack: AsyncExitStack,
61
84
  logger: logging.Logger = logging.getLogger(__name__)
62
- ) -> StdioTransport:
85
+ ) -> Transport:
63
86
  """
64
87
  Spawns an MCP server process and establishes communication channels.
65
88
 
@@ -79,43 +102,75 @@ async def spawn_mcp_server_and_get_transport(
79
102
  logger.info(f'MCP server "{server_name}": '
80
103
  f'initializing with: {server_config}')
81
104
 
82
- # NOTE: `uv` and `npx` seem to require PATH to be set.
83
- # To avoid confusion, it was decided to automatically append it
84
- # to the env if not explicitly set by the config.
85
- env = dict(server_config.get('env', {}))
86
- if 'PATH' not in env:
87
- env['PATH'] = os.environ.get('PATH', '')
88
-
89
- # Create server parameters with command, arguments and environment
90
- server_params = StdioServerParameters(
91
- command=server_config['command'],
92
- args=server_config.get('args', []),
93
- env=env
94
- )
105
+ url_str = str(server_config.get('url')) # None becomes 'None'
106
+ # no exception thrown even for a malformed URL
107
+ url_scheme = urlparse(url_str).scheme
95
108
 
96
- # Initialize stdio client and register it with exit stack for cleanup
97
- stdio_transport = await exit_stack.enter_async_context(
98
- stdio_client(server_params)
99
- )
109
+ if url_scheme in ('http', 'https'):
110
+ transport = await exit_stack.enter_async_context(
111
+ sse_client(url_str)
112
+ )
113
+
114
+ elif url_scheme in ('ws', 'wss'):
115
+ transport = await exit_stack.enter_async_context(
116
+ websocket_client(url_str)
117
+ )
118
+
119
+ else:
120
+ # NOTE: `uv` and `npx` seem to require PATH to be set.
121
+ # To avoid confusion, it was decided to automatically append it
122
+ # to the env if not explicitly set by the config.
123
+ config = cast(McpServerCommandBasedConfig, server_config)
124
+ # env = config.get('env', {}) does't work since it can yield None
125
+ env_val = config.get('env')
126
+ env = {} if env_val is None else dict(env_val)
127
+ if 'PATH' not in env:
128
+ env['PATH'] = os.environ.get('PATH', '')
129
+
130
+ # Use stdio client for commands
131
+ # args = config.get('args', []) does't work since it can yield None
132
+ args_val = config.get('args')
133
+ args = [] if args_val is None else list(args_val)
134
+ server_parameters = StdioServerParameters(
135
+ command=config.get('command', ''),
136
+ args=args,
137
+ env=env,
138
+ cwd=config.get('cwd', None)
139
+ )
140
+
141
+ # Initialize stdio client and register it with exit stack for
142
+ # cleanup
143
+ # NOTE: Why the key name `errlog` for `server_config` was chosen:
144
+ # Unlike TypeScript SDK's `StdioServerParameters`, the Python
145
+ # SDK's `StdioServerParameters` doesn't include `stderr: int`.
146
+ # Instead, it calls `stdio_client()` with a separate argument
147
+ # `errlog: TextIO`. I once included `stderr: int` for
148
+ # compatibility with the TypeScript version, but decided to
149
+ # follow the Python SDK more closely.
150
+ errlog_val = server_config.get('errlog')
151
+ kwargs = {'errlog': errlog_val} if errlog_val is not None else {}
152
+ transport = await exit_stack.enter_async_context(
153
+ stdio_client(server_parameters, **kwargs)
154
+ )
100
155
  except Exception as e:
101
156
  logger.error(f'Error spawning MCP server: {str(e)}')
102
157
  raise
103
158
 
104
- return stdio_transport
159
+ return transport
105
160
 
106
161
 
107
162
  async def get_mcp_server_tools(
108
163
  server_name: str,
109
- stdio_transport: StdioTransport,
164
+ transport: Transport,
110
165
  exit_stack: AsyncExitStack,
111
166
  logger: logging.Logger = logging.getLogger(__name__)
112
- ) -> List[BaseTool]:
167
+ ) -> list[BaseTool]:
113
168
  """
114
169
  Retrieves and converts MCP server tools to LangChain format.
115
170
 
116
171
  Args:
117
172
  server_name: Server instance name to use for better logging
118
- stdio_transport: Communication channels tuple
173
+ transport: Communication channels tuple
119
174
  exit_stack: Context manager for cleanup handling
120
175
  logger: Logger instance for debugging and monitoring
121
176
 
@@ -126,7 +181,7 @@ async def get_mcp_server_tools(
126
181
  Exception: If tool conversion fails
127
182
  """
128
183
  try:
129
- read, write = stdio_transport
184
+ read, write = transport
130
185
 
131
186
  # Use an intermediate `asynccontextmanager` to log the cleanup message
132
187
  @asynccontextmanager
@@ -153,7 +208,7 @@ async def get_mcp_server_tools(
153
208
  tools_response = await session.list_tools()
154
209
 
155
210
  # Wrap MCP tools into LangChain tools
156
- langchain_tools: List[BaseTool] = []
211
+ langchain_tools: list[BaseTool] = []
157
212
  for tool in tools_response.tools:
158
213
 
159
214
  # Define adapter class to convert MCP tool to LangChain format
@@ -161,10 +216,10 @@ async def get_mcp_server_tools(
161
216
  name: str = tool.name or 'NO NAME'
162
217
  description: str = tool.description or ''
163
218
  # Convert JSON schema to Pydantic model for argument validation
164
- args_schema: Type[BaseModel] = jsonschema_to_pydantic(
219
+ args_schema: type[BaseModel] = jsonschema_to_pydantic(
165
220
  fix_schema(tool.inputSchema) # Apply schema conversion
166
221
  )
167
- session: Optional[ClientSession] = None
222
+ session: ClientSession | None = None
168
223
 
169
224
  def _run(self, **kwargs: Any) -> NoReturn:
170
225
  raise NotImplementedError(
@@ -247,7 +302,7 @@ async def get_mcp_server_tools(
247
302
  return langchain_tools
248
303
 
249
304
 
250
- # A very simple pre-implemented logger for fallback
305
+ # A very simple pre-configured logger for fallback
251
306
  def init_logger() -> logging.Logger:
252
307
  logging.basicConfig(
253
308
  level=logging.INFO, # logging.DEBUG,
@@ -261,9 +316,9 @@ McpServerCleanupFn = Callable[[], Awaitable[None]]
261
316
 
262
317
 
263
318
  async def convert_mcp_to_langchain_tools(
264
- server_configs: Dict[str, Dict[str, Any]],
265
- logger: Optional[logging.Logger] = None
266
- ) -> Tuple[List[BaseTool], McpServerCleanupFn]:
319
+ server_configs: dict[str, McpServerConfig],
320
+ logger: logging.Logger | None = None
321
+ ) -> tuple[list[BaseTool], McpServerCleanupFn]:
267
322
  """Initialize multiple MCP servers and convert their tools to
268
323
  LangChain format.
269
324
 
@@ -287,8 +342,12 @@ async def convert_mcp_to_langchain_tools(
287
342
 
288
343
  Example:
289
344
  server_configs = {
290
- "server1": {"command": "npm", "args": ["start"]},
291
- "server2": {"command": "./server", "args": ["-p", "8000"]}
345
+ 'fetch': {
346
+ 'command': 'uvx', 'args': ['mcp-server-fetch']
347
+ },
348
+ 'weather': {
349
+ 'command': 'npx', 'args': ['-y','@h1deya/mcp-server-weather']
350
+ }
292
351
  }
293
352
  tools, cleanup = await convert_mcp_to_langchain_tools(server_configs)
294
353
  # Use tools...
@@ -299,11 +358,11 @@ async def convert_mcp_to_langchain_tools(
299
358
  logger = logging.getLogger(__name__)
300
359
  # Check if the root logger has handlers configured
301
360
  if not logging.root.handlers and not logger.handlers:
302
- # No logging configured, use our pre-implemented logger
361
+ # No logging configured, use a simple pre-configured logger
303
362
  logger = init_logger()
304
363
 
305
364
  # Initialize AsyncExitStack for managing multiple server lifecycles
306
- stdio_transports: List[StdioTransport] = []
365
+ transports: list[Transport] = []
307
366
  async_exit_stack = AsyncExitStack()
308
367
 
309
368
  # Spawn all MCP servers concurrently
@@ -312,24 +371,24 @@ async def convert_mcp_to_langchain_tools(
312
371
  # is spawned, i.e. after returning from the `await`, the spawned
313
372
  # subprocess starts its initialization independently of (so in
314
373
  # parallel with) the Python execution of the following lines.
315
- stdio_transport = await spawn_mcp_server_and_get_transport(
374
+ transport = await spawn_mcp_server_and_get_transport(
316
375
  server_name,
317
376
  server_config,
318
377
  async_exit_stack,
319
378
  logger
320
379
  )
321
- stdio_transports.append(stdio_transport)
380
+ transports.append(transport)
322
381
 
323
382
  # Convert tools from each server to LangChain format
324
- langchain_tools: List[BaseTool] = []
325
- for (server_name, server_config), stdio_transport in zip(
383
+ langchain_tools: list[BaseTool] = []
384
+ for (server_name, server_config), transport in zip(
326
385
  server_configs.items(),
327
- stdio_transports,
386
+ transports,
328
387
  strict=True
329
388
  ):
330
389
  tools = await get_mcp_server_tools(
331
390
  server_name,
332
- stdio_transport,
391
+ transport,
333
392
  async_exit_stack,
334
393
  logger
335
394
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-mcp-tools
3
- Version: 0.1.10
3
+ Version: 0.2.0
4
4
  Summary: Model Context Protocol (MCP) To LangChain Tools Conversion Utility
5
5
  Project-URL: Bug Tracker, https://github.com/hideya/langchain-mcp-tools-py/issues
6
6
  Project-URL: Source Code, https://github.com/hideya/langchain-mcp-tools-py
@@ -10,8 +10,9 @@ Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: jsonschema-pydantic>=0.6
12
12
  Requires-Dist: langchain>=0.3.14
13
- Requires-Dist: mcp>=1.2.0
13
+ Requires-Dist: mcp>=1.6.0
14
14
  Requires-Dist: pyjson5>=1.6.8
15
+ Requires-Dist: websockets>=15.0.1
15
16
  Provides-Extra: dev
16
17
  Requires-Dist: dotenv>=0.9.9; extra == "dev"
17
18
  Requires-Dist: langchain-anthropic>=0.3.1; extra == "dev"
@@ -35,21 +36,20 @@ dramatically expands LLM’s scope
35
36
  by enabling external tool and resource integration, including
36
37
  Google Drive, Slack, Notion, Spotify, Docker, PostgreSQL, and more…
37
38
 
38
- Over 1500 functional components available as MCP servers:
39
+ Over 2000 functional components available as MCP servers:
39
40
 
41
+ - [MCP Server Listing on the Official Site](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#model-context-protocol-servers)
40
42
  - [MCP.so - Find Awesome MCP Servers and Clients](https://mcp.so/)
41
43
  - [Smithery: MCP Server Registry](https://smithery.ai/)
42
- - [pulse - Browse and discover MCP use cases, servers, clients, and news](https://www.pulsemcp.com/)
43
- - [MCP Get Started/Example Servers](https://modelcontextprotocol.io/examples)
44
44
 
45
- The goal of this utility is to make these 1500+ MCP servers readily accessible from LangChain.
45
+ The goal of this utility is to make these 2000+ MCP servers readily accessible from LangChain.
46
46
 
47
47
  It contains a utility function `convert_mcp_to_langchain_tools()`.
48
48
  This async function handles parallel initialization of specified multiple MCP servers
49
49
  and converts their available tools into a list of LangChain-compatible tools.
50
50
 
51
51
  For detailed information on how to use this library, please refer to the following document:
52
- - ["Supercharging LangChain: Integrating 1500+ MCP with ReAct"](https://medium.com/@h1deya/supercharging-langchain-integrating-1500-mcp-with-react-d4e467cbf41a)
52
+ - ["Supercharging LangChain: Integrating 2000+ MCP with ReAct"](https://medium.com/@h1deya/supercharging-langchain-integrating-450-mcp-with-react-d4e467cbf41a)
53
53
 
54
54
  A typescript equivalent of this utility is available
55
55
  [here](https://www.npmjs.com/package/@h1deya/langchain-mcp-tools)
@@ -73,25 +73,25 @@ but only the contents of the `mcpServers` property,
73
73
  and is expressed as a `dict`, e.g.:
74
74
 
75
75
  ```python
76
- mcp_configs = {
77
- 'filesystem': {
78
- 'command': 'npx',
79
- 'args': ['-y', '@modelcontextprotocol/server-filesystem', '.']
76
+ mcp_servers = {
77
+ "filesystem": {
78
+ "command": "npx",
79
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
80
80
  },
81
- 'fetch': {
82
- 'command': 'uvx',
83
- 'args': ['mcp-server-fetch']
81
+ "fetch": {
82
+ "command": "uvx",
83
+ "args": ["mcp-server-fetch"]
84
84
  }
85
85
  }
86
86
 
87
87
  tools, cleanup = await convert_mcp_to_langchain_tools(
88
- mcp_configs
88
+ mcp_servers
89
89
  )
90
90
  ```
91
91
 
92
92
  This utility function initializes all specified MCP servers in parallel,
93
93
  and returns LangChain Tools
94
- ([`tools: List[BaseTool]`](https://python.langchain.com/api_reference/core/tools/langchain_core.tools.base.BaseTool.html#langchain_core.tools.base.BaseTool))
94
+ ([`tools: list[BaseTool]`](https://python.langchain.com/api_reference/core/tools/langchain_core.tools.base.BaseTool.html#langchain_core.tools.base.BaseTool))
95
95
  by gathering available MCP tools from the servers,
96
96
  and by wrapping them into LangChain tools.
97
97
  It also returns an async callback function (`cleanup: McpServerCleanupFn`)
@@ -120,11 +120,64 @@ For hands-on experimentation with MCP server integration,
120
120
  try [this LangChain application built with the utility](https://github.com/hideya/mcp-client-langchain-py)
121
121
 
122
122
  For detailed information on how to use this library, please refer to the following document:
123
- ["Supercharging LangChain: Integrating 1500+ MCP with ReAct"](https://medium.com/@h1deya/supercharging-langchain-integrating-1500-mcp-with-react-d4e467cbf41a)
123
+ ["Supercharging LangChain: Integrating 2000+ MCP with ReAct"](https://medium.com/@h1deya/supercharging-langchain-integrating-450-mcp-with-react-d4e467cbf41a)
124
+
125
+ ## Experimental Features
126
+
127
+ ### Remote MCP Server Support
128
+
129
+ `mcp_servers` configuration for SSE and Websocket servers are as follows:
130
+
131
+ ```python
132
+ "sse-server-name": {
133
+ "url": f"http://{sse_server_host}:{sse_server_port}/..."
134
+ },
135
+
136
+ "ws-server-name": {
137
+ "url": f"ws://{ws_server_host}:{ws_server_port}/..."
138
+ },
139
+ ```
140
+
141
+ Note that the key name `"url"` may be changed in the future to match
142
+ the MCP server configurations used by Claude for Desktop once
143
+ it introduces remote server support.
144
+
145
+ ### Working Directory Configuration for Local MCP Servers
146
+
147
+ The working directory that is used when spawning a local MCP server
148
+ can be specified with the `cwd` key as follows:
149
+
150
+ ```python
151
+ "local-server-name": {
152
+ "command": "...",
153
+ "args": [...],
154
+ "cwd": "/working/directory" # the working dir to be use by the server
155
+ },
156
+ ```
157
+
158
+ ### Configuration for MCP Server stderr Redirection
159
+
160
+ A new key `errlog` has been introduced in to specify a file-like object
161
+ to which MCP server's stderr is redirected.
162
+
163
+ ```python
164
+ log_path = f"mcp-server-{server_name}.log"
165
+ log_file = open(log_path, "w")
166
+ mcp_servers[server_name]["errlog"] = log_file
167
+ ```
168
+
169
+ **NOTE: Why the key name `errlog` for `server_config` was chosen:**
170
+ Unlike TypeScript SDK's `StdioServerParameters`, the Python
171
+ SDK's `StdioServerParameters` doesn't include `stderr: int`.
172
+ Instead, it calls `stdio_client()` with a separate argument
173
+ `errlog: TextIO`. I once included `stderr: int` for
174
+ compatibility with the TypeScript version, but decided to
175
+ follow the Python SDK more closely.
124
176
 
125
177
  ## Limitations
126
178
 
127
- Currently, only text results of tool calls are supported.
179
+ - Currently, only text results of tool calls are supported.
180
+ - Fatures other than [Tools](https://modelcontextprotocol.io/docs/concepts/tools) are not supported.
128
181
 
129
182
  ## Change Log
130
183
 
@@ -0,0 +1,8 @@
1
+ langchain_mcp_tools/__init__.py,sha256=iatHG2fCpz143wgQUZpyFVilgri4yOh2P0vxr22gguE,114
2
+ langchain_mcp_tools/langchain_mcp_tools.py,sha256=n4u9fN3_sVMG1CmSKk8FrlXrBt79Ly1tz0TLhmDUaic,15118
3
+ langchain_mcp_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ langchain_mcp_tools-0.2.0.dist-info/licenses/LICENSE,sha256=CRC91e8v116gCpnp7h49oIa6_zjhxqnHFTREeoZFJwA,1072
5
+ langchain_mcp_tools-0.2.0.dist-info/METADATA,sha256=oCzGJZvkhbhF_OA8Pm-Iy2JSjbeIJEEK1cr8pPxxh1g,6808
6
+ langchain_mcp_tools-0.2.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
7
+ langchain_mcp_tools-0.2.0.dist-info/top_level.txt,sha256=aR_9V2A1Yt-Bca60KmndmGLUWb2wiM5IOG-Gkaf1dxY,20
8
+ langchain_mcp_tools-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.0.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- langchain_mcp_tools/__init__.py,sha256=Xtv2VphhrWB_KlxTIofHZqtCIGtNEl0MxugnrNXTERA,94
2
- langchain_mcp_tools/langchain_mcp_tools.py,sha256=vIiOB8u62Q-7Mi8U6XI247g-xfxpE2QvoBeo0pbtFX4,12838
3
- langchain_mcp_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- langchain_mcp_tools-0.1.10.dist-info/licenses/LICENSE,sha256=CRC91e8v116gCpnp7h49oIa6_zjhxqnHFTREeoZFJwA,1072
5
- langchain_mcp_tools-0.1.10.dist-info/METADATA,sha256=kAMCbY0YTAA5aj-SaiGqtAanX30ggpEeZO39zChua0Y,5135
6
- langchain_mcp_tools-0.1.10.dist-info/WHEEL,sha256=DK49LOLCYiurdXXOXwGJm6U4DkHkg4lcxjhqwRa0CP4,91
7
- langchain_mcp_tools-0.1.10.dist-info/top_level.txt,sha256=aR_9V2A1Yt-Bca60KmndmGLUWb2wiM5IOG-Gkaf1dxY,20
8
- langchain_mcp_tools-0.1.10.dist-info/RECORD,,