refactor(api): migrate console mcp-server responses to BaseModel#35219
refactor(api): migrate console mcp-server responses to BaseModel#35219ai-hpc wants to merge 4 commits intolanggenius:mainfrom
Conversation
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-04-14 21:07:26.903769770 +0000
+++ /tmp/pyrefly_pr.txt 2026-04-14 21:07:16.455774337 +0000
@@ -1926,6 +1926,8 @@
--> tests/unit_tests/controllers/console/app/test_app_response_models.py:110:12
ERROR Object of class `object` has no attribute `exec_module` [missing-attribute]
--> tests/unit_tests/controllers/console/app/test_app_response_models.py:111:5
+ERROR Could not import `AppMCPServerResponse` from `controllers.console.app.mcp_server` [missing-module-attribute]
+ --> tests/unit_tests/controllers/console/app/test_mcp_server_response.py:3:48
ERROR Argument `SimpleNamespace` is not assignable to parameter `workflow` with type `Workflow` in function `controllers.console.app.workflow._parse_file` [bad-argument-type]
--> tests/unit_tests/controllers/console/app/test_workflow.py:25:40
ERROR Argument `SimpleNamespace` is not assignable to parameter `workflow` with type `Workflow` in function `controllers.console.app.workflow._parse_file` [bad-argument-type]
|
Pyrefly DiffNo changes detected. |
| class MCPServerCreatePayload(BaseModel): | ||
| description: str | None = Field(default=None, description="Server description") | ||
| parameters: dict[str, Any] = Field(..., description="Server parameters configuration") | ||
| parameters: dict = Field(..., description="Server parameters configuration") |
There was a problem hiding this comment.
This change looks incorrect. Changing parameters from dict[str, Any] to bare dict would be a regression — PR #35107 (merged just yesterday) specifically added the [str, Any] annotation to improve type safety [1].
The codebase convention strongly favors dict[str, Any] for flexible dictionary fields in Pydantic models (e.g., WorkflowRunPayload.inputs: dict[str, Any] [2], DatasetUpdatePayload.external_retrieval_model: dict[str, Any] | None [3]). Bare dict only appears in legacy code.
The parameters field should stay as dict[str, Any] to maintain explicit type information and consistency with the rest of the API layer.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
There was a problem hiding this comment.
Pull request overview
Migrates the console MCP server controller to use Pydantic ResponseModel schemas (via register_schema_models()) instead of Flask-RESTX response models for response marshalling.
Changes:
- Replaces MCP server request/response models with Pydantic schemas and validators for JSON normalization.
- Updates Swagger/OpenAPI
@console_ns.response(...)metadata/messages for MCP server endpoints. - Adjusts response field types (e.g.,
statusasAppMCPServerStatus) and parameter normalization logic.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) | ||
| @login_required | ||
| @account_initialization_required | ||
| @setup_required | ||
| @get_app_model | ||
| def get(self, app_model): | ||
| server = db.session.scalar(select(AppMCPServer).where(AppMCPServer.app_id == app_model.id).limit(1)) |
There was a problem hiding this comment.
get() no longer handles the case where no AppMCPServer row exists for the app. model_validate(None, from_attributes=True) will raise a validation error (likely resulting in a 500) instead of returning an empty config or a 404. Restore explicit handling (e.g., raise NotFound and document a 404 response, or keep the previous empty-object behavior and adjust the response schema accordingly).
| ) | |
| @login_required | |
| @account_initialization_required | |
| @setup_required | |
| @get_app_model | |
| def get(self, app_model): | |
| server = db.session.scalar(select(AppMCPServer).where(AppMCPServer.app_id == app_model.id).limit(1)) | |
| ) | |
| @console_ns.response(404, "MCP server configuration not found") | |
| @login_required | |
| @account_initialization_required | |
| @setup_required | |
| @get_app_model | |
| def get(self, app_model): | |
| server = db.session.scalar(select(AppMCPServer).where(AppMCPServer.app_id == app_model.id).limit(1)) | |
| if server is None: | |
| raise NotFound("MCP server configuration not found") |
| @console_ns.expect(console_ns.models[MCPServerCreatePayload.__name__]) | ||
| @console_ns.response(200, "Server created", console_ns.models[AppMCPServerResponse.__name__]) | ||
| @console_ns.response( | ||
| 201, "MCP server configuration created successfully", console_ns.models[AppMCPServerResponse.__name__] | ||
| ) |
There was a problem hiding this comment.
This route is documented as returning HTTP 201, but post() currently returns only the body (which will default to 200). Return a (body, 201) tuple (or equivalent) so the runtime status code matches the declared response.
| class MCPServerCreatePayload(BaseModel): | ||
| description: str | None = Field(default=None, description="Server description") | ||
| parameters: dict[str, Any] = Field(..., description="Server parameters configuration") | ||
| parameters: dict = Field(..., description="Server parameters configuration") | ||
|
|
||
|
|
||
| class MCPServerUpdatePayload(BaseModel): | ||
| id: str = Field(..., description="Server ID") | ||
| description: str | None = Field(default=None, description="Server description") | ||
| parameters: dict[str, Any] = Field(..., description="Server parameters configuration") | ||
| parameters: dict = Field(..., description="Server parameters configuration") | ||
| status: str | None = Field(default=None, description="Server status") |
There was a problem hiding this comment.
parameters is now typed as plain dict, which loses the key/value typing used elsewhere in console payload schemas (e.g. api/controllers/console/workspace/tool_providers.py:118 and api/controllers/console/datasets/data_source.py:49 use dict[str, Any]). Consider switching back to dict[str, Any] (and re-adding the Any import) to keep typing and generated schemas consistent.
Part of #28015
Summary
Migrate response marshalling in
api/controllers/console/app/mcp_server.pyfrom Flask-RESTX response models to PydanticResponseModelschemas registered withregister_schema_models().Checklist
make lintandmake type-check(backend) andcd web && pnpm exec vp staged(frontend) to appease the lint gods