kaboo_workflows.mcp¶
Model Context Protocol client/server lifecycle and transports.
kaboo_workflows.mcp ¶
MCP server and client lifecycle management.
MCP_SERVER_TRANSPORT
module-attribute
¶
MCP_SERVER_TRANSPORT = Literal['sse', 'streamable-http']
Transport types valid for :class:~kaboo_workflows.mcp.server.MCPServer.
stdio is excluded because it is a client-side transport where the
client spawns the server as a subprocess and communicates over
stdin/stdout pipes — there is no HTTP server to manage.
MCPLifecycle ¶
MCPLifecycle(server_ready_timeout=30)
Manages MCP server and client lifecycle ordering.
Initialize the MCPLifecycle.
Ensures servers are fully ready before clients connect, and clients are stopped before servers on shutdown.
Example::
lifecycle = MCPLifecycle()
lifecycle.add_server("postgres", pg_server)
lifecycle.add_client("pg_client", pg_client)
with lifecycle:
# All servers started and ready, all clients connected
agent = Agent(tools=[lifecycle.get_client("pg_client")])
agent("Query the database")
# All cleaned up
Or without context manager::
lifecycle.start()
try:
...
finally:
lifecycle.stop()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
server_ready_timeout
|
float
|
Seconds to wait for each server to become ready. |
30
|
Source code in src/kaboo_workflows/mcp/lifecycle.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
add_server ¶
add_server(name, server)
Register an MCP server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique server identifier. |
required |
server
|
MCPServer
|
The MCP server instance. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a server with this name is already registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
67 68 69 70 71 72 73 74 75 76 77 78 79 | |
add_client ¶
add_client(name, client)
Register an MCP client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique client identifier. |
required |
client
|
MCPClient
|
The strands MCP client instance. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a client with this name is already registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
81 82 83 84 85 86 87 88 89 90 91 92 93 | |
get_server ¶
get_server(name)
Get a registered server by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Server identifier. |
required |
Returns:
| Type | Description |
|---|---|
MCPServer
|
The registered MCP server. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no server with this name is registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
get_client ¶
get_client(name)
Get a registered client by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Client identifier. |
required |
Returns:
| Type | Description |
|---|---|
MCPClient
|
The registered strands MCP client. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no client with this name is registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
start ¶
start()
Start all servers and wait for readiness.
Idempotent: if already started, returns immediately.
load() calls this before creating agents (so MCP clients can
connect), and the context manager calls it again on enter — the
second call is a no-op. The context manager is still needed for
graceful shutdown via stop().
Clients are not started here — strands automatically starts MCPClient instances when they are registered as tool providers on an Agent. Starting them here would cause a "session is currently running" error when the Agent tries to start them again. Instead we pin each client with a permanent lifecycle consumer so its session is not torn down when consuming Agents are garbage-collected.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If any server fails to start or become ready. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | |
stop ¶
stop()
Stop all clients first, then all servers.
Clients that were never started (e.g., never registered on an Agent) are skipped gracefully.
Source code in src/kaboo_workflows/mcp/lifecycle.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
__enter__ ¶
__enter__()
Start lifecycle on context entry.
Source code in src/kaboo_workflows/mcp/lifecycle.py
202 203 204 205 | |
__exit__ ¶
__exit__(exc_type, exc_val, exc_tb)
Stop lifecycle on context exit.
Source code in src/kaboo_workflows/mcp/lifecycle.py
207 208 209 210 211 212 213 214 | |
__aenter__
async
¶
__aenter__()
Async context entry — delegates to sync :meth:start.
Useful with Starlette / ASGI lifespan::
@asynccontextmanager
async def lifespan(app):
async with lifecycle:
yield
Source code in src/kaboo_workflows/mcp/lifecycle.py
216 217 218 219 220 221 222 223 224 225 226 227 | |
__aexit__
async
¶
__aexit__(exc_type, exc_val, exc_tb)
Async context exit — delegates to sync :meth:stop.
Source code in src/kaboo_workflows/mcp/lifecycle.py
229 230 231 232 233 234 235 236 | |
MCPServer ¶
MCPServer(
*,
name,
host="127.0.0.1",
port=8000,
transport=DEFAULT_TRANSPORT,
server_params=None,
)
Bases: ABC
Abstract base for kaboo_workflows MCP servers.
Initialize the MCPServer.
Subclasses implement _register_tools() to register tools on the
FastMCP instance. The base class manages background-thread
lifecycle and readiness signaling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique server identifier. |
required |
host
|
str
|
Bind address for the HTTP transport. |
'127.0.0.1'
|
port
|
int
|
Bind port for the HTTP transport. |
8000
|
transport
|
MCP_SERVER_TRANSPORT
|
MCP server transport type ( |
DEFAULT_TRANSPORT
|
server_params
|
dict[str, Any] | None
|
Extra keyword arguments forwarded to |
None
|
Source code in src/kaboo_workflows/mcp/server.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
create_server ¶
create_server()
Build the FastMCP instance and register tools.
The result is cached — calling twice returns the same instance.
Source code in src/kaboo_workflows/mcp/server.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
run ¶
run()
Start the server blocking (for standalone CLI usage).
In the main thread FastMCP.run() installs signal handlers so
that Ctrl-C triggers a graceful uvicorn shutdown.
Source code in src/kaboo_workflows/mcp/server.py
157 158 159 160 161 162 163 164 | |
start ¶
start()
Start the server in a background daemon thread.
Creates its own uvicorn.Server instead of delegating to
FastMCP.run(). This keeps a reference to the server so that
:meth:stop can trigger a graceful shutdown via
uvicorn.Server.should_exit.
Source code in src/kaboo_workflows/mcp/server.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
wait_ready ¶
wait_ready(timeout=30)
Wait for the server to be ready by polling the TCP port.
Returns:
| Type | Description |
|---|---|
bool
|
True if server is ready, False if timed out. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the server thread died before becoming ready. |
Source code in src/kaboo_workflows/mcp/server.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
stop ¶
stop()
Stop the server and clean up the background thread.
Signals uvicorn.Server.should_exit which triggers a graceful
drain (stop accepting new connections, finish in-flight requests).
If the thread does not exit within :attr:STOP_TIMEOUT seconds,
force_exit is set to skip connection draining. After a
further :attr:STOP_FORCE_TIMEOUT seconds the thread is
abandoned as a daemon thread and will be reaped when the process
exits.
Source code in src/kaboo_workflows/mcp/server.py
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
create_mcp_client ¶
create_mcp_client(
*,
server=None,
url=None,
command=None,
transport=DEFAULT_TRANSPORT,
transport_options=None,
**kwargs,
)
Create a strands MCPClient from connection configuration.
Exactly one of server, url, or command must be provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
server
|
MCPServer | None
|
A managed MCPServer instance (connects via its URL). |
None
|
url
|
str | None
|
External MCP server URL (for SSE or streamable-http). |
None
|
command
|
list[str] | None
|
Command to start an MCP server subprocess (stdio transport). |
None
|
transport
|
MCP_TRANSPORT
|
Override transport type ("stdio", "sse", "streamable-http"). Auto-detected if not specified. |
DEFAULT_TRANSPORT
|
transport_options
|
dict[str, Any] | None
|
Extra kwargs forwarded to the transport factory. These are transport-specific — see each transport function for available options: stdio: sse: streamable-http: |
None
|
**kwargs
|
Any
|
Additional kwargs forwarded to strands MCPClient (startup_timeout, tool_filters, prefix, elicitation_callback, tasks_config, etc.). |
{}
|
Returns:
| Type | Description |
|---|---|
MCPClient
|
A strands MCPClient instance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If connection parameters are ambiguous. |
Source code in src/kaboo_workflows/mcp/client.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
create_mcp_server ¶
create_mcp_server(
*,
name,
tools,
host="127.0.0.1",
port=8000,
transport=DEFAULT_TRANSPORT,
server_params=None,
)
Create an MCP server from a list of callables — no subclassing needed.
Each callable (sync or async) is registered as a tool on the underlying
FastMCP instance. For advanced use (custom state, routes, resources),
subclass :class:MCPServer directly.
Example::
def get_weather(city: str) -> str:
return f"Sunny in {city}"
async def query_db(sql: str) -> str: ...
server = create_mcp_server(name="weather", tools=[get_weather, query_db], port=8001)
server.start()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique server identifier. |
required |
tools
|
list[Callable[..., Any]]
|
Callables to register as MCP tools. |
required |
host
|
str
|
Bind address (default |
'127.0.0.1'
|
port
|
int
|
Bind port (default |
8000
|
transport
|
MCP_SERVER_TRANSPORT
|
Server transport type ( |
DEFAULT_TRANSPORT
|
server_params
|
dict[str, Any] | None
|
Extra kwargs forwarded to |
None
|
Returns:
| Type | Description |
|---|---|
MCPServer
|
A ready-to-use :class: |
Source code in src/kaboo_workflows/mcp/server.py
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | |
sse_transport ¶
sse_transport(
url,
headers=None,
*,
timeout=5,
sse_read_timeout=300,
auth=None,
httpx_client_factory=None,
)
Create an SSE (Server-Sent Events) transport callable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
SSE endpoint URL. |
required |
headers
|
dict[str, Any] | None
|
Optional HTTP headers. |
None
|
timeout
|
float
|
HTTP timeout in seconds (default: 5). |
5
|
sse_read_timeout
|
float
|
Timeout waiting for SSE events in seconds (default: 300). |
300
|
auth
|
Any | None
|
Optional httpx.Auth instance (e.g., OAuth provider). |
None
|
httpx_client_factory
|
Any | None
|
Optional factory for creating httpx client. |
None
|
Returns:
| Type | Description |
|---|---|
Callable[[], Any]
|
Transport callable for strands MCPClient. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If url is empty. |
Source code in src/kaboo_workflows/mcp/transports.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
stdio_transport ¶
stdio_transport(
command,
env=None,
*,
cwd=None,
encoding="utf-8",
encoding_error_handler="strict",
)
Create a stdio transport callable for a subprocess MCP server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
command
|
list[str]
|
Command to start the MCP server (e.g., ["python", "-m", "myserver"]). |
required |
env
|
dict[str, str] | None
|
Optional environment variables for the subprocess. |
None
|
cwd
|
str | Path | None
|
Working directory for the subprocess. |
None
|
encoding
|
str
|
Text encoding for messages (default: utf-8). |
'utf-8'
|
encoding_error_handler
|
Literal['strict', 'ignore', 'replace']
|
How to handle encoding errors (default: strict). |
'strict'
|
Returns:
| Type | Description |
|---|---|
Callable[[], Any]
|
Transport callable for strands MCPClient. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If command is empty. |
Source code in src/kaboo_workflows/mcp/transports.py
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
streamable_http_transport ¶
streamable_http_transport(
url,
headers=None,
*,
http_client=None,
terminate_on_close=True,
)
Create a streamable HTTP transport callable.
For full control (auth, timeouts, custom TLS, etc.), pass a pre-configured
httpx.AsyncClient via http_client. When http_client is provided,
headers is ignored (configure headers on the client directly).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
HTTP endpoint URL (e.g., "http://localhost:8000/mcp"). |
required |
headers
|
dict[str, str] | None
|
Optional HTTP headers. Ignored when |
None
|
http_client
|
Any | None
|
Optional pre-configured |
None
|
terminate_on_close
|
bool
|
Send DELETE to close session (default: True). |
True
|
Returns:
| Type | Description |
|---|---|
Callable[[], Any]
|
Transport callable for strands MCPClient. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If url is empty. |
Source code in src/kaboo_workflows/mcp/transports.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |