java-codebase-rag 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mcp_v2.py CHANGED
@@ -31,7 +31,7 @@ from index_common import SBERT_MODEL
31
31
  from java_codebase_rag.config import resolved_sbert_model_for_process_env
32
32
  from java_ontology import EDGE_SCHEMA, ResolveReason
33
33
  from kuzu_queries import KuzuGraph, OVERRIDE_AXIS_COMPOSED_EDGE_TYPES
34
- from mcp_hints import MCP_HINTS_FIELD_DESCRIPTION, generate_hints
34
+ from mcp_hints import generate_hints, MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION
35
35
  from search_lancedb import TABLES, run_search
36
36
 
37
37
  DeclarationSymbolKind = Literal["class", "interface", "enum", "record", "annotation", "method", "constructor"]
@@ -175,6 +175,14 @@ class EdgeFilter(BaseModel):
175
175
  _NODEFILTER_FIELD_ORDER: tuple[str, ...] = tuple(NodeFilter.model_fields.keys())
176
176
  _EDGEFILTER_FIELD_ORDER: tuple[str, ...] = tuple(EdgeFilter.model_fields.keys())
177
177
 
178
+
179
+ class StructuredHint(BaseModel):
180
+ label: str = ""
181
+ tool: Literal["search", "find", "describe", "neighbors", "resolve"]
182
+ args: dict[str, Any]
183
+ actionable: bool = True
184
+ reason: str = ""
185
+
178
186
  # Populated EdgeFilter field -> EDGE_SCHEMA attribute name used in Cypher pushdown.
179
187
  _EDGEFILTER_FIELD_TO_ATTR: dict[str, str] = {
180
188
  "min_confidence": "confidence",
@@ -340,6 +348,10 @@ def _edgefilter_applicability_error(edge_types: list[str], ef: EdgeFilter) -> st
340
348
  return None
341
349
 
342
350
 
351
+ def _to_structured_hints(raw: list[Any]) -> list[StructuredHint]:
352
+ return [StructuredHint(label=h.label, tool=h.tool, args=h.args, actionable=h.actionable, reason=h.reason) for h in raw]
353
+
354
+
343
355
  def _coerce_edge_filter(
344
356
  value: EdgeFilter | dict[str, Any] | str | None,
345
357
  ) -> EdgeFilter | dict[str, Any] | None:
@@ -452,7 +464,8 @@ class SearchOutput(BaseModel):
452
464
  default=None,
453
465
  description="Echoed from the request — the page offset the server applied. None on success=False.",
454
466
  )
455
- hints: list[str] = Field(default_factory=list, description=MCP_HINTS_FIELD_DESCRIPTION)
467
+ advisories: list[str] = Field(default_factory=list, description="Pure informational text with no tool call suggestion")
468
+ hints_structured: list[StructuredHint] = Field(default_factory=list, description=MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION)
456
469
 
457
470
 
458
471
  class FindOutput(BaseModel):
@@ -467,14 +480,16 @@ class FindOutput(BaseModel):
467
480
  default=None,
468
481
  description="Echoed from the request — the page offset the server applied. None on success=False.",
469
482
  )
470
- hints: list[str] = Field(default_factory=list, description=MCP_HINTS_FIELD_DESCRIPTION)
483
+ advisories: list[str] = Field(default_factory=list, description="Pure informational text with no tool call suggestion")
484
+ hints_structured: list[StructuredHint] = Field(default_factory=list, description=MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION)
471
485
 
472
486
 
473
487
  class DescribeOutput(BaseModel):
474
488
  success: bool
475
489
  record: NodeRecord | None = None
476
490
  message: str | None = None
477
- hints: list[str] = Field(default_factory=list, description=MCP_HINTS_FIELD_DESCRIPTION)
491
+ advisories: list[str] = Field(default_factory=list, description="Pure informational text with no tool call suggestion")
492
+ hints_structured: list[StructuredHint] = Field(default_factory=list, description=MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION)
478
493
 
479
494
 
480
495
  class NeighborsOutput(BaseModel):
@@ -485,7 +500,8 @@ class NeighborsOutput(BaseModel):
485
500
  default_factory=list,
486
501
  description="Echo of neighbors(edge_types=...) from the request; empty when success=False.",
487
502
  )
488
- hints: list[str] = Field(default_factory=list, description=MCP_HINTS_FIELD_DESCRIPTION)
503
+ advisories: list[str] = Field(default_factory=list, description="Pure informational text with no tool call suggestion")
504
+ hints_structured: list[StructuredHint] = Field(default_factory=list, description=MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION)
489
505
 
490
506
 
