2020
2121
2222class ResourceManager :
23- """Manages MCPServer resources."""
23+ """Manages MCPServer resources with optional tenant-scoped storage.
24+
25+ Resources and templates are stored in dicts keyed by
26+ ``(tenant_id, uri_string)`` and ``(tenant_id, uri_template)``
27+ respectively. This allows the same URI to exist independently under
28+ different tenants. When ``tenant_id`` is ``None`` (the default),
29+ entries live in a global scope, preserving backward compatibility
30+ with single-tenant usage.
31+ """
2432
2533 def __init__ (self , warn_on_duplicate_resources : bool = True ):
26- self ._resources : dict [str , Resource ] = {}
27- self ._templates : dict [str , ResourceTemplate ] = {}
34+ self ._resources : dict [tuple [ str | None , str ] , Resource ] = {}
35+ self ._templates : dict [tuple [ str | None , str ] , ResourceTemplate ] = {}
2836 self .warn_on_duplicate_resources = warn_on_duplicate_resources
2937
30- def add_resource (self , resource : Resource ) -> Resource :
31- """Add a resource to the manager.
38+ def add_resource (self , resource : Resource , * , tenant_id : str | None = None ) -> Resource :
39+ """Add a resource to the manager, optionally scoped to a tenant .
3240
3341 Args:
3442 resource: A Resource instance to add
43+ tenant_id: Optional tenant scope for the resource
3544
3645 Returns:
3746 The added resource. If a resource with the same URI already exists,
@@ -45,12 +54,13 @@ def add_resource(self, resource: Resource) -> Resource:
4554 "resource_name" : resource .name ,
4655 },
4756 )
48- existing = self ._resources .get (str (resource .uri ))
57+ key = (tenant_id , str (resource .uri ))
58+ existing = self ._resources .get (key )
4959 if existing :
5060 if self .warn_on_duplicate_resources :
5161 logger .warning (f"Resource already exists: { resource .uri } " )
5262 return existing
53- self ._resources [str ( resource . uri ) ] = resource
63+ self ._resources [key ] = resource
5464 return resource
5565
5666 def add_template (
@@ -64,8 +74,10 @@ def add_template(
6474 icons : list [Icon ] | None = None ,
6575 annotations : Annotations | None = None ,
6676 meta : dict [str , Any ] | None = None ,
77+ * ,
78+ tenant_id : str | None = None ,
6779 ) -> ResourceTemplate :
68- """Add a template from a function."""
80+ """Add a template from a function, optionally scoped to a tenant ."""
6981 template = ResourceTemplate .from_function (
7082 fn ,
7183 uri_template = uri_template ,
@@ -77,20 +89,28 @@ def add_template(
7789 annotations = annotations ,
7890 meta = meta ,
7991 )
80- self ._templates [template .uri_template ] = template
92+ self ._templates [( tenant_id , template .uri_template ) ] = template
8193 return template
8294
83- async def get_resource (self , uri : AnyUrl | str , context : Context [LifespanContextT , RequestT ]) -> Resource :
95+ async def get_resource (
96+ self ,
97+ uri : AnyUrl | str ,
98+ context : Context [LifespanContextT , RequestT ],
99+ * ,
100+ tenant_id : str | None = None ,
101+ ) -> Resource :
84102 """Get resource by URI, checking concrete resources first, then templates."""
85103 uri_str = str (uri )
86104 logger .debug ("Getting resource" , extra = {"uri" : uri_str })
87105
88106 # First check concrete resources
89- if resource := self ._resources .get (uri_str ):
107+ if resource := self ._resources .get (( tenant_id , uri_str ) ):
90108 return resource
91109
92- # Then check templates
93- for template in self ._templates .values ():
110+ # Then check templates for this tenant scope
111+ for (tid , _ ), template in self ._templates .items ():
112+ if tid != tenant_id :
113+ continue
94114 if params := template .matches (uri_str ):
95115 try :
96116 return await template .create_resource (uri_str , params , context = context )
@@ -99,12 +119,14 @@ async def get_resource(self, uri: AnyUrl | str, context: Context[LifespanContext
99119
100120 raise ValueError (f"Unknown resource: { uri } " )
101121
102- def list_resources (self ) -> list [Resource ]:
103- """List all registered resources."""
104- logger .debug ("Listing resources" , extra = {"count" : len (self ._resources )})
105- return list (self ._resources .values ())
106-
107- def list_templates (self ) -> list [ResourceTemplate ]:
108- """List all registered templates."""
109- logger .debug ("Listing templates" , extra = {"count" : len (self ._templates )})
110- return list (self ._templates .values ())
122+ def list_resources (self , * , tenant_id : str | None = None ) -> list [Resource ]:
123+ """List all registered resources for a given tenant scope."""
124+ resources = [r for (tid , _ ), r in self ._resources .items () if tid == tenant_id ]
125+ logger .debug ("Listing resources" , extra = {"count" : len (resources )})
126+ return resources
127+
128+ def list_templates (self , * , tenant_id : str | None = None ) -> list [ResourceTemplate ]:
129+ """List all registered templates for a given tenant scope."""
130+ templates = [t for (tid , _ ), t in self ._templates .items () if tid == tenant_id ]
131+ logger .debug ("Listing templates" , extra = {"count" : len (templates )})
132+ return templates
0 commit comments