diff --git a/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl
index e34980b1..aa2cba5c 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl
@@ -176,28 +176,72 @@ jsonpath "$.result.tools[0].name" == "list_directory"
jsonpath "$.result.tools[0].description" == "List the contents of a directory in the catalog"
jsonpath "$.result.tools[0].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/list/rpc"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[1].name" == "get_schema_dependencies"
jsonpath "$.result.tools[1].description" == "Look up the dependency graph of a specific schema (incoming or outgoing)"
jsonpath "$.result.tools[1].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/dependencies/rpc"
jsonpath "$.result.tools[1].annotations.title" == "Get Schema Dependencies"
+jsonpath "$.result.tools[1].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[1].annotations.destructiveHint" == false
+jsonpath "$.result.tools[1].annotations.idempotentHint" == true
+jsonpath "$.result.tools[1].annotations.openWorldHint" == false
jsonpath "$.result.tools[2].name" == "get_schema_dependents"
jsonpath "$.result.tools[2].annotations.title" == "Get Schema Dependents"
+jsonpath "$.result.tools[2].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[2].annotations.destructiveHint" == false
+jsonpath "$.result.tools[2].annotations.idempotentHint" == true
+jsonpath "$.result.tools[2].annotations.openWorldHint" == false
jsonpath "$.result.tools[3].name" == "get_schema_health"
jsonpath "$.result.tools[3].annotations.title" == "Get Schema Health"
+jsonpath "$.result.tools[3].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[3].annotations.destructiveHint" == false
+jsonpath "$.result.tools[3].annotations.idempotentHint" == true
+jsonpath "$.result.tools[3].annotations.openWorldHint" == false
jsonpath "$.result.tools[4].name" == "get_schema_locations"
jsonpath "$.result.tools[4].annotations.title" == "Get Schema Locations"
+jsonpath "$.result.tools[4].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[4].annotations.destructiveHint" == false
+jsonpath "$.result.tools[4].annotations.idempotentHint" == true
+jsonpath "$.result.tools[4].annotations.openWorldHint" == false
jsonpath "$.result.tools[5].name" == "get_schema_positions"
jsonpath "$.result.tools[5].annotations.title" == "Get Schema Positions"
+jsonpath "$.result.tools[5].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[5].annotations.destructiveHint" == false
+jsonpath "$.result.tools[5].annotations.idempotentHint" == true
+jsonpath "$.result.tools[5].annotations.openWorldHint" == false
jsonpath "$.result.tools[6].name" == "get_schema_stats"
jsonpath "$.result.tools[6].annotations.title" == "Get Schema Stats"
+jsonpath "$.result.tools[6].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[6].annotations.destructiveHint" == false
+jsonpath "$.result.tools[6].annotations.idempotentHint" == true
+jsonpath "$.result.tools[6].annotations.openWorldHint" == false
jsonpath "$.result.tools[7].name" == "get_schema_metadata"
jsonpath "$.result.tools[7].annotations.title" == "Get Schema Metadata"
+jsonpath "$.result.tools[7].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[7].annotations.destructiveHint" == false
+jsonpath "$.result.tools[7].annotations.idempotentHint" == true
+jsonpath "$.result.tools[7].annotations.openWorldHint" == false
jsonpath "$.result.tools[8].name" == "evaluate_schema"
jsonpath "$.result.tools[8].annotations.title" == "Evaluate Schema"
+jsonpath "$.result.tools[8].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[8].annotations.destructiveHint" == false
+jsonpath "$.result.tools[8].annotations.idempotentHint" == true
+jsonpath "$.result.tools[8].annotations.openWorldHint" == false
jsonpath "$.result.tools[9].name" == "trace_schema_evaluation"
jsonpath "$.result.tools[9].annotations.title" == "Trace Schema Evaluation"
+jsonpath "$.result.tools[9].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[9].annotations.destructiveHint" == false
+jsonpath "$.result.tools[9].annotations.idempotentHint" == true
+jsonpath "$.result.tools[9].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
# Added in 2025-06-18 ("outputSchema" on Tool)
# https://modelcontextprotocol.io/specification/2025-06-18/changelog
jsonpath "$..outputSchema" not exists
diff --git a/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl
index 400308aa..7a96af69 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl
@@ -167,6 +167,10 @@ jsonpath "$.result.tools[0].description" == "List the contents of a directory in
jsonpath "$.result.tools[0].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/list/rpc"
jsonpath "$.result.tools[0].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/list/response"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
# Added in 2025-11-25 (SEP-973)
# https://github.com/modelcontextprotocol/modelcontextprotocol/issues/973
jsonpath "$..icons" not exists
diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-gzip.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-gzip.all.hurl
index 47840b9f..ef9b739b 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-11-25-gzip.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-gzip.all.hurl
@@ -160,8 +160,16 @@ jsonpath "$.id" == "tools-identity"
jsonpath "$.result.tools" count == 11
jsonpath "$.result.tools[0].name" == "list_directory"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
```
@@ -192,8 +200,16 @@ jsonpath "$.id" == "tools-gzip"
jsonpath "$.result.tools" count == 11
jsonpath "$.result.tools[0].name" == "list_directory"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
```
diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-resources.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-resources.all.hurl
index 2dc8132d..8098f04e 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-11-25-resources.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-resources.all.hurl
@@ -223,7 +223,7 @@ jsonpath "$.result.resources[40].uri" == "{{base}}/self/v1/schemas/mcp/response"
jsonpath "$.result.resources[40].name" == "response"
jsonpath "$.result.resources[40].description" == "Any outgoing MCP response the server returns"
jsonpath "$.result.resources[40].mimeType" == "application/schema+json"
-jsonpath "$.result.resources[40].size" == 2965
+jsonpath "$.result.resources[40].size" == 3117
jsonpath "$.result.resources[41].uri" == "{{base}}/self/v1/schemas/mcp/tools/call/request"
jsonpath "$.result.resources[41].name" == "request"
jsonpath "$.result.resources[41].description" == "Request from a client to invoke an MCP tool by name"
@@ -243,7 +243,7 @@ jsonpath "$.result.resources[44].uri" == "{{base}}/self/v1/schemas/mcp/tools/lis
jsonpath "$.result.resources[44].name" == "response"
jsonpath "$.result.resources[44].description" == "Sourcemeta One's list of MCP tools"
jsonpath "$.result.resources[44].mimeType" == "application/schema+json"
-jsonpath "$.result.resources[44].size" == 3106
+jsonpath "$.result.resources[44].size" == 3820
jsonpath "$.result.resources[45].uri" == "{{base}}/test/object"
jsonpath "$.result.resources[45].name" == "object"
jsonpath "$.result.resources[45].description" == "An Object Example"
diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-tools.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-tools.all.hurl
index 6fceb716..3dcc5224 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-11-25-tools.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-tools.all.hurl
@@ -24,56 +24,100 @@ jsonpath "$.result.tools[0].description" == "List the contents of a directory in
jsonpath "$.result.tools[0].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/list/rpc"
jsonpath "$.result.tools[0].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/list/response"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[1].name" == "get_schema_dependencies"
jsonpath "$.result.tools[1].description" == "Look up the dependency graph of a specific schema (incoming or outgoing)"
jsonpath "$.result.tools[1].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/dependencies/rpc"
jsonpath "$.result.tools[1].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/dependencies/response"
jsonpath "$.result.tools[1].annotations.title" == "Get Schema Dependencies"
+jsonpath "$.result.tools[1].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[1].annotations.destructiveHint" == false
+jsonpath "$.result.tools[1].annotations.idempotentHint" == true
+jsonpath "$.result.tools[1].annotations.openWorldHint" == false
jsonpath "$.result.tools[2].name" == "get_schema_dependents"
jsonpath "$.result.tools[2].description" == "Look up the dependency graph of a specific schema (incoming or outgoing)"
jsonpath "$.result.tools[2].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/dependents/rpc"
jsonpath "$.result.tools[2].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/dependents/response"
jsonpath "$.result.tools[2].annotations.title" == "Get Schema Dependents"
+jsonpath "$.result.tools[2].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[2].annotations.destructiveHint" == false
+jsonpath "$.result.tools[2].annotations.idempotentHint" == true
+jsonpath "$.result.tools[2].annotations.openWorldHint" == false
jsonpath "$.result.tools[3].name" == "get_schema_health"
jsonpath "$.result.tools[3].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[3].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/health/rpc"
jsonpath "$.result.tools[3].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/health/response"
jsonpath "$.result.tools[3].annotations.title" == "Get Schema Health"
+jsonpath "$.result.tools[3].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[3].annotations.destructiveHint" == false
+jsonpath "$.result.tools[3].annotations.idempotentHint" == true
+jsonpath "$.result.tools[3].annotations.openWorldHint" == false
jsonpath "$.result.tools[4].name" == "get_schema_locations"
jsonpath "$.result.tools[4].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[4].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/locations/rpc"
jsonpath "$.result.tools[4].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/locations/response"
jsonpath "$.result.tools[4].annotations.title" == "Get Schema Locations"
+jsonpath "$.result.tools[4].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[4].annotations.destructiveHint" == false
+jsonpath "$.result.tools[4].annotations.idempotentHint" == true
+jsonpath "$.result.tools[4].annotations.openWorldHint" == false
jsonpath "$.result.tools[5].name" == "get_schema_positions"
jsonpath "$.result.tools[5].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[5].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/positions/rpc"
jsonpath "$.result.tools[5].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/positions/response"
jsonpath "$.result.tools[5].annotations.title" == "Get Schema Positions"
+jsonpath "$.result.tools[5].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[5].annotations.destructiveHint" == false
+jsonpath "$.result.tools[5].annotations.idempotentHint" == true
+jsonpath "$.result.tools[5].annotations.openWorldHint" == false
jsonpath "$.result.tools[6].name" == "get_schema_stats"
jsonpath "$.result.tools[6].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[6].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/stats/rpc"
jsonpath "$.result.tools[6].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/stats/response"
jsonpath "$.result.tools[6].annotations.title" == "Get Schema Stats"
+jsonpath "$.result.tools[6].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[6].annotations.destructiveHint" == false
+jsonpath "$.result.tools[6].annotations.idempotentHint" == true
+jsonpath "$.result.tools[6].annotations.openWorldHint" == false
jsonpath "$.result.tools[7].name" == "get_schema_metadata"
jsonpath "$.result.tools[7].description" == "Read a navigation artifact for browsing schemas"
jsonpath "$.result.tools[7].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/metadata/rpc"
jsonpath "$.result.tools[7].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/metadata/response"
jsonpath "$.result.tools[7].annotations.title" == "Get Schema Metadata"
+jsonpath "$.result.tools[7].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[7].annotations.destructiveHint" == false
+jsonpath "$.result.tools[7].annotations.idempotentHint" == true
+jsonpath "$.result.tools[7].annotations.openWorldHint" == false
jsonpath "$.result.tools[8].name" == "evaluate_schema"
jsonpath "$.result.tools[8].description" == "Validate a JSON instance against a schema and return whether it is valid plus any errors"
jsonpath "$.result.tools[8].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/evaluate/rpc"
jsonpath "$.result.tools[8].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/evaluate/response"
jsonpath "$.result.tools[8].annotations.title" == "Evaluate Schema"
+jsonpath "$.result.tools[8].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[8].annotations.destructiveHint" == false
+jsonpath "$.result.tools[8].annotations.idempotentHint" == true
+jsonpath "$.result.tools[8].annotations.openWorldHint" == false
jsonpath "$.result.tools[9].name" == "trace_schema_evaluation"
jsonpath "$.result.tools[9].description" == "Validate a JSON instance against a schema and return detailed information about every step of the evaluation process"
jsonpath "$.result.tools[9].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/trace/rpc"
jsonpath "$.result.tools[9].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/trace/response"
jsonpath "$.result.tools[9].annotations.title" == "Trace Schema Evaluation"
+jsonpath "$.result.tools[9].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[9].annotations.destructiveHint" == false
+jsonpath "$.result.tools[9].annotations.idempotentHint" == true
+jsonpath "$.result.tools[9].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].description" == "Search for schemas by query term"
jsonpath "$.result.tools[10].inputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/search/rpc"
jsonpath "$.result.tools[10].outputSchema['$ref']" == "{{base}}/self/v1/schemas/api/schemas/search/response"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
```
diff --git a/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl
index aa384f2c..958921c4 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl
@@ -176,28 +176,72 @@ jsonpath "$.result.tools[0].name" == "list_directory"
jsonpath "$.result.tools[0].description" == "List the contents of a directory in the catalog"
jsonpath "$.result.tools[0].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/list/rpc"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[1].name" == "get_schema_dependencies"
jsonpath "$.result.tools[1].description" == "Look up the dependency graph of a specific schema (incoming or outgoing)"
jsonpath "$.result.tools[1].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/dependencies/rpc"
jsonpath "$.result.tools[1].annotations.title" == "Get Schema Dependencies"
+jsonpath "$.result.tools[1].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[1].annotations.destructiveHint" == false
+jsonpath "$.result.tools[1].annotations.idempotentHint" == true
+jsonpath "$.result.tools[1].annotations.openWorldHint" == false
jsonpath "$.result.tools[2].name" == "get_schema_dependents"
jsonpath "$.result.tools[2].annotations.title" == "Get Schema Dependents"
+jsonpath "$.result.tools[2].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[2].annotations.destructiveHint" == false
+jsonpath "$.result.tools[2].annotations.idempotentHint" == true
+jsonpath "$.result.tools[2].annotations.openWorldHint" == false
jsonpath "$.result.tools[3].name" == "get_schema_health"
jsonpath "$.result.tools[3].annotations.title" == "Get Schema Health"
+jsonpath "$.result.tools[3].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[3].annotations.destructiveHint" == false
+jsonpath "$.result.tools[3].annotations.idempotentHint" == true
+jsonpath "$.result.tools[3].annotations.openWorldHint" == false
jsonpath "$.result.tools[4].name" == "get_schema_locations"
jsonpath "$.result.tools[4].annotations.title" == "Get Schema Locations"
+jsonpath "$.result.tools[4].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[4].annotations.destructiveHint" == false
+jsonpath "$.result.tools[4].annotations.idempotentHint" == true
+jsonpath "$.result.tools[4].annotations.openWorldHint" == false
jsonpath "$.result.tools[5].name" == "get_schema_positions"
jsonpath "$.result.tools[5].annotations.title" == "Get Schema Positions"
+jsonpath "$.result.tools[5].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[5].annotations.destructiveHint" == false
+jsonpath "$.result.tools[5].annotations.idempotentHint" == true
+jsonpath "$.result.tools[5].annotations.openWorldHint" == false
jsonpath "$.result.tools[6].name" == "get_schema_stats"
jsonpath "$.result.tools[6].annotations.title" == "Get Schema Stats"
+jsonpath "$.result.tools[6].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[6].annotations.destructiveHint" == false
+jsonpath "$.result.tools[6].annotations.idempotentHint" == true
+jsonpath "$.result.tools[6].annotations.openWorldHint" == false
jsonpath "$.result.tools[7].name" == "get_schema_metadata"
jsonpath "$.result.tools[7].annotations.title" == "Get Schema Metadata"
+jsonpath "$.result.tools[7].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[7].annotations.destructiveHint" == false
+jsonpath "$.result.tools[7].annotations.idempotentHint" == true
+jsonpath "$.result.tools[7].annotations.openWorldHint" == false
jsonpath "$.result.tools[8].name" == "evaluate_schema"
jsonpath "$.result.tools[8].annotations.title" == "Evaluate Schema"
+jsonpath "$.result.tools[8].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[8].annotations.destructiveHint" == false
+jsonpath "$.result.tools[8].annotations.idempotentHint" == true
+jsonpath "$.result.tools[8].annotations.openWorldHint" == false
jsonpath "$.result.tools[9].name" == "trace_schema_evaluation"
jsonpath "$.result.tools[9].annotations.title" == "Trace Schema Evaluation"
+jsonpath "$.result.tools[9].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[9].annotations.destructiveHint" == false
+jsonpath "$.result.tools[9].annotations.idempotentHint" == true
+jsonpath "$.result.tools[9].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
# Added in 2025-06-18 ("outputSchema" on Tool)
# https://modelcontextprotocol.io/specification/2025-06-18/changelog
jsonpath "$..outputSchema" not exists
diff --git a/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl
index 0d393b5d..49069053 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl
@@ -167,6 +167,10 @@ jsonpath "$.result.tools[0].description" == "List the contents of a directory in
jsonpath "$.result.tools[0].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/list/rpc"
jsonpath "$.result.tools[0].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/list/response"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
# Added in 2025-11-25 (SEP-973)
# https://github.com/modelcontextprotocol/modelcontextprotocol/issues/973
jsonpath "$..icons" not exists
diff --git a/enterprise/e2e/path/hurl/mcp-2025-11-25-gzip.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-11-25-gzip.all.hurl
index efdade08..a337a49b 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-11-25-gzip.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-11-25-gzip.all.hurl
@@ -160,8 +160,16 @@ jsonpath "$.id" == "tools-identity"
jsonpath "$.result.tools" count == 11
jsonpath "$.result.tools[0].name" == "list_directory"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
```
@@ -192,8 +200,16 @@ jsonpath "$.id" == "tools-gzip"
jsonpath "$.result.tools" count == 11
jsonpath "$.result.tools[0].name" == "list_directory"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
```
diff --git a/enterprise/e2e/path/hurl/mcp-2025-11-25-resources.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-11-25-resources.all.hurl
index aa37cfd3..40b92fc8 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-11-25-resources.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-11-25-resources.all.hurl
@@ -223,7 +223,7 @@ jsonpath "$.result.resources[40].uri" == "{{base}}/v1/catalog/self/v1/schemas/mc
jsonpath "$.result.resources[40].name" == "response"
jsonpath "$.result.resources[40].description" == "Any outgoing MCP response the server returns"
jsonpath "$.result.resources[40].mimeType" == "application/schema+json"
-jsonpath "$.result.resources[40].size" == 2976
+jsonpath "$.result.resources[40].size" == 3128
jsonpath "$.result.resources[41].uri" == "{{base}}/v1/catalog/self/v1/schemas/mcp/tools/call/request"
jsonpath "$.result.resources[41].name" == "request"
jsonpath "$.result.resources[41].description" == "Request from a client to invoke an MCP tool by name"
@@ -243,7 +243,7 @@ jsonpath "$.result.resources[44].uri" == "{{base}}/v1/catalog/self/v1/schemas/mc
jsonpath "$.result.resources[44].name" == "response"
jsonpath "$.result.resources[44].description" == "Sourcemeta One's list of MCP tools"
jsonpath "$.result.resources[44].mimeType" == "application/schema+json"
-jsonpath "$.result.resources[44].size" == 3117
+jsonpath "$.result.resources[44].size" == 3831
jsonpath "$.result.resources[45].uri" == "{{base}}/v1/catalog/example/full"
jsonpath "$.result.resources[45].name" == "full"
jsonpath "$.result.resources[45].description" == "A schema that has both a title and a description"
diff --git a/enterprise/e2e/path/hurl/mcp-2025-11-25-tools.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-11-25-tools.all.hurl
index b2efb940..be5161ed 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-11-25-tools.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-11-25-tools.all.hurl
@@ -24,56 +24,100 @@ jsonpath "$.result.tools[0].description" == "List the contents of a directory in
jsonpath "$.result.tools[0].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/list/rpc"
jsonpath "$.result.tools[0].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/list/response"
jsonpath "$.result.tools[0].annotations.title" == "List Directory"
+jsonpath "$.result.tools[0].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[0].annotations.destructiveHint" == false
+jsonpath "$.result.tools[0].annotations.idempotentHint" == true
+jsonpath "$.result.tools[0].annotations.openWorldHint" == false
jsonpath "$.result.tools[1].name" == "get_schema_dependencies"
jsonpath "$.result.tools[1].description" == "Look up the dependency graph of a specific schema (incoming or outgoing)"
jsonpath "$.result.tools[1].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/dependencies/rpc"
jsonpath "$.result.tools[1].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/dependencies/response"
jsonpath "$.result.tools[1].annotations.title" == "Get Schema Dependencies"
+jsonpath "$.result.tools[1].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[1].annotations.destructiveHint" == false
+jsonpath "$.result.tools[1].annotations.idempotentHint" == true
+jsonpath "$.result.tools[1].annotations.openWorldHint" == false
jsonpath "$.result.tools[2].name" == "get_schema_dependents"
jsonpath "$.result.tools[2].description" == "Look up the dependency graph of a specific schema (incoming or outgoing)"
jsonpath "$.result.tools[2].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/dependents/rpc"
jsonpath "$.result.tools[2].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/dependents/response"
jsonpath "$.result.tools[2].annotations.title" == "Get Schema Dependents"
+jsonpath "$.result.tools[2].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[2].annotations.destructiveHint" == false
+jsonpath "$.result.tools[2].annotations.idempotentHint" == true
+jsonpath "$.result.tools[2].annotations.openWorldHint" == false
jsonpath "$.result.tools[3].name" == "get_schema_health"
jsonpath "$.result.tools[3].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[3].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/health/rpc"
jsonpath "$.result.tools[3].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/health/response"
jsonpath "$.result.tools[3].annotations.title" == "Get Schema Health"
+jsonpath "$.result.tools[3].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[3].annotations.destructiveHint" == false
+jsonpath "$.result.tools[3].annotations.idempotentHint" == true
+jsonpath "$.result.tools[3].annotations.openWorldHint" == false
jsonpath "$.result.tools[4].name" == "get_schema_locations"
jsonpath "$.result.tools[4].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[4].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/locations/rpc"
jsonpath "$.result.tools[4].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/locations/response"
jsonpath "$.result.tools[4].annotations.title" == "Get Schema Locations"
+jsonpath "$.result.tools[4].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[4].annotations.destructiveHint" == false
+jsonpath "$.result.tools[4].annotations.idempotentHint" == true
+jsonpath "$.result.tools[4].annotations.openWorldHint" == false
jsonpath "$.result.tools[5].name" == "get_schema_positions"
jsonpath "$.result.tools[5].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[5].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/positions/rpc"
jsonpath "$.result.tools[5].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/positions/response"
jsonpath "$.result.tools[5].annotations.title" == "Get Schema Positions"
+jsonpath "$.result.tools[5].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[5].annotations.destructiveHint" == false
+jsonpath "$.result.tools[5].annotations.idempotentHint" == true
+jsonpath "$.result.tools[5].annotations.openWorldHint" == false
jsonpath "$.result.tools[6].name" == "get_schema_stats"
jsonpath "$.result.tools[6].description" == "Look up a precomputed artifact about a specific schema by its absolute URI"
jsonpath "$.result.tools[6].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/stats/rpc"
jsonpath "$.result.tools[6].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/stats/response"
jsonpath "$.result.tools[6].annotations.title" == "Get Schema Stats"
+jsonpath "$.result.tools[6].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[6].annotations.destructiveHint" == false
+jsonpath "$.result.tools[6].annotations.idempotentHint" == true
+jsonpath "$.result.tools[6].annotations.openWorldHint" == false
jsonpath "$.result.tools[7].name" == "get_schema_metadata"
jsonpath "$.result.tools[7].description" == "Read a navigation artifact for browsing schemas"
jsonpath "$.result.tools[7].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/metadata/rpc"
jsonpath "$.result.tools[7].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/metadata/response"
jsonpath "$.result.tools[7].annotations.title" == "Get Schema Metadata"
+jsonpath "$.result.tools[7].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[7].annotations.destructiveHint" == false
+jsonpath "$.result.tools[7].annotations.idempotentHint" == true
+jsonpath "$.result.tools[7].annotations.openWorldHint" == false
jsonpath "$.result.tools[8].name" == "evaluate_schema"
jsonpath "$.result.tools[8].description" == "Validate a JSON instance against a schema and return whether it is valid plus any errors"
jsonpath "$.result.tools[8].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/evaluate/rpc"
jsonpath "$.result.tools[8].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/evaluate/response"
jsonpath "$.result.tools[8].annotations.title" == "Evaluate Schema"
+jsonpath "$.result.tools[8].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[8].annotations.destructiveHint" == false
+jsonpath "$.result.tools[8].annotations.idempotentHint" == true
+jsonpath "$.result.tools[8].annotations.openWorldHint" == false
jsonpath "$.result.tools[9].name" == "trace_schema_evaluation"
jsonpath "$.result.tools[9].description" == "Validate a JSON instance against a schema and return detailed information about every step of the evaluation process"
jsonpath "$.result.tools[9].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/trace/rpc"
jsonpath "$.result.tools[9].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/trace/response"
jsonpath "$.result.tools[9].annotations.title" == "Trace Schema Evaluation"
+jsonpath "$.result.tools[9].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[9].annotations.destructiveHint" == false
+jsonpath "$.result.tools[9].annotations.idempotentHint" == true
+jsonpath "$.result.tools[9].annotations.openWorldHint" == false
jsonpath "$.result.tools[10].name" == "search_schemas"
jsonpath "$.result.tools[10].description" == "Search for schemas by query term"
jsonpath "$.result.tools[10].inputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/search/rpc"
jsonpath "$.result.tools[10].outputSchema['$ref']" == "{{base}}/v1/catalog/self/v1/schemas/api/schemas/search/response"
jsonpath "$.result.tools[10].annotations.title" == "Search Schemas"
+jsonpath "$.result.tools[10].annotations.readOnlyHint" == true
+jsonpath "$.result.tools[10].annotations.destructiveHint" == false
+jsonpath "$.result.tools[10].annotations.idempotentHint" == true
+jsonpath "$.result.tools[10].annotations.openWorldHint" == false
POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
```
diff --git a/enterprise/index/enterprise_index.cc b/enterprise/index/enterprise_index.cc
index 0756e9d9..a6e51065 100644
--- a/enterprise/index/enterprise_index.cc
+++ b/enterprise/index/enterprise_index.cc
@@ -155,18 +155,32 @@ auto generate_mcp_tools(const sourcemeta::core::URITemplateRouterView &router,
output_schema_ref.emplace(std::move(ref));
}
- auto annotations{sourcemeta::core::JSON::make_object()};
std::string title{operation_id};
sourcemeta::core::to_title_case(title);
- annotations.assign("title", sourcemeta::core::JSON{title});
tool_routes.assign(
std::string{operation_id},
sourcemeta::core::JSON{static_cast(identifier)});
- tools.push_back(sourcemeta::one::mcp_make_tool_descriptor(
- sourcemeta::one::MCPProtocolVersion::V_2025_11_25, operation_id,
- description, std::move(input_schema_ref), std::move(output_schema_ref),
- std::move(annotations)));
+
+ auto tool_entry{sourcemeta::core::JSON::make_array()};
+ tool_entry.push_back(sourcemeta::core::JSON{operation_id});
+ tool_entry.push_back(sourcemeta::core::JSON{description});
+ tool_entry.push_back(std::move(input_schema_ref));
+ if (output_schema_ref.has_value()) {
+ tool_entry.push_back(std::move(output_schema_ref).value());
+ } else {
+ tool_entry.push_back(sourcemeta::core::JSON{nullptr});
+ }
+ tool_entry.push_back(sourcemeta::core::JSON{title});
+ tool_entry.push_back(
+ sourcemeta::core::JSON{sourcemeta::one::is_read_only(context)});
+ tool_entry.push_back(
+ sourcemeta::core::JSON{sourcemeta::one::is_destructive(context)});
+ tool_entry.push_back(
+ sourcemeta::core::JSON{sourcemeta::one::is_idempotent(context)});
+ tool_entry.push_back(
+ sourcemeta::core::JSON{sourcemeta::one::is_open_world(context)});
+ tools.push_back(std::move(tool_entry));
}
}
diff --git a/enterprise/server/action_mcp_v1.cc b/enterprise/server/action_mcp_v1.cc
index c5e7eca6..54bdbc28 100644
--- a/enterprise/server/action_mcp_v1.cc
+++ b/enterprise/server/action_mcp_v1.cc
@@ -9,17 +9,39 @@
#include
#include
-#include // std::exception
-#include // std::filesystem
-#include // std::ostringstream
-#include // std::string
-#include // std::string_view
-#include // std::move
+#include // assert
+#include // std::from_chars
+#include // std::size_t
+#include // std::exception
+#include // std::filesystem
+#include // std::optional, std::nullopt
+#include // std::ostringstream
+#include // std::string
+#include // std::string_view
+#include // std::errc
+#include // std::move
namespace {
constexpr std::string_view MCP_TEMPLATE_MIME_TYPE{"application/schema+json"};
+auto parse_cursor_as_unsigned_integer(const std::string_view cursor)
+ -> std::optional {
+ if (cursor.empty()) {
+ return std::nullopt;
+ }
+ if (cursor.size() > 1 && cursor.front() == '0') {
+ return std::nullopt;
+ }
+ std::size_t parsed{0};
+ const auto [end, error]{
+ std::from_chars(cursor.data(), cursor.data() + cursor.size(), parsed)};
+ if (error != std::errc{} || end != cursor.data() + cursor.size()) {
+ return std::nullopt;
+ }
+ return parsed;
+}
+
} // namespace
namespace sourcemeta::one::enterprise {
@@ -33,8 +55,7 @@ auto ActionMCP_v1::on_resources_list(const sourcemeta::core::JSON &request_json)
if (params != nullptr && params->defines("cursor")) {
const auto &cursor_string{params->at("cursor").to_string()};
if (!cursor_string.empty()) {
- const auto parsed{
- sourcemeta::one::mcp_parse_cursor_as_unsigned_integer(cursor_string)};
+ const auto parsed{parse_cursor_as_unsigned_integer(cursor_string)};
if (!parsed.has_value()) {
return sourcemeta::core::jsonrpc_make_error_invalid_params(
id, sourcemeta::core::JSON{cursor_string});
@@ -59,8 +80,22 @@ auto ActionMCP_v1::on_initialize(const sourcemeta::core::JSON &request_json)
const auto &parts{
this->mcp_metadata_.at(sourcemeta::one::MCP_METHOD_INITIALIZE)};
return sourcemeta::one::mcp_make_initialize_result(
- request_json, parts.at("capabilities"), parts.at("serverInfo"),
- parts.at("instructions").to_string());
+ request_json,
+ sourcemeta::one::MCPServerCapabilities{
+ .prompts = parts.at(0).to_boolean(),
+ .resources = parts.at(1).to_boolean(),
+ .tools = parts.at(2).to_boolean(),
+ .logging = parts.at(3).to_boolean(),
+ .completions = parts.at(4).to_boolean(),
+ },
+ sourcemeta::one::MCPImplementation{
+ .name = parts.at(5).to_string(),
+ .version = parts.at(6).to_string(),
+ .title = parts.at(7).to_string(),
+ .description = parts.at(8).to_string(),
+ .website_url = parts.at(9).to_string(),
+ },
+ parts.at(10).to_string());
}
auto ActionMCP_v1::on_tools_list(
@@ -69,25 +104,23 @@ auto ActionMCP_v1::on_tools_list(
-> sourcemeta::core::JSON {
const auto &precomputed{
this->mcp_metadata_.at(sourcemeta::one::MCP_METHOD_TOOLS_LIST)};
- if (sourcemeta::one::mcp_supports_output_schema(version)) {
- return sourcemeta::core::jsonrpc_make_success(request_json.at("id"),
- precomputed);
- }
auto tools{sourcemeta::core::JSON::make_array()};
- for (const auto &tool : precomputed.at("tools").as_array()) {
+ for (const auto &tool : precomputed.as_array()) {
std::optional output_schema;
- if (tool.defines("outputSchema")) {
- output_schema = tool.at("outputSchema");
- }
- std::optional annotations;
- if (tool.defines("annotations")) {
- annotations = tool.at("annotations");
+ if (!tool.at(3).is_null()) {
+ output_schema = tool.at(3);
}
tools.push_back(sourcemeta::one::mcp_make_tool_descriptor(
- version, tool.at("name").to_string(),
- tool.at("description").to_string(), tool.at("inputSchema"),
- std::move(output_schema), std::move(annotations)));
+ version, tool.at(0).to_string(), tool.at(1).to_string(), tool.at(2),
+ std::move(output_schema),
+ sourcemeta::one::MCPToolAnnotations{
+ .title = tool.at(4).to_string(),
+ .read_only = tool.at(5).to_boolean(),
+ .destructive = tool.at(6).to_boolean(),
+ .idempotent = tool.at(7).to_boolean(),
+ .open_world = tool.at(8).to_boolean(),
+ }));
}
auto result{sourcemeta::core::JSON::make_object()};
result.assign_assume_new(std::string{"tools"}, std::move(tools));
@@ -219,13 +252,14 @@ auto ActionMCP_v1::on_message(const sourcemeta::one::MCPProtocolVersion version,
return;
}
- const auto &id{request_json.at("id")};
+ const auto *id{sourcemeta::core::jsonrpc_request_id(request_json)};
+ assert(id != nullptr);
const auto method{sourcemeta::core::jsonrpc_method(request_json)};
sourcemeta::core::JSON envelope{nullptr};
if (!sourcemeta::one::mcp_is_request_method(method)) {
- envelope = sourcemeta::core::jsonrpc_make_error_method_not_found(id);
+ envelope = sourcemeta::core::jsonrpc_make_error_method_not_found(*id);
} else if (!this->validate(this->request_schema_, request_json)) {
- envelope = sourcemeta::core::jsonrpc_make_error_invalid_request(&id);
+ envelope = sourcemeta::core::jsonrpc_make_error_invalid_request(id);
} else if (method == sourcemeta::one::MCP_METHOD_INITIALIZE) {
envelope = this->on_initialize(request_json);
} else if (method == sourcemeta::one::MCP_METHOD_TOOLS_LIST) {
@@ -238,9 +272,9 @@ auto ActionMCP_v1::on_message(const sourcemeta::one::MCPProtocolVersion version,
envelope = this->on_tools_call(version, request_json, body);
} else if (this->mcp_metadata_.defines(method)) {
envelope = sourcemeta::core::jsonrpc_make_success(
- id, this->mcp_metadata_.at(method));
+ *id, this->mcp_metadata_.at(method));
} else {
- envelope = sourcemeta::core::jsonrpc_make_success_empty(id);
+ envelope = sourcemeta::core::jsonrpc_make_success_empty(*id);
}
this->write_envelope(request, response, sourcemeta::one::STATUS_OK, envelope);
diff --git a/enterprise/server/include/sourcemeta/one/enterprise_server_actions.h b/enterprise/server/include/sourcemeta/one/enterprise_server_actions.h
index 89a6613f..d763b5b2 100644
--- a/enterprise/server/include/sourcemeta/one/enterprise_server_actions.h
+++ b/enterprise/server/include/sourcemeta/one/enterprise_server_actions.h
@@ -25,6 +25,10 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Handle Model Context Protocol JSON-RPC requests"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionMCP_v1(const std::filesystem::path &base,
const sourcemeta::core::URITemplateRouterView &router,
diff --git a/src/actions/action_default_v1.h b/src/actions/action_default_v1.h
index 66a6a747..2b31d938 100644
--- a/src/actions/action_default_v1.h
+++ b/src/actions/action_default_v1.h
@@ -21,6 +21,10 @@ class ActionDefault_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Default fallback action for unmatched URIs"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionDefault_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_dependency_tree_v1.h b/src/actions/action_dependency_tree_v1.h
index cb7a5e5a..6fa2ca6d 100644
--- a/src/actions/action_dependency_tree_v1.h
+++ b/src/actions/action_dependency_tree_v1.h
@@ -26,6 +26,10 @@ class ActionDependencyTree_v1 : public sourcemeta::one::RouterAction {
static constexpr std::string_view DESCRIPTION{
"Look up the dependency graph of a specific schema (incoming or "
"outgoing)"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionDependencyTree_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_health_check_v1.h b/src/actions/action_health_check_v1.h
index 05f2ac28..757fa718 100644
--- a/src/actions/action_health_check_v1.h
+++ b/src/actions/action_health_check_v1.h
@@ -17,6 +17,10 @@ class ActionHealthCheck_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Report the server's health status"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionHealthCheck_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_jsonschema_evaluate_v1.h b/src/actions/action_jsonschema_evaluate_v1.h
index 1aaf4b4c..7330b77f 100644
--- a/src/actions/action_jsonschema_evaluate_v1.h
+++ b/src/actions/action_jsonschema_evaluate_v1.h
@@ -30,6 +30,10 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::RouterAction {
static constexpr std::string_view DESCRIPTION{
"Validate a JSON instance against a schema and return whether it "
"is valid plus any errors"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionJSONSchemaEvaluate_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_jsonschema_trace_v1.h b/src/actions/action_jsonschema_trace_v1.h
index f93d07a0..6b0bbb8e 100644
--- a/src/actions/action_jsonschema_trace_v1.h
+++ b/src/actions/action_jsonschema_trace_v1.h
@@ -33,6 +33,10 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::RouterAction {
static constexpr std::string_view DESCRIPTION{
"Validate a JSON instance against a schema and return detailed "
"information about every step of the evaluation process"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionJSONSchemaTrace_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_list_directory_v1.h b/src/actions/action_list_directory_v1.h
index 07f39df0..a0fe6c22 100644
--- a/src/actions/action_list_directory_v1.h
+++ b/src/actions/action_list_directory_v1.h
@@ -24,6 +24,10 @@ class ActionListDirectory_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"List the contents of a directory in the catalog"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionListDirectory_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_mcp_v1.h b/src/actions/action_mcp_v1.h
index 108e0b7a..43a43f63 100644
--- a/src/actions/action_mcp_v1.h
+++ b/src/actions/action_mcp_v1.h
@@ -35,6 +35,10 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Handle Model Context Protocol JSON-RPC requests"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionMCP_v1(const std::filesystem::path &base,
const sourcemeta::core::URITemplateRouterView &router,
@@ -195,8 +199,22 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
const auto &parts{
this->mcp_metadata_.at(sourcemeta::one::MCP_METHOD_INITIALIZE)};
return sourcemeta::one::mcp_make_initialize_result(
- request_json, parts.at("capabilities"), parts.at("serverInfo"),
- parts.at("instructions").to_string());
+ request_json,
+ sourcemeta::one::MCPServerCapabilities{
+ .prompts = parts.at(0).to_boolean(),
+ .resources = parts.at(1).to_boolean(),
+ .tools = parts.at(2).to_boolean(),
+ .logging = parts.at(3).to_boolean(),
+ .completions = parts.at(4).to_boolean(),
+ },
+ sourcemeta::one::MCPImplementation{
+ .name = parts.at(5).to_string(),
+ .version = parts.at(6).to_string(),
+ .title = parts.at(7).to_string(),
+ .description = parts.at(8).to_string(),
+ .website_url = parts.at(9).to_string(),
+ },
+ parts.at(10).to_string());
}
if (method == sourcemeta::one::MCP_METHOD_RESOURCES_TEMPLATES_LIST) {
diff --git a/src/actions/action_not_found_v1.h b/src/actions/action_not_found_v1.h
index 56745b01..5200a2f3 100644
--- a/src/actions/action_not_found_v1.h
+++ b/src/actions/action_not_found_v1.h
@@ -17,6 +17,10 @@ class ActionNotFound_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Return a 404 Not Found response"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionNotFound_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_schema_search_v1.h b/src/actions/action_schema_search_v1.h
index 8aced568..27ebee00 100644
--- a/src/actions/action_schema_search_v1.h
+++ b/src/actions/action_schema_search_v1.h
@@ -26,6 +26,10 @@ class ActionSchemaSearch_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Search for schemas by query term"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionSchemaSearch_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_serve_explorer_artifact_v1.h b/src/actions/action_serve_explorer_artifact_v1.h
index 4bfcd319..45660549 100644
--- a/src/actions/action_serve_explorer_artifact_v1.h
+++ b/src/actions/action_serve_explorer_artifact_v1.h
@@ -23,6 +23,10 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Read a navigation artifact for browsing schemas"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionServeExplorerArtifact_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_serve_schema_artifact_v1.h b/src/actions/action_serve_schema_artifact_v1.h
index 95cba865..4ada3d4c 100644
--- a/src/actions/action_serve_schema_artifact_v1.h
+++ b/src/actions/action_serve_schema_artifact_v1.h
@@ -24,6 +24,10 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::RouterAction {
static constexpr std::string_view DESCRIPTION{
"Look up a precomputed artifact about a specific schema by its "
"absolute URI"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionServeSchemaArtifact_v1(
const std::filesystem::path &base,
diff --git a/src/actions/action_serve_static_v1.h b/src/actions/action_serve_static_v1.h
index 67b72b28..fb112e92 100644
--- a/src/actions/action_serve_static_v1.h
+++ b/src/actions/action_serve_static_v1.h
@@ -19,6 +19,10 @@ class ActionServeStatic_v1 : public sourcemeta::one::RouterAction {
public:
static constexpr std::string_view DESCRIPTION{
"Serve a static asset bundled with the server"};
+ static constexpr bool READ_ONLY{true};
+ static constexpr bool DESTRUCTIVE{false};
+ static constexpr bool IDEMPOTENT{true};
+ static constexpr bool OPEN_WORLD{false};
ActionServeStatic_v1(
const std::filesystem::path &base,
diff --git a/src/actions/actions.cc b/src/actions/actions.cc
index d373a7b2..5fd6adae 100644
--- a/src/actions/actions.cc
+++ b/src/actions/actions.cc
@@ -1,5 +1,8 @@
#include
+#include // assert
+#include // std::string_view
+
#include "action_default_v1.h"
#include "action_dependency_tree_v1.h"
#include "action_health_check_v1.h"
@@ -13,6 +16,27 @@
#include "action_serve_schema_artifact_v1.h"
#include "action_serve_static_v1.h"
+namespace {
+
+struct ActionMetadata {
+ std::string_view description;
+ bool read_only;
+ bool destructive;
+ bool idempotent;
+ bool open_world;
+};
+
+#define SOURCEMETA_ONE_DEFINE_METADATA(Name, Class) \
+ ActionMetadata{Class::DESCRIPTION, Class::READ_ONLY, Class::DESTRUCTIVE, \
+ Class::IDEMPOTENT, Class::OPEN_WORLD},
+
+const std::array METADATA{
+ {SOURCEMETA_ONE_FOR_EACH_ACTION(SOURCEMETA_ONE_DEFINE_METADATA)}};
+
+#undef SOURCEMETA_ONE_DEFINE_METADATA
+
+} // namespace
+
namespace sourcemeta::one {
#define SOURCEMETA_ONE_MAKE_CONSTRUCTOR_ENTRY(Name, Class) \
@@ -26,20 +50,41 @@ const std::array CONSTRUCTORS{[] {
#undef SOURCEMETA_ONE_MAKE_CONSTRUCTOR_ENTRY
-#define SOURCEMETA_ONE_DEFINE_DESCRIPTION(Name, Class) Class::DESCRIPTION,
-
-const std::array DESCRIPTIONS{
- {SOURCEMETA_ONE_FOR_EACH_ACTION(SOURCEMETA_ONE_DEFINE_DESCRIPTION)}};
-
-#undef SOURCEMETA_ONE_DEFINE_DESCRIPTION
-
auto action_description(
const sourcemeta::core::URITemplateRouter::Identifier context) noexcept
-> std::string_view {
- if (context >= DESCRIPTIONS.size()) {
+ if (context >= METADATA.size()) {
return {};
}
- return DESCRIPTIONS[context];
+ return METADATA[context].description;
+}
+
+auto is_read_only(
+ const sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool {
+ assert(context < METADATA.size());
+ return METADATA[context].read_only;
+}
+
+auto is_destructive(
+ const sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool {
+ assert(context < METADATA.size());
+ return METADATA[context].destructive;
+}
+
+auto is_idempotent(
+ const sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool {
+ assert(context < METADATA.size());
+ return METADATA[context].idempotent;
+}
+
+auto is_open_world(
+ const sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool {
+ assert(context < METADATA.size());
+ return METADATA[context].open_world;
}
} // namespace sourcemeta::one
diff --git a/src/actions/include/sourcemeta/one/actions.h b/src/actions/include/sourcemeta/one/actions.h
index 1058d303..f5f266c3 100644
--- a/src/actions/include/sourcemeta/one/actions.h
+++ b/src/actions/include/sourcemeta/one/actions.h
@@ -34,12 +34,27 @@ enum : std::uint8_t {
extern const std::array
CONSTRUCTORS;
-extern const std::array DESCRIPTIONS;
[[nodiscard]] auto action_description(
sourcemeta::core::URITemplateRouter::Identifier context) noexcept
-> std::string_view;
+[[nodiscard]] auto
+is_read_only(sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool;
+
+[[nodiscard]] auto
+is_destructive(sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool;
+
+[[nodiscard]] auto
+is_idempotent(sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool;
+
+[[nodiscard]] auto
+is_open_world(sourcemeta::core::URITemplateRouter::Identifier context) noexcept
+ -> bool;
+
} // namespace sourcemeta::one
#endif
diff --git a/src/index/explorer.h b/src/index/explorer.h
index eb1c0774..06c4a70d 100644
--- a/src/index/explorer.h
+++ b/src/index/explorer.h
@@ -559,18 +559,13 @@ struct GENERATE_MCP {
const sourcemeta::core::JSON &) -> void {
const auto timestamp_start{std::chrono::steady_clock::now()};
- auto server_info{sourcemeta::core::JSON::make_object()};
#if defined(SOURCEMETA_ONE_ENTERPRISE)
- server_info.assign("name",
- sourcemeta::core::JSON{"sourcemeta-one-enterprise"});
- server_info.assign("title",
- sourcemeta::core::JSON{"Sourcemeta One Enterprise"});
+ constexpr std::string_view SERVER_NAME{"sourcemeta-one-enterprise"};
+ constexpr std::string_view SERVER_TITLE{"Sourcemeta One Enterprise"};
#else
- server_info.assign("name", sourcemeta::core::JSON{"sourcemeta-one"});
- server_info.assign("title", sourcemeta::core::JSON{"Sourcemeta One"});
+ constexpr std::string_view SERVER_NAME{"sourcemeta-one"};
+ constexpr std::string_view SERVER_TITLE{"Sourcemeta One"};
#endif
- server_info.assign("version",
- sourcemeta::core::JSON{sourcemeta::one::version()});
constexpr std::string_view INSTRUCTIONS_BODY{
"Sourcemeta One is a JSON Schema registry. It serves a catalog of "
@@ -616,25 +611,25 @@ struct GENERATE_MCP {
}
#endif
- auto capabilities{sourcemeta::core::JSON::make_object()};
- capabilities.assign("resources", sourcemeta::core::JSON::make_object());
- if (!tools.empty()) {
- capabilities.assign("tools", sourcemeta::core::JSON::make_object());
- }
-
- auto initialize_ingredients{sourcemeta::core::JSON::make_object()};
- initialize_ingredients.assign("capabilities", std::move(capabilities));
- initialize_ingredients.assign("serverInfo", std::move(server_info));
- initialize_ingredients.assign("instructions",
- sourcemeta::core::JSON{instructions.str()});
+ auto initialize_ingredients{sourcemeta::core::JSON::make_array()};
+ initialize_ingredients.push_back(sourcemeta::core::JSON{false});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{true});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{!tools.empty()});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{false});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{false});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{SERVER_NAME});
+ initialize_ingredients.push_back(
+ sourcemeta::core::JSON{sourcemeta::one::version()});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{SERVER_TITLE});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{""});
+ initialize_ingredients.push_back(sourcemeta::core::JSON{""});
+ initialize_ingredients.push_back(
+ sourcemeta::core::JSON{instructions.str()});
auto resource_templates_response{sourcemeta::core::JSON::make_object()};
resource_templates_response.assign("resourceTemplates",
std::move(resource_templates));
- auto tools_response{sourcemeta::core::JSON::make_object()};
- tools_response.assign("tools", std::move(tools));
-
auto document{sourcemeta::core::JSON::make_object()};
document.assign("origin", sourcemeta::core::JSON{configuration.origin});
document.assign(std::string{sourcemeta::one::MCP_METHOD_INITIALIZE},
@@ -644,7 +639,7 @@ struct GENERATE_MCP {
std::move(resource_templates_response));
document.assign("resources", std::move(resources));
document.assign(std::string{sourcemeta::one::MCP_METHOD_TOOLS_LIST},
- std::move(tools_response));
+ std::move(tools));
document.assign("toolRoutes", std::move(tool_routes));
const auto timestamp_end{std::chrono::steady_clock::now()};
diff --git a/src/mcp/include/sourcemeta/one/mcp.h b/src/mcp/include/sourcemeta/one/mcp.h
index c6bc23a9..cf5e372e 100644
--- a/src/mcp/include/sourcemeta/one/mcp.h
+++ b/src/mcp/include/sourcemeta/one/mcp.h
@@ -44,72 +44,95 @@ constexpr std::string_view MCP_METHOD_NOTIFICATIONS_INITIALIZED{
"notifications/initialized"};
constexpr std::int64_t MCP_CODE_RESOURCE_NOT_FOUND{-32002};
+constexpr std::int64_t MCP_CODE_URL_ELICITATION_REQUIRED{-32042};
-auto mcp_make_text_block(std::string_view text) -> sourcemeta::core::JSON;
+auto mcp_make_text_block(const std::string_view text) -> sourcemeta::core::JSON;
-auto mcp_make_resource_link(MCPProtocolVersion version, std::string_view uri,
- std::string_view mime_type,
- std::string_view name = {},
- std::string_view description = {})
+auto mcp_make_resource_link(const MCPProtocolVersion version,
+ const std::string_view uri,
+ const std::string_view mime_type,
+ const std::string_view name = {},
+ const std::string_view description = {})
-> sourcemeta::core::JSON;
-auto mcp_make_tool_success(MCPProtocolVersion version,
+auto mcp_make_tool_success(const MCPProtocolVersion version,
const sourcemeta::core::JSON &id,
sourcemeta::core::JSON result)
-> sourcemeta::core::JSON;
-auto mcp_make_tool_success(MCPProtocolVersion version,
+auto mcp_make_tool_success(const MCPProtocolVersion version,
const sourcemeta::core::JSON &id,
sourcemeta::core::JSON structured,
sourcemeta::core::JSON content_blocks)
-> sourcemeta::core::JSON;
auto mcp_make_tool_error(const sourcemeta::core::JSON &id,
- std::string_view message) -> sourcemeta::core::JSON;
+ const std::string_view message)
+ -> sourcemeta::core::JSON;
auto mcp_make_error_resource_not_found(const sourcemeta::core::JSON &id,
- std::string_view uri)
+ const std::string_view uri)
-> sourcemeta::core::JSON;
-auto mcp_make_resource(std::string_view uri, std::string_view name,
- std::string_view mime_type,
- std::string_view description = {},
- std::optional size = std::nullopt)
+auto mcp_make_resource(const std::string_view uri, const std::string_view name,
+ const std::string_view mime_type,
+ const std::string_view description = {},
+ const std::optional size = std::nullopt)
-> sourcemeta::core::JSON;
-auto mcp_make_resource_text_content(std::string_view uri,
- std::string_view mime_type,
- std::string_view text)
+auto mcp_make_resource_text_content(const std::string_view uri,
+ const std::string_view mime_type,
+ const std::string_view text)
-> sourcemeta::core::JSON;
auto mcp_make_resources_read_result(sourcemeta::core::JSON contents)
-> sourcemeta::core::JSON;
-auto mcp_make_resource_template(std::string_view uri_template,
- std::string_view name,
- std::string_view description,
- std::string_view mime_type)
+auto mcp_make_resource_template(const std::string_view uri_template,
+ const std::string_view name,
+ const std::string_view description,
+ const std::string_view mime_type)
-> sourcemeta::core::JSON;
+struct MCPToolAnnotations {
+ std::string_view title = {};
+ bool read_only = false;
+ bool destructive = true;
+ bool idempotent = false;
+ bool open_world = true;
+};
+
auto mcp_make_tool_descriptor(
- MCPProtocolVersion version, std::string_view name,
- std::string_view description, sourcemeta::core::JSON input_schema,
+ const MCPProtocolVersion version, const std::string_view name,
+ const std::string_view description, sourcemeta::core::JSON input_schema,
std::optional output_schema = std::nullopt,
- std::optional annotations = std::nullopt)
- -> sourcemeta::core::JSON;
+ const MCPToolAnnotations &annotations = {}) -> sourcemeta::core::JSON;
+
+struct MCPImplementation {
+ std::string_view name;
+ std::string_view version;
+ std::string_view title = {};
+ std::string_view description = {};
+ std::string_view website_url = {};
+};
+
+struct MCPServerCapabilities {
+ bool prompts = false;
+ bool resources = false;
+ bool tools = false;
+ bool logging = false;
+ bool completions = false;
+};
auto mcp_make_initialize_result(const sourcemeta::core::JSON &request,
- sourcemeta::core::JSON capabilities,
- sourcemeta::core::JSON server_info,
- std::string_view instructions = {})
+ const MCPServerCapabilities &capabilities,
+ const MCPImplementation &server,
+ const std::string_view instructions = {})
-> sourcemeta::core::JSON;
auto mcp_tool_call_arguments(const sourcemeta::core::JSON &envelope)
-> const sourcemeta::core::JSON *;
-auto mcp_parse_cursor_as_unsigned_integer(std::string_view cursor)
- -> std::optional;
-
constexpr auto mcp_is_request_method(const std::string_view method) noexcept
-> bool {
return method == MCP_METHOD_INITIALIZE || method == MCP_METHOD_PING ||
@@ -160,6 +183,16 @@ mcp_supports_implementation_title(const MCPProtocolVersion version) noexcept
return version != MCPProtocolVersion::V_2025_03_26;
}
+constexpr auto mcp_supports_implementation_description(
+ const MCPProtocolVersion version) noexcept -> bool {
+ return version == MCPProtocolVersion::V_2025_11_25;
+}
+
+constexpr auto mcp_supports_implementation_website_url(
+ const MCPProtocolVersion version) noexcept -> bool {
+ return version == MCPProtocolVersion::V_2025_11_25;
+}
+
} // namespace sourcemeta::one
#endif
diff --git a/src/mcp/mcp.cc b/src/mcp/mcp.cc
index 9cd54fbe..e3b0db79 100644
--- a/src/mcp/mcp.cc
+++ b/src/mcp/mcp.cc
@@ -3,22 +3,93 @@
#include
#include
-#include // std::from_chars
-#include // std::size_t
-#include // std::optional
-#include // std::ostringstream
-#include // std::string
-#include // std::string_view
-#include // std::errc
-#include // std::move
+#include // assert
+#include // std::size_t
+#include // std::optional
+#include // std::ostringstream
+#include // std::string
+#include // std::string_view
+#include // std::move
+
+namespace {
+
+const auto MCP_HASH_ANNOTATIONS{
+ sourcemeta::core::JSON::make_object().as_object().hash("annotations")};
+const auto MCP_HASH_ARGUMENTS{
+ sourcemeta::core::JSON::make_object().as_object().hash("arguments")};
+const auto MCP_HASH_CAPABILITIES{
+ sourcemeta::core::JSON::make_object().as_object().hash("capabilities")};
+const auto MCP_HASH_COMPLETIONS{
+ sourcemeta::core::JSON::make_object().as_object().hash("completions")};
+const auto MCP_HASH_CONTENT{
+ sourcemeta::core::JSON::make_object().as_object().hash("content")};
+const auto MCP_HASH_CONTENTS{
+ sourcemeta::core::JSON::make_object().as_object().hash("contents")};
+const auto MCP_HASH_DESCRIPTION{
+ sourcemeta::core::JSON::make_object().as_object().hash("description")};
+const auto MCP_HASH_DESTRUCTIVE_HINT{
+ sourcemeta::core::JSON::make_object().as_object().hash("destructiveHint")};
+const auto MCP_HASH_IDEMPOTENT_HINT{
+ sourcemeta::core::JSON::make_object().as_object().hash("idempotentHint")};
+const auto MCP_HASH_INPUT_SCHEMA{
+ sourcemeta::core::JSON::make_object().as_object().hash("inputSchema")};
+const auto MCP_HASH_INSTRUCTIONS{
+ sourcemeta::core::JSON::make_object().as_object().hash("instructions")};
+const auto MCP_HASH_IS_ERROR{
+ sourcemeta::core::JSON::make_object().as_object().hash("isError")};
+const auto MCP_HASH_LOGGING{
+ sourcemeta::core::JSON::make_object().as_object().hash("logging")};
+const auto MCP_HASH_MIME_TYPE{
+ sourcemeta::core::JSON::make_object().as_object().hash("mimeType")};
+const auto MCP_HASH_NAME{
+ sourcemeta::core::JSON::make_object().as_object().hash("name")};
+const auto MCP_HASH_OPEN_WORLD_HINT{
+ sourcemeta::core::JSON::make_object().as_object().hash("openWorldHint")};
+const auto MCP_HASH_OUTPUT_SCHEMA{
+ sourcemeta::core::JSON::make_object().as_object().hash("outputSchema")};
+const auto MCP_HASH_PROMPTS{
+ sourcemeta::core::JSON::make_object().as_object().hash("prompts")};
+const auto MCP_HASH_PROTOCOL_VERSION{
+ sourcemeta::core::JSON::make_object().as_object().hash("protocolVersion")};
+const auto MCP_HASH_READ_ONLY_HINT{
+ sourcemeta::core::JSON::make_object().as_object().hash("readOnlyHint")};
+const auto MCP_HASH_RESOURCES{
+ sourcemeta::core::JSON::make_object().as_object().hash("resources")};
+const auto MCP_HASH_SERVER_INFO{
+ sourcemeta::core::JSON::make_object().as_object().hash("serverInfo")};
+const auto MCP_HASH_SIZE{
+ sourcemeta::core::JSON::make_object().as_object().hash("size")};
+const auto MCP_HASH_STRUCTURED_CONTENT{
+ sourcemeta::core::JSON::make_object().as_object().hash(
+ "structuredContent")};
+const auto MCP_HASH_TEXT{
+ sourcemeta::core::JSON::make_object().as_object().hash("text")};
+const auto MCP_HASH_TITLE{
+ sourcemeta::core::JSON::make_object().as_object().hash("title")};
+const auto MCP_HASH_TOOLS{
+ sourcemeta::core::JSON::make_object().as_object().hash("tools")};
+const auto MCP_HASH_TYPE{
+ sourcemeta::core::JSON::make_object().as_object().hash("type")};
+const auto MCP_HASH_URI{
+ sourcemeta::core::JSON::make_object().as_object().hash("uri")};
+const auto MCP_HASH_URI_TEMPLATE{
+ sourcemeta::core::JSON::make_object().as_object().hash("uriTemplate")};
+const auto MCP_HASH_VERSION{
+ sourcemeta::core::JSON::make_object().as_object().hash("version")};
+const auto MCP_HASH_WEBSITE_URL{
+ sourcemeta::core::JSON::make_object().as_object().hash("websiteUrl")};
+
+} // namespace
namespace sourcemeta::one {
auto mcp_make_text_block(const std::string_view text)
-> sourcemeta::core::JSON {
auto block{sourcemeta::core::JSON::make_object()};
- block.assign_assume_new(std::string{"type"}, sourcemeta::core::JSON{"text"});
- block.assign_assume_new(std::string{"text"}, sourcemeta::core::JSON{text});
+ block.assign_assume_new(std::string{"type"}, sourcemeta::core::JSON{"text"},
+ MCP_HASH_TYPE);
+ block.assign_assume_new(std::string{"text"}, sourcemeta::core::JSON{text},
+ MCP_HASH_TEXT);
return block;
}
@@ -48,17 +119,22 @@ auto mcp_make_resource_link(const MCPProtocolVersion version,
auto block{sourcemeta::core::JSON::make_object()};
block.assign_assume_new(std::string{"type"},
- sourcemeta::core::JSON{"resource_link"});
- block.assign_assume_new(std::string{"uri"}, sourcemeta::core::JSON{uri});
+ sourcemeta::core::JSON{"resource_link"},
+ MCP_HASH_TYPE);
+ block.assign_assume_new(std::string{"uri"}, sourcemeta::core::JSON{uri},
+ MCP_HASH_URI);
if (!name.empty()) {
- block.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name});
+ block.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name},
+ MCP_HASH_NAME);
}
if (!description.empty()) {
block.assign_assume_new(std::string{"description"},
- sourcemeta::core::JSON{description});
+ sourcemeta::core::JSON{description},
+ MCP_HASH_DESCRIPTION);
}
block.assign_assume_new(std::string{"mimeType"},
- sourcemeta::core::JSON{mime_type});
+ sourcemeta::core::JSON{mime_type},
+ MCP_HASH_MIME_TYPE);
return block;
}
@@ -73,13 +149,15 @@ auto mcp_make_tool_success(const MCPProtocolVersion version,
content.push_back(mcp_make_text_block(payload.str()));
auto envelope_result{sourcemeta::core::JSON::make_object()};
- envelope_result.assign_assume_new(std::string{"content"}, std::move(content));
+ envelope_result.assign_assume_new(std::string{"content"}, std::move(content),
+ MCP_HASH_CONTENT);
if (mcp_supports_structured_content(version)) {
envelope_result.assign_assume_new(std::string{"structuredContent"},
- std::move(result));
+ std::move(result),
+ MCP_HASH_STRUCTURED_CONTENT);
}
- envelope_result.assign_assume_new(std::string{"isError"},
- sourcemeta::core::JSON{false});
+ envelope_result.assign_assume_new(
+ std::string{"isError"}, sourcemeta::core::JSON{false}, MCP_HASH_IS_ERROR);
return sourcemeta::core::jsonrpc_make_success(id, std::move(envelope_result));
}
@@ -89,14 +167,15 @@ auto mcp_make_tool_success(const MCPProtocolVersion version,
sourcemeta::core::JSON content_blocks)
-> sourcemeta::core::JSON {
auto envelope_result{sourcemeta::core::JSON::make_object()};
- envelope_result.assign_assume_new(std::string{"content"},
- std::move(content_blocks));
+ envelope_result.assign_assume_new(
+ std::string{"content"}, std::move(content_blocks), MCP_HASH_CONTENT);
if (mcp_supports_structured_content(version)) {
envelope_result.assign_assume_new(std::string{"structuredContent"},
- std::move(structured));
+ std::move(structured),
+ MCP_HASH_STRUCTURED_CONTENT);
}
- envelope_result.assign_assume_new(std::string{"isError"},
- sourcemeta::core::JSON{false});
+ envelope_result.assign_assume_new(
+ std::string{"isError"}, sourcemeta::core::JSON{false}, MCP_HASH_IS_ERROR);
return sourcemeta::core::jsonrpc_make_success(id, std::move(envelope_result));
}
@@ -107,9 +186,10 @@ auto mcp_make_tool_error(const sourcemeta::core::JSON &id,
content.push_back(mcp_make_text_block(message));
auto envelope_result{sourcemeta::core::JSON::make_object()};
- envelope_result.assign_assume_new(std::string{"content"}, std::move(content));
- envelope_result.assign_assume_new(std::string{"isError"},
- sourcemeta::core::JSON{true});
+ envelope_result.assign_assume_new(std::string{"content"}, std::move(content),
+ MCP_HASH_CONTENT);
+ envelope_result.assign_assume_new(
+ std::string{"isError"}, sourcemeta::core::JSON{true}, MCP_HASH_IS_ERROR);
return sourcemeta::core::jsonrpc_make_success(id, std::move(envelope_result));
}
@@ -127,17 +207,22 @@ auto mcp_make_resource(const std::string_view uri, const std::string_view name,
const std::optional size)
-> sourcemeta::core::JSON {
auto resource{sourcemeta::core::JSON::make_object()};
- resource.assign_assume_new(std::string{"uri"}, sourcemeta::core::JSON{uri});
- resource.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name});
+ resource.assign_assume_new(std::string{"uri"}, sourcemeta::core::JSON{uri},
+ MCP_HASH_URI);
+ resource.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name},
+ MCP_HASH_NAME);
if (!description.empty()) {
resource.assign_assume_new(std::string{"description"},
- sourcemeta::core::JSON{description});
+ sourcemeta::core::JSON{description},
+ MCP_HASH_DESCRIPTION);
}
resource.assign_assume_new(std::string{"mimeType"},
- sourcemeta::core::JSON{mime_type});
+ sourcemeta::core::JSON{mime_type},
+ MCP_HASH_MIME_TYPE);
if (size.has_value()) {
resource.assign_assume_new(std::string{"size"},
- sourcemeta::core::JSON{size.value()});
+ sourcemeta::core::JSON{size.value()},
+ MCP_HASH_SIZE);
}
return resource;
}
@@ -149,12 +234,16 @@ auto mcp_make_resource_template(const std::string_view uri_template,
-> sourcemeta::core::JSON {
auto entry{sourcemeta::core::JSON::make_object()};
entry.assign_assume_new(std::string{"uriTemplate"},
- sourcemeta::core::JSON{uri_template});
- entry.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name});
+ sourcemeta::core::JSON{uri_template},
+ MCP_HASH_URI_TEMPLATE);
+ entry.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name},
+ MCP_HASH_NAME);
entry.assign_assume_new(std::string{"description"},
- sourcemeta::core::JSON{description});
+ sourcemeta::core::JSON{description},
+ MCP_HASH_DESCRIPTION);
entry.assign_assume_new(std::string{"mimeType"},
- sourcemeta::core::JSON{mime_type});
+ sourcemeta::core::JSON{mime_type},
+ MCP_HASH_MIME_TYPE);
return entry;
}
@@ -162,27 +251,52 @@ auto mcp_make_tool_descriptor(
const MCPProtocolVersion version, const std::string_view name,
const std::string_view description, sourcemeta::core::JSON input_schema,
std::optional output_schema,
- std::optional annotations)
- -> sourcemeta::core::JSON {
+ const MCPToolAnnotations &annotations) -> sourcemeta::core::JSON {
+ assert(!annotations.read_only || !annotations.destructive);
+ assert(!annotations.read_only || annotations.idempotent);
+
auto entry{sourcemeta::core::JSON::make_object()};
- entry.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name});
+ entry.assign_assume_new(std::string{"name"}, sourcemeta::core::JSON{name},
+ MCP_HASH_NAME);
entry.assign_assume_new(std::string{"description"},
- sourcemeta::core::JSON{description});
- entry.assign_assume_new(std::string{"inputSchema"}, std::move(input_schema));
+ sourcemeta::core::JSON{description},
+ MCP_HASH_DESCRIPTION);
+ entry.assign_assume_new(std::string{"inputSchema"}, std::move(input_schema),
+ MCP_HASH_INPUT_SCHEMA);
if (output_schema.has_value() && mcp_supports_output_schema(version)) {
entry.assign_assume_new(std::string{"outputSchema"},
- std::move(output_schema).value());
+ std::move(output_schema).value(),
+ MCP_HASH_OUTPUT_SCHEMA);
}
- if (annotations.has_value()) {
- entry.assign_assume_new(std::string{"annotations"},
- std::move(annotations).value());
+
+ auto annotations_object{sourcemeta::core::JSON::make_object()};
+ if (!annotations.title.empty()) {
+ annotations_object.assign_assume_new(
+ std::string{"title"}, sourcemeta::core::JSON{annotations.title},
+ MCP_HASH_TITLE);
}
+ annotations_object.assign_assume_new(
+ std::string{"readOnlyHint"},
+ sourcemeta::core::JSON{annotations.read_only}, MCP_HASH_READ_ONLY_HINT);
+ annotations_object.assign_assume_new(
+ std::string{"destructiveHint"},
+ sourcemeta::core::JSON{annotations.destructive},
+ MCP_HASH_DESTRUCTIVE_HINT);
+ annotations_object.assign_assume_new(
+ std::string{"idempotentHint"},
+ sourcemeta::core::JSON{annotations.idempotent}, MCP_HASH_IDEMPOTENT_HINT);
+ annotations_object.assign_assume_new(
+ std::string{"openWorldHint"},
+ sourcemeta::core::JSON{annotations.open_world}, MCP_HASH_OPEN_WORLD_HINT);
+ entry.assign_assume_new(std::string{"annotations"},
+ std::move(annotations_object), MCP_HASH_ANNOTATIONS);
+
return entry;
}
auto mcp_make_initialize_result(const sourcemeta::core::JSON &request,
- sourcemeta::core::JSON capabilities,
- sourcemeta::core::JSON server_info,
+ const MCPServerCapabilities &capabilities,
+ const MCPImplementation &server,
const std::string_view instructions)
-> sourcemeta::core::JSON {
const auto *id{sourcemeta::core::jsonrpc_request_id(request)};
@@ -192,28 +306,79 @@ auto mcp_make_initialize_result(const sourcemeta::core::JSON &request,
}
std::string_view requested_version{};
- if (params->defines("protocolVersion") &&
- params->at("protocolVersion").is_string()) {
- requested_version = params->at("protocolVersion").to_string();
+ if (params->defines("protocolVersion", MCP_HASH_PROTOCOL_VERSION) &&
+ params->at("protocolVersion", MCP_HASH_PROTOCOL_VERSION).is_string()) {
+ requested_version =
+ params->at("protocolVersion", MCP_HASH_PROTOCOL_VERSION).to_string();
}
const auto resolved{mcp_resolve_protocol_version(requested_version)};
const auto version{resolved.value_or(MCPProtocolVersion::V_2025_11_25)};
- if (!mcp_supports_implementation_title(version) && server_info.is_object() &&
- server_info.defines("title")) {
- server_info.erase("title");
+ auto capabilities_object{sourcemeta::core::JSON::make_object()};
+ if (capabilities.prompts) {
+ capabilities_object.assign_assume_new(std::string{"prompts"},
+ sourcemeta::core::JSON::make_object(),
+ MCP_HASH_PROMPTS);
+ }
+ if (capabilities.resources) {
+ capabilities_object.assign_assume_new(std::string{"resources"},
+ sourcemeta::core::JSON::make_object(),
+ MCP_HASH_RESOURCES);
+ }
+ if (capabilities.tools) {
+ capabilities_object.assign_assume_new(std::string{"tools"},
+ sourcemeta::core::JSON::make_object(),
+ MCP_HASH_TOOLS);
+ }
+ if (capabilities.logging) {
+ capabilities_object.assign_assume_new(std::string{"logging"},
+ sourcemeta::core::JSON::make_object(),
+ MCP_HASH_LOGGING);
+ }
+ if (capabilities.completions) {
+ capabilities_object.assign_assume_new(std::string{"completions"},
+ sourcemeta::core::JSON::make_object(),
+ MCP_HASH_COMPLETIONS);
+ }
+
+ auto server_info{sourcemeta::core::JSON::make_object()};
+ server_info.assign_assume_new(
+ std::string{"name"}, sourcemeta::core::JSON{server.name}, MCP_HASH_NAME);
+ server_info.assign_assume_new(std::string{"version"},
+ sourcemeta::core::JSON{server.version},
+ MCP_HASH_VERSION);
+ if (!server.title.empty() && mcp_supports_implementation_title(version)) {
+ server_info.assign_assume_new(std::string{"title"},
+ sourcemeta::core::JSON{server.title},
+ MCP_HASH_TITLE);
+ }
+ if (!server.description.empty() &&
+ mcp_supports_implementation_description(version)) {
+ server_info.assign_assume_new(std::string{"description"},
+ sourcemeta::core::JSON{server.description},
+ MCP_HASH_DESCRIPTION);
+ }
+ if (!server.website_url.empty() &&
+ mcp_supports_implementation_website_url(version)) {
+ server_info.assign_assume_new(std::string{"websiteUrl"},
+ sourcemeta::core::JSON{server.website_url},
+ MCP_HASH_WEBSITE_URL);
}
auto result{sourcemeta::core::JSON::make_object()};
result.assign_assume_new(
std::string{"protocolVersion"},
- sourcemeta::core::JSON{mcp_protocol_version_string(version)});
+ sourcemeta::core::JSON{mcp_protocol_version_string(version)},
+ MCP_HASH_PROTOCOL_VERSION);
result.assign_assume_new(std::string{"capabilities"},
- std::move(capabilities));
- result.assign_assume_new(std::string{"serverInfo"}, std::move(server_info));
+ std::move(capabilities_object),
+ MCP_HASH_CAPABILITIES);
+ result.assign_assume_new(std::string{"serverInfo"}, std::move(server_info),
+ MCP_HASH_SERVER_INFO);
if (!instructions.empty()) {
result.assign_assume_new(std::string{"instructions"},
- sourcemeta::core::JSON{instructions});
+ sourcemeta::core::JSON{instructions},
+ MCP_HASH_INSTRUCTIONS);
}
return sourcemeta::core::jsonrpc_make_success(*id, std::move(result));
}
@@ -223,45 +388,32 @@ auto mcp_make_resource_text_content(const std::string_view uri,
const std::string_view text)
-> sourcemeta::core::JSON {
auto entry{sourcemeta::core::JSON::make_object()};
- entry.assign_assume_new(std::string{"uri"}, sourcemeta::core::JSON{uri});
+ entry.assign_assume_new(std::string{"uri"}, sourcemeta::core::JSON{uri},
+ MCP_HASH_URI);
entry.assign_assume_new(std::string{"mimeType"},
- sourcemeta::core::JSON{mime_type});
- entry.assign_assume_new(std::string{"text"}, sourcemeta::core::JSON{text});
+ sourcemeta::core::JSON{mime_type},
+ MCP_HASH_MIME_TYPE);
+ entry.assign_assume_new(std::string{"text"}, sourcemeta::core::JSON{text},
+ MCP_HASH_TEXT);
return entry;
}
auto mcp_make_resources_read_result(sourcemeta::core::JSON contents)
-> sourcemeta::core::JSON {
auto result{sourcemeta::core::JSON::make_object()};
- result.assign_assume_new(std::string{"contents"}, std::move(contents));
+ result.assign_assume_new(std::string{"contents"}, std::move(contents),
+ MCP_HASH_CONTENTS);
return result;
}
-auto mcp_parse_cursor_as_unsigned_integer(const std::string_view cursor)
- -> std::optional {
- if (cursor.empty()) {
- return std::nullopt;
- }
- if (cursor.size() > 1 && cursor.front() == '0') {
- return std::nullopt;
- }
- std::size_t parsed{0};
- const auto [end, error]{
- std::from_chars(cursor.data(), cursor.data() + cursor.size(), parsed)};
- if (error != std::errc{} || end != cursor.data() + cursor.size()) {
- return std::nullopt;
- }
- return parsed;
-}
-
auto mcp_tool_call_arguments(const sourcemeta::core::JSON &envelope)
-> const sourcemeta::core::JSON * {
const auto *params{sourcemeta::core::jsonrpc_params(envelope)};
if (params == nullptr || !params->is_object() ||
- !params->defines("arguments")) {
+ !params->defines("arguments", MCP_HASH_ARGUMENTS)) {
return nullptr;
}
- return ¶ms->at("arguments");
+ return ¶ms->at("arguments", MCP_HASH_ARGUMENTS);
}
} // namespace sourcemeta::one
diff --git a/src/router/include/sourcemeta/one/router.h b/src/router/include/sourcemeta/one/router.h
index f8d469ab..4c322f22 100644
--- a/src/router/include/sourcemeta/one/router.h
+++ b/src/router/include/sourcemeta/one/router.h
@@ -105,8 +105,7 @@ class Router {
public:
Router(const std::filesystem::path &base,
const core::URITemplateRouterView &router,
- std::span constructors,
- std::span descriptions);
+ std::span constructors);
~Router() = default;
// To avoid mistakes
@@ -132,10 +131,6 @@ class Router {
const char *const code, std::string &&identifier,
std::string &&message) const -> void;
- [[nodiscard]] auto
- description(core::URITemplateRouter::Identifier context) const noexcept
- -> std::string_view;
-
private:
struct Slot {
std::unique_ptr instance;
@@ -147,7 +142,6 @@ class Router {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
const core::URITemplateRouterView &router_;
std::span constructors_;
- std::span descriptions_;
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
std::unique_ptr slots_;
std::size_t slots_size_;
diff --git a/src/router/router.cc b/src/router/router.cc
index 77537432..9a2f2dc2 100644
--- a/src/router/router.cc
+++ b/src/router/router.cc
@@ -9,10 +9,8 @@ namespace sourcemeta::one {
Router::Router(const std::filesystem::path &base,
const sourcemeta::core::URITemplateRouterView &router,
- const std::span constructors,
- const std::span descriptions)
+ const std::span constructors)
: base_{base}, router_{router}, constructors_{constructors},
- descriptions_{descriptions},
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
slots_{std::make_unique(router.size() + 1)},
slots_size_{router.size() + 1} {
@@ -23,14 +21,6 @@ Router::Router(const std::filesystem::path &base,
});
}
-auto Router::description(const sourcemeta::core::URITemplateRouter::Identifier
- context) const noexcept -> std::string_view {
- if (context >= this->descriptions_.size()) {
- return {};
- }
- return this->descriptions_[context];
-}
-
auto Router::error(const sourcemeta::one::HTTPRequest &request,
sourcemeta::one::HTTPResponse &response,
const char *const code, std::string &&identifier,
diff --git a/src/self/v1/schemas/mcp/response.json b/src/self/v1/schemas/mcp/response.json
index 3d57c9bc..e204dcc5 100644
--- a/src/self/v1/schemas/mcp/response.json
+++ b/src/self/v1/schemas/mcp/response.json
@@ -80,7 +80,11 @@
"$ref": "https://example.com/v1/self/v1/schemas/api/list/response"
},
"annotations": {
- "title": "Tool title"
+ "title": "Tool title",
+ "readOnlyHint": true,
+ "destructiveHint": false,
+ "idempotentHint": true,
+ "openWorldHint": false
}
}
]
diff --git a/src/self/v1/schemas/mcp/tools/list/response.json b/src/self/v1/schemas/mcp/tools/list/response.json
index 210b1ed2..4ac7c618 100644
--- a/src/self/v1/schemas/mcp/tools/list/response.json
+++ b/src/self/v1/schemas/mcp/tools/list/response.json
@@ -18,7 +18,11 @@
"$ref": "https://example.com/v1/self/v1/schemas/api/list/response"
},
"annotations": {
- "title": "Tool title"
+ "title": "Tool title",
+ "readOnlyHint": true,
+ "destructiveHint": false,
+ "idempotentHint": true,
+ "openWorldHint": false
}
}
]
@@ -81,11 +85,29 @@
},
"annotations": {
"type": "object",
- "required": [ "title" ],
+ "required": [
+ "title",
+ "readOnlyHint",
+ "destructiveHint",
+ "idempotentHint",
+ "openWorldHint"
+ ],
"properties": {
"title": {
"type": "string",
"minLength": 1
+ },
+ "readOnlyHint": {
+ "type": "boolean"
+ },
+ "destructiveHint": {
+ "type": "boolean"
+ },
+ "idempotentHint": {
+ "type": "boolean"
+ },
+ "openWorldHint": {
+ "type": "boolean"
}
},
"additionalProperties": false
diff --git a/src/server/server.cc b/src/server/server.cc
index d7b1f683..81859dc6 100644
--- a/src/server/server.cc
+++ b/src/server/server.cc
@@ -94,8 +94,8 @@ auto main(int argc, char *argv[]) noexcept -> int {
}
const sourcemeta::core::URITemplateRouterView router{base / "routes.bin"};
- sourcemeta::one::Router actions{base, router, sourcemeta::one::CONSTRUCTORS,
- sourcemeta::one::DESCRIPTIONS};
+ sourcemeta::one::Router actions{base, router,
+ sourcemeta::one::CONSTRUCTORS};
sourcemeta::one::HTTPServer(
port,