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.
- java_codebase_rag-0.2.0.dist-info/METADATA +228 -0
- {java_codebase_rag-0.1.0.dist-info → java_codebase_rag-0.2.0.dist-info}/RECORD +9 -9
- mcp_hints.py +647 -463
- mcp_v2.py +57 -28
- server.py +5 -6
- java_codebase_rag-0.1.0.dist-info/METADATA +0 -818
- {java_codebase_rag-0.1.0.dist-info → java_codebase_rag-0.2.0.dist-info}/WHEEL +0 -0
- {java_codebase_rag-0.1.0.dist-info → java_codebase_rag-0.2.0.dist-info}/entry_points.txt +0 -0
- {java_codebase_rag-0.1.0.dist-info → java_codebase_rag-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {java_codebase_rag-0.1.0.dist-info → java_codebase_rag-0.2.0.dist-info}/top_level.txt +0 -0
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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),
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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),
|
|
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,
|
|
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}`",
|
|
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
|
-
|
|
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),
|
|
1120
|
+
return DescribeOutput(success=False, message=str(exc), advisories=[])
|
|
1098
1121
|
except Exception as exc:
|
|
1099
|
-
return DescribeOutput(success=False, message=str(exc),
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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),
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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
|
|
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'); "
|