|
12 | 12 | logger = logging.getLogger("mcp_utils") |
13 | 13 |
|
14 | 14 |
|
| 15 | +def build_json_schema_for_msgspec_struct(struct_type: type[Any]) -> dict[str, Any]: |
| 16 | + """Return a clean JSON Schema dict for a msgspec Struct. |
| 17 | + """ |
| 18 | + if struct_type is None: |
| 19 | + return {"type": "object", "properties": {}} |
| 20 | + |
| 21 | + (schemas,), components = msgspec.json.schema_components([struct_type]) |
| 22 | + |
| 23 | + # Try direct component by the class name for stable selection |
| 24 | + name = getattr(struct_type, "__name__", None) |
| 25 | + if name and name in components: |
| 26 | + return components[name] |
| 27 | + else: |
| 28 | + raise ValueError() |
| 29 | + |
| 30 | + |
15 | 31 | class Role(str, Enum): |
16 | 32 | """Role in the MCP protocol.""" |
17 | 33 |
|
@@ -100,13 +116,13 @@ class CancelledNotification(msgspec.Struct): |
100 | 116 | class ClientCapabilities(msgspec.Struct): |
101 | 117 | """Capabilities a client may support.""" |
102 | 118 |
|
103 | | - experimental: dict[str, dict[str, Any]] | None | msgspec.UnsetType = msgspec.UNSET |
104 | | - roots: dict[str, bool] | None | msgspec.UnsetType = msgspec.UNSET |
105 | | - sampling: dict[str, Any] | None | msgspec.UnsetType = msgspec.UNSET |
106 | | - prompts: dict[str, bool] | None | msgspec.UnsetType = msgspec.UNSET |
107 | | - resources: dict[str, bool] | None | msgspec.UnsetType = msgspec.UNSET |
108 | | - tools: dict[str, bool] | None | msgspec.UnsetType = msgspec.UNSET |
109 | | - logging: dict[str, bool] | None | msgspec.UnsetType = msgspec.UNSET |
| 119 | + experimental: dict[str, dict[str, Any]] | msgspec.UnsetType = msgspec.UNSET |
| 120 | + roots: dict[str, bool] | msgspec.UnsetType = msgspec.UNSET |
| 121 | + sampling: dict[str, Any] | msgspec.UnsetType = msgspec.UNSET |
| 122 | + prompts: dict[str, bool] | msgspec.UnsetType = msgspec.UNSET |
| 123 | + resources: dict[str, bool] | msgspec.UnsetType = msgspec.UNSET |
| 124 | + tools: dict[str, bool] | msgspec.UnsetType = msgspec.UNSET |
| 125 | + logging: dict[str, bool] | msgspec.UnsetType = msgspec.UNSET |
110 | 126 |
|
111 | 127 |
|
112 | 128 | class CompleteRequestArgument(msgspec.Struct): |
@@ -203,7 +219,7 @@ class ListResourcesResult(msgspec.Struct): |
203 | 219 | """Result of listing resources.""" |
204 | 220 |
|
205 | 221 | resources: list[ResourceInfo] |
206 | | - nextCursor: str | None | msgspec.UnsetType = msgspec.UNSET |
| 222 | + nextCursor: str | msgspec.UnsetType = msgspec.UNSET |
207 | 223 |
|
208 | 224 |
|
209 | 225 | class ResourceTemplateInfo(msgspec.Struct): |
@@ -233,7 +249,7 @@ class ListResourceTemplateResult(msgspec.Struct): |
233 | 249 | """Result of listing resource templates.""" |
234 | 250 |
|
235 | 251 | resourceTemplates: list[ResourceTemplateInfo] |
236 | | - nextCursor: str | None | msgspec.UnsetType = msgspec.UNSET |
| 252 | + nextCursor: str | msgspec.UnsetType = msgspec.UNSET |
237 | 253 |
|
238 | 254 |
|
239 | 255 | class ReadResourceRequest(msgspec.Struct): |
@@ -291,7 +307,7 @@ class ListPromptsResult(msgspec.Struct): |
291 | 307 | """Result of listing prompts.""" |
292 | 308 |
|
293 | 309 | prompts: list[PromptInfo] |
294 | | - nextCursor: str | None | msgspec.UnsetType = msgspec.UNSET |
| 310 | + nextCursor: str | msgspec.UnsetType = msgspec.UNSET |
295 | 311 |
|
296 | 312 |
|
297 | 313 | class GetPromptRequest(msgspec.Struct): |
@@ -332,27 +348,25 @@ class ToolInfo(msgspec.Struct, omit_defaults=True): |
332 | 348 | name: str |
333 | 349 | inputSchema: dict[str, Any] |
334 | 350 | description: str | None = None |
335 | | - arg_model: type[msgspec.Struct] | None | msgspec.UnsetType = msgspec.UNSET |
| 351 | + arg_model: type[msgspec.Struct] | msgspec.UnsetType = msgspec.UNSET |
336 | 352 |
|
337 | 353 | @classmethod |
338 | 354 | def from_callable(cls, callable: Callable, name: str) -> "ToolInfo": |
339 | 355 | """Create a ToolInfo from a callable.""" |
340 | 356 | metadata = inspect_callable(callable) |
341 | | - # Generate a Pydantic-like schema: root as $ref with $defs components. |
342 | | - # Also key the root definition using the tool name for stability. |
343 | 357 |
|
344 | 358 | return cls( |
345 | 359 | name=name, |
346 | 360 | description=callable.__doc__ or "", |
347 | | - inputSchema=list(msgspec.json.schema_components([metadata.arg_model])[1].values())[0], # f this. |
| 361 | + inputSchema=build_json_schema_for_msgspec_struct(metadata.arg_model), |
348 | 362 | ) |
349 | 363 |
|
350 | 364 |
|
351 | 365 | class ListToolsResult(msgspec.Struct): |
352 | 366 | """Result of listing tools.""" |
353 | 367 |
|
354 | 368 | tools: list[ToolInfo] |
355 | | - nextCursor: str | None | msgspec.UnsetType = msgspec.UNSET |
| 369 | + nextCursor: str | msgspec.UnsetType = msgspec.UNSET |
356 | 370 |
|
357 | 371 | class SubscribeRequest(msgspec.Struct): |
358 | 372 | """Request to subscribe to a resource.""" |
@@ -441,7 +455,7 @@ class ListRootsResult(msgspec.Struct): |
441 | 455 | """Result of listing roots.""" |
442 | 456 |
|
443 | 457 | roots: list[RootInfo] |
444 | | - nextCursor: str | None | msgspec.UnsetType = msgspec.UNSET |
| 458 | + nextCursor: str | msgspec.UnsetType = msgspec.UNSET |
445 | 459 |
|
446 | 460 | class ErrorResponse(msgspec.Struct): |
447 | 461 | """Error response in MCP.""" |
|
0 commit comments