forked from modelcontextprotocol/python-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresource_manager.py
More file actions
132 lines (112 loc) · 4.88 KB
/
Copy pathresource_manager.py
File metadata and controls
132 lines (112 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""Resource manager functionality."""
from __future__ import annotations
from collections.abc import Callable
from typing import TYPE_CHECKING, Any
from pydantic import AnyUrl
from mcp.server.mcpserver.resources.base import Resource
from mcp.server.mcpserver.resources.templates import ResourceTemplate
from mcp.server.mcpserver.utilities.logging import get_logger
from mcp.types import Annotations, Icon
if TYPE_CHECKING:
from mcp.server.context import LifespanContextT, RequestT
from mcp.server.mcpserver.context import Context
logger = get_logger(__name__)
class ResourceManager:
"""Manages MCPServer resources with optional tenant-scoped storage.
Resources and templates are stored in dicts keyed by
``(tenant_id, uri_string)`` and ``(tenant_id, uri_template)``
respectively. This allows the same URI to exist independently under
different tenants. When ``tenant_id`` is ``None`` (the default),
entries live in a global scope, preserving backward compatibility
with single-tenant usage.
"""
def __init__(self, warn_on_duplicate_resources: bool = True):
self._resources: dict[tuple[str | None, str], Resource] = {}
self._templates: dict[tuple[str | None, str], ResourceTemplate] = {}
self.warn_on_duplicate_resources = warn_on_duplicate_resources
def add_resource(self, resource: Resource, *, tenant_id: str | None = None) -> Resource:
"""Add a resource to the manager, optionally scoped to a tenant.
Args:
resource: A Resource instance to add
tenant_id: Optional tenant scope for the resource
Returns:
The added resource. If a resource with the same URI already exists,
returns the existing resource.
"""
logger.debug(
"Adding resource",
extra={
"uri": resource.uri,
"type": type(resource).__name__,
"resource_name": resource.name,
},
)
key = (tenant_id, str(resource.uri))
existing = self._resources.get(key)
if existing:
if self.warn_on_duplicate_resources:
logger.warning(f"Resource already exists: {resource.uri}")
return existing
self._resources[key] = resource
return resource
def add_template(
self,
fn: Callable[..., Any],
uri_template: str,
name: str | None = None,
title: str | None = None,
description: str | None = None,
mime_type: str | None = None,
icons: list[Icon] | None = None,
annotations: Annotations | None = None,
meta: dict[str, Any] | None = None,
*,
tenant_id: str | None = None,
) -> ResourceTemplate:
"""Add a template from a function, optionally scoped to a tenant."""
template = ResourceTemplate.from_function(
fn,
uri_template=uri_template,
name=name,
title=title,
description=description,
mime_type=mime_type,
icons=icons,
annotations=annotations,
meta=meta,
)
self._templates[(tenant_id, template.uri_template)] = template
return template
async def get_resource(
self,
uri: AnyUrl | str,
context: Context[LifespanContextT, RequestT],
*,
tenant_id: str | None = None,
) -> Resource:
"""Get resource by URI, checking concrete resources first, then templates."""
uri_str = str(uri)
logger.debug("Getting resource", extra={"uri": uri_str})
# First check concrete resources
if resource := self._resources.get((tenant_id, uri_str)):
return resource
# Then check templates for this tenant scope
for (tid, _), template in self._templates.items():
if tid != tenant_id:
continue
if params := template.matches(uri_str):
try:
return await template.create_resource(uri_str, params, context=context)
except Exception as e: # pragma: no cover
raise ValueError(f"Error creating resource from template: {e}")
raise ValueError(f"Unknown resource: {uri}")
def list_resources(self, *, tenant_id: str | None = None) -> list[Resource]:
"""List all registered resources for a given tenant scope."""
resources = [r for (tid, _), r in self._resources.items() if tid == tenant_id]
logger.debug("Listing resources", extra={"count": len(resources)})
return resources
def list_templates(self, *, tenant_id: str | None = None) -> list[ResourceTemplate]:
"""List all registered templates for a given tenant scope."""
templates = [t for (tid, _), t in self._templates.items() if tid == tenant_id]
logger.debug("Listing templates", extra={"count": len(templates)})
return templates