Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .agents/design/support/mcp-publish-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# MCP 发布鉴权与身份代理

## 任务概述

MCP 发布能力与个人 APIKey 的管理和执行语义保持一致:

- 管理列表仅返回当前团队成员创建的 MCP 发布项。
- `tools/list` 是公开元数据接口,持有发布 key 即可读取工具描述,不校验发布者的实时应用权限。
- `tools/call` 是执行接口,每次调用都使用有效团队成员身份校验目标应用读权限。
- 团队 owner 可以为发布项开启 `authProxy`。调用方通过
`x-fastgpt-auth-proxy-username` 或 `x-fastgpt-auth-proxy-tmb-id` 指定代理成员;两个请求头同时存在时必须指向同一成员。

## 设计

### 管理边界

`mcp_keys` 继续记录创建成员 `tmbId`。列表固定按 `{ teamId, tmbId }` 查询,更新和删除也只允许创建成员操作,团队管理权限不扩大到其他成员的发布项。

### 公开协议

`tools/list` 只根据发布 key 读取绑定应用及最新版本并生成 tool schema。该路径不解析身份代理,也不调用应用权限鉴权。

`tools/call` 根据发布 key 读取 `teamId`、`tmbId`、`authProxy` 和绑定应用:

1. 未传身份代理时,以发布者 `tmbId` 作为有效成员。
2. 传入身份代理时,要求发布项已开启 `authProxy`。
3. username 和 tmbId 都只能解析到当前团队内未离开的成员;同时传入时必须匹配同一成员。
4. 使用有效成员调用 `authAppByTmbId(..., ReadPermissionVal)`,通过后再运行工作流。
5. 对话记录、运行用户信息和工作流 `uid` 都归属有效成员。

Streamable HTTP 直接读取请求头。独立 SSE 服务在建立连接时保存代理请求头,并在每次 `tools/call` 转发到主应用;`tools/list` 不转发身份信息。

### 兼容性

`authProxy` 缺省为 `false`,旧记录无需迁移。未使用代理头的现有 MCP 客户端仍以发布者身份执行;发布者失去目标应用读权限后,后续执行会立即失败。

## 风险与注意事项

- 发布 key 仍是执行凭证,需要按密钥管理;公开仅指工具描述无需额外用户登录态。
- 身份代理不跨团队,已离开成员不能继续被代理。
- SSE 连接只缓存调用方提供的代理标识,发布项开关与成员权限在每次执行时重新读取。

## TODO

- [x] MCP schema、OpenAPI schema 和前端类型增加 `authProxy`。
- [x] 管理列表及 CRUD 权限收敛到创建成员。
- [x] 创建/更新接口增加 owner-only `authProxy` 校验。
- [x] 实现代理身份解析和执行时应用权限校验。
- [x] Streamable HTTP 与 SSE 转发代理请求头。
- [x] 发布表单增加 owner-only 身份代理开关及多语言文案。
- [x] 补充局部测试并运行类型检查。
70 changes: 68 additions & 2 deletions document/content/guide/build/publish/mcp_server.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ MCP has two main components: Client and Server. The Client is the AI model consu

FastGPT's MCP Server feature lets you select `multiple` applications built on FastGPT and expose them via MCP protocol for external consumption.

Currently, FastGPT's MCP Server uses the SSE transport protocol, with plans to migrate to `HTTP Streamable` in the future.
FastGPT supports the `Streamable HTTP` transport. Self-hosted deployments can also expose the compatible `SSE` transport through the standalone MCP Server service.

## Using MCP Server in FastGPT

Expand All @@ -35,7 +35,7 @@ After creating an MCP Server, click `Start Using` to get the access URL.
| -------------------------- | -------------------------- |
| ![](/imgs/mcp_server4.png) | ![](/imgs/mcp_server5.png) |

#### 3. Use the MCP Server
### 3. Use the MCP Server

Use the URL in any MCP-compatible client to call your FastGPT applications — for example, `Cursor` or `Cherry Studio`. Here's how to set it up in Cursor.

Expand All @@ -54,6 +54,72 @@ After sending a question about `fastgpt`, you'll see Cursor invoke an MCP tool (
| -------------------------- | --------------------------- |
| ![](/imgs/mcp_server9.png) | ![](/imgs/mcp_server10.png) |

## Configure Identity Proxy

