mirror of
https://github.com/langgenius/dify.git
synced 2026-08-01 01:49:30 +08:00
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:
parent
676dff04c8
commit
4b415e7d6b
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"},
|
||||
)
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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) => {
|
||||
|
||||
70
web/service/__tests__/use-tools.spec.tsx
Normal file
70
web/service/__tests__/use-tools.spec.tsx
Normal 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()
|
||||
})
|
||||
})
|
||||
@ -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,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user