ouroboros-ai 0.3.0__py3-none-any.whl → 0.4.0__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 ouroboros-ai might be problematic. Click here for more details.
- ouroboros/__init__.py +1 -1
- ouroboros/bigbang/__init__.py +9 -0
- ouroboros/bigbang/ontology.py +180 -0
- ouroboros/cli/commands/__init__.py +2 -0
- ouroboros/cli/commands/mcp.py +161 -0
- ouroboros/cli/commands/run.py +165 -27
- ouroboros/cli/main.py +2 -1
- ouroboros/core/ontology_aspect.py +455 -0
- ouroboros/core/ontology_questions.py +462 -0
- ouroboros/evaluation/__init__.py +16 -1
- ouroboros/evaluation/consensus.py +569 -11
- ouroboros/evaluation/models.py +81 -0
- ouroboros/events/ontology.py +135 -0
- ouroboros/mcp/__init__.py +83 -0
- ouroboros/mcp/client/__init__.py +20 -0
- ouroboros/mcp/client/adapter.py +632 -0
- ouroboros/mcp/client/manager.py +600 -0
- ouroboros/mcp/client/protocol.py +161 -0
- ouroboros/mcp/errors.py +377 -0
- ouroboros/mcp/resources/__init__.py +22 -0
- ouroboros/mcp/resources/handlers.py +328 -0
- ouroboros/mcp/server/__init__.py +21 -0
- ouroboros/mcp/server/adapter.py +408 -0
- ouroboros/mcp/server/protocol.py +291 -0
- ouroboros/mcp/server/security.py +636 -0
- ouroboros/mcp/tools/__init__.py +24 -0
- ouroboros/mcp/tools/definitions.py +351 -0
- ouroboros/mcp/tools/registry.py +269 -0
- ouroboros/mcp/types.py +333 -0
- ouroboros/orchestrator/__init__.py +31 -0
- ouroboros/orchestrator/events.py +40 -0
- ouroboros/orchestrator/mcp_config.py +419 -0
- ouroboros/orchestrator/mcp_tools.py +483 -0
- ouroboros/orchestrator/runner.py +119 -2
- ouroboros/providers/claude_code_adapter.py +75 -0
- ouroboros/strategies/__init__.py +23 -0
- ouroboros/strategies/devil_advocate.py +197 -0
- {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/METADATA +10 -5
- {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/RECORD +42 -17
- {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/WHEEL +0 -0
- {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/entry_points.txt +0 -0
- {ouroboros_ai-0.3.0.dist-info → ouroboros_ai-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""MCP Server adapter implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the MCPServerAdapter class that implements the MCPServer
|
|
4
|
+
protocol using the MCP SDK (FastMCP). It handles tool registration, resource
|
|
5
|
+
handling, and server lifecycle.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import structlog
|
|
13
|
+
|
|
14
|
+
from ouroboros.core.types import Result
|
|
15
|
+
from ouroboros.mcp.errors import (
|
|
16
|
+
MCPResourceNotFoundError,
|
|
17
|
+
MCPServerError,
|
|
18
|
+
MCPToolError,
|
|
19
|
+
)
|
|
20
|
+
from ouroboros.mcp.server.protocol import PromptHandler, ResourceHandler, ToolHandler
|
|
21
|
+
from ouroboros.mcp.server.security import AuthConfig, RateLimitConfig, SecurityLayer
|
|
22
|
+
from ouroboros.mcp.types import (
|
|
23
|
+
MCPCapabilities,
|
|
24
|
+
MCPPromptDefinition,
|
|
25
|
+
MCPResourceContent,
|
|
26
|
+
MCPResourceDefinition,
|
|
27
|
+
MCPServerInfo,
|
|
28
|
+
MCPToolDefinition,
|
|
29
|
+
MCPToolResult,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
log = structlog.get_logger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MCPServerAdapter:
|
|
36
|
+
"""Concrete implementation of MCPServer protocol.
|
|
37
|
+
|
|
38
|
+
Uses the MCP SDK to expose Ouroboros functionality as an MCP server.
|
|
39
|
+
Supports tool registration, resource handling, and optional security.
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
server = MCPServerAdapter(
|
|
43
|
+
name="ouroboros-mcp",
|
|
44
|
+
version="1.0.0",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Register handlers
|
|
48
|
+
server.register_tool(ExecuteSeedHandler())
|
|
49
|
+
server.register_resource(SessionResourceHandler())
|
|
50
|
+
|
|
51
|
+
# Start serving
|
|
52
|
+
await server.serve()
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
name: str = "ouroboros-mcp",
|
|
59
|
+
version: str = "1.0.0",
|
|
60
|
+
auth_config: AuthConfig | None = None,
|
|
61
|
+
rate_limit_config: RateLimitConfig | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Initialize the server adapter.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
name: Server name for identification.
|
|
67
|
+
version: Server version.
|
|
68
|
+
auth_config: Optional authentication configuration.
|
|
69
|
+
rate_limit_config: Optional rate limiting configuration.
|
|
70
|
+
"""
|
|
71
|
+
self._name = name
|
|
72
|
+
self._version = version
|
|
73
|
+
self._tool_handlers: dict[str, ToolHandler] = {}
|
|
74
|
+
self._resource_handlers: dict[str, ResourceHandler] = {}
|
|
75
|
+
self._prompt_handlers: dict[str, PromptHandler] = {}
|
|
76
|
+
self._mcp_server: Any = None
|
|
77
|
+
|
|
78
|
+
# Initialize security layer
|
|
79
|
+
self._security = SecurityLayer(
|
|
80
|
+
auth_config=auth_config or AuthConfig(),
|
|
81
|
+
rate_limit_config=rate_limit_config or RateLimitConfig(),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def info(self) -> MCPServerInfo:
|
|
86
|
+
"""Return server information."""
|
|
87
|
+
return MCPServerInfo(
|
|
88
|
+
name=self._name,
|
|
89
|
+
version=self._version,
|
|
90
|
+
capabilities=MCPCapabilities(
|
|
91
|
+
tools=len(self._tool_handlers) > 0,
|
|
92
|
+
resources=len(self._resource_handlers) > 0,
|
|
93
|
+
prompts=len(self._prompt_handlers) > 0,
|
|
94
|
+
logging=True,
|
|
95
|
+
),
|
|
96
|
+
tools=tuple(h.definition for h in self._tool_handlers.values()),
|
|
97
|
+
resources=tuple(
|
|
98
|
+
defn
|
|
99
|
+
for handler in self._resource_handlers.values()
|
|
100
|
+
for defn in handler.definitions
|
|
101
|
+
),
|
|
102
|
+
prompts=tuple(h.definition for h in self._prompt_handlers.values()),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def register_tool(self, handler: ToolHandler) -> None:
|
|
106
|
+
"""Register a tool handler.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
handler: The tool handler to register.
|
|
110
|
+
"""
|
|
111
|
+
name = handler.definition.name
|
|
112
|
+
self._tool_handlers[name] = handler
|
|
113
|
+
log.info("mcp.server.tool_registered", tool=name)
|
|
114
|
+
|
|
115
|
+
def register_resource(self, handler: ResourceHandler) -> None:
|
|
116
|
+
"""Register a resource handler.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
handler: The resource handler to register.
|
|
120
|
+
"""
|
|
121
|
+
for defn in handler.definitions:
|
|
122
|
+
self._resource_handlers[defn.uri] = handler
|
|
123
|
+
log.info("mcp.server.resource_registered", uri=defn.uri)
|
|
124
|
+
|
|
125
|
+
def register_prompt(self, handler: PromptHandler) -> None:
|
|
126
|
+
"""Register a prompt handler.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
handler: The prompt handler to register.
|
|
130
|
+
"""
|
|
131
|
+
name = handler.definition.name
|
|
132
|
+
self._prompt_handlers[name] = handler
|
|
133
|
+
log.info("mcp.server.prompt_registered", prompt=name)
|
|
134
|
+
|
|
135
|
+
async def list_tools(self) -> Sequence[MCPToolDefinition]:
|
|
136
|
+
"""List all registered tools.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Sequence of tool definitions.
|
|
140
|
+
"""
|
|
141
|
+
return tuple(h.definition for h in self._tool_handlers.values())
|
|
142
|
+
|
|
143
|
+
async def list_resources(self) -> Sequence[MCPResourceDefinition]:
|
|
144
|
+
"""List all registered resources.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Sequence of resource definitions.
|
|
148
|
+
"""
|
|
149
|
+
# Collect unique definitions from all handlers
|
|
150
|
+
seen_uris: set[str] = set()
|
|
151
|
+
definitions: list[MCPResourceDefinition] = []
|
|
152
|
+
|
|
153
|
+
for handler in self._resource_handlers.values():
|
|
154
|
+
for defn in handler.definitions:
|
|
155
|
+
if defn.uri not in seen_uris:
|
|
156
|
+
seen_uris.add(defn.uri)
|
|
157
|
+
definitions.append(defn)
|
|
158
|
+
|
|
159
|
+
return definitions
|
|
160
|
+
|
|
161
|
+
async def list_prompts(self) -> Sequence[MCPPromptDefinition]:
|
|
162
|
+
"""List all registered prompts.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Sequence of prompt definitions.
|
|
166
|
+
"""
|
|
167
|
+
return tuple(h.definition for h in self._prompt_handlers.values())
|
|
168
|
+
|
|
169
|
+
async def call_tool(
|
|
170
|
+
self,
|
|
171
|
+
name: str,
|
|
172
|
+
arguments: dict[str, Any],
|
|
173
|
+
credentials: dict[str, str] | None = None,
|
|
174
|
+
) -> Result[MCPToolResult, MCPServerError]:
|
|
175
|
+
"""Call a registered tool.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
name: Name of the tool to call.
|
|
179
|
+
arguments: Arguments for the tool.
|
|
180
|
+
credentials: Optional credentials for authentication.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Result containing the tool result or an error.
|
|
184
|
+
"""
|
|
185
|
+
handler = self._tool_handlers.get(name)
|
|
186
|
+
if not handler:
|
|
187
|
+
return Result.err(
|
|
188
|
+
MCPResourceNotFoundError(
|
|
189
|
+
f"Tool not found: {name}",
|
|
190
|
+
server_name=self._name,
|
|
191
|
+
resource_type="tool",
|
|
192
|
+
resource_id=name,
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Security check
|
|
197
|
+
security_result = await self._security.check_request(name, arguments, credentials)
|
|
198
|
+
if security_result.is_err:
|
|
199
|
+
return Result.err(security_result.error)
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
result = await asyncio.wait_for(handler.handle(arguments), timeout=30.0)
|
|
203
|
+
return result
|
|
204
|
+
except asyncio.TimeoutError:
|
|
205
|
+
log.error("mcp.server.tool_timeout", tool=name)
|
|
206
|
+
return Result.err(
|
|
207
|
+
MCPToolError(
|
|
208
|
+
f"Tool execution timed out after 30s: {name}",
|
|
209
|
+
server_name=self._name,
|
|
210
|
+
tool_name=name,
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
log.error("mcp.server.tool_error", tool=name, error=str(e))
|
|
215
|
+
return Result.err(
|
|
216
|
+
MCPToolError(
|
|
217
|
+
f"Tool execution failed: {e}",
|
|
218
|
+
server_name=self._name,
|
|
219
|
+
tool_name=name,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
async def read_resource(
|
|
224
|
+
self,
|
|
225
|
+
uri: str,
|
|
226
|
+
) -> Result[MCPResourceContent, MCPServerError]:
|
|
227
|
+
"""Read a registered resource.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
uri: URI of the resource to read.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Result containing the resource content or an error.
|
|
234
|
+
"""
|
|
235
|
+
handler = self._resource_handlers.get(uri)
|
|
236
|
+
if not handler:
|
|
237
|
+
return Result.err(
|
|
238
|
+
MCPResourceNotFoundError(
|
|
239
|
+
f"Resource not found: {uri}",
|
|
240
|
+
server_name=self._name,
|
|
241
|
+
resource_type="resource",
|
|
242
|
+
resource_id=uri,
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
result = await handler.handle(uri)
|
|
248
|
+
return result
|
|
249
|
+
except Exception as e:
|
|
250
|
+
log.error("mcp.server.resource_error", uri=uri, error=str(e))
|
|
251
|
+
return Result.err(
|
|
252
|
+
MCPServerError(
|
|
253
|
+
f"Resource read failed: {e}",
|
|
254
|
+
server_name=self._name,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def get_prompt(
|
|
259
|
+
self,
|
|
260
|
+
name: str,
|
|
261
|
+
arguments: dict[str, str],
|
|
262
|
+
) -> Result[str, MCPServerError]:
|
|
263
|
+
"""Get a filled prompt.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
name: Name of the prompt.
|
|
267
|
+
arguments: Arguments to fill in the template.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Result containing the filled prompt or an error.
|
|
271
|
+
"""
|
|
272
|
+
handler = self._prompt_handlers.get(name)
|
|
273
|
+
if not handler:
|
|
274
|
+
return Result.err(
|
|
275
|
+
MCPResourceNotFoundError(
|
|
276
|
+
f"Prompt not found: {name}",
|
|
277
|
+
server_name=self._name,
|
|
278
|
+
resource_type="prompt",
|
|
279
|
+
resource_id=name,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
result = await handler.handle(arguments)
|
|
285
|
+
return result
|
|
286
|
+
except Exception as e:
|
|
287
|
+
log.error("mcp.server.prompt_error", prompt=name, error=str(e))
|
|
288
|
+
return Result.err(
|
|
289
|
+
MCPServerError(
|
|
290
|
+
f"Prompt generation failed: {e}",
|
|
291
|
+
server_name=self._name,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def serve(self, transport: str = "stdio") -> None:
|
|
296
|
+
"""Start serving MCP requests.
|
|
297
|
+
|
|
298
|
+
This method blocks until the server is stopped.
|
|
299
|
+
Uses the MCP SDK's FastMCP server implementation.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
transport: Transport type - "stdio" or "sse".
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
from mcp.server.fastmcp import FastMCP
|
|
306
|
+
except ImportError as e:
|
|
307
|
+
msg = "mcp package not installed. Install with: pip install mcp"
|
|
308
|
+
raise ImportError(msg) from e
|
|
309
|
+
|
|
310
|
+
# Create FastMCP server
|
|
311
|
+
self._mcp_server = FastMCP(self._name)
|
|
312
|
+
|
|
313
|
+
# Register tools with FastMCP
|
|
314
|
+
for _name, handler in self._tool_handlers.items():
|
|
315
|
+
defn = handler.definition
|
|
316
|
+
|
|
317
|
+
# Create a closure to capture the handler
|
|
318
|
+
async def create_tool_wrapper(
|
|
319
|
+
h: ToolHandler,
|
|
320
|
+
) -> Any:
|
|
321
|
+
async def tool_wrapper(**kwargs: Any) -> Any:
|
|
322
|
+
result = await h.handle(kwargs)
|
|
323
|
+
if result.is_ok:
|
|
324
|
+
# Convert MCPToolResult to FastMCP format
|
|
325
|
+
tool_result = result.value
|
|
326
|
+
return tool_result.text_content
|
|
327
|
+
else:
|
|
328
|
+
return f"Error: {result.error}"
|
|
329
|
+
|
|
330
|
+
return tool_wrapper
|
|
331
|
+
|
|
332
|
+
wrapper = await create_tool_wrapper(handler)
|
|
333
|
+
self._mcp_server.tool(
|
|
334
|
+
name=defn.name,
|
|
335
|
+
description=defn.description,
|
|
336
|
+
)(wrapper)
|
|
337
|
+
|
|
338
|
+
# Register resources with FastMCP
|
|
339
|
+
for uri, res_handler in self._resource_handlers.items():
|
|
340
|
+
|
|
341
|
+
async def create_resource_wrapper(
|
|
342
|
+
h: ResourceHandler,
|
|
343
|
+
resource_uri: str,
|
|
344
|
+
) -> Any:
|
|
345
|
+
async def resource_wrapper() -> str:
|
|
346
|
+
result = await h.handle(resource_uri)
|
|
347
|
+
if result.is_ok:
|
|
348
|
+
content = result.value
|
|
349
|
+
return content.text or ""
|
|
350
|
+
else:
|
|
351
|
+
return f"Error: {result.error}"
|
|
352
|
+
|
|
353
|
+
return resource_wrapper
|
|
354
|
+
|
|
355
|
+
wrapper = await create_resource_wrapper(res_handler, uri)
|
|
356
|
+
self._mcp_server.resource(uri)(wrapper)
|
|
357
|
+
|
|
358
|
+
log.info(
|
|
359
|
+
"mcp.server.starting",
|
|
360
|
+
name=self._name,
|
|
361
|
+
tools=len(self._tool_handlers),
|
|
362
|
+
resources=len(self._resource_handlers),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Run the server with the appropriate transport
|
|
366
|
+
if transport == "sse":
|
|
367
|
+
await self._mcp_server.run_sse_async()
|
|
368
|
+
else:
|
|
369
|
+
await self._mcp_server.run_stdio_async()
|
|
370
|
+
|
|
371
|
+
async def shutdown(self) -> None:
|
|
372
|
+
"""Shutdown the server gracefully."""
|
|
373
|
+
log.info("mcp.server.shutdown", name=self._name)
|
|
374
|
+
# FastMCP handles its own shutdown when run_async completes
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def create_ouroboros_server(
|
|
378
|
+
*,
|
|
379
|
+
name: str = "ouroboros-mcp",
|
|
380
|
+
version: str = "1.0.0",
|
|
381
|
+
auth_config: AuthConfig | None = None,
|
|
382
|
+
rate_limit_config: RateLimitConfig | None = None,
|
|
383
|
+
) -> MCPServerAdapter:
|
|
384
|
+
"""Create an Ouroboros MCP server with default handlers.
|
|
385
|
+
|
|
386
|
+
This is a convenience function that creates a server with all
|
|
387
|
+
standard Ouroboros tools and resources pre-registered.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
name: Server name.
|
|
391
|
+
version: Server version.
|
|
392
|
+
auth_config: Optional authentication configuration.
|
|
393
|
+
rate_limit_config: Optional rate limiting configuration.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Configured MCPServerAdapter ready to serve.
|
|
397
|
+
"""
|
|
398
|
+
server = MCPServerAdapter(
|
|
399
|
+
name=name,
|
|
400
|
+
version=version,
|
|
401
|
+
auth_config=auth_config,
|
|
402
|
+
rate_limit_config=rate_limit_config,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Tools and resources will be registered separately
|
|
406
|
+
# to avoid circular imports
|
|
407
|
+
|
|
408
|
+
return server
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""MCP Server protocol definitions.
|
|
2
|
+
|
|
3
|
+
This module defines the protocols that MCP server implementations must follow.
|
|
4
|
+
It provides interfaces for the server, tool handlers, and resource handlers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
8
|
+
from typing import Any, Protocol
|
|
9
|
+
|
|
10
|
+
from ouroboros.core.types import Result
|
|
11
|
+
from ouroboros.mcp.errors import MCPServerError
|
|
12
|
+
from ouroboros.mcp.types import (
|
|
13
|
+
MCPPromptDefinition,
|
|
14
|
+
MCPResourceContent,
|
|
15
|
+
MCPResourceDefinition,
|
|
16
|
+
MCPServerInfo,
|
|
17
|
+
MCPToolDefinition,
|
|
18
|
+
MCPToolResult,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Type aliases for handler functions
|
|
22
|
+
ToolHandlerFunc = Callable[[dict[str, Any]], Awaitable[Result[MCPToolResult, MCPServerError]]]
|
|
23
|
+
ResourceHandlerFunc = Callable[[str], Awaitable[Result[MCPResourceContent, MCPServerError]]]
|
|
24
|
+
PromptHandlerFunc = Callable[[dict[str, str]], Awaitable[Result[str, MCPServerError]]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ToolHandler(Protocol):
|
|
28
|
+
"""Protocol for tool handler implementations.
|
|
29
|
+
|
|
30
|
+
Tool handlers process incoming tool calls from MCP clients.
|
|
31
|
+
Each handler corresponds to a specific tool and is responsible
|
|
32
|
+
for validating arguments and executing the tool logic.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
class MyToolHandler:
|
|
36
|
+
def __init__(self, service: MyService):
|
|
37
|
+
self._service = service
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def definition(self) -> MCPToolDefinition:
|
|
41
|
+
return MCPToolDefinition(
|
|
42
|
+
name="my_tool",
|
|
43
|
+
description="Does something useful",
|
|
44
|
+
parameters=(
|
|
45
|
+
MCPToolParameter(name="input", type=ToolInputType.STRING),
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def handle(
|
|
50
|
+
self, arguments: dict[str, Any]
|
|
51
|
+
) -> Result[MCPToolResult, MCPServerError]:
|
|
52
|
+
result = await self._service.process(arguments["input"])
|
|
53
|
+
return Result.ok(MCPToolResult(...))
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def definition(self) -> MCPToolDefinition:
|
|
58
|
+
"""Return the tool definition."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
async def handle(
|
|
62
|
+
self,
|
|
63
|
+
arguments: dict[str, Any],
|
|
64
|
+
) -> Result[MCPToolResult, MCPServerError]:
|
|
65
|
+
"""Handle a tool call.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
arguments: The arguments passed to the tool.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Result containing the tool result or an error.
|
|
72
|
+
"""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ResourceHandler(Protocol):
|
|
77
|
+
"""Protocol for resource handler implementations.
|
|
78
|
+
|
|
79
|
+
Resource handlers provide read access to resources via URI.
|
|
80
|
+
Each handler is responsible for one or more resource URIs.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
class SessionResourceHandler:
|
|
84
|
+
@property
|
|
85
|
+
def definitions(self) -> Sequence[MCPResourceDefinition]:
|
|
86
|
+
return [
|
|
87
|
+
MCPResourceDefinition(
|
|
88
|
+
uri="ouroboros://sessions/current",
|
|
89
|
+
name="Current Session",
|
|
90
|
+
description="The current active session",
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
async def handle(
|
|
95
|
+
self, uri: str
|
|
96
|
+
) -> Result[MCPResourceContent, MCPServerError]:
|
|
97
|
+
# Fetch and return the resource content
|
|
98
|
+
...
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def definitions(self) -> Sequence[MCPResourceDefinition]:
|
|
103
|
+
"""Return the list of resource definitions this handler provides."""
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
async def handle(
|
|
107
|
+
self,
|
|
108
|
+
uri: str,
|
|
109
|
+
) -> Result[MCPResourceContent, MCPServerError]:
|
|
110
|
+
"""Handle a resource read request.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
uri: The URI of the resource to read.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Result containing the resource content or an error.
|
|
117
|
+
"""
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PromptHandler(Protocol):
|
|
122
|
+
"""Protocol for prompt handler implementations.
|
|
123
|
+
|
|
124
|
+
Prompt handlers provide dynamic prompt templates that can be
|
|
125
|
+
filled with arguments from the client.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
class AnalysisPromptHandler:
|
|
129
|
+
@property
|
|
130
|
+
def definition(self) -> MCPPromptDefinition:
|
|
131
|
+
return MCPPromptDefinition(
|
|
132
|
+
name="analyze",
|
|
133
|
+
description="Analyze code for issues",
|
|
134
|
+
arguments=(
|
|
135
|
+
MCPPromptArgument(name="code", required=True),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
async def handle(
|
|
140
|
+
self, arguments: dict[str, str]
|
|
141
|
+
) -> Result[str, MCPServerError]:
|
|
142
|
+
return Result.ok(f"Analyze this code:\n{arguments['code']}")
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def definition(self) -> MCPPromptDefinition:
|
|
147
|
+
"""Return the prompt definition."""
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
async def handle(
|
|
151
|
+
self,
|
|
152
|
+
arguments: dict[str, str],
|
|
153
|
+
) -> Result[str, MCPServerError]:
|
|
154
|
+
"""Handle a prompt request.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
arguments: The arguments to fill in the prompt template.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Result containing the filled prompt or an error.
|
|
161
|
+
"""
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class MCPServer(Protocol):
|
|
166
|
+
"""Protocol for MCP server implementations.
|
|
167
|
+
|
|
168
|
+
This protocol defines the interface that all MCP server adapters must
|
|
169
|
+
implement. It supports registering handlers for tools, resources, and
|
|
170
|
+
prompts, as well as starting and stopping the server.
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
server = MCPServerAdapter(name="ouroboros-mcp")
|
|
174
|
+
|
|
175
|
+
# Register handlers
|
|
176
|
+
server.register_tool(ExecuteSeedHandler())
|
|
177
|
+
server.register_resource(SessionResourceHandler())
|
|
178
|
+
|
|
179
|
+
# Start serving
|
|
180
|
+
await server.serve()
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def info(self) -> MCPServerInfo:
|
|
185
|
+
"""Return server information."""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
def register_tool(self, handler: ToolHandler) -> None:
|
|
189
|
+
"""Register a tool handler.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
handler: The tool handler to register.
|
|
193
|
+
"""
|
|
194
|
+
...
|
|
195
|
+
|
|
196
|
+
def register_resource(self, handler: ResourceHandler) -> None:
|
|
197
|
+
"""Register a resource handler.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
handler: The resource handler to register.
|
|
201
|
+
"""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
def register_prompt(self, handler: PromptHandler) -> None:
|
|
205
|
+
"""Register a prompt handler.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
handler: The prompt handler to register.
|
|
209
|
+
"""
|
|
210
|
+
...
|
|
211
|
+
|
|
212
|
+
async def list_tools(self) -> Sequence[MCPToolDefinition]:
|
|
213
|
+
"""List all registered tools.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Sequence of tool definitions.
|
|
217
|
+
"""
|
|
218
|
+
...
|
|
219
|
+
|
|
220
|
+
async def list_resources(self) -> Sequence[MCPResourceDefinition]:
|
|
221
|
+
"""List all registered resources.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Sequence of resource definitions.
|
|
225
|
+
"""
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
async def list_prompts(self) -> Sequence[MCPPromptDefinition]:
|
|
229
|
+
"""List all registered prompts.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Sequence of prompt definitions.
|
|
233
|
+
"""
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
async def call_tool(
|
|
237
|
+
self,
|
|
238
|
+
name: str,
|
|
239
|
+
arguments: dict[str, Any],
|
|
240
|
+
) -> Result[MCPToolResult, MCPServerError]:
|
|
241
|
+
"""Call a registered tool.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
name: Name of the tool to call.
|
|
245
|
+
arguments: Arguments for the tool.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Result containing the tool result or an error.
|
|
249
|
+
"""
|
|
250
|
+
...
|
|
251
|
+
|
|
252
|
+
async def read_resource(
|
|
253
|
+
self,
|
|
254
|
+
uri: str,
|
|
255
|
+
) -> Result[MCPResourceContent, MCPServerError]:
|
|
256
|
+
"""Read a registered resource.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
uri: URI of the resource to read.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Result containing the resource content or an error.
|
|
263
|
+
"""
|
|
264
|
+
...
|
|
265
|
+
|
|
266
|
+
async def get_prompt(
|
|
267
|
+
self,
|
|
268
|
+
name: str,
|
|
269
|
+
arguments: dict[str, str],
|
|
270
|
+
) -> Result[str, MCPServerError]:
|
|
271
|
+
"""Get a filled prompt.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
name: Name of the prompt.
|
|
275
|
+
arguments: Arguments to fill in the template.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Result containing the filled prompt or an error.
|
|
279
|
+
"""
|
|
280
|
+
...
|
|
281
|
+
|
|
282
|
+
async def serve(self) -> None:
|
|
283
|
+
"""Start serving MCP requests.
|
|
284
|
+
|
|
285
|
+
This method blocks until the server is stopped.
|
|
286
|
+
"""
|
|
287
|
+
...
|
|
288
|
+
|
|
289
|
+
async def shutdown(self) -> None:
|
|
290
|
+
"""Shutdown the server gracefully."""
|
|
291
|
+
...
|