88import threading
99from typing import TYPE_CHECKING , Any , Literal , cast
1010
11- from jacobian .adapters .mcp .constants import CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
11+ from jacobian .adapters .mcp .constants import (
12+ CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT ,
13+ CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT ,
14+ )
1215from jacobian .bounded_process import bounded_process_cancellation
1316from jacobian .canonical import canonicalize_json
1417from jacobian .capability_service import CapabilityDiscoveryCursorError
@@ -62,54 +65,6 @@ def _mcp_text_json_bytes(value: object) -> bytes:
6265 "materialize named Boolean CNF for finite colorings and forbidden patterns" ,
6366 ),
6467 ),
65- "graph.invariant.maximum_matching.compute" : (
66- (
67- "graph.invariant.maximum_matching.verify" ,
68- "independently replay the stored Tutte-Berge certificate" ,
69- ),
70- ),
71- "graph.invariant.maximum_matching.verify" : (
72- (
73- "graph.invariant.maximum_matching.compute" ,
74- "produce a matching witness and Tutte-Berge certificate" ,
75- ),
76- ),
77- "graph.hamiltonian_path.decide" : (
78- (
79- "graph.hamiltonian_path.verify" ,
80- "independently verify the stored positive or negative decision" ,
81- ),
82- ),
83- "graph.hamiltonian_path.verify" : (
84- (
85- "graph.hamiltonian_path.decide" ,
86- "produce a complete bounded decision and optional path witness" ,
87- ),
88- ),
89- "polynomial.jacobian_syzygy.minimum_degree.compute" : (
90- (
91- "polynomial.jacobian_syzygy.minimum_degree.verify" ,
92- "independently rebuild the graded maps, ranks, minors, and first kernel" ,
93- ),
94- ),
95- "polynomial.jacobian_syzygy.minimum_degree.verify" : (
96- (
97- "polynomial.jacobian_syzygy.minimum_degree.compute" ,
98- "produce the provenance-bound graded rank ledger and kernel witness" ,
99- ),
100- ),
101- "geometry.projective_line_arrangement.flats.materialize" : (
102- (
103- "geometry.projective_line_arrangement.flats.verify" ,
104- "independently rebuild all projective flats and pair accounting" ,
105- ),
106- ),
107- "geometry.projective_line_arrangement.flats.verify" : (
108- (
109- "geometry.projective_line_arrangement.flats.materialize" ,
110- "materialize normalized lines, exact flats, incidences and multiplicities" ,
111- ),
112- ),
11368}
11469
11570
@@ -128,16 +83,27 @@ def _capability_inspection_extensions(
12883 descriptors : dict [str , CapabilityDescriptor ],
12984) -> dict [str , Any ]:
13085 extensions : dict [str , Any ] = {}
131- related = [
86+ related = {
87+ item .capability_id : item .model_dump (mode = "json" )
88+ for item in descriptors [capability_id ].related_capabilities
89+ if item .capability_id in descriptors and item .capability_id != capability_id
90+ }
91+ related .update (
13292 {
133- "capability_id" : related_id ,
134- "relationship" : relationship ,
93+ related_id : {
94+ "capability_id" : related_id ,
95+ "relationship" : relationship ,
96+ }
97+ for related_id , relationship in _RELATED_CAPABILITIES .get (capability_id , ())
98+ if related_id in descriptors
99+ and related_id != capability_id
100+ and related_id not in related
135101 }
136- for related_id , relationship in _RELATED_CAPABILITIES .get (capability_id , ())
137- if related_id in descriptors
138- ]
102+ )
139103 if related :
140- extensions ["related_capabilities" ] = related
104+ extensions ["related_capabilities" ] = [
105+ related [related_id ] for related_id in sorted (related )
106+ ]
141107 if capability_id .startswith (("sat." , "smt." )):
142108 extensions ["synchronous_execution" ] = {
143109 "remote_safe_wall_seconds_max" : 150 ,
@@ -210,16 +176,26 @@ def _discovery_operation_card(
210176 """Add compact decision facts without recommending a research action."""
211177
212178 runtime = descriptor .provider_runtime
213- related = [
179+ related = {
180+ item .capability_id : item .model_dump (mode = "json" )
181+ for item in descriptor .related_capabilities
182+ if item .capability_id in descriptors
183+ and item .capability_id != descriptor .capability_id
184+ }
185+ related .update (
214186 {
215- "capability_id" : related_id ,
216- "relationship" : relationship ,
187+ related_id : {
188+ "capability_id" : related_id ,
189+ "relationship" : relationship ,
190+ }
191+ for related_id , relationship in _RELATED_CAPABILITIES .get (
192+ descriptor .capability_id , ()
193+ )
194+ if related_id in descriptors
195+ and related_id != descriptor .capability_id
196+ and related_id not in related
217197 }
218- for related_id , relationship in _RELATED_CAPABILITIES .get (
219- descriptor .capability_id , ()
220- )
221- if related_id in descriptors
222- ]
198+ )
223199 invocation_example = None
224200 if descriptor .invocation_examples :
225201 example = descriptor .invocation_examples [0 ]
@@ -247,7 +223,7 @@ def _discovery_operation_card(
247223 "provider_availability" : (
248224 runtime .availability .value if runtime is not None else "UNKNOWN"
249225 ),
250- "related_capabilities" : related ,
226+ "related_capabilities" : [ related [ item ] for item in sorted ( related )] ,
251227 ** (
252228 {"invocation_example" : invocation_example }
253229 if invocation_example is not None
@@ -258,6 +234,41 @@ def _discovery_operation_card(
258234 }
259235
260236
237+ def _compact_discovery_relationships (
238+ response : dict [str , Any ],
239+ matches : list [dict [str , Any ]],
240+ ) -> None :
241+ """Remove deterministic suffix links until the discovery response is bounded."""
242+
243+ while (
244+ len (_mcp_text_json_bytes (response )) > CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
245+ ):
246+ for match in reversed (matches ):
247+ related = match .get ("related_capabilities" )
248+ if isinstance (related , list ) and related :
249+ related .pop ()
250+ response ["related_capabilities_truncated" ] = True
251+ response ["truncation_reason" ] = "BYTE_LIMIT"
252+ break
253+ else :
254+ return
255+
256+
257+ def _compact_inspection_relationships (response : dict [str , Any ]) -> None :
258+ """Bound exact-inspection relationships without truncating the descriptor."""
259+
260+ related = response .get ("related_capabilities" )
261+ while (
262+ len (_mcp_text_json_bytes (related ))
263+ > CAPABILITY_INSPECTION_RELATIONSHIPS_BYTE_LIMIT
264+ and isinstance (related , list )
265+ and related
266+ ):
267+ related .pop ()
268+ response ["related_capabilities_truncated" ] = True
269+ response ["truncation_reason" ] = "BYTE_LIMIT"
270+
271+
261272def _discovery_recovery_paths (
262273 request : CapabilityDiscoveryRequest ,
263274 * ,
@@ -313,7 +324,7 @@ def _capability_descriptor_view(
313324 view : CapabilityDescriptionView ,
314325) -> dict [str , Any ]:
315326 if view == "FULL" :
316- return descriptor .model_dump (mode = "json" )
327+ return descriptor .model_dump (mode = "json" , exclude = { "related_capabilities" } )
317328 runtime = descriptor .provider_runtime
318329 if view == "SUMMARY" :
319330 runtime_summary = (
@@ -462,8 +473,13 @@ def _capability_discovery_response(
462473 "recovery_paths_are_unranked" : True ,
463474 "response_byte_limit" : CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT ,
464475 "truncation_reason" : None ,
476+ "related_capabilities_truncated" : False ,
477+ "available_domains_total" : len (discovered_payload ["available_domains" ]),
478+ "available_domains_truncated" : False ,
479+ "match_metadata_truncated" : False ,
465480 }
466481 matches = cast (list [dict [str , Any ]], response ["matches" ])
482+ _compact_discovery_relationships (response , matches )
467483 while (
468484 len (_mcp_text_json_bytes (response )) > CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
469485 and len (matches ) > 1
@@ -473,16 +489,13 @@ def _capability_discovery_response(
473489 response ["next_cursor" ] = matches [- 1 ]["capability_id" ]
474490 response ["truncation_reason" ] = "BYTE_LIMIT"
475491 available_domains = cast (list [str ], response ["available_domains" ])
476- response ["available_domains_total" ] = len (available_domains )
477- response ["available_domains_truncated" ] = False
478492 while (
479493 len (_mcp_text_json_bytes (response )) > CAPABILITY_DISCOVERY_RESPONSE_BYTE_LIMIT
480494 and available_domains
481495 ):
482496 available_domains .pop ()
483497 response ["available_domains_truncated" ] = True
484498 response ["truncation_reason" ] = "BYTE_LIMIT"
485- response ["match_metadata_truncated" ] = False
486499 compact_fields = (
487500 "tags" ,
488501 "matched_on" ,
0 commit comments