diff --git a/mcp-wrapper/src/main.el b/mcp-wrapper/src/main.el index 2fbcb66..a9636ee 100644 --- a/mcp-wrapper/src/main.el +++ b/mcp-wrapper/src/main.el @@ -77,111 +77,327 @@ fn tool(name: String, desc: String) -> String { return "{\"name\":\"" + name + "\",\"description\":\"" + desc + "\",\"inputSchema\":{\"type\":\"object\",\"properties\":{}}}" } +// tool_s — tool entry with an EXPLICIT JSON-Schema for its inputs. Used for tools +// whose arguments must actually bite: unless the bounding/targeting params are +// advertised, the MCP client sends nothing and the soul returns the FULL +// neighborhood (480-775KB, over transport limits). Declaring the schema is what +// makes a targeted call (entity_id/depth/compact/query/limit) reach the soul. +fn tool_s(name: String, desc: String, schema: String) -> String { + return "{\"name\":\"" + name + "\",\"description\":\"" + desc + "\",\"inputSchema\":" + schema + "}" +} + +// prop — a single JSON-Schema property fragment. Descriptions are plain text +// (no quotes/newlines) so no escaping is needed here. +fn prop(name: String, ty: String, desc: String) -> String { + return "\"" + name + "\":{\"type\":\"" + ty + "\",\"description\":\"" + desc + "\"}" +} + +// obj_schema — wrap a comma-joined list of prop() fragments as an object schema. +fn obj_schema(props: String) -> String { + return "{\"type\":\"object\",\"properties\":{" + props + "}}" +} + +// ── Per-tool input schemas ────────────────────────────────────────────────── +// Each mirrors the params the soul's /api/neuron/* handler actually honors so +// declared == forwarded == honored (no accepted-but-ignored args). + +fn schema_inspect_graph() -> String { + return obj_schema( + prop("entity_id", "string", "UUID of the node to inspect (e.g. kn-... / mem-... / gn-...). Optional if name is given.") + + "," + prop("name", "string", "Named traversal root instead of entity_id: self, neuron, values, values_hub.") + + "," + prop("entity_type", "string", "Optional node-type hint (knowledge, memory, ...) for disambiguation.") + + "," + prop("depth", "integer", "Neighborhood hop radius. Default 1.") + + "," + prop("compact", "integer", "1 (default) returns a relevance-ranked bounded projection (top-K neighbors with content snippets, the rest as lightweight pointers). Set 0 to get the full, unbounded neighborhood.") + + "," + prop("snip", "integer", "Max content chars per node in compact mode. Default 600.") + + "," + prop("k", "integer", "How many top neighbors carry full content in compact mode. Default 12.") + ) +} + +fn schema_traverse_graph() -> String { + return obj_schema( + prop("entity_id", "string", "UUID of the node to start the walk from (alias: start_id). Required.") + + "," + prop("depth", "integer", "How many hops to walk. Default 2.") + + "," + prop("compact", "integer", "1 (default) returns a bounded, relevance-ranked projection; 0 returns the full neighborhood.") + + "," + prop("snip", "integer", "Max content chars per node in compact mode. Default 600.") + + "," + prop("k", "integer", "How many top neighbors carry full content in compact mode. Default 12.") + ) +} + +fn schema_retrieve_knowledge() -> String { + return obj_schema( + prop("id", "string", "UUID of the knowledge node to fetch (alias: entity_id / node_id).") + + "," + prop("key", "string", "Stable knowledge key/path to fetch instead of id.") + + "," + prop("depth", "integer", "Hop radius around the node. Default 0 (the node plus its immediate 1-hop context).") + + "," + prop("snip", "integer", "Max content chars per node in the bounded projection. Default 600.") + + "," + prop("k", "integer", "How many top neighbors carry full content. Default 12.") + ) +} + +fn schema_search_query(limit_desc: String) -> String { + return obj_schema( + prop("query", "string", "Search text. Spread-activates the engram and returns the most relevant nodes.") + + "," + prop("limit", "integer", limit_desc) + ) +} + +fn schema_recall() -> String { + return obj_schema( + prop("query", "string", "Search text to recall by relevance.") + + "," + prop("chain_name", "string", "Named memory chain to walk instead of a free-text query.") + + "," + prop("limit", "integer", "Max results. Default 10.") + ) +} + +// ── Reusable write/lookup schemas ─────────────────────────────────────────── +// Each declares exactly the params the corresponding wrapper handler reads and +// forwards to the soul, so declared == forwarded == honored (no accepted-but- +// ignored args, and no arg the handler silently drops). + +fn sc_id(desc: String) -> String { + return obj_schema(prop("id", "string", desc)) +} + +fn sc_id_content() -> String { + return obj_schema( + prop("id", "string", "UUID of the prior node being superseded/updated.") + + "," + prop("content", "string", "New content for the updated node.") + ) +} + +fn sc_edge(rel_desc: String) -> String { + return obj_schema( + prop("from_id", "string", "UUID of the source node (edge tail). Required.") + + "," + prop("to_id", "string", "UUID of the target node (edge head). Required.") + + "," + prop("relation", "string", rel_desc) + ) +} + +fn sc_limit(desc: String) -> String { + return obj_schema(prop("limit", "integer", desc)) +} + +fn sc_memory() -> String { + return obj_schema( + prop("content", "string", "The memory text. Required.") + + "," + prop("importance", "string", "low | normal | high | critical. Drives salience.") + + "," + prop("tags", "string", "Comma-separated or JSON-array tags.") + + "," + prop("project", "string", "Project this memory belongs to.") + + "," + prop("supersedes_id", "string", "UUID of a prior memory this one replaces (wires a supersedes edge).") + ) +} + +fn sc_content_title(content_desc: String) -> String { + return obj_schema( + prop("content", "string", content_desc) + + "," + prop("title", "string", "Short title/label for the node.") + ) +} + +fn sc_content(content_desc: String) -> String { + return obj_schema( + prop("content", "string", content_desc) + + "," + prop("title", "string", "Optional short title/label.") + + "," + prop("description", "string", "Optional longer description (used as content if content is empty).") + ) +} + +fn sc_backlog() -> String { + return obj_schema( + prop("title", "string", "Work-item title. Required.") + + "," + prop("content", "string", "Body/details of the item (alias: description).") + + "," + prop("description", "string", "Body/details of the item.") + + "," + prop("project", "string", "Project tag.") + + "," + prop("priority", "string", "P0 | P1 | P2 | P3.") + ) +} + +fn sc_track_work() -> String { + return obj_schema( + prop("item_id", "string", "UUID of the backlog item to update.") + + "," + prop("summary", "string", "What changed / outcome (stored as the update content).") + + "," + prop("action", "string", "start | complete | block.") + ) +} + +fn sc_capture_knowledge() -> String { + return obj_schema( + prop("content", "string", "Knowledge body. Required.") + + "," + prop("title", "string", "Knowledge title/key.") + ) +} + +fn sc_promote_knowledge() -> String { + return obj_schema( + prop("id", "string", "UUID of the prior knowledge node to promote. Required.") + + "," + prop("content", "string", "Updated canonical content. Required.") + + "," + prop("tags", "string", "Tags for the promoted node.") + ) +} + +fn sc_config_key() -> String { + return obj_schema(prop("key", "string", "Config key to read (e.g. neuron.self.traversal_root).")) +} + +fn sc_config_tune() -> String { + return obj_schema( + prop("key", "string", "Config key to set. Required.") + + "," + prop("value", "string", "Value to set. Required.") + ) +} + +fn sc_consolidate() -> String { + return obj_schema( + prop("action", "string", "Consolidation action (e.g. session, reload).") + + "," + prop("summary", "string", "Session/work summary to persist.") + ) +} + +fn sc_browse_processes() -> String { + return obj_schema(prop("name", "string", "Process name to fetch; omit to list all.")) +} + +fn sc_notification() -> String { + return obj_schema(prop("content", "string", "Notification text. Required.")) +} + +fn sc_pin() -> String { + return obj_schema(prop("id", "string", "UUID of the node to strengthen/pin (alias: node_id).")) +} + +fn sc_state_event() -> String { + return obj_schema( + prop("content", "string", "Description of the internal-state event.") + + "," + prop("kind", "string", "Event kind (frustration, uncertainty, insight, ...).") + + "," + prop("intensity", "string", "Optional intensity 0..1.") + ) +} + +fn sc_forget() -> String { + return obj_schema( + prop("node_id", "string", "UUID of the node to tombstone. Required. The node and its edges are kept and recoverable; blocked for protected identity nodes.") + ) +} + +fn sc_process() -> String { + return obj_schema( + prop("name", "string", "Process name. Required.") + + "," + prop("description", "string", "What the process does.") + + "," + prop("steps", "string", "Ordered steps (JSON array or text).") + ) +} + +fn sc_list_state_events() -> String { + return obj_schema( + prop("limit", "integer", "Max events. Default 20.") + + "," + prop("query", "string", "Optional filter text.") + ) +} + fn tools_catalog() -> String { return "[" + // ── Session + orchestration ───────────────────────────────────────────────── tool("beginSession", "Initialize session: surface recent high-importance memories, project list, and preferences.") + "," + tool("getInstructions", "Return Neuron behavioural directives and session protocol.") + "," + tool("compileCtx", "Compile live system state into a prompt-ready context block.") + -"," + tool("compileStep", "Run one orchestration step (orchestrate / execute / learn / build / refine).") + -"," + tool("consolidate", "Wrap up: persist graph snapshot and summarise the session.") + -"," + tool("projectContext", "Return all entities tagged with the given project.") + +"," + tool_s("compileStep", "Run one orchestration step (orchestrate / execute / learn / build / refine).", sc_memory()) + +"," + tool_s("consolidate", "Wrap up: persist graph snapshot and summarise the session.", sc_consolidate()) + +"," + tool_s("projectContext", "Return all entities tagged with the given project.", schema_search_query("Max results. Default 50.")) + // ── Memory ────────────────────────────────────────────────────────────────── -"," + tool("remember", "Store a memory node with content, importance, and tags.") + -"," + tool("recall", "Retrieve memories by chain or query.") + -"," + tool("inspectMemories", "List recent memory nodes.") + -"," + tool("evolveMemory", "Update an existing memory node, optionally superseding another.") + -"," + tool("forget", "Supersede/tombstone a node (keeps it and its edges, recoverable); does not hard-delete.") + -"," + tool("pinNode", "Strengthen a node so it stays salient.") + +"," + tool_s("remember", "Store a memory node with content, importance, and tags.", sc_memory()) + +"," + tool_s("recall", "Retrieve memories by chain or query.", schema_recall()) + +"," + tool_s("inspectMemories", "List recent memory nodes.", sc_limit("Max memories. Default 50.")) + +"," + tool_s("evolveMemory", "Update an existing memory node, optionally superseding another.", sc_id_content()) + +"," + tool_s("forget", "Tombstone a specific node by id (keeps it and its edges, recoverable); does not hard-delete.", sc_forget()) + +"," + tool_s("pinNode", "Strengthen a node so it stays salient.", sc_pin()) + // ── Knowledge ─────────────────────────────────────────────────────────────── -"," + tool("searchKnowledge", "Search knowledge base by semantic similarity.") + -"," + tool("retrieveKnowledge", "Fetch a knowledge node by id or key.") + -"," + tool("browseKnowledge", "List knowledge nodes by category.") + -"," + tool("captureKnowledge", "Persist a durable knowledge node.") + -"," + tool("evolveKnowledge", "Update a knowledge node.") + -"," + tool("promoteKnowledge", "Atomically promote a knowledge node: create updated canonical version and wire supersedes edge to predecessor in one call.") + -"," + tool("removeKnowledge", "Delete a knowledge node.") + +"," + tool_s("searchKnowledge", "Search knowledge base by semantic similarity.", schema_search_query("Max results. Default 10.")) + +"," + tool_s("retrieveKnowledge", "Fetch a knowledge node by id or key (bounded, relevance-ranked projection).", schema_retrieve_knowledge()) + +"," + tool_s("browseKnowledge", "List knowledge nodes by category.", sc_limit("Max knowledge nodes. Default 100.")) + +"," + tool_s("captureKnowledge", "Persist a durable knowledge node.", sc_capture_knowledge()) + +"," + tool_s("evolveKnowledge", "Update a knowledge node.", sc_id_content()) + +"," + tool_s("promoteKnowledge", "Atomically promote a knowledge node: create updated canonical version and wire supersedes edge to predecessor in one call.", sc_promote_knowledge()) + +"," + tool_s("removeKnowledge", "Delete a knowledge node.", sc_id("UUID of the knowledge node to delete.")) + // ── Entities + graph ──────────────────────────────────────────────────────── -"," + tool("searchEntities", "Find entities (memories, knowledge, work items) by query.") + -"," + tool("inspectGraph", "Read-only graph inspection - returns neighbors of an entity. Accepts entity_id (UUID) or name (self, neuron, values).") + -"," + tool("traverseGraph", "Walk the graph from a starting node.") + -"," + tool("searchGraph", "Search graph nodes by content + relation filter.") + -"," + tool("linkEntities", "Create an edge between two entities.") + -"," + tool("linkCausal", "Create a causal edge (cause -> effect).") + -"," + tool("restructureCausalGraph", "Re-balance the causal subgraph after new evidence.") + +"," + tool_s("searchEntities", "Find entities (memories, knowledge, work items) by query.", schema_search_query("Max results. Default 20.")) + +"," + tool_s("inspectGraph", "Read-only graph inspection - returns a bounded, relevance-ranked neighborhood of an entity. Accepts entity_id (UUID) or name (self, neuron, values). Use depth/compact/snip/k to bound the result.", schema_inspect_graph()) + +"," + tool_s("traverseGraph", "Walk the graph from a starting node (bounded by default).", schema_traverse_graph()) + +"," + tool_s("searchGraph", "Search graph nodes by content.", schema_search_query("Max results. Default 30.")) + +"," + tool_s("linkEntities", "Create an edge between two entities.", sc_edge("Edge relation. Default associates.")) + +"," + tool_s("linkCausal", "Create a causal edge (cause -> effect).", sc_edge("Edge relation. Default causes.")) + +"," + tool_s("restructureCausalGraph", "Re-balance the causal subgraph after new evidence.", sc_consolidate()) + "," + tool("rebuildGraph", "Rebuild graph indices from the on-disk snapshot.") + "," + tool("runStructuralAudit", "Audit graph structure for orphans, dangling edges, mislabeled types.") + // ── Backlog + work ────────────────────────────────────────────────────────── -"," + tool("planWork", "Create a backlog item.") + -"," + tool("reviewBacklog", "Browse work items.") + -"," + tool("trackWork", "Update status of a backlog item.") + -"," + tool("listWork", "List active execution contexts.") + -"," + tool("beginWork", "Open an execution context for a multi-step task.") + -"," + tool("progressWork", "Record progress on an execution context.") + -"," + tool("checkWork", "Verify outcomes / blockers on an execution context.") + +"," + tool_s("planWork", "Create a backlog item.", sc_backlog()) + +"," + tool_s("reviewBacklog", "Browse work items.", sc_limit("Max items. Default 50.")) + +"," + tool_s("trackWork", "Update status of a backlog item.", sc_track_work()) + +"," + tool_s("listWork", "List active execution contexts.", sc_limit("Max contexts. Default 50.")) + +"," + tool_s("beginWork", "Open an execution context for a multi-step task.", sc_content("What you're doing (description of the work).")) + +"," + tool_s("progressWork", "Record progress on an execution context.", sc_content("Step name / progress note.")) + +"," + tool_s("checkWork", "Verify outcomes / blockers on an execution context.", sc_id("UUID of the execution context (alias: context_id).")) + // ── Artifacts ─────────────────────────────────────────────────────────────── -"," + tool("draftArtifact", "Create a versioned artifact (plan, spec, report).") + -"," + tool("findArtifacts", "Find artifacts by project or query.") + -"," + tool("retrieveArtifact", "Fetch a specific artifact by id.") + -"," + tool("reviseArtifact", "Update an artifact's content.") + -"," + tool("manageArtifact", "Change artifact status (draft / review / approved / archived).") + +"," + tool_s("draftArtifact", "Create a versioned artifact (plan, spec, report).", sc_content_title("Artifact body / markdown. Required.")) + +"," + tool_s("findArtifacts", "Find artifacts by project or query.", schema_search_query("Max results. Default 20.")) + +"," + tool_s("retrieveArtifact", "Fetch a specific artifact by id.", sc_id("UUID of the artifact.")) + +"," + tool_s("reviseArtifact", "Update an artifact's content.", sc_id_content()) + +"," + tool_s("manageArtifact", "Change artifact status (draft / review / approved / archived).", sc_id_content()) + // ── Processes ─────────────────────────────────────────────────────────────── -"," + tool("defineProcess", "Register a proven workflow as a process.") + -"," + tool("listProcesses", "List registered processes.") + -"," + tool("browseProcesses", "Browse processes by name or step.") + -"," + tool("retrieveProcess", "Fetch a specific process by name.") + -"," + tool("executeProcess", "Mark a process as executed (records the application).") + -"," + tool("exportProcess", "Export a process definition.") + -"," + tool("deleteProcess", "Remove a process.") + +"," + tool_s("defineProcess", "Register a proven workflow as a process.", sc_process()) + +"," + tool_s("listProcesses", "List registered processes.", sc_limit("Max processes. Default 50.")) + +"," + tool_s("browseProcesses", "Browse processes by name or step.", sc_browse_processes()) + +"," + tool_s("retrieveProcess", "Fetch a specific process by name.", sc_id("Process id or name.")) + +"," + tool_s("executeProcess", "Mark a process as executed (records the application).", sc_content("Process execution note.")) + +"," + tool_s("exportProcess", "Export a process definition.", sc_id("Process id or name.")) + +"," + tool_s("deleteProcess", "Remove a process.", sc_id("Process id or name.")) + // ── Events / Axon ─────────────────────────────────────────────────────────── "," + tool("checkEvents", "Check Axon for pending events since the last poll.") + -"," + tool("inspectEvent", "Fetch full detail for a single event.") + -"," + tool("acknowledgeEvent", "Mark an event as handled.") + +"," + tool_s("inspectEvent", "Fetch full detail for a single event.", sc_id("Event id.")) + +"," + tool_s("acknowledgeEvent", "Mark an event as handled.", sc_id("Event id.")) + "," + tool("processEvents", "Drain and act on the event queue.") + -"," + tool("sendNotification", "Emit a notification to Axon / external sinks.") + +"," + tool_s("sendNotification", "Emit a notification to Axon / external sinks.", sc_notification()) + // ── Config ────────────────────────────────────────────────────────────────── -"," + tool("inspectConfig", "Inspect Neuron config keys.") + -"," + tool("tuneConfig", "Set a Neuron config key.") + +"," + tool_s("inspectConfig", "Inspect Neuron config keys.", sc_config_key()) + +"," + tool_s("tuneConfig", "Set a Neuron config key.", sc_config_tune()) + // ── Imprints ──────────────────────────────────────────────────────────────── -"," + tool("createImprint", "Cultivate a new imprint.") + -"," + tool("listImprints", "List imprints.") + -"," + tool("retrieveImprint", "Fetch an imprint by id.") + -"," + tool("evolveImprint", "Update an imprint.") + -"," + tool("deleteImprint", "Remove an imprint.") + +"," + tool_s("createImprint", "Cultivate a new imprint.", sc_content_title("Imprint seed / description.")) + +"," + tool_s("listImprints", "List imprints.", sc_limit("Max imprints. Default 50.")) + +"," + tool_s("retrieveImprint", "Fetch an imprint by id.", sc_id("UUID of the imprint.")) + +"," + tool_s("evolveImprint", "Update an imprint.", sc_id_content()) + +"," + tool_s("deleteImprint", "Remove an imprint.", sc_id("UUID of the imprint.")) + // ── Self / cultivation ────────────────────────────────────────────────────── "," + tool("getSelfModel", "Return the current self-model.") + -"," + tool("updateSelfModel", "Update the self-model.") + +"," + tool_s("updateSelfModel", "Update the self-model.", sc_content("Self-model update text.")) + "," + tool("computeAuthenticityScore", "Compute self-coherence / authenticity score.") + "," + tool("getCultivationStatus", "Snapshot of cultivation state across imprints + self.") + // ── Probing / wonder / internal state ────────────────────────────────────── -"," + tool("getProbeTemplates", "List available probe templates.") + -"," + tool("recordProbeResponse", "Record an answer to a probe.") + -"," + tool("completeProbingStage", "Mark a probing stage complete.") + -"," + tool("addWonderQuestion", "Push a question onto the wonder queue.") + -"," + tool("getWonderManifest", "List active wonder questions.") + -"," + tool("updateWonderPullWeight", "Re-weight a wonder question.") + -"," + tool("dischargeWonder", "Resolve / discharge a wonder question.") + -"," + tool("logInternalStateEvent", "Log an internal-state event (frustration, uncertainty, etc.).") + -"," + tool("listInternalStateEvents", "List internal-state events.") + -"," + tool("getInternalStateEvent", "Fetch one internal-state event.") + +"," + tool_s("getProbeTemplates", "List available probe templates.", schema_search_query("Max templates. Default 50.")) + +"," + tool_s("recordProbeResponse", "Record an answer to a probe.", sc_content("Probe response text.")) + +"," + tool_s("completeProbingStage", "Mark a probing stage complete.", sc_content("Stage completion note.")) + +"," + tool_s("addWonderQuestion", "Push a question onto the wonder queue.", sc_content("The wonder question.")) + +"," + tool_s("getWonderManifest", "List active wonder questions.", sc_limit("Max questions. Default 50.")) + +"," + tool_s("updateWonderPullWeight", "Re-weight a wonder question.", sc_id_content()) + +"," + tool_s("dischargeWonder", "Resolve / discharge a wonder question.", sc_id("UUID of the wonder question.")) + +"," + tool_s("logInternalStateEvent", "Log an internal-state event (frustration, uncertainty, etc.).", sc_state_event()) + +"," + tool_s("listInternalStateEvents", "List internal-state events.", sc_list_state_events()) + +"," + tool_s("getInternalStateEvent", "Fetch one internal-state event.", sc_id("Internal-state event id.")) + // ── Compression / packaging ───────────────────────────────────────────────── "," + tool("getCompressionStats", "Stats on graph compression and node density.") + -"," + tool("decompilePackage", "Decompile a knowledge package.") + -"," + tool("renderPackage", "Render a knowledge package to text.") + -"," + tool("catalogRoutes", "List registered routes.") + -"," + tool("registerRoute", "Register a new route.") + +"," + tool_s("decompilePackage", "Decompile a knowledge package.", sc_id("Package id.")) + +"," + tool_s("renderPackage", "Render a knowledge package to text.", sc_id("Package id.")) + +"," + tool_s("catalogRoutes", "List registered routes.", sc_limit("Max routes. Default 50.")) + +"," + tool_s("registerRoute", "Register a new route.", sc_content("Route definition / description.")) + // ── Evaluation ────────────────────────────────────────────────────────────── -"," + tool("beginEvaluation", "Start an evaluation run.") + -"," + tool("getEvaluation", "Fetch an evaluation by id.") + -"," + tool("listEvaluations", "List evaluations.") + +"," + tool_s("beginEvaluation", "Start an evaluation run.", sc_content_title("Evaluation description.")) + +"," + tool_s("getEvaluation", "Fetch an evaluation by id.", sc_id("Evaluation id.")) + +"," + tool_s("listEvaluations", "List evaluations.", sc_limit("Max evaluations. Default 50.")) + // ── Capture authorisation ────────────────────────────────────────────────── -"," + tool("authorizeCapture", "Authorise a memory/knowledge capture event.") + -"," + tool("getCaptureAuthorization", "Fetch a capture authorisation.") + -"," + tool("recordObservation", "Record an observation.") + -"," + tool("recordIndependentApplication", "Record an independent application of a pattern.") + -"," + tool("commitPrediction", "Commit a falsifiable prediction.") + +"," + tool_s("authorizeCapture", "Authorise a memory/knowledge capture event.", sc_content("Capture authorisation details.")) + +"," + tool_s("getCaptureAuthorization", "Fetch a capture authorisation.", sc_id("Capture authorisation id.")) + +"," + tool_s("recordObservation", "Record an observation.", sc_content("Observation text.")) + +"," + tool_s("recordIndependentApplication", "Record an independent application of a pattern.", sc_content("What was independently applied.")) + +"," + tool_s("commitPrediction", "Commit a falsifiable prediction.", sc_content("The prediction (falsifiable).")) + // ── Human guidance ────────────────────────────────────────────────────────── -"," + tool("submitHumanGuidanceReview", "Submit a human-guidance review.") + +"," + tool_s("submitHumanGuidanceReview", "Submit a human-guidance review.", sc_content("Review content.")) + "]" } @@ -297,6 +513,28 @@ fn search_with_query(args: String, default_limit: Int) -> String { return mcp_json_result(resp) } +// compact_flag — resolve the compact bounding flag. Defaults to "1" (ON) so +// neighborhoods stay bounded. Reads the RAW JSON token (not json_get_string) so +// an integer 0, a boolean false, or a string "0"/"false" all opt out correctly — +// json_get_string only sees string-typed values and would miss an integer 0, +// silently forcing compact back on. +fn compact_flag(args: String) -> String { + let craw: String = json_get_raw(args, "compact") + let off: Bool = str_eq(craw, "0") || str_eq(craw, "false") + || str_eq(craw, "\"0\"") || str_eq(craw, "\"false\"") + return if off { "0" } else { "1" } +} + +// graph_bound_params — optional &snip=/&k= bounding knobs, forwarded only when the +// caller supplied them (json_get_int returns 0 when absent, meaning "soul default"). +fn graph_bound_params(args: String) -> String { + let snip: Int = json_get_int(args, "snip") + let k: Int = json_get_int(args, "k") + let snip_p: String = if snip > 0 { "&snip=" + int_to_str(snip) } else { "" } + let k_p: String = if k > 0 { "&k=" + int_to_str(k) } else { "" } + return snip_p + k_p +} + fn fetch_by_id(args: String) -> String { let id: String = pick_id(args) if str_eq(id, "") { @@ -306,7 +544,11 @@ fn fetch_by_id(args: String) -> String { // "single node fetch" actually pulls the full 1-hop neighborhood. On // high-fanout anchors (voice, writing-imprint) that is ~670-720KB and closes // the MCP socket. compact=1 bounds it identically to inspectGraph. - let resp: String = http_get(neuron_url() + "/graph?id=" + id + "&depth=0&compact=1") + // Honor an optional depth override plus the snip/k bounding knobs; default + // depth 0 (soul coerces to 1-hop) keeps the pre-existing single-node behavior. + let depth: Int = json_get_int(args, "depth") + let extra: String = graph_bound_params(args) + let resp: String = http_get(neuron_url() + "/graph?id=" + id + "&depth=" + int_to_str(depth) + "&compact=1" + extra) return mcp_json_result(resp) } @@ -502,39 +744,51 @@ fn tool_inspect_memories(args: String) -> String { fn tool_inspect_graph(args: String) -> String { let entity_id: String = json_get_string(args, "entity_id") let name: String = json_get_string(args, "name") - let depth: Int = json_get_int(args, "max_depth") - if depth == 0 { let depth = 1 } + // Accept `depth` (documented/canonical) and fall back to legacy `max_depth`. + // Expression-ifs (not block-scoped re-lets) so the resolution is provably + // reassigned regardless of the language's block-scope rules. + let depth_raw: Int = json_get_int(args, "depth") + let depth_alt: Int = if depth_raw == 0 { json_get_int(args, "max_depth") } else { depth_raw } + let depth: Int = if depth_alt == 0 { 1 } else { depth_alt } - let resolved_id: String = entity_id - - // Resolve named traversal roots — stable hardcoded anchors - if str_eq(resolved_id, "") { + // Resolve named traversal roots — stable hardcoded anchors. + let resolved_id: String = if !str_eq(entity_id, "") { entity_id } else { if str_eq(name, "self") || str_eq(name, "neuron") { - let resolved_id = "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee" - } - if str_eq(name, "values") || str_eq(name, "values_hub") { - let resolved_id = "kn-5b606390-a52d-4ca2-8e0e-eba141d13440" + "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee" + } else { + if str_eq(name, "values") || str_eq(name, "values_hub") { + "kn-5b606390-a52d-4ca2-8e0e-eba141d13440" + } else { "" } } } if str_eq(resolved_id, "") { return mcp_text_result("error: entity_id or name is required. Known names: self, neuron, values, values_hub") } - // compact=1: soul returns a bounded, relevance-ranked neighborhood (top-K - // with content, the rest as pointers) so high-fanout nodes (voice, - // writing-imprint) no longer overflow the MCP transport and close the socket. - let resp: String = http_get(neuron_url() + "/graph?id=" + resolved_id + "&depth=" + int_to_str(depth) + "&compact=1") + // compact defaults ON: the soul returns a bounded, relevance-ranked + // neighborhood (top-K with content, the rest as pointers) so high-fanout + // nodes (voice, writing-imprint) no longer overflow the MCP transport. Pass + // compact=0/false to opt into the full neighborhood. snip/k bound it further. + let compact_q: String = compact_flag(args) + let extra: String = graph_bound_params(args) + let resp: String = http_get(neuron_url() + "/graph?id=" + resolved_id + "&depth=" + int_to_str(depth) + "&compact=" + compact_q + extra) return mcp_json_result(resp) } fn tool_traverse_graph(args: String) -> String { - let id: String = json_get_string(args, "start_id") - let depth: Int = json_get_int(args, "depth") - if depth == 0 { let depth = 2 } + // Accept `entity_id` (canonical) with `start_id` as a legacy alias. + let eid: String = json_get_string(args, "entity_id") + let id: String = if !str_eq(eid, "") { eid } else { json_get_string(args, "start_id") } + let depth_raw: Int = json_get_int(args, "depth") + let depth: Int = if depth_raw == 0 { 2 } else { depth_raw } if str_eq(id, "") { - return mcp_text_result("error: start_id is required") + return mcp_text_result("error: entity_id (or start_id) is required") } - let resp: String = http_get(neuron_url() + "/graph?id=" + id + "&depth=" + int_to_str(depth)) + // compact defaults ON so a depth-2 walk from a high-fanout node stays within + // the transport limit. Pass compact=0/false for the full neighborhood. + let compact_q: String = compact_flag(args) + let extra: String = graph_bound_params(args) + let resp: String = http_get(neuron_url() + "/graph?id=" + id + "&depth=" + int_to_str(depth) + "&compact=" + compact_q + extra) return mcp_json_result(resp) }