491
507
  ResolveStatus = Literal["one", "many", "none"]
@@ -555,7 +571,8 @@ class ResolveOutput(BaseModel):
555
571
  candidates: list[ResolveCandidate] = Field(default_factory=list)
556
572
  message: str | None = None
557
573
  resolved_identifier: str | None = None
558
- hints: list[str] = Field(default_factory=list, description=MCP_HINTS_FIELD_DESCRIPTION)
574
+ advisories: list[str] = Field(default_factory=list, description="Pure informational text with no tool call suggestion")
575
+ hints_structured: list[StructuredHint] = Field(default_factory=list, description=MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION)
559
576
 
560
577
 
561
578
  def _node_kind_from_id(
@@ -873,16 +890,16 @@ def search_v2(
873
890
  return SearchOutput(
874
891
  success=False,
875
892
  message=_filter_validation_error_message(exc),
876
- hints=[],
893
+ advisories=[],
877
894
  limit=None,
878
895
  offset=None,
879
896
  )
880
897
  if nf and (err := _nodefilter_applicability_error("symbol", nf)):
881
898
  _log_fail_loud("applicability")
882
- return SearchOutput(success=False, message=err, hints=[], limit=None, offset=None)
899
+ return SearchOutput(success=False, message=err, advisories=[], limit=None, offset=None)
883
900
  if nf and (err := _validate_no_wildcards(nf)):
884
901
  _log_fail_loud("wildcard")
885
- return SearchOutput(success=False, message=err, hints=[], limit=None, offset=None)
902
+ return SearchOutput(success=False, message=err, advisories=[], limit=None, offset=None)
886
903
  model_name = resolved_sbert_model_for_process_env(SBERT_MODEL)
887
904
  device = os.environ.get("SBERT_DEVICE") or None
888
905
  model = _get_sentence_transformer(model_name, device)
@@ -920,15 +937,17 @@ def search_v2(
920
937
  "limit": limit,
921
938
  "offset": offset,
922
939
  }
940
+ raw_struct, raw_advisories = generate_hints("search", hint_payload)
923
941
  return SearchOutput(
924
942
  success=True,
925
943
  results=hits,
926
944
  limit=limit,
927
945
  offset=offset,
928
- hints=generate_hints("search", hint_payload),
946
+ advisories=raw_advisories,
947
+ hints_structured=_to_structured_hints(raw_struct),
929
948
  )
930
949
  except Exception as exc:
931
- return SearchOutput(success=False, message=str(exc), hints=[], limit=None, offset=None)
950
+ return SearchOutput(success=False, message=str(exc), advisories=[], limit=None, offset=None)
932
951
 
933
952
 
934
953
  def find_v2(
@@ -950,16 +969,16 @@ def find_v2(
950
969
  return FindOutput(
951
970
  success=False,
952
971
  message=_filter_validation_error_message(exc),
953
- hints=[],
972
+ advisories=[],
954
973
  limit=None,
955
974
  offset=None,
956
975
  )
957
976
  if err := _nodefilter_applicability_error(kind, nf):
958
977
  _log_fail_loud("applicability")
959
- return FindOutput(success=False, message=err, hints=[], limit=None, offset=None)
978
+ return FindOutput(success=False, message=err, advisories=[], limit=None, offset=None)
960
979
  if err := _validate_no_wildcards(nf):
961
980
  _log_fail_loud("wildcard")
962
- return FindOutput(success=False, message=err, hints=[], limit=None, offset=None)
981
+ return FindOutput(success=False, message=err, advisories=[], limit=None, offset=None)
963
982
  fetch_cap = int(limit) + int(offset) + 1
964
983
  if kind == "symbol":
965
984
  where, params = _symbol_where_from_filter(nf)
@@ -1009,15 +1028,17 @@ def find_v2(
1009
1028
  "filter": filter_dump,
1010
1029
  "has_more_results": has_more_results,
1011
1030
  }
1031
+ raw_struct, raw_advisories = generate_hints("find", hint_payload)
1012
1032
  return FindOutput(
1013
1033
  success=True,
1014
1034
  results=refs,
1015
1035
  limit=limit,
1016
1036
  offset=offset,
1017
- hints=generate_hints("find", hint_payload),
1037
+ advisories=raw_advisories,
1038
+ hints_structured=_to_structured_hints(raw_struct),
1018
1039
  )
1019
1040
  except Exception as exc:
1020
- return FindOutput(success=False, message=str(exc), hints=[], limit=None, offset=None)
1041
+ return FindOutput(success=False, message=str(exc), advisories=[], limit=None, offset=None)
1021
1042
 
1022
1043
 
1023
1044
  _DESCRIBE_UCS_ID_MESSAGE = (
@@ -1061,10 +1082,10 @@ def describe_v2(
1061
1082
  )
1062
1083
  kind = _resolve_node_kind(g, node_id)
1063
1084
  if kind == "unresolved_call_site":
1064
- return DescribeOutput(success=False, message=_DESCRIBE_UCS_ID_MESSAGE, hints=[])
1085
+ return DescribeOutput(success=False, message=_DESCRIBE_UCS_ID_MESSAGE, advisories=[])
1065
1086
  row = _load_node_record(g, node_id, kind)
1066
1087
  if row is None:
1067
- return DescribeOutput(success=False, message=f"No node found for `{node_id}`", hints=[])
1088
+ return DescribeOutput(success=False, message=f"No node found for `{node_id}`", advisories=[])
1068
1089
  ref = _node_ref_from_row(kind, row)
1069
1090
  edge_summary = _edge_summary_for_node(g, node_id, kind=kind, row=row)
1070
1091
  data = dict(row)
@@ -1087,16 +1108,18 @@ def describe_v2(
1087
1108
  f"java-codebase-rag unresolved-calls list --method-id {node_id} for the full list"
1088
1109
  )
1089
1110
  record = NodeRecord(id=ref.id, kind=kind, fqn=ref.fqn, data=data, edge_summary=edge_summary)
1111
+ raw_struct, raw_advisories = generate_hints("describe", {"success": True, "record": record.model_dump()})
1090
1112
  return DescribeOutput(
1091
1113
  success=True,
1092
1114
  record=record,
1093
1115
  message=hint_message,
1094
- hints=generate_hints("describe", {"success": True, "record": record.model_dump()}),
1116
+ advisories=raw_advisories,
1117
+ hints_structured=_to_structured_hints(raw_struct),
1095
1118
  )
1096
1119
  except ValueError as exc:
1097
- return DescribeOutput(success=False, message=str(exc), hints=[])
1120
+ return DescribeOutput(success=False, message=str(exc), advisories=[])
1098
1121
  except Exception as exc:
1099
- return DescribeOutput(success=False, message=str(exc), hints=[])
1122
+ return DescribeOutput(success=False, message=str(exc), advisories=[])
1100
1123
 
1101
1124
 
1102
1125
  def _resolve_validate_identifier(raw: str) -> tuple[str | None, str | None]:
@@ -1415,7 +1438,11 @@ def _resolve_finalize_success(
1415
1438
  "path_prefix_seed": path_prefix_seed,
1416
1439
  "target_service_seed": target_service_seed,
1417
1440
  }
1418
- out = out.model_copy(update={"hints": generate_hints("resolve", hint_payload)})
1441
+ raw_struct, raw_advisories = generate_hints("resolve", hint_payload)
1442
+ out = out.model_copy(update={
1443
+ "advisories": raw_advisories,
1444
+ "hints_structured": _to_structured_hints(raw_struct),
1445
+ })
1419
1446
  _resolve_assert_invariants(out)
1420
1447
  return out
1421
1448
 
@@ -1432,7 +1459,7 @@ def resolve_v2(
1432
1459
  success=False,
1433
1460
  status="none",
1434
1461
  message=err,
1435
- hints=[],
1462
+ advisories=[],
1436
1463
  resolved_identifier=None,
1437
1464
  )
1438
1465
  _resolve_assert_invariants(out)
@@ -1463,7 +1490,7 @@ def resolve_v2(
1463
1490
  success=False,
1464
1491
  status="none",
1465
1492
  message=str(exc),
1466
- hints=[],
1493
+ advisories=[],
1467
1494
  resolved_identifier=None,
1468
1495
  )
1469
1496
  _resolve_assert_invariants(out)
@@ -1700,7 +1727,7 @@ def neighbors_v2(
1700
1727
  return NeighborsOutput(
1701
1728
  success=False,
1702
1729
  message=_filter_validation_error_message(exc),
1703
- hints=[],
1730
+ advisories=[],
1704
1731
  requested_edge_types=[],
1705
1732
  )
1706
1733
  try:
@@ -1715,7 +1742,7 @@ def neighbors_v2(
1715
1742
  return NeighborsOutput(
1716
1743
  success=False,
1717
1744
  message=_filter_validation_error_message(exc),
1718
- hints=[],
1745
+ advisories=[],
1719
1746
  requested_edge_types=[],
1720
1747
  )
1721
1748
  except ValueError as exc:
@@ -1945,13 +1972,15 @@ def neighbors_v2(
1945
1972
  "unresolved_count": unresolved_count,
1946
1973
  "calls_row_count": calls_row_count,
1947
1974
  }
1975
+ raw_struct, raw_advisories = generate_hints("neighbors", neigh_payload)
1948
1976
  return NeighborsOutput(
1949
1977
  success=True,
1950
1978
  results=sliced,
1951
1979
  requested_edge_types=requested_edge_types,
1952
- hints=generate_hints("neighbors", neigh_payload),
1980
+ advisories=raw_advisories,
1981
+ hints_structured=_to_structured_hints(raw_struct),
1953
1982
  )
1954
1983
  except ValidationError:
1955
1984
  raise
1956
1985
  except Exception as exc:
1957
- return NeighborsOutput(success=False, message=str(exc), hints=[], requested_edge_types=[])
1986
+ return NeighborsOutput(success=False, message=str(exc), advisories=[], requested_edge_types=[])
server.py CHANGED
@@ -341,7 +341,7 @@ def create_mcp_server() -> FastMCP:
341
341
  "structured DSL inside `query`; structured predicates belong in `find`. "
342
342
  "For identifier-shaped lookups (FQN, id prefix, route/client identifiers, …), use `resolve` first; "
343
343
  "use `search` for natural-language or ranked fuzzy discovery. "
344
- "Successful responses echo `limit`/`offset` and may include `hints` (advisory next-step strings)."
344
+ "Successful responses echo `limit`/`offset` and may include `hints_structured` (tool call suggestions with `reason` field) and `advisories` (pure informational text)."
345
345
  ),
346
346
  )
347
347
  async def search(
@@ -390,7 +390,7 @@ def create_mcp_server() -> FastMCP:
390
390
  "module, source_layer, producer_kind, topic_prefix. "
391
391
  "Wildcards in prefix fields are rejected. An empty filter (`{}`) or `filter=None` means no predicate (all nodes of "
392
392
  "that kind; use pagination). Unknown keys or inapplicable populated fields return success=false. "
393
- "Successful responses echo `limit`/`offset` and may include `hints` (advisory next-step strings)."
393
+ "Successful responses echo `limit`/`offset` and may include `hints_structured` (tool call suggestions with `reason` field) and `advisories` (pure informational text)."
394
394
  ),
395
395
  )
396
396
  async def find(
@@ -426,7 +426,7 @@ def create_mcp_server() -> FastMCP:
426
426
  "Pass `id` for any kind, or exact `fqn` for Symbol lookup (`id` wins when both are set). "
427
427
  "`describe(fqn=…)` keeps the first graph row when multiple symbols share that FQN; when an FQN may collide, "
428
428
  "prefer `resolve(identifier=…, hint_kind='symbol')` first, then `describe(id=…)` on the chosen node. "
429
- "Successful responses may include `hints` (advisory next-step strings)."
429
+ "Successful responses may include `hints_structured` (tool call suggestions with `reason` field) and `advisories` (pure informational text)."
430
430
  ),
431
431
  )
432
432
  async def describe(
@@ -460,8 +460,7 @@ def create_mcp_server() -> FastMCP:
460
460
  "Optional `edge_filter` requires edge_types=['CALLS'] only (no composed dot-keys or extra stored "
461
461
  "labels); projects the ordered CALLS stream by edge attributes (min_confidence, strategies, "
462
462
  "callee_declaring_role). Wildcards in prefix fields are rejected. Unknown filter keys return success=false. "
463
- "Successful responses echo `requested_edge_types` and may include `hints` (advisory next-step strings; "
464
- "empty results may include EDGE_SCHEMA-driven traversal hints). "
463
+ "Successful responses echo `requested_edge_types` and may include `hints_structured` (tool call suggestions with `reason` field) and `advisories` (pure informational text). "
465
464
  "Each edge's `attrs.strategy` indicates resolution quality (brownfield/fallback vs primary paths)."
466
465
  ),
467
466
  )
@@ -546,7 +545,7 @@ def create_mcp_server() -> FastMCP:
546
545
  "status=one (single node), many (≥2 ranked candidates with reason), or none "
547
546
  "(no match — fall back to search(query=...) for natural language or fuzzy text). "
548
547
  "Optional hint_kind narrows to symbol, route, client, or producer. "
549
- "Successful responses may include advisory hints (same contract as other v2 tools). "
548
+ "Successful responses may include hints_structured (tool call suggestions with `reason` field) and advisories (pure informational text) — same contract as other v2 tools. "
550
549
  "Malformed empty/whitespace identifier returns success=false. "
551
550
  "Examples: resolve('com.foo.Bar', hint_kind='symbol'); "
552
551
  "resolve('GET /api/v1/customers', hint_kind='route'); "