fix(mcp-wrapper): declare real input schemas so tool args actually bite #150
Reference in New Issue
Block a user
Delete Branch "fix-mcp-wrapper-tool-schemas"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Every Neuron MCP tool advertised an empty
inputSchema({"type":"object","properties":{}}). MCP clients therefore sent no arguments, so:inspectGraph,traverseGraph,searchGraph,retrieveKnowledge) ignoredentity_id/depth/query/limitand returned the full neighborhood — 480–775KB per call, over the transport's token limit. The documented traversalinspectGraph(entity_id=…, depth=1)did not deliver a bounded, targeted result.inspectGraphalso read only the legacymax_depth, so adeptharg was silently dropped.forget,linkEntities,evolveMemory,remember, …) had no way to receivenode_id/from_id+to_id/id+content— so they couldn't target a specific node/edge.Fix
neuron-api.el) already accepts. 76 of 87 tools now declare params; the remaining 11 genuinely take no inputs (beginSession,getInstructions,compileCtx,rebuildGraph,runStructuralAudit,checkEvents,processEvents,getSelfModel,computeAuthenticityScore,getCultivationStatus,getCompressionStats).inspectGraphnow honorsdepth(fell back tomax_depth),compact(default on),snip,k; resolvesnameroots via a provably-reassigning expression-if.traverseGraphacceptsentity_id(aliasstart_id) and defaultscompact=1so a depth-2 walk stays bounded (previously it sent no compact → full dump).retrieveKnowledgeforwardsid/keyplus optionaldepth/snip/k; keepscompact=1.compact_flag()reads the raw JSON token so an integer0,false, or"0"opts out correctly (json_get_stringcan't see an integer and would silently force compact back on).Builds on and preserves PR #149's relevance-ranked
compactprojection (kept on by default for graph neighborhoods).Proof (throwaway build over a COPY of the engram; live :7770/:7779/~/.neuron untouched)
tools/listnow advertises params, e.g.inspectGraph: [entity_id, name, entity_type, depth, compact, snip, k].Targeted calls now bite (via the throwaway wrapper → throwaway soul over a 13k-node engram copy):
inspectGraph(kn-10fa60db…, depth=1)inspectGraph(fcd0ebc2… deg=357)inspectGraph(entity_id, depth=3, compact=0, snip=150, k=4)→/graph?id=…&depth=3&compact=0&snip=150&k=4;name=self→ resolves to the anchor id;traverseGraphdefaultscompact=1.searchGraphhonorslimit(3 args → 3 results / 15KB; 15 → 15 results / 46KB); distinct queries → distinct results.retrieveKnowledgehonorsid(different id → different bounded node).linkEntities(from_id=A, to_id=B, relation=proof_edge_xyz)created exactly the A→B edge; control node untouched.forget(node_id=Q)tombstoned exactly Q (atombstonesmarker appears on Q, content = Q's id); control node P had none.Guardrails
Branch + throwaway proof only. No merge, no live cutover, no deploy. Live wrapper/soul binaries and
~/.neuronwere never touched. Base isfix/bound-session-payload(stacked on PR #149).The cognitive-graph and write tools advertised an empty inputSchema ({"properties":{}}), so MCP clients never sent entity_id/depth/query/ from_id/node_id etc. Graph reads fell back to the full neighborhood (480-775KB, over transport limits) and write tools (forget, linkEntities, evolveMemory) had no way to target a node. - Declare per-tool JSON-Schemas matching the params each soul handler already accepts (76 of 87 tools; 11 are genuinely param-less). - Read + forward the declared args: inspectGraph now honors depth (was reading only legacy max_depth), compact (default on), snip, k; traverseGraph accepts entity_id and defaults compact on so a depth-2 walk stays bounded; retrieveKnowledge forwards depth/snip/k. - compact_flag() reads the raw JSON token so an integer 0 / false / "0" opts out correctly (json_get_string could not see an integer and silently forced compact back on). Builds on PR #149's compact projection; keeps the relevance-ranked bound on by default for graph neighborhoods.auto_term_try_slot now passes the WM node's ID to engram_salient_term() instead of passing its label to a first-word extractor. The runtime scores every candidate token in the node's text and returns the best one, falling back from a sentinel label ("memory:remembered") to content — which is the only reason Memory nodes are visible to the extractor at all. They dominate working memory, and dynamic seeding had been dead for 50+ consecutive scans because of it. Policy stays here: node-type filter, df thresholds, stopword list. The runtime measures, the soul decides — same split as engram_label_df. The stopword list stays, and not as belt-and-braces. An earlier draft assumed the min_df floor would subsume it based on 08-03's finding that function words have df 0 in labels. Re-measured under word-boundary df: about:2, whole:1, them:2 — they clear a floor of 1. What keeps them from winning is the argmax, not the floor. The old extractor and its five guards are retained as auto_term_try_slot_legacy, unreferenced, so the reasoning behind each guard stays readable next to what replaced it. Delete once the new path has a month of live telemetry. Live after restart: auto_term producing DRIFT, Wrote; empty streak reset to 0 and holding; activation counts 123-281, within the normal band, no flood.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.