Skip to content

feat(skills-audit): flag hardcoded MCP tool names whose server is installed under another prefix - #801

Merged
CSalcedoDataBI merged 15 commits into
nextfrom
issue-758-feat-skills-audit-flag-skills-that-call
Oct 24, 2026
Merged

CSalcedoDataBI merged 15 commits into
nextfrom
issue-758-feat-skills-audit-flag-skills-that-call

Conversation

@CSalcedoDataBI

@CSalcedoDataBI CSalcedoDataBI commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #758

What

skills-audit gets a new static finding, mcp-tool-name. It catches a skill whose SKILL.md or references/ hardcode an MCP tool name under a prefix the agent does not have. The typical case is mcp__powerbi-modeling-mcp__connection_operations while the server comes from a plugin as mcp__plugin_fabric-skills_powerbi-modeling-mcp__…, or the reverse. The finding gives the name that works on this machine (in a local-only working field; the detail that may be filed names only the install form) and recommends the server-agnostic wording ("the powerbi-modeling-mcp connection_operations tool").

  • Get-McpServerNames.ps1 (new) lists the prefixes the agent really sees. It reads ~/.claude.json (user and per-project entries), the project .mcp.json and the enabled plugins' manifests. It skips what is not loaded:

    • disabled plugins (user < project < local settings);
    • project-scope installs of another project;
    • disabledMcpServers;
    • unapproved or rejected .mcp.json servers.

    Names are normalized the way Claude Code builds tool names. It does not call claude mcp list, because that command health-checks every server and does not show the plugin prefix.

  • Invoke-SkillAudit.ps1 checks these forms: mcp__<prefix>__<tool>, __* and the bare mcp__<server> used in allowed-tools. It reports only when the server IS available under another prefix: a server that is not installed says nothing. It does not flag:

    • a plugin skill that names its own plugin prefix;
    • a skill that already gives the working name too.

    Copies of a skill are folded into one finding. A discovery failure skips only this check. -McpServers injects the prefix set.

Tests

tests/agentic-board/SkillAudit.McpToolNames.Tests.ps1 has 54 tests, all on fixtures. The audit's own discovery path is driven through a stub copy of the scripts, never the live config. A live run on the maintainer's machine flagged real skills that hardcode mcp__powerbi-modeling-mcp__* where that server comes from a plugin.

Closes #758

🤖 Generated with Claude Code

