Skip to content

MCP robustness: validate/coerce inputs, report no-ops, GLOBAL vs LAYER tools, compact profile - #769

Merged
CaYatur merged 5 commits into
mainfrom
fix/mcp-robustness-768
Oct 17, 2026
Merged

CaYatur merged 5 commits into
mainfrom
fix/mcp-robustness-768

Conversation

@CaYatur

@CaYatur CaYatur commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Closes #768

Summary

Every mutating MCP tool now either applies its input or returns a clear { ok:false, error } that names the right tool. Writes report changed: true/false. Effect tools say clearly whether they are GLOBAL or LAYER tools. An optional compact tool list helps small-context local models.

Changes (per checklist)

  • postfx guard: sv_set_layer_settings refuses postfx in all three forms (settings object, settings JSON string, key: "postfx"). sv_update_layer refuses patch.settings.postfx. The error names sv_add_layer_effect / sv_set_layer_effect_param.
  • coerceParams(): used by sv_set_effect_param, sv_set_layer_effect_param, sv_add_effect, sv_add_layer_effect, and for settings in sv_set_layer_settings. It accepts an object or a JSON string that parses to an object.
    • Invalid JSON, arrays, null and non-objects are errors.
    • On the *_param tools, missing or empty params are errors.
    • __proto__ / constructor / prototype keys at any depth are refused after parsing.
  • coerceBool(): applies to enabled on the global/layer effect tools, the add tools and sv_set_modulation_enabled. It accepts true/false and "true"/"false" (case-insensitive); 0, "yes" and null are errors.
  • Unknown arguments: every mutating tool (any tool with a permission group) refuses a call whose arguments are all unknown, and the error lists the arguments it takes. When unknown keys come next to known ones, they are still applied and reported as ignored, as before. Read-only tools are unchanged.
  • changed: withConfig compares the config before and after. A no-op answers ok:true, changed:false and is not saved or broadcast.
  • Read-after-write: sv_set_layer_settings returns applied (the stored values for the keys sent) and visual. The *_param tools return applied, and the effect enable tools now include visual.
  • GLOBAL:/LAYER: prefixes: every effect tool description, EN and TR, starts with GLOBAL: or LAYER:. It names its list (postfx vs layers[n].postfx), the matching tool of the other family, and sv_list_effects. The schema param descriptions name the target list too.
  • Consistent errors with hints: "effect not found" errors explain where GLOBAL vs LAYER effects live and which tools to use.
  • Compact profile: new mcp.toolProfile setting (full default, or compact), with a "Compact tool list" toggle on the MCP card.
    • tools/list then returns 27 core read and common edit tools with one-sentence descriptions.
    • All tools stay callable by name. The default list is unchanged.
  • Legacy cleanup: a stray layers[n].settings.postfx is dropped in layers.normalizeLayer (panel), in mcp.normalizeLayer, and on any MCP write (reported as cleaned). It is not moved into the real chain, and the rest of the layer is untouched.

Edge cases covered by tests

  • params as invalid JSON, a JSON array, null, {} or "{}".
  • enabled as "false", 0 or "yes".
  • Locked layers are rejected before input validation.
  • Unsafe keys are refused after string parsing.

Backward compatibility

  • Valid calls apply as before. Replies only gain fields (changed, applied, visual).
  • The default tool list is unchanged.
  • Existing configs load. Only the stray key is removed.

Tests

  • npm test: 3165 → 3178 (round 1) → 3182, all passing.
  • New tests/mcp-robustness-768.test.js (11 tests): one per finding, plus locked-layer order, unsafe keys, the helpers, and tools/list with the compact profile.
  • tests/mcp-coverage.test.js: GLOBAL/LAYER prefixes (EN+TR) and the compact profile.
  • README / README.tr: the MCP section documents the error format, changed, GLOBAL/LAYER naming and the compact profile. README, README.tr and ROADMAP test counts are updated.

Round 2

  • Unknown arguments, generic: a write tool that fails or changes nothing while unknown arguments were sent now returns Unknown argument(s): … <tool> takes: …, with the original reason in parentheses. Before, it answered with "settings or key is required" plus ignored. Example: sv_set_layer_settings {id, patch}.
  • Fields outside patch: on tools that take patch, fields sent at the top level (sv_update_layer {id, locked:true}) get the hint "Put these fields inside patch".
  • Load-time cleanup made observable: the main process now drops a stray layers[].settings.postfx when settings load (sanitizeLoaded) and logs it. The next MCP write reports those layer ids once as cleaned.strayLayerSettingsPostfx. There is still no migration.
  • Tests: 3178 → 3182; README/TR docs updated.

…ools (#768)

- sv_set_layer_settings / sv_update_layer refuse postfx and point to the LAYER effect tools.
- coerceParams(): params/settings as object or JSON-string object; invalid JSON, arrays, null, empty and unsafe keys error.
- coerceBool(): enabled accepts true/false and "true"/"false"; 0, "yes" error.
- Mutating tools refuse calls whose arguments are all unknown.
- withConfig reports changed true/false and skips no-op writes; drops stray layer settings.postfx (also in layers.normalizeLayer).
- Read-after-write: applied values and visual in setter replies.
- GLOBAL:/LAYER: description prefixes (TR GENEL:/KATMAN:) naming postfx vs layers[n].postfx.
- Optional compact tool profile (mcp.toolProfile) with a toggle on the MCP card.
@cayadraft

cayadraft Bot commented Oct 17, 2026

Copy link
Copy Markdown

@CaYatur

CaYaDraft — pull request

Summary
This pull request enhances MCP tool robustness by validating and coercing inputs, enforcing strict rules for mutating operations, and reporting clear status indicators like changed and ok. It introduces a new compact profile mode to reduce the tool list size for local models while maintaining full backward compatibility for existing configurations.

Files

  • README.md / README.tr.md (documentation updates)
  • ROADMAP.md (test count update)
  • src/admin/mcp-panel.js (compact profile toggle UI)
  • src/shared/i18n.js (new translations)
  • src/shared/mcp.js (input validation, coercion, and profile logic)
  • src/visualizer/layers.js (legacy key cleanup)
  • tests/mcp-coverage.test.js (GLOBAL/LAYER prefix checks)
  • tests/mcp-robustness-768.test.js (new robustness tests)

Note
Nothing blocking is visible from the text.


Stopping here. I will not merge, push, or change this pull request until you ask.

/caya explain describes the diff. /caya plan previews the next edit. /caya revise adds a commit.

- A write that fails or changes nothing while unknown arguments were sent returns 'Unknown argument(s): … <tool> takes: …' (generic, all write tools); tools with a patch argument add a 'put these fields inside patch' hint.
- Stray layer settings.postfx is dropped when settings load in main (sanitizeLoaded); the layer ids are reported once as cleaned by the next MCP write (ctx.takeStartupCleaned) and logged.
- Tests; test count 3182.
Settings are read twice at startup (startup parse, then the panel's get-settings via loadSettings) and the file keeps the stray key until the cleaned config is saved, so sanitizeLoaded logged the same ids twice. Track first sightings: cleanup still runs on every read; the log line and the cleaned report happen once per id. Test count 3183.
@CaYatur
CaYatur merged commit 1092ed2 into main Oct 17, 2026
5 checks passed
@CaYatur
CaYatur deleted the fix/mcp-robustness-768 branch October 17, 2026 06:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP robustness: reject silent no-ops, coerce/validate inputs, clarify global vs layer tools

1 participant