Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions enterprise/e2e/auth-closed/environment
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ONE_E2E_FULL_KEY=full-secret-key
ONE_E2E_MCP_KEY=mcp-secret-key
ONE_E2E_OIDC_CLIENT_SECRET=registry-client-secret
ONE_E2E_OIDC_SESSION_SECRET=a-session-signing-secret-for-the-auth-closed-sandbox
339 changes: 339 additions & 0 deletions enterprise/e2e/auth-closed/hurl/mcp-scoped.all.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
# A policy may be narrower than the whole instance, and the `mcp` policy here
# governs the MCP endpoint alone while `registry` governs everything. A key
# from the narrow policy therefore opens the transport without opening the
# schema surface, and every JSON-RPC message it sends still has to be checked
# for well-formedness against a schema that lives on that closed surface.
# Checking it as if the caller had asked for it refused the instance its own
# bookkeeping and aborted the process, so a bare ping on this key was enough to
# take the server down. The instance owns those schemas rather than serving
# them here, and this file holds that apart: the narrow key is answered, the
# closed surface stays closed to it, and malformed messages are still rejected.

# The narrow key cannot read the schema surface, which is what makes the rest
# of this file meaningful rather than incidental
GET {{base}}/self/v1/schemas/mcp/request
Authorization: Bearer mcp-secret-key
HTTP 401
Cache-Control: no-store
Content-Type: application/problem+json
WWW-Authenticate: Bearer realm="registry"
Link: </self/v1/schemas/api/error>; rel="describedby"
Access-Control-Allow-Origin: *
[Captures]
denied_body: body
error_schema: header "Link" regex "<([^>]+)>"
{
"type": "urn:sourcemeta:one:authentication-required",
"title": "Unauthorized",
"status": 401,
"detail": "This resource requires authentication"
}

# The round-trip runs on the wide key, since the evaluate endpoint is itself
# behind the policy that closes the instance
POST {{base}}/self/v1/api/schemas/evaluate{{error_schema}}
Authorization: Bearer full-secret-key
```
{{denied_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# A bare ping is the whole regression: it names no schema and reads no
# content, and it once aborted the process
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 1, "method": "ping" }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
Access-Control-Expose-Headers: Link, ETag
[Captures]
ping_body: body
mcp_schema: header "Link" regex "<([^>]+)>"
{
"jsonrpc": "2.0",
"id": 1,
"result": {}
}

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{ping_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# The handshake answers on the narrow key too
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 2, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": { "name": "scoped", "version": "1" } } }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
[Captures]
initialize_body: body
[Asserts]
jsonpath "$.error" not exists
jsonpath "$.jsonrpc" == "2.0"
jsonpath "$.id" == 2
jsonpath "$.result.protocolVersion" == "2025-11-25"
jsonpath "$.result.capabilities.resources" exists
jsonpath "$.result.capabilities.tools" exists
jsonpath "$.result.serverInfo.name" == "sourcemeta-one-enterprise"
jsonpath "$.result.serverInfo.title" == "Sourcemeta One Enterprise"
jsonpath "$.result.serverInfo.websiteUrl" == "{{base}}"
jsonpath "$.result.instructions" contains "Fully Private Sandbox"

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{initialize_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# The tool list is the instance's own description of itself rather than
# catalog content, so the narrow key sees all of it
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 3, "method": "tools/list" }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
[Captures]
tools_body: body
[Asserts]
jsonpath "$.error" not exists
jsonpath "$.jsonrpc" == "2.0"
jsonpath "$.id" == 3
jsonpath "$.result.tools" count == 12
jsonpath "$.result.tools[*].name" includes "list_directory"
jsonpath "$.result.tools[*].name" includes "search_schemas"
jsonpath "$.result.tools[*].name" includes "evaluate_schema"
jsonpath "$.result.tools[*].name" includes "trace_schema_evaluation"
jsonpath "$.result.tools[*].name" includes "instance_to_rdf"
jsonpath "$.result.tools[*].name" includes "get_schema_metadata"
jsonpath "$.result.tools[*].name" includes "get_schema_dependencies"
jsonpath "$.result.tools[*].name" includes "get_schema_dependents"
jsonpath "$.result.tools[*].name" includes "get_schema_health"
jsonpath "$.result.tools[*].name" includes "get_schema_locations"
jsonpath "$.result.tools[*].name" includes "get_schema_positions"
jsonpath "$.result.tools[*].name" includes "get_schema_stats"

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{tools_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# Answering the transport is not the same as opening the catalog. Every schema
# on this instance sits behind the wide policy, so the narrow key enumerates
# nothing and the empty list is the whole body
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 4, "method": "resources/list" }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
[Captures]
resources_body: body
[Asserts]
jsonpath "$.result.nextCursor" not exists
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"resources": []
}
}

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{resources_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# Naming a gated schema outright is refused rather than served
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 5, "method": "resources/read", "params": { "uri": "{{base}}/catalog/classified" } }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
[Captures]
read_body: body
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32010,
"message": "Authentication required"
}
}

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{read_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# A tool that walks the catalog is refused the same way, as a tool result
# rather than a protocol error
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": { "name": "list_directory", "arguments": { "path": "/" } } }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
[Captures]
call_body: body
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"content": [
{
"type": "text",
"text": "Authentication required"
}
],
"isError": true
}
}

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{call_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# Resolving those schemas apart from the caller must not stop them being
# applied, so a message the protocol forbids is still refused on the narrow key
POST {{base}}/self/v1/mcp
Authorization: Bearer mcp-secret-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": null, "method": "ping" }
```
HTTP 200
Cache-Control: no-store
Content-Type: application/json
Link: </self/v1/schemas/mcp/response>; rel="describedby"
[Captures]
malformed_body: body
{
"jsonrpc": "2.0",
"id": null,
"error": {
"code": -32600,
"message": "Invalid Request"
}
}

