Repository navigation
Conversation
Review of
|
| Check | Result |
|---|---|
cargo test -p omnigraph-server --locked --test integration mcp:: |
6 passed, 0 failed, 0 ignored (all 6 MCP tests, including the 2 new ones) |
cargo test -p omnigraph-server --locked --test integration openapi::openapi_spec_is_up_to_date |
1 passed (the committed openapi.json matches the generated spec) |
cargo fmt --all --check |
clean |
cargo clippy -p omnigraph-server --all-targets --locked -- -D warnings -W clippy::dbg_macro |
clean |
bash scripts/check-agents-md.sh |
OK |
Not run in this environment (CI will cover): the full workspace test and clippy graphs, --features aws, GQT, DST.
Specific test coverage observations
The two new tests cover the critical properties well:
mcp_tool_list_follows_the_adhoc_setting_and_describes_every_tool— verifies the tool list per setting (default and adhoc), tool metadata properties (title, description length,readOnlyHint,openWorldHint,additionalProperties, output schema shape), instructions text (mentionsschema, mentionsreadonly when enabled), and thatreadis refused as an unknown tool when adhoc is off.mcp_schema_and_adhoc_read_reuse_their_routes_and_policy— provesschemaandreadreturn the exact same JSON as their HTTP routes, that policy denials carry the same HTTP status, that insert/update/delete/branch-create are refused with 400, that the manifest version is unchanged after every refused mutation, thatdeny_unknown_fieldscatches stray arguments, and that the 64 KiB request bound applies.
Items for the author (non-blocking)
See inline comments below for specifics. None of these block the merge.
|
|
||
| impl Gate { | ||
| fn open(self, settings: &McpSettings) -> bool { | ||
| match self { | ||
| Gate::Always => true, | ||
| Gate::Adhoc => settings.allow_adhoc, | ||
| } |
There was a problem hiding this comment.
Optional / non-blocking. The JSON intermediate here (serde_json::from_value(json!({...}))) is consistent with how query constructs InvokeStoredQueryRequest, so this is not a new pattern. Worth noting for Phase 2: if QueryRequest gains a new required field, this will fail at runtime rather than at compile time. The test coverage catches it today, so this is an observation, not a request to change it now.
If this ever becomes a concern, a direct struct construction (crate::api::QueryRequest { query: args.query, name: args.name, ... settings: None }) would give a compile-time guarantee.
| } | ||
|
|
||
| impl McpSettings { |
There was a problem hiding this comment.
Observation (non-blocking). is_resource_metadata_path returns true for any suffix under the well-known prefix. Its two callers (identify_response and describe_contract) use it as "skip the contract header / annotation", which is safe because the actual routing decision (wildcard route + exact-path comparison) is narrower. A future caller that uses this as an authorization or routing predicate would need to be aware of the wider match. The current use sites are correct.
| "properties": { | ||
| "graph": graph_argument(), | ||
| "name": text_schema("Stored read name, from `queries`."), | ||
| "params": {"type": "object", "description": "Values keyed by the read's declared parameter names."}, | ||
| "branch": text_schema("Branch to read. Defaults to `main`."), | ||
| }, |
There was a problem hiding this comment.
Non-blocking / Phase 2 note. The anyOf [output(), error_schema()] shape and the comment explaining why (MCP clients validate structuredContent against outputSchema even for isError results) is well-placed. When Phase 2 adds more fixed-shape tools, this pattern already handles them.
| const RESOURCE_METADATA_PATH: &str = "/.well-known/oauth-protected-resource"; | ||
|
|
||
| /// Environment equivalent of `--mcp-allow-adhoc`. | ||
| pub const MCP_ALLOW_ADHOC_ENV: &str = "OMNIGRAPH_MCP_ALLOW_ADHOC"; | ||
|
|
||
| /// Process boot settings for `/mcp`. They select which opt-in tools are listed |
There was a problem hiding this comment.
Optional / non-blocking. The env_flag helper is clean. For future extensibility (Phase 2 will add --mcp-allow-writes), you could consider extracting this into a shared utility if the pattern repeats. The current single-use is fine.
| without a proxy rewrite, and a refused bearer token now gets | ||
| `error="invalid_token"` in that challenge. | ||
|
|
||
| [mcp-schema-read-guide]: ../docs/user/operations/mcp.md |
There was a problem hiding this comment.
Non-blocking. The relative link ../docs/user/operations/mcp.md here — confirm this resolves correctly when the changelog is rendered. If the rendering context is the repository root, the ../ prefix would step out of the repo. Consider an absolute path from the repo root like docs/user/operations/mcp.md or a full URL. (This may already be the correct convention for your changelog rendering; just flagging it.)
| )); | ||
| // Computed once from the fixed binding. A wildcard route compared with the | ||
| // raw request path keeps the configured path out of the router's syntax. | ||
| let advertised = state | ||
| .oidc_resource_url() | ||
| .and_then(|resource| advertised_metadata_url(&resource)) | ||
| .map(|url| AdvertisedMetadataPath(Arc::from(url.path()))); | ||
| let mut discovery = Router::new().route(RESOURCE_METADATA_PATH, get(resource_metadata)); | ||
| if let Some(advertised) = advertised { |
There was a problem hiding this comment.
Observation (non-blocking). Good defense-in-depth: the AdvertisedMetadataPath extension is only installed when the OIDC resource URL is configured and parses successfully. If the URL has no meaningful path, advertised_metadata_url would produce /.well-known/oauth-protected-resource (the root), which equals the existing route. The if let Some(advertised) gate ensures the wildcard handler is mounted only when there is a genuine path suffix to match. Clean boot-time computation.
| When a bearer credential was presented and refused (expired, wrong audience, | ||
| unknown subject), the challenge also carries `error="invalid_token"`, which tells | ||
| a client to refresh or sign in again. | ||
|
|
||
| The server answers the metadata both at `/.well-known/oauth-protected-resource` | ||
| and at the advertised path: the well-known prefix followed by the resource |
There was a problem hiding this comment.
Minor / non-blocking. The settings table is clear. For Phase 2, this will grow with --mcp-allow-writes and --mcp-allowed-host. The single-table layout scales well.
Phase 1 of the RFC 0003 amendment in #992 (issue #991). Implementation is ready for review; it should merge only after #992 is accepted.
What changes
GET /.well-known/oauth-protected-resource/{resource_path}answers the same document as the root route when the request path equals the path the/mcpchallenge advertises. The advertised path is computed once from the boot binding and compared with the raw request path; any other suffix is 404. A wildcard route plus that comparison keeps the configured path out of axum's route syntax. OpenAPI documents the new route; it carries no contract header, like the root route.error="invalid_token". A 401 for a request that presented aBearercredential carriesBearer error="invalid_token", resource_metadata="…"; without one (or with another scheme) the challenge is unchanged.schematool →server_schema_get, Cedarread.readtool →server_query, Cedarread; mutations and branch writes keep the route's 400. Listed and admitted only with--mcp-allow-adhoc/OMNIGRAPH_MCP_ALLOW_ADHOC=1(newMcpSettings, wired throughserve_with_mcp_settingsandManagedServerConfig::with_mcp_settings, soServerConfig's public struct literal is unchanged).openWorldHint: false, settings-awareinitializeinstructions (graphs → schema → stored read orread), andoutputSchemaforgraphs,schemaandqueries. Each output schema isanyOfthe success shape and theisErrorenvelope, because MCP clients validate anystructuredContentagainst it, error results included (checked in@modelcontextprotocol/sdk1.29Client.callTool).queriesalready returned typed parameter descriptors; a test now pins them.server.md(at the 350-line docs cap) into a newdocs/user/operations/mcp.md; the identity RFC's "initial tools" paragraph now points at RFC 0003; the self-contained testing RFC dropsunsupported_capability: mcp. RFC 0003 itself is amended in rfc: one MCP surface built into the server #992.run_read's observer, ingress lease, graph capture and slot ownership through cancellation and the deadline is unchanged; both new tools run through it. rmcp stays pinned at=3.3.0.Tests
Run locally on this branch (macOS,
RUST_MIN_STACK=16777216as in CI):cargo test -p omnigraph-server --locked: lib 199 passed (2 ignored);boot_settings39 passed (1 ignored);integration258 passed;multi_graph8 passed. The 10 tests inintegration mcp::include the newmcp_tool_list_follows_the_adhoc_setting_and_describes_every_tool(thetools/listsnapshot per setting, plus the instructions) andmcp_schema_and_adhoc_read_reuse_their_routes_and_policy(schemaandreadequalGET /schemaandPOST /query, includingbranch list; refusals for denied actors;insert/update/delete/branch createrefused with the manifest version unchanged; 64 KiB → 413).mcp_authenticates_every_request_and_enforces_resource_and_http_boundsnow also covers metadata at the advertised path, six other suffixes returning 404,invalid_tokenfor refused bearers, and no error code for aBasiccredential.OMNIGRAPH_UPDATE_OPENAPI=1 cargo test -p omnigraph-server --test integration openapi::openapi_spec_is_up_to_date, then the same test without the variable passes;openapi.jsongains the one route.cargo fmt --all --check: clean.cargo clippy -p omnigraph-server --all-targets --locked -- -D warnings -W clippy::dbg_macro: clean.python3 scripts/check-docs.py,bash scripts/check-agents-md.sh,typos: clean.Not run locally: the workspace-wide test and clippy graphs (only
omnigraph-serverchanged),--features aws(auth.rsis untouched), and the GQT and DST suites. CI runs all of them.