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
c1c6b952
authored
Apr 28, 2026
by
Hinotobi
Committed by
GitHub
Apr 28, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: validate stored MCP tool URLs (#6826)
parent
c483a563
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
39 additions
and
13 deletions
+39
-13
packages/service/core/app/mcp.ts
+7
-0
packages/service/core/workflow/dispatch/ai/agent/sub/tool/index.ts
+4
-1
packages/service/core/workflow/dispatch/child/runTool.ts
+5
-1
packages/service/test/core/app/mcp.test.ts
+13
-1
projects/app/src/pages/api/core/app/mcpTools/create.ts
+3
-0
projects/app/src/pages/api/core/app/mcpTools/getTools.ts
+2
-5
projects/app/src/pages/api/core/app/mcpTools/runTool.ts
+2
-5
projects/app/src/pages/api/core/app/mcpTools/update.ts
+3
-0
No files found.
packages/service/core/app/mcp.ts
View file @
c1c6b952
...
...
@@ -10,9 +10,16 @@ import type { McpToolDataType } from '@fastgpt/global/core/app/tool/mcpTool/type
import
{
UserError
}
from
'@fastgpt/global/common/error/utils'
;
import
$RefParser
from
'@apidevtools/json-schema-ref-parser'
;
import
{
getLogger
,
LogCategories
}
from
'../../common/logger'
;
import
{
isInternalAddress
,
PRIVATE_URL_TEXT
}
from
'../../common/system/utils'
;
const
logger
=
getLogger
(
LogCategories
.
MODULE
.
APP
.
MCP_TOOLS
);
export
const
assertMCPUrlNotInternal
=
async
(
url
:
string
)
=>
{
if
(
await
isInternalAddress
(
url
))
{
return
Promise
.
reject
(
PRIVATE_URL_TEXT
);
}
};
export
class
MCPClient
{
private
client
:
Client
;
private
url
:
string
;
...
...
packages/service/core/workflow/dispatch/ai/agent/sub/tool/index.ts
View file @
c1c6b952
...
...
@@ -16,7 +16,7 @@ import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import
{
pushTrack
}
from
'../../../../../../../common/middle/tracks/utils'
;
import
{
getErrText
}
from
'@fastgpt/global/common/error/utils'
;
import
{
getAppVersionById
}
from
'../../../../../../app/version/controller'
;
import
{
MCPClient
}
from
'../../../../../../app/mcp'
;
import
{
assertMCPUrlNotInternal
,
MCPClient
}
from
'../../../../../../app/mcp'
;
import
{
runHTTPTool
}
from
'../../../../../../app/http'
;
import
{
getS3ChatSource
}
from
'../../../../../../../common/s3/sources/chat'
;
import
{
parseToolId
}
from
'../../../../child/runTool'
;
...
...
@@ -197,6 +197,9 @@ export const dispatchTool = async ({
const
{
headerSecret
,
url
}
=
tool
.
nodes
[
0
].
toolConfig
?.
mcpToolSet
??
tool
.
nodes
[
0
].
inputs
[
0
].
value
;
await
assertMCPUrlNotInternal
(
url
);
const
mcpClient
=
new
MCPClient
({
url
,
headers
:
getSecretValue
({
...
...
packages/service/core/workflow/dispatch/child/runTool.ts
View file @
c1c6b952
...
...
@@ -7,7 +7,7 @@ import {
type
ModuleDispatchProps
}
from
'@fastgpt/global/core/workflow/runtime/type'
;
import
{
NodeInputKeyEnum
}
from
'@fastgpt/global/core/workflow/constants'
;
import
{
MCPClient
}
from
'../../../app/mcp'
;
import
{
assertMCPUrlNotInternal
,
MCPClient
}
from
'../../../app/mcp'
;
import
{
getSecretValue
}
from
'../../../../common/secret/utils'
;
import
type
{
McpToolDataType
}
from
'@fastgpt/global/core/app/tool/mcpTool/type'
;
import
type
{
HttpToolConfigType
}
from
'@fastgpt/global/core/app/tool/httpTool/type'
;
...
...
@@ -214,6 +214,8 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
const
{
headerSecret
,
url
}
=
tool
.
nodes
[
0
].
toolConfig
?.
mcpToolSet
??
tool
.
nodes
[
0
].
inputs
[
0
].
value
;
await
assertMCPUrlNotInternal
(
url
);
const
context
=
getWorkflowContext
();
// Buffer mcpClient in this workflow
const
mcpClient
=
...
...
@@ -301,6 +303,8 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
const
{
toolData
,
system_toolData
,
...
restParams
}
=
params
;
const
{
name
:
toolName
,
url
,
headerSecret
}
=
toolData
||
system_toolData
;
await
assertMCPUrlNotInternal
(
url
);
const
mcpClient
=
new
MCPClient
({
url
,
headers
:
getSecretValue
({
...
...
packages/service/test/core/app/mcp.test.ts
View file @
c1c6b952
...
...
@@ -18,7 +18,7 @@ vi.mock('@fastgpt/service/core/app/schema', () => ({
}
}));
import
{
MCPClient
,
getMCPChildren
}
from
'@fastgpt/service/core/app/mcp'
;
import
{
MCPClient
,
assertMCPUrlNotInternal
,
getMCPChildren
}
from
'@fastgpt/service/core/app/mcp'
;
import
type
{
AppSchemaType
}
from
'@fastgpt/global/core/app/type'
;
// Access private client via prototype for spying
...
...
@@ -38,6 +38,18 @@ beforeEach(() => {
describe
(
'MCPClient'
,
()
=>
{
const
config
=
{
url
:
'http://localhost:3000/mcp'
,
headers
:
{
Authorization
:
'Bearer test'
}
};
describe
(
'assertMCPUrlNotInternal'
,
()
=>
{
it
(
'should reject localhost MCP endpoints'
,
async
()
=>
{
await
expect
(
assertMCPUrlNotInternal
(
'http://localhost:3000/mcp'
)).
rejects
.
toBe
(
'Request to private network not allowed'
);
});
it
(
'should allow public MCP endpoints'
,
async
()
=>
{
await
expect
(
assertMCPUrlNotInternal
(
'https://example.com/mcp'
)).
resolves
.
toBeUndefined
();
});
});
// Helper: stub getConnection to avoid real network calls
const
stubConnection
=
(
mcpClient
:
MCPClient
)
=>
{
const
client
=
getPrivateClient
(
mcpClient
);
...
...
projects/app/src/pages/api/core/app/mcpTools/create.ts
View file @
c1c6b952
...
...
@@ -17,6 +17,7 @@ import {
type
CreateMcpToolsBodyType
,
type
CreateMcpToolsResponseType
}
from
'@fastgpt/global/openapi/core/app/mcpTools/api'
;
import
{
assertMCPUrlNotInternal
}
from
'@fastgpt/service/core/app/mcp'
;
export
type
createMCPToolsQuery
=
{};
...
...
@@ -37,6 +38,8 @@ async function handler(
?
await
authApp
({
req
,
appId
:
parentId
,
per
:
WritePermissionVal
,
authToken
:
true
})
:
await
authUserPer
({
req
,
authToken
:
true
,
per
:
TeamAppCreatePermissionVal
});
await
assertMCPUrlNotInternal
(
url
);
await
checkTeamAppTypeLimit
({
teamId
,
appCheckType
:
'tool'
});
const
formatedHeaderAuth
=
storeSecretValue
(
headerSecret
);
...
...
projects/app/src/pages/api/core/app/mcpTools/getTools.ts
View file @
c1c6b952
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
type
{
ApiRequestProps
,
ApiResponseType
}
from
'@fastgpt/service/type/next'
;
import
{
MCPClient
}
from
'@fastgpt/service/core/app/mcp'
;
import
{
assertMCPUrlNotInternal
,
MCPClient
}
from
'@fastgpt/service/core/app/mcp'
;
import
{
getSecretValue
}
from
'@fastgpt/service/common/secret/utils'
;
import
{
GetMcpToolsBodySchema
,
...
...
@@ -8,7 +8,6 @@ import {
type
GetMcpToolsBodyType
,
type
GetMcpToolsResponseType
}
from
'@fastgpt/global/openapi/core/app/mcpTools/api'
;
import
{
isInternalAddress
,
PRIVATE_URL_TEXT
}
from
'@fastgpt/service/common/system/utils'
;
import
{
authCert
}
from
'@fastgpt/service/support/permission/auth/common'
;
async
function
handler
(
...
...
@@ -19,9 +18,7 @@ async function handler(
const
{
url
,
headerSecret
}
=
GetMcpToolsBodySchema
.
parse
(
req
.
body
);
if
(
await
isInternalAddress
(
url
))
{
return
Promise
.
reject
(
PRIVATE_URL_TEXT
);
}
await
assertMCPUrlNotInternal
(
url
);
const
mcpClient
=
new
MCPClient
({
url
,
...
...
projects/app/src/pages/api/core/app/mcpTools/runTool.ts
View file @
c1c6b952
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
type
{
ApiRequestProps
,
ApiResponseType
}
from
'@fastgpt/service/type/next'
;
import
{
MCPClient
}
from
'@fastgpt/service/core/app/mcp'
;
import
{
assertMCPUrlNotInternal
,
MCPClient
}
from
'@fastgpt/service/core/app/mcp'
;
import
{
getSecretValue
}
from
'@fastgpt/service/common/secret/utils'
;
import
{
RunMcpToolBodySchema
,
type
RunMcpToolBodyType
,
type
RunMcpToolResponseType
}
from
'@fastgpt/global/openapi/core/app/mcpTools/api'
;
import
{
isInternalAddress
,
PRIVATE_URL_TEXT
}
from
'@fastgpt/service/common/system/utils'
;
import
{
authCert
}
from
'@fastgpt/service/support/permission/auth/common'
;
async
function
handler
(
...
...
@@ -18,9 +17,7 @@ async function handler(
const
{
url
,
toolName
,
headerSecret
,
params
}
=
RunMcpToolBodySchema
.
parse
(
req
.
body
);
if
(
await
isInternalAddress
(
url
))
{
return
Promise
.
reject
(
PRIVATE_URL_TEXT
);
}
await
assertMCPUrlNotInternal
(
url
);
const
mcpClient
=
new
MCPClient
({
url
,
...
...
projects/app/src/pages/api/core/app/mcpTools/update.ts
View file @
c1c6b952
...
...
@@ -13,6 +13,7 @@ import {
UpdateMcpToolsBodySchema
,
type
UpdateMcpToolsBodyType
}
from
'@fastgpt/global/openapi/core/app/mcpTools/api'
;
import
{
assertMCPUrlNotInternal
}
from
'@fastgpt/service/core/app/mcp'
;
export
type
updateMCPToolsQuery
=
{};
...
...
@@ -20,6 +21,8 @@ async function handler(req: ApiRequestProps<UpdateMcpToolsBodyType>, res: ApiRes
const
{
appId
,
url
,
toolList
,
headerSecret
}
=
UpdateMcpToolsBodySchema
.
parse
(
req
.
body
);
const
{
app
}
=
await
authApp
({
req
,
authToken
:
true
,
appId
,
per
:
ManagePermissionVal
});
await
assertMCPUrlNotInternal
(
url
);
const
formatedHeaderAuth
=
storeSecretValue
(
headerSecret
);
// create tool set node
...
...
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