@forestadmin/mcp-服务器
[大 森林出入口的内部构件. 用于新部署,使用
@forestadmin/gateway编辑forest-mcp-server命令继续工作, 贬值.
具有OAuth认证支持的森林管理员模型背景协议(MCP)服务器.
概览
这个 MCP 服务器提供 HTTP REST API 访问 Forest Admin 操作,使 AI 助手和其他 MCP 客户端能够通过标准化的协议与您的 Forest Admin 数据互动.
可用工具
| 工具 | 说明 |
|---|---|
describeCollection | 获取收藏的策略( 字段、 类型、 关系) |
list | 从收藏中获取记录 |
listRelated | 检索相关记录 |
create | 创建新记录 |
update | 更新现有记录 |
delete | 删除记录 |
associate | 关联记录 |
dissociate | 将记录与关系分离 |
getActionForm | 获取自定义动作的窗体字段 |
executeAction | 执行自定义动作 |
requestActionFileUpload | 获取上传文件的目的地, 用于动作 File 字段(仅与 fileUploads(中文(简体) ) |
listWorkflows | 在调用者的渲染中列出 MCP 启用的工作流程, 可选择通过收藏过滤 |
triggerWorkflow | 在记录上启动工作流程运行,然后返回 runId |
getWorkflowRun | 读取从 MCP 开始的运行: 它的状态和一步步的历史 |
triggerWorkflow 启动一个带有副作用的进程, 因此它会被附加注释 destructiveHint: true 财务报告和审定财务报表
MCP客户端应在每次通话前询问用户. 就像其他工具一样 启用者
默认,但它保持惰性,直到有人打开 MCP 触发器 在森林的某个工作流程
——服务器拒绝任何未选择进入的工作流程的触发器.
使用量
森林管理员
MCP服务器包含有森林管理员代理. 仅此而已 mountAiMcpServer()数字 :
import { createAgent } from '@forestadmin/agent';
const agent = createAgent(options)
.addDataSource(myDataSource)
.mountAiMcpServer();
agent.mountOnExpress(app);
agent.start();
MCP 服务器将被自动初始化并挂载在您的应用程序上 .
独立服务器
您可以使用 CLI 单独运行 MCP 服务器 :
npx forest-mcp-server
或者从软件包目录 :
yarn start # Production
yarn start:dev # Development (loads .env file automatically)
环境变量
| 变量 | 需求 | 默认 | 说明 |
|---|---|---|---|
FOREST_ENV_SECRET | 对 | - 说吧 | 您的森林管理环境秘密 |
FOREST_AUTH_SECRET | 对 | - 说吧 | 您的森林管理员认证密钥( 必须匹配您的代理) |
MCP_SERVER_PORT | 没有 | 3931 | HTTP 服务器端口 |
FOREST_MCP_SERVER_URL | 没有 | http://localhost:<port> | 公共 URL 此服务器可访问 http( s) 仅来源没有路径. 需要用于任何已部署的服务器:没有它 OAuth 元数据广告 localhost,和内嵌上传商店薄荷 localhost 上传任何远程客户端都无法到达的 URL |
FOREST_MCP_ENABLED_TOOLS | 没有 | - 说吧 | 以逗号分隔的工具列表以启用( allowlist) |
FOREST_MCP_ALLOWED_OAUTH_CLIENTS | 没有 | - 说吧 | 允许连接的 OAuth 客户端应用程序的逗号分隔域( )allowedOAuthClients) (中文(简体) ). 未设置, 接受任何注册客户端 |
FOREST_AGENT_URL | 没有 | 您的环境后端 URL | MCP服务器用来到达后端的数据层的URL. 当服务器运行在内部地址(如. http://localhost:3310,而不是在Forest注册的公开URL |
FOREST_MCP_ACCESS_TOKEN_TTL_SECONDS | 没有 | 3600 (1小时) | OAuth 访问的最大使用寿命表示服务器问题(tokenTtl.accessTokenSeconds) (中文(简体) ). 最低 60 |
FOREST_MCP_REFRESH_TOKEN_TTL_SECONDS | 没有 | 无约束 | 两个交互式登录之间的最大时间( E)tokenTtl.refreshTokenSeconds) (中文(简体) ). 取消设置,一个不断刷新的客户端 永远不会再次签名 。 最低 60 |
FOREST_MCP_FILE_UPLOADS | 没有 | - 说吧 | false 关闭动作文件上传( 它们是) 打开 (默认情况下,为内存). 任何其他数值,但 true编号 :false 启动时失败 |
FOREST_MCP_UPLOAD_STORAGE_MODULE | 没有 | - 说吧 | 模块路径 fileUploads 选项,用于真正的存储后端. FOREST_MCP_FILE_UPLOADS=false 胜过它 |
示例配置
创建一个 .env 软件包目录中的文件 :
FOREST_ENV_SECRET="your-env-secret"
FOREST_AUTH_SECRET="your-auth-secret"
然后运行 :
yarn start:dev
或设置内置变量 :
FOREST_ENV_SECRET="your-env-secret" FOREST_AUTH_SECRET="your-auth-secret" npx forest-mcp-server
在公共網址后部署
FOREST_MCP_SERVER_URL="https://mcp.example.com" \
FOREST_ENV_SECRET="your-env-secret" FOREST_AUTH_SECRET="your-auth-secret" npx forest-mcp-server
限制工具
您可以限制 MCP 服务器使用的工具 enabledTools。只有所列工具. 未来发行中增加的新工具不会自动启用 -你必须明确加入.
例如,设立 只读模式 其中 AI 助手只能浏览数据( 不创建、 更新、 删除或动作执行) :
// With Forest Admin Agent — read-only example
agent.mountAiMcpServer({
enabledTools: ['describeCollection', 'list', 'listRelated'],
});
# Standalone
export FOREST_MCP_ENABLED_TOOLS="describeCollection,list,listRelated"
npx forest-mcp-server
何时 enabledTools 未设定,所有工具默认启用.
见 可用工具 完整名单. describeCollection 总是被启用,因为MCP服务器需要正常运行.
限制 OAuth 客户端
任何 OAuth 客户端都可以通过动态客户端注册对 MCP 服务器进行注册,一旦您的用户之一在注册中签名,就可以获得令牌. 使用 allowedOAuthClients 仅接受经批准的客户申请:
// With Forest Admin Agent
agent.mountAiMcpServer({
allowedOAuthClients: ['dust.tt'],
});
# Standalone
export FOREST_MCP_ALLOWED_OAUTH_CLIENTS="dust.tt"
npx forest-mcp-server
只有当 每个 重定向它注册的 URI 是 http(s) 列出域或其子域之一的URI(dust.tt 匹配 eu.dust.tt);定制方案被拒绝,因为它们向任何注册的本地应用程序提供回调,无论主机名如何. 匹配使用重定向URI,因为它们是冒名顶替者无法获益的注册元数据之一——授权代码只在那里交付. 自言自语的字段如客户端名被忽略.
仅列出裸域——unicode域通过它们的punycode形式相匹配. 包含一个方案、 端口、 路径或空格的条目在启动时失败, 包含完全没有域的配置值也是如此 : 允许列表从不会默默地回到接受或拒绝每个配置错误的人 .
每一个客户都被标准拒绝 invalid_client 错误让用户联系他们的管理员; 响应不会显示允许的域 。 注册本身仍然成功——它发生在森林管理员服务器上——客户端只是不能用它来对付这个服务器. 在您启用选项前发布的访问令牌将一直有效, 直至其过期( 最多1小时); 刷新立即被屏蔽 .
本地桌面客户端( Claude Desktop, MCP Victor,...) 注册回回路( )localhost) 重定向 URI, 域名允许列表从不匹配 —— 除非您明确列出, 否则将被拒绝 localhost,这将承认 每个 本地应用并击败限制。 在需要本地客户的环境(如开发)中选择选项.
短暂的托肯生命
森林赠款 1小时 (3600s)用于访问符和 8天时间 (691200s)作为新鲜的纪念物——但它在8天的时间内重新授予 每个 刷新,所以没有 refreshTokenSeconds 一个继续工作的客户再也不会被要求签字了.
这两个值都是上限的:它们只能缩短,永远不能扩展. 对于 accessTokenSeconds a 值超过3600,因此没有效果. refreshTokenSeconds 限制整个会话, 否则森林会在每一个刷新上重新扩展, 所以任何值都会缩短它, 不管它有多大.
// With Forest Agent
agent.mountAiMcpServer({
tokenTtl: { accessTokenSeconds: 900, refreshTokenSeconds: 86400 },
});
# Standalone
export FOREST_MCP_ACCESS_TOKEN_TTL_SECONDS=900
export FOREST_MCP_REFRESH_TOKEN_TTL_SECONDS=86400
npx forest-mcp-server
这两个设置在用户通知的内容上有所不同:
accessTokenSeconds缩短一个被泄露的访问符可以驱动的时间 此服务器:MCP路径关闭,其范围停止应用,其呼叫停止被审计. 没错 没有 缩短在JWT内所携带的森林令牌——JWT是签名的,而不是加密的,因此将泄漏作为森林令牌的泄漏处理,并在源头撤销. 它对用户是透明的——助理默默地获得一个新的.refreshTokenSeconds将时间限制在 2 之间 交互式登录:一旦被擦出,助手就不能再刷新了,用户再次通过浏览器进行签名. 它从登录本身来测量,因此一个活跃的助手无法继续延长它的会话. 在启用此选项前发布的刷新符号不会带有登录时间戳, 因此它们的窗口是从上次刷新中测量的 —— 每一次更长的会话, 然后被绑定 .
两个值的最小值为60秒;任何更低的值都会提高。 无效的值( 0, 负数, 分数) 在启动时失败, 而不是默默地让符号被封住 .
动作文件上传
实验. MCP 规格仍在设计自己的文件传输故事 (单位:千美元)SEP-2631 (英语)) (中文(简体) ). 这个
UploadStorage合同有望存续,但requestActionFileUpload工具与手柄 一旦着陆,格式可能会改变,以遵循规格.
与 文件字段 通常不能运行 MCP。 代理服务器期望文件值为数据
尿液,可以传送模型的上下文窗口,并超过大多数MCP客户的有效载荷限制.
这个 fileUploads 选项允许它们通过一个上传的侧通道来保存字节
对话:
- 客户打电话
requestActionFileUpload工具为{ "filename", "mimeType", "sha256"? },并收到预先授权的上传 URL+签名fileHandle绳子. - 客户端将生字节直接上传到存储后端,因此他们从不通过MCP服务器或模型.
- 客户通过手柄(
"$uploadedFile:<...>"作为字段值executeAction。服务器下载对象并将其交给代理人。 模型只交换过小手柄.
requestActionFileUpload 如下 enabledTools 就像其他工具一样,所以一个不放出去的服务器从不做广告,也从不为上传端点服务.
上传的 URL 是未经认证的 —— 该模型没有代理证书, 并且不能。 因此 URL
它本身就是授权,就像S3预先签名的PUT一样. 它携带一个随机的uuid,被拒绝
在读取一个字节之前,除非该服务器发布,否则过期 uploadUrlTtlSeconds,并服务
除了那个 PUT对内幕商店,它也 接受单一上传: 一旦
字节土地,被泄露的URL已经无法取而代之了. 写作也不费钱
需要签名的手柄,该手柄绑定在请求它的用户身上.
预先签名的后端 URL 是不同的动物: 它通常是 可重播 直至失效——S3
接受数量 PUT适用于 expiresInSeconds 因此,一个 URL 泄露到访问日志中
覆盖字节 之后 合法上传和动作运行前。 这个 sha256 针是
即刻覆盖每个后端的防御: S3 把它签入 URL,所以不同的有效载荷是
在上传时被拒绝, 赎回会重新验证摘要, 不管后端是什么
检查。 工具指示模型默认会标出; 将未标注的上传视为接受
窗口.
没有东西可以提供,也没有东西可以打开
这是默认的。 没有 storage,服务器在内存中持有对象并服务于自身
上传终点 <origin>/mcp/uploads编辑 fileUploads 仅选项 配置 这 -- -- a
后端, 大小限制, ttls :
agent.mountAiMcpServer(); // in memory, single instance
agent.mountAiMcpServer({ fileUploads: { storage } }); // a real backend
要关闭特性,通过 fileUploads: false 在独立服务器上,
FOREST_MCP_FILE_UPLOADS=false。工具没有注册,上传端点从未挂起,
财务报告和审定财务报表 executeAction 别再提了 穿过 enabledTools 也会有用,但是
允许列表 —— 拒绝这个特征,这意味着命名所有其他工具并选择退出
之后所有的东西都运走了.
只有一个例子. 上传和赎罪是两个独立的请求. 有几个 复制品,在集群模式下,或者在无服务器运行时,其中之一着陆 在从未见过对方的情况下,行动失败——断断续续地说 片面特征,而不是配置错误。 在重启时也丢失对象 。 服务器警告 请求上传目的地的首次请求,以及失败名称. 这些部署需要
storage。 。 。 .
ephemeralMaxTotalBytes 限制内存店持有的所有待上传内容, 64 MiB by
默认。 重编文件不会释放它——对象活到 handleTtlSeconds 如此重试
在失败的动作仍然找到它后, 因此在默认情况下, 商店持有三个最大大小的
每个45分钟窗口的文件,而不是一个滚动的64 MiB。 以此为大小,或缩短
handleTtlSeconds它故意是绝对的,而不是多重的 maxBytes: 派生, 提高
每个文件的限度将乘以进程所能持之以恒.
带有存储后端
提供 storage 对于任何超出一个案例。 任何可以预授权的后端
上传并读取对象后行作品——S3预先签名的URL(下),GCS签名的URL,Azure SAS. 这个
软件包本身没有存储依赖性.
在独立的服务器上
上传内容与内幕商店同时上传,单告提示内容相同.
对于真正的后端,存储器是具有方法的对象,所以不同于其他独立选项
无法通过环境变量。 点 FOREST_MCP_UPLOAD_STORAGE_MODULE 在模块中
默认导出选项时失败
启动, 而不是以默禁的上传方式运行 :
// forest-upload-storage.js
module.exports = {
storage: {
/* createUploadUrl / download / getSize, as below */
},
maxBytes: 50 * 1024 * 1024,
downloadTimeoutSeconds: 10,
};
FOREST_MCP_UPLOAD_STORAGE_MODULE=./forest-upload-storage.js npx forest-mcp-server
模块还可以导出一个函数、同步或同步,返回相同的选项——当 后端需要获取证书 .
客户端必须能够上传
步骤2是普通的HTTPS请求,由客户端提出,在MCP协议之外. 客户必须 能够做到:
-
克劳德代码 海关人员:工作。 外壳和开发者运行在同一个机器上,所以 它达到一个
localhost代理也是 - 这是整个流动可以尝试结束的地方 对抗本地特工 已验. -
Claude桌面、Claude.ai和同事: 编码执行沙盒中所附文件区域 和模型可以
curl -X PUT -T <path> <uploadUrl>应用工具返回的每个信头, 由于一个被钉住sha256签名为x-amz-checksum-sha256在S3上, PUT 被拒绝 没有它。 两个条件都需要:uploadUrl必须能够公开接触. 沙盒是自己保管和运行的 网络,所以localhost或私人地址 永远无法从它, 不管是什么 已配置 。 在开发者机器上运行的代理无法通过这种方式进行测试.- 它的宿主必须允许外出交通 在那个沙盒里。 在配置时 客户端和版本之间的差异——在管理的工作空间上,设置可能不是 完全暴露给终端用户,在这种情况下,只有管理员才能解禁上传. 不要向用户保证菜单路径; 给他们主机允许 .
两者都是客户端, 并且不在服务器的控制范围内, 因此记录您的上传主机 用户.
已验证端到端 从一个 Claude 桌面聊天和一个同事云会话: 代理 在公开的 HTTPS URL 后面,主机添加到沙盒允许的域中, 而模型的
PUT通过。 在主机被允许之前,同一个沙盒回答了Host not in allowlist即使是普通公有领域——所以允许列表是条件2的全部, 满足它就够了。 Cowork 运行从一个自然的句子开始,没有工具 名称: 模型找到表格, 请求指定目的地, 并绑定sha256未发作,以及 没有工具参数带了基数64 – 与隧道中的请求体核对,而不是 记录.可以看到一个皱纹: 动作商店的文件名是客户端报告的内容, 以及 a 沙盒可能使其正常化(
rapport-1815.pdf以rapport1815.pdf字节和 mIME类型是准确的)。 把它当作标签,而不是标识符.
该工具在描述中陈述了这个先决条件,并在回应中重复它,所以一个模型 其上传被屏蔽 诊断在上下文.
试试看
packages/_example 不需要为此配置 – 没有云账户, 没有存储代码 .
内容 review 收藏带一个 Attach a document 与 a 动作 File (单位:千美元) FileList 字段.
启动示例代理, 将 MCP 客户端连接到它, 用文件请求此动作
动作报告它收到的名称、 mime 类型和字节数.
sequenceDiagram
participant Client as MCP client
participant Server as MCP server
participant Storage as Storage backend
participant Agent as Forest Admin agent
Client->>Server: requestActionFileUpload {filename, mimeType, sha256?}
Server-->>Client: uploadUrl + fileHandle (user-bound JWT)
Client->>Storage: PUT raw bytes to uploadUrl
Note over Client,Storage: bytes bypass the server and the model
Client->>Server: executeAction {values: {field: "$uploadedFile:..."}}
Server->>Storage: download object
Note over Server: verify user, TTL, maxBytes, sha256 pin
Server->>Agent: executeAction with the file
Agent-->>Server: action result
Server-->>Client: result (the model only saw the handle)
存储后端是可插接的,这个软件包没有存储依赖性. 提供
执行情况 UploadStorage; 任何可以预先授权上传并读取对象的后端
后置工作, 如 S3 预先签名的 URL (以下), GCS 签名的 URL, Azure SAS, 或您服务的终点
你自己来 唯一的硬要求是,从MCP客户端可以到达URL,因为
什么上传了字节.
import {
S3Client,
GetObjectCommand,
HeadObjectCommand,
PutObjectCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import type { UploadStorage } from '@forestadmin/mcp-server';
const s3 = new S3Client({});
const bucket = 'my-uploads-bucket';
const storage: UploadStorage = {
async createUploadUrl({ key, mimeType, sha256, expiresInSeconds }) {
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
ContentType: mimeType,
...(sha256 && { ChecksumSHA256: sha256 }),
});
const url = await getSignedUrl(s3, command, {
expiresIn: expiresInSeconds,
// Load-bearing: without it the checksum is hoisted to the query string, which S3 does not
// enforce — the pin would silently stop protecting the upload.
...(sha256 && { unhoistableHeaders: new Set(['x-amz-checksum-sha256']) }),
});
return {
url,
headers: { 'Content-Type': mimeType, ...(sha256 && { 'x-amz-checksum-sha256': sha256 }) },
};
},
async getSize(key) {
const head = await s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
return head.ContentLength;
},
async download(key) {
const object = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
return Buffer.from(await object.Body.transformToByteArray());
},
};
const server = new ForestMCPServer({
// ...
fileUploads: { storage },
});
其他选择是: keyPrefix (默认) mcp-uploads/, (中文(简体) ), uploadUrlTtlSeconds (违约15分钟),
handleTtlSeconds (默认为45分钟,比上传的URL更长,所以缓慢上传仍然会留下时间
运行动作, maxBytes (默认为20 MiB), maxConcurrentDownloads (违约5)和
downloadTimeoutSeconds (违约15 s),以及 ephemeralMaxTotalBytes (默认64 MiB,仅存内存商店).
下调 downloadTimeoutSeconds 如果客户打电话给你的代理 更快地削减请求。 这个
整体 executeAction 必须适应他们的超时:读取对象、编码和运行
你的动作。 如果读到传呼者活活在浪费工作之后
打电话者放弃之后的货币档期.
少数属性在生产中很重要.
- 服务器仍然处于无国籍状态. 手柄是签有JWT的
authSecret,所以没有数据库,也没有会话亲和性,任何复制品都可以赎回由他人发行的手柄. - 手柄绑定在它发给的用户身上. 只有用户的"熊鼠"(Bearer sord)才能赎回它,并且过期于
handleTtlSeconds它在那之前是可以赎回的,所以保持TTL短. - 当客户端发送时
sha256(hex或base64),上传的URL被固定在该摘要上,摘要在赎回时再次被下载的字节上检查. 上传 URL 泄漏后替换的内容无法赎回 . maxBytes不将记忆束缚在自己身上. 预先授权的上传 URL 无法总是限制对象大小, 因此限制在赎回时执行: 在下载时前getSize报告大小,只有在字节返回时才存在内存undefined。执行getSize只要后端能便宜地回答.maxConcurrentDownloads限制同步下载,而不是峰值内存 . 所有文件一个executeAction调用参考文献会一起保存, 直到调用完成, 所以一个带有 N 文件字段的表单会保存到 N×maxBytes无论货币限额是什么 大小maxBytes针对文件字段数, 您的动作声明 .- 超时读取被放弃, 不取消 .
UploadStorage.download采取不AbortSignal因此,当downloadTimeoutSeconds点出货币槽,而下面的读数会继续运行。 在后端比超时速慢 飞行中读数可以超过maxConcurrentDownloads保留downloadTimeoutSeconds低而慢的后端会迅速失效而不是积累. - 服务器从不删除对象, 一旦赎回, 也不会删除 :
executeAction在设置字段或运行前下载每个引用,因此在读取后,如果在以后出现任何故障时,会留下一个模型重试,其对象丢失的手柄,在上传时会告知失败。 配置存储后端的生命周期规则, 例如删除下面的对象keyPrefix一天后; 内饰店开垦handleTtlSeconds和它的总数.
仅 executeAction 解决了手柄. getActionForm 将字段值回溯到模型,所以
手柄保留一个手柄 : 解决它能使文件内容回到模型的背景中.
换钩从不见一柄. 打开 getActionForm 路径,文件引用从
tryToSetFields,因此另一个字段所发射的钩子读取文件字段为未设置,而不是一个
弦它会叫 .buffer 继续 打开 executeAction 路径控点已经解决, 所以
一个钩子按它期望的数据uri接收文件. 不管怎样 钩子都不必知道
侧通道存在.
API 终点
MCP服务器运行后会暴露出以下终点:
| 方法 | 路径 | 说明 |
|---|---|---|
| 邮政 | /mcp | 主 MCP 协议端点( 需要 Bearer 令牌) |
| 邮政 | /oauth/authorize | OAuth 2.0 授权 |
| 邮政 | /oauth/token | OAuth 2.0 令牌交换 |
| 获取 | /.well-known/oauth-protected-resource/mcp | OAuth 元数据发现 |
这个 /mcp 端点预期 MCP 协议消息( JSON- RPC 2. 0) , 并且需要至少带有一个有效的 OAuth Bearer 令牌 mcp:read 范围.
一个没有任何人的自主系统可以发送一个服务账户证书(Authorization: Bearer fgw_…) (中文(简体) ). 森林服务器解决了它,并在项目计划不包括Gateway MCP时拒绝. 会议随后作为服务账户,包括它的作用和团队.
特征
- HTTP 运输: 用于MCP通信的可流式HTTP运输
- OAuth 认证: 由 OAuth 2.0 搭建,单机
mcp:read范围;用户可以做什么由森林许可决定 - CORS 已启用: 允许跨源请求
- 基于快件: 为可靠性和可扩展性而建于Express.js上方
发展
大楼
yarn build
监视模式
yarn build:watch
线性
yarn lint
测试
yarn test
清理
yarn clean
内部环境变量
这些只是森林管理员开发人员需要的(例如指向本地或中转服务器):
| 变量 | 默认 | 说明 |
|---|---|---|
FOREST_SERVER_URL | https://api.forestadmin.com | 森林管理员 API URL |
FOREST_APP_URL | https://app.forestadmin.com | 森林管理应用程序 URL |
建筑
服务器包括:
- 森林气象卫星服务员: 主服务器类 管理 MCP 服务器生命周期
- 麦克塞尔夫: MCP 核心协议执行
- 可流式HTTPServer 运输: MCP 的 HTTP 传输层
- 快捷 App: HTTP 服务器处理收到的请求
许可证
阿帕奇 - 2.0
仓库
https://github.com/ForestAdmin/agent-nodejs (法语)
支助
关于问题和专题请求,请访问 GitHub 仓库。 。 。 .