Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
5a221391
authored
May 26, 2026
by
Archer
Committed by
GitHub
May 26, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: clarify MCP key runtime authorization snapshot (#6992)
parent
d059cbaf
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
87 additions
and
48 deletions
+87
-48
packages/service/test/support/mcp/utils.test.ts
+51
-1
projects/app/src/service/support/mcp/utils.ts
+36
-47
No files found.
packages/service/test/support/mcp/utils.test.ts
View file @
5a221391
import
{
describe
,
expect
,
it
,
vi
}
from
'vitest'
;
import
{
beforeEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
;
import
{
import
{
pluginNodes2InputSchema
,
pluginNodes2InputSchema
,
workflow2InputSchema
,
workflow2InputSchema
,
...
@@ -49,6 +49,10 @@ vi.mock('@fastgpt/service/core/chat/saveChat', () => ({
...
@@ -49,6 +49,10 @@ vi.mock('@fastgpt/service/core/chat/saveChat', () => ({
pushChatRecords
:
vi
.
fn
()
pushChatRecords
:
vi
.
fn
()
}));
}));
beforeEach
(()
=>
{
vi
.
clearAllMocks
();
});
describe
(
'pluginNodes2InputSchema'
,
()
=>
{
describe
(
'pluginNodes2InputSchema'
,
()
=>
{
it
(
'should generate input schema from plugin nodes'
,
()
=>
{
it
(
'should generate input schema from plugin nodes'
,
()
=>
{
const
nodes
=
[
const
nodes
=
[
...
@@ -190,6 +194,52 @@ describe('getMcpServerTools', () => {
...
@@ -190,6 +194,52 @@ describe('getMcpServerTools', () => {
expect
(
tools
[
0
].
name
).
toBe
(
'test-tool'
);
expect
(
tools
[
0
].
name
).
toBe
(
'test-tool'
);
});
});
it
(
'should use MCP key bindings as runtime tool snapshot'
,
async
()
=>
{
const
mockMcp
=
{
tmbId
:
'test-tmb'
,
apps
:
[
{
appId
:
'test-app'
,
toolName
:
'test-tool'
,
description
:
'test description'
}
]
};
vi
.
mocked
(
MongoMcpKey
.
findOne
).
mockReturnValue
({
lean
:
()
=>
mockMcp
});
vi
.
mocked
(
MongoApp
.
find
).
mockReturnValue
({
lean
:
()
=>
[
{
_id
:
'test-app'
,
name
:
'Test App'
,
type
:
AppTypeEnum
.
workflowTool
}
]
});
vi
.
mocked
(
authAppByTmbId
).
mockRejectedValue
(
new
Error
(
'unAuthApp'
));
vi
.
mocked
(
getAppLatestVersion
).
mockResolvedValue
({
nodes
:
[
{
flowNodeType
:
FlowNodeTypeEnum
.
pluginInput
,
inputs
:
[]
}
],
edges
:
[],
chatConfig
:
{}
});
const
tools
=
await
getMcpServerTools
(
'test-key'
);
expect
(
authAppByTmbId
).
not
.
toHaveBeenCalled
();
expect
(
tools
).
toHaveLength
(
1
);
expect
(
tools
[
0
].
name
).
toBe
(
'test-tool'
);
});
it
(
'should reject if key not found'
,
async
()
=>
{
it
(
'should reject if key not found'
,
async
()
=>
{
vi
.
mocked
(
MongoMcpKey
.
findOne
).
mockReturnValue
({
vi
.
mocked
(
MongoMcpKey
.
findOne
).
mockReturnValue
({
lean
:
()
=>
null
lean
:
()
=>
null
...
...
projects/app/src/service/support/mcp/utils.ts
View file @
5a221391
import
{
MongoMcpKey
}
from
'@fastgpt/service/support/mcp/schema'
;
import
{
MongoMcpKey
}
from
'@fastgpt/service/support/mcp/schema'
;
import
{
CommonErrEnum
}
from
'@fastgpt/global/common/error/code/common'
;
import
{
CommonErrEnum
}
from
'@fastgpt/global/common/error/code/common'
;
import
{
MongoApp
}
from
'@fastgpt/service/core/app/schema'
;
import
{
MongoApp
}
from
'@fastgpt/service/core/app/schema'
;
import
{
authAppByTmbId
}
from
'@fastgpt/service/support/permission/app/auth'
;
import
{
ReadPermissionVal
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
getAppLatestVersion
}
from
'@fastgpt/service/core/app/version/controller'
;
import
{
getAppLatestVersion
}
from
'@fastgpt/service/core/app/version/controller'
;
import
{
type
Tool
}
from
'@modelcontextprotocol/sdk/types.js'
;
import
{
type
Tool
}
from
'@modelcontextprotocol/sdk/types.js'
;
import
{
FlowNodeTypeEnum
}
from
'@fastgpt/global/core/workflow/node/constant'
;
import
{
FlowNodeTypeEnum
}
from
'@fastgpt/global/core/workflow/node/constant'
;
...
@@ -113,6 +111,12 @@ export const workflow2InputSchema = (chatConfig?: {
...
@@ -113,6 +111,12 @@ export const workflow2InputSchema = (chatConfig?: {
return
schema
;
return
schema
;
};
};
/**
* 获取 MCP key 当前绑定的工具列表。
*
* MCP key 在创建或更新绑定应用时已经完成权限校验;运行时按 key 中保存的应用快照提供工具,
* 不再因为创建人的应用权限后续变化而隐藏工具,避免已发布集成被普通权限调整意外中断。
*/
export
const
getMcpServerTools
=
async
(
key
:
string
):
Promise
<
Tool
[]
>
=>
{
export
const
getMcpServerTools
=
async
(
key
:
string
):
Promise
<
Tool
[]
>
=>
{
const
mcp
=
await
MongoMcpKey
.
findOne
({
key
},
{
apps
:
1
}).
lean
();
const
mcp
=
await
MongoMcpKey
.
findOne
({
key
},
{
apps
:
1
}).
lean
();
if
(
!
mcp
)
{
if
(
!
mcp
)
{
...
@@ -130,26 +134,12 @@ export const getMcpServerTools = async (key: string): Promise<Tool[]> => {
...
@@ -130,26 +134,12 @@ export const getMcpServerTools = async (key: string): Promise<Tool[]> => {
{
name
:
1
,
intro
:
1
}
{
name
:
1
,
intro
:
1
}
).
lean
();
).
lean
();
// Filter not permission app
const
permissionAppList
=
await
Promise
.
all
(
appList
.
filter
(
async
(
app
)
=>
{
try
{
await
authAppByTmbId
({
tmbId
:
mcp
.
tmbId
,
appId
:
app
.
_id
,
per
:
ReadPermissionVal
});
return
true
;
}
catch
(
error
)
{
return
false
;
}
})
);
// Get latest version
// Get latest version
const
versionList
=
await
Promise
.
all
(
const
versionList
=
await
Promise
.
all
(
appList
.
map
((
app
)
=>
getAppLatestVersion
(
app
.
_id
,
app
)));
permissionAppList
.
map
((
app
)
=>
getAppLatestVersion
(
app
.
_id
,
app
))
);
// Compute mcp tools
// Compute mcp tools
const
tools
=
versionList
.
map
<
Tool
>
((
version
,
index
)
=>
{
const
tools
=
versionList
.
map
<
Tool
>
((
version
,
index
)
=>
{
const
app
=
permissionA
ppList
[
index
];
const
app
=
a
ppList
[
index
];
const
mcpApp
=
mcp
.
apps
.
find
((
mcpApp
)
=>
String
(
mcpApp
.
appId
)
===
String
(
app
.
_id
))
!
;
const
mcpApp
=
mcp
.
apps
.
find
((
mcpApp
)
=>
String
(
mcpApp
.
appId
)
===
String
(
app
.
_id
))
!
;
const
isPlugin
=
!!
version
.
nodes
.
find
(
const
isPlugin
=
!!
version
.
nodes
.
find
(
...
@@ -168,7 +158,12 @@ export const getMcpServerTools = async (key: string): Promise<Tool[]> => {
...
@@ -168,7 +158,12 @@ export const getMcpServerTools = async (key: string): Promise<Tool[]> => {
return
tools
;
return
tools
;
};
};
// Call tool
/**
* 调用 MCP key 已绑定的工具。
*
* 这里延续 MCP key 的绑定快照语义:调用时只校验 key 和 toolName 是否存在于绑定关系中,
* 不根据创建人的实时应用权限再次拒绝执行;如需撤销 MCP 访问,应更新或删除对应 MCP key。
*/
export
const
callMcpServerTool
=
async
({
key
,
toolName
,
inputs
}:
toolCallProps
)
=>
{
export
const
callMcpServerTool
=
async
({
key
,
toolName
,
inputs
}:
toolCallProps
)
=>
{
const
dispatchApp
=
async
(
app
:
AppSchemaType
,
variables
:
Record
<
string
,
any
>
)
=>
{
const
dispatchApp
=
async
(
app
:
AppSchemaType
,
variables
:
Record
<
string
,
any
>
)
=>
{
const
isPlugin
=
app
.
type
===
AppTypeEnum
.
workflowTool
;
const
isPlugin
=
app
.
type
===
AppTypeEnum
.
workflowTool
;
...
@@ -215,34 +210,28 @@ export const callMcpServerTool = async ({ key, toolName, inputs }: toolCallProps
...
@@ -215,34 +210,28 @@ export const callMcpServerTool = async ({ key, toolName, inputs }: toolCallProps
const
chatId
=
getNanoid
();
const
chatId
=
getNanoid
();
const
{
const
{
assistantResponses
,
newVariables
,
flowResponses
,
durationSeconds
,
system_memories
}
=
flowUsages
,
await
dispatchWorkFlow
({
assistantResponses
,
chatId
,
newVariables
,
mode
:
'chat'
,
flowResponses
,
usageSource
:
UsageSourceEnum
.
mcp
,
durationSeconds
,
runningAppInfo
:
{
system_memories
id
:
String
(
app
.
_id
),
}
=
await
dispatchWorkFlow
({
name
:
app
.
name
,
chatId
,
teamId
:
String
(
app
.
teamId
),
mode
:
'chat'
,
tmbId
:
String
(
app
.
tmbId
)
usageSource
:
UsageSourceEnum
.
mcp
,
},
runningAppInfo
:
{
runningUserInfo
:
await
getRunningUserInfoByTmbId
(
app
.
tmbId
),
id
:
String
(
app
.
_id
),
uid
:
String
(
app
.
tmbId
),
name
:
app
.
name
,
runtimeNodes
,
teamId
:
String
(
app
.
teamId
),
runtimeEdges
:
storeEdges2RuntimeEdges
(
edges
),
tmbId
:
String
(
app
.
tmbId
)
variables
,
},
query
:
removeEmptyUserInput
(
userQuestion
.
value
),
runningUserInfo
:
await
getRunningUserInfoByTmbId
(
app
.
tmbId
),
chatConfig
,
uid
:
String
(
app
.
tmbId
),
histories
:
[],
runtimeNodes
,
stream
:
false
,
runtimeEdges
:
storeEdges2RuntimeEdges
(
edges
),
maxRunTimes
:
WORKFLOW_MAX_RUN_TIMES
variables
,
});
query
:
removeEmptyUserInput
(
userQuestion
.
value
),
chatConfig
,
histories
:
[],
stream
:
false
,
maxRunTimes
:
WORKFLOW_MAX_RUN_TIMES
});
// Save chat
// Save chat
const
aiResponse
:
AIChatItemType
&
{
dataId
?:
string
}
=
{
const
aiResponse
:
AIChatItemType
&
{
dataId
?:
string
}
=
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment