Skip to content

[BUG]: Prompts service β€” null visibility crash, missing private conflict guard, and stale argument schemaΒ #4611

@shoummu1

Description

@shoummu1

🐞 Bug Summary

Four related bugs found in the prompts service layer (mcpgateway/services/prompt_service.py), discovered during investigation of #2420.

Bug 1 β€” AttributeError crash when visibility=None
register_prompt and update_prompt call visibility.lower() unconditionally. The REST API endpoint declares visibility: Optional[str] = Body("public", ...), so a client sending {"visibility": null} causes AttributeError: 'NoneType' object has no attribute 'lower'.

Bug 2 β€” Missing private conflict check in register_prompt
register_prompt only guards against duplicate public and team prompts. The private conflict check that exists in update_prompt is absent, allowing two private prompts with the same name and owner to be silently created.

Bug 3 β€” Stale argument_schema after template-only update
When update_prompt receives a new template but no arguments, the argument_schema.required list is never recomputed. The stored schema stays tied to the old template's variables, causing spurious validation errors for new template placeholders until arguments is also explicitly re-sent.

Bug 4 β€” _get_required_arguments adds false required args via Formatter.parse()
In addition to Jinja2's meta.find_undeclared_variables, the method also runs Python's string.Formatter().parse() over the raw template source. Any single-brace {...} in JSON content, dict literals, or Jinja2 {% set x = {"a": 1} %} expressions is spuriously added as a required argument.


🧩 Affected Component

Select the area of the project impacted:

  • mcpgateway - API
  • mcpgateway - UI (admin panel)
  • mcpgateway.wrapper - stdio wrapper
  • Federation or Transports
  • CLI, Makefiles, or shell scripts
  • Container setup (Docker/Podman/Compose)
  • Other (explain below)

πŸ” Steps to Reproduce

Bug 1 β€” AttributeError on None visibility:

  1. POST /prompts with body {"name": "test", "template": "hello", "visibility": null}.
  2. Observe 500 Internal Server Error with AttributeError: 'NoneType' object has no attribute 'lower'.

Bug 2 β€” Duplicate private prompt:

  1. POST /prompts with {"visibility": "private", "name": "my-prompt", "template": "hello"} as user alice.
  2. Repeat the same request.
  3. Observe: both succeed. No 409 Conflict is raised, unlike public and team prompts.

Bug 3 β€” Stale argument schema:

  1. POST /prompts with {"template": "Hello {{ name }}"} β€” argument_schema.required is ["name"].
  2. PATCH /prompts/{id} with only {"template": "Hello {{ first }} {{ last }}"}.
  3. POST /prompts/{id} with body {"first": "A", "last": "B"} (MCP spec prompt retrieval).
  4. Observe: validation error claiming name is missing, because argument_schema.required was never updated.

Bug 4 β€” False required args:

  1. Register a prompt whose template contains a JSON literal, e.g.:
    Here is the config: {"key": "value"}
    
  2. Observe: argument_schema.required includes "key" even though it is not a Jinja2 variable.

πŸ€” Expected Behavior

  1. visibility=None should default to "public" (the declared parameter default) without crashing.
  2. Registering a duplicate private prompt for the same owner should raise PromptNameConflictError (HTTP 409), consistent with update_prompt and with the existing public/team checks in register_prompt.
  3. Updating only template should atomically recompute argument_schema.required to reflect the new template variables.
  4. _get_required_arguments should return only genuine Jinja2 undeclared variables as identified by jinja2.meta.find_undeclared_variables, without adding Python str.format-style tokens from literal content.

πŸ““ Logs / Error Output

Bug 1:

AttributeError: 'NoneType' object has no attribute 'lower'
  File "mcpgateway/services/prompt_service.py", line 844, in register_prompt
    if visibility.lower() == "public":

Bug 3 β€” example misleading 422:

{"detail": "Missing required argument: name"}

returned when {"first": "Alice", "last": "Smith"} is supplied after a template-only update.


🧠 Environment Info

Key Value
Version or commit main (identified during review of #2420)
Runtime Python 3.11, FastAPI / Gunicorn
Platform / OS Any
Container Any

🧩 Additional Context

  • All four bugs are confined to mcpgateway/services/prompt_service.py.
  • Bug 1: register_prompt and update_prompt call visibility.lower() unconditionally β€” callers can pass None explicitly via the REST API.
  • Bug 2: register_prompt has public and team duplicate-name guards but no private one; update_prompt does have a private guard β€” an asymmetry introduced at some point between the two methods.
  • Bug 3: argument_schema is only recomputed inside if prompt_update.arguments is not None:; a template-only PATCH skips that block entirely.
  • Bug 4: from string import Formatter is imported solely for _get_required_arguments; jinja2.meta.find_undeclared_variables already covers all Jinja2 variables correctly.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingtriageIssues / Features awaiting triage

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions