@revealui/mcp 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hypervisor.d.ts +1 -1
- package/dist/hypervisor.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/rate-limit-store.d.ts +85 -0
- package/dist/rate-limit-store.d.ts.map +1 -0
- package/dist/rate-limit-store.js +120 -0
- package/dist/rate-limit-store.js.map +1 -0
- package/dist/rate-limiter.d.ts +27 -10
- package/dist/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limiter.js +38 -26
- package/dist/rate-limiter.js.map +1 -1
- package/package.json +4 -4
package/dist/hypervisor.d.ts
CHANGED
|
@@ -159,7 +159,7 @@ export declare class MCPHypervisor {
|
|
|
159
159
|
/**
|
|
160
160
|
* Start an MCP server with tenant-specific credentials.
|
|
161
161
|
* Each tenant gets its own isolated server process with credentials
|
|
162
|
-
* resolved from the database (
|
|
162
|
+
* resolved from the database (stored keys or platform defaults).
|
|
163
163
|
*
|
|
164
164
|
* Server instances are keyed by `${tenantId}:${serverName}`.
|
|
165
165
|
*/
|
package/dist/hypervisor.js
CHANGED
|
@@ -371,7 +371,7 @@ export class MCPHypervisor {
|
|
|
371
371
|
/**
|
|
372
372
|
* Start an MCP server with tenant-specific credentials.
|
|
373
373
|
* Each tenant gets its own isolated server process with credentials
|
|
374
|
-
* resolved from the database (
|
|
374
|
+
* resolved from the database (stored keys or platform defaults).
|
|
375
375
|
*
|
|
376
376
|
* Server instances are keyed by `${tenantId}:${serverName}`.
|
|
377
377
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,8 @@ export { getMcpConfig, type McpConfig as McpEnvConfig, type McpMetricsMode, } fr
|
|
|
27
27
|
export { agentDefinitionToAgentCard, agentDefinitionToMcpTools, contractsToolDefinitionToMcpTool, type MCPAdapterConfig, MCPAdapterConfigSchema, type MCPRequest, type MCPRequestOptions, MCPRequestOptionsSchema, MCPRequestSchema, type MCPResponse, type MCPResponseMetadata, MCPResponseMetadataSchema, MCPResponseSchema, mcpToolToContractsToolDefinition, type ToolOutputSchemaName, ToolOutputSchemas, validateToolOutput, } from './contracts.js';
|
|
28
28
|
export { type MCPCredentialResolver, MCPHypervisor, type MCPServerConfig, type MCPTenantContext, type MCPTool, type NamespacedTool, } from './hypervisor.js';
|
|
29
29
|
export { executePipeline, type PipelineResult, type PipelineStep, type PipelineStepResult, } from './pipeline.js';
|
|
30
|
-
export {
|
|
30
|
+
export { InMemoryRateLimitStore, PGliteRateLimitStore, type RateLimitStore, type WindowEntry, } from './rate-limit-store.js';
|
|
31
|
+
export { DEFAULT_TIER_LIMITS, McpRateLimiter, type McpRateLimiterOptions, type RateLimitConfig, type RateLimitResult, } from './rate-limiter.js';
|
|
31
32
|
export { createMCPAdapter, disposeAllAdapters, generateIdempotencyKey, generateUniqueIdempotencyKey, type IdempotencyStore, MCPAdapter, type MCPConfig, NeonAdapter, StripeAdapter, VercelAdapter, } from './servers/adapter.js';
|
|
32
33
|
export { launchNeonMcp } from './servers/neon.js';
|
|
33
34
|
export { launchNextDevtoolsMcp } from './servers/next-devtools.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAUxD;AAGD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,iBAAiB,EACjB,KAAK,aAAa,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,YAAY,EACZ,KAAK,SAAS,IAAI,YAAY,EAC9B,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,gCAAgC,EAChC,KAAK,oBAAoB,EACzB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,KAAK,qBAAqB,EAC1B,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACZ,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,4BAA4B,EAC5B,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,SAAS,EACd,WAAW,EACX,aAAa,EACb,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,YAAY,GACb,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAUxD;AAGD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,iBAAiB,EACjB,KAAK,aAAa,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,YAAY,EACZ,KAAK,SAAS,IAAI,YAAY,EAC9B,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,gCAAgC,EAChC,KAAK,oBAAoB,EACzB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,KAAK,qBAAqB,EAC1B,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACZ,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,4BAA4B,EAC5B,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,SAAS,EACd,WAAW,EACX,aAAa,EACb,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,YAAY,GACb,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,8 @@ export { agentDefinitionToAgentCard, agentDefinitionToMcpTools, contractsToolDef
|
|
|
44
44
|
export { MCPHypervisor, } from './hypervisor.js';
|
|
45
45
|
// Tool pipeline (composition / chaining)
|
|
46
46
|
export { executePipeline, } from './pipeline.js';
|
|
47
|
+
// Rate limit stores (pluggable backends)
|
|
48
|
+
export { InMemoryRateLimitStore, PGliteRateLimitStore, } from './rate-limit-store.js';
|
|
47
49
|
// Rate limiting (per-tier)
|
|
48
50
|
export { DEFAULT_TIER_LIMITS, McpRateLimiter, } from './rate-limiter.js';
|
|
49
51
|
// Adapter framework
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,iBAAiB,EAAE,CAAC;IAC1B,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CACT,+EAA+E;YAC7E,iDAAiD,CACpD,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mBAAmB;AACnB,OAAO,EAGL,aAAa,EACb,eAAe,EACf,iBAAiB,GAGlB,MAAM,kBAAkB,CAAC;AAC1B,2DAA2D;AAC3D,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,gBAAgB;AAChB,OAAO,EACL,YAAY,GAGb,MAAM,mBAAmB,CAAC;AAC3B,0CAA0C;AAC1C,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAEhC,sBAAsB,EAGtB,uBAAuB,EACvB,gBAAgB,EAGhB,yBAAyB,EACzB,iBAAiB,EACjB,gCAAgC,EAEhC,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,0DAA0D;AAC1D,OAAO,EAEL,aAAa,GAKd,MAAM,iBAAiB,CAAC;AACzB,yCAAyC;AACzC,OAAO,EACL,eAAe,GAIhB,MAAM,eAAe,CAAC;AACvB,2BAA2B;AAC3B,OAAO,EACL,mBAAmB,EACnB,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAE7D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,iBAAiB,EAAE,CAAC;IAC1B,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CACT,+EAA+E;YAC7E,iDAAiD,CACpD,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mBAAmB;AACnB,OAAO,EAGL,aAAa,EACb,eAAe,EACf,iBAAiB,GAGlB,MAAM,kBAAkB,CAAC;AAC1B,2DAA2D;AAC3D,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,gBAAgB;AAChB,OAAO,EACL,YAAY,GAGb,MAAM,mBAAmB,CAAC;AAC3B,0CAA0C;AAC1C,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAEhC,sBAAsB,EAGtB,uBAAuB,EACvB,gBAAgB,EAGhB,yBAAyB,EACzB,iBAAiB,EACjB,gCAAgC,EAEhC,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,0DAA0D;AAC1D,OAAO,EAEL,aAAa,GAKd,MAAM,iBAAiB,CAAC;AACzB,yCAAyC;AACzC,OAAO,EACL,eAAe,GAIhB,MAAM,eAAe,CAAC;AACvB,yCAAyC;AACzC,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GAGrB,MAAM,uBAAuB,CAAC;AAC/B,2BAA2B;AAC3B,OAAO,EACL,mBAAmB,EACnB,cAAc,GAIf,MAAM,mBAAmB,CAAC;AAC3B,oBAAoB;AACpB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,4BAA4B,EAE5B,UAAU,EAEV,WAAW,EACX,aAAa,EACb,aAAa,GACd,MAAM,sBAAsB,CAAC;AAC9B,mBAAmB;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,gCAAgC;AAChC,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,8CAA8C;AAC9C,OAAO,EAIL,YAAY,GACb,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Store — Pluggable backends for rate limiter state.
|
|
3
|
+
*
|
|
4
|
+
* - InMemoryRateLimitStore: Map-backed, zero-dep, single-instance (default)
|
|
5
|
+
* - PGliteRateLimitStore: PostgreSQL-backed via PGlite, supports per-instance
|
|
6
|
+
* persistence and can be extended with ElectricSQL for distributed sync.
|
|
7
|
+
*/
|
|
8
|
+
export interface WindowEntry {
|
|
9
|
+
count: number;
|
|
10
|
+
windowStart: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Pluggable storage backend for rate limiter window entries.
|
|
14
|
+
* Implementations must handle TTL-based expiry internally.
|
|
15
|
+
*/
|
|
16
|
+
export interface RateLimitStore {
|
|
17
|
+
/** Get the current window entry for a key, or null if expired/missing. */
|
|
18
|
+
get(key: string): Promise<WindowEntry | null>;
|
|
19
|
+
/** Set or overwrite the window entry for a key. */
|
|
20
|
+
set(key: string, entry: WindowEntry): Promise<void>;
|
|
21
|
+
/** Increment the count for a key. Returns the new count. */
|
|
22
|
+
increment(key: string): Promise<number>;
|
|
23
|
+
/**
|
|
24
|
+
* Atomically increment count only if below limit.
|
|
25
|
+
* Returns the new count and whether the increment happened.
|
|
26
|
+
* This prevents race conditions where concurrent requests all pass
|
|
27
|
+
* a non-atomic read-check-increment sequence.
|
|
28
|
+
*/
|
|
29
|
+
incrementIfBelow(key: string, limit: number): Promise<{
|
|
30
|
+
count: number;
|
|
31
|
+
incremented: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
/** Remove expired entries older than the given cutoff timestamp. */
|
|
34
|
+
cleanup(cutoffMs: number): Promise<number>;
|
|
35
|
+
/** Clear all entries. */
|
|
36
|
+
clear(): Promise<void>;
|
|
37
|
+
/** Release any resources (timers, connections). */
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare class InMemoryRateLimitStore implements RateLimitStore {
|
|
41
|
+
private windows;
|
|
42
|
+
get(key: string): Promise<WindowEntry | null>;
|
|
43
|
+
set(key: string, entry: WindowEntry): Promise<void>;
|
|
44
|
+
increment(key: string): Promise<number>;
|
|
45
|
+
incrementIfBelow(key: string, limit: number): Promise<{
|
|
46
|
+
count: number;
|
|
47
|
+
incremented: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
cleanup(cutoffMs: number): Promise<number>;
|
|
50
|
+
clear(): Promise<void>;
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/** Minimal PGlite interface — avoids importing the full @electric-sql/pglite package. */
|
|
54
|
+
interface PGliteInstance {
|
|
55
|
+
exec(query: string): Promise<unknown>;
|
|
56
|
+
query<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<{
|
|
57
|
+
rows: T[];
|
|
58
|
+
}>;
|
|
59
|
+
close(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
interface PGliteRateLimitStoreOptions {
|
|
62
|
+
/** PGlite instance (caller owns lifecycle unless closeOnDestroy is true). */
|
|
63
|
+
db: PGliteInstance;
|
|
64
|
+
/** Close the PGlite instance when close() is called (default: false). */
|
|
65
|
+
closeOnDestroy?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare class PGliteRateLimitStore implements RateLimitStore {
|
|
68
|
+
private db;
|
|
69
|
+
private ready;
|
|
70
|
+
private closeOnDestroy;
|
|
71
|
+
constructor(options: PGliteRateLimitStoreOptions);
|
|
72
|
+
private init;
|
|
73
|
+
get(key: string): Promise<WindowEntry | null>;
|
|
74
|
+
set(key: string, entry: WindowEntry): Promise<void>;
|
|
75
|
+
increment(key: string): Promise<number>;
|
|
76
|
+
incrementIfBelow(key: string, limit: number): Promise<{
|
|
77
|
+
count: number;
|
|
78
|
+
incremented: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
cleanup(cutoffMs: number): Promise<number>;
|
|
81
|
+
clear(): Promise<void>;
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=rate-limit-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-store.d.ts","sourceRoot":"","sources":["../src/rate-limit-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Store — Pluggable backends for rate limiter state.
|
|
3
|
+
*
|
|
4
|
+
* - InMemoryRateLimitStore: Map-backed, zero-dep, single-instance (default)
|
|
5
|
+
* - PGliteRateLimitStore: PostgreSQL-backed via PGlite, supports per-instance
|
|
6
|
+
* persistence and can be extended with ElectricSQL for distributed sync.
|
|
7
|
+
*/
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// In-memory store (Map-backed, default)
|
|
10
|
+
// =============================================================================
|
|
11
|
+
export class InMemoryRateLimitStore {
|
|
12
|
+
windows = new Map();
|
|
13
|
+
async get(key) {
|
|
14
|
+
return this.windows.get(key) ?? null;
|
|
15
|
+
}
|
|
16
|
+
async set(key, entry) {
|
|
17
|
+
this.windows.set(key, entry);
|
|
18
|
+
}
|
|
19
|
+
async increment(key) {
|
|
20
|
+
const entry = this.windows.get(key);
|
|
21
|
+
if (!entry)
|
|
22
|
+
return 0;
|
|
23
|
+
entry.count++;
|
|
24
|
+
return entry.count;
|
|
25
|
+
}
|
|
26
|
+
async incrementIfBelow(key, limit) {
|
|
27
|
+
const entry = this.windows.get(key);
|
|
28
|
+
if (!entry)
|
|
29
|
+
return { count: 0, incremented: false };
|
|
30
|
+
if (entry.count >= limit)
|
|
31
|
+
return { count: entry.count, incremented: false };
|
|
32
|
+
entry.count++;
|
|
33
|
+
return { count: entry.count, incremented: true };
|
|
34
|
+
}
|
|
35
|
+
async cleanup(cutoffMs) {
|
|
36
|
+
let removed = 0;
|
|
37
|
+
for (const [key, entry] of this.windows) {
|
|
38
|
+
if (entry.windowStart < cutoffMs) {
|
|
39
|
+
this.windows.delete(key);
|
|
40
|
+
removed++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return removed;
|
|
44
|
+
}
|
|
45
|
+
async clear() {
|
|
46
|
+
this.windows.clear();
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
this.windows.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const CREATE_RATE_LIMIT_TABLE_SQL = `
|
|
53
|
+
CREATE TABLE IF NOT EXISTS _rate_limit_windows (
|
|
54
|
+
key TEXT PRIMARY KEY,
|
|
55
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
window_start BIGINT NOT NULL
|
|
57
|
+
);
|
|
58
|
+
`;
|
|
59
|
+
export class PGliteRateLimitStore {
|
|
60
|
+
db;
|
|
61
|
+
ready;
|
|
62
|
+
closeOnDestroy;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.db = options.db;
|
|
65
|
+
this.closeOnDestroy = options.closeOnDestroy ?? false;
|
|
66
|
+
this.ready = this.init();
|
|
67
|
+
}
|
|
68
|
+
async init() {
|
|
69
|
+
await this.db.exec(CREATE_RATE_LIMIT_TABLE_SQL);
|
|
70
|
+
}
|
|
71
|
+
async get(key) {
|
|
72
|
+
await this.ready;
|
|
73
|
+
const result = await this.db.query('SELECT count, window_start FROM _rate_limit_windows WHERE key = $1', [key]);
|
|
74
|
+
const row = result.rows[0];
|
|
75
|
+
if (!row)
|
|
76
|
+
return null;
|
|
77
|
+
return { count: row.count, windowStart: Number(row.window_start) };
|
|
78
|
+
}
|
|
79
|
+
async set(key, entry) {
|
|
80
|
+
await this.ready;
|
|
81
|
+
await this.db.query(`INSERT INTO _rate_limit_windows (key, count, window_start)
|
|
82
|
+
VALUES ($1, $2, $3)
|
|
83
|
+
ON CONFLICT (key) DO UPDATE
|
|
84
|
+
SET count = EXCLUDED.count, window_start = EXCLUDED.window_start`, [key, entry.count, entry.windowStart]);
|
|
85
|
+
}
|
|
86
|
+
async increment(key) {
|
|
87
|
+
await this.ready;
|
|
88
|
+
const result = await this.db.query(`UPDATE _rate_limit_windows SET count = count + 1 WHERE key = $1 RETURNING count`, [key]);
|
|
89
|
+
const row = result.rows[0];
|
|
90
|
+
return row?.count ?? 0;
|
|
91
|
+
}
|
|
92
|
+
async incrementIfBelow(key, limit) {
|
|
93
|
+
await this.ready;
|
|
94
|
+
const result = await this.db.query(`UPDATE _rate_limit_windows SET count = count + 1
|
|
95
|
+
WHERE key = $1 AND count < $2
|
|
96
|
+
RETURNING count`, [key, limit]);
|
|
97
|
+
const row = result.rows[0];
|
|
98
|
+
if (row)
|
|
99
|
+
return { count: row.count, incremented: true };
|
|
100
|
+
// No rows updated — either key missing or limit reached
|
|
101
|
+
const current = await this.get(key);
|
|
102
|
+
return { count: current?.count ?? 0, incremented: false };
|
|
103
|
+
}
|
|
104
|
+
async cleanup(cutoffMs) {
|
|
105
|
+
await this.ready;
|
|
106
|
+
const result = await this.db.query(`WITH deleted AS (DELETE FROM _rate_limit_windows WHERE window_start < $1 RETURNING 1)
|
|
107
|
+
SELECT count(*)::text AS count FROM deleted`, [cutoffMs]);
|
|
108
|
+
return Number.parseInt(result.rows[0]?.count ?? '0', 10);
|
|
109
|
+
}
|
|
110
|
+
async clear() {
|
|
111
|
+
await this.ready;
|
|
112
|
+
await this.db.exec('DELETE FROM _rate_limit_windows');
|
|
113
|
+
}
|
|
114
|
+
async close() {
|
|
115
|
+
if (this.closeOnDestroy) {
|
|
116
|
+
await this.db.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=rate-limit-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-store.js","sourceRoot":"","sources":["../src/rate-limit-store.ts"],"names":[],"mappings":"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"}
|
package/dist/rate-limiter.d.ts
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Rate Limiter
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Fixed-window rate limiter scoped by tenant + tier.
|
|
5
|
+
* Supports pluggable storage backends via RateLimitStore:
|
|
6
|
+
* - InMemoryRateLimitStore (default, Map-backed)
|
|
7
|
+
* - PGliteRateLimitStore (SQL-backed, persistent)
|
|
6
8
|
*
|
|
7
9
|
* @example
|
|
8
10
|
* ```typescript
|
|
11
|
+
* // In-memory (default)
|
|
9
12
|
* const limiter = new McpRateLimiter();
|
|
10
13
|
*
|
|
11
|
-
*
|
|
14
|
+
* // PGlite-backed
|
|
15
|
+
* import { PGlite } from '@electric-sql/pglite';
|
|
16
|
+
* import { PGliteRateLimitStore } from './rate-limit-store.js';
|
|
17
|
+
* const db = new PGlite();
|
|
18
|
+
* const limiter = new McpRateLimiter({
|
|
19
|
+
* store: new PGliteRateLimitStore({ db }),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const result = await limiter.check('tenant-123', 'pro');
|
|
12
23
|
* if (!result.allowed) {
|
|
13
24
|
* throw new Error(`Rate limited. Retry after ${result.resetMs}ms`);
|
|
14
25
|
* }
|
|
15
|
-
* // result.remaining === requests left in window
|
|
16
26
|
* ```
|
|
17
27
|
*/
|
|
28
|
+
import { type RateLimitStore } from './rate-limit-store.js';
|
|
18
29
|
export interface RateLimitConfig {
|
|
19
30
|
/** Max requests per window */
|
|
20
31
|
maxRequests: number;
|
|
@@ -27,27 +38,33 @@ export interface RateLimitResult {
|
|
|
27
38
|
limit: number;
|
|
28
39
|
resetMs: number;
|
|
29
40
|
}
|
|
41
|
+
export interface McpRateLimiterOptions {
|
|
42
|
+
/** Tier-based rate limit configuration. */
|
|
43
|
+
limits?: Record<string, RateLimitConfig>;
|
|
44
|
+
/** Storage backend (default: InMemoryRateLimitStore). */
|
|
45
|
+
store?: RateLimitStore;
|
|
46
|
+
}
|
|
30
47
|
/** Default rate limits per tier. */
|
|
31
48
|
export declare const DEFAULT_TIER_LIMITS: Record<string, RateLimitConfig>;
|
|
32
49
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
50
|
+
* Fixed-window rate limiter scoped by tenant + tier.
|
|
51
|
+
* Accepts a pluggable RateLimitStore for different storage backends.
|
|
35
52
|
*/
|
|
36
53
|
export declare class McpRateLimiter {
|
|
37
|
-
private
|
|
54
|
+
private store;
|
|
38
55
|
private limits;
|
|
39
56
|
private cleanupInterval;
|
|
40
|
-
constructor(
|
|
57
|
+
constructor(options?: McpRateLimiterOptions);
|
|
41
58
|
/**
|
|
42
59
|
* Check if a request is allowed and consume one token if so.
|
|
43
60
|
* Returns quota information including remaining requests and reset time.
|
|
44
61
|
*/
|
|
45
|
-
check(tenantId: string, tier: string): RateLimitResult
|
|
62
|
+
check(tenantId: string, tier: string): Promise<RateLimitResult>;
|
|
46
63
|
/** Override limits for a specific tier. */
|
|
47
64
|
setTierLimit(tier: string, config: RateLimitConfig): void;
|
|
48
65
|
/** Clean up expired window entries. */
|
|
49
66
|
private cleanup;
|
|
50
67
|
/** Dispose the rate limiter, clearing the cleanup timer and all windows. */
|
|
51
|
-
dispose(): void
|
|
68
|
+
dispose(): Promise<void>;
|
|
52
69
|
}
|
|
53
70
|
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMpF,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACzC,yDAAyD;IACzD,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAMD,oCAAoC;AACpC,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAK/D,CAAC;AAMF;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,eAAe,CAA+C;gBAE1D,OAAO,GAAE,qBAA0B;IAU/C;;;OAGG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA8BrE,2CAA2C;IAC3C,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAIzD,uCAAuC;YACzB,OAAO;IAOrB,4EAA4E;IACtE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B"}
|
package/dist/rate-limiter.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Rate Limiter
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Fixed-window rate limiter scoped by tenant + tier.
|
|
5
|
+
* Supports pluggable storage backends via RateLimitStore:
|
|
6
|
+
* - InMemoryRateLimitStore (default, Map-backed)
|
|
7
|
+
* - PGliteRateLimitStore (SQL-backed, persistent)
|
|
6
8
|
*
|
|
7
9
|
* @example
|
|
8
10
|
* ```typescript
|
|
11
|
+
* // In-memory (default)
|
|
9
12
|
* const limiter = new McpRateLimiter();
|
|
10
13
|
*
|
|
11
|
-
*
|
|
14
|
+
* // PGlite-backed
|
|
15
|
+
* import { PGlite } from '@electric-sql/pglite';
|
|
16
|
+
* import { PGliteRateLimitStore } from './rate-limit-store.js';
|
|
17
|
+
* const db = new PGlite();
|
|
18
|
+
* const limiter = new McpRateLimiter({
|
|
19
|
+
* store: new PGliteRateLimitStore({ db }),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const result = await limiter.check('tenant-123', 'pro');
|
|
12
23
|
* if (!result.allowed) {
|
|
13
24
|
* throw new Error(`Rate limited. Retry after ${result.resetMs}ms`);
|
|
14
25
|
* }
|
|
15
|
-
* // result.remaining === requests left in window
|
|
16
26
|
* ```
|
|
17
27
|
*/
|
|
28
|
+
import { InMemoryRateLimitStore } from './rate-limit-store.js';
|
|
18
29
|
// =============================================================================
|
|
19
30
|
// Default tier limits
|
|
20
31
|
// =============================================================================
|
|
@@ -29,17 +40,20 @@ export const DEFAULT_TIER_LIMITS = {
|
|
|
29
40
|
// Rate limiter
|
|
30
41
|
// =============================================================================
|
|
31
42
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
43
|
+
* Fixed-window rate limiter scoped by tenant + tier.
|
|
44
|
+
* Accepts a pluggable RateLimitStore for different storage backends.
|
|
34
45
|
*/
|
|
35
46
|
export class McpRateLimiter {
|
|
36
|
-
|
|
47
|
+
store;
|
|
37
48
|
limits;
|
|
38
49
|
cleanupInterval = null;
|
|
39
|
-
constructor(
|
|
40
|
-
this.limits = limits;
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this.limits = options.limits ?? DEFAULT_TIER_LIMITS;
|
|
52
|
+
this.store = options.store ?? new InMemoryRateLimitStore();
|
|
41
53
|
// Clean up expired windows every 60s
|
|
42
|
-
this.cleanupInterval = setInterval(() =>
|
|
54
|
+
this.cleanupInterval = setInterval(() => {
|
|
55
|
+
void this.cleanup();
|
|
56
|
+
}, 60_000);
|
|
43
57
|
if (this.cleanupInterval.unref)
|
|
44
58
|
this.cleanupInterval.unref();
|
|
45
59
|
}
|
|
@@ -47,24 +61,26 @@ export class McpRateLimiter {
|
|
|
47
61
|
* Check if a request is allowed and consume one token if so.
|
|
48
62
|
* Returns quota information including remaining requests and reset time.
|
|
49
63
|
*/
|
|
50
|
-
check(tenantId, tier) {
|
|
64
|
+
async check(tenantId, tier) {
|
|
51
65
|
const config = this.limits[tier] ?? this.limits.free ?? { maxRequests: 60, windowMs: 60_000 };
|
|
52
66
|
const key = `${tenantId}:${tier}`;
|
|
53
67
|
const now = Date.now();
|
|
54
|
-
let entry = this.
|
|
68
|
+
let entry = await this.store.get(key);
|
|
55
69
|
// New window or expired window
|
|
56
70
|
if (!entry || now - entry.windowStart >= config.windowMs) {
|
|
57
71
|
entry = { count: 0, windowStart: now };
|
|
58
|
-
this.
|
|
72
|
+
await this.store.set(key, entry);
|
|
59
73
|
}
|
|
60
74
|
const resetMs = config.windowMs - (now - entry.windowStart);
|
|
61
|
-
|
|
75
|
+
// Atomic check-and-increment: prevents concurrent requests from
|
|
76
|
+
// all passing a stale count check before any increment lands.
|
|
77
|
+
const result = await this.store.incrementIfBelow(key, config.maxRequests);
|
|
78
|
+
if (!result.incremented) {
|
|
62
79
|
return { allowed: false, remaining: 0, limit: config.maxRequests, resetMs };
|
|
63
80
|
}
|
|
64
|
-
entry.count++;
|
|
65
81
|
return {
|
|
66
82
|
allowed: true,
|
|
67
|
-
remaining: Math.max(0, config.maxRequests -
|
|
83
|
+
remaining: Math.max(0, config.maxRequests - result.count),
|
|
68
84
|
limit: config.maxRequests,
|
|
69
85
|
resetMs,
|
|
70
86
|
};
|
|
@@ -74,23 +90,19 @@ export class McpRateLimiter {
|
|
|
74
90
|
this.limits[tier] = config;
|
|
75
91
|
}
|
|
76
92
|
/** Clean up expired window entries. */
|
|
77
|
-
cleanup() {
|
|
93
|
+
async cleanup() {
|
|
78
94
|
const now = Date.now();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (now - entry.windowStart >= config.windowMs * 2) {
|
|
83
|
-
this.windows.delete(key);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
95
|
+
// Use the longest window duration × 2 as the expiry cutoff
|
|
96
|
+
const maxWindowMs = Math.max(...Object.values(this.limits).map((c) => c.windowMs));
|
|
97
|
+
await this.store.cleanup(now - maxWindowMs * 2);
|
|
86
98
|
}
|
|
87
99
|
/** Dispose the rate limiter, clearing the cleanup timer and all windows. */
|
|
88
|
-
dispose() {
|
|
100
|
+
async dispose() {
|
|
89
101
|
if (this.cleanupInterval) {
|
|
90
102
|
clearInterval(this.cleanupInterval);
|
|
91
103
|
this.cleanupInterval = null;
|
|
92
104
|
}
|
|
93
|
-
this.
|
|
105
|
+
await this.store.close();
|
|
94
106
|
}
|
|
95
107
|
}
|
|
96
108
|
//# sourceMappingURL=rate-limiter.js.map
|
package/dist/rate-limiter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revealui/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Model Context Protocol integrations for RevealUI — adapter framework, hypervisor, and MCP contracts",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
13
13
|
"dotenv": "^17.2.4",
|
|
14
|
-
"@revealui/config": "0.3.
|
|
15
|
-
"@revealui/contracts": "1.3.
|
|
16
|
-
"@revealui/core": "0.5.
|
|
14
|
+
"@revealui/config": "0.3.2",
|
|
15
|
+
"@revealui/contracts": "1.3.5",
|
|
16
|
+
"@revealui/core": "0.5.4"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@electric-sql/pglite": "^0.4.2",
|