Skip to content

Commit 6178495

Browse files
committed
feat: enhance tool normalization and injection handling
- Introduced `_server_keys_for_enrichment` to extract and normalize server keys from tools, improving the enrichment process. - Updated `_normalize_tools_list` to utilize the new enrichment function, ensuring tools are enriched with relevant identities based on server keys. - Added `apply_inject_via_overlay` function to streamline the configuration of injection modes for agents, enhancing flexibility in tool setup. - Adjusted tests to reflect changes in tool normalization and injection handling, ensuring consistent behavior across the codebase. - This update aims to improve the robustness and clarity of tool management and injection processes.
1 parent 0d4b833 commit 6178495

6 files changed

Lines changed: 50 additions & 15 deletions

File tree

‎src/cyt/cyt_mcp/catalog.py‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,37 @@ def _normalize_tool(tool: dict[str, Any]) -> dict[str, Any] | None:
215215
return normalized
216216

217217

218+
def _server_keys_for_enrichment(tools: Sequence[Any]) -> list[str]:
219+
keys: set[str] = set()
220+
for item in tools:
221+
if not isinstance(item, dict):
222+
continue
223+
server_key = item.get("server_key")
224+
if isinstance(server_key, str) and server_key.strip():
225+
keys.add(server_key.strip())
226+
try:
227+
from cyt_mcp.config import load_known_mcp_server_keys
228+
229+
keys.update(load_known_mcp_server_keys())
230+
except Exception:
231+
pass
232+
return sorted(keys, key=len, reverse=True)
233+
234+
218235
def _normalize_tools_list(tools: Sequence[Any]) -> list[dict[str, Any]]:
236+
from cyt_mcp.tool_identity import enrich_tool_identity
237+
219238
normalized: list[dict[str, Any]] = []
220239
for item in tools:
221240
if not isinstance(item, dict):
222241
continue
223242
tool = _normalize_tool(item)
224243
if tool is not None:
225244
normalized.append(tool)
226-
return normalized
245+
if not normalized:
246+
return []
247+
server_keys = _server_keys_for_enrichment(normalized)
248+
return [enrich_tool_identity(tool, server_keys) for tool in normalized]
227249

228250