Identity proxy lets a caller select the team member who executes an MCP tool. FastGPT checks that member's read permission for the target application and attributes chat and runtime records to that member. Use it when a gateway or shared MCP client sends requests on behalf of different team members.

### Prerequisites

- Only a team owner can enable identity proxy for an MCP Server.
- The proxied user must be an active member of the same team and have read permission for the target application.
- The key in the published MCP URL is an execution credential. Do not commit it to public code or share it with unrelated users.

### 1. Enable identity proxy

Create or edit an MCP Server, turn on `Auth proxy` in the publishing settings, and save the configuration.

When a request does not include an identity proxy header, the tool continues to run as the MCP Server publisher.

### 2. Configure the proxy identity headers

Pass the identity in the MCP transport headers, not in the tool arguments. FastGPT accepts these headers:

| Header | Value | Description |
| ------------------------------- | -------------------------- | ---------------------------------------------------------------- |
| `x-fastgpt-auth-proxy-username` | Team member login username | Recommended; this is usually the member's login email address |
| `x-fastgpt-auth-proxy-tmb-id` | FastGPT team member ID | Use this when your system already stores FastGPT team member IDs |

Either header is sufficient. If you provide both, they must resolve to the same team member.

For MCP clients that support custom headers, add `headers` to the configuration copied in step 2. This example uses a `Streamable HTTP` URL and a login username:

```json
{
"mcpServers": {
"fastgpt": {
"url": "https://fastgpt.example.com/api/mcp/app/<MCP_KEY>/mcp",
"headers": {
"x-fastgpt-auth-proxy-username": "member@example.com"
}
}
}
}
```

To use a team member ID, replace `headers` with:

```json
{
"x-fastgpt-auth-proxy-tmb-id": "<TEAM_MEMBER_ID>"
}
```

SSE URLs use the same headers. The SSE service captures the proxy identity when it establishes the connection, so reconnect after changing a header. `Streamable HTTP` reads the headers for each request.

### 3. Verify the configuration

Call a published tool from the MCP client. A successful call confirms that the proxied user is still an active team member and has read permission for the target application.

If the tool list loads but a tool call returns an authorization error, check the following:

1. Identity proxy is enabled for the MCP Server.
2. The username or team member ID in the request header is correct.
3. If both headers are present, they identify the same member.
4. The member is still active in the team that published the MCP Server.
5. The member has read permission for the application being called.

The tool list exposes only metadata such as tool names and parameters. FastGPT rechecks team membership and application permission for every tool call.

## Self-Hosted MCP Server Setup

Self-hosted FastGPT deployments require version `v4.9.6` or higher to use MCP Server.
Expand Down
70 changes: 68 additions & 2 deletions document/content/guide/build/publish/mcp_server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ MCP 协议主要包含 Client 和 Server 两部分。简单来说,Client 是

FastGPT MCP Server 功能允许你选择 `多个` 在 FastGPT 上构建好的应用,以 MCP 协议对外提供调用 FastGPT 应用的能力。

目前 FastGPT 提供的 MCP server 为 SSE 通信协议,未来将会替换成 `HTTP streamable`
FastGPT 支持 `Streamable HTTP` 协议;私有化部署还可以通过独立的 MCP Server 服务提供兼容的 `SSE` 协议

## FastGPT 使用 MCP server

Expand All @@ -35,7 +35,7 @@ FastGPT MCP Server 功能允许你选择 `多个` 在 FastGPT 上构建好的应
| -------------------------- | -------------------------- |
| ![](/imgs/mcp_server4.png) | ![](/imgs/mcp_server5.png) |

#### 3. 使用 MCP server
### 3. 使用 MCP server

可以在支持 MCP 协议的客户端使用这些地址,来调用 FastGPT 应用,例如:`Cursor`、`Cherry Studio`。下面以 Cursor 为例,介绍如何使用 MCP server。

Expand All @@ -54,6 +54,72 @@ FastGPT MCP Server 功能允许你选择 `多个` 在 FastGPT 上构建好的应
| -------------------------- | --------------------------- |
| ![](/imgs/mcp_server9.png) | ![](/imgs/mcp_server10.png) |

## 配置身份代理

身份代理允许调用方指定团队成员来执行 MCP 工具。工具执行时会校验该成员对目标应用的读取权限,对话记录和运行记录也归属该成员。适合由网关或统一 MCP 客户端代表不同团队成员发起调用的场景。

