Skip to content

Commit e88fbc7

Browse files
committed
fix: rename Resource.template field to uri_template
- Rename 'template' column to 'uri_template' in Resource model for clarity - Add Alembic migration (191a2def08d7) to rename the column - Update schemas (ResourceCreate, ResourceUpdate, ResourceRead) to use uri_template - Add uri_template field to ResourceRead for API responses - Fetch and store resource templates when registering MCP gateways - Update URI validation pattern to allow curly braces {} for templates - Fix read_resource to accept either resource_id or resource_uri - Update ResourceTemplate model to use proper JSON alias (uriTemplate) - Update all related tests to use new field name Closes #1455 Signed-off-by: Mihai Criveti <[email protected]>
1 parent ba35cdb commit e88fbc7

22 files changed

+911
-352
lines changed

charts/mcp-stack/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ mcpContextForge:
349349
VALIDATION_DANGEROUS_JS_PATTERN: '(?i)(?:^|\s|[\"''`<>=])(javascript:|vbscript:|data:\s*[^,]*[;\s]*(javascript|vbscript)|\bon[a-z]+\s*=|<\s*script\b)' # pattern to detect JavaScript injection
350350
VALIDATION_NAME_PATTERN: '^[a-zA-Z0-9_.\-\s]+$' # pattern for validating names (allows spaces)
351351
VALIDATION_IDENTIFIER_PATTERN: '^[a-zA-Z0-9_\-\.]+$' # pattern for validating IDs (no spaces)
352-
VALIDATION_SAFE_URI_PATTERN: '^[a-zA-Z0-9_\-.:/?=&%]+$' # pattern for safe URI characters
352+
VALIDATION_SAFE_URI_PATTERN: '^[a-zA-Z0-9_\-.:/?=&%{}]+$' # pattern for safe URI characters
353353
VALIDATION_UNSAFE_URI_PATTERN: '[<>"''\\]' # pattern to detect unsafe URI characters
354354
VALIDATION_TOOL_NAME_PATTERN: '^[a-zA-Z][a-zA-Z0-9._-]*$' # MCP tool naming pattern
355355
VALIDATION_TOOL_METHOD_PATTERN: '^[a-zA-Z][a-zA-Z0-9_\./-]*$' # MCP tool method naming pattern

docs/config.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@
16091609
"type": "string"
16101610
},
16111611
"validation_safe_uri_pattern": {
1612-
"default": "^[a-zA-Z0-9_\\-.:/?=&%]+$",
1612+
"default": "^[a-zA-Z0-9_\\-.:/?=&%{}]+$",
16131613
"title": "Validation Safe Uri Pattern",
16141614
"type": "string"
16151615
},

docs/docs/config.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@
16091609
"type": "string"
16101610
},
16111611
"validation_safe_uri_pattern": {
1612-
"default": "^[a-zA-Z0-9_\\-.:/?=&%]+$",
1612+
"default": "^[a-zA-Z0-9_\\-.:/?=&%{}]+$",
16131613
"title": "Validation Safe Uri Pattern",
16141614
"type": "string"
16151615
},

mcpgateway/admin.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7566,7 +7566,7 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
75667566
... ("name", "Test Resource"),
75677567
... ("description", "A test resource"),
75687568
... ("mimeType", "text/plain"),
7569-
... ("template", ""),
7569+
... ("uri_template", ""),
75707570
... ("content", "Sample content"),
75717571
... ])
75727572
>>> mock_request = MagicMock(spec=Request)
@@ -7599,15 +7599,22 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
75997599

76007600
try:
76017601
# Handle template field: convert empty string to None for optional field
7602-
template_value = form.get("template")
7602+
template = None
7603+
template_value = form.get("uri_template")
76037604
template = template_value if template_value else None
7605+
template_value = form.get("uri_template")
7606+
uri_value = form.get("uri")
7607+
7608+
# Ensure uri_value is a string
7609+
if isinstance(uri_value, str) and "{" in uri_value and "}" in uri_value:
7610+
template = uri_value
76047611

