@ frontmcp/ 守护
FrontMCP的速率限制,货币限制,执行超时和IP过滤. @frontmcp/sdk 用它来 throttle 选项 @FrontMcp 编辑 rateLimit, (中文(简体) ), concurrency 财务报告和审定财务报表 timeout 选项,所以在 FrontMCP 服务器中,您设置了这些选项,而不要导入此软件包。 直接安装以自行使用限制器. 他们把柜台放在后面 StorageAdapter 接口 @frontmcp/utils,因此它们与记忆,Redis,Vercel KV,Upstash和Cloudflare KV存储一起工作.
安装
npm install @frontmcp/guard @frontmcp/lazy-zod zod
@frontmcp/lazy-zod 是一个对等依赖(配置方案使用),它需要 zod 4. 节点.js 24或后. FrontMCP服务器不需要此安装 : @frontmcp/sdk 取决于 @frontmcp/guard 然后再出口里面的一切.
使用量
在 FrontMCP 服务器中
为工具设定限制, 并为整个服务器设定默认值 。 SDK将这些选项传递给此套件:
import { App, FrontMcp, Tool, ToolContext } from '@frontmcp/sdk';
@Tool({
name: 'export_tickets',
description: 'Export every ticket as CSV. At most 2 exports an hour per caller, one at a time.',
inputSchema: {},
rateLimit: { maxRequests: 2, windowMs: 60 * 60_000, partitionBy: 'userId' },
concurrency: { maxConcurrent: 1, queueTimeoutMs: 5_000 },
timeout: { executeMs: 30_000 },
})
class ExportTickets extends ToolContext {
async execute() {
return { csv: 'id,title' };
}
}
@App({ id: 'help-desk', name: 'Help Desk', tools: [ExportTickets] })
class HelpDeskApp {}
@FrontMcp({
info: { name: 'help-desk', version: '1.0.0' },
apps: [HelpDeskApp],
throttle: {
enabled: true,
defaultRateLimit: { maxRequests: 60, windowMs: 60_000, partitionBy: 'userId' },
global: { maxRequests: 1_000, windowMs: 60_000, partitionBy: 'ip' },
ipFilter: { denyList: ['192.0.2.0/24'] },
},
})
export default class Server {}
守护选项 覆盖每个选项在服务器中所做的,并且调用者返回错误.
独自一人
createGuardManager 构建一个 GuardManager 从一个配置对象:
import { createGuardManager } from '@frontmcp/guard';
const guard = await createGuardManager({
config: {
enabled: true,
defaultRateLimit: { maxRequests: 100, windowMs: 60_000, partitionBy: 'session' },
defaultConcurrency: { maxConcurrent: 5 },
ipFilter: { denyList: ['10.0.0.0/8'], defaultAction: 'allow' },
},
});
const ipResult = guard.checkIpFilter('203.0.113.5');
if (ipResult && !ipResult.allowed) throw new Error('IP refused');
const rateLimit = await guard.checkRateLimit('my-tool', undefined, { sessionId: 'sess-123' });
if (!rateLimit.allowed) throw new Error(`Retry after ${rateLimit.retryAfterMs}ms`);
const ticket = await guard.acquireSemaphore('my-tool', undefined, { sessionId: 'sess-123' });
if (!ticket) throw new Error('No free slot');
try {
// ... do the work ...
} finally {
await ticket.release();
}
await guard.destroy();
没有 storage 配置管理器将其计数器保存在内存中,每个进程一组,并通过它发出警告 logger 当你通过一个.
限制费率
SlidingWindowRateLimiter 使用滑动窗口计数器。 它保留两个相邻的固定窗口计数器 并按窗口的倒数来计算 windowMs 现在。 这大概是一个真实的滑动窗口,每把钥匙都有恒定的存储,没有连续的一扇固定窗口允许在其边缘. 视窗与时钟对齐.
班级采用存储适配器. createMemoryStorage 其他适配器来自 @frontmcp/utils,它与此软件包一起安装;将其添加到自己的依赖性中以导入.
import { SlidingWindowRateLimiter } from '@frontmcp/guard';
import { createMemoryStorage } from '@frontmcp/utils';
const storage = createMemoryStorage();
await storage.connect();
const limiter = new SlidingWindowRateLimiter(storage);
const result = await limiter.check(
'user:42', // key
100, // max requests
60_000, // window in ms
);
if (!result.allowed) {
console.log(`Rate limited. Retry after ${result.retryAfterMs}ms`);
}
允许的检查计算请求数 . RateLimitResult 拥有:
allowed:请求能否继续remaining: 本窗口所剩的大致请求resetMs: 毫秒直至当前窗口结束retryAfterMs:当被封锁时,等待多长时间
reset(key, windowMs) 删除密钥的计数器.
货币管制
DistributedSemaphore 限制一个关键处刑次数。 钥匙有 maxConcurrent 槽,每个被存储为自己的密钥. 执行者要求有一个自由的插槽,该插槽有写出(如果缺席),并带有其活命的出票时间(TTL,默认为300秒),并且每三分之一延长TTL,直到它打电话 release()。因此,该插槽的保存时间与执行运行的时间相同,TTL只释放一个已死亡的过程的插槽而不释放它. 取出或续作只触及一个仍然持有同一张罚单的插槽,因此存储需要原子相比较和去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去去 用另一个适配器, acquire() 掷出 GuardStorageUnsupportedError,以及 createGuardManager 当 globalConcurrency 或者说 defaultConcurrency 已经设定.
当每个插槽被取下时,一个呼叫者可以等待一个,初时每100米投票一次,后退一次. 如果存储支持发布/订阅(memory, Redis,以及带有发布/订阅打开的Upstash),一个被发布的插槽立刻会唤醒等待呼叫者.
import { DistributedSemaphore } from '@frontmcp/guard';
import { createMemoryStorage } from '@frontmcp/utils';
const storage = createMemoryStorage();
await storage.connect();
const semaphore = new DistributedSemaphore(storage, 300 /* ticket TTL in seconds */);
const ticket = await semaphore.acquire(
'my-tool:global', // key
5, // max concurrent
10_000, // how long to wait for a slot, in ms (0 = don't wait)
'my-tool', // name used in error messages
);
if (ticket) {
try {
// ... do the work ...
} finally {
await ticket.release();
}
}
acquire 返回时 null 当每个槽被取走,等待被取走的时候 0投掷 QueueTimeoutError 当它等待的时候,没有空隙. getActiveCount(key) 返回已取槽的数目,以及 forceReset(key) 释放每个插槽.
超时
withTimeout 运行带有最后期限的同步函数。 它传递函数 a AbortSignal 当时间流逝时,它将被中止,而以 ExecutionTimeoutError。函数继续运行,除非它听到信号.
import { withTimeout } from '@frontmcp/guard';
const response = await withTimeout(
(signal) => fetch('https://api.example.com/data', { signal }),
5_000, // timeout in ms
'fetch-data', // name used in error messages
);
IP 过滤
IpFilter 请检查access-date=中的日期值 (帮助) 客户端IP对照允许和拒绝地址列表和CIDR范围,IPv4和IPv6. 一个 IPv4 的 IPv6 地址 (::ffff:192.168.1.1,而双层套接字为 IPv4 客户端报告)与 IPv4 地址相匹配。 相配使用大意算术,因此精确地横跨了整个IPv6地址空间.
拒绝列表先被检查: 拒绝其中的IP, 无论允许列表内容如何 。 然后,允许列表,然后 defaultAction。允许列表本身拒绝任何内容:添加 defaultAction: 'deny' 拒绝其他地址 缺少或无法解析的 IP 匹配规则并获得 defaultAction这样 check(undefined) 拒绝 defaultAction: 'deny'。 。 。 .
import { IpFilter } from '@frontmcp/guard';
const filter = new IpFilter({
allowList: ['192.168.0.0/16'],
denyList: ['192.168.1.100'],
defaultAction: 'deny',
});
const result = filter.check('192.168.2.50');
// { allowed: true, reason: 'allowlisted', matchedRule: '192.168.0.0/16' }
const blocked = filter.check('192.168.1.100');
// { allowed: false, reason: 'denylisted', matchedRule: '192.168.1.100' }
isAllowListed(ip) 告诉你一个IP是否在允许列表中,比如跳过对信任地址的速率限制.
分区密钥
一个分区键决定哪个调用共享利率限制计数或一组货币槽:
| 战略 | 要求共享一个数 |
|---|---|
'global' | 所有的人 |
'ip' | 同一客户端IP的电话. 没有IP,同一个电话 userId;没有两个,一个桶 |
'session' | 在同一届会议上的呼吁 |
'userId' | 同一认证用户的呼叫, 或当没有用户时在同一会话中 |
您也可以通过 PartitionKeyContext,已经 sessionId (总是设定)和可选 clientIp 财务报告和审定财务报表 userId数字 :
import type { RateLimitConfig } from '@frontmcp/guard';
const config: RateLimitConfig = {
maxRequests: 100,
windowMs: 60_000,
partitionBy: (ctx) => (ctx.userId ? `user:${ctx.userId}` : `session:${ctx.sessionId}`),
};
警卫经理
GuardManager 结合一个配置后面的速率限制,货币控制和IP过滤. createGuardManager 创建它的存储并连接它.
配置有两个级别:
- 全球,适用于每项请求:
global(费率限额)和globalConcurrency。 。 。 . - 默认,适用于不通过自己的实体:
defaultRateLimit, (中文(简体) ),defaultConcurrency, (中文(简体) ),defaultTimeout。 。 。 .
一个实体通过的配置取代了默认;它不会增加它.
import { createGuardManager } from '@frontmcp/guard';
const guard = await createGuardManager({
config: {
enabled: true,
storage: { type: 'redis', redis: { config: { host: 'localhost', port: 6379 } } },
keyPrefix: 'myapp:guard:',
global: { maxRequests: 1000, windowMs: 60_000, partitionBy: 'ip' },
globalConcurrency: { maxConcurrent: 50, partitionBy: 'global' },
defaultRateLimit: { maxRequests: 100, windowMs: 60_000, partitionBy: 'session' },
defaultConcurrency: { maxConcurrent: 10, queueTimeoutMs: 5_000 },
defaultTimeout: { executeMs: 30_000 },
ipFilter: {
denyList: ['10.0.0.0/8'],
allowList: ['10.0.1.0/24'],
defaultAction: 'allow',
},
},
logger: console,
});
const context = { sessionId: 'sess-1', clientIp: '203.0.113.5' };
const globalRateLimit = await guard.checkGlobalRateLimit(context);
const entityRateLimit = await guard.checkRateLimit('my-tool', undefined, context);
const globalTicket = await guard.acquireGlobalSemaphore(context);
const ticket = await guard.acquireSemaphore('my-tool', undefined, context);
await ticket?.release();
await globalTicket?.release();
await guard.destroy();
| 方法 | 回返 |
|---|---|
checkIpFilter(ip) | 一个 IpFilterResult,或 undefined 当没有 ipFilter |
isIpAllowListed(ip) | IP是否在允许列表中 |
checkRateLimit(name, config, context) | A 类 RateLimitResult没有 config 没有 defaultRateLimit,总是允许 |
checkGlobalRateLimit(context) | A 类 RateLimitResult 反对 global |
acquireSemaphore(name, config, context) | A 类 SemaphoreTicket,或 null 当没有空档或者没有限制时 |
acquireGlobalSemaphore(context) | A 类 SemaphoreTicket 反对 globalConcurrency,或 null |
destroy() | 断开存储 |
经理不申请 defaultTimeout 本身:从 guard.config 并传递给 withTimeout。 。 。 .
配置参考
GuardConfig
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
enabled | boolean | 所需经费 | 卫视是否开播. @frontmcp/sdk 读取; createGuardManager 没有 |
storage | StorageConfig | 内存 | 存储后端, 从 @frontmcp/utils |
keyPrefix | string | 'mcp:guard:' | 每个存储密钥的前缀( 后缀) : 已删除) |
global | RateLimitConfig | 无 | 每次请求的费率限额 |
globalConcurrency | ConcurrencyConfig | 无 | 每个实体的货币限额 |
defaultRateLimit | RateLimitConfig | 无 | 对没有自己的实体的费率限制 |
defaultConcurrency | ConcurrencyConfig | 无 | 非本实体的货币限额 |
defaultTimeout | TimeoutConfig | 无 | 没有自己实体的超时 |
ipFilter | IpFilterConfig | 无 | IP 过滤 |
RateLimitConfig
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
maxRequests | number | 所需经费 | 每个窗口允许的请求 |
windowMs | number | 60000 | 窗口长度( 毫秒) |
partitionBy | PartitionKey | 'global' | 呼叫共享一个数 |
ConcurrencyConfig
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
maxConcurrent | number | 所需经费 | 允许立即处决 |
queueTimeoutMs | number | 0 | 等待自由空档的时间有多长(0 立即拒绝) |
partitionBy | PartitionKey | 'global' | 调用共享槽 |
TimeoutConfig
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
executeMs | number | 所需经费 | 最大执行时间( 毫秒) |
IpFilterConfig
| 外地 | 类型 | 默认 | 说明 |
|---|---|---|---|
allowList | string[] | 无 | IP 地址或 CIDR 范围允许 |
denyList | string[] | 无 | IP 地址或 CIDR 区域拒绝 |
defaultAction | 'allow' | 'deny' | 'allow' | 两个列表上的IP会发生什么,或者当客户端IP不知时 |
trustProxy | boolean | false | 阅读者 @frontmcp/sdk,不通过 IpFilter数字 : true 将客户端 IP 从 X-Forwarded-For· ; false 交给我 FRONTMCP_TRUST_PROXY |
trustedProxyDepth | number | 1 | 阅读者 @frontmcp/sdk 何时 trustProxy 实值 true: 服务器附件前有多少代理 X-Forwarded-For |
存储
警卫把一切藏在后面 StorageAdapter 接口 @frontmcp/utils。 。 。 . createGuardManager 从 storage 与 createStorage数字 :
| 后端 | 用它来 |
|---|---|
内存( E)type: 'memory',默认值) | 开发,测试和单流程服务器. 每个过程都靠自己 |
复数( R)type: 'redis'(中文(简体) ) | 多个共享限制的例子。 发布/订阅等待槽的呼叫者 |
Vercel KV (英语:type: 'vercel-kv'(中文(简体) ) | Vercel 上的服务器 |
上调( U)type: 'upstash'(中文(简体) ) | 无服务器功能: HTTP 之上的 Redis, 可选的发布/订阅 |
Cloudflare KV (英语:type: 'cloudflare-kv'(中文(简体) ) | Cloudflare工人, KV 命名空间传入 |
storage 是一个 StorageConfig,例如 { type: 'redis', redis: { config: { host, port } } } 或者说 { type: 'redis', redis: { url } }没有 type 这是 'auto',它从环境变量中选择后端.
storage.fallback 决定后端无法到达时会发生什么. 与 'error',生产中的默认值(NODE_ENV=production, (中文(简体) ), createGuardManager 拒绝方式 GuardStorageUnavailableError: 速率限制未关闭。 与 'memory',在其他地方的默认, 它从 per-intance 计数器开始并登录一个警告。 如果后端稍后停止回答, 同样的设置也适用: 每次检查都会失败 GuardStorageUnavailableError,或,与 'memory',从 per-intance 计数器服务到后端再次回答.
密钥为 <keyPrefix><entity>:<partition>:<kind>:… 单人 : 前缀之后, 例如 mcp:guard:export_tickets:global:rl:…。在1.8.6之前,生成默认前缀 mcp:guard::export_tickets:…更古老的例子写的计数器不是较新的,所以限制在滚动部署时简单分拆.
错误
每个错误都会延伸 GuardError,它有一个机器可读 code 和一个HTTP statusCode。 。 。 . instanceof GuardError 还确认同一过程中该软件包的另一个副本的错误.
| 错误类 | 代码 | 状态 | 被丢弃 |
|---|---|---|---|
ExecutionTimeoutError | EXECUTION_TIMEOUT | 408 | withTimeout当时间流逝时 |
QueueTimeoutError | QUEUE_TIMEOUT | 429 | DistributedSemaphore.acquire当它等待的时候,没有空隙 |
GuardStorageUnavailableError | GUARD_STORAGE_UNAVAILABLE | 503 | createGuardManager 和经理的支票, 当无法到达存储 |
ConcurrencyLimitError | CONCURRENCY_LIMIT | 429 | 您的代码(和 @frontmcp/sdk),何时 acquireSemaphore 返回时 null |
IpBlockedError | IP_BLOCKED | 403 | 您的代码, 用于拒绝列表中的 IP |
IpNotAllowedError | IP_NOT_ALLOWED | 403 | 您的代码, 对于一个不在允许列表的 IP |
import { ExecutionTimeoutError, withTimeout } from '@frontmcp/guard';
try {
await withTimeout(() => new Promise((resolve) => setTimeout(resolve, 10_000)), 5_000, 'slow-op');
} catch (err) {
if (err instanceof ExecutionTimeoutError) {
console.log(err.code); // 'EXECUTION_TIMEOUT'
console.log(err.statusCode); // 408
console.log(err.timeoutMs); // 5000
}
}
API 参考
所有东西都从包根导出.
| 仁爱 | 出口 |
|---|---|
| 类 | GuardManager, (中文(简体) ), SlidingWindowRateLimiter, (中文(简体) ), DistributedSemaphore, (中文(简体) ), IpFilter |
| 职能 | createGuardManager, (中文(简体) ), withTimeout, (中文(简体) ), resolvePartitionKey(partitionBy, context), (中文(简体) ), buildStorageKey(entity, partition, suffix?) |
| 错误 | GuardError, (中文(简体) ), ExecutionTimeoutError, (中文(简体) ), ConcurrencyLimitError, (中文(简体) ), QueueTimeoutError, (中文(简体) ), IpBlockedError, (中文(简体) ), IpNotAllowedError, (中文(简体) ), GuardStorageUnavailableError |
| Zod 计划 | guardConfigSchema, (中文(简体) ), rateLimitConfigSchema, (中文(简体) ), concurrencyConfigSchema, (中文(简体) ), timeoutConfigSchema, (中文(简体) ), ipFilterConfigSchema, (中文(简体) ), partitionKeySchema |
| 类型 | GuardConfig, (中文(简体) ), GuardLogger, (中文(简体) ), GuardManagerOptions, (中文(简体) ), CreateGuardManagerArgs, (中文(简体) ), RateLimitConfig, (中文(简体) ), RateLimitResult, (中文(简体) ), ConcurrencyConfig, (中文(简体) ), SemaphoreTicket, (中文(简体) ), TimeoutConfig, (中文(简体) ), IpFilterConfig, (中文(简体) ), IpFilterResult |
| 分区类型 | PartitionKey, (中文(简体) ), PartitionKeyStrategy, (中文(简体) ), CustomPartitionKeyFn, (中文(简体) ), PartitionKeyContext |
| Schema 输入 | GuardConfigInput, (中文(简体) ), RateLimitConfigInput, (中文(简体) ), ConcurrencyConfigInput, (中文(简体) ), TimeoutConfigInput, (中文(简体) ), IpFilterConfigInput |
文档
- 守护选项数字 :
rateLimit, (中文(简体) ),concurrency, (中文(简体) ),timeout财务报告和审定财务报表throttle在 FrontMCP 服务器中 组件 此软件包导出 - 限制和时间安排: 相同的选项, 逐步
- 错误类别: 呼叫者返回错误
相关软件包
@frontmcp/sdk:将这些限制适用于工具和代理人的框架@frontmcp/utils: 存储器适配器 守卫将计数器放在
捐款
资料来源: libs/guard欢迎提出问题和拉动请求;见 捐款额。 。 。 .
许可证
阿帕奇2.0 (英语). 见 自由主义。 。 。 .