Skip to content

Commit e5c963e

Browse files
authored
Merge pull request #2 from mrexodia/resources
Add support for MCP resources
2 parents e32c56a + ec84166 commit e5c963e

File tree

4 files changed

+279
-114
lines changed

4 files changed

+279
-114
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,26 @@ def divide(
172172
return numerator / denominator
173173
```
174174

175+
## Resources
176+
177+
Expose read-only data via URI patterns. Resources are serialized as JSON.
178+
179+
```python
180+
from typing import Annotated
181+
182+
@mcp.resource("file://data.txt")
183+
def read_file() -> dict:
184+
"""Get information about data.txt"""
185+
return {"name": "data.txt", "size": 1024}
186+
187+
@mcp.resource("file://{filename}")
188+
def read_any_file(
189+
filename: Annotated[str, "Name of file to read"]
190+
) -> dict:
191+
"""Get information about any file"""
192+
return {"name": filename, "size": 2048}
193+
```
194+
175195
## Supported clients
176196

177197
The following clients have been tested:

examples/mcp_example.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ def struct_get(
7878
for name in (names if isinstance(names, list) else [names])
7979
]
8080

81+
@mcp.resource("example://system_info")
82+
def system_info_resource() -> SystemInfo:
83+
"""Resource providing system information"""
84+
return get_system_info()
85+
86+
@mcp.resource("example://greeting/{name}")
87+
def greeting_resource(
88+
name: Annotated[str, "Name to greet from resource"]
89+
) -> GreetingResponse:
90+
"""Resource providing greeting message"""
91+
return greet(name)
92+
8193
if __name__ == "__main__":
8294
parser = argparse.ArgumentParser(description="MCP Example Server")
8395
parser.add_argument("--stdio", action="store_true", help="Run MCP server over stdio")
@@ -87,8 +99,8 @@ def struct_get(
8799
else:
88100
print("Starting MCP Example Server...")
89101
print("\nAvailable tools:")
90-
for name in mcp.tools.methods.keys():
91-
func = mcp.tools.methods[name]
102+
for name in mcp._tools.methods.keys():
103+
func = mcp._tools.methods[name]
92104
print(f" - {name}: {func.__doc__}")
93105

94106
mcp.serve("127.0.0.1", 5001)
@@ -103,4 +115,3 @@ def struct_get(
103115
except KeyboardInterrupt:
104116
print("\n\nStopping server...")
105117
mcp.stop()
106-
print("Server stopped.")

src/zeromcp/jsonrpc.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import types
33
import inspect
44
import traceback
5-
from typing import Any, Callable, get_type_hints, get_origin, get_args, Union, TypedDict, TypeAlias, NotRequired
5+
from typing import Any, Callable, get_type_hints, get_origin, get_args, Union, TypedDict, TypeAlias, NotRequired, is_typeddict
66

77
JsonRpcId: TypeAlias = str | int | float | None
88
JsonRpcParams: TypeAlias = dict[str, Any] | list[Any] | None
@@ -168,6 +168,10 @@ def _call(self, method: str, params: Any) -> Any:
168168
arg_origin = get_origin(arg_type)
169169
check_type = arg_origin if arg_origin is not None else arg_type
170170

171+
# TypedDict cannot be used with isinstance - check for dict instead
172+
if is_typeddict(arg_type):
173+
check_type = dict
174+
171175
if isinstance(value, check_type):
172176
type_matched = True
173177
break
@@ -187,6 +191,16 @@ def _call(self, method: str, params: Any) -> Any:
187191
validated_params[param_name] = value
188192
continue
189193

194+
# Handle TypedDict (must check before basic types)
195+
if is_typeddict(expected_type):
196+
if not isinstance(value, dict):
197+
raise JsonRpcException(
198+
-32602,
199+
f"Invalid params: {param_name} expected dict, got {type(value).__name__}"
200+
)
201+
validated_params[param_name] = value
202+
continue
203+
190204
# Handle basic types
191205
if isinstance(expected_type, type):
192206
# Allow int -> float conversion

0 commit comments

Comments
 (0)