76057612
resource = ResourceCreate(
76067613
uri=str(form["uri"]),
76077614
name=str(form["name"]),
76087615
description=str(form.get("description", "")),
76097616
mime_type=str(form.get("mimeType", "")),
7610-
template=template,
7617+
uri_template=template,
76117618
content=str(form["content"]),
76127619
tags=tags,
76137620
visibility=visibility,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""resource_rename_template_to_uri_template
2+
3+
Revision ID: 191a2def08d7
4+
Revises: f3a3a3d901b8
5+
Create Date: 2025-11-17 21:20:05.223248
6+
"""
7+
8+
# Standard
9+
from typing import Sequence, Union
10+
11+
# Third-Party
12+
from alembic import op
13+
import sqlalchemy as sa
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "191a2def08d7"
17+
down_revision: Union[str, Sequence[str], None] = "f3a3a3d901b8"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
"""Upgrade schema."""
24+
conn = op.get_bind()
25+
inspector = sa.inspect(conn)
26+
27+
columns = [c["name"] for c in inspector.get_columns("resources")]
28+
29+
# Only rename if old column exists
30+
if "template" in columns and "uri_template" not in columns:
31+
with op.batch_alter_table("resources") as batch_op:
32+
batch_op.alter_column("template", new_column_name="uri_template")
33+
34+
35+
def downgrade() -> None:
36+
"""Downgrade schema."""
37+
conn = op.get_bind()
38+
inspector = sa.inspect(conn)
39+
40+
columns = [c["name"] for c in inspector.get_columns("resources")]
41+
42+
# Only rename back if current column exists
43+
if "uri_template" in columns and "template" not in columns:
44+
with op.batch_alter_table("resources") as batch_op:
45+
batch_op.alter_column("uri_template", new_column_name="template")

mcpgateway/common/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Settings(BaseSettings):
3131
# Character validation patterns
3232
validation_name_pattern: str = r"^[a-zA-Z0-9_.\-\s]+$" # Allow spaces for names
3333
validation_identifier_pattern: str = r"^[a-zA-Z0-9_\-\.]+$" # No spaces for IDs
34-
validation_safe_uri_pattern: str = r"^[a-zA-Z0-9_\-.:/?=&%]+$"
34+
validation_safe_uri_pattern: str = r"^[a-zA-Z0-9_\-.:/?=&%{}]+$"
3535
validation_unsafe_uri_pattern: str = r'[<>"\'\\]'
3636
validation_tool_name_pattern: str = r"^[a-zA-Z][a-zA-Z0-9._-]*$" # MCP tool naming
3737
validation_tool_method_pattern: str = r"^[a-zA-Z][a-zA-Z0-9_\./-]*$"

mcpgateway/common/models.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,11 +715,14 @@ class ResourceTemplate(BaseModelWithConfigDict):
715715
Serialized as '_meta' in JSON.
716716
"""
717717

718-
uri_template: str
718+
# ✅ DB field name: uri_template
719+
# ✅ API (JSON) alias:
720+
id: Optional[int] = None
721+
uri_template: str = Field(..., alias="uriTemplate")
719722
name: str
720723
description: Optional[str] = None
721724
mime_type: Optional[str] = None
722-
annotations: Optional[Annotations] = None
725+
annotations: Optional[Dict[str, Any]] = None
723726
meta: Optional[Dict[str, Any]] = Field(None, alias="_meta")
724727

725728

mcpgateway/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ def validate_database(self) -> None:
13031303
# Character validation patterns
13041304
validation_name_pattern: str = r"^[a-zA-Z0-9_.\-\s]+$" # Allow spaces for names
13051305
validation_identifier_pattern: str = r"^[a-zA-Z0-9_\-\.]+$" # No spaces for IDs
1306-
validation_safe_uri_pattern: str = r"^[a-zA-Z0-9_\-.:/?=&%]+$"
1306+
validation_safe_uri_pattern: str = r"^[a-zA-Z0-9_\-.:/?=&%{}]+$"
13071307
validation_unsafe_uri_pattern: str = r'[<>"\'\\]'
13081308
validation_tool_name_pattern: str = r"^[a-zA-Z][a-zA-Z0-9._-]*$" # MCP tool naming
13091309
validation_tool_method_pattern: str = r"^[a-zA-Z][a-zA-Z0-9_\./-]*$"

mcpgateway/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2161,7 +2161,7 @@ class Resource(Base):
21612161
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
21622162
mime_type: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
21632163
size: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
2164-
template: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # URI template for parameterized resources
2164+
uri_template: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # URI template for parameterized resources
21652165
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
21662166
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
21672167
is_active: Mapped[bool] = mapped_column(default=True)

mcpgateway/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2865,7 +2865,7 @@ async def read_resource(resource_id: str, request: Request, db: Session = Depend
28652865

28662866
try:
28672867
# Call service with context for plugin support
2868-
content = await resource_service.read_resource(db, resource_id, request_id=request_id, user=user, server_id=server_id)
2868+
content = await resource_service.read_resource(db, resource_id=resource_id, request_id=request_id, user=user, server_id=server_id)
28692869
except (ResourceNotFoundError, ResourceError) as exc:
28702870
# Translate to FastAPI HTTP error
28712871
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc

0 commit comments

Comments
 (0)