mcp-use 1.2.11__tar.gz → 1.2.13__tar.gz
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.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- {mcp_use-1.2.11 → mcp_use-1.2.13}/.github/workflows/publish.yml +8 -4
- mcp_use-1.2.11/.github/workflows/tests.yml → mcp_use-1.2.13/.github/workflows/unittests.yml +1 -1
- {mcp_use-1.2.11 → mcp_use-1.2.13}/PKG-INFO +32 -16
- {mcp_use-1.2.11 → mcp_use-1.2.13}/README.md +31 -15
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/adapters/base.py +31 -10
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/adapters/langchain_adapter.py +112 -2
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/client.py +6 -5
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/connectors/base.py +83 -20
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/session.py +3 -29
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/task_managers/base.py +3 -5
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/task_managers/sse.py +2 -5
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/task_managers/stdio.py +2 -6
- mcp_use-1.2.13/mcp_use/task_managers/websocket.py +63 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/pyproject.toml +1 -1
- mcp_use-1.2.13/static/logo_black.svg +8 -0
- mcp_use-1.2.13/static/logo_white.svg +8 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/unit/test_client.py +3 -3
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/unit/test_http_connector.py +69 -27
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/unit/test_session.py +0 -58
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/unit/test_stdio_connector.py +75 -22
- mcp_use-1.2.11/mcp_use/task_managers/websocket.py +0 -63
- mcp_use-1.2.11/static/image.jpg +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/.github/pull_request_template.md +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/.gitignore +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/.pre-commit-config.yaml +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/CONTRIBUTING.md +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/LICENSE +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/README.md +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/api-reference/introduction.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/api-reference/mcpagent.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/building-custom-agents.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/development.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/docs.json +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/essentials/configuration.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/essentials/connection-types.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/essentials/debugging.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/essentials/llm-integration.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/essentials/server-manager.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/favicon.svg +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/images/hero-dark.png +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/images/hero-light.png +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/introduction.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/logo/dark.svg +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/logo/light.svg +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/quickstart.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/docs/snippets/snippet-intro.mdx +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/airbnb_mcp.json +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/airbnb_use.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/blender_use.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/browser_mcp.json +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/browser_use.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/chat_example.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/filesystem_use.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/http_example.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/mcp_everything.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/examples/multi_server_example.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/adapters/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/agents/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/agents/base.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/agents/mcpagent.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/agents/prompts/system_prompt_builder.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/agents/prompts/templates.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/config.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/connectors/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/connectors/http.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/connectors/stdio.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/connectors/websocket.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/logging.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/server_manager.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/base_tool.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/connect_server.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/disconnect_server.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/get_active_server.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/list_servers_tool.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/search_tools.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/managers/tools/use_tool.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/mcp_use/task_managers/__init__.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/pytest.ini +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/ruff.toml +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/conftest.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/unit/test_config.py +0 -0
- {mcp_use-1.2.11 → mcp_use-1.2.13}/tests/unit/test_logging.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
name: Check Version Bump and Publish to PyPI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ["Python Tests"]
|
|
5
6
|
branches:
|
|
6
7
|
- main
|
|
7
|
-
|
|
8
|
-
-
|
|
8
|
+
types:
|
|
9
|
+
- completed
|
|
9
10
|
|
|
10
11
|
# Required for PyPI trusted publishing
|
|
11
12
|
permissions:
|
|
@@ -14,11 +15,14 @@ permissions:
|
|
|
14
15
|
|
|
15
16
|
jobs:
|
|
16
17
|
check-version-and-publish:
|
|
18
|
+
# Only proceed if the referenced workflow completed successfully
|
|
19
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
17
20
|
runs-on: ubuntu-latest
|
|
18
21
|
steps:
|
|
19
22
|
- uses: actions/checkout@v3
|
|
20
23
|
with:
|
|
21
|
-
fetch-depth: 0
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
ref: ${{ github.event.workflow_run.head_sha }}
|
|
22
26
|
|
|
23
27
|
- name: Set up Python
|
|
24
28
|
uses: actions/setup-python@v4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-use
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.13
|
|
4
4
|
Summary: MCP Library for LLMs
|
|
5
5
|
Author-email: Pietro Zullo <pietro.zullo@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -39,23 +39,39 @@ Provides-Extra: search
|
|
|
39
39
|
Requires-Dist: fastembed>=0.0.1; extra == 'search'
|
|
40
40
|
Description-Content-Type: text/markdown
|
|
41
41
|
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
42
|
+
<div align="center" style="margin: 0 auto; max-width: 80%;">
|
|
43
|
+
<picture>
|
|
44
|
+
<source media="(prefers-color-scheme: dark)" srcset="static/logo_white.svg">
|
|
45
|
+
<source media="(prefers-color-scheme: light)" srcset="static/logo_black.svg">
|
|
46
|
+
<img alt="mcp use logo" src="./static/logo-white.svg" width="80%" style="margin: 20px auto;">
|
|
47
|
+
</picture>
|
|
48
|
+
</div>
|
|
45
49
|
|
|
46
50
|
<h1 align="center">Unified MCP Client Library </h1>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
<p align="center">
|
|
52
|
+
<a href="https://pypi.org/project/mcp_use/" alt="PyPI Version">
|
|
53
|
+
<img src="https://img.shields.io/pypi/v/mcp_use.svg"/></a>
|
|
54
|
+
<a href="https://pypi.org/project/mcp_use/" alt="PyPI Downloads">
|
|
55
|
+
<img src="https://static.pepy.tech/badge/mcp-use" /></a>
|
|
56
|
+
<a href="https://pypi.org/project/mcp_use/" alt="Python Versions">
|
|
57
|
+
<img src="https://img.shields.io/pypi/pyversions/mcp_use.svg" /></a>
|
|
58
|
+
<a href="https://docs.mcp-use.io" alt="Documentation">
|
|
59
|
+
<img src="https://img.shields.io/badge/docs-mcp--use.io-blue" /></a>
|
|
60
|
+
<a href="https://mcp-use.io" alt="Website">
|
|
61
|
+
<img src="https://img.shields.io/badge/website-mcp--use.io-blue" /></a>
|
|
62
|
+
<a href="https://github.com/pietrozullo/mcp-use/blob/main/LICENSE" alt="License">
|
|
63
|
+
<img src="https://img.shields.io/github/license/pietrozullo/mcp-use" /></a>
|
|
64
|
+
<a href="https://github.com/astral-sh/ruff" alt="Code style: Ruff">
|
|
65
|
+
<img src="https://img.shields.io/badge/code%20style-ruff-000000.svg" /></a>
|
|
66
|
+
<a href="https://github.com/pietrozullo/mcp-use/stargazers" alt="GitHub stars">
|
|
67
|
+
<img src="https://img.shields.io/github/stars/pietrozullo/mcp-use?style=social" /></a>
|
|
68
|
+
</p>
|
|
69
|
+
<p align="center">
|
|
70
|
+
<a href="https://x.com/pietrozullo" alt="Twitter Follow">
|
|
71
|
+
<img src="https://img.shields.io/twitter/follow/Pietro?style=social" /></a>
|
|
72
|
+
<a href="https://discord.gg/XkNkSkMz3V" alt="Discord">
|
|
73
|
+
<img src="https://dcbadge.limes.pink/api/server/https://discord.gg/XkNkSkMz3V?style=flat" /></a>
|
|
74
|
+
</p>
|
|
59
75
|
🌐 MCP-Use is the open source way to connect **any LLM to any MCP server** and build custom agents that have tool access, without using closed source or application clients.
|
|
60
76
|
|
|
61
77
|
💡 Let developers easily connect any LLM to tools like web browsing, file operations, and more.
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
1
|
+
<div align="center" style="margin: 0 auto; max-width: 80%;">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="static/logo_white.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="static/logo_black.svg">
|
|
5
|
+
<img alt="mcp use logo" src="./static/logo-white.svg" width="80%" style="margin: 20px auto;">
|
|
6
|
+
</picture>
|
|
7
|
+
</div>
|
|
4
8
|
|
|
5
9
|
<h1 align="center">Unified MCP Client Library </h1>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://pypi.org/project/mcp_use/" alt="PyPI Version">
|
|
12
|
+
<img src="https://img.shields.io/pypi/v/mcp_use.svg"/></a>
|
|
13
|
+
<a href="https://pypi.org/project/mcp_use/" alt="PyPI Downloads">
|
|
14
|
+
<img src="https://static.pepy.tech/badge/mcp-use" /></a>
|
|
15
|
+
<a href="https://pypi.org/project/mcp_use/" alt="Python Versions">
|
|
16
|
+
<img src="https://img.shields.io/pypi/pyversions/mcp_use.svg" /></a>
|
|
17
|
+
<a href="https://docs.mcp-use.io" alt="Documentation">
|
|
18
|
+
<img src="https://img.shields.io/badge/docs-mcp--use.io-blue" /></a>
|
|
19
|
+
<a href="https://mcp-use.io" alt="Website">
|
|
20
|
+
<img src="https://img.shields.io/badge/website-mcp--use.io-blue" /></a>
|
|
21
|
+
<a href="https://github.com/pietrozullo/mcp-use/blob/main/LICENSE" alt="License">
|
|
22
|
+
<img src="https://img.shields.io/github/license/pietrozullo/mcp-use" /></a>
|
|
23
|
+
<a href="https://github.com/astral-sh/ruff" alt="Code style: Ruff">
|
|
24
|
+
<img src="https://img.shields.io/badge/code%20style-ruff-000000.svg" /></a>
|
|
25
|
+
<a href="https://github.com/pietrozullo/mcp-use/stargazers" alt="GitHub stars">
|
|
26
|
+
<img src="https://img.shields.io/github/stars/pietrozullo/mcp-use?style=social" /></a>
|
|
27
|
+
</p>
|
|
28
|
+
<p align="center">
|
|
29
|
+
<a href="https://x.com/pietrozullo" alt="Twitter Follow">
|
|
30
|
+
<img src="https://img.shields.io/twitter/follow/Pietro?style=social" /></a>
|
|
31
|
+
<a href="https://discord.gg/XkNkSkMz3V" alt="Discord">
|
|
32
|
+
<img src="https://dcbadge.limes.pink/api/server/https://discord.gg/XkNkSkMz3V?style=flat" /></a>
|
|
33
|
+
</p>
|
|
18
34
|
🌐 MCP-Use is the open source way to connect **any LLM to any MCP server** and build custom agents that have tool access, without using closed source or application clients.
|
|
19
35
|
|
|
20
36
|
💡 Let developers easily connect any LLM to tools like web browsing, file operations, and more.
|
|
@@ -5,7 +5,9 @@ This module provides the abstract base class that all MCP tool adapters should i
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
from mcp.types import Prompt, Resource, Tool
|
|
9
11
|
|
|
10
12
|
from ..client import MCPClient
|
|
11
13
|
from ..connectors.base import BaseConnector
|
|
@@ -90,13 +92,13 @@ class BaseAdapter(ABC):
|
|
|
90
92
|
return self._connector_tool_map[connector]
|
|
91
93
|
|
|
92
94
|
# Create tools for this connector
|
|
93
|
-
connector_tools = []
|
|
94
95
|
|
|
95
96
|
# Make sure the connector is initialized and has tools
|
|
96
97
|
success = await self._ensure_connector_initialized(connector)
|
|
97
98
|
if not success:
|
|
98
99
|
return []
|
|
99
100
|
|
|
101
|
+
connector_tools = []
|
|
100
102
|
# Now create tools for each MCP tool
|
|
101
103
|
for tool in connector.tools:
|
|
102
104
|
# Convert the tool and add it to the list if conversion was successful
|
|
@@ -104,6 +106,23 @@ class BaseAdapter(ABC):
|
|
|
104
106
|
if converted_tool:
|
|
105
107
|
connector_tools.append(converted_tool)
|
|
106
108
|
|
|
109
|
+
# Convert resources to tools so that agents can access resource content directly
|
|
110
|
+
resources_list = connector.resources or []
|
|
111
|
+
if resources_list:
|
|
112
|
+
for resource in resources_list:
|
|
113
|
+
converted_resource = self._convert_resource(resource, connector)
|
|
114
|
+
if converted_resource:
|
|
115
|
+
connector_tools.append(converted_resource)
|
|
116
|
+
|
|
117
|
+
# Convert prompts to tools so that agents can retrieve prompt content
|
|
118
|
+
prompts_list = connector.prompts or []
|
|
119
|
+
if prompts_list:
|
|
120
|
+
for prompt in prompts_list:
|
|
121
|
+
converted_prompt = self._convert_prompt(prompt, connector)
|
|
122
|
+
if converted_prompt:
|
|
123
|
+
connector_tools.append(converted_prompt)
|
|
124
|
+
# ------------------------------
|
|
125
|
+
|
|
107
126
|
# Store the tools for this connector
|
|
108
127
|
self._connector_tool_map[connector] = connector_tools
|
|
109
128
|
|
|
@@ -116,16 +135,18 @@ class BaseAdapter(ABC):
|
|
|
116
135
|
return connector_tools
|
|
117
136
|
|
|
118
137
|
@abstractmethod
|
|
119
|
-
def _convert_tool(self, mcp_tool:
|
|
120
|
-
"""Convert an MCP tool to the target framework's tool format.
|
|
138
|
+
def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> T:
|
|
139
|
+
"""Convert an MCP tool to the target framework's tool format."""
|
|
140
|
+
pass
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> T:
|
|
144
|
+
"""Convert an MCP resource to the target framework's resource format."""
|
|
145
|
+
pass
|
|
125
146
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"""
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> T:
|
|
149
|
+
"""Convert an MCP prompt to the target framework's prompt format."""
|
|
129
150
|
pass
|
|
130
151
|
|
|
131
152
|
async def _create_tools_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
|
|
@@ -4,12 +4,21 @@ LangChain adapter for MCP tools.
|
|
|
4
4
|
This module provides utilities to convert MCP tools to LangChain tools.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import re
|
|
7
8
|
from typing import Any, NoReturn
|
|
8
9
|
|
|
9
10
|
from jsonschema_pydantic import jsonschema_to_pydantic
|
|
10
11
|
from langchain_core.tools import BaseTool, ToolException
|
|
11
|
-
from mcp.types import
|
|
12
|
-
|
|
12
|
+
from mcp.types import (
|
|
13
|
+
CallToolResult,
|
|
14
|
+
EmbeddedResource,
|
|
15
|
+
ImageContent,
|
|
16
|
+
Prompt,
|
|
17
|
+
ReadResourceRequestParams,
|
|
18
|
+
Resource,
|
|
19
|
+
TextContent,
|
|
20
|
+
)
|
|
21
|
+
from pydantic import BaseModel, Field, create_model
|
|
13
22
|
|
|
14
23
|
from ..connectors.base import BaseConnector
|
|
15
24
|
from ..logging import logger
|
|
@@ -162,3 +171,104 @@ class LangChainAdapter(BaseAdapter):
|
|
|
162
171
|
raise
|
|
163
172
|
|
|
164
173
|
return McpToLangChainAdapter()
|
|
174
|
+
|
|
175
|
+
def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> BaseTool:
|
|
176
|
+
"""Convert an MCP resource to LangChain's tool format.
|
|
177
|
+
|
|
178
|
+
Each resource becomes an async tool that returns its content when called.
|
|
179
|
+
The tool takes **no** arguments because the resource URI is fixed.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def _sanitize(name: str) -> str:
|
|
183
|
+
return re.sub(r"[^A-Za-z0-9_]+", "_", name).lower().strip("_")
|
|
184
|
+
|
|
185
|
+
class ResourceTool(BaseTool):
|
|
186
|
+
name: str = _sanitize(mcp_resource.name or f"resource_{mcp_resource.uri}")
|
|
187
|
+
description: str = (
|
|
188
|
+
mcp_resource.description
|
|
189
|
+
or f"Return the content of the resource located at URI {mcp_resource.uri}."
|
|
190
|
+
)
|
|
191
|
+
args_schema: type[BaseModel] = ReadResourceRequestParams
|
|
192
|
+
tool_connector: BaseConnector = connector
|
|
193
|
+
handle_tool_error: bool = True
|
|
194
|
+
|
|
195
|
+
def _run(self, **kwargs: Any) -> NoReturn:
|
|
196
|
+
raise NotImplementedError("Resource tools only support async operations")
|
|
197
|
+
|
|
198
|
+
async def _arun(self, **kwargs: Any) -> Any:
|
|
199
|
+
logger.debug(f'Resource tool: "{self.name}" called')
|
|
200
|
+
try:
|
|
201
|
+
result = await self.tool_connector.read_resource(mcp_resource.uri)
|
|
202
|
+
for content in result.contents:
|
|
203
|
+
# Attempt to decode bytes if necessary
|
|
204
|
+
if isinstance(content, bytes):
|
|
205
|
+
content_decoded = content.decode()
|
|
206
|
+
else:
|
|
207
|
+
content_decoded = str(content)
|
|
208
|
+
|
|
209
|
+
return content_decoded
|
|
210
|
+
except Exception as e:
|
|
211
|
+
if self.handle_tool_error:
|
|
212
|
+
return f"Error reading resource: {str(e)}"
|
|
213
|
+
raise
|
|
214
|
+
|
|
215
|
+
return ResourceTool()
|
|
216
|
+
|
|
217
|
+
def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> BaseTool:
|
|
218
|
+
"""Convert an MCP prompt to LangChain's tool format.
|
|
219
|
+
|
|
220
|
+
The resulting tool executes `get_prompt` on the connector with the prompt's name and
|
|
221
|
+
the user-provided arguments (if any). The tool returns the decoded prompt content.
|
|
222
|
+
"""
|
|
223
|
+
prompt_arguments = mcp_prompt.arguments
|
|
224
|
+
|
|
225
|
+
# Sanitize the prompt name to create a valid Python identifier for the model name
|
|
226
|
+
base_model_name = re.sub(r"[^a-zA-Z0-9_]", "_", mcp_prompt.name)
|
|
227
|
+
if not base_model_name or base_model_name[0].isdigit():
|
|
228
|
+
base_model_name = "PromptArgs_" + base_model_name
|
|
229
|
+
dynamic_model_name = f"{base_model_name}_InputSchema"
|
|
230
|
+
|
|
231
|
+
if prompt_arguments:
|
|
232
|
+
field_definitions_for_create: dict[str, Any] = {}
|
|
233
|
+
for arg in prompt_arguments:
|
|
234
|
+
param_type: type = getattr(arg, "type", str)
|
|
235
|
+
if arg.required:
|
|
236
|
+
field_definitions_for_create[arg.name] = (
|
|
237
|
+
param_type,
|
|
238
|
+
Field(description=arg.description),
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
field_definitions_for_create[arg.name] = (
|
|
242
|
+
param_type | None,
|
|
243
|
+
Field(None, description=arg.description),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
InputSchema = create_model(
|
|
247
|
+
dynamic_model_name, **field_definitions_for_create, __base__=BaseModel
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
# Create an empty Pydantic model if there are no arguments
|
|
251
|
+
InputSchema = create_model(dynamic_model_name, __base__=BaseModel)
|
|
252
|
+
|
|
253
|
+
class PromptTool(BaseTool):
|
|
254
|
+
name: str = mcp_prompt.name
|
|
255
|
+
description: str = mcp_prompt.description
|
|
256
|
+
|
|
257
|
+
args_schema: type[BaseModel] = InputSchema
|
|
258
|
+
tool_connector: BaseConnector = connector
|
|
259
|
+
handle_tool_error: bool = True
|
|
260
|
+
|
|
261
|
+
def _run(self, **kwargs: Any) -> NoReturn:
|
|
262
|
+
raise NotImplementedError("Prompt tools only support async operations")
|
|
263
|
+
|
|
264
|
+
async def _arun(self, **kwargs: Any) -> Any:
|
|
265
|
+
logger.debug(f'Prompt tool: "{self.name}" called with args: {kwargs}')
|
|
266
|
+
try:
|
|
267
|
+
result = await self.tool_connector.get_prompt(self.name, kwargs)
|
|
268
|
+
return result.messages
|
|
269
|
+
except Exception as e:
|
|
270
|
+
if self.handle_tool_error:
|
|
271
|
+
return f"Error fetching prompt: {str(e)}"
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
return PromptTool()
|
|
@@ -6,6 +6,7 @@ and sessions from configuration.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
+
import warnings
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from .config import create_connector_from_config, load_config_file
|
|
@@ -115,12 +116,12 @@ class MCPClient:
|
|
|
115
116
|
The created MCPSession.
|
|
116
117
|
|
|
117
118
|
Raises:
|
|
118
|
-
ValueError: If
|
|
119
|
+
ValueError: If the specified server doesn't exist.
|
|
119
120
|
"""
|
|
120
121
|
# Get server config
|
|
121
122
|
servers = self.config.get("mcpServers", {})
|
|
122
123
|
if not servers:
|
|
123
|
-
|
|
124
|
+
warnings.warn("No MCP servers defined in config", UserWarning, stacklevel=2)
|
|
124
125
|
return None
|
|
125
126
|
|
|
126
127
|
if server_name not in servers:
|
|
@@ -153,13 +154,13 @@ class MCPClient:
|
|
|
153
154
|
Returns:
|
|
154
155
|
The created MCPSession. If server_name is None, returns the first created session.
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
Warns:
|
|
158
|
+
Warning: If no servers are configured.
|
|
158
159
|
"""
|
|
159
160
|
# Get server config
|
|
160
161
|
servers = self.config.get("mcpServers", {})
|
|
161
162
|
if not servers:
|
|
162
|
-
|
|
163
|
+
warnings.warn("No MCP servers defined in config", UserWarning, stacklevel=2)
|
|
163
164
|
return {}
|
|
164
165
|
|
|
165
166
|
# Create sessions for all servers
|
|
@@ -9,7 +9,8 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from mcp import ClientSession
|
|
12
|
-
from mcp.
|
|
12
|
+
from mcp.shared.exceptions import McpError
|
|
13
|
+
from mcp.types import CallToolResult, GetPromptResult, Prompt, ReadResourceResult, Resource, Tool
|
|
13
14
|
|
|
14
15
|
from ..logging import logger
|
|
15
16
|
from ..task_managers import ConnectionManager
|
|
@@ -26,6 +27,8 @@ class BaseConnector(ABC):
|
|
|
26
27
|
self.client: ClientSession | None = None
|
|
27
28
|
self._connection_manager: ConnectionManager | None = None
|
|
28
29
|
self._tools: list[Tool] | None = None
|
|
30
|
+
self._resources: list[Resource] | None = None
|
|
31
|
+
self._prompts: list[Prompt] | None = None
|
|
29
32
|
self._connected = False
|
|
30
33
|
|
|
31
34
|
@abstractmethod
|
|
@@ -74,6 +77,8 @@ class BaseConnector(ABC):
|
|
|
74
77
|
|
|
75
78
|
# Reset tools
|
|
76
79
|
self._tools = None
|
|
80
|
+
self._resources = None
|
|
81
|
+
self._prompts = None
|
|
77
82
|
|
|
78
83
|
if errors:
|
|
79
84
|
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
@@ -88,21 +93,58 @@ class BaseConnector(ABC):
|
|
|
88
93
|
# Initialize the session
|
|
89
94
|
result = await self.client.initialize()
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
server_capabilities = result.capabilities
|
|
97
|
+
|
|
98
|
+
if server_capabilities.tools:
|
|
99
|
+
# Get available tools
|
|
100
|
+
tools_result = await self.list_tools()
|
|
101
|
+
self._tools = tools_result or []
|
|
102
|
+
else:
|
|
103
|
+
self._tools = []
|
|
104
|
+
|
|
105
|
+
if server_capabilities.resources:
|
|
106
|
+
# Get available resources
|
|
107
|
+
resources_result = await self.list_resources()
|
|
108
|
+
self._resources = resources_result or []
|
|
109
|
+
else:
|
|
110
|
+
self._resources = []
|
|
111
|
+
|
|
112
|
+
if server_capabilities.prompts:
|
|
113
|
+
# Get available prompts
|
|
114
|
+
prompts_result = await self.list_prompts()
|
|
115
|
+
self._prompts = prompts_result or []
|
|
116
|
+
else:
|
|
117
|
+
self._prompts = []
|
|
118
|
+
|
|
119
|
+
logger.debug(
|
|
120
|
+
f"MCP session initialized with {len(self._tools)} tools, "
|
|
121
|
+
f"{len(self._resources)} resources, "
|
|
122
|
+
f"and {len(self._prompts)} prompts"
|
|
123
|
+
)
|
|
96
124
|
|
|
97
125
|
return result
|
|
98
126
|
|
|
99
127
|
@property
|
|
100
128
|
def tools(self) -> list[Tool]:
|
|
101
129
|
"""Get the list of available tools."""
|
|
102
|
-
if
|
|
130
|
+
if self._tools is None:
|
|
103
131
|
raise RuntimeError("MCP client is not initialized")
|
|
104
132
|
return self._tools
|
|
105
133
|
|
|
134
|
+
@property
|
|
135
|
+
def resources(self) -> list[Resource]:
|
|
136
|
+
"""Get the list of available resources."""
|
|
137
|
+
if self._resources is None:
|
|
138
|
+
raise RuntimeError("MCP client is not initialized")
|
|
139
|
+
return self._resources
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def prompts(self) -> list[Prompt]:
|
|
143
|
+
"""Get the list of available prompts."""
|
|
144
|
+
if self._prompts is None:
|
|
145
|
+
raise RuntimeError("MCP client is not initialized")
|
|
146
|
+
return self._prompts
|
|
147
|
+
|
|
106
148
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
107
149
|
"""Call an MCP tool with the given arguments."""
|
|
108
150
|
if not self.client:
|
|
@@ -113,43 +155,64 @@ class BaseConnector(ABC):
|
|
|
113
155
|
logger.debug(f"Tool '{name}' called with result: {result}")
|
|
114
156
|
return result
|
|
115
157
|
|
|
116
|
-
async def
|
|
158
|
+
async def list_tools(self) -> list[Tool]:
|
|
159
|
+
"""List all available tools from the MCP implementation."""
|
|
160
|
+
if not self.client:
|
|
161
|
+
raise RuntimeError("MCP client is not connected")
|
|
162
|
+
|
|
163
|
+
logger.debug("Listing tools")
|
|
164
|
+
try:
|
|
165
|
+
result = await self.client.list_tools()
|
|
166
|
+
return result.tools
|
|
167
|
+
except McpError as e:
|
|
168
|
+
logger.error(f"Error listing tools: {e}")
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
async def list_resources(self) -> list[Resource]:
|
|
117
172
|
"""List all available resources from the MCP implementation."""
|
|
118
173
|
if not self.client:
|
|
119
174
|
raise RuntimeError("MCP client is not connected")
|
|
120
175
|
|
|
121
176
|
logger.debug("Listing resources")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
try:
|
|
178
|
+
result = await self.client.list_resources()
|
|
179
|
+
return result.resources
|
|
180
|
+
except McpError as e:
|
|
181
|
+
logger.error(f"Error listing resources: {e}")
|
|
182
|
+
return []
|
|
183
|
+
|
|
184
|
+
async def read_resource(self, uri: str) -> ReadResourceResult:
|
|
126
185
|
"""Read a resource by URI."""
|
|
127
186
|
if not self.client:
|
|
128
187
|
raise RuntimeError("MCP client is not connected")
|
|
129
188
|
|
|
130
189
|
logger.debug(f"Reading resource: {uri}")
|
|
131
|
-
|
|
132
|
-
return
|
|
190
|
+
result = await self.client.read_resource(uri)
|
|
191
|
+
return result
|
|
133
192
|
|
|
134
|
-
async def list_prompts(self) -> list[
|
|
193
|
+
async def list_prompts(self) -> list[Prompt]:
|
|
135
194
|
"""List all available prompts from the MCP implementation."""
|
|
136
195
|
if not self.client:
|
|
137
196
|
raise RuntimeError("MCP client is not connected")
|
|
138
197
|
|
|
139
198
|
logger.debug("Listing prompts")
|
|
140
|
-
|
|
141
|
-
|
|
199
|
+
try:
|
|
200
|
+
result = await self.client.list_prompts()
|
|
201
|
+
return result.prompts
|
|
202
|
+
except McpError as e:
|
|
203
|
+
logger.error(f"Error listing prompts: {e}")
|
|
204
|
+
return []
|
|
142
205
|
|
|
143
206
|
async def get_prompt(
|
|
144
207
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
145
|
-
) ->
|
|
208
|
+
) -> GetPromptResult:
|
|
146
209
|
"""Get a prompt by name."""
|
|
147
210
|
if not self.client:
|
|
148
211
|
raise RuntimeError("MCP client is not connected")
|
|
149
212
|
|
|
150
213
|
logger.debug(f"Getting prompt: {name}")
|
|
151
|
-
|
|
152
|
-
return
|
|
214
|
+
result = await self.client.get_prompt(name, arguments)
|
|
215
|
+
return result
|
|
153
216
|
|
|
154
217
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
155
218
|
"""Send a raw request to the MCP implementation."""
|
|
@@ -7,6 +7,8 @@ which handles authentication, initialization, and tool discovery.
|
|
|
7
7
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from mcp.types import Tool
|
|
11
|
+
|
|
10
12
|
from .connectors.base import BaseConnector
|
|
11
13
|
|
|
12
14
|
|
|
@@ -30,7 +32,7 @@ class MCPSession:
|
|
|
30
32
|
"""
|
|
31
33
|
self.connector = connector
|
|
32
34
|
self.session_info: dict[str, Any] | None = None
|
|
33
|
-
self.tools: list[
|
|
35
|
+
self.tools: list[Tool] = []
|
|
34
36
|
self.auto_connect = auto_connect
|
|
35
37
|
|
|
36
38
|
async def __aenter__(self) -> "MCPSession":
|
|
@@ -73,9 +75,6 @@ class MCPSession:
|
|
|
73
75
|
# Initialize the session
|
|
74
76
|
self.session_info = await self.connector.initialize()
|
|
75
77
|
|
|
76
|
-
# Discover available tools
|
|
77
|
-
await self.discover_tools()
|
|
78
|
-
|
|
79
78
|
return self.session_info
|
|
80
79
|
|
|
81
80
|
@property
|
|
@@ -86,28 +85,3 @@ class MCPSession:
|
|
|
86
85
|
True if the connector is connected, False otherwise.
|
|
87
86
|
"""
|
|
88
87
|
return hasattr(self.connector, "client") and self.connector.client is not None
|
|
89
|
-
|
|
90
|
-
async def discover_tools(self) -> list[dict[str, Any]]:
|
|
91
|
-
"""Discover available tools from the MCP implementation.
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
The list of available tools in MCP format.
|
|
95
|
-
"""
|
|
96
|
-
self.tools = self.connector.tools
|
|
97
|
-
return self.tools
|
|
98
|
-
|
|
99
|
-
async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
100
|
-
"""Call an MCP tool with the given arguments.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
name: The name of the tool to call.
|
|
104
|
-
arguments: The arguments to pass to the tool.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
The result of the tool call.
|
|
108
|
-
"""
|
|
109
|
-
# Make sure we're connected
|
|
110
|
-
if not self.is_connected and self.auto_connect:
|
|
111
|
-
await self.connect()
|
|
112
|
-
|
|
113
|
-
return await self.connector.call_tool(name, arguments)
|
|
@@ -46,14 +46,12 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
48
|
@abstractmethod
|
|
49
|
-
async def _close_connection(self
|
|
49
|
+
async def _close_connection(self) -> None:
|
|
50
50
|
"""Close the connection.
|
|
51
51
|
|
|
52
52
|
This method should be implemented by subclasses to close
|
|
53
53
|
the specific type of connection.
|
|
54
54
|
|
|
55
|
-
Args:
|
|
56
|
-
connection: The connection to close.
|
|
57
55
|
"""
|
|
58
56
|
pass
|
|
59
57
|
|
|
@@ -139,10 +137,10 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
139
137
|
self._ready_event.set()
|
|
140
138
|
|
|
141
139
|
finally:
|
|
142
|
-
# Close the connection if it was
|
|
140
|
+
# Close the connection if it was establishedSUPABASE_URL
|
|
143
141
|
if self._connection is not None:
|
|
144
142
|
try:
|
|
145
|
-
await self._close_connection(
|
|
143
|
+
await self._close_connection()
|
|
146
144
|
except Exception as e:
|
|
147
145
|
logger.warning(f"Error closing connection in {self.__class__.__name__}: {e}")
|
|
148
146
|
self._connection = None
|