Skip to content

Commit c4ea9b7

Browse files
Peterclaude
andcommitted
feat: implement MCP resources handlers for Claude Code compatibility
Implements @server.list_resources() and @server.read_resource() handlers to provide full MCP resources protocol support. Resources expose cache status and popular crates list via standard MCP resource URIs. - Add list_resources handler returning configured resources - Add read_resource handler for cache://status and cache://popular URIs - Import correct types (ReadResourceContents, Resource) from MCP SDK - Handle both PopularCrate objects and string crate names - Fix async/sync function calls (get_popular_crates_status is sync) - Update Architecture.md with complete MCP resources documentation - Document solution in UsefulInformation.json for future reference Resolves missing MCP resources implementation blocking Claude Code client. Verified with JSON-RPC test client showing correct resource listing and content retrieval for both cache status and popular crates resources. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 8466c0f commit c4ea9b7

File tree

3 files changed

+189
-5
lines changed

3 files changed

+189
-5
lines changed

Architecture.md

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ graph LR
9595
end
9696
9797
subgraph "MCP Implementations"
98-
OFFICIAL_SVR[mcp_sdk_server.py<br/>Official MCP SDK 1.13.1 - Default<br/>Native @server.tool() decorators<br/>Standard protocol support<br/>All 10 tools migrated]
98+
OFFICIAL_SVR[mcp_sdk_server.py<br/>Official MCP SDK 1.13.1 - Default<br/>Native @server.tool() decorators<br/>Complete MCP resources support<br/>All 10 tools + resource handlers]
9999
FASTMCP_SVR[fastmcp_server.py<br/>FastMCP 2.11.1 - Deprecated<br/>Schema override support<br/>Legacy compatibility layer]
100100
end
101101
@@ -158,7 +158,7 @@ graph LR
158158
end
159159
160160
subgraph "Server Layer"
161-
MCP_SERVER[mcp_sdk_server.py<br/>Official Python MCP SDK 1.13.1 (Default)<br/>Native @server.tool() decorators<br/>STDIO transport<br/>stderr logging<br/>All 10 tools migrated]
161+
MCP_SERVER[mcp_sdk_server.py<br/>Official Python MCP SDK 1.13.1 (Default)<br/>Native @server.tool() decorators<br/>Complete MCP resources support<br/>STDIO transport<br/>stderr logging<br/>All 10 tools + resource handlers]
162162
end
163163
164164
subgraph "Utilities"
@@ -302,6 +302,7 @@ The system now supports two parallel MCP implementations to ensure compatibility
302302
**mcp_sdk_server.py (Default Implementation)**
303303
- Contains all 10 MCP tools with @server.tool() decorators
304304
- Implements complete `handle_list_tools()` with proper JSON schemas
305+
- **Complete MCP Resources Support**: Implements @server.list_resources() and @server.read_resource() handlers for cache status and popular crates access
305306
- String-only parameters with validation for universal MCP client compatibility
306307
- Native MCP protocol support without conversion layers
307308
- Integrated with MCPServerRunner for memory management
@@ -320,6 +321,45 @@ All 10 tools successfully migrated:
320321
9. `get_ingestion_stats` - Pipeline status monitoring
321322
10. `get_version_info` - Version-specific information
322323

324+
### MCP Resources Implementation
325+
326+
The MCP SDK server provides complete resource endpoint support as defined in the MCP protocol:
327+
328+
#### Resource Handler Implementation
329+
330+
**@server.list_resources() Handler**:
331+
- Returns available resources from MCP_RESOURCES_CONFIG
332+
- Exposes `cache://status` and `cache://popular` resource URIs
333+
- Uses native Resource type from mcp.types for protocol compliance
334+
335+
**@server.read_resource() Handler**:
336+
- **cache://status**: Returns comprehensive cache status via `get_popular_crates_status()`
337+
- Provides cache_stats, ingestion_stats, and scheduler_stats
338+
- Synchronous call pattern (not async) for immediate status retrieval
339+
- **cache://popular**: Returns popular crates list via `PopularCratesManager.get_popular_crates()`
340+
- Returns PopularCrate objects with name, version, downloads, and description fields
341+
- Leverages existing popular crates management infrastructure
342+
343+
#### Technical Implementation Details
344+
345+
**Key Imports Added**:
346+
```python
347+
from mcp.server.lowlevel.helper_types import ReadResourceContents
348+
from mcp.types import Resource
349+
from pydantic import AnyUrl
350+
```
351+
352+
**Resource Content Format**:
353+
- Uses `ReadResourceContents` with separate content and mime_type fields
354+
- Returns JSON content with "application/json" mime type
355+
- Proper error handling for unknown resource URIs with informative error messages
356+
357+
**Integration Benefits**:
358+
- Full MCP protocol compatibility verified with test clients
359+
- Seamless integration with existing PopularCratesManager infrastructure
360+
- No performance impact on tool operations - resources provide read-only access
361+
- Enables external MCP clients to monitor cache status and discover available crates
362+
323363
### Memory Leak Mitigation
324364

325365
The architecture includes comprehensive memory management to handle long-running server instances:
@@ -676,7 +716,7 @@ graph TB
676716
CLI[CLI Entry Point<br/>--mode & --mcp-implementation flags]
677717
678718
subgraph "MCP Implementations"
679-
OFFICIAL_SDK[Official MCP SDK 1.13.1<br/>Default - Native @server.tool()<br/>All 10 tools migrated]
719+
OFFICIAL_SDK[Official MCP SDK 1.13.1<br/>Default - Native @server.tool()<br/>Complete MCP resources support<br/>All 10 tools + resource handlers]
680720
FASTMCP[FastMCP 2.11.1<br/>Deprecated - Schema overrides<br/>Legacy support only]
681721
MCP_RUNNER[MCPServerRunner<br/>Memory leak mitigation<br/>1000 calls/1GB restart]
682722
PARAM_VAL[Parameter Validation<br/>String-first handling<br/>Type conversion utilities]
@@ -744,8 +784,11 @@ graph TB
744784
**1. Created mcp_tools_config.py (255 lines)**
745785
- Extracted all MCP tool definitions and schemas as configuration data
746786
- Contains `MCP_TOOLS_CONFIG` list with tool schemas
747-
- Contains `MCP_RESOURCES_CONFIG` list with resource definitions
787+
- Contains `MCP_RESOURCES_CONFIG` list with resource definitions:
788+
- `cache://status` - Cache status and statistics resource
789+
- `cache://popular` - Popular crates list resource
748790
- Reduced `get_mcp_manifest` from 273 lines to ~30 lines of configuration access logic
791+
- **Full MCP Resources Protocol**: Resource definitions are now fully implemented via handler endpoints in mcp_sdk_server.py
749792

750793
**2. Rebalanced Endpoint Modules**
751794
- Moved `search_items` and `search_examples` from `endpoints.py` to `endpoints_tools.py`
@@ -788,7 +831,7 @@ MCP_TOOLS_CONFIG = [
788831
def get_mcp_manifest():
789832
return {
790833
"tools": MCP_TOOLS_CONFIG,
791-
"resources": MCP_RESOURCES_CONFIG
834+
"resources": MCP_RESOURCES_CONFIG # Now fully supported by MCP SDK server
792835
}
793836
```
794837

@@ -849,6 +892,7 @@ These remaining files exceed the target due to inherent complexity rather than o
849892
- ✅ Official SDK is now the default implementation
850893
- ✅ FastMCP deprecated but maintained for backward compatibility
851894
- ✅ Complete schemas with handle_list_tools() for all tools
895+
- ✅ Full MCP resources protocol support with @server.list_resources() and @server.read_resource() handlers
852896

853897
### Compatibility Guarantees
854898

UsefulInformation.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,42 @@
11851185
],
11861186
"preventionStrategy": "Establish consistent parameter type declaration policy: strings in config, conversion in validators. Include schema consistency checks in development workflow.",
11871187
"validationPattern": "Use string-only parameter declarations with mode='before' field validators for maximum MCP client compatibility while maintaining type safety"
1188+
},
1189+
{
1190+
"error": "Missing MCP Resources Implementation - Claude Code client requires MCP resources endpoints",
1191+
"rootCause": "MCP SDK server had no @server.list_resources() or @server.read_resource() decorators. Resources were defined in MCP_RESOURCES_CONFIG but handlers were missing. Client expected standard MCP resource handlers for discovering available data.",
1192+
"solution": "Implemented proper MCP resources handlers with correct import paths and field names. Added @server.list_resources() and @server.read_resource() decorators to expose resources via MCP protocol.",
1193+
"context": "Claude Code client compatibility blocked by missing resource endpoints despite resources being configured",
1194+
"implementation": [
1195+
"Import ReadResourceContents from mcp.server.lowlevel.helper_types (not TextResourceContents from types)",
1196+
"Use proper field names: content and mime_type (not text and mimeType)",
1197+
"Handle both string and object crate types in popular crates list",
1198+
"get_popular_crates_status() is NOT async - don't await it",
1199+
"Use snake_case field names throughout (mime_type not mimeType)"
1200+
],
1201+
"lesson": "MCP resources require explicit handler implementation even when resources are defined in configuration. Import paths and field naming must match MCP SDK requirements exactly.",
1202+
"pattern": "Always implement both @server.list_resources() and @server.read_resource() handlers when defining MCP resources",
1203+
"dateEncountered": "2025-09-04",
1204+
"relatedFiles": ["src/docsrs_mcp/mcp_sdk_server.py", "src/docsrs_mcp/mcp_resources_config.py"],
1205+
"codeExample": "@server.list_resources()\nasync def handle_list_resources() -> list[Resource]:\n return [\n Resource(uri=AnyUrl(uri), name=name, description=desc, mimeType=mime)\n for uri, name, desc, mime in MCP_RESOURCES_CONFIG\n ]\n\[email protected]_resource()\nasync def handle_read_resource(uri: AnyUrl) -> list[ReadResourceContents]:\n if str(uri) == \"cache://status\":\n status = get_popular_crates_status() # NOT async\n content = json.dumps(status, indent=2)\n return [ReadResourceContents(uri=uri, content=content, mime_type=\"application/json\")]\n elif str(uri) == \"cache://popular\":\n popular_crates = await get_popular_crates_list()\n # Handle both string and object types with hasattr check\n formatted_crates = []\n for crate in popular_crates:\n if hasattr(crate, 'name'):\n formatted_crates.append({'name': crate.name, 'downloads': crate.downloads})\n else:\n formatted_crates.append({'name': str(crate), 'downloads': 'unknown'})\n content = json.dumps(formatted_crates, indent=2)\n return [ReadResourceContents(uri=uri, content=content, mime_type=\"application/json\")]\n else:\n raise ValueError(f\"Unknown resource URI: {uri}\")",
1206+
"resourceUrisImplemented": [
1207+
"cache://status - Cache and ingestion statistics",
1208+
"cache://popular - List of popular crates with metadata"
1209+
],
1210+
"testingVerification": [
1211+
"JSON-RPC protocol test with resources/list method returns configured resources",
1212+
"JSON-RPC protocol test with resources/read method returns valid JSON content",
1213+
"MIME types correctly set to application/json for structured data",
1214+
"Full Claude Code client compatibility restored"
1215+
],
1216+
"commonPitfalls": [
1217+
"get_popular_crates_status() is synchronous - don't await it",
1218+
"PopularCrate objects need hasattr() checking for attribute access",
1219+
"Use snake_case field names (mime_type not mimeType)",
1220+
"Import ReadResourceContents from correct module path"
1221+
],
1222+
"impact": "Full Claude Code client compatibility restored. Resources now discoverable via standard MCP protocol. Foundation for adding more resources in future.",
1223+
"debuggingTechnique": "Test with JSON-RPC protocol directly using resources/list and resources/read methods to verify proper MCP resource implementation"
11881224
}
11891225
]
11901226
},

src/docsrs_mcp/mcp_sdk_server.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99

1010
from mcp import types
1111
from mcp.server import Server
12+
from mcp.server.lowlevel.helper_types import ReadResourceContents
1213
from mcp.server.models import InitializationOptions
1314
from mcp.server.stdio import stdio_server
15+
from mcp.types import Resource
16+
from pydantic import AnyUrl
1417

1518
from .models import (
1619
AssociatedItemResponse,
@@ -1802,6 +1805,107 @@ async def handle_call_tool(
18021805
return [types.TextContent(type="text", text=f"Error: {str(e)}")]
18031806

18041807

1808+
@server.list_resources()
1809+
async def handle_list_resources() -> list[Resource]:
1810+
"""List available resources."""
1811+
try:
1812+
# Import config here to avoid circular imports
1813+
from .mcp_tools_config import MCP_RESOURCES_CONFIG
1814+
1815+
resources = []
1816+
for resource_config in MCP_RESOURCES_CONFIG:
1817+
resources.append(
1818+
Resource(
1819+
uri=resource_config["uri"],
1820+
name=resource_config["name"],
1821+
description=resource_config["description"],
1822+
)
1823+
)
1824+
1825+
logger.info(f"Listed {len(resources)} resources")
1826+
return resources
1827+
except Exception as e:
1828+
logger.error(f"Error listing resources: {e}")
1829+
return []
1830+
1831+
1832+
@server.read_resource()
1833+
async def handle_read_resource(uri: AnyUrl) -> list[ReadResourceContents]:
1834+
"""Read a specific resource by URI."""
1835+
try:
1836+
uri_str = str(uri)
1837+
logger.info(f"Reading resource: {uri_str}")
1838+
1839+
if uri_str == "cache://status":
1840+
# Get cache status (not async)
1841+
from .popular_crates import get_popular_crates_status
1842+
1843+
status = get_popular_crates_status()
1844+
content = json.dumps(status, indent=2)
1845+
1846+
return [
1847+
ReadResourceContents(
1848+
content=content,
1849+
mime_type="application/json",
1850+
)
1851+
]
1852+
1853+
elif uri_str == "cache://popular":
1854+
# Get popular crates list
1855+
from .popular_crates import PopularCratesManager
1856+
1857+
manager = PopularCratesManager()
1858+
crates = await manager.get_popular_crates()
1859+
1860+
# Convert to dict for JSON serialization
1861+
# Handle both PopularCrate objects and strings
1862+
crates_data = []
1863+
for crate in crates:
1864+
if isinstance(crate, str):
1865+
# Simple string crate name
1866+
crates_data.append({
1867+
"name": crate,
1868+
"version": "latest",
1869+
"downloads": 0,
1870+
"description": "",
1871+
})
1872+
else:
1873+
# PopularCrate object
1874+
crates_data.append({
1875+
"name": crate.name,
1876+
"version": crate.version if hasattr(crate, 'version') else "latest",
1877+
"downloads": crate.downloads if hasattr(crate, 'downloads') else 0,
1878+
"description": crate.description if hasattr(crate, 'description') else "",
1879+
})
1880+
1881+
content = json.dumps({"crates": crates_data, "count": len(crates_data)}, indent=2)
1882+
1883+
return [
1884+
ReadResourceContents(
1885+
content=content,
1886+
mime_type="application/json",
1887+
)
1888+
]
1889+
1890+
else:
1891+
logger.warning(f"Unknown resource URI: {uri_str}")
1892+
return [
1893+
ReadResourceContents(
1894+
content=f"Resource not found: {uri_str}",
1895+
mime_type="text/plain",
1896+
)
1897+
]
1898+
1899+
except Exception as e:
1900+
logger.error(f"Error reading resource {uri}: {e}")
1901+
return [
1902+
ReadResourceContents(
1903+
content=f"Error reading resource: {str(e)}",
1904+
mime_type="text/plain",
1905+
)
1906+
]
1907+
1908+
18051909
def setup_uvx_environment():
18061910
"""Configure environment for uvx execution compatibility."""
18071911
# Essential for real-time STDIO output

0 commit comments

Comments
 (0)