229251
def _filter_tools_by_permissions(

‎src/cyt/skills/catalog.py‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ def _build_registry_from_inline_sources(
629629
inline_sources: list[dict[str, str]],
630630
*,
631631
original_by_hash: dict[str, Path],
632+
allow_ephemeral_paths: bool = False,
632633
) -> list[SkillEntryRef]:
633634
"""Build a skills registry from inline ``{path, content, content_sha256}`` sources."""
634635
if not inline_sources:
@@ -657,7 +658,7 @@ def _build_registry_from_inline_sources(
657658
continue
658659
from cyt.tiers.adapters.skills import is_ephemeral_skill_path
659660

660-
if is_ephemeral_skill_path(str(original_path)):
661+
if not allow_ephemeral_paths and is_ephemeral_skill_path(str(original_path)):
661662
continue
662663
entry = _entry_from_rust_ref(
663664
ref,
@@ -699,10 +700,6 @@ def _build_registry_from_client_skills(
699700

700701
for skill in client_skills:
701702
original_path = Path(skill["path"]).expanduser()
702-
from cyt.tiers.adapters.skills import is_ephemeral_skill_path
703-
704-
if is_ephemeral_skill_path(str(original_path)):
705-
continue
706703
content = skill["content"]
707704
content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
708705
if content_hash in seen_content:
@@ -722,6 +719,7 @@ def _build_registry_from_client_skills(
722719
cfg,
723720
inline_sources,
724721
original_by_hash=original_by_hash,
722+
allow_ephemeral_paths=True,
725723
)
726724

727725

‎src/cyt/testing/inject_via_maps.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22

33
from __future__ import annotations
44

5+
from typing import Any
6+
57
from cyt.config import inject_via_agents
68

79
_agents = inject_via_agents()
810
INJECT_VIA_ALL_HOOK: dict[str, str] = dict.fromkeys(_agents, "hook")
911
INJECT_VIA_DEFAULT: dict[str, str] = dict(_agents)
1012
INJECT_VIA_ALL_PROXY: dict[str, str] = dict.fromkeys(_agents, "proxy")
13+
14+
15+
def apply_inject_via_overlay(config: dict[str, Any], inject_via: dict[str, str]) -> None:
16+
"""Write per-agent ``agents.<agent>.tools.inject_via`` (canonical config path)."""
17+
agents = config.setdefault("agents", {})
18+
for agent, mode in inject_via.items():
19+
agents.setdefault(agent, {}).setdefault("tools", {})["inject_via"] = mode

‎src/tests/conftest.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ def isolated_maintenance_marker_path(config: dict) -> Path:
279279
def _isolate_hook_catalog_state(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
280280
"""Stop background catalog schedulers and clear in-memory hook caches."""
281281
from cyt.cloudflare.catalog import clear_cloudflare_catalog_cache
282+
from cyt.cyt_mcp.catalog import clear_cyt_mcp_catalog_cache
282283
from cyt.executor.http import clear_executor_catalog_cache
283284
from cyt.mcpc.catalog import clear_mcpc_catalog_cache
284285
from cyt.tools.catalog_cache import clear_decomposed_catalog_cache
@@ -311,6 +312,7 @@ def _isolate_hook_catalog_state(monkeypatch: pytest.MonkeyPatch) -> Iterator[Non
311312

312313
def _clear_all_hook_catalog_state() -> None:
313314
clear_cloudflare_catalog_cache()
315+
clear_cyt_mcp_catalog_cache()
314316
clear_executor_catalog_cache()
315317
clear_mcpc_catalog_cache()
316318
clear_master_catalog_cache()

‎src/tests/unit/test_master_catalog.py‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ def test_build_master_tools_stamps_token_count() -> None:
6969

7070
def test_get_master_tool_catalog_concatenates_configured_sources() -> None:
7171
config = load_config()
72-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
7372
config["tools"]["enabled"] = True
7473
config["tools"]["hook"]["tools_from"] = ["mcpc", "executor"]
7574

@@ -91,7 +90,6 @@ def test_get_master_tool_catalog_concatenates_configured_sources() -> None:
9190

9291
def test_get_master_tool_catalog_returns_empty_list_not_none_on_cold_start() -> None:
9392
config = load_config()
94-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
9593
config["tools"]["enabled"] = True
9694
config["tools"]["hook"]["tools_from"] = ["mcpc"]
9795

@@ -103,7 +101,6 @@ def test_get_master_tool_catalog_returns_empty_list_not_none_on_cold_start() ->
103101

104102
def test_rebuild_master_catalog_preserves_prior_source_on_empty_non_blocking_read() -> None:
105103
config = load_config()
106-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
107104
config["tools"]["enabled"] = True
108105
config["tools"]["hook"]["tools_from"] = ["mcpc", "executor"]
109106

@@ -136,7 +133,6 @@ def test_rebuild_master_catalog_preserves_prior_source_on_empty_non_blocking_rea
136133

137134
def test_rebuild_master_catalog_drops_source_when_fingerprint_changes_and_read_empty() -> None:
138135
config = load_config()
139-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
140136
config["tools"]["enabled"] = True
141137
config["tools"]["hook"]["tools_from"] = ["mcpc", "executor"]
142138

@@ -172,7 +168,6 @@ def test_rebuild_master_catalog_drops_source_when_fingerprint_changes_and_read_e
172168

173169
def test_rebuild_master_catalog_clears_stale_tools_when_all_sources_genuinely_empty() -> None:
174170
config = load_config()
175-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
176171
config["tools"]["enabled"] = True
177172
config["tools"]["hook"]["tools_from"] = ["executor"]
178173

@@ -201,7 +196,6 @@ def test_rebuild_master_catalog_clears_stale_tools_when_all_sources_genuinely_em
201196

202197
def test_blocking_get_master_waits_for_in_progress_rebuild() -> None:
203198
config = load_config()
204-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
205199
config["tools"]["enabled"] = True
206200
config["tools"]["hook"]["tools_from"] = ["mcpc"]
207201

‎src/tests/unit/test_tools_hook_setup.py‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pytest
88

99
from cyt.config import load_config
10+
from cyt.testing.inject_via_maps import apply_inject_via_overlay
1011
from cyt.tools import hook_setup
1112
from cyt.tools.hook_setup import prompt_tools_hook_config
1213

@@ -15,7 +16,10 @@ def test_prompt_tools_hook_config_preserves_multi_source_list_when_not_hook_mode
1516
monkeypatch: pytest.MonkeyPatch,
1617
) -> None:
1718
config = load_config()
18-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "proxy", "codex": "proxy"}
19+
apply_inject_via_overlay(
20+
config,
21+
{"cursor": "hook", "claude": "proxy", "codex": "proxy"},
22+
)
1923
config["tools"]["hook"]["tools_from"] = ["mcpc", "executor"]
2024

2125
overlay = prompt_tools_hook_config(config, context="setup", inject_mode="proxy")
@@ -27,7 +31,10 @@ def test_prompt_tools_hook_config_preserves_single_source_as_list_when_not_hook_
2731
monkeypatch: pytest.MonkeyPatch,
2832
) -> None:
2933
config = load_config()
30-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "proxy", "codex": "proxy"}
34+
apply_inject_via_overlay(
35+
config,
36+
{"cursor": "hook", "claude": "proxy", "codex": "proxy"},
37+
)
3138
config["tools"]["hook"]["tools_from"] = ["mcpc"]
3239

3340
overlay = prompt_tools_hook_config(config, context="setup", inject_mode="proxy")
@@ -78,7 +85,10 @@ def test_ensure_tools_hook_file_interactive_prompts_for_missing_cloudflare_url(
7885

7986
config_path = tmp_path / "config.yaml"
8087
config = load_config()
81-
config["pruning"]["inject_via"] = {"cursor": "hook", "claude": "hook", "codex": "hook"}
88+
apply_inject_via_overlay(
89+
config,
90+
{"cursor": "hook", "claude": "hook", "codex": "hook"},
91+
)
8292
config["tools"]["hook"]["tools_from"] = ["cloudflare"]
8393
config["tools"]["hook"]["cloudflare_url"] = ""
8494

0 commit comments

Comments
 (0)