strands-mcp-server 0.1.2__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.
- strands_mcp_server/__init__.py +223 -0
- strands_mcp_server/cli.py +308 -0
- strands_mcp_server/mcp_client.py +543 -0
- strands_mcp_server/mcp_server.py +722 -0
- strands_mcp_server-0.1.2.dist-info/METADATA +306 -0
- strands_mcp_server-0.1.2.dist-info/RECORD +9 -0
- strands_mcp_server-0.1.2.dist-info/WHEEL +4 -0
- strands_mcp_server-0.1.2.dist-info/entry_points.txt +2 -0
- strands_mcp_server-0.1.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Strands MCP Server - Bidirectional MCP integration for Strands Agents.
|
|
2
|
+
|
|
3
|
+
This package provides complete Model Context Protocol (MCP) integration for Strands Agents,
|
|
4
|
+
enabling both server and client capabilities. Transform your agents into MCP servers that
|
|
5
|
+
expose tools to Claude Desktop and other MCP clients, or connect your agents to remote MCP
|
|
6
|
+
servers to use their tools.
|
|
7
|
+
|
|
8
|
+
Architecture:
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ Strands Agent │
|
|
12
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
13
|
+
│ │ mcp_server │ │ mcp_client │ │
|
|
14
|
+
│ │ (expose tools) │ │ (consume tools) │ │
|
|
15
|
+
│ └──────────────────┘ └──────────────────┘ │
|
|
16
|
+
└─────────────────────────────────────────────────────────────┘
|
|
17
|
+
↓ ↓
|
|
18
|
+
MCP Protocol MCP Protocol
|
|
19
|
+
↓ ↓
|
|
20
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
21
|
+
│ MCP Clients │ │ MCP Servers │
|
|
22
|
+
│ • Claude Desktop│ │ • Other Agents │
|
|
23
|
+
│ • Other Agents │ │ • Remote APIs │
|
|
24
|
+
│ • Custom Clients│ │ • MCP Services │
|
|
25
|
+
└─────────────────┘ └─────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Components:
|
|
29
|
+
|
|
30
|
+
1. **mcp_server** - Turn agent into MCP server
|
|
31
|
+
- Exposes agent tools as MCP tools
|
|
32
|
+
- Optional full agent invocation capability
|
|
33
|
+
- Multiple transport modes (HTTP, stdio)
|
|
34
|
+
- Stateless and stateful session management
|
|
35
|
+
- Production-ready with StreamableHTTPSessionManager
|
|
36
|
+
|
|
37
|
+
2. **mcp_client** - Connect to remote MCP servers
|
|
38
|
+
- Discover and call remote MCP tools
|
|
39
|
+
- Multiple transport support (HTTP, stdio, SSE)
|
|
40
|
+
- Connection management and persistence
|
|
41
|
+
- Session handling with ClientSession
|
|
42
|
+
|
|
43
|
+
3. **CLI** - Command-line MCP server
|
|
44
|
+
- stdio mode for Claude Desktop/Kiro integration
|
|
45
|
+
- Local mode: Expose tools from ./tools/ directory
|
|
46
|
+
- Proxy mode: Bridge stdio to upstream HTTP server
|
|
47
|
+
- Hot reload support via Strands native tool loading
|
|
48
|
+
|
|
49
|
+
Key Features:
|
|
50
|
+
|
|
51
|
+
**Server Capabilities:**
|
|
52
|
+
- **Stateless HTTP**: Multi-node ready, horizontally scalable
|
|
53
|
+
- **Stateful HTTP**: Session persistence for single-node
|
|
54
|
+
- **stdio Mode**: Direct stdin/stdout for CLI integration
|
|
55
|
+
- **Tool Filtering**: Expose specific tools only
|
|
56
|
+
- **Agent Invocation**: Full conversational access
|
|
57
|
+
- **CORS Support**: Browser-based client compatibility
|
|
58
|
+
|
|
59
|
+
**Client Capabilities:**
|
|
60
|
+
- **Multi-transport**: HTTP, stdio, SSE connections
|
|
61
|
+
- **Tool Discovery**: List available remote tools
|
|
62
|
+
- **Tool Execution**: Call remote tools with arguments
|
|
63
|
+
- **Connection Management**: Persistent session tracking
|
|
64
|
+
- **Error Handling**: Comprehensive error recovery
|
|
65
|
+
|
|
66
|
+
**Production Features:**
|
|
67
|
+
- StreamableHTTPSessionManager for production-grade HTTP
|
|
68
|
+
- Background thread execution for non-blocking servers
|
|
69
|
+
- Proper ASGI lifecycle management
|
|
70
|
+
- Comprehensive logging and error tracking
|
|
71
|
+
- Daemon thread cleanup
|
|
72
|
+
|
|
73
|
+
Usage Examples:
|
|
74
|
+
|
|
75
|
+
**1. As Server (Expose Agent):**
|
|
76
|
+
```python
|
|
77
|
+
from strands import Agent
|
|
78
|
+
from strands_tools import calculator, shell, file_read
|
|
79
|
+
from strands_mcp_server import mcp_server
|
|
80
|
+
|
|
81
|
+
agent = Agent(tools=[calculator, shell, file_read, mcp_server])
|
|
82
|
+
|
|
83
|
+
# Start HTTP server (background)
|
|
84
|
+
agent("start mcp server on port 8000")
|
|
85
|
+
|
|
86
|
+
# Stateless mode for production (multi-node)
|
|
87
|
+
agent("start stateless mcp server on port 8000")
|
|
88
|
+
|
|
89
|
+
# With specific tools only
|
|
90
|
+
agent.tool.mcp_server(
|
|
91
|
+
action="start",
|
|
92
|
+
tools=["calculator", "file_read"],
|
|
93
|
+
agent=agent
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# stdio mode (foreground, blocking - for CLI)
|
|
97
|
+
agent.tool.mcp_server(
|
|
98
|
+
action="start",
|
|
99
|
+
transport="stdio",
|
|
100
|
+
agent=agent
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**2. As Client (Connect to Remote Servers):**
|
|
105
|
+
```python
|
|
106
|
+
from strands import Agent
|
|
107
|
+
from strands_mcp_server import mcp_client
|
|
108
|
+
|
|
109
|
+
agent = Agent(tools=[mcp_client])
|
|
110
|
+
|
|
111
|
+
# Connect to HTTP server
|
|
112
|
+
agent.tool.mcp_client(
|
|
113
|
+
action="connect",
|
|
114
|
+
connection_id="remote-agent",
|
|
115
|
+
transport="http",
|
|
116
|
+
server_url="http://localhost:8000/mcp"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# List remote tools
|
|
120
|
+
agent.tool.mcp_client(
|
|
121
|
+
action="list_tools",
|
|
122
|
+
connection_id="remote-agent"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Call remote tool
|
|
126
|
+
agent.tool.mcp_client(
|
|
127
|
+
action="call_tool",
|
|
128
|
+
connection_id="remote-agent",
|
|
129
|
+
tool_name="calculator",
|
|
130
|
+
tool_args={"expression": "42 * 89"}
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**3. CLI for Claude Desktop:**
|
|
135
|
+
```bash
|
|
136
|
+
# Local mode: Load tools from ./tools/ directory
|
|
137
|
+
strands-mcp-server --cwd /path/to/project
|
|
138
|
+
|
|
139
|
+
# Proxy mode: Bridge stdio to upstream HTTP server
|
|
140
|
+
strands-mcp-server --upstream-url http://localhost:8000/mcp
|
|
141
|
+
|
|
142
|
+
# With custom system prompt
|
|
143
|
+
strands-mcp-server --system-prompt "You are a helpful assistant"
|
|
144
|
+
|
|
145
|
+
# Debug mode
|
|
146
|
+
strands-mcp-server --debug
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Claude Desktop Config (Local):**
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"mcpServers": {
|
|
153
|
+
"strands-tools": {
|
|
154
|
+
"command": "strands-mcp-server",
|
|
155
|
+
"args": ["--cwd", "/absolute/path/to/project"]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Claude Desktop Config (Proxy):**
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"mcpServers": {
|
|
165
|
+
"strands-proxy": {
|
|
166
|
+
"command": "strands-mcp-server",
|
|
167
|
+
"args": ["--upstream-url", "http://localhost:8000/mcp"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Agent-to-Agent Communication:**
|
|
174
|
+
```python
|
|
175
|
+
# Server agent
|
|
176
|
+
data_agent = Agent(tools=[file_read, calculator, mcp_server])
|
|
177
|
+
data_agent.tool.mcp_server(action="start", port=8001, agent=data_agent)
|
|
178
|
+
|
|
179
|
+
# Client agent
|
|
180
|
+
coordinator = Agent(tools=[mcp_client])
|
|
181
|
+
coordinator.tool.mcp_client(
|
|
182
|
+
action="connect",
|
|
183
|
+
connection_id="data",
|
|
184
|
+
transport="http",
|
|
185
|
+
server_url="http://localhost:8001/mcp"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Use remote tools
|
|
189
|
+
coordinator("use data agent's calculator to compute 42 * 89")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Transport Modes:
|
|
193
|
+
|
|
194
|
+
**HTTP Transport (Background):**
|
|
195
|
+
- Runs in daemon thread (non-blocking)
|
|
196
|
+
- StreamableHTTPSessionManager
|
|
197
|
+
- CORS middleware enabled
|
|
198
|
+
- Uvicorn + Starlette ASGI
|
|
199
|
+
- Port-based connection
|
|
200
|
+
|
|
201
|
+
**stdio Transport (Foreground):**
|
|
202
|
+
- Blocks current thread
|
|
203
|
+
- Direct stdin/stdout communication
|
|
204
|
+
- Required for Claude Desktop
|
|
205
|
+
- Logging to stderr only
|
|
206
|
+
- Process-based connection
|
|
207
|
+
|
|
208
|
+
**Stateless vs Stateful:**
|
|
209
|
+
- **Stateless**: Fresh session per request, multi-node ready, horizontally scalable
|
|
210
|
+
- **Stateful**: Session persistence, single-node, connection state maintained
|
|
211
|
+
|
|
212
|
+
References:
|
|
213
|
+
- MCP Specification: https://spec.modelcontextprotocol.io/
|
|
214
|
+
- MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk
|
|
215
|
+
- Strands Agents: https://strandsagents.com
|
|
216
|
+
- Project Repository: https://github.com/cagataycali/strands-mcp-server
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
from strands_mcp_server.mcp_client import mcp_client
|
|
220
|
+
from strands_mcp_server.mcp_server import mcp_server
|
|
221
|
+
|
|
222
|
+
__version__ = "0.1.2"
|
|
223
|
+
__all__ = ["mcp_server", "mcp_client"]
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLI entrypoint for strands-mcp-server.
|
|
3
|
+
|
|
4
|
+
This provides a command-line interface for running an MCP server in stdio mode,
|
|
5
|
+
making it easy to integrate with Claude Desktop, Kiro, and other MCP clients.
|
|
6
|
+
|
|
7
|
+
Two modes:
|
|
8
|
+
1. Local mode: Exposes tools from ./tools/ directory (Strands hot reload)
|
|
9
|
+
2. Proxy mode: Acts like npx mcp-remote - bridges stdio to upstream HTTP server
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
# Local mode: Load tools from ./tools/ directory
|
|
13
|
+
strands-mcp-server
|
|
14
|
+
|
|
15
|
+
# Proxy mode: Bridge to upstream HTTP MCP server
|
|
16
|
+
strands-mcp-server --upstream-url http://localhost:8000/mcp
|
|
17
|
+
|
|
18
|
+
# With custom working directory
|
|
19
|
+
strands-mcp-server --cwd /path/to/project
|
|
20
|
+
|
|
21
|
+
# With system prompt
|
|
22
|
+
strands-mcp-server --system-prompt "You are a helpful assistant"
|
|
23
|
+
|
|
24
|
+
# Without agent invocation
|
|
25
|
+
strands-mcp-server --no-agent-invocation
|
|
26
|
+
|
|
27
|
+
Claude Desktop Config (Local):
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"strands-tools": {
|
|
31
|
+
"command": "strands-mcp-server",
|
|
32
|
+
"args": ["--cwd", "/absolute/path/to/your/project"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Claude Desktop Config (Proxy):
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"strands-proxy": {
|
|
41
|
+
"command": "strands-mcp-server",
|
|
42
|
+
"args": ["--upstream-url", "http://localhost:8000/mcp"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
import argparse
|
|
49
|
+
import asyncio
|
|
50
|
+
import logging
|
|
51
|
+
import sys
|
|
52
|
+
import os
|
|
53
|
+
from strands import Agent
|
|
54
|
+
from strands_mcp_server.mcp_server import mcp_server
|
|
55
|
+
from strands_mcp_server.mcp_client import mcp_client
|
|
56
|
+
|
|
57
|
+
# Configure logging to stderr (stdio mode requirement)
|
|
58
|
+
logging.basicConfig(
|
|
59
|
+
level=logging.INFO,
|
|
60
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
61
|
+
stream=sys.stderr, # MCP stdio servers MUST use stderr for logging
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main() -> None:
|
|
68
|
+
"""Main CLI entrypoint for strands-mcp-server."""
|
|
69
|
+
parser = argparse.ArgumentParser(
|
|
70
|
+
description="Strands MCP Server - Expose Strands Agent tools via MCP stdio protocol",
|
|
71
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
72
|
+
epilog="""
|
|
73
|
+
Examples:
|
|
74
|
+
# Default: Load tools from ./tools/ directory (with hot reload)
|
|
75
|
+
strands-mcp-server
|
|
76
|
+
|
|
77
|
+
# Proxy mode: Bridge to upstream HTTP MCP server (like npx mcp-remote)
|
|
78
|
+
strands-mcp-server --upstream-url http://localhost:8000/mcp
|
|
79
|
+
|
|
80
|
+
# With custom working directory
|
|
81
|
+
strands-mcp-server --cwd /path/to/project
|
|
82
|
+
|
|
83
|
+
# With system prompt
|
|
84
|
+
strands-mcp-server --system-prompt "You are a research assistant"
|
|
85
|
+
|
|
86
|
+
# Without agent invocation capability
|
|
87
|
+
strands-mcp-server --no-agent-invocation
|
|
88
|
+
|
|
89
|
+
# Debug mode
|
|
90
|
+
strands-mcp-server --debug
|
|
91
|
+
|
|
92
|
+
Claude Desktop Config (Local Tools):
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"strands-tools": {
|
|
96
|
+
"command": "strands-mcp-server",
|
|
97
|
+
"args": ["--cwd", "/absolute/path/to/your/project"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Claude Desktop Config (Proxy Mode):
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"strands-proxy": {
|
|
106
|
+
"command": "strands-mcp-server",
|
|
107
|
+
"args": ["--upstream-url", "http://localhost:8000/mcp"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
""",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--system-prompt",
|
|
116
|
+
type=str,
|
|
117
|
+
help="System prompt for the agent",
|
|
118
|
+
default="You are a helpful AI assistant with access to various tools.",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
"--no-agent-invocation",
|
|
123
|
+
action="store_true",
|
|
124
|
+
help="Disable agent invocation capability (tools only)",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--debug",
|
|
129
|
+
action="store_true",
|
|
130
|
+
help="Enable debug logging",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--upstream-url",
|
|
135
|
+
type=str,
|
|
136
|
+
help="Upstream MCP server URL to proxy (e.g., http://localhost:8000/mcp)",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--upstream-transport",
|
|
141
|
+
type=str,
|
|
142
|
+
choices=["http", "sse", "streamable_http"],
|
|
143
|
+
default="streamable_http",
|
|
144
|
+
help="Transport type for upstream connection (default: streamable_http)",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--cwd",
|
|
149
|
+
type=str,
|
|
150
|
+
help="Working directory for tool loading (default: current directory)",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
args = parser.parse_args()
|
|
154
|
+
|
|
155
|
+
# Set debug logging if requested
|
|
156
|
+
if args.debug:
|
|
157
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
158
|
+
logger.debug("Debug logging enabled")
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
logger.info("Starting Strands MCP Server in stdio mode")
|
|
162
|
+
|
|
163
|
+
# Change working directory if specified
|
|
164
|
+
if args.cwd:
|
|
165
|
+
logger.info(f"Changing working directory to: {args.cwd}")
|
|
166
|
+
os.chdir(args.cwd)
|
|
167
|
+
logger.info(f"CWD now: {os.getcwd()}")
|
|
168
|
+
|
|
169
|
+
# Check if we're proxying to an upstream server
|
|
170
|
+
if args.upstream_url:
|
|
171
|
+
logger.info(f"Proxy mode: Connecting to upstream MCP server at {args.upstream_url}")
|
|
172
|
+
asyncio.run(run_proxy_mode(args))
|
|
173
|
+
else:
|
|
174
|
+
# Local mode: Create agent with package tools + hot reload
|
|
175
|
+
cwd = os.getcwd()
|
|
176
|
+
tools_dir = os.path.join(cwd, "tools")
|
|
177
|
+
logger.info(f"Local mode: Loading tools from directory")
|
|
178
|
+
logger.info(f" CWD: {cwd}")
|
|
179
|
+
logger.info(f" Tools dir: {tools_dir}")
|
|
180
|
+
logger.info(f" Tools dir exists: {os.path.exists(tools_dir)}")
|
|
181
|
+
|
|
182
|
+
if os.path.exists(tools_dir):
|
|
183
|
+
tools_files = [f for f in os.listdir(tools_dir) if f.endswith(".py") and not f.startswith("_")]
|
|
184
|
+
logger.info(f" Found {len(tools_files)} .py files: {tools_files}")
|
|
185
|
+
|
|
186
|
+
logger.debug("Strands native hot reload enabled for ./tools/")
|
|
187
|
+
|
|
188
|
+
# Create agent with package tools + hot reload from ./tools/
|
|
189
|
+
agent = Agent(
|
|
190
|
+
name="strands-mcp-cli",
|
|
191
|
+
tools=[mcp_server, mcp_client],
|
|
192
|
+
load_tools_from_directory=True, # Strands handles ./tools/ automatically
|
|
193
|
+
system_prompt=args.system_prompt,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
tool_count = len(agent.tool_registry.get_all_tools_config())
|
|
197
|
+
all_tool_names = list(agent.tool_registry.registry.keys())
|
|
198
|
+
logger.info(f"Agent created with {tool_count} tools: {all_tool_names}")
|
|
199
|
+
logger.debug(" • mcp_server (from package)")
|
|
200
|
+
logger.debug(" • mcp_client (from package)")
|
|
201
|
+
logger.debug(" • Plus any tools in ./tools/ (hot reload enabled)")
|
|
202
|
+
|
|
203
|
+
# Start MCP server in stdio mode using mcp_server tool
|
|
204
|
+
# This BLOCKS the current thread - perfect for CLI!
|
|
205
|
+
logger.info("Starting MCP server in stdio mode (foreground, blocking)...")
|
|
206
|
+
agent.tool.mcp_server(
|
|
207
|
+
action="start",
|
|
208
|
+
transport="stdio",
|
|
209
|
+
expose_agent=not args.no_agent_invocation,
|
|
210
|
+
agent=agent,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
except KeyboardInterrupt:
|
|
214
|
+
logger.info("Shutting down...")
|
|
215
|
+
sys.exit(0)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.exception("Fatal error")
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def run_proxy_mode(args) -> None:
|
|
222
|
+
"""Run MCP server in proxy mode - bridge stdio to upstream HTTP server.
|
|
223
|
+
|
|
224
|
+
This acts like npx mcp-remote:
|
|
225
|
+
- Accepts stdio connections from Claude Desktop/Kiro
|
|
226
|
+
- Forwards requests to upstream MCP server via HTTP
|
|
227
|
+
- Returns upstream responses back through stdio
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
args: CLI arguments with upstream_url and upstream_transport
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
from mcp import types, ClientSession
|
|
234
|
+
from mcp.server.lowlevel import Server
|
|
235
|
+
from mcp.server.stdio import stdio_server
|
|
236
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
237
|
+
except ImportError as e:
|
|
238
|
+
logger.error(f"Failed to import MCP dependencies: {e}")
|
|
239
|
+
logger.error("Install with: pip install mcp")
|
|
240
|
+
sys.exit(1)
|
|
241
|
+
|
|
242
|
+
logger.info(f"Connecting to upstream MCP server: {args.upstream_url}")
|
|
243
|
+
logger.info(f"Transport: streamable_http")
|
|
244
|
+
|
|
245
|
+
# Connect to upstream MCP server using SDK client
|
|
246
|
+
upstream_session = None
|
|
247
|
+
upstream_tools = []
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
# Create client session to upstream server
|
|
251
|
+
async with streamablehttp_client(args.upstream_url) as client:
|
|
252
|
+
async with ClientSession(client[0], client[1]) as session:
|
|
253
|
+
# Initialize and get tools
|
|
254
|
+
await session.initialize()
|
|
255
|
+
logger.info("✅ Connected to upstream MCP server")
|
|
256
|
+
|
|
257
|
+
# List tools from upstream
|
|
258
|
+
response = await session.list_tools()
|
|
259
|
+
upstream_tools = response.tools
|
|
260
|
+
logger.info(f"✅ Discovered {len(upstream_tools)} upstream tools")
|
|
261
|
+
for tool in upstream_tools:
|
|
262
|
+
logger.debug(
|
|
263
|
+
f" • {tool.name}: {tool.description[:50] if tool.description else 'No description'}..."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Create MCP server for stdio
|
|
267
|
+
server = Server("strands-mcp-proxy")
|
|
268
|
+
|
|
269
|
+
# Register list_tools handler
|
|
270
|
+
@server.list_tools()
|
|
271
|
+
async def list_tools() -> list[types.Tool]:
|
|
272
|
+
"""Return list of upstream tools."""
|
|
273
|
+
logger.debug(f"list_tools called, returning {len(upstream_tools)} upstream tools")
|
|
274
|
+
return upstream_tools
|
|
275
|
+
|
|
276
|
+
# Register call_tool handler
|
|
277
|
+
@server.call_tool()
|
|
278
|
+
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
|
|
279
|
+
"""Proxy tool calls to upstream server."""
|
|
280
|
+
try:
|
|
281
|
+
logger.debug(f"Proxying tool call: {name} with args: {arguments}")
|
|
282
|
+
|
|
283
|
+
# Call tool on upstream server
|
|
284
|
+
result = await session.call_tool(name, arguments)
|
|
285
|
+
|
|
286
|
+
logger.debug(f"Upstream result received")
|
|
287
|
+
|
|
288
|
+
# Convert result to MCP TextContent
|
|
289
|
+
return result.content
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.exception(f"Error proxying tool '{name}'")
|
|
293
|
+
return [types.TextContent(type="text", text=f"❌ Error: {str(e)}")]
|
|
294
|
+
|
|
295
|
+
# Run stdio server (blocks until terminated)
|
|
296
|
+
logger.info("🚀 MCP proxy ready - listening on stdin/stdout")
|
|
297
|
+
logger.info(f" Forwarding to: {args.upstream_url}")
|
|
298
|
+
|
|
299
|
+
async with stdio_server() as streams:
|
|
300
|
+
await server.run(streams[0], streams[1], server.create_initialization_options())
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.exception(f"Failed to connect to upstream MCP server: {e}")
|
|
304
|
+
sys.exit(1)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
main()
|