use crate::config::{RerankerMode, ResolvedModels, TierConfig};
use crate::db;
use crate::mcp::param_names;
use crate::mcp::registry::McpTool;
use crate::reranker::BatchedReranker;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[allow(dead_code)] pub struct CapabilitiesRequest {
#[serde(default)]
pub accept: Option<String>,
#[serde(default)]
pub family: Option<String>,
#[serde(default)]
pub include_schema: Option<bool>,
#[serde(default)]
pub verbose: Option<bool>,
}
#[allow(dead_code)]
pub struct CapabilitiesTool;
impl McpTool for CapabilitiesTool {
fn name() -> &'static str {
crate::mcp::registry::tool_names::MEMORY_CAPABILITIES
}
fn description() -> &'static str {
"Discover runtime capabilities; family=<name> drills in."
}
fn docs() -> &'static str {
"Caps-v3: tier, profile, summary, callable_now, agent_permitted_families, harness detection. \
family+include_schema drills one family. verbose=true restores full schema. \
NOTE per #864: `family` here = MCP tool-family (8 groups: \
core/lifecycle/graph/governance/power/meta/archive/other), NOT memory_kind taxonomy."
}
fn input_schema() -> Value {
crate::mcp::registry::input_schema_for::<CapabilitiesRequest>()
}
fn family() -> &'static str {
crate::profile::Family::Meta.name()
}
}
#[cfg(test)]
mod d1_1_982_tests {
use super::*;
#[test]
fn capabilities_tool_metadata_982() {
assert_eq!(CapabilitiesTool::name(), "memory_capabilities");
assert_eq!(CapabilitiesTool::family(), "meta");
assert!(CapabilitiesTool::description().contains("capabilities"));
assert!(CapabilitiesTool::docs().contains("family"));
}
#[test]
fn capabilities_input_schema_has_expected_fields_982() {
let schema = CapabilitiesTool::input_schema();
let direct = schema.get("properties").and_then(Value::as_object);
let nested = schema
.pointer("/definitions/CapabilitiesRequest/properties")
.and_then(Value::as_object);
let props = direct
.or(nested)
.expect("schemars must emit properties under direct or definitions path");
for field in &["accept", "family", "include_schema", "verbose"] {
assert!(
props.contains_key(*field),
"schemars-derived schema must include `{field}` (got keys: {:?})",
props.keys().collect::<Vec<_>>()
);
}
}
#[test]
fn capabilities_request_deserializes_empty_982() {
let parsed: CapabilitiesRequest = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(parsed.accept.is_none());
assert!(parsed.family.is_none());
assert!(parsed.include_schema.is_none());
assert!(parsed.verbose.is_none());
}
#[test]
fn capabilities_request_deserializes_full_982() {
let parsed: CapabilitiesRequest = serde_json::from_value(serde_json::json!({
"accept": "v3",
"family": "core",
"include_schema": true,
"verbose": false
}))
.unwrap();
assert_eq!(parsed.accept.as_deref(), Some("v3"));
assert_eq!(parsed.family.as_deref(), Some("core"));
assert_eq!(parsed.include_schema, Some(true));
assert_eq!(parsed.verbose, Some(false));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilitiesAccept {
V1,
V2,
V3,
}
impl CapabilitiesAccept {
#[must_use]
pub fn parse(s: &str) -> Self {
match s.trim().to_ascii_lowercase().as_str() {
"v1" | "1" => Self::V1,
"v2" | "2" => Self::V2,
_ => Self::V3,
}
}
}
pub fn handle_capabilities_with_conn(
tier_config: &TierConfig,
resolved_models: &ResolvedModels,
reranker: Option<&BatchedReranker>,
embedder_loaded: bool,
conn: Option<&rusqlite::Connection>,
accept: CapabilitiesAccept,
) -> Result<Value, String> {
let caps = build_capabilities_overlay(
tier_config,
resolved_models,
reranker,
embedder_loaded,
conn,
);
match accept {
CapabilitiesAccept::V2 => serde_json::to_value(caps).map_err(|e| e.to_string()),
CapabilitiesAccept::V1 => serde_json::to_value(caps.to_v1()).map_err(|e| e.to_string()),
CapabilitiesAccept::V3 => Err(
"capabilities v3 requires profile context — call handle_capabilities_with_conn_v3"
.to_string(),
),
}
}
pub fn handle_capabilities_with_conn_v3(
tier_config: &TierConfig,
resolved_models: &ResolvedModels,
reranker: Option<&BatchedReranker>,
embedder_loaded: bool,
conn: Option<&rusqlite::Connection>,
profile: &crate::profile::Profile,
mcp_config: Option<&crate::config::McpConfig>,
agent_id: Option<&str>,
harness: Option<&crate::harness::Harness>,
) -> Result<Value, String> {
let caps = build_capabilities_overlay(
tier_config,
resolved_models,
reranker,
embedder_loaded,
conn,
);
let summary = build_capabilities_summary(profile);
let describe = build_capabilities_describe_to_user(profile);
let tools = build_capabilities_tools(profile, mcp_config, agent_id);
let permitted = build_agent_permitted_families(mcp_config, agent_id);
let deferred = harness.map(crate::harness::Harness::supports_deferred_registration);
let mut value = serde_json::to_value(caps.to_v3(summary, describe, tools, permitted, deferred))
.map_err(|e| e.to_string())?;
if let Some(obj) = value.as_object_mut() {
let gov = obj
.entry("governance".to_string())
.or_insert_with(|| serde_json::json!({}));
if let Some(gov_obj) = gov.as_object_mut() {
gov_obj.insert(
"agent_action_check".to_string(),
serde_json::Value::String("substrate-authoritative-for-internal-ops".to_string()),
);
gov_obj.insert(
"rules_immutable_seed".to_string(),
serde_json::Value::Bool(true),
);
}
}
Ok(value)
}
fn build_capabilities_overlay(
tier_config: &TierConfig,
resolved_models: &ResolvedModels,
reranker: Option<&BatchedReranker>,
embedder_loaded: bool,
conn: Option<&rusqlite::Connection>,
) -> crate::config::Capabilities {
let mut caps = tier_config.capabilities_with_resolved(resolved_models);
caps.features.reranker_active = match reranker {
Some(ce) if ce.is_neural() => RerankerMode::Neural,
Some(_) => {
caps.features.cross_encoder_reranking = false;
caps.models.cross_encoder = "lexical-fallback (neural download failed)".to_string();
RerankerMode::LexicalFallback
}
None => {
caps.features.cross_encoder_reranking = false;
RerankerMode::Off
}
};
if let Some(ce) = reranker {
caps.features.reflection_boost =
crate::config::ReflectionBoostReport::from(ce.reflection_boost());
}
caps.features.embedder_loaded = embedder_loaded;
caps.features.recall_mode_active = compute_recall_mode(tier_config, embedder_loaded);
caps.hnsw.evictions_total = crate::hnsw::index_evictions_total();
caps.hnsw.evicted_recently = crate::hnsw::evicted_recently(60);
caps.hooks.auto_export_spawn_failed_total = crate::metrics::auto_export_spawn_failed_count();
if let Some(c) = conn {
if let Ok(n) = db::count_active_governance_rules(c) {
caps.permissions.active_rules = n;
}
if let Ok(rules) = db::list_active_governance_policies(c) {
caps.permissions.rule_summary = rules
.into_iter()
.map(|(ns, p)| format_rule_summary(&ns, &p))
.collect();
}
if let Ok(n) = db::count_subscriptions(c) {
caps.hooks.registered_count = n;
}
if let Ok(n) = db::count_pending_actions_by_status(c, "pending") {
caps.approval.pending_requests = n;
}
if let Ok(n) = crate::governance::deferred_audit::dlq_size(c) {
caps.approval.deferred_audit_dlq_size = n;
}
if let Ok((count, bytes)) = c.query_row(
"SELECT COUNT(*), COALESCE(SUM(compressed_size), 0) FROM memory_transcripts",
[],
|r| Ok((r.get::<_, i64>(0)?, r.get::<_, i64>(1)?)),
) {
#[allow(clippy::cast_sign_loss)]
let count_usize = count.max(0) as usize;
#[allow(clippy::cast_sign_loss)]
let bytes_u64 = bytes.max(0) as u64;
caps.transcripts.total_count = count_usize;
caps.transcripts.total_size_mb = bytes_u64 / (1024 * 1024);
if count_usize > 0 {
caps.transcripts.status.enabled = true;
}
}
}
caps
}
#[must_use]
pub fn format_rule_summary(namespace: &str, policy: &crate::models::GovernancePolicy) -> String {
use crate::models::ApproverType;
let approver = match &policy.core.approver {
ApproverType::Human => "human".to_string(),
ApproverType::Agent(id) => format!("agent:{id}"),
ApproverType::Consensus(n) => format!("consensus:{n}"),
};
format!(
"{namespace} — write={write}, promote={promote}, delete={delete}, approver={approver}, inherit={inherit}",
write = policy.core.write.as_str(),
promote = policy.core.promote.as_str(),
delete = policy.core.delete.as_str(),
inherit = policy.core.inherit,
)
}
#[must_use]
pub fn build_capabilities_summary(profile: &crate::profile::Profile) -> String {
use crate::profile::{ALWAYS_ON_TOOLS, Family};
let total: usize = Family::all()
.iter()
.map(|f| f.expected_tool_count())
.sum::<usize>()
.saturating_sub(ALWAYS_ON_TOOLS.len());
let from_families: usize = profile.expected_tool_count();
let always_on_in_loaded_family: usize = ALWAYS_ON_TOOLS
.iter()
.filter(|name| Family::for_tool(name).is_some_and(|f| profile.includes(f)))
.count();
let visible = from_families.saturating_sub(always_on_in_loaded_family);
let unloaded = total.saturating_sub(visible);
let label = profile_summary_label(profile);
format!(
"{visible} of {total} memory tools are advertised in tools/list under the current \
profile ({label}). The other {unloaded} are listed in this manifest but NOT directly \
callable. To use any unloaded tool, choose one of: \
(a) restart the server with --profile <family> or --profile full, \
(b) call memory_load_family(family=<name>) — preferred, \
(c) call memory_smart_load(intent='<plain language>') — easiest, \
(d) call the tool by name and recover from JSON-RPC -32601."
)
}
#[must_use]
pub fn build_capabilities_describe_to_user(profile: &crate::profile::Profile) -> String {
use crate::profile::Family;
let loaded_tools: Vec<&'static str> = Family::all()
.iter()
.filter(|f| profile.includes(**f))
.flat_map(|f| f.tool_names().iter().copied())
.filter(|name| !crate::profile::ALWAYS_ON_TOOLS.contains(name))
.collect();
let unloaded_tools: Vec<&'static str> = Family::all()
.iter()
.filter(|f| !profile.includes(**f))
.flat_map(|f| f.tool_names().iter().copied())
.filter(|name| !crate::profile::ALWAYS_ON_TOOLS.contains(name))
.collect();
let n_loaded = loaded_tools.len();
let n_unloaded = unloaded_tools.len();
let preview_loaded = loaded_tools
.iter()
.take(5)
.map(|name| short_tool_name(name))
.collect::<Vec<_>>()
.join(", ");
let loaded_more_marker = if n_loaded > 5 { ", ..." } else { "" };
if n_unloaded == 0 {
format!(
"I can directly use all {n_loaded} memory tools right now \
({preview_loaded}{loaded_more_marker}). Nothing more to load — \
the full memory surface is already active."
)
} else {
let preview_unloaded = unloaded_tools
.iter()
.take(4)
.map(|name| short_tool_name(name))
.collect::<Vec<_>>()
.join(", ");
let plural_loaded = if n_loaded == 1 { "" } else { "s" };
format!(
"I can directly use {n_loaded} memory tool{plural_loaded} right now \
({preview_loaded}{loaded_more_marker}). {n_unloaded} more \
({preview_unloaded}, etc.) are available on demand — I can load them \
if you ask for something that needs them, or you can restart the \
server with a different profile."
)
}
}
fn short_tool_name(name: &'static str) -> &'static str {
name.strip_prefix("memory_").unwrap_or(name)
}
#[must_use]
pub fn build_capabilities_tools(
profile: &crate::profile::Profile,
mcp_config: Option<&crate::config::McpConfig>,
agent_id: Option<&str>,
) -> Vec<crate::config::ToolEntry> {
use crate::config::{AllowlistDecision, ToolEntry};
use crate::profile::{ALWAYS_ON_TOOLS, Family};
let mut entries: Vec<ToolEntry> = Vec::with_capacity(50);
for fam in Family::all() {
let family_name = fam.name();
let loaded = profile.includes(*fam);
let allowed = match mcp_config {
Some(_) if agent_id.is_none() => true,
Some(cfg) => match cfg.allowlist_decision(agent_id, family_name) {
AllowlistDecision::Disabled | AllowlistDecision::Allow => true,
AllowlistDecision::Deny => false,
},
None => true,
};
for name in fam.tool_names() {
entries.push(ToolEntry {
name: (*name).to_string(),
family: family_name.to_string(),
loaded,
callable_now: loaded && allowed,
examples: tool_examples(name),
});
}
}
for name in ALWAYS_ON_TOOLS {
if !entries.iter().any(|e| e.name == *name) {
entries.push(ToolEntry {
name: (*name).to_string(),
family: "always_on".to_string(),
loaded: true,
callable_now: true,
examples: tool_examples(name),
});
}
}
entries
}
#[must_use]
pub fn tool_examples(name: &str) -> Vec<crate::config::ToolExample> {
use crate::config::ToolExample;
use crate::mcp::registry::tool_names as tn;
use crate::models::Tier;
use serde_json::json;
let ex = |call: serde_json::Value, desc: &str| ToolExample {
call,
description: desc.to_string(),
};
match name {
tn::MEMORY_STORE => vec![ex(
json!({"title": "design", "content": "wt-1 atomisation", "tier": Tier::Long.as_str(), "namespace": "ai-memory"}),
"Persists a long-tier memory; returns {id, tier, title, namespace, agent_id}.",
)],
tn::MEMORY_RECALL => vec![ex(
json!({"context": "atomisation gates", "namespace": "ai-memory", "limit": 5}),
"Hybrid FTS+semantic recall; returns top-K ranked memories.",
)],
tn::MEMORY_SEARCH => vec![ex(
json!({"query": "L1-6 governance", "limit": 10}),
"FTS5 keyword search across namespaces.",
)],
tn::MEMORY_LINK => vec![ex(
json!({"source_id": "<uuid-a>", "target_id": "<uuid-b>", "relation": "derives_from"}),
"Signed directional edge; returns {linked, source_id, target_id, relation, invalidation_notified, attest_level}.",
)],
tn::MEMORY_REFLECT => vec![ex(
json!({
"source_ids": ["<uuid-1>", "<uuid-2>"],
"title": "Reflection over alpha + beta",
"content": "Synthesis of the two source memories.",
"depth": 1,
}),
"Curator synthesises a Reflection; returns {id, reflection_depth, reflects_on, namespace}.",
)],
tn::MEMORY_PERSONA_GENERATE => vec![
ex(
json!({"entity_id": "alice", "namespace": "team/alpha"}),
"Single-namespace scope.",
),
ex(
json!({"entity_id": "alice"}),
"#848 cross-namespace; persona lands in 'global'.",
),
],
tn::MEMORY_CONSOLIDATE => vec![ex(
json!({"ids": ["<uuid-a>", "<uuid-b>"], "title": "Distilled summary", "namespace": "team/alpha"}),
"Curator distils the listed memories into one consolidated memory.",
)],
tn::MEMORY_ATOMISE => vec![ex(
json!({"memory_id": "<long-uuid>", "max_atom_tokens": 200}),
"WT-1 decomposition; archives parent.",
)],
tn::MEMORY_FIND_PATHS => vec![ex(
json!({"source_id": "<uuid-a>", "target_id": "<uuid-b>", "max_depth": 4}),
"BFS over KG; returns path arrays of memory ids.",
)],
tn::MEMORY_KG_QUERY => vec![ex(
json!({"source_id": "<uuid>", "max_depth": 2}),
"Typed KG walk; returns nodes+edges.",
)],
tn::MEMORY_EXPORT_REFLECTION => vec![ex(
json!({"memory_id": "<reflection-uuid>", "format": "md"}),
"QW-1 export; returns {content, suggested_filename}.",
)],
tn::MEMORY_SMART_LOAD => vec![ex(
json!({"intent": "inspect the knowledge graph", "k": 10}),
"B2 intent routing.",
)],
tn::MEMORY_LOAD_FAMILY => vec![ex(
json!({"family": "graph", "k": 10}),
"B1 explicit family load.",
)],
tn::MEMORY_SESSION_START => vec![ex(
json!({"namespace": "ai-memory", "limit": 10}),
"SessionStart bootstrap; returns memories+persona+rules.",
)],
tn::MEMORY_VERIFY => vec![
ex(
json!({"source_id": "<uuid-a>", "target_id": "<uuid-b>", "relation": "derives_from"}),
"H4 on-demand link-signature re-verify; returns {signature_verified, attest_level, signed_by, signed_at}.",
),
ex(
json!({"link_id": "<uuid-a>--derives_from--><uuid-b>"}),
"Composite link_id form of the same re-verify call.",
),
],
tn::MEMORY_NOTIFY => vec![ex(
json!({"target_agent_id": "ai:claude@host-a", "title": "deploy completed", "payload": "prod deploy finished green"}),
"Write a message to the target agent's inbox; read via memory_inbox.",
)],
tn::MEMORY_SKILL_REGISTER => vec![
ex(
json!({"folder_path": "/path/to/skill-dir"}),
"Register a SKILL.md folder (optional resources/ sub-dir); returns {id, digest, signed}.",
),
ex(
json!({"inline_skill": "---\nnamespace: example\nname: demo\ndescription: A demo skill.\n---\n\nBody.\n"}),
"Register a SKILL.md from inline text (no filesystem dependency).",
),
],
_ => Vec::new(),
}
}
#[must_use]
pub fn build_agent_permitted_families(
mcp_config: Option<&crate::config::McpConfig>,
agent_id: Option<&str>,
) -> Option<Vec<String>> {
use crate::config::AllowlistDecision;
use crate::profile::Family;
let cfg = mcp_config?;
let aid = agent_id?;
let table = cfg.allowlist.as_ref()?;
if table.is_empty() {
return None;
}
let permitted: Vec<String> = Family::all()
.iter()
.filter(|fam| {
matches!(
cfg.allowlist_decision(Some(aid), fam.name()),
AllowlistDecision::Allow
)
})
.map(|fam| fam.name().to_string())
.collect();
Some(permitted)
}
fn profile_summary_label(profile: &crate::profile::Profile) -> String {
use crate::profile::Profile;
if *profile == Profile::full() {
"full".to_string()
} else if *profile == Profile::core() {
"core".to_string()
} else if *profile == Profile::graph() {
"graph".to_string()
} else if *profile == Profile::admin() {
"admin".to_string()
} else if *profile == Profile::power() {
"power".to_string()
} else {
profile
.families()
.iter()
.map(|f| f.name())
.collect::<Vec<_>>()
.join(",")
}
}
#[must_use]
pub fn effective_tier_label(has_llm: bool, has_embedder: bool, has_reranker: bool) -> &'static str {
if has_llm && has_embedder && has_reranker {
"autonomous"
} else if has_llm && has_embedder {
"smart"
} else if has_embedder {
"semantic"
} else {
"keyword"
}
}
pub fn overlay_tool_payloads(
obj: &mut serde_json::Map<String, Value>,
_profile: &crate::profile::Profile,
include_schema: bool,
verbose: bool,
) {
if !include_schema && !verbose {
return;
}
let defs = if verbose {
crate::mcp::tool_definitions()
} else {
let mut defs = crate::mcp::tool_definitions();
if let Some(arr) = defs.get_mut("tools").and_then(Value::as_array_mut) {
crate::mcp::registry::strip_docs_from_tools(arr);
}
defs
};
let lookup: std::collections::HashMap<String, (Option<Value>, Option<Value>)> = defs
.get("tools")
.and_then(Value::as_array)
.map(|tools| {
tools
.iter()
.filter_map(|t| {
let name = t
.get(param_names::NAME)
.and_then(Value::as_str)?
.to_string();
let docs = t.get("docs").cloned();
let schema = t.get("inputSchema").cloned();
Some((name, (docs, schema)))
})
.collect()
})
.unwrap_or_default();
if let Some(tools) = obj.get_mut("tools").and_then(Value::as_array_mut) {
for tool in tools.iter_mut() {
let Some(tool_obj) = tool.as_object_mut() else {
continue;
};
let Some(name) = tool_obj.get(param_names::NAME).and_then(Value::as_str) else {
continue;
};
let Some((docs, schema)) = lookup.get(name) else {
continue;
};
if include_schema && let Some(s) = schema {
tool_obj.insert("inputSchema".to_string(), s.clone());
}
if verbose && let Some(d) = docs {
tool_obj.insert("docstring".to_string(), d.clone());
}
}
} else {
let payloads: Vec<Value> = lookup
.iter()
.map(|(name, (docs, schema))| {
let mut entry = serde_json::Map::new();
entry.insert("name".to_string(), Value::String(name.clone()));
if include_schema && let Some(s) = schema {
entry.insert("inputSchema".to_string(), s.clone());
}
if verbose && let Some(d) = docs {
entry.insert("docstring".to_string(), d.clone());
}
Value::Object(entry)
})
.collect();
obj.insert("tool_payloads".to_string(), Value::Array(payloads));
}
}
fn compute_recall_mode(
tier_config: &TierConfig,
embedder_loaded: bool,
) -> crate::config::RecallMode {
use crate::config::RecallMode;
if tier_config.embedding_model.is_none() {
RecallMode::Disabled
} else if embedder_loaded {
RecallMode::Hybrid
} else {
RecallMode::Degraded
}
}
#[cfg(test)]
mod example_validity_1606_tests {
#[test]
fn recall_example_payload_parses_1606() {
let examples = super::tool_examples(crate::mcp::registry::tool_names::MEMORY_RECALL);
assert!(
!examples.is_empty(),
"memory_recall must carry a worked example (#803)"
);
for example in &examples {
crate::models::RecallRequest::from_mcp_params(&example.call).unwrap_or_else(|e| {
panic!(
"memory_recall capabilities example must be byte-equal to a \
valid MCP call (#1606/#1325); parser said: {e}"
)
});
}
}
}
#[cfg(test)]
mod example_validity_1644_tests {
use serde_json::Value;
fn example_bearing_tools() -> Vec<String> {
let defs = crate::mcp::tool_definitions();
let mut names: Vec<String> = defs["tools"]
.as_array()
.expect("tool_definitions must emit `tools` array")
.iter()
.filter_map(|t| t.get("name").and_then(Value::as_str))
.map(str::to_string)
.collect();
for name in crate::profile::ALWAYS_ON_TOOLS {
if !names.iter().any(|n| n == name) {
names.push((*name).to_string());
}
}
names.retain(|n| !super::tool_examples(n).is_empty());
assert!(
!names.is_empty(),
"the #803 worked-example catalog must not be empty"
);
names
}
fn parses_as<T: serde::de::DeserializeOwned>(name: &str, call: &Value) {
if let Err(e) = serde_json::from_value::<T>(call.clone()) {
panic!(
"{name} capabilities example must be byte-equal to a valid \
MCP call (#1644/#1325); parser said: {e}"
);
}
}
#[test]
fn issue_1644_every_example_round_trips_through_its_parser() {
use crate::mcp::registry::tool_names as tn;
for name in example_bearing_tools() {
for example in &super::tool_examples(&name) {
let call = &example.call;
match name.as_str() {
x if x == tn::MEMORY_STORE => {
parses_as::<crate::mcp::store::StoreRequest>(&name, call);
}
x if x == tn::MEMORY_RECALL => {
crate::models::RecallRequest::from_mcp_params(call).unwrap_or_else(|e| {
panic!("memory_recall example must parse (#1644): {e}")
});
}
x if x == tn::MEMORY_SEARCH => {
parses_as::<crate::mcp::search::SearchRequest>(&name, call);
}
x if x == tn::MEMORY_LINK => {
parses_as::<crate::mcp::link::LinkRequest>(&name, call);
}
x if x == tn::MEMORY_REFLECT => {
parses_as::<crate::mcp::reflect::ReflectRequest>(&name, call);
}
x if x == tn::MEMORY_PERSONA_GENERATE => {
parses_as::<crate::mcp::persona::PersonaGenerateRequest>(&name, call);
}
x if x == tn::MEMORY_CONSOLIDATE => {
parses_as::<crate::mcp::consolidate::ConsolidateRequest>(&name, call);
}
x if x == tn::MEMORY_ATOMISE => {
parses_as::<crate::mcp::atomise::AtomiseRequest>(&name, call);
}
x if x == tn::MEMORY_FIND_PATHS => {
parses_as::<crate::mcp::find_paths::FindPathsRequest>(&name, call);
}
x if x == tn::MEMORY_KG_QUERY => {
parses_as::<crate::mcp::kg_query::KgQueryRequest>(&name, call);
}
x if x == tn::MEMORY_EXPORT_REFLECTION => {
parses_as::<crate::mcp::export_reflection::ExportReflectionRequest>(
&name, call,
);
}
x if x == tn::MEMORY_SMART_LOAD => {
parses_as::<crate::mcp::load_family::SmartLoadRequest>(&name, call);
}
x if x == tn::MEMORY_LOAD_FAMILY => {
parses_as::<crate::mcp::load_family::LoadFamilyRequest>(&name, call);
}
x if x == tn::MEMORY_SESSION_START => {
parses_as::<crate::mcp::session_start::SessionStartRequest>(&name, call);
}
x if x == tn::MEMORY_VERIFY => {
parses_as::<crate::mcp::verify::VerifyRequest>(&name, call);
let obj = call.as_object().expect("verify example is an object");
if let Some(lid) = obj.get("link_id").and_then(Value::as_str) {
assert!(
crate::mcp::link::parse_link_id(lid).is_some(),
"memory_verify link_id example must satisfy parse_link_id (#1644): {lid}"
);
} else {
assert!(
obj.contains_key("source_id") && obj.contains_key("target_id"),
"memory_verify example must carry link_id or source_id+target_id (#1644)"
);
}
}
x if x == tn::MEMORY_NOTIFY => {
parses_as::<crate::mcp::notify::NotifyRequest>(&name, call);
}
x if x == tn::MEMORY_SKILL_REGISTER => {
parses_as::<crate::mcp::skill_register::SkillRegisterRequest>(&name, call);
}
other => panic!(
"tool `{other}` carries worked examples but has no parser \
round-trip arm in this test — add one so the example \
stays byte-valid (#1644 class guard)"
),
}
}
}
}
#[test]
fn issue_1644_example_keys_match_declared_schema() {
let defs = crate::mcp::tool_definitions();
let tools = defs["tools"]
.as_array()
.expect("tool_definitions must emit `tools` array");
for name in example_bearing_tools() {
let tool = tools
.iter()
.find(|t| t.get("name").and_then(Value::as_str) == Some(name.as_str()))
.unwrap_or_else(|| panic!("`{name}` must be in the tool catalog"));
let props: std::collections::BTreeSet<&str> = tool
.pointer("/inputSchema/properties")
.and_then(Value::as_object)
.unwrap_or_else(|| panic!("`{name}` must carry inputSchema.properties"))
.keys()
.map(String::as_str)
.collect();
let required: Vec<&str> = tool
.pointer("/inputSchema/required")
.and_then(Value::as_array)
.map(|a| a.iter().filter_map(Value::as_str).collect())
.unwrap_or_default();
for (idx, example) in super::tool_examples(&name).iter().enumerate() {
let obj = example
.call
.as_object()
.unwrap_or_else(|| panic!("{name} example {idx} call must be an object"));
for key in obj.keys() {
assert!(
props.contains(key.as_str()),
"{name} example {idx}: key `{key}` is not a declared \
inputSchema property — the handler never reads it \
(#1644 inert-param class); declared: {props:?}"
);
}
for req in &required {
assert!(
obj.contains_key(*req),
"{name} example {idx}: missing required property `{req}` (#1644)"
);
}
}
}
}
#[test]
fn issue_1644_return_shape_claims_are_truthful() {
use crate::mcp::registry::tool_names as tn;
let store = super::tool_examples(tn::MEMORY_STORE);
assert!(
store[0]
.description
.contains("{id, tier, title, namespace, agent_id}"),
"memory_store example must claim the real success envelope (#1644)"
);
assert!(
!store[0].description.contains("{id, status}"),
"memory_store example must not claim the fictitious {{id, status}} shape (#1644)"
);
let link = super::tool_examples(tn::MEMORY_LINK);
assert!(
!link[0].description.contains("link_id"),
"memory_link's success envelope carries no link_id (#1644)"
);
assert!(
link[0].description.contains("invalidation_notified")
&& link[0].description.contains("attest_level"),
"memory_link example must claim the real success envelope (#1644)"
);
let verify = super::tool_examples(tn::MEMORY_VERIFY);
assert!(
verify[0].description.contains("signature_verified"),
"memory_verify's truthful return key is signature_verified (#1644)"
);
}
}
#[cfg(test)]
mod d1_2_983_tests {
use super::*;
use serde_json::Value;
fn derived_properties() -> serde_json::Map<String, Value> {
let schema = CapabilitiesTool::input_schema();
if let Some(props) = schema.get("properties").and_then(Value::as_object) {
return props.clone();
}
if let Some(props) = schema
.pointer("/definitions/CapabilitiesRequest/properties")
.and_then(Value::as_object)
{
return props.clone();
}
panic!("schemars schema must emit properties at a known path; got {schema:#}")
}
fn legacy_properties() -> serde_json::Map<String, Value> {
let defs = crate::mcp::registry::tool_definitions();
let tools = defs
.get("tools")
.and_then(Value::as_array)
.expect("tool_definitions must emit `tools` array");
let cap = tools
.iter()
.find(|t| t.get("name").and_then(Value::as_str) == Some("memory_capabilities"))
.expect("memory_capabilities must be in the legacy tool catalog");
cap.pointer("/inputSchema/properties")
.and_then(Value::as_object)
.expect("memory_capabilities.inputSchema.properties must be an object")
.clone()
}
#[test]
fn capabilities_parity_property_set_983() {
let legacy = legacy_properties();
let derived = derived_properties();
let legacy_keys: std::collections::BTreeSet<&str> =
legacy.keys().map(String::as_str).collect();
let derived_keys: std::collections::BTreeSet<&str> =
derived.keys().map(String::as_str).collect();
assert_eq!(
legacy_keys,
derived_keys,
"schemars-derived schema must cover every legacy property; missing/extra: {:?}",
legacy_keys
.symmetric_difference(&derived_keys)
.collect::<Vec<_>>()
);
}
#[test]
fn no_reranker_handle_flips_cross_encoder_flag_1647() {
let tier_config = crate::config::FeatureTier::Autonomous.config();
let caps = build_capabilities_overlay(
&tier_config,
&crate::config::ResolvedModels::default(),
None,
false,
None,
);
assert!(
!caps.features.cross_encoder_reranking,
"#1647: no handle ⇒ flag false"
);
assert_eq!(caps.features.reranker_active, RerankerMode::Off);
}
#[test]
fn capabilities_parity_descriptions_983() {
let legacy = legacy_properties();
let derived = derived_properties();
for (name, legacy_prop) in &legacy {
let legacy_desc = legacy_prop.get("description").and_then(Value::as_str);
let derived_desc = derived
.get(name)
.and_then(|p| p.get("description"))
.and_then(Value::as_str);
if let Some(want) = legacy_desc {
assert_eq!(
derived_desc,
Some(want),
"property `{name}`: legacy description must match the schemars-derived one byte-for-byte"
);
}
}
}
#[test]
fn capabilities_parity_top_level_object_983() {
let schema = CapabilitiesTool::input_schema();
assert_eq!(
schema.get("type").and_then(Value::as_str),
Some("object"),
"top-level type must be `object`"
);
}
#[test]
fn capabilities_parity_no_required_fields_983() {
let schema = CapabilitiesTool::input_schema();
let required = schema.get("required");
if let Some(arr) = required.and_then(Value::as_array) {
assert!(
arr.is_empty(),
"schemars-derived schema must not require any field; got {arr:?}"
);
}
}
#[test]
fn capabilities_parity_allowed_diffs_documented_983() {
let derived = derived_properties();
for name in &["accept", "family", "include_schema", "verbose"] {
let prop = derived
.get(*name)
.unwrap_or_else(|| panic!("derived property `{name}` missing"));
let type_value = prop.get("type").expect("each property has `type`");
let arr = type_value
.as_array()
.unwrap_or_else(|| panic!("`{name}.type` must be an array (Option<T> nullable)"));
assert!(
arr.iter().any(|v| v.as_str() == Some("null")),
"`{name}.type` must include `\"null\"` (Option<T> derive)"
);
assert_eq!(
prop.get("default"),
Some(&Value::Null),
"`{name}.default` must be `null` (Option<T>::None)"
);
}
}
}