Skip to content

fix(api): MCP Tool Validation Fix#3749

Merged
crivetimihai merged 5 commits intomainfrom
3711-tool-validation-fix
Mar 20, 2026
Merged

fix(api): MCP Tool Validation Fix#3749
crivetimihai merged 5 commits intomainfrom
3711-tool-validation-fix

Conversation

@gcgoncalves
Copy link
Copy Markdown
Collaborator

@gcgoncalves gcgoncalves commented Mar 19, 2026

🐛 Bug-fix PR

📌 Summary

MCP servers often include tools whose description fields contain Markdown syntax (e.g. > quoted text blockquotes, tokens, pipe characters in shell command examples). In v0.9.0 these descriptions registered without error. In v1.0.0.Beta2 a forbidden_patterns check was added to ToolCreate.validate_description in schemas.py that rejects descriptions containing &&, ;, ||, $(, |, > , or < . Unlike the similar validation in validators.py (which honours settings.validation_strict), this Pydantic validator is hardcoded — it raises unconditionally regardless of VALIDATION_STRICT, JSON_SCHEMA_VALIDATION_STRICT, or EXPERIMENTAL_VALIDATE_IO being set to false.

The result is that setting these environment variables has no effect on the registration error, contrary to documented expectations and to v0.9.0 behaviour.

Closes #3711

VALIDATION_STRICT=true:

Screenshot 2026-03-20 at 09 34 47

VALIDATION_STRICT=false:

Screenshot 2026-03-20 at 09 35 05 Screenshot 2026-03-20 at 09 35 18

🔁 Reproduction Steps

  1. Start an MCP server that exposes a tool whose description contains any of: &&, ;, ||, $(, |, > , < .
    Example description: "Search FICO product documentation > returns results" or "> Lists available options".
  2. Register that server in ContextForge (v1.0.0.Beta2+).
  3. Observe the error:
    All 1 tools failed validation. First error: Validation failed for tool 'search':
    [{'type': 'value_error', 'loc': ('description',),
      'msg': "Value error, Description contains unsafe characters: '> '", ...}]
    
  4. Set VALIDATION_STRICT=false, JSON_SCHEMA_VALIDATION_STRICT=false, and EXPERIMENTAL_VALIDATE_IO=false.
  5. Restart ContextForge and retry — the error persists unchanged (confirms env vars are ignored).

🐞 Root Cause

File: mcpgateway/schemas.py, ToolCreate.validate_description, lines 488–493

# Note: backticks (`) are allowed as they are commonly used in Markdown
# for inline code examples in tool descriptions
forbidden_patterns = ["&&", ";", "||", "$(", "|", "> ", "< "]
for pat in forbidden_patterns:
    if pat in v:
        raise ValueError(f"Description contains unsafe characters: '{pat}'")

This check:

  • Is present only in ToolCreate.validate_description. ToolUpdate and all other schema validators (Resource, Prompt, Gateway, Server, A2AAgent, Team…) do not have it.
  • Is hardcodedsettings.validation_strict is imported in schemas.py (line 43) but never consulted here.
  • Runs at Pydantic validation time, before any middleware or db-layer validation, so EXPERIMENTAL_VALIDATE_IO (which toggles ValidationMiddleware) and JSON_SCHEMA_VALIDATION_STRICT (which toggles db-level JSON Schema validation) are both irrelevant to this code path.
  • VALIDATION_STRICT is the correct setting to control this, as it is already used in validate_shell_parameter and validate_sql_parameter in validators.py for the same purpose.

💡 Fix Description

Wrap the raise with a settings.validation_strict guard:

# Note: backticks (`) are allowed as they are commonly used in Markdown
# for inline code examples in tool descriptions
forbidden_patterns = ["&&", ";", "||", "$(", "|", "> ", "< "]
for pat in forbidden_patterns:
    if pat in v:
        if settings.validation_strict:
            raise ValueError(f"Description contains unsafe characters: '{pat}'")
        logger.warning("Description contains potentially unsafe characters: '%s' (VALIDATION_STRICT=false, proceeding)", pat)
        break

🧪 Verification

Check Command Status
Lint suite make lint
Unit tests make test
Coverage ≥ 80 % make coverage
Manual regression no longer fails steps / screenshots

📐 MCP Compliance (if relevant)

  • Matches current MCP spec
  • No breaking change to MCP clients

Checklist

  • Code formatted (make black isort pre-commit)
  • No secrets/credentials committed

@gcgoncalves gcgoncalves marked this pull request as ready for review March 20, 2026 08:47
@gcgoncalves gcgoncalves force-pushed the 3711-tool-validation-fix branch from 6af0017 to c6fbfed Compare March 20, 2026 09:38
cafalchio
cafalchio previously approved these changes Mar 20, 2026
Copy link
Copy Markdown
Collaborator

@cafalchio cafalchio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix.

Copy link
Copy Markdown
Collaborator

@gandhipratik203 gandhipratik203 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix. This lines up with the reported regression, and the added tests make the strict vs non-strict behavior easy to follow.

@gcgoncalves gcgoncalves added release-fix Critical bugfix required for the release ready Validated, ready-to-work-on items MUST P1: Non-negotiable, critical requirements without which the product is non-functional or unsafe labels Mar 20, 2026
@gcgoncalves gcgoncalves added this to the Release 1.0.0 milestone Mar 20, 2026
@gcgoncalves gcgoncalves added bug Something isn't working api REST API Related item labels Mar 20, 2026
gcgoncalves and others added 2 commits March 20, 2026 17:07
Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
- Parameterize non-strict test to verify ALL 7 forbidden patterns pass
  through (was only testing "> ")
- Add test for break behavior: single warning logged even with multiple
  matching patterns in one description
- Move inline `import logging` to top-level imports
- Covers issue #3711

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
The forbidden-pattern list matches "< " (with trailing space), not bare
"<".  Strings like "<placeholder>" never hit the forbidden-pattern check;
they are stripped by sanitize_display_text() as HTML-like content
regardless of VALIDATION_STRICT.  Replace with "< input" and "cmd | grep"
which actually match the gated patterns.

Addresses Codex review finding #1.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…way init

Cover the `raw_error = str(root_cause) or type(root_cause).__name__`
guard in _initialize_gateway: when an exception's str() is empty, the
GatewayConnectionError message now includes the type name instead of
a blank suffix.  Tests both plain exceptions and ExceptionGroup unwrapping.

Addresses Codex review finding #2.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai crivetimihai dismissed stale reviews from gandhipratik203 and cafalchio via bd476d2 March 20, 2026 17:50
@crivetimihai crivetimihai force-pushed the 3711-tool-validation-fix branch from c6fbfed to bd476d2 Compare March 20, 2026 17:50
Copy link
Copy Markdown
Member

@crivetimihai crivetimihai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — fix is correct, consistent, and well-tested.

Review Summary

The core change gates ToolCreate.validate_description's forbidden-pattern check behind settings.validation_strict, matching the existing pattern used by validate_shell_parameter and validate_sql_parameter. When VALIDATION_STRICT=false, forbidden patterns log a warning instead of raising ValueError. This resolves the regression from v0.9.0 → v1.0.0.Beta2 where MCP servers with Markdown-formatted tool descriptions could not register.

Verification

  • Unit tests: 13/13 pass (11 schema validator tests + 2 gateway error fallback tests)
  • Live integration tests (localhost:8080): 18/18 pass — all 7 forbidden patterns correctly rejected in strict mode, all 7 accepted in non-strict mode, plus regression tests on all core endpoints

Commits added during review

  1. test: improve differential test coverage for validation_strict gating — Parameterized non-strict test across all 7 patterns (was only testing > ); added test for break behavior (single warning per description); moved inline import logging to top-level
  2. fix(docs): replace misleading <placeholder> example in validation docs<placeholder> never matches "< " (trailing space required); it's stripped by sanitize_display_text() as HTML. Replaced with < input / cmd | grep in schemas.py, .env.example, and docs
  3. test: add regression tests for empty exception str() fallback in gateway init — Covers raw_error = str(root_cause) or type(root_cause).__name__ for both plain exceptions and ExceptionGroup unwrapping

Note

Filed #3773 for the pre-existing gap: ToolUpdate.validate_description lacks the forbidden-pattern check entirely, allowing bypass via update after creation.

@crivetimihai crivetimihai merged commit b32e8c5 into main Mar 20, 2026
39 checks passed
@crivetimihai crivetimihai deleted the 3711-tool-validation-fix branch March 20, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api REST API Related item bug Something isn't working MUST P1: Non-negotiable, critical requirements without which the product is non-functional or unsafe ready Validated, ready-to-work-on items release-fix Critical bugfix required for the release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][API]: Tool description validation ignores VALIDATION_STRICT env var — blocks MCP server registration

4 participants