mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-24 02:38:06 +02:00
feat(mcp): MCP server surface — Streamable-HTTP transport + tool/resource projection (RFC-003)
Add the `omnigraph-mcp` crate (stateless Streamable-HTTP transport, `McpBackend`
seam, fail-closed Host/Origin policy) and the server backend projecting built-in
operations and the per-graph stored-query registry as MCP tools + resources over
`POST /graphs/{id}/mcp`. Every tool delegates to the same engine/handler
functions the REST routes use and is gated by the same Cedar `authorize` path;
reads/writes carry structured output.
Includes three correctness fixes from review + live testing:
- tools/list is a faithful relaxation of the per-call gate: a built-in whose
authorization depends on a caller-chosen branch is shown iff the actor could
invoke it on some branch, via PolicyEngine::permits_on_any_branch (capability
probe through the same Cedar authorizer). A fabricated-`main` probe wrongly
hid graph_mutate under the canonical "protect main, write unprotected" policy.
- The stored-query surface honors mode + `expose` on call as well as on list:
resolve_stored_tool is the single membership test, so the meta pair
(stored_query_list/stored_query_run) is callable only in `meta` mode and
stored_query_run resolves exposed-only. An `expose:false` query is unreachable
by name on the agent surface (it stays HTTP/service-callable).
- The loopback Host allow-list is the full set [127.0.0.1, ::1, localhost]
(matches rmcp's default), so an IPv6 loopback `Host: [::1]` is accepted
regardless of which stack the server bound.
The protocol-version contract is documented (initialize negotiates the version
in its body, so the MCP-Protocol-Version header is validated on non-init
requests only) and pinned by a test.
Tests: omnigraph-mcp/tests/standalone.rs, omnigraph-server/tests/mcp.rs,
omnigraph-policy permits_on_any_branch unit test, omnigraph-api-types schema
projection. Full workspace gate green.
This commit is contained in:
parent
c43b81d318
commit
bcd0d9c867
20 changed files with 2968 additions and 43 deletions
|
|
@ -27,7 +27,7 @@ pub use query::lint::{
|
|||
lint_query_file,
|
||||
};
|
||||
pub use query_input::{
|
||||
JsonParamMode, RunInputError, RunInputResult, ToParam, find_named_query,
|
||||
JsonParamMode, RunInputError, RunInputResult, ToParam, coerce_param_typed, find_named_query,
|
||||
json_params_to_param_map,
|
||||
};
|
||||
pub use result::{MutationExecResult, MutationResult, QueryResult, RunResult};
|
||||
|
|
|
|||
|
|
@ -322,6 +322,23 @@ pub fn json_params_to_param_map(
|
|||
Ok(map)
|
||||
}
|
||||
|
||||
/// Coerce one JSON value to a typed [`Literal`] by the engine's input
|
||||
/// contract — the single authority for what a param accepts. Exposed so the
|
||||
/// shared `param_json_schema` projection (in `omnigraph-api-types`) can be
|
||||
/// locked to this coercer by an equivalence test: the JSON Schema a client
|
||||
/// validates against must accept at least what this accepts, or a strict
|
||||
/// client would reject inputs the engine would have taken. Does **not** apply
|
||||
/// the nullable rule — explicit `null` is handled by [`json_params_to_param_map`],
|
||||
/// not here.
|
||||
pub fn coerce_param_typed(
|
||||
key: &str,
|
||||
value: &Value,
|
||||
type_name: &str,
|
||||
mode: JsonParamMode,
|
||||
) -> RunInputResult<Literal> {
|
||||
json_value_to_literal_typed(key, value, type_name, mode)
|
||||
}
|
||||
|
||||
fn json_value_to_literal_typed(
|
||||
key: &str,
|
||||
value: &Value,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue