ouroboros-ai 0.2.3__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.

Files changed (44) hide show
  1. ouroboros/__init__.py +1 -1
  2. ouroboros/bigbang/__init__.py +9 -0
  3. ouroboros/bigbang/interview.py +16 -18
  4. ouroboros/bigbang/ontology.py +180 -0
  5. ouroboros/cli/commands/__init__.py +2 -0
  6. ouroboros/cli/commands/init.py +162 -97
  7. ouroboros/cli/commands/mcp.py +161 -0
  8. ouroboros/cli/commands/run.py +165 -27
  9. ouroboros/cli/main.py +2 -1
  10. ouroboros/core/ontology_aspect.py +455 -0
  11. ouroboros/core/ontology_questions.py +462 -0
  12. ouroboros/evaluation/__init__.py +16 -1
  13. ouroboros/evaluation/consensus.py +569 -11
  14. ouroboros/evaluation/models.py +81 -0
  15. ouroboros/events/ontology.py +135 -0
  16. ouroboros/mcp/__init__.py +83 -0
  17. ouroboros/mcp/client/__init__.py +20 -0
  18. ouroboros/mcp/client/adapter.py +632 -0
  19. ouroboros/mcp/client/manager.py +600 -0
  20. ouroboros/mcp/client/protocol.py +161 -0
  21. ouroboros/mcp/errors.py +377 -0
  22. ouroboros/mcp/resources/__init__.py +22 -0
  23. ouroboros/mcp/resources/handlers.py +328 -0
  24. ouroboros/mcp/server/__init__.py +21 -0
  25. ouroboros/mcp/server/adapter.py +408 -0
  26. ouroboros/mcp/server/protocol.py +291 -0
  27. ouroboros/mcp/server/security.py +636 -0
  28. ouroboros/mcp/tools/__init__.py +24 -0
  29. ouroboros/mcp/tools/definitions.py +351 -0
  30. ouroboros/mcp/tools/registry.py +269 -0
  31. ouroboros/mcp/types.py +333 -0
  32. ouroboros/orchestrator/__init__.py +31 -0
  33. ouroboros/orchestrator/events.py +40 -0
  34. ouroboros/orchestrator/mcp_config.py +419 -0
  35. ouroboros/orchestrator/mcp_tools.py +483 -0
  36. ouroboros/orchestrator/runner.py +119 -2
  37. ouroboros/providers/claude_code_adapter.py +75 -0
  38. ouroboros/strategies/__init__.py +23 -0
  39. ouroboros/strategies/devil_advocate.py +197 -0
  40. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/METADATA +73 -17
  41. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/RECORD +44 -19
  42. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/WHEEL +0 -0
  43. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/entry_points.txt +0 -0
  44. {ouroboros_ai-0.2.3.dist-info → ouroboros_ai-0.4.0.dist-info}/licenses/LICENSE +0 -0