### 前提条件

- 只有团队所有者可以为 MCP server 开启身份代理。
- 被代理用户必须是当前团队内未离开的成员,并且拥有目标应用的读取权限。
- MCP 发布地址中的 key 是执行凭证,请勿写入公开代码或发送给无关人员。

### 1. 开启身份代理

创建或编辑 MCP server,在发布配置中打开 `身份代理`,然后保存。

未携带身份代理请求头时,工具仍以 MCP server 发布者的身份执行。

### 2. 配置代理身份请求头

身份信息需要放在 MCP transport 请求头中,不要放入工具参数。支持以下请求头:

| 请求头 | 值 | 说明 |
| ------------------------------- | -------------------- | ------------------------------ |
| `x-fastgpt-auth-proxy-username` | 团队成员的登录用户名 | 推荐使用,通常为成员的登录邮箱 |
| `x-fastgpt-auth-proxy-tmb-id` | FastGPT 团队成员 ID | 适合已经保存团队成员 ID 的系统 |

两个请求头任选一个即可。同时提供时,必须指向同一团队成员。

支持自定义请求头的 MCP 客户端可以在第 2 步复制的配置中增加 `headers`。以下示例使用 `Streamable HTTP` 地址和登录用户名:

```json
{
"mcpServers": {
"fastgpt": {
"url": "https://fastgpt.example.com/api/mcp/app/<MCP_KEY>/mcp",
"headers": {
"x-fastgpt-auth-proxy-username": "member@example.com"
}
}
}
}
```

使用团队成员 ID 时,将 `headers` 替换为:

```json
{
"x-fastgpt-auth-proxy-tmb-id": "<TEAM_MEMBER_ID>"
}
```

SSE 地址使用相同的请求头。SSE 服务会在建立连接时保存代理身份,因此修改请求头后需要断开并重新连接;`Streamable HTTP` 会逐次读取请求头。

### 3. 验证配置

在 MCP 客户端调用一个已发布工具。调用成功表示代理成员仍在当前团队,并且拥有目标应用的读取权限。

工具列表可以正常显示、工具执行却提示无权限时,依次检查:

1. MCP server 是否已开启 `身份代理`。
2. 请求头中的用户名或团队成员 ID 是否正确。
3. 同时传入两个请求头时,它们是否对应同一成员。
4. 该成员是否仍在发布 MCP server 的团队中。
5. 该成员是否拥有被调用应用的读取权限。

工具列表只公开工具名称和参数等元数据;FastGPT 会在每次工具执行时重新校验成员状态和应用权限。

## 私有化部署 MCP server 问题

私有化部署版本的 FastGPT,需要升级到 `v4.9.6` 及以上版本才可使用 MCP server 功能。
Expand Down
43 changes: 43 additions & 0 deletions packages/global/openapi/support/mcpServer/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const McpListResponseItemSchema = z.object({
key: z.string().meta({ example: 'abcDEF123...', description: 'MCP Server 访问密钥' }),
teamId: ObjectIdSchema.meta({ description: '团队 ID' }),
tmbId: ObjectIdSchema.meta({ description: '团队成员 ID' }),
authProxy: z.boolean().default(false).meta({ description: '是否允许调用方代理团队成员身份' }),
apps: z.array(McpAppSchema).meta({ description: '应用工具列表' })
});
export const McpListResponseSchema = z.array(McpListResponseItemSchema);
Expand All @@ -57,6 +58,9 @@ export type McpListResponseType = z.infer<typeof McpListResponseSchema>;

export const McpCreateBodySchema = z.object({
name: McpNameSchema,
authProxy: z.boolean().default(false).meta({
description: '是否允许调用方代理团队成员身份,仅团队所有者可开启'
}),
apps: McpAppsBodySchema
});
export type McpCreateBodyType = z.infer<typeof McpCreateBodySchema>;
Expand All @@ -72,6 +76,9 @@ export type McpCreateResponseType = z.infer<typeof McpCreateResponseSchema>;
export const McpUpdateBodySchema = z.object({
id: ObjectIdSchema.meta({ description: 'MCP Server ID' }),
name: McpNameSchema.optional(),
authProxy: z.boolean().optional().meta({
description: '是否允许调用方代理团队成员身份,仅团队所有者可开启'
}),
apps: McpAppsBodySchema
});
export type McpUpdateBodyType = z.infer<typeof McpUpdateBodySchema>;
Expand All @@ -91,3 +98,39 @@ export type McpDeleteQueryType = z.infer<typeof McpDeleteQuerySchema>;

