strands-mcp-server 0.1.3__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.

Potentially problematic release.


This version of strands-mcp-server might be problematic. Click here for more details.

@@ -0,0 +1,203 @@
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
+ Components:
9
+
10
+ 1. **mcp_server** - Turn agent into MCP server
11
+ - Exposes agent tools as MCP tools
12
+ - Optional full agent invocation capability
13
+ - Multiple transport modes (HTTP, stdio)
14
+ - Stateless and stateful session management
15
+ - Production-ready with StreamableHTTPSessionManager
16
+
17
+ 2. **mcp_client** - Connect to remote MCP servers
18
+ - Discover and call remote MCP tools
19
+ - Multiple transport support (HTTP, stdio, SSE)
20
+ - Connection management and persistence
21
+ - Session handling with ClientSession
22
+
23
+ 3. **CLI** - Command-line MCP server
24
+ - stdio mode for Claude Desktop/Kiro integration
25
+ - Local mode: Expose tools from ./tools/ directory
26
+ - Proxy mode: Bridge stdio to upstream HTTP server
27
+ - Hot reload support via Strands native tool loading
28
+
29
+ Key Features:
30
+
31
+ **Server Capabilities:**
32
+ - **Stateless HTTP**: Multi-node ready, horizontally scalable
33
+ - **Stateful HTTP**: Session persistence for single-node
34
+ - **stdio Mode**: Direct stdin/stdout for CLI integration
35
+ - **Tool Filtering**: Expose specific tools only
36
+ - **Agent Invocation**: Full conversational access
37
+ - **CORS Support**: Browser-based client compatibility
38
+
39
+ **Client Capabilities:**
40
+ - **Multi-transport**: HTTP, stdio, SSE connections
41
+ - **Tool Discovery**: List available remote tools
42
+ - **Tool Execution**: Call remote tools with arguments
43
+ - **Connection Management**: Persistent session tracking
44
+ - **Error Handling**: Comprehensive error recovery
45
+
46
+ **Production Features:**
47
+ - StreamableHTTPSessionManager for production-grade HTTP
48
+ - Background thread execution for non-blocking servers
49
+ - Proper ASGI lifecycle management
50
+ - Comprehensive logging and error tracking
51
+ - Daemon thread cleanup
52
+
53
+ Usage Examples:
54
+
55
+ **1. As Server (Expose Agent):**
56
+ ```python
57
+ from strands import Agent
58
+ from strands_tools import calculator, shell, file_read
59
+ from strands_mcp_server import mcp_server
60
+
61
+ agent = Agent(tools=[calculator, shell, file_read, mcp_server])
62
+
63
+ # Start HTTP server (background)
64
+ agent("start mcp server on port 8000")
65
+
66
+ # Stateless mode for production (multi-node)
67
+ agent("start stateless mcp server on port 8000")
68
+
69
+ # With specific tools only
70
+ agent.tool.mcp_server(
71
+ action="start",
72
+ tools=["calculator", "file_read"],
73
+ agent=agent
74
+ )
75
+
76
+ # stdio mode (foreground, blocking - for CLI)
77
+ agent.tool.mcp_server(
78
+ action="start",
79
+ transport="stdio",
80
+ agent=agent
81
+ )
82
+ ```
83
+
84
+ **2. As Client (Connect to Remote Servers):**
85
+ ```python
86
+ from strands import Agent
87
+ from strands_mcp_server import mcp_client
88
+
89
+ agent = Agent(tools=[mcp_client])
90
+
91
+ # Connect to HTTP server
92
+ agent.tool.mcp_client(
93
+ action="connect",
94
+ connection_id="remote-agent",
95
+ transport="http",
96
+ server_url="http://localhost:8000/mcp"
97
+ )
98
+
99
+ # List remote tools
100
+ agent.tool.mcp_client(
101
+ action="list_tools",
102
+ connection_id="remote-agent"
103
+ )
104
+
105
+ # Call remote tool
106
+ agent.tool.mcp_client(
107
+ action="call_tool",
108
+ connection_id="remote-agent",
109
+ tool_name="calculator",
110
+ tool_args={"expression": "42 * 89"}
111
+ )
112
+ ```
113
+
114
+ **3. CLI for Claude Desktop:**
115
+ ```bash
116
+ # Local mode: Load tools from ./tools/ directory
117
+ strands-mcp-server --cwd /path/to/project
118
+
119
+ # Proxy mode: Bridge stdio to upstream HTTP server
120
+ strands-mcp-server --upstream-url http://localhost:8000/mcp
121
+
122
+ # With custom system prompt
123
+ strands-mcp-server --system-prompt "You are a helpful assistant"
124
+
125
+ # Debug mode
126
+ strands-mcp-server --debug
127
+ ```
128
+
129
+ **Claude Desktop Config (Local):**
130
+ ```json
131
+ {
132
+ "mcpServers": {
133
+ "strands-tools": {
134
+ "command": "strands-mcp-server",
135
+ "args": ["--cwd", "/absolute/path/to/project"]
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ **Claude Desktop Config (Proxy):**
142
+ ```json
143
+ {
144
+ "mcpServers": {
145
+ "strands-proxy": {
146
+ "command": "strands-mcp-server",
147
+ "args": ["--upstream-url", "http://localhost:8000/mcp"]
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ **Agent-to-Agent Communication:**
154
+ ```python
155
+ # Server agent
156
+ data_agent = Agent(tools=[file_read, calculator, mcp_server])
157
+ data_agent.tool.mcp_server(action="start", port=8001, agent=data_agent)
158
+
159
+ # Client agent
160
+ coordinator = Agent(tools=[mcp_client])
161
+ coordinator.tool.mcp_client(
162
+ action="connect",
163
+ connection_id="data",
164
+ transport="http",
165
+ server_url="http://localhost:8001/mcp"
166
+ )
167
+
168
+ # Use remote tools
169
+ coordinator("use data agent's calculator to compute 42 * 89")
170
+ ```
171
+
172
+ Transport Modes:
173
+
174
+ **HTTP Transport (Background):**
175
+ - Runs in daemon thread (non-blocking)
176
+ - StreamableHTTPSessionManager
177
+ - CORS middleware enabled
178
+ - Uvicorn + Starlette ASGI
179
+ - Port-based connection
180
+
181
+ **stdio Transport (Foreground):**
182
+ - Blocks current thread
183
+ - Direct stdin/stdout communication
184
+ - Required for Claude Desktop
185
+ - Logging to stderr only
186
+ - Process-based connection
187
+
188
+ **Stateless vs Stateful:**
189
+ - **Stateless**: Fresh session per request, multi-node ready, horizontally scalable
190
+ - **Stateful**: Session persistence, single-node, connection state maintained
191
+
192
+ References:
193
+ - MCP Specification: https://spec.modelcontextprotocol.io/
194
+ - MCP Python SDK: https://github.com/modelcontextprotocol/python-sdk
195
+ - Strands Agents: https://strandsagents.com
196
+ - Project Repository: https://github.com/cagataycali/strands-mcp-server
197
+ """
198
+
199
+ from strands_mcp_server.mcp_client import mcp_client
200
+ from strands_mcp_server.mcp_server import mcp_server
201
+
202
+ __version__ = "0.1.3"
203
+ __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()