ouroboros/mcp/types.py ADDED
@@ -0,0 +1,333 @@
1
+ """MCP types for Ouroboros.
2
+
3
+ This module defines frozen dataclasses for MCP data structures including
4
+ server configuration, tool definitions, and results.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from enum import StrEnum
9
+ from typing import Any
10
+
11
+
12
+ class TransportType(StrEnum):
13
+ """MCP transport type for server connections."""
14
+
15
+ STDIO = "stdio"
16
+ SSE = "sse"
17
+ STREAMABLE_HTTP = "streamable-http"
18
+
19
+
20
+ class ToolInputType(StrEnum):
21
+ """JSON Schema types for tool input parameters."""
22
+
23
+ STRING = "string"
24
+ NUMBER = "number"
25
+ INTEGER = "integer"
26
+ BOOLEAN = "boolean"
27
+ ARRAY = "array"
28
+ OBJECT = "object"
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class MCPServerConfig:
33
+ """Configuration for connecting to an MCP server.
34
+
35
+ Attributes:
36
+ name: Unique name for the server connection.
37
+ transport: Transport type (stdio, sse, etc.).
38
+ command: Command to run for stdio transport.
39
+ args: Arguments for the command.
40
+ url: URL for SSE/HTTP transport.
41
+ env: Environment variables to set.
42
+ timeout: Connection timeout in seconds.
43
+ headers: HTTP headers for SSE/HTTP transport.
44
+ """
45
+
46
+ name: str
47
+ transport: TransportType
48
+ command: str | None = None
49
+ args: tuple[str, ...] = field(default_factory=tuple)
50
+ url: str | None = None
51
+ env: dict[str, str] = field(default_factory=dict)
52
+ timeout: float = 30.0
53
+ headers: dict[str, str] = field(default_factory=dict)
54
+
55
+ def __post_init__(self) -> None:
56
+ """Validate configuration after initialization."""
57
+ if self.transport == TransportType.STDIO and not self.command:
58
+ msg = "command is required for stdio transport"
59
+ raise ValueError(msg)
60
+ if self.transport in (TransportType.SSE, TransportType.STREAMABLE_HTTP) and not self.url:
61
+ msg = f"url is required for {self.transport} transport"
62
+ raise ValueError(msg)
63
+
64
+
65
+ @dataclass(frozen=True, slots=True)
66
+ class MCPToolParameter:
67
+ """A single parameter for an MCP tool.
68
+
69
+ Attributes:
70
+ name: Parameter name.
71
+ type: JSON Schema type of the parameter.
72
+ description: Human-readable description.
73
+ required: Whether the parameter is required.
74
+ default: Default value if not provided.
75
+ enum: Allowed values if restricted.
76
+ """
77
+
78
+ name: str
79
+ type: ToolInputType
80
+ description: str = ""
81
+ required: bool = True
82
+ default: Any = None
83
+ enum: tuple[str, ...] | None = None
84
+
85
+
86
+ @dataclass(frozen=True, slots=True)
87
+ class MCPToolDefinition:
88
+ """Definition of an MCP tool.
89
+
90
+ Attributes:
91
+ name: Unique tool name.
92
+ description: Human-readable description.
93
+ parameters: List of tool parameters.
94
+ server_name: Name of the server providing this tool.
95
+ """
96
+
97
+ name: str
98
+ description: str
99
+ parameters: tuple[MCPToolParameter, ...] = field(default_factory=tuple)
100
+ server_name: str | None = None
101
+
102
+ def to_input_schema(self) -> dict[str, Any]:
103
+ """Convert to JSON Schema for tool input.
104
+
105
+ Returns:
106
+ A JSON Schema dict describing the tool's input parameters.
107
+ """
108
+ properties: dict[str, Any] = {}
109
+ required: list[str] = []
110
+
111
+ for param in self.parameters:
112
+ prop: dict[str, Any] = {
113
+ "type": param.type.value,
114
+ "description": param.description,
115
+ }
116
+ if param.default is not None:
117
+ prop["default"] = param.default
118
+ if param.enum is not None:
119
+ prop["enum"] = list(param.enum)
120
+ properties[param.name] = prop
121
+ if param.required:
122
+ required.append(param.name)
123
+
124
+ return {
125
+ "type": "object",
126
+ "properties": properties,
127
+ "required": required,
128
+ }
129
+
130
+
131
+ @dataclass(frozen=True, slots=True)
132
+ class MCPToolResult:
133
+ """Result from an MCP tool invocation.
134
+
135
+ Attributes:
136
+ content: List of content items from the tool.
137
+ is_error: Whether the tool execution resulted in an error.
138
+ meta: Optional metadata from the tool.
139
+ """
140
+
141
+ content: tuple[MCPContentItem, ...] = field(default_factory=tuple)
142
+ is_error: bool = False
143
+ meta: dict[str, Any] = field(default_factory=dict)
144
+
145
+ @property
146
+ def text_content(self) -> str:
147
+ """Return concatenated text content from all text items.
148
+
149
+ Returns:
150
+ All text content joined with newlines.
151
+ """
152
+ return "\n".join(
153
+ item.text for item in self.content if item.type == ContentType.TEXT and item.text
154
+ )
155
+
156
+
157
+ class ContentType(StrEnum):
158
+ """Type of content in an MCP response."""
159
+
160
+ TEXT = "text"
161
+ IMAGE = "image"
162
+ RESOURCE = "resource"
163
+
164
+
165
+ @dataclass(frozen=True, slots=True)
166
+ class MCPContentItem:
167
+ """A single content item in an MCP response.
168
+
169
+ Attributes:
170
+ type: Type of content (text, image, resource).
171
+ text: Text content if type is TEXT.
172
+ data: Binary data (base64) if type is IMAGE.
173
+ mime_type: MIME type for binary data.
174
+ uri: Resource URI if type is RESOURCE.
175
+ """
176
+
177
+ type: ContentType
178
+ text: str | None = None
179
+ data: str | None = None
180
+ mime_type: str | None = None
181
+ uri: str | None = None
182
+
183
+
184
+ @dataclass(frozen=True, slots=True)
185
+ class MCPResourceDefinition:
186
+ """Definition of an MCP resource.
187
+
188
+ Attributes:
189
+ uri: Resource URI (unique identifier).
190
+ name: Human-readable name.
191
+ description: Description of the resource.
192
+ mime_type: MIME type of the resource content.
193
+ """
194
+
195
+ uri: str
196
+ name: str
197
+ description: str = ""
198
+ mime_type: str = "text/plain"
199
+
200
+
201
+ @dataclass(frozen=True, slots=True)
202
+ class MCPResourceContent:
203
+ """Content of an MCP resource.
204
+
205
+ Attributes:
206
+ uri: Resource URI.
207
+ text: Text content (for text resources).
208
+ blob: Binary content as base64 (for binary resources).
209
+ mime_type: MIME type of the content.
210
+ """
211
+
212
+ uri: str
213
+ text: str | None = None
214
+ blob: str | None = None
215
+ mime_type: str = "text/plain"
216
+
217
+
218
+ @dataclass(frozen=True, slots=True)
219
+ class MCPPromptDefinition:
220
+ """Definition of an MCP prompt.
221
+
222
+ Attributes:
223
+ name: Unique prompt name.
224
+ description: Description of what the prompt does.
225
+ arguments: List of argument definitions.
226
+ """
227
+
228
+ name: str
229
+ description: str = ""
230
+ arguments: tuple[MCPPromptArgument, ...] = field(default_factory=tuple)
231
+
232
+
233
+ @dataclass(frozen=True, slots=True)
234
+ class MCPPromptArgument:
235
+ """Argument definition for an MCP prompt.
236
+
237
+ Attributes:
238
+ name: Argument name.
239
+ description: Description of the argument.
240
+ required: Whether the argument is required.
241
+ """
242
+
243
+ name: str
244
+ description: str = ""
245
+ required: bool = True
246
+
247
+
248
+ @dataclass(frozen=True, slots=True)
249
+ class MCPCapabilities:
250
+ """Capabilities of an MCP server.
251
+
252
+ Attributes:
253
+ tools: Whether the server supports tools.
254
+ resources: Whether the server supports resources.
255
+ prompts: Whether the server supports prompts.
256
+ logging: Whether the server supports logging.
257
+ """
258
+
259
+ tools: bool = False
260
+ resources: bool = False
261
+ prompts: bool = False
262
+ logging: bool = False
263
+
264
+
265
+ @dataclass(frozen=True, slots=True)
266
+ class MCPServerInfo:
267
+ """Information about an MCP server.
268
+
269
+ Attributes:
270
+ name: Server name.
271
+ version: Server version.
272
+ capabilities: Server capabilities.
273
+ tools: Available tools.
274
+ resources: Available resources.
275
+ prompts: Available prompts.
276
+ """
277
+
278
+ name: str
279
+ version: str = "1.0.0"
280
+ capabilities: MCPCapabilities = field(default_factory=MCPCapabilities)
281
+ tools: tuple[MCPToolDefinition, ...] = field(default_factory=tuple)
282
+ resources: tuple[MCPResourceDefinition, ...] = field(default_factory=tuple)
283
+ prompts: tuple[MCPPromptDefinition, ...] = field(default_factory=tuple)
284
+
285
+
286
+ @dataclass(frozen=True, slots=True)
287
+ class MCPRequest:
288
+ """An MCP request message.
289
+
290
+ Attributes:
291
+ method: The MCP method being called.
292
+ params: Parameters for the method.
293
+ request_id: Unique request identifier.
294
+ """
295
+
296
+ method: str
297
+ params: dict[str, Any] = field(default_factory=dict)
298
+ request_id: str | None = None
299
+
300
+
301
+ @dataclass(frozen=True, slots=True)
302
+ class MCPResponse:
303
+ """An MCP response message.
304
+
305
+ Attributes:
306
+ result: The result data if successful.
307
+ error: Error information if failed.
308
+ request_id: The request ID this is responding to.
309
+ """
310
+
311
+ result: dict[str, Any] | None = None
312
+ error: MCPResponseError | None = None
313
+ request_id: str | None = None
314
+
315
+ @property
316
+ def is_success(self) -> bool:
317
+ """Return True if this is a successful response."""
318
+ return self.error is None
319
+
320
+
321
+ @dataclass(frozen=True, slots=True)
322
+ class MCPResponseError:
323
+ """Error information in an MCP response.
324
+
325
+ Attributes:
326
+ code: Error code.
327
+ message: Error message.
328
+ data: Additional error data.
329
+ """
330
+
331
+ code: int
332
+ message: str
333
+ data: dict[str, Any] | None = None
@@ -8,6 +8,7 @@ Key Components:
8
8
  - SessionTracker: Immutable session state tracking
9
9
  - SessionRepository: Event-based session persistence
10
10
  - OrchestratorRunner: Main orchestration logic
11
+ - MCPToolProvider: Integration with external MCP tools
11
12
 
12
13
  Usage:
13
14
  from ouroboros.orchestrator import ClaudeAgentAdapter, OrchestratorRunner
@@ -16,9 +17,15 @@ Usage:
16
17
  runner = OrchestratorRunner(adapter, event_store)
17
18
  result = await runner.execute_seed(seed, execution_id)
18
19
 
20
+ # With MCP tools:
21
+ from ouroboros.mcp.client.manager import MCPClientManager
22
+ mcp_manager = MCPClientManager()
23
+ runner = OrchestratorRunner(adapter, event_store, mcp_manager=mcp_manager)
24
+
19
25
  CLI Usage:
20
26
  ouroboros run --orchestrator seed.yaml
21
27
  ouroboros run --orchestrator seed.yaml --resume <session_id>
28
+ ouroboros run --orchestrator seed.yaml --mcp-config mcp.yaml
22
29
  """