export const McpDeleteResponseSchema = z.undefined().meta({ description: '删除成功' });
export type McpDeleteResponseType = z.infer<typeof McpDeleteResponseSchema>;

/* ============================================================================
* API: 获取已发布 MCP Server 的工具列表
* Route: GET /api/support/mcp/server/toolList
* ============================================================================ */

export const McpToolListQuerySchema = z.object({
key: z.string().min(1).meta({ description: 'MCP Server 发布密钥' })
});

/* ============================================================================
* API: 调用已发布 MCP Server 的工具
* Route: POST /api/support/mcp/server/toolCall
* ============================================================================ */

export const McpAuthProxySchema = z
.object({
username: z.string().trim().min(1).max(128).optional().meta({
example: 'user@example.com',
description: '代理调用的团队成员用户名'
}),
tmbId: ObjectIdSchema.optional().meta({
description: '代理调用的团队成员 ID'
})
})
.strict()
.refine(({ username, tmbId }) => !!username || !!tmbId, {
message: 'authProxy.username or authProxy.tmbId is required'
});
export type McpAuthProxyType = z.infer<typeof McpAuthProxySchema>;

export const McpToolCallBodySchema = z.object({
key: z.string().min(1).meta({ description: 'MCP Server 发布密钥' }),
toolName: z.string().min(1).meta({ description: '要调用的工具名称' }),
inputs: z.record(z.string(), z.any()).meta({ description: '工具调用参数' })
});
6 changes: 6 additions & 0 deletions packages/global/support/mcp/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type McpKeyType = {
tmbId: string;
apps: McpAppType[];
name: string;
authProxy: boolean;
};

export type McpAppType = {
Expand All @@ -13,3 +14,8 @@ export type McpAppType = {
toolName: string;
description: string;
};

export const McpAuthProxyHeader = {
username: 'x-fastgpt-auth-proxy-username',
tmbId: 'x-fastgpt-auth-proxy-tmb-id'
} as const;
4 changes: 4 additions & 0 deletions packages/service/support/mcp/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const McpKeySchema = new Schema({
ref: TeamMemberCollectionName,
required: true
},
authProxy: {
type: Boolean,
default: false
},
apps: {
type: [
{
Expand Down
3 changes: 2 additions & 1 deletion packages/service/support/permission/mcp/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const authMcp = async ({
return Promise.reject(TeamErrEnum.unPermission);
}

if (!permission.hasManagePer && !isRoot && tmbId !== String(mcp.tmbId)) {
// MCP 发布项与个人 APIKey 一样只归创建成员管理。
if (tmbId !== String(mcp.tmbId)) {
return Promise.reject(TeamErrEnum.unPermission);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/web/i18n/en/dashboard_mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"app_description": "Application Description",
"app_name": "Application name",
"apps": "Exposed applications",
"auth_proxy": "Auth proxy",
"auth_proxy_tip": "Allow callers to run tools as a team member through request headers. Only team owners can enable it.",
"create_mcp": "Create an MCP service",
"create_mcp_server": "Create a new service",
"delete_mcp_server_confirm_tip": "Confirm to delete the service?",
Expand Down
2 changes: 2 additions & 0 deletions packages/web/i18n/zh-CN/dashboard_mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"app_description": "应用描述",
"app_name": "应用名",
"apps": "暴露的应用",
"auth_proxy": "身份代理",
"auth_proxy_tip": "允许调用方通过请求头代理团队成员身份执行工具。仅团队所有者可开启。",
"create_mcp": "创建 MCP 服务",
"create_mcp_server": "新建服务",
"delete_mcp_server_confirm_tip": "确认删除该服务?",
Expand Down
2 changes: 2 additions & 0 deletions packages/web/i18n/zh-Hant/dashboard_mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"app_description": "應用描述",
"app_name": "應用名",
"apps": "暴露的應用",
"auth_proxy": "身份代理",
"auth_proxy_tip": "允許調用方透過請求標頭代理團隊成員身份執行工具。僅團隊擁有者可開啟。",
"create_mcp": "創建 MCP 服務",
"create_mcp_server": "新建服務",
"delete_mcp_server_confirm_tip": "確認刪除該服務?",
Expand Down
Loading
Loading