POST {{base}}/self/v1/api/schemas/evaluate{{mcp_schema}}
Authorization: Bearer full-secret-key
```
{{malformed_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# A credential belonging to no policy is still refused at the transport
POST {{base}}/self/v1/mcp
Authorization: Bearer not-any-key
MCP-Protocol-Version: 2025-11-25
Content-Type: application/json
```
{ "jsonrpc": "2.0", "id": 7, "method": "ping" }
```
HTTP 401
Cache-Control: no-store
Content-Type: application/problem+json
WWW-Authenticate: Bearer realm="registry"
Link: </self/v1/schemas/api/error>; rel="describedby"
[Captures]
wrong_body: body
{
"type": "urn:sourcemeta:one:authentication-required",
"title": "Unauthorized",
"status": 401,
"detail": "This resource requires authentication"
}

POST {{base}}/self/v1/api/schemas/evaluate{{error_schema}}
Authorization: Bearer full-secret-key
```
{{wrong_body}}
```
HTTP 200
[Asserts]
jsonpath "$.valid" == true

# The narrow policy reaches its own endpoint and no further, so the key that
# opens the transport does not open a sibling surface
GET {{base}}/self/v1/static/style.min.css
Authorization: Bearer mcp-secret-key
HTTP 401
Cache-Control: no-store
WWW-Authenticate: Bearer realm="registry"
[Asserts]
jsonpath "$.type" == "urn:sourcemeta:one:authentication-required"
jsonpath "$.status" == 401

GET {{base}}/catalog/example.json
Authorization: Bearer mcp-secret-key
HTTP 401
Cache-Control: no-store
WWW-Authenticate: Bearer realm="registry"
[Asserts]
jsonpath "$.type" == "urn:sourcemeta:one:authentication-required"
jsonpath "$.status" == 401
7 changes: 7 additions & 0 deletions enterprise/e2e/auth-closed/one.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
"paths": [ "/" ],
"keys": [ { "environmentVariable": "ONE_E2E_FULL_KEY" } ]
},
{
"type": "apiKey",
"algorithm": "identity",
"name": "mcp",
"paths": [ "/self/v1/mcp" ],
"keys": [ { "environmentVariable": "ONE_E2E_MCP_KEY" } ]
},
{
"type": "jwt",
"name": "machine",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ class ActionJSONSchemaRDF_v1 : public sourcemeta::one::RouterAction {
}

const sourcemeta::one::RequestCookies fields{cookies};
if (!this->schema_evaluate_fast({.bearer = bearer, .cookies = fields},
this->request_schema_, envelope)) {
if (!this->structural_evaluate_fast(this->request_schema_,
envelope)) {
sourcemeta::one::json_error(
callback_request, callback_response,
sourcemeta::core::HTTP_STATUS_BAD_REQUEST,
Expand Down Expand Up @@ -341,8 +341,8 @@ class ActionJSONSchemaRDF_v1 : public sourcemeta::one::RouterAction {
const sourcemeta::one::Credentials &credentials)
-> sourcemeta::core::JSON override {
auto [request_valid, request_output]{
this->schema_evaluate(credentials, this->rpc_request_schema_, arguments,
sourcemeta::blaze::Mode::Exhaustive)};
this->structural_evaluate(this->rpc_request_schema_, arguments,
sourcemeta::blaze::Mode::Exhaustive)};
if (!request_valid) {
return sourcemeta::core::jsonrpc_make_error(
&request_id, -32602, "Params fail against the tool request schema",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -676,8 +676,7 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
// §4 only calls null-id "discouraged" (technically valid). Sourcemeta One
// follows MCP's tighter rule and rejects null-id requests here.
// https://www.jsonrpc.org/specification (§4)
if (!this->schema_evaluate_fast(credentials, this->request_schema_,
request_json)) {
if (!this->structural_evaluate_fast(this->request_schema_, request_json)) {
return sourcemeta::core::jsonrpc_make_error_invalid_request(id);
}
if (method == sourcemeta::core::MCP_METHOD_INITIALIZE) {
Expand Down
Loading
Loading