fix(mcp): k8s_enumerate even distribution + load_skill summary paragraphs

- k8s_enumerate: distribute max_urls evenly across namespaces instead of
  truncating first namespaces. Remove cross-product from short_forms.
- load_skill: summary_only now returns title + first paragraph (up to
  500 chars) instead of just the # heading line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ms6RB 2026-03-26 01:51:46 +02:00
parent 86780fa89c
commit 94e4e997d3
3 changed files with 28 additions and 22 deletions

View file

@ -489,11 +489,22 @@ def register_tools(mcp: FastMCP, sandbox: SandboxManager) -> None:
result["failed_skills"] = failed
if summary_only:
# Return just names and first-line descriptions
result["skill_summaries"] = {
name: content.split("\n", 1)[0][:200]
for name, content in loaded_content.items()
}
# Return title + first non-empty paragraph for context
summaries: dict[str, str] = {}
for name, content in loaded_content.items():
lines = content.strip().splitlines()
summary_parts: list[str] = []
for line in lines:
stripped = line.strip()
if not stripped:
if summary_parts and not summary_parts[-1].startswith("#"):
break # end of first paragraph
continue
summary_parts.append(stripped)
if len(summary_parts) >= 4:
break
summaries[name] = " ".join(summary_parts)[:500]
result["skill_summaries"] = summaries
else:
# Apply max_content_length: truncate largest skills first
total_len = sum(len(c) for c in loaded_content.values())

View file

@ -1304,28 +1304,22 @@ def register_analysis_tools(mcp: FastMCP, sandbox: SandboxManager) -> None:
total += 1
by_namespace[ns] = urls
# Also generate short-form names for targets that resolve short names
# Also generate short-form names (service only, no namespace cross-product)
short_forms: list[str] = []
for svc in service_ports:
short_forms.append(f"{scheme}://{svc}")
for ns in ns_list:
short_forms.append(f"{scheme}://{svc}.{ns}")
# Cap output
# Cap output — distribute evenly across namespaces
omitted = 0
if total > max_urls:
per_ns = max(max_urls // len(by_namespace), 1)
new_total = 0
for ns in by_namespace:
if total <= max_urls:
break
excess = total - max_urls
if excess >= len(by_namespace[ns]):
total -= len(by_namespace[ns])
omitted += len(by_namespace[ns])
by_namespace[ns] = []
else:
by_namespace[ns] = by_namespace[ns][:-excess]
omitted += excess
total -= excess
if len(by_namespace[ns]) > per_ns:
omitted += len(by_namespace[ns]) - per_ns
by_namespace[ns] = by_namespace[ns][:per_ns]
new_total += len(by_namespace[ns])
total = new_total
result: dict[str, Any] = {
"total_urls": total,

View file

@ -546,9 +546,10 @@ class TestLoadSkillTool:
assert "skill_summaries" in result
assert "idor" in result["skill_summaries"]
assert "xss" in result["skill_summaries"]
# Summaries should be short strings (first line)
# Summaries should include title + first paragraph (up to 500 chars)
for summary in result["skill_summaries"].values():
assert len(summary) <= 200
assert len(summary) <= 500
assert len(summary) > 10 # not just empty
@pytest.mark.asyncio
async def test_max_content_length_no_truncation_when_under(self, mcp_no_scan):