23
30
 
24
31
  from ouroboros.orchestrator.adapter import (
@@ -28,6 +35,7 @@ from ouroboros.orchestrator.adapter import (
28
35
  TaskResult,
29
36
  )
30
37
  from ouroboros.orchestrator.events import (
38
+ create_mcp_tools_loaded_event,
31
39
  create_progress_event,
32
40
  create_session_completed_event,
33
41
  create_session_failed_event,
@@ -37,6 +45,18 @@ from ouroboros.orchestrator.events import (
37
45
  create_task_started_event,
38
46
  create_tool_called_event,
39
47
  )
48
+ from ouroboros.orchestrator.mcp_config import (
49
+ ConfigError,
50
+ MCPClientConfig,
51
+ MCPConnectionConfig,
52
+ load_mcp_config,
53
+ )
54
+ from ouroboros.orchestrator.mcp_tools import (
55
+ MCPToolInfo,
56
+ MCPToolProvider,
57
+ MCPToolsLoadedEvent,
58
+ ToolConflict,
59
+ )
40
60
  from ouroboros.orchestrator.runner import (
41
61
  OrchestratorError,
42
62
  OrchestratorResult,
@@ -66,7 +86,18 @@ __all__ = [
66
86
  "OrchestratorRunner",
67
87
  "build_system_prompt",
68
88
  "build_task_prompt",
89
+ # MCP Config
90
+ "ConfigError",
91
+ "MCPClientConfig",
92
+ "MCPConnectionConfig",
93
+ "load_mcp_config",
94
+ # MCP Tools
95
+ "MCPToolInfo",
96
+ "MCPToolProvider",
97
+ "MCPToolsLoadedEvent",
98
+ "ToolConflict",
69
99
  # Events
100
+ "create_mcp_tools_loaded_event",
70
101
  "create_progress_event",
71
102
  "create_session_completed_event",
72
103
  "create_session_failed_event",
@@ -266,7 +266,47 @@ def create_tool_called_event(
266
266
  )
267
267
 
268
268
 
269
+ def create_mcp_tools_loaded_event(
270
+ session_id: str,
271
+ tool_count: int,
272
+ server_names: tuple[str, ...],
273
+ conflict_count: int = 0,
274
+ tool_names: list[str] | None = None,
275
+ ) -> BaseEvent:
276
+ """Create MCP tools loaded event.
277
+
278
+ Emitted when MCP tools are discovered and loaded for a session.
279
+
280
+ Args:
281
+ session_id: Session loading the tools.
282
+ tool_count: Number of MCP tools loaded.
283
+ server_names: Names of MCP servers providing tools.
284
+ conflict_count: Number of tool name conflicts detected.
285
+ tool_names: Optional list of loaded tool names.
286
+
287
+ Returns:
288
+ BaseEvent for MCP tools loaded.
289
+ """
290
+ data: dict[str, Any] = {
291
+ "tool_count": tool_count,
292
+ "server_names": list(server_names),
293
+ "conflict_count": conflict_count,
294
+ "loaded_at": datetime.now(UTC).isoformat(),
295
+ }
296
+
297
+ if tool_names:
298
+ data["tool_names"] = tool_names[:50] # Limit to 50 for storage
299
+
300
+ return BaseEvent(
301
+ type="orchestrator.mcp_tools.loaded",
302
+ aggregate_type="session",
303
+ aggregate_id=session_id,
304
+ data=data,
305
+ )
306
+
307
+
269
308
  __all__ = [
309
+ "create_mcp_tools_loaded_event",
270
310
  "create_progress_event",
271
311
  "create_session_completed_event",
272
312
  "create_session_failed_event",