CSalcedoDataBI and others added 5 commits October 23, 2026 15:09
…talled under another prefix (#758)

Invoke-SkillAudit gains an mcp-tool-name finding: a skill that writes
mcp__<server>__<tool> while the agent has that server as
mcp__plugin_<plugin>_<server>__ (or the reverse) gets the working name
and the server-agnostic wording to use instead. Get-McpServerNames.ps1
lists the real prefixes from ~/.claude.json, the project's .mcp.json
and enabled plugins' manifests. Pester tests run on fixtures only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… settings and manifest shapes (#758)

Review round 1: pick the newest install by DateTime (not its MM/dd/yyyy
text), count project/local installs only in their project, merge
enabledPlugins user < project < local, resolve -Root against the
PowerShell location, read array-valued plugin.json mcpServers, and
normalize names the way Claude Code builds tool names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… servers that are not loaded (#758)

Review round 2: read config -AsHashtable (a case-variant project key used
to drop all of ~/.claude.json), apply disabledMcpServers and .mcp.json
approval, and skip a plugin skill's reference to its own plugin prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t already give the working name (#758)

Review round 3: a discovery error now warns and skips only the
mcp-tool-name check; a skill listing both install forms is not flagged;
the self-reference test carries a control so it cannot pass vacuously.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r-only references, harden tests (#758)

Review round 4: discovered records keep their server name instead of a
lossy re-split; a referenced plugin prefix is split on the known server
names; the bare mcp__<server> form of allowed-tools is checked. Tests now
drive the audit's own discovery through a stub, prove an empty
-McpServers never falls back to it, cover the wrapped .mcp.json shape and
assert no local path reaches a finding detail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Review (sha 1f06ff3) — check mcp-tool-name en skills-audit + Get-McpServerNames.ps1.

En general está bien: es solo lectura, no expone valores de config (solo nombres de servidores), degrada con Write-Warning si falla la discovery y solo reporta cuando existe un nombre alternativo que funciona. No veo bugs bloqueantes ni problemas de seguridad. Observaciones menores:

  1. Requisito de PowerShell 7: ConvertFrom-Json -AsHashtable (Read-Json en Get-McpServerNames.ps1) no existe en Windows PowerShell 5.1. Ahí Read-Json capturaría el error y emitiría un warning por archivo, dejando la lista vacía y desactivando el check casi en silencio. Conviene #requires -Version 7 o un warning explícito.
  2. Ambigüedad de plugin_<plugin>_<server>: con -McpServers (strings sueltos) el split se hace en el primer _, así que un plugin con _ en el nombre produce un server incorrecto. Está comentado en el código; un test que fije ese comportamiento evitaría regresiones.
  3. Mantenibilidad: el bloque nuevo en Invoke-SkillAudit.ps1 (~75 líneas, con Resolve-RefServer definida dentro de un if) sería más legible extraído a función o script propio, como ya se hizo con Get-McpServerNames.ps1.

Tests: la cobertura nueva (SkillAudit.McpToolNames.Tests.ps1) parece amplia; no la ejecuté en esta revisión.

…enabledPlugins true and honors CLAUDE_CONFIG_DIR (#758)

Review round 5: tools under one hardcoded prefix fold into one finding;
a skill giving the working prefix in any form is not flagged; working
prefixes are de-duplicated. Discovery counts only plugins enabledPlugins
sets to true, reads CLAUDE_CONFIG_DIR, and a stale plugin entry costs
that plugin only. Tests: positive controls for every negative case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Sin hallazgos bloqueantes. Revisé Get-McpServerNames.ps1, el check mcp-tool-name en Invoke-SkillAudit.ps1, la doc de SKILL.md y los tests. No ejecuté los tests de Pester.

  • El descubrimiento es de solo lectura. No hay inyección ni secretos, porque solo lee JSON de configuración y nunca ejecuta su contenido.
  • Si falla el descubrimiento, solo se omite este check y se emite un warning.
  • La resolución de plugin_<plugin>_<server> con _ en el nombre del plugin y el filtro de plugins/servidores no cargados están bien cubiertos por los tests.
  • Observación menor, no bloqueante: -AsHashtable requiere PowerShell 7+. Conviene confirmar que Invoke-SkillAudit.ps1 ya exige esa versión; si no, falla con un mensaje poco claro.

…if the MCP check ran (#758)

Review round 6: a plugin prefix is split only at the kebab-case boundary
(a suffix guess reported servers that are not installed); markdown beside
SKILL.md is scanned; summary.mcpCheck = ran | no-servers |
discovery-failed; the remediation covers literal allowed-tools lists;
enableAllProjectMcpServers follows the most specific level. Tests pin
CLAUDE_CONFIG_DIR plugins/settings and no longer touch C:/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Review (sha 0ffda07)

Sin hallazgos bloqueantes. El check mcp-tool-name y Get-McpServerNames.ps1 son solo lectura, no ejecutan nada externo y manejan bien los fallos (la discovery falla aislada, JSON ilegible se omite con warning). Los tests cubren los casos principales.

Observaciones menores, no bloqueantes:

  • Con -McpServers los prefijos de plugin se separan en el primer _. Un plugin con _ en el nombre se resuelve mal, aunque la discovery real no tiene ese problema. Conviene dejarlo documentado.
  • SKILL.md y el comentario de summary quedan claros. Considera un test para mcpCheck = 'discovery-failed' si todavía no existe.

…e docs, rank the project entry as local (#758)

Review round 7: the prefix no longer absorbs the __ separator, so
mcp__srv__<tool> is flagged; README/CHANGELOG/CONTRIBUTING/LICENSE beside
SKILL.md are not scanned; mcpCheck is initialized; the ~/.claude.json
project entry ranks after the settings files for .mcp.json approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Revisé el diff (Get-McpServerNames.ps1, el check mcp-tool-name en Invoke-SkillAudit.ps1, SKILL.md y los tests Pester): sin hallazgos bloqueantes. Es solo lectura, sin secretos ni ejecución de entrada externa, y los tests usan fixtures sin tocar la config real. Cubren los casos límite principales: prefijo de plugin, placeholders, README ignorado, allowed-tools y fallo de discovery. No ejecuté los tests Pester en esta revisión.

…e config errors fail loudly (#758)

Review round 8: prefixes compare with exact case; mcp__srv, mcp__srv__
and mcp__srv__* are one name; partial wildcards keep their star; mcp__
needs a word boundary; repo docs beside SKILL.md are skipped; the own
plugin name is also read from a source tree's manifest. Discovery throws
when a core config file is unreadable instead of returning a partial set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Review (sha a7fd0f5): sin hallazgos bloqueantes.

La lógica de descubrimiento y de detección (mcp-tool-name) es coherente: solo lectura, sin shell-out, sin secretos ni inyección, y los errores de config core fallan de forma explícita. Los tests nuevos (~440 líneas) cubren los casos principales.

Notas menores, no bloqueantes:

  • ConvertFrom-Json -AsHashtable exige PowerShell 7+. En 5.1 la lectura de un archivo requerido lanza, y el check se salta con discovery-failed. Conviene documentar el requisito o agregar un test para ese caso.
  • Resolve-RefServer asume plugins en kebab-case (primer _ después de plugin_). Es una limitación aceptada y documentada en el comentario, pero un plugin con _ en el nombre en una referencia hardcodeada no se detectará.

…exact-case refs; placeholder covers plugin forms (#758)

Review round 9: a plugin skill's own prefix is skipped only when none of
that plugin's servers are loaded; refs use an ordinal dictionary; a
mcp__plugin_<plugin>_srv placeholder counts as documenting the plugin
install; a source-tree manifest name replaces the folder name. Tests add
controls for these and the inline plugin.json mcpServers shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Sin hallazgos bloqueantes. Revisé el diff leyéndolo, sin ejecutar los Pester (el entorno no me dejó correr pwsh).

  • Seguridad: Get-McpServerNames.ps1 es de solo lectura y emite únicamente nombres de servidores, no valores de config. ~/.claude.json puede contener secretos, pero no se vuelcan. Los mensajes de error solo incluyen la ruta y el mensaje de la excepción.
  • Lógica: el manejo de -AsHashtable (claves que difieren solo en mayúsculas), la precedencia user < project < local y el fallo ruidoso en config core coinciden con lo que dicen los comentarios. -AsHashtable ya se usa en otros scripts del repo.
  • Tests: la cobertura de fixtures es amplia (468 líneas, discovery y audit por separado).

Nit, no bloqueante: Resolve-RefServer parte el prefijo de plugin en el primer _, así que un plugin con _ en el nombre produce un falso negativo (no un falso positivo). Esto queda documentado en el comentario, así que basta con dejarlo como está.

…le detail (#758)

Review round 10: the detail describes a plugin-provided alternative by
its form only; the exact working names go to a local-only 'working'
field, like paths. A case-only spelling no longer excuses a wrong-case
name, and the plugin-form placeholder reads server keys holding _.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Revisé el diff (Get-McpServerNames.ps1, el chequeo mcp-tool-name en Invoke-SkillAudit.ps1, SKILL.md y los tests). No encontré bugs ni problemas de seguridad que bloqueen el merge.

  • Los errores de lectura de config fallan en voz alta y el chequeo se omite sin romper el resto de la auditoría. summary.mcpCheck lo deja visible.
  • El detail no incluye nombres de plugins locales; van solo en working.
  • Los tests usan fixtures y no tocan la config real.
  • Nit (opcional): -AsHashtable requiere PowerShell 7+. Conviene confirmar que el resto de los scripts ya exige pwsh 7 y, si no, declararlo con #Requires -Version 7.
  • Nit (opcional): $findings[$findings.Count - 1] asume que Add-Finding siempre agrega un elemento. Si algún día deduplica, working se adjuntaría al finding equivocado. Se podría hacer que Add-Finding devuelva el objeto.

…plugin refs; case tests compare exactly (#758)

Review round 11: the plugin form and the operation text use the
installed server's spelling, so a wrong-case reference gets the right
case in the fileable detail too; a plugin-form placeholder also excuses
a plugin reference whose alternatives are all plugin forms. Tests use
exact-case assertions, add a positive control for the placeholder case
and check that no plugin name leaks into a detail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Sin hallazgos bloqueantes. Revisé Get-McpServerNames.ps1 y el check mcp-tool-name en Invoke-SkillAudit.ps1: solo lectura, sin ejecución de comandos ni secretos, los fallos de discovery degradan solo este check (con mcpCheck = discovery-failed), y el detalle del finding no filtra nombres de plugins locales. Los tests cubren bien los casos límite (case exacto, placeholders, plugin propio, scopes).

Nota menor, no bloqueante: ConvertFrom-Json -AsHashtable exige PowerShell 7+; si el script debe correr en Windows PowerShell 5.1, fallará (en el audit queda como discovery-failed, no rompe el resto).

…records and native projectPath covered (#758)

Review round 12 (tests): the plugin-name leak check compares against
what each fixture skill wrote, not against backtick formatting; record
server/plugin fields are asserted (normalized names, a plugin name with
_); a backslash, other-drive-case projectPath still matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CSalcedoDataBI

Copy link
Copy Markdown
Owner Author

External review - claude-review (Claude API, clean context)

Reviewed commit: da0e962dd39de87108476aa8fcaf32eab5004ce8

12 rounds with claude-review (Claude API, Opus 5.5, xhigh, clean context; same model family as the implementer, an independent context, not another vendor). R1-4 on the whole diff, R5-12 per file with tests separate. Every finding verified in the source; about 45 fixed with tests, and 6 rejected with a reason: inventory paths are already forward-slash; existing fixtures have no mcp refs; strict:false marketplace mcpServers are out of scope; the local-plugin-names finding was rejected 3 times, then adopted in R10 (local-only 'working' field). Final state: Get-McpServerNames reviewed clean in R9 and unchanged since. The tests file was 'patch is correct' in R12, and its 3 findings are fixed in da0e962. Invoke-SkillAudit.ps1 was last reviewed in full in R11; its R11 fixes (installed server spelling, placeholder covers plugin refs) were NOT re-reviewed by the API, because R12 hit max_tokens twice (mechanical; stopped per coordinator rule). They are covered by new exact-case tests. 54 fixture tests in SkillAudit.McpToolNames.Tests.ps1; 62 green with Invoke-SkillAudit.Tests.ps1.

@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Revisión del diff (Get-McpServerNames.ps1, Invoke-SkillAudit.ps1, SKILL.md, tests): no encontré bugs bloqueantes ni problemas de seguridad. La lectura de config es de solo lectura, el detalle que podría subirse upstream no incluye nombres de plugins locales, y un fallo de discovery solo omite este check.

Observaciones menores, no bloqueantes:

  • Invoke-SkillAudit.ps1: el bloque de MCP (~140 líneas, regex y reglas de exclusión muy densas) vive inline. Extraerlo a una función o script propio, como Get-McpServerNames.ps1, facilitaría testearlo y mantenerlo.
  • Get-McpServerNames.ps1: settings.json se lee con -Required, así que un settings con comentarios o comas finales hace fallar el discovery y se salta el check, con warning. Es aceptable, pero conviene tenerlo presente si aparecen reportes de "check skipped".
  • Get-McpServerNames.ps1: [IO.Path]::Combine($base, $ms) con un mcpServers de plugin.json que sea ruta absoluta o con .. lee fuera del plugin. Solo se leen claves JSON, así que el riesgo es bajo, pero se podría validar que la ruta quede bajo $base.
  • Los tests no se ejecutaron en esta revisión.

…nux runner can (#758)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CSalcedoDataBI

Copy link
Copy Markdown
Owner Author

External review - claude-review (Claude API, clean context)

Reviewed commit: ac64c2fb759bdaf82f18a21c784f897018a86380

12 rounds with claude-review (Claude API, Opus 5.5, xhigh, clean context; same model family as the implementer, an independent context, not another vendor). R1-4 on the whole diff, R5-12 per file with tests separate. Every finding verified in the source; about 45 fixed with tests, and 6 rejected with a reason (inventory paths are already forward-slash; existing fixtures have no mcp refs; strict:false marketplace mcpServers are out of scope); the local-plugin-names finding was adopted in R10 (local-only 'working' field). Get-McpServerNames reviewed clean in R9 and unchanged since. The tests file was 'patch is correct' in R12; its 3 findings are fixed, plus a Linux dot-file fix found by CI. Invoke-SkillAudit.ps1 was last reviewed in full in R11; its R11 fixes (installed server spelling, placeholder covers plugin refs) were NOT re-reviewed by the API, because R12 hit max_tokens twice (mechanical; stopped per coordinator rule). They are covered by exact-case tests. 54 fixture tests, 62 green locally with Invoke-SkillAudit.Tests.ps1.

@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Sin hallazgos bloqueantes. Revisé Get-McpServerNames.ps1 y el nuevo chequeo mcp-tool-name en Invoke-SkillAudit.ps1. Lo que vi:

  • Es de solo lectura y no ejecuta comandos externos.
  • El detalle que puede archivarse no incluye nombres de plugins locales; esos van en working.
  • Si falla el descubrimiento, solo se omite este chequeo y queda indicado en mcpCheck.

No ejecuté los tests (los 54 de SkillAudit.McpToolNames.Tests.ps1); este review es solo de lectura del diff.

Nit opcional: -AsHashtable exige PowerShell 7+. Si el repo soporta 5.1, Get-McpServerNames fallaría y el chequeo se omitiría con el warning de descubrimiento.

@CSalcedoDataBI

Copy link
Copy Markdown
Owner Author

On hold: waiting for the Anthropic plugin directory review. Do not merge yet.

The agentic-board plugin is in its first review in the Anthropic plugin directory, and the directory tracks main. Every merge to main becomes a new version that replaces the one under review and restarts the queue. This PR stays on its branch until the plugin leaves review, or until the owner decides to merge the whole batch at once.

Batch waiting together: #799, #800, #801, #802, #803, #804.

@claude

claude Bot commented Oct 24, 2026

Copy link
Copy Markdown

Review (solo lectura del diff; no ejecuté los tests):

Sin bugs bloqueantes. La lógica es coherente: lectura de config de solo lectura, sin ejecución de comandos externos ni input sin validar, y los fallos de descubrimiento degradan solo este check (mcpCheck = discovery-failed). Los tests cubren bien los casos límite (case, plugins, scopes, aprobación de .mcp.json).

Observaciones menores, no bloqueantes:

  • ConvertFrom-Json -AsHashtable exige PowerShell 7+; en Windows PowerShell 5.1 el script falla. Si 5.1 está soportado, el audit lo reportará como discovery-failed. Conviene documentar o comprobar $PSVersionTable de forma explícita.
  • Invoke-SkillAudit.ps1 suma ~130 líneas de lógica inline en el script principal. Extraer el check a una función o script propio, como Get-McpServerNames.ps1, facilitaría mantenerlo.

@CSalcedoDataBI
CSalcedoDataBI merged commit 3462d7c into next Oct 24, 2026
5 checks passed
@CSalcedoDataBI
CSalcedoDataBI deleted the issue-758-feat-skills-audit-flag-skills-that-call branch October 24, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

on-hold-directory Waiting for the Anthropic plugin directory review before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(skills-audit): flag skills that call MCP tools by a name that does not exist when the server comes from a plugin

1 participant