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.
@@ -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()