diff --git a/crates/omnigraph-server/src/config.rs b/crates/omnigraph-server/src/config.rs index 6e2eae0..0b621e1 100644 --- a/crates/omnigraph-server/src/config.rs +++ b/crates/omnigraph-server/src/config.rs @@ -115,17 +115,33 @@ pub struct QueryEntry { /// MCP exposure for a stored query. A *deployment* concern (the same /// `.gq` may be exposed in one graph and hidden in another), so it lives -/// in YAML rather than in the `.gq` source. Default `expose: false` — -/// a query is HTTP-callable but absent from the MCP tool catalog unless -/// the operator opts in. The catalog projection lands in a later slice; -/// v1 round-trips these fields. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +/// in YAML rather than in the `.gq` source. **Default `expose: true`** — +/// declaring a query in the manifest *is* the opt-in, so it appears in the +/// MCP tool catalog (`GET /queries`) by default; set `expose: false` to +/// keep a query HTTP/service-callable but hidden from the agent tool list. +/// `expose` governs catalog membership only — it is **not** an +/// authorization gate (invocation is gated by `invoke_query`), so a hidden +/// query is still invocable by name with the right permission. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct McpSettings { - #[serde(default)] + #[serde(default = "mcp_expose_default")] pub expose: bool, pub tool_name: Option, } +fn mcp_expose_default() -> bool { + true +} + +impl Default for McpSettings { + fn default() -> Self { + Self { + expose: mcp_expose_default(), + tool_name: None, + } + } +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AliasCommand { @@ -601,9 +617,9 @@ queries: assert_eq!(find_user.file, "./queries/find_user.gq"); assert!(find_user.mcp.expose); assert_eq!(find_user.mcp.tool_name.as_deref(), Some("lookup_user")); - // Default exposure is false (safe by default) and tool_name absent. + // Default exposure is true (the manifest entry is the opt-in); tool_name absent. let audit = &prod["internal_audit"]; - assert!(!audit.mcp.expose); + assert!(audit.mcp.expose); assert!(audit.mcp.tool_name.is_none()); // Top-level registry (single-graph mode). diff --git a/crates/omnigraph-server/src/queries.rs b/crates/omnigraph-server/src/queries.rs index 07a4883..ae6a367 100644 --- a/crates/omnigraph-server/src/queries.rs +++ b/crates/omnigraph-server/src/queries.rs @@ -36,9 +36,10 @@ pub struct StoredQuery { pub source: Arc, /// Parsed declaration (params, mutations, description, …). pub decl: QueryDecl, - /// Whether this query is listed in the MCP tool catalog. Default - /// `false`: HTTP-callable but not MCP-enumerated until the operator - /// opts in. Consulted by the catalog projection (a later slice). + /// Whether this query is listed in the MCP tool catalog (`GET /queries`). + /// Default `true` (the manifest entry is the opt-in); `expose: false` + /// keeps it HTTP/service-callable but hidden from the agent tool list. + /// Catalog membership only — not an authorization gate. pub expose: bool, /// Optional MCP tool-name override; defaults to `name`. pub tool_name: Option, diff --git a/docs/user/cli-reference.md b/docs/user/cli-reference.md index 5091953..2f9e198 100644 --- a/docs/user/cli-reference.md +++ b/docs/user/cli-reference.md @@ -39,7 +39,7 @@ graphs: : # key MUST equal the `query ` symbol inside the .gq file: # relative to this config's directory mcp: - expose: false # default false: HTTP-callable but not listed as an MCP tool + expose: true # default true: listed in the MCP catalog (GET /queries); set false to hide (still HTTP-callable) tool_name: # optional MCP tool-name override (defaults to ; # must be unique across exposed queries) server: @@ -68,7 +68,7 @@ aliases: branch: format: queries: # top-level stored-query registry (single-graph mode); mirrors top-level `policy` - : { file: , mcp: { expose: false } } + : { file: } # mcp.expose defaults to true policy: file: ./policy.yaml ```