codebuddy-agent-sdk 0.1.27__py3-none-macosx_11_0_arm64.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 codebuddy-agent-sdk might be problematic. Click here for more details.

@@ -0,0 +1,300 @@
1
+ """Type definitions for SDK MCP Server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Awaitable, Callable
7
+ from dataclasses import dataclass, field
8
+ from typing import TYPE_CHECKING, Any, Literal, TypedDict, cast
9
+
10
+ if TYPE_CHECKING:
11
+ from .sdk_control_server_transport import SdkControlServerTransport
12
+
13
+
14
+ # ============= JSON-RPC Types =============
15
+
16
+
17
+ class JSONRPCRequest(TypedDict, total=False):
18
+ """JSON-RPC 2.0 request."""
19
+
20
+ jsonrpc: Literal["2.0"]
21
+ id: str | int
22
+ method: str
23
+ params: dict[str, Any] | list[Any] | None
24
+
25
+
26
+ class JSONRPCError(TypedDict, total=False):
27
+ """JSON-RPC 2.0 error."""
28
+
29
+ code: int
30
+ message: str
31
+ data: Any
32
+
33
+
34
+ class JSONRPCResponse(TypedDict, total=False):
35
+ """JSON-RPC 2.0 response."""
36
+
37
+ jsonrpc: Literal["2.0"]
38
+ id: str | int | None
39
+ result: Any
40
+ error: JSONRPCError
41
+
42
+
43
+ class JSONRPCNotification(TypedDict, total=False):
44
+ """JSON-RPC 2.0 notification."""
45
+
46
+ jsonrpc: Literal["2.0"]
47
+ method: str
48
+ params: dict[str, Any] | list[Any] | None
49
+
50
+
51
+ JSONRPCMessage = JSONRPCRequest | JSONRPCResponse | JSONRPCNotification
52
+
53
+
54
+ # ============= MCP Tool Types =============
55
+
56
+
57
+ class TextContent(TypedDict, total=False):
58
+ """Text content in tool result."""
59
+
60
+ type: Literal["text"]
61
+ text: str
62
+
63
+
64
+ class ImageContent(TypedDict, total=False):
65
+ """Image content in tool result."""
66
+
67
+ type: Literal["image"]
68
+ data: str
69
+ mimeType: str
70
+
71
+
72
+ class EmbeddedResource(TypedDict, total=False):
73
+ """Embedded resource content in tool result."""
74
+
75
+ type: Literal["resource"]
76
+ resource: dict[str, Any]
77
+
78
+
79
+ ToolResultContent = TextContent | ImageContent | EmbeddedResource
80
+
81
+
82
+ class CallToolResult(TypedDict, total=False):
83
+ """Result from calling a tool."""
84
+
85
+ content: list[ToolResultContent]
86
+ isError: bool
87
+
88
+
89
+ # Tool handler type - takes arguments dict and returns CallToolResult
90
+ ToolHandler = Callable[[dict[str, Any]], CallToolResult | Awaitable[CallToolResult]]
91
+
92
+
93
+ @dataclass
94
+ class ToolInputProperty:
95
+ """Property definition for tool input schema."""
96
+
97
+ type: str
98
+ description: str | None = None
99
+ enum: list[str] | None = None
100
+ default: Any = None
101
+ minimum: float | None = None
102
+ maximum: float | None = None
103
+
104
+
105
+ @dataclass
106
+ class ToolInputSchema:
107
+ """JSON Schema for tool input."""
108
+
109
+ type: Literal["object"] = "object"
110
+ properties: dict[str, dict[str, Any]] = field(default_factory=dict)
111
+ required: list[str] = field(default_factory=list)
112
+
113
+
114
+ @dataclass
115
+ class SdkMcpToolDefinition:
116
+ """
117
+ Tool definition for SDK MCP Server.
118
+
119
+ Example:
120
+ ```python
121
+ tool_def = SdkMcpToolDefinition(
122
+ name="get_weather",
123
+ description="Get the current weather for a location",
124
+ input_schema=ToolInputSchema(
125
+ properties={
126
+ "location": {"type": "string", "description": "The city name"},
127
+ "units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
128
+ },
129
+ required=["location"],
130
+ ),
131
+ handler=get_weather_handler,
132
+ )
133
+ ```
134
+ """
135
+
136
+ name: str
137
+ description: str
138
+ input_schema: ToolInputSchema
139
+ handler: ToolHandler
140
+
141
+
142
+ @dataclass
143
+ class SdkMcpServerOptions:
144
+ """
145
+ Options for creating an SDK MCP Server.
146
+
147
+ Attributes:
148
+ name: Server name (must be unique within the session)
149
+ version: Server version (defaults to "1.0.0")
150
+ tools: List of tool definitions to register
151
+ """
152
+
153
+ name: str
154
+ version: str = "1.0.0"
155
+ tools: list[SdkMcpToolDefinition] = field(default_factory=list)
156
+
157
+
158
+ @dataclass
159
+ class SdkMcpServerResult:
160
+ """
161
+ Result type for create_sdk_mcp_server.
162
+
163
+ Attributes:
164
+ type: Type discriminator - always "sdk" for SDK MCP servers
165
+ name: Server name
166
+ server: The MCP server instance
167
+ """
168
+
169
+ type: Literal["sdk"]
170
+ name: str
171
+ server: SdkMcpServer
172
+
173
+
174
+ class SdkMcpServer:
175
+ """
176
+ SDK MCP Server implementation.
177
+
178
+ This class implements an MCP server that runs within the SDK process
179
+ and communicates with the CLI via the control protocol.
180
+ """
181
+
182
+ def __init__(self, options: SdkMcpServerOptions):
183
+ self.name = options.name
184
+ self.version = options.version
185
+ self.tools: dict[str, SdkMcpToolDefinition] = {}
186
+ self._transport: SdkControlServerTransport | None = None
187
+
188
+ # Register tools
189
+ for tool_def in options.tools:
190
+ self.tools[tool_def.name] = tool_def
191
+
192
+ def connect(self, transport: SdkControlServerTransport) -> None:
193
+ """Connect the server to a transport."""
194
+ self._transport = transport
195
+
196
+ async def handle_message(self, message: JSONRPCMessage) -> JSONRPCMessage | None:
197
+ """Handle an incoming JSON-RPC message."""
198
+ # Check if it's a request (has method and id)
199
+ if "method" not in message:
200
+ return None
201
+
202
+ method = cast(str, message.get("method", ""))
203
+ msg_id = cast("str | int | None", message.get("id"))
204
+ params = cast("dict[str, Any] | None", message.get("params", {}))
205
+
206
+ if method == "initialize":
207
+ return await self._handle_initialize(msg_id, params)
208
+ elif method == "tools/list":
209
+ return await self._handle_tools_list(msg_id)
210
+ elif method == "tools/call":
211
+ return await self._handle_tools_call(msg_id, params)
212
+ elif method == "notifications/initialized":
213
+ # Notification, no response needed
214
+ return None
215
+ else:
216
+ # Unknown method
217
+ if msg_id is not None:
218
+ return self._create_error_response(msg_id, -32601, f"Method not found: {method}")
219
+ return None
220
+
221
+ async def _handle_initialize(
222
+ self, msg_id: str | int | None, params: dict[str, Any] | None
223
+ ) -> JSONRPCMessage:
224
+ """Handle initialize request."""
225
+ result = {
226
+ "protocolVersion": "2024-11-05",
227
+ "capabilities": {
228
+ "tools": {},
229
+ },
230
+ "serverInfo": {
231
+ "name": self.name,
232
+ "version": self.version,
233
+ },
234
+ }
235
+ return self._create_response(msg_id, result)
236
+
237
+ async def _handle_tools_list(self, msg_id: str | int | None) -> JSONRPCMessage:
238
+ """Handle tools/list request."""
239
+ tools_list = []
240
+ for tool_def in self.tools.values():
241
+ tools_list.append(
242
+ {
243
+ "name": tool_def.name,
244
+ "description": tool_def.description,
245
+ "inputSchema": {
246
+ "type": tool_def.input_schema.type,
247
+ "properties": tool_def.input_schema.properties,
248
+ "required": tool_def.input_schema.required,
249
+ },
250
+ }
251
+ )
252
+ return self._create_response(msg_id, {"tools": tools_list})
253
+
254
+ async def _handle_tools_call(
255
+ self, msg_id: str | int | None, params: dict[str, Any] | None
256
+ ) -> JSONRPCMessage:
257
+ """Handle tools/call request."""
258
+ if not isinstance(params, dict):
259
+ return self._create_error_response(msg_id, -32602, "Invalid params")
260
+
261
+ tool_name = params.get("name", "")
262
+ arguments = params.get("arguments", {})
263
+
264
+ tool_def = self.tools.get(tool_name)
265
+ if not tool_def:
266
+ return self._create_error_response(msg_id, -32602, f"Tool not found: {tool_name}")
267
+
268
+ try:
269
+ # Call the handler
270
+ result = tool_def.handler(arguments)
271
+ # Handle async handlers
272
+ if inspect.isawaitable(result):
273
+ result = await result
274
+
275
+ return self._create_response(msg_id, result)
276
+ except Exception as e:
277
+ # Return error as tool result
278
+ error_result: CallToolResult = {
279
+ "content": [{"type": "text", "text": str(e)}],
280
+ "isError": True,
281
+ }
282
+ return self._create_response(msg_id, error_result)
283
+
284
+ def _create_response(self, msg_id: str | int | None, result: Any) -> JSONRPCResponse:
285
+ """Create a JSON-RPC response."""
286
+ return {
287
+ "jsonrpc": "2.0",
288
+ "id": msg_id,
289
+ "result": result,
290
+ }
291
+
292
+ def _create_error_response(
293
+ self, msg_id: str | int | None, code: int, message: str
294
+ ) -> JSONRPCResponse:
295
+ """Create a JSON-RPC error response."""
296
+ return {
297
+ "jsonrpc": "2.0",
298
+ "id": msg_id,
299
+ "error": {"code": code, "message": message},
300
+ }
File without changes