fix: refresh mcp server code by app id (#39828)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Xin Zhang 2026-07-31 12:57:02 +08:00 committed by GitHub
parent 676dff04c8
commit 4b415e7d6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 310 additions and 165 deletions

View File

@ -1,7 +1,6 @@
import json
from datetime import datetime
from typing import Any
from uuid import UUID
from flask_restx import Resource
from pydantic import BaseModel, Field, field_validator
@ -179,11 +178,11 @@ class AppMCPServerController(Resource):
return dump_response(AppMCPServerResponse, server)
@console_ns.route("/apps/<uuid:server_id>/server/refresh")
@console_ns.route("/apps/<uuid:app_id>/server/refresh")
class AppMCPServerRefreshController(Resource):
@console_ns.doc("refresh_app_mcp_server")
@console_ns.doc(description="Refresh MCP server configuration and regenerate server code")
@console_ns.doc(params={"server_id": "Server ID"})
@console_ns.doc(params={"app_id": "App ID"})
@console_ns.response(200, "MCP server refreshed successfully", console_ns.models[AppMCPServerResponse.__name__])
@console_ns.response(403, "Insufficient permissions")
@console_ns.response(404, "Server not found")
@ -193,10 +192,11 @@ class AppMCPServerRefreshController(Resource):
@edit_permission_required
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
@with_current_tenant_id
def get(self, current_tenant_id: str, server_id: UUID):
@get_app_model
def post(self, current_tenant_id: str, app_model: App):
server = db.session.scalar(
select(AppMCPServer)
.where(AppMCPServer.id == server_id, AppMCPServer.tenant_id == current_tenant_id)
.where(AppMCPServer.app_id == app_model.id, AppMCPServer.tenant_id == current_tenant_id)
.limit(1)
)
if not server:

View File

@ -3204,6 +3204,23 @@ Update MCP server configuration for an application
| 403 | Insufficient permissions | |
| 404 | Server not found | |
### [POST] /apps/{app_id}/server/refresh
Refresh MCP server configuration and regenerate server code
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| app_id | path | App ID | Yes | string (uuid) |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | MCP server refreshed successfully | **application/json**: [AppMCPServerResponse](#appmcpserverresponse)<br> |
| 403 | Insufficient permissions | |
| 404 | Server not found | |
### [POST] /apps/{app_id}/site
Update application site configuration
@ -5140,23 +5157,6 @@ Restore a published workflow version into the draft workflow
| ---- | ----------- |
| 204 | API key deleted successfully |
### [GET] /apps/{server_id}/server/refresh
Refresh MCP server configuration and regenerate server code
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| server_id | path | Server ID | Yes | string (uuid) |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | MCP server refreshed successfully | **application/json**: [AppMCPServerResponse](#appmcpserverresponse)<br> |
| 403 | Insufficient permissions | |
| 404 | Server not found | |
### [GET] /auth/plugin/datasource/default-list
#### Responses

View File

@ -3,10 +3,16 @@ from inspect import unwrap
from types import SimpleNamespace
from unittest.mock import PropertyMock, patch
import pytest
from flask import Flask
from controllers.console import console_ns
from controllers.console.app.mcp_server import AppMCPServerController, AppMCPServerResponse
from controllers.console.app.mcp_server import (
AppMCPServerController,
AppMCPServerRefreshController,
AppMCPServerResponse,
)
from controllers.console.wraps import RBACPermission, RBACResourceScope
class _ValidatedResponse:
@ -177,3 +183,75 @@ class TestAppMCPServerController:
get_mock.assert_not_called()
commit.assert_called_once()
assert response == {"id": "server-1"}
class TestAppMCPServerRefreshController:
def test_post_refreshes_server_bound_to_app_and_tenant(self):
api = AppMCPServerRefreshController()
method = unwrap(api.post)
server = SimpleNamespace(server_code="old-code")
with (
patch("controllers.console.app.mcp_server.db.session.scalar", return_value=server) as scalar,
patch("controllers.console.app.mcp_server.db.session.commit") as commit,
patch("controllers.console.app.mcp_server.AppMCPServer.generate_server_code", return_value="new-code"),
patch(
"controllers.console.app.mcp_server.AppMCPServerResponse.model_validate",
return_value=_ValidatedResponse({"id": "server-1", "server_code": "new-code"}),
),
):
response = method(api, "tenant-1", app_model=SimpleNamespace(id="app-1"))
stmt = scalar.call_args.args[0]
compiled = stmt.compile()
statement = str(compiled)
assert "app_mcp_servers.tenant_id" in statement
assert "app_mcp_servers.app_id" in statement
assert "tenant-1" in compiled.params.values()
assert "app-1" in compiled.params.values()
assert server.server_code == "new-code"
commit.assert_called_once()
assert response == {"id": "server-1", "server_code": "new-code"}
def test_route_is_app_scoped_post(self):
route_map = {
resource.__name__: urls
for resource, urls, _route_doc, _kwargs in console_ns.resources
if resource.__name__ == "AppMCPServerRefreshController"
}
assert route_map["AppMCPServerRefreshController"] == ("/apps/<uuid:app_id>/server/refresh",)
assert hasattr(AppMCPServerRefreshController, "post")
assert not hasattr(AppMCPServerRefreshController, "get")
def test_post_requires_app_view_layout_permission(self):
method = AppMCPServerRefreshController.post
while "rbac_permission_required" not in method.__code__.co_qualname:
method = method.__wrapped__
class PermissionCheckedError(Exception):
pass
current_user = SimpleNamespace(id="account-1")
with (
patch("controllers.common.wraps.dify_config.RBAC_ENABLED", True),
patch(
"controllers.common.wraps.current_account_with_tenant",
return_value=(current_user, "tenant-1"),
),
patch(
"controllers.common.wraps.enforce_rbac_access",
side_effect=PermissionCheckedError,
) as enforce_rbac_access,
pytest.raises(PermissionCheckedError),
):
method(AppMCPServerRefreshController(), app_id="app-1")
enforce_rbac_access.assert_called_once_with(
tenant_id="tenant-1",
account_id="account-1",
resource_type=RBACResourceScope.APP,
scene=RBACPermission.APP_VIEW_LAYOUT,
resource_required=True,
path_args={"app_id": "app-1"},
)

View File

@ -272,8 +272,6 @@ import {
zGetAppsByAppIdWorkflowsTriggersWebhookResponse,
zGetAppsByResourceIdApiKeysPath,
zGetAppsByResourceIdApiKeysResponse,
zGetAppsByServerIdServerRefreshPath,
zGetAppsByServerIdServerRefreshResponse,
zGetAppsImportsByAppIdCheckDependenciesPath,
zGetAppsImportsByAppIdCheckDependenciesResponse,
zGetAppsQuery,
@ -375,6 +373,8 @@ import {
zPostAppsByAppIdPublishToCreatorsPlatformResponse,
zPostAppsByAppIdServerBody,
zPostAppsByAppIdServerPath,
zPostAppsByAppIdServerRefreshPath,
zPostAppsByAppIdServerRefreshResponse,
zPostAppsByAppIdServerResponse,
zPostAppsByAppIdSiteAccessTokenResetPath,
zPostAppsByAppIdSiteAccessTokenResetResponse,
@ -2290,6 +2290,25 @@ export const publishToCreatorsPlatform = {
post: post30,
}
/**
* Refresh MCP server configuration and regenerate server code
*/
export const post31 = oc
.route({
description: 'Refresh MCP server configuration and regenerate server code',
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAppsByAppIdServerRefresh',
path: '/apps/{app_id}/server/refresh',
tags: ['console'],
})
.input(z.object({ params: zPostAppsByAppIdServerRefreshPath }))
.output(zPostAppsByAppIdServerRefreshResponse)
export const refresh = {
post: post31,
}
/**
* Get MCP server configuration for an application
*/
@ -2308,7 +2327,7 @@ export const get39 = oc
/**
* Create MCP server configuration for an application
*/
export const post31 = oc
export const post32 = oc
.route({
description: 'Create MCP server configuration for an application',
inputStructure: 'detailed',
@ -2338,14 +2357,15 @@ export const put = oc
export const server = {
get: get39,
post: post31,
post: post32,
put,
refresh,
}
/**
* Reset access token for application site
*/
export const post32 = oc
export const post33 = oc
.route({
description: 'Reset access token for application site',
inputStructure: 'detailed',
@ -2358,13 +2378,13 @@ export const post32 = oc
.output(zPostAppsByAppIdSiteAccessTokenResetResponse)
export const accessTokenReset = {
post: post32,
post: post33,
}
/**
* Update application site configuration
*/
export const post33 = oc
export const post34 = oc
.route({
description: 'Update application site configuration',
inputStructure: 'detailed',
@ -2377,14 +2397,14 @@ export const post33 = oc
.output(zPostAppsByAppIdSiteResponse)
export const site = {
post: post33,
post: post34,
accessTokenReset,
}
/**
* Enable or disable app site
*/
export const post34 = oc
export const post35 = oc
.route({
description: 'Enable or disable app site',
inputStructure: 'detailed',
@ -2397,7 +2417,7 @@ export const post34 = oc
.output(zPostAppsByAppIdSiteEnableResponse)
export const siteEnable = {
post: post34,
post: post35,
}
/**
@ -2418,7 +2438,7 @@ export const delete9 = oc
/**
* Star an application for the current account
*/
export const post35 = oc
export const post36 = oc
.route({
description: 'Star an application for the current account',
inputStructure: 'detailed',
@ -2432,7 +2452,7 @@ export const post35 = oc
export const star = {
delete: delete9,
post: post35,
post: post36,
}
/**
@ -2665,7 +2685,7 @@ export const voices = {
/**
* Convert text to speech for chat messages
*/
export const post36 = oc
export const post37 = oc
.route({
description: 'Convert text to speech for chat messages',
inputStructure: 'detailed',
@ -2680,7 +2700,7 @@ export const post36 = oc
.output(zPostAppsByAppIdTextToAudioResponse)
export const textToAudio = {
post: post36,
post: post37,
voices,
}
@ -2705,7 +2725,7 @@ export const get49 = oc
/**
* Update app tracing configuration
*/
export const post37 = oc
export const post38 = oc
.route({
description: 'Update app tracing configuration',
inputStructure: 'detailed',
@ -2719,7 +2739,7 @@ export const post37 = oc
export const trace = {
get: get49,
post: post37,
post: post38,
}
/**
@ -2788,7 +2808,7 @@ export const patch = oc
*
* Create a new tracing configuration for an application
*/
export const post38 = oc
export const post39 = oc
.route({
description: 'Create a new tracing configuration for an application',
inputStructure: 'detailed',
@ -2808,13 +2828,13 @@ export const traceConfig = {
delete: delete10,
get: get50,
patch,
post: post38,
post: post39,
}
/**
* Update app trigger (enable/disable)
*/
export const post39 = oc
export const post40 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
@ -2832,7 +2852,7 @@ export const post39 = oc
.output(zPostAppsByAppIdTriggerEnableResponse)
export const triggerEnable = {
post: post39,
post: post40,
}
/**
@ -2940,7 +2960,7 @@ export const count3 = {
*
* Stop running workflow task
*/
export const post40 = oc
export const post41 = oc
.route({
description: 'Stop running workflow task',
inputStructure: 'detailed',
@ -2954,7 +2974,7 @@ export const post40 = oc
.output(zPostAppsByAppIdWorkflowRunsTasksByTaskIdStopResponse)
export const stop3 = {
post: post40,
post: post41,
}
export const byTaskId3 = {
@ -3057,7 +3077,7 @@ export const read = {
/**
* Upload one workflow Agent sandbox file and return a signed download URL
*/
export const post41 = oc
export const post42 = oc
.route({
description: 'Upload one workflow Agent sandbox file and return a signed download URL',
inputStructure: 'detailed',
@ -3075,7 +3095,7 @@ export const post41 = oc
.output(zPostAppsByAppIdWorkflowRunsByWorkflowRunIdAgentNodesByNodeIdSandboxFilesUploadResponse)
export const upload3 = {
post: post41,
post: post42,
}
/**
@ -3225,7 +3245,7 @@ export const byReplyId = {
*
* Add a reply to a workflow comment
*/
export const post42 = oc
export const post43 = oc
.route({
description: 'Add a reply to a workflow comment',
inputStructure: 'detailed',
@ -3245,7 +3265,7 @@ export const post42 = oc
.output(zPostAppsByAppIdWorkflowCommentsByCommentIdRepliesResponse)
export const replies = {
post: post42,
post: post43,
byReplyId,
}
@ -3254,7 +3274,7 @@ export const replies = {
*
* Resolve a workflow comment
*/
export const post43 = oc
export const post44 = oc
.route({
description: 'Resolve a workflow comment',
inputStructure: 'detailed',
@ -3268,7 +3288,7 @@ export const post43 = oc
.output(zPostAppsByAppIdWorkflowCommentsByCommentIdResolveResponse)
export const resolve = {
post: post43,
post: post44,
}
/**
@ -3362,7 +3382,7 @@ export const get63 = oc
*
* Create a new workflow comment
*/
export const post44 = oc
export const post45 = oc
.route({
description: 'Create a new workflow comment',
inputStructure: 'detailed',
@ -3383,7 +3403,7 @@ export const post44 = oc
export const comments = {
get: get63,
post: post44,
post: post45,
mentionUsers,
byCommentId,
}
@ -3564,7 +3584,7 @@ export const get70 = oc
/**
* Update conversation variables for workflow draft
*/
export const post45 = oc
export const post46 = oc
.route({
description: 'Update conversation variables for workflow draft',
inputStructure: 'detailed',
@ -3583,7 +3603,7 @@ export const post45 = oc
export const conversationVariables2 = {
get: get70,
post: post45,
post: post46,
}
/**
@ -3607,7 +3627,7 @@ export const get71 = oc
/**
* Update environment variables for workflow draft
*/
export const post46 = oc
export const post47 = oc
.route({
description: 'Update environment variables for workflow draft',
inputStructure: 'detailed',
@ -3626,13 +3646,13 @@ export const post46 = oc
export const environmentVariables = {
get: get71,
post: post46,
post: post47,
}
/**
* Update draft workflow features
*/
export const post47 = oc
export const post48 = oc
.route({
description: 'Update draft workflow features',
inputStructure: 'detailed',
@ -3650,7 +3670,7 @@ export const post47 = oc
.output(zPostAppsByAppIdWorkflowsDraftFeaturesResponse)
export const features = {
post: post47,
post: post48,
}
/**
@ -3658,7 +3678,7 @@ export const features = {
*
* Test human input delivery for workflow
*/
export const post48 = oc
export const post49 = oc
.route({
description: 'Test human input delivery for workflow',
inputStructure: 'detailed',
@ -3677,7 +3697,7 @@ export const post48 = oc
.output(zPostAppsByAppIdWorkflowsDraftHumanInputNodesByNodeIdDeliveryTestResponse)
export const deliveryTest = {
post: post48,
post: post49,
}
/**
@ -3685,7 +3705,7 @@ export const deliveryTest = {
*
* Get human input form preview for workflow
*/
export const post49 = oc
export const post50 = oc
.route({
description: 'Get human input form preview for workflow',
inputStructure: 'detailed',
@ -3704,7 +3724,7 @@ export const post49 = oc
.output(zPostAppsByAppIdWorkflowsDraftHumanInputNodesByNodeIdFormPreviewResponse)
export const preview5 = {
post: post49,
post: post50,
}
/**
@ -3712,7 +3732,7 @@ export const preview5 = {
*
* Submit human input form preview for workflow
*/
export const post50 = oc
export const post51 = oc
.route({
description: 'Submit human input form preview for workflow',
inputStructure: 'detailed',
@ -3731,7 +3751,7 @@ export const post50 = oc
.output(zPostAppsByAppIdWorkflowsDraftHumanInputNodesByNodeIdFormRunResponse)
export const run5 = {
post: post50,
post: post51,
}
export const form2 = {
@ -3757,7 +3777,7 @@ export const humanInput2 = {
*
* Run draft workflow iteration node
*/
export const post51 = oc
export const post52 = oc
.route({
description: 'Run draft workflow iteration node',
inputStructure: 'detailed',
@ -3776,7 +3796,7 @@ export const post51 = oc
.output(zPostAppsByAppIdWorkflowsDraftIterationNodesByNodeIdRunResponse)
export const run6 = {
post: post51,
post: post52,
}
export const byNodeId6 = {
@ -3796,7 +3816,7 @@ export const iteration2 = {
*
* Run draft workflow loop node
*/
export const post52 = oc
export const post53 = oc
.route({
description: 'Run draft workflow loop node',
inputStructure: 'detailed',
@ -3815,7 +3835,7 @@ export const post52 = oc
.output(zPostAppsByAppIdWorkflowsDraftLoopNodesByNodeIdRunResponse)
export const run7 = {
post: post52,
post: post53,
}
export const byNodeId7 = {
@ -3847,7 +3867,7 @@ export const candidates = {
get: get72,
}
export const post53 = oc
export const post54 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
@ -3864,10 +3884,10 @@ export const post53 = oc
.output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCopyFromRosterResponse)
export const copyFromRoster = {
post: post53,
post: post54,
}
export const post54 = oc
export const post55 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
@ -3884,10 +3904,10 @@ export const post54 = oc
.output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse)
export const impact = {
post: post54,
post: post55,
}
export const post55 = oc
export const post56 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
@ -3904,10 +3924,10 @@ export const post55 = oc
.output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterResponse)
export const saveToRoster = {
post: post55,
post: post56,
}
export const post56 = oc
export const post57 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
@ -3924,7 +3944,7 @@ export const post56 = oc
.output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateResponse)
export const validate = {
post: post56,
post: post57,
}
export const get73 = oc
@ -3993,7 +4013,7 @@ export const lastRun = {
*
* Run draft workflow node
*/
export const post57 = oc
export const post58 = oc
.route({
description: 'Run draft workflow node',
inputStructure: 'detailed',
@ -4012,7 +4032,7 @@ export const post57 = oc
.output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdRunResponse)
export const run8 = {
post: post57,
post: post58,
}
/**
@ -4020,7 +4040,7 @@ export const run8 = {
*
* Poll for trigger events and execute single node when event arrives
*/
export const post58 = oc
export const post59 = oc
.route({
description: 'Poll for trigger events and execute single node when event arrives',
inputStructure: 'detailed',
@ -4034,7 +4054,7 @@ export const post58 = oc
.output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdTriggerRunResponse)
export const run9 = {
post: post58,
post: post59,
}
export const trigger = {
@ -4094,7 +4114,7 @@ export const nodes7 = {
*
* Run draft workflow
*/
export const post59 = oc
export const post60 = oc
.route({
description: 'Run draft workflow',
inputStructure: 'detailed',
@ -4113,7 +4133,7 @@ export const post59 = oc
.output(zPostAppsByAppIdWorkflowsDraftRunResponse)
export const run10 = {
post: post59,
post: post60,
}
/**
@ -4235,7 +4255,7 @@ export const systemVariables = {
*
* Poll for trigger events and execute full workflow when event arrives
*/
export const post60 = oc
export const post61 = oc
.route({
description: 'Poll for trigger events and execute full workflow when event arrives',
inputStructure: 'detailed',
@ -4254,7 +4274,7 @@ export const post60 = oc
.output(zPostAppsByAppIdWorkflowsDraftTriggerRunResponse)
export const run11 = {
post: post60,
post: post61,
}
/**
@ -4262,7 +4282,7 @@ export const run11 = {
*
* Full workflow debug when the start node is a trigger
*/
export const post61 = oc
export const post62 = oc
.route({
description: 'Full workflow debug when the start node is a trigger',
inputStructure: 'detailed',
@ -4281,7 +4301,7 @@ export const post61 = oc
.output(zPostAppsByAppIdWorkflowsDraftTriggerRunAllResponse)
export const runAll = {
post: post61,
post: post62,
}
export const trigger2 = {
@ -4434,7 +4454,7 @@ export const get83 = oc
*
* Sync draft workflow configuration
*/
export const post62 = oc
export const post63 = oc
.route({
description: 'Sync draft workflow configuration',
inputStructure: 'detailed',
@ -4454,7 +4474,7 @@ export const post62 = oc
export const draft2 = {
get: get83,
post: post62,
post: post63,
conversationVariables: conversationVariables2,
environmentVariables,
features,
@ -4490,7 +4510,7 @@ export const get84 = oc
/**
* Publish workflow
*/
export const post63 = oc
export const post64 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
@ -4509,7 +4529,7 @@ export const post63 = oc
export const publish = {
get: get84,
post: post63,
post: post64,
}
/**
@ -4646,7 +4666,7 @@ export const triggers2 = {
/**
* Restore a published workflow version into the draft workflow
*/
export const post64 = oc
export const post65 = oc
.route({
description: 'Restore a published workflow version into the draft workflow',
inputStructure: 'detailed',
@ -4659,7 +4679,7 @@ export const post64 = oc
.output(zPostAppsByAppIdWorkflowsByWorkflowIdRestoreResponse)
export const restore = {
post: post64,
post: post65,
}
/**
@ -4884,7 +4904,7 @@ export const get92 = oc
*
* Create a new API key for an app
*/
export const post65 = oc
export const post66 = oc
.route({
description: 'Create a new API key for an app',
inputStructure: 'detailed',
@ -4900,7 +4920,7 @@ export const post65 = oc
export const apiKeys = {
get: get92,
post: post65,
post: post66,
byApiKeyId,
}
@ -4908,39 +4928,12 @@ export const byResourceId = {
apiKeys,
}
/**
* Refresh MCP server configuration and regenerate server code
*/
export const get93 = oc
.route({
description: 'Refresh MCP server configuration and regenerate server code',
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByServerIdServerRefresh',
path: '/apps/{server_id}/server/refresh',
tags: ['console'],
})
.input(z.object({ params: zGetAppsByServerIdServerRefreshPath }))
.output(zGetAppsByServerIdServerRefreshResponse)
export const refresh = {
get: get93,
}
export const server2 = {
refresh,
}
export const byServerId = {
server: server2,
}
/**
* Get app list
*
* Get list of applications with pagination and filtering
*/
export const get94 = oc
export const get93 = oc
.route({
description: 'Get list of applications with pagination and filtering',
inputStructure: 'detailed',
@ -4958,7 +4951,7 @@ export const get94 = oc
*
* Create a new application
*/
export const post66 = oc
export const post67 = oc
.route({
description: 'Create a new application',
inputStructure: 'detailed',
@ -4973,15 +4966,14 @@ export const post66 = oc
.output(zPostAppsResponse)
export const apps = {
get: get94,
post: post66,
get: get93,
post: post67,
imports,
recent,
starred,
workflows,
byAppId: byAppId2,
byResourceId,
byServerId,
}
export const contract = {

View File

@ -5010,6 +5010,27 @@ export type PutAppsByAppIdServerResponses = {
export type PutAppsByAppIdServerResponse =
PutAppsByAppIdServerResponses[keyof PutAppsByAppIdServerResponses]
export type PostAppsByAppIdServerRefreshData = {
body?: never
path: {
app_id: string
}
query?: never
url: '/apps/{app_id}/server/refresh'
}
export type PostAppsByAppIdServerRefreshErrors = {
403: unknown
404: unknown
}
export type PostAppsByAppIdServerRefreshResponses = {
200: AppMcpServerResponse
}
export type PostAppsByAppIdServerRefreshResponse =
PostAppsByAppIdServerRefreshResponses[keyof PostAppsByAppIdServerRefreshResponses]
export type PostAppsByAppIdSiteData = {
body: AppSiteUpdatePayload
path: {
@ -7001,24 +7022,3 @@ export type DeleteAppsByResourceIdApiKeysByApiKeyIdResponses = {
export type DeleteAppsByResourceIdApiKeysByApiKeyIdResponse =
DeleteAppsByResourceIdApiKeysByApiKeyIdResponses[keyof DeleteAppsByResourceIdApiKeysByApiKeyIdResponses]
export type GetAppsByServerIdServerRefreshData = {
body?: never
path: {
server_id: string
}
query?: never
url: '/apps/{server_id}/server/refresh'
}
export type GetAppsByServerIdServerRefreshErrors = {
403: unknown
404: unknown
}
export type GetAppsByServerIdServerRefreshResponses = {
200: AppMcpServerResponse
}
export type GetAppsByServerIdServerRefreshResponse =
GetAppsByServerIdServerRefreshResponses[keyof GetAppsByServerIdServerRefreshResponses]

View File

@ -5527,6 +5527,15 @@ export const zPutAppsByAppIdServerPath = z.object({
*/
export const zPutAppsByAppIdServerResponse = zAppMcpServerResponse
export const zPostAppsByAppIdServerRefreshPath = z.object({
app_id: z.uuid(),
})
/**
* MCP server refreshed successfully
*/
export const zPostAppsByAppIdServerRefreshResponse = zAppMcpServerResponse
export const zPostAppsByAppIdSiteBody = zAppSiteUpdatePayload
export const zPostAppsByAppIdSitePath = z.object({
@ -6772,12 +6781,3 @@ export const zDeleteAppsByResourceIdApiKeysByApiKeyIdPath = z.object({
* API key deleted successfully
*/
export const zDeleteAppsByResourceIdApiKeysByApiKeyIdResponse = z.void()
export const zGetAppsByServerIdServerRefreshPath = z.object({
server_id: z.uuid(),
})
/**
* MCP server refreshed successfully
*/
export const zGetAppsByServerIdServerRefreshResponse = zAppMcpServerResponse

View File

@ -152,7 +152,7 @@ describe('MCPServiceCard', () => {
expect(screen.getByRole('switch')).toBeInTheDocument()
})
it('should keep status visible and disable management controls without mcp.manage', () => {
it('should keep status visible and disable management controls without app edit permission', () => {
mockHookState = createDefaultHookState({
canManageMCP: false,
toggleDisabled: true,

View File

@ -166,7 +166,7 @@ describe('useMCPServiceCardState', () => {
describe('Permission Flags', () => {
it('should expose MCP manage capability from app edit ACL', () => {
const appInfo = createMockAppInfo()
const appInfo = createMockAppInfo(AppModeEnum.CHAT, [AppACLPermission.Edit])
const { result } = renderHook(() => useMCPServiceCardState(appInfo, false), {
wrapper: createWrapper(),
})
@ -320,7 +320,7 @@ describe('useMCPServiceCardState', () => {
})
describe('Handler Functions', () => {
it('should call handleGenCode and invalidate server detail', async () => {
it('should refresh the MCP server by app ID and invalidate server detail', async () => {
const appInfo = createMockAppInfo()
const { result } = renderHook(() => useMCPServiceCardState(appInfo, false), {
wrapper: createWrapper(),
@ -330,8 +330,8 @@ describe('useMCPServiceCardState', () => {
await result.current.handleGenCode()
})
// handleGenCode should complete without error
expect(result.current.genLoading).toBe(false)
expect(mockRefreshMCPServerCode).toHaveBeenCalledWith('app-123')
expect(mockInvalidateMCPServerDetail).toHaveBeenCalledWith('app-123')
})
it('should call invalidateBasicAppConfig', () => {

View File

@ -122,9 +122,9 @@ export const useMCPServiceCardState = (appInfo: AppInfo, triggerModeDisabled: bo
const handleGenCode = useCallback(async () => {
if (!canManageMCP) return
await refreshMCPServerCode(detail?.id || '')
await refreshMCPServerCode(appId)
invalidateMCPServerDetail(appId)
}, [canManageMCP, refreshMCPServerCode, detail?.id, invalidateMCPServerDetail, appId])
}, [canManageMCP, refreshMCPServerCode, invalidateMCPServerDetail, appId])
const handleStatusChange = useCallback(
async (state: boolean) => {

View File

@ -0,0 +1,70 @@
import type { ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { renderHook } from '@/test/console/render'
import { useRefreshMCPServerCode } from '../use-tools'
const { mockGet, mockPost } = vi.hoisted(() => ({
mockGet: vi.fn(),
mockPost: vi.fn(),
}))
vi.mock('@/service/base', () => ({
del: vi.fn(),
get: mockGet,
post: mockPost,
put: vi.fn(),
}))
vi.mock('@/service/client', () => ({
consoleClient: {
apps: {
byAppId: {
server: {
refresh: {
post: mockPost,
},
},
},
},
},
}))
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
retry: false,
},
},
})
return function Wrapper({ children }: { children: ReactNode }) {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
}
describe('useRefreshMCPServerCode', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('refreshes the MCP server with POST and the app ID', async () => {
mockPost.mockResolvedValue({ id: 'server-1', server_code: 'new-code' })
const { result } = renderHook(() => useRefreshMCPServerCode(), {
wrapper: createWrapper(),
})
await act(async () => {
await result.current.mutateAsync('app-1')
})
expect(mockPost).toHaveBeenCalledWith({
params: {
app_id: 'app-1',
},
})
expect(mockGet).not.toHaveBeenCalled()
})
})

View File

@ -11,6 +11,7 @@ import type { AppIconType } from '@/types/app'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { CollectionType } from '@/app/components/tools/types'
import { del, get, post, put } from './base'
import { consoleClient } from './client'
import { useInvalid } from './use-base'
const NAME_SPACE = 'tools'
@ -250,7 +251,11 @@ export const useRefreshMCPServerCode = () => {
return useMutation({
mutationKey: [NAME_SPACE, 'refresh-mcp-server-code'],
mutationFn: (appID: string) => {
return get<MCPServerDetail>(`apps/${appID}/server/refresh`)
return consoleClient.apps.byAppId.server.refresh.post({
params: {
app_id: appID,
},
})
},
})
}