mcp-usage-control-cloudflare 1.1.1 → 1.3.0
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/README.md +73 -1
- package/dist/mcp-flow-validation.d.ts +8 -0
- package/dist/mcp-flow-validation.d.ts.map +1 -0
- package/dist/mcp-flow-validation.js +177 -0
- package/dist/mcp-flow-validation.js.map +1 -0
- package/dist/mcp-flow-worker.d.ts +13 -0
- package/dist/mcp-flow-worker.d.ts.map +1 -0
- package/dist/mcp-flow-worker.js +109 -0
- package/dist/mcp-flow-worker.js.map +1 -0
- package/dist/mcp-flow.d.ts +50 -0
- package/dist/mcp-flow.d.ts.map +1 -0
- package/dist/mcp-flow.js +75 -0
- package/dist/mcp-flow.js.map +1 -0
- package/package.json +30 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Cloudflare Durable Objects + SQLite adapter for `mcp-usage-control`.
|
|
4
4
|
|
|
5
|
-
> **
|
|
5
|
+
> **Distribution:** Check the current published version on [npm](https://www.npmjs.com/package/mcp-usage-control-cloudflare) and find verified source artifacts on [GitHub Releases](https://github.com/git-ksk/mcp-usage-control/releases). For an unreleased source candidate or local tarballs, follow [Use from source](https://github.com/git-ksk/mcp-usage-control/blob/main/docs/using-from-source.md).
|
|
6
6
|
|
|
7
7
|
## English
|
|
8
8
|
|
|
@@ -119,3 +119,75 @@ The reconciliation subpath now uses the core `UsageOperationReconciliation` voca
|
|
|
119
119
|
## Atomic vector usage (v0.7)
|
|
120
120
|
|
|
121
121
|
`CloudflareUsageStore` and `RemoteCloudflareUsageStore` implement optional `VectorUsageStore`. Schema v3 adds the `reservation_vectors` sidecar without rewriting v1/v2 scalar accounting rows. Durable Object `transactionSync` keeps all vector dimensions atomic; workerd integration covers vector conformance and remote committed-growth acknowledgement-loss replay.
|
|
122
|
+
|
|
123
|
+
## MCP durable multi-round Flow Store (v1.3 development, opt-in)
|
|
124
|
+
|
|
125
|
+
`mcp-usage-control-cloudflare/mcp-flow` provides `CloudflareMcpUsageFlowStore`
|
|
126
|
+
for `protectMultiRoundTool()`; separately export
|
|
127
|
+
`CloudflareMcpFlowDurableObject` from
|
|
128
|
+
`mcp-usage-control-cloudflare/mcp-flow-worker` as a **new** SQLite Durable
|
|
129
|
+
Object class. The existing `UsageControlDurableObject` accounting schema is
|
|
130
|
+
not modified. Bind the separate flow class in your Wrangler configuration:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"durable_objects": { "bindings": [
|
|
135
|
+
{ "name": "MCP_FLOWS", "class_name": "CloudflareMcpFlowDurableObject" }
|
|
136
|
+
] },
|
|
137
|
+
"exports": {
|
|
138
|
+
"CloudflareMcpFlowDurableObject": { "type": "durable-object", "storage": "sqlite" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { CloudflareMcpUsageFlowStore } from 'mcp-usage-control-cloudflare/mcp-flow';
|
|
145
|
+
export { CloudflareMcpFlowDurableObject } from 'mcp-usage-control-cloudflare/mcp-flow-worker';
|
|
146
|
+
|
|
147
|
+
// Create inside your Worker, using its private, server-side namespace binding.
|
|
148
|
+
const flowStore = new CloudflareMcpUsageFlowStore({
|
|
149
|
+
namespace: env.MCP_FLOWS,
|
|
150
|
+
domainPrefix: 'my-service-mcp-v1',
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Each flow ID is deterministically routed to **one separate SQLite DO**; the
|
|
155
|
+
DO atomically compares the server-trusted principal/tenant/tool/argument
|
|
156
|
+
binding and marks the flow consumed once. A consumed tombstone prevents ID
|
|
157
|
+
reuse before retention. DO-side `Date.now()` determines expiry; **it is not
|
|
158
|
+
Redis server TIME nor a promise of synchronized clocks across providers**.
|
|
159
|
+
Bounded state (64 KiB) is kept server-side; the client must never receive raw
|
|
160
|
+
lease state. Only trust signed SDK `requestState` and server-established
|
|
161
|
+
identity. The default 24h retention after flow expiry uses a DO alarm to
|
|
162
|
+
clear the tombstone/record; unlike the older accounting adapter's lazy cleanup,
|
|
163
|
+
this optional flow adapter **does schedule an alarm** and has per-flow DO/alarm
|
|
164
|
+
cost and quota implications. Configure a separate migration for the SQLite DO
|
|
165
|
+
class when deploying it; never reuse the accounting DO binding/schema.
|
|
166
|
+
|
|
167
|
+
The adapter supports **direct same-Worker DO RPC only**, not a remote HTTP
|
|
168
|
+
FlowStore. It provides no authentication gateway or business-task scheduler.
|
|
169
|
+
Do not serialize/store the flow in browser storage, attach it to public telemetry,
|
|
170
|
+
or bypass Worker authentication. An ambiguous suspend/consume RPC/ACK is a
|
|
171
|
+
hard failure: never blind-retry the same paid business effect. Workerd tests
|
|
172
|
+
cover 16-way consume contention, expired flows, duplicate suspension, binding
|
|
173
|
+
mismatch, lost ACK and consumed tombstones. Full official MCP SDK cross-provider
|
|
174
|
+
HTTP lifecycle tests are tracked separately in #271 before production release.
|
|
175
|
+
|
|
176
|
+
### MCP複数ラウンド用Cloudflare Flow Store(v1.3開発中)
|
|
177
|
+
|
|
178
|
+
`mcp-usage-control-cloudflare/mcp-flow` は `protectMultiRoundTool()` 向けの
|
|
179
|
+
opt-in 永続Flow Storeです。`mcp-flow-worker` の専用SQLite Durable Objectを
|
|
180
|
+
**既存の課金用DOとは別の名前・binding・schema**で設定してください。
|
|
181
|
+
各フローIDは専用DOに一意にルーティングし、principal/tenant/tool/argsを
|
|
182
|
+
照合して一度だけatomicに取得します。消費済みtombstoneが同じIDの再利用を防ぎます。
|
|
183
|
+
期限判定はDO側の`Date.now()`が基準で、他プロバイダーの時計同期や
|
|
184
|
+
完全な分散HA保証ではありません。
|
|
185
|
+
|
|
186
|
+
状態は最大64KiBでサーバー内部だけに保持し、clientが送信したIDや
|
|
187
|
+
未検証の`requestState`を信頼してはいけません。デフォルト期限後24時間の
|
|
188
|
+
保存後、**DO alarm**で記録を削除します。既存の会計用DOはlazy cleanupですが
|
|
189
|
+
新Flow Storeはアラームとフロー単位DOのコストが発生します。
|
|
190
|
+
同一Worker内の直接DO RPCのみが対応範囲で、リモートHTTP版や認証gateway、
|
|
191
|
+
タスクスケジューラは提供しません。ACK喪失時は安全側に停止し、
|
|
192
|
+
業務副作用を無条件再実行しないでください。workerdでの16並列競合等を
|
|
193
|
+
テストし、公式SDKを使った全プロバイダーE2Eは #271 の受入条件です。
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpUsageFlowBinding, McpUsageFlowRecord } from 'mcp-usage-control-mcp';
|
|
2
|
+
export declare function validateFlowRecord(record: McpUsageFlowRecord): void;
|
|
3
|
+
export declare function validateBinding(binding: McpUsageFlowBinding): void;
|
|
4
|
+
export declare function validateFlowId(flowId: string): void;
|
|
5
|
+
export declare function sameBinding(left: McpUsageFlowBinding, right: McpUsageFlowBinding): boolean;
|
|
6
|
+
export declare function cloneFlowRecord(record: McpUsageFlowRecord): McpUsageFlowRecord;
|
|
7
|
+
export declare function digestFlowBinding(binding: McpUsageFlowBinding): Promise<string>;
|
|
8
|
+
//# sourceMappingURL=mcp-flow-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-flow-validation.d.ts","sourceRoot":"","sources":["../src/mcp-flow-validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAKrF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CA6BnE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAclE;AAoFD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAInD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAO1F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CA2B9E;AAMD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { UsageStateError } from 'mcp-usage-control';
|
|
2
|
+
const FLOW_ID_PATTERN = /^[A-Za-z0-9._~-]{16,128}$/;
|
|
3
|
+
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
4
|
+
export function validateFlowRecord(record) {
|
|
5
|
+
if (!isRecord(record))
|
|
6
|
+
throw new UsageStateError('MCP usage flow record must be an object');
|
|
7
|
+
validateFlowId(record.flowId);
|
|
8
|
+
validateBinding(record.binding);
|
|
9
|
+
if (!Number.isSafeInteger(record.round) || record.round <= 0) {
|
|
10
|
+
throw new UsageStateError('MCP usage flow round must be a positive safe integer');
|
|
11
|
+
}
|
|
12
|
+
if (!Number.isSafeInteger(record.expiresAt) || record.expiresAt <= 0) {
|
|
13
|
+
throw new UsageStateError('MCP usage flow expiry must be a positive safe integer');
|
|
14
|
+
}
|
|
15
|
+
validateLeaseResumeState(record.lease);
|
|
16
|
+
if (record.lease.reservation.expiresAt !== record.expiresAt) {
|
|
17
|
+
throw new UsageStateError('MCP usage flow and lease expiry must match');
|
|
18
|
+
}
|
|
19
|
+
if (record.lease.reservation.principalId !== record.binding.principalId ||
|
|
20
|
+
record.lease.reservation.tenantId !== record.binding.tenantId ||
|
|
21
|
+
record.lease.reservation.tool !== record.binding.tool) {
|
|
22
|
+
throw new UsageStateError('MCP usage flow binding must match the resumable lease identity');
|
|
23
|
+
}
|
|
24
|
+
if (record.applicationRequestState !== undefined &&
|
|
25
|
+
(typeof record.applicationRequestState !== 'string' ||
|
|
26
|
+
record.applicationRequestState.length === 0 ||
|
|
27
|
+
record.applicationRequestState.length > 16_384)) {
|
|
28
|
+
throw new UsageStateError('Application requestState must be a non-empty bounded string');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function validateBinding(binding) {
|
|
32
|
+
if (!isRecord(binding))
|
|
33
|
+
throw new UsageStateError('MCP usage flow binding must be an object');
|
|
34
|
+
if (typeof binding.principalId !== 'string' || binding.principalId.length === 0) {
|
|
35
|
+
throw new UsageStateError('MCP usage flow principalId must be non-empty');
|
|
36
|
+
}
|
|
37
|
+
if (binding.tenantId !== undefined && typeof binding.tenantId !== 'string') {
|
|
38
|
+
throw new UsageStateError('MCP usage flow tenantId must be a string when present');
|
|
39
|
+
}
|
|
40
|
+
if (typeof binding.tool !== 'string' || binding.tool.length === 0) {
|
|
41
|
+
throw new UsageStateError('MCP usage flow tool must be non-empty');
|
|
42
|
+
}
|
|
43
|
+
if (typeof binding.argsHash !== 'string' || !HASH_PATTERN.test(binding.argsHash)) {
|
|
44
|
+
throw new UsageStateError('MCP usage flow argsHash must be SHA-256 hex');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function validateLeaseResumeState(state) {
|
|
48
|
+
if (!isRecord(state))
|
|
49
|
+
throw new UsageStateError('MCP usage lease resume state must be an object');
|
|
50
|
+
if (!Number.isSafeInteger(state.ttlMs) || state.ttlMs <= 0) {
|
|
51
|
+
throw new UsageStateError('MCP usage lease ttlMs must be a positive safe integer');
|
|
52
|
+
}
|
|
53
|
+
const reservation = state.reservation;
|
|
54
|
+
if (!isRecord(reservation))
|
|
55
|
+
throw new UsageStateError('MCP usage resume reservation must be an object');
|
|
56
|
+
for (const [name, value] of [
|
|
57
|
+
['id', reservation.id],
|
|
58
|
+
['operationId', reservation.operationId],
|
|
59
|
+
['principalId', reservation.principalId],
|
|
60
|
+
['tool', reservation.tool],
|
|
61
|
+
]) {
|
|
62
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
63
|
+
throw new UsageStateError(`MCP usage resume reservation ${name} must be non-empty`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (reservation.tenantId !== undefined && typeof reservation.tenantId !== 'string') {
|
|
67
|
+
throw new UsageStateError('MCP usage resume reservation tenantId must be a string when present');
|
|
68
|
+
}
|
|
69
|
+
if (reservation.plan !== undefined && typeof reservation.plan !== 'string') {
|
|
70
|
+
throw new UsageStateError('MCP usage resume reservation plan must be a string when present');
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(reservation.budgetKeys) ||
|
|
73
|
+
reservation.budgetKeys.length === 0 ||
|
|
74
|
+
reservation.budgetKeys.some(key => typeof key !== 'string' || key.length === 0)) {
|
|
75
|
+
throw new UsageStateError('MCP usage resume reservation must contain non-empty budget keys');
|
|
76
|
+
}
|
|
77
|
+
if (!Number.isSafeInteger(reservation.reservedUnits) || reservation.reservedUnits < 0) {
|
|
78
|
+
throw new UsageStateError('MCP usage resume reservation reservedUnits must be a non-negative safe integer');
|
|
79
|
+
}
|
|
80
|
+
if (!Number.isSafeInteger(reservation.expiresAt) || reservation.expiresAt <= 0) {
|
|
81
|
+
throw new UsageStateError('MCP usage resume reservation expiresAt must be a positive safe integer');
|
|
82
|
+
}
|
|
83
|
+
if (state.metadata !== undefined)
|
|
84
|
+
validateMetadata(state.metadata);
|
|
85
|
+
if (state.unresolvedGrowth !== undefined)
|
|
86
|
+
validateUnresolvedGrowth(state.unresolvedGrowth);
|
|
87
|
+
}
|
|
88
|
+
function validateUnresolvedGrowth(value) {
|
|
89
|
+
if (!isRecord(value)) {
|
|
90
|
+
throw new UsageStateError('MCP usage unresolved growth must be an object');
|
|
91
|
+
}
|
|
92
|
+
if (typeof value.incrementId !== 'string' || value.incrementId.length === 0) {
|
|
93
|
+
throw new UsageStateError('MCP usage unresolved growth incrementId must be non-empty');
|
|
94
|
+
}
|
|
95
|
+
if (!Number.isSafeInteger(value.additionalUnits) || value.additionalUnits <= 0) {
|
|
96
|
+
throw new UsageStateError('MCP usage unresolved growth additionalUnits must be a positive safe integer');
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(value.budgets) || value.budgets.length === 0) {
|
|
99
|
+
throw new UsageStateError('MCP usage unresolved growth must contain budgets');
|
|
100
|
+
}
|
|
101
|
+
let previousKey;
|
|
102
|
+
for (const budget of value.budgets) {
|
|
103
|
+
if (!isRecord(budget) || typeof budget.key !== 'string' || budget.key.length === 0) {
|
|
104
|
+
throw new UsageStateError('MCP usage unresolved growth budget key must be non-empty');
|
|
105
|
+
}
|
|
106
|
+
if (!Number.isSafeInteger(budget.limit) || budget.limit < 0) {
|
|
107
|
+
throw new UsageStateError('MCP usage unresolved growth budget limit must be a non-negative safe integer');
|
|
108
|
+
}
|
|
109
|
+
if (previousKey !== undefined && previousKey.localeCompare(budget.key) >= 0) {
|
|
110
|
+
throw new UsageStateError('MCP usage unresolved growth budgets must be canonical and unique');
|
|
111
|
+
}
|
|
112
|
+
previousKey = budget.key;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function validateMetadata(metadata) {
|
|
116
|
+
if (!isRecord(metadata))
|
|
117
|
+
throw new UsageStateError('MCP usage resume metadata must be an object');
|
|
118
|
+
for (const value of Object.values(metadata)) {
|
|
119
|
+
if (value !== null &&
|
|
120
|
+
typeof value !== 'string' &&
|
|
121
|
+
typeof value !== 'number' &&
|
|
122
|
+
typeof value !== 'boolean') {
|
|
123
|
+
throw new UsageStateError('MCP usage resume metadata contains an unsupported value');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export function validateFlowId(flowId) {
|
|
128
|
+
if (typeof flowId !== 'string' || !FLOW_ID_PATTERN.test(flowId)) {
|
|
129
|
+
throw new UsageStateError('MCP usage flow ID must be 16-128 URL-safe characters');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function sameBinding(left, right) {
|
|
133
|
+
return (left.principalId === right.principalId &&
|
|
134
|
+
left.tenantId === right.tenantId &&
|
|
135
|
+
left.tool === right.tool &&
|
|
136
|
+
left.argsHash === right.argsHash);
|
|
137
|
+
}
|
|
138
|
+
export function cloneFlowRecord(record) {
|
|
139
|
+
return {
|
|
140
|
+
flowId: record.flowId,
|
|
141
|
+
binding: { ...record.binding },
|
|
142
|
+
lease: {
|
|
143
|
+
reservation: {
|
|
144
|
+
...record.lease.reservation,
|
|
145
|
+
budgetKeys: [...record.lease.reservation.budgetKeys],
|
|
146
|
+
},
|
|
147
|
+
ttlMs: record.lease.ttlMs,
|
|
148
|
+
...(record.lease.metadata === undefined ? {} : { metadata: { ...record.lease.metadata } }),
|
|
149
|
+
...(record.lease.unresolvedGrowth === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: {
|
|
152
|
+
unresolvedGrowth: {
|
|
153
|
+
incrementId: record.lease.unresolvedGrowth.incrementId,
|
|
154
|
+
additionalUnits: record.lease.unresolvedGrowth.additionalUnits,
|
|
155
|
+
budgets: record.lease.unresolvedGrowth.budgets.map(budget => ({ ...budget })),
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
158
|
+
},
|
|
159
|
+
round: record.round,
|
|
160
|
+
expiresAt: record.expiresAt,
|
|
161
|
+
...(record.applicationRequestState === undefined
|
|
162
|
+
? {}
|
|
163
|
+
: { applicationRequestState: record.applicationRequestState }),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function isRecord(value) {
|
|
167
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
168
|
+
}
|
|
169
|
+
export async function digestFlowBinding(binding) {
|
|
170
|
+
validateBinding(binding);
|
|
171
|
+
const bytes = new TextEncoder().encode(JSON.stringify([
|
|
172
|
+
binding.principalId, binding.tenantId ?? null, binding.tool, binding.argsHash,
|
|
173
|
+
]));
|
|
174
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
|
175
|
+
return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=mcp-flow-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-flow-validation.js","sourceRoot":"","sources":["../src/mcp-flow-validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DurableObject, type DurableObjectState } from 'cloudflare:workers';
|
|
2
|
+
import { type CloudflareMcpFlowSuspend, type CloudflareMcpFlowConsume } from './mcp-flow.js';
|
|
3
|
+
/**
|
|
4
|
+
* Dedicated per-flow SQLite Durable Object. Never reuse the accounting DO class
|
|
5
|
+
* or its SQLite schema. Keep RPC binding private to the enclosing Worker.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CloudflareMcpFlowDurableObject extends DurableObject<unknown> {
|
|
8
|
+
constructor(ctx: DurableObjectState, env: unknown);
|
|
9
|
+
suspendFlow(input: CloudflareMcpFlowSuspend): Promise<'ok'>;
|
|
10
|
+
consumeFlow(input: CloudflareMcpFlowConsume): Promise<string | undefined>;
|
|
11
|
+
alarm(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=mcp-flow-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-flow-worker.d.ts","sourceRoot":"","sources":["../src/mcp-flow-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EAAwD,KAAK,wBAAwB,EAAE,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAYnJ;;;GAGG;AACH,qBAAa,8BAA+B,SAAQ,aAAa,CAAC,OAAO,CAAC;gBAC5D,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO;IAY3C,WAAW,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IA2C3D,WAAW,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyCzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
2
|
+
import { UsageStateError } from 'mcp-usage-control';
|
|
3
|
+
import { MAX_MCP_FLOW_RECORD_BYTES, MAX_MCP_FLOW_RETENTION_MS } from './mcp-flow.js';
|
|
4
|
+
import { digestFlowBinding, sameBinding, validateBinding, validateFlowId, validateFlowRecord } from './mcp-flow-validation.js';
|
|
5
|
+
/**
|
|
6
|
+
* Dedicated per-flow SQLite Durable Object. Never reuse the accounting DO class
|
|
7
|
+
* or its SQLite schema. Keep RPC binding private to the enclosing Worker.
|
|
8
|
+
*/
|
|
9
|
+
export class CloudflareMcpFlowDurableObject extends DurableObject {
|
|
10
|
+
constructor(ctx, env) {
|
|
11
|
+
super(ctx, env);
|
|
12
|
+
this.ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS mcp_flow_v1 (
|
|
13
|
+
flow_id TEXT PRIMARY KEY NOT NULL,
|
|
14
|
+
payload TEXT NOT NULL,
|
|
15
|
+
binding_digest TEXT NOT NULL,
|
|
16
|
+
expires_at INTEGER NOT NULL,
|
|
17
|
+
retain_until INTEGER NOT NULL,
|
|
18
|
+
status TEXT NOT NULL
|
|
19
|
+
)`);
|
|
20
|
+
}
|
|
21
|
+
async suspendFlow(input) {
|
|
22
|
+
validateFlowId(input.flowId);
|
|
23
|
+
if (!/^[a-f0-9]{64}$/.test(input.bindingDigest))
|
|
24
|
+
throw new UsageStateError('Invalid MCP flow binding digest');
|
|
25
|
+
if (typeof input.payload !== 'string' || new TextEncoder().encode(input.payload).byteLength > MAX_MCP_FLOW_RECORD_BYTES) {
|
|
26
|
+
throw new UsageStateError('Invalid MCP flow payload size');
|
|
27
|
+
}
|
|
28
|
+
if (!Number.isSafeInteger(input.expiresAt) || !Number.isSafeInteger(input.retentionMs) ||
|
|
29
|
+
input.retentionMs < 0 || input.retentionMs > MAX_MCP_FLOW_RETENTION_MS) {
|
|
30
|
+
throw new UsageStateError('Invalid MCP flow expiration/retention');
|
|
31
|
+
}
|
|
32
|
+
let record;
|
|
33
|
+
try {
|
|
34
|
+
record = JSON.parse(input.payload);
|
|
35
|
+
validateFlowRecord(record);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new UsageStateError('Invalid MCP flow record');
|
|
39
|
+
}
|
|
40
|
+
if (await digestFlowBinding(record.binding) !== input.bindingDigest) {
|
|
41
|
+
throw new UsageStateError('MCP flow digest does not match stored binding');
|
|
42
|
+
}
|
|
43
|
+
if (record.flowId !== input.flowId || record.expiresAt !== input.expiresAt) {
|
|
44
|
+
throw new UsageStateError('MCP flow record identity/expiry mismatch');
|
|
45
|
+
}
|
|
46
|
+
const retainUntil = input.expiresAt + input.retentionMs;
|
|
47
|
+
if (!Number.isSafeInteger(retainUntil))
|
|
48
|
+
throw new UsageStateError('MCP flow retention timestamp out of range');
|
|
49
|
+
this.ctx.storage.transactionSync(() => {
|
|
50
|
+
const now = Date.now(); // DO-side platform time domain: not application-host wall clock.
|
|
51
|
+
if (input.expiresAt <= now)
|
|
52
|
+
throw new UsageStateError('MCP flow is expired according to Durable Object time');
|
|
53
|
+
const exists = this.ctx.storage.sql.exec('SELECT flow_id FROM mcp_flow_v1 WHERE flow_id = ?', input.flowId).toArray();
|
|
54
|
+
if (exists.length > 0)
|
|
55
|
+
throw new UsageStateError('MCP flow ID already exists');
|
|
56
|
+
this.ctx.storage.sql.exec('INSERT INTO mcp_flow_v1 (flow_id,payload,binding_digest,expires_at,retain_until,status) VALUES (?,?,?,?,?,?)', input.flowId, input.payload, input.bindingDigest, input.expiresAt, retainUntil, 'active');
|
|
57
|
+
});
|
|
58
|
+
// A lost alarm RPC acknowledgement cannot authorize replay. The record
|
|
59
|
+
// remains safe if scheduling fails; future same-ID calls cannot overwrite.
|
|
60
|
+
await this.ctx.storage.setAlarm(retainUntil);
|
|
61
|
+
return 'ok';
|
|
62
|
+
}
|
|
63
|
+
async consumeFlow(input) {
|
|
64
|
+
validateFlowId(input.flowId);
|
|
65
|
+
if (!/^[a-f0-9]{64}$/.test(input.bindingDigest))
|
|
66
|
+
throw new UsageStateError('Invalid MCP flow binding digest');
|
|
67
|
+
validateBinding(input.binding);
|
|
68
|
+
if (await digestFlowBinding(input.binding) !== input.bindingDigest) {
|
|
69
|
+
throw new UsageStateError('MCP flow requested binding/digest mismatch');
|
|
70
|
+
}
|
|
71
|
+
return this.ctx.storage.transactionSync(() => {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const row = this.ctx.storage.sql.exec('SELECT * FROM mcp_flow_v1 WHERE flow_id = ?', input.flowId).toArray()[0];
|
|
74
|
+
if (!row || row.status === 'consumed')
|
|
75
|
+
return undefined;
|
|
76
|
+
if (row.status !== 'active' || typeof row.payload !== 'string' ||
|
|
77
|
+
typeof row.binding_digest !== 'string' || !Number.isSafeInteger(row.expires_at) ||
|
|
78
|
+
new TextEncoder().encode(row.payload).byteLength > MAX_MCP_FLOW_RECORD_BYTES) {
|
|
79
|
+
throw new UsageStateError('MCP flow DO record is corrupt');
|
|
80
|
+
}
|
|
81
|
+
if (row.binding_digest !== input.bindingDigest)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (row.expires_at <= now)
|
|
84
|
+
return undefined;
|
|
85
|
+
// Confirm the payload is structurally valid BEFORE the destructive claim.
|
|
86
|
+
let record;
|
|
87
|
+
try {
|
|
88
|
+
record = JSON.parse(row.payload);
|
|
89
|
+
validateFlowRecord(record);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new UsageStateError('MCP flow DO payload is invalid');
|
|
93
|
+
}
|
|
94
|
+
if (record.flowId !== input.flowId || record.expiresAt !== row.expires_at ||
|
|
95
|
+
!sameBinding(record.binding, input.binding)) {
|
|
96
|
+
throw new UsageStateError('MCP flow DO record is inconsistent');
|
|
97
|
+
}
|
|
98
|
+
// The digest is bound to the actual record by the separate protected adapter.
|
|
99
|
+
this.ctx.storage.sql.exec("UPDATE mcp_flow_v1 SET status = 'consumed', payload = '' WHERE flow_id = ? AND status = 'active'", input.flowId);
|
|
100
|
+
return row.payload;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async alarm() {
|
|
104
|
+
this.ctx.storage.transactionSync(() => {
|
|
105
|
+
this.ctx.storage.sql.exec('DELETE FROM mcp_flow_v1 WHERE retain_until <= ?', Date.now());
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=mcp-flow-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-flow-worker.js","sourceRoot":"","sources":["../src/mcp-flow-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { McpUsageFlowBinding, McpUsageFlowRecord } from 'mcp-usage-control-mcp';
|
|
2
|
+
export declare const MAX_MCP_FLOW_RECORD_BYTES = 65536;
|
|
3
|
+
export declare const MAX_MCP_FLOW_RETENTION_MS = 604800000;
|
|
4
|
+
/** Same-worker Durable Object RPC only; no network gateway or bearer-token auth is provided here. */
|
|
5
|
+
export interface CloudflareMcpFlowStub {
|
|
6
|
+
suspendFlow(input: CloudflareMcpFlowSuspend): Promise<'ok'>;
|
|
7
|
+
consumeFlow(input: CloudflareMcpFlowConsume): Promise<string | undefined>;
|
|
8
|
+
}
|
|
9
|
+
export interface CloudflareMcpFlowNamespace {
|
|
10
|
+
getByName?(name: string): CloudflareMcpFlowStub;
|
|
11
|
+
idFromName?(name: string): unknown;
|
|
12
|
+
get?(id: unknown): CloudflareMcpFlowStub;
|
|
13
|
+
}
|
|
14
|
+
/** Internal server-authorized command; never accept these fields from an MCP client. */
|
|
15
|
+
export interface CloudflareMcpFlowSuspend {
|
|
16
|
+
flowId: string;
|
|
17
|
+
payload: string;
|
|
18
|
+
bindingDigest: string;
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
retentionMs: number;
|
|
21
|
+
}
|
|
22
|
+
export interface CloudflareMcpFlowConsume {
|
|
23
|
+
flowId: string;
|
|
24
|
+
bindingDigest: string;
|
|
25
|
+
binding: McpUsageFlowBinding;
|
|
26
|
+
}
|
|
27
|
+
export interface CloudflareMcpUsageFlowStoreOptions {
|
|
28
|
+
/** Distinct namespace/domain for the Flow DO class, never the accounting DO. */
|
|
29
|
+
namespace: CloudflareMcpFlowNamespace;
|
|
30
|
+
/** Prevent collisions if multiple independent applications share one Flow DO namespace. */
|
|
31
|
+
domainPrefix?: string;
|
|
32
|
+
/** Keep consumed/expired tombstones after expiry, default 24h. */
|
|
33
|
+
retentionMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Opt-in, direct same-Worker FlowStore backed by one SQLite DO per flow ID.
|
|
37
|
+
* DO-host Date.now() is the expiry authority; application host clocks are not.
|
|
38
|
+
* Any RPC/ACK error is propagated: the caller MUST fail closed and never blindly
|
|
39
|
+
* retry business work or create another usage reservation.
|
|
40
|
+
*/
|
|
41
|
+
export declare class CloudflareMcpUsageFlowStore {
|
|
42
|
+
private readonly namespace;
|
|
43
|
+
private readonly domainPrefix;
|
|
44
|
+
private readonly retentionMs;
|
|
45
|
+
constructor(options: CloudflareMcpUsageFlowStoreOptions);
|
|
46
|
+
suspend(record: McpUsageFlowRecord): Promise<void>;
|
|
47
|
+
consume(flowId: string, binding: McpUsageFlowBinding): Promise<McpUsageFlowRecord | undefined>;
|
|
48
|
+
private stub;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=mcp-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-flow.d.ts","sourceRoot":"","sources":["../src/mcp-flow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGrF,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAChD,eAAO,MAAM,yBAAyB,YAAc,CAAC;AAErD,qGAAqG;AACrG,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,WAAW,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CAAC;IAChD,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,GAAG,qBAAqB,CAAC;CAC1C;AAED,wFAAwF;AACxF,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,MAAM,WAAW,kCAAkC;IACjD,gFAAgF;IAChF,SAAS,EAAE,0BAA0B,CAAC;IACtC,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBAAa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,OAAO,EAAE,kCAAkC;IAYjD,OAAO,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBlD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAsBpG,OAAO,CAAC,IAAI;CASb"}
|
package/dist/mcp-flow.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { UsageStateError } from 'mcp-usage-control';
|
|
2
|
+
import { cloneFlowRecord, digestFlowBinding, sameBinding, validateBinding, validateFlowId, validateFlowRecord } from './mcp-flow-validation.js';
|
|
3
|
+
export const MAX_MCP_FLOW_RECORD_BYTES = 65_536;
|
|
4
|
+
export const MAX_MCP_FLOW_RETENTION_MS = 604_800_000;
|
|
5
|
+
/**
|
|
6
|
+
* Opt-in, direct same-Worker FlowStore backed by one SQLite DO per flow ID.
|
|
7
|
+
* DO-host Date.now() is the expiry authority; application host clocks are not.
|
|
8
|
+
* Any RPC/ACK error is propagated: the caller MUST fail closed and never blindly
|
|
9
|
+
* retry business work or create another usage reservation.
|
|
10
|
+
*/
|
|
11
|
+
export class CloudflareMcpUsageFlowStore {
|
|
12
|
+
namespace;
|
|
13
|
+
domainPrefix;
|
|
14
|
+
retentionMs;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.namespace = options.namespace;
|
|
17
|
+
this.domainPrefix = options.domainPrefix ?? 'muc-flow-v1';
|
|
18
|
+
if (!/^[A-Za-z0-9._~-]{1,80}$/.test(this.domainPrefix)) {
|
|
19
|
+
throw new RangeError('MCP Flow DO domainPrefix must be 1-80 URL-safe characters');
|
|
20
|
+
}
|
|
21
|
+
this.retentionMs = options.retentionMs ?? 86_400_000;
|
|
22
|
+
if (!Number.isSafeInteger(this.retentionMs) || this.retentionMs < 0 || this.retentionMs > MAX_MCP_FLOW_RETENTION_MS) {
|
|
23
|
+
throw new RangeError('MCP Flow DO retentionMs must be a safe integer from 0 to 7 days');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async suspend(record) {
|
|
27
|
+
validateFlowRecord(record);
|
|
28
|
+
const payload = JSON.stringify(cloneFlowRecord(record));
|
|
29
|
+
if (new TextEncoder().encode(payload).byteLength > MAX_MCP_FLOW_RECORD_BYTES) {
|
|
30
|
+
throw new RangeError(`Cloudflare MCP flow payload exceeds ${MAX_MCP_FLOW_RECORD_BYTES} bytes`);
|
|
31
|
+
}
|
|
32
|
+
const bindingDigest = await digestFlowBinding(record.binding);
|
|
33
|
+
await this.stub(record.flowId).suspendFlow({
|
|
34
|
+
flowId: record.flowId,
|
|
35
|
+
payload,
|
|
36
|
+
bindingDigest,
|
|
37
|
+
expiresAt: record.expiresAt,
|
|
38
|
+
retentionMs: this.retentionMs,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async consume(flowId, binding) {
|
|
42
|
+
validateFlowId(flowId);
|
|
43
|
+
validateBinding(binding);
|
|
44
|
+
const bindingDigest = await digestFlowBinding(binding);
|
|
45
|
+
const payload = await this.stub(flowId).consumeFlow({ flowId, bindingDigest, binding });
|
|
46
|
+
if (payload === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (typeof payload !== 'string' || new TextEncoder().encode(payload).byteLength > MAX_MCP_FLOW_RECORD_BYTES) {
|
|
49
|
+
throw new UsageStateError('Cloudflare MCP flow payload is malformed');
|
|
50
|
+
}
|
|
51
|
+
let record;
|
|
52
|
+
try {
|
|
53
|
+
record = JSON.parse(payload);
|
|
54
|
+
validateFlowRecord(record);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new UsageStateError('Cloudflare MCP flow payload could not be decoded');
|
|
58
|
+
}
|
|
59
|
+
if (record.flowId !== flowId || !sameBinding(record.binding, binding)) {
|
|
60
|
+
throw new UsageStateError('Cloudflare MCP flow payload binding mismatch');
|
|
61
|
+
}
|
|
62
|
+
return cloneFlowRecord(record);
|
|
63
|
+
}
|
|
64
|
+
stub(flowId) {
|
|
65
|
+
validateFlowId(flowId);
|
|
66
|
+
const domain = `${this.domainPrefix}:${flowId}`;
|
|
67
|
+
if (typeof this.namespace.getByName === 'function')
|
|
68
|
+
return this.namespace.getByName(domain);
|
|
69
|
+
if (typeof this.namespace.idFromName === 'function' && typeof this.namespace.get === 'function') {
|
|
70
|
+
return this.namespace.get(this.namespace.idFromName(domain));
|
|
71
|
+
}
|
|
72
|
+
throw new UsageStateError('Cloudflare MCP Flow DO namespace does not support getByName or idFromName/get');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=mcp-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-flow.js","sourceRoot":"","sources":["../src/mcp-flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEhJ,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAChD,MAAM,CAAC,MAAM,yBAAyB,GAAG,WAAW,CAAC;AAsCrD;;;;;GAKG;AACH,MAAM,OAAO,2BAA2B;IACrB,SAAS,CAA6B;IACtC,YAAY,CAAS;IACrB,WAAW,CAAS;IAErC,YAAY,OAA2C;QACrD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,aAAa,CAAC;QAC1D,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,UAAU,CAAC,2DAA2D,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,UAAU,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,WAAW,GAAG,yBAAyB,EAAE,CAAC;YACpH,MAAM,IAAI,UAAU,CAAC,iEAAiE,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAA0B;QACtC,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;QACxD,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,UAAU,GAAG,yBAAyB,EAAE,CAAC;YAC7E,MAAM,IAAI,UAAU,CAAC,uCAAuC,yBAAyB,QAAQ,CAAC,CAAC;QACjG,CAAC;QACD,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;YACzC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO;YACP,aAAa;YACb,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,OAA4B;QACxD,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,CAAC;QACxF,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC5C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,UAAU,GAAG,yBAAyB,EAAE,CAAC;YAC5G,MAAM,IAAI,eAAe,CAAC,0CAA0C,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAuB,CAAC;YACnD,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,eAAe,CAAC,kDAAkD,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,eAAe,CAAC,8CAA8C,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAEO,IAAI,CAAC,MAAc;QACzB,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC;QAChD,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC5F,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;YAChG,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,IAAI,eAAe,CAAC,+EAA+E,CAAC,CAAC;IAC7G,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-usage-control-cloudflare",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Cloudflare Durable Objects usage store for mcp-usage-control.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
"types": "./dist/maintenance.d.ts",
|
|
38
38
|
"import": "./dist/maintenance.js",
|
|
39
39
|
"default": "./dist/maintenance.js"
|
|
40
|
+
},
|
|
41
|
+
"./mcp-flow": {
|
|
42
|
+
"types": "./dist/mcp-flow.d.ts",
|
|
43
|
+
"import": "./dist/mcp-flow.js",
|
|
44
|
+
"default": "./dist/mcp-flow.js"
|
|
45
|
+
},
|
|
46
|
+
"./mcp-flow-worker": {
|
|
47
|
+
"types": "./dist/mcp-flow-worker.d.ts",
|
|
48
|
+
"import": "./dist/mcp-flow-worker.js",
|
|
49
|
+
"default": "./dist/mcp-flow-worker.js"
|
|
40
50
|
}
|
|
41
51
|
},
|
|
42
52
|
"files": [
|
|
@@ -81,7 +91,19 @@
|
|
|
81
91
|
"dist/maintenance-protocol.d.ts",
|
|
82
92
|
"dist/maintenance-protocol.d.ts.map",
|
|
83
93
|
"README.md",
|
|
84
|
-
"LICENSE"
|
|
94
|
+
"LICENSE",
|
|
95
|
+
"dist/mcp-flow.js",
|
|
96
|
+
"dist/mcp-flow.js.map",
|
|
97
|
+
"dist/mcp-flow.d.ts",
|
|
98
|
+
"dist/mcp-flow.d.ts.map",
|
|
99
|
+
"dist/mcp-flow-validation.js",
|
|
100
|
+
"dist/mcp-flow-validation.js.map",
|
|
101
|
+
"dist/mcp-flow-validation.d.ts",
|
|
102
|
+
"dist/mcp-flow-validation.d.ts.map",
|
|
103
|
+
"dist/mcp-flow-worker.js",
|
|
104
|
+
"dist/mcp-flow-worker.js.map",
|
|
105
|
+
"dist/mcp-flow-worker.d.ts",
|
|
106
|
+
"dist/mcp-flow-worker.d.ts.map"
|
|
85
107
|
],
|
|
86
108
|
"engines": {
|
|
87
109
|
"node": ">=22"
|
|
@@ -112,6 +134,11 @@
|
|
|
112
134
|
"test": "vitest run src"
|
|
113
135
|
},
|
|
114
136
|
"dependencies": {
|
|
115
|
-
"mcp-usage-control": "^1.
|
|
137
|
+
"mcp-usage-control": "^1.3.0"
|
|
138
|
+
},
|
|
139
|
+
"devDependencies": {
|
|
140
|
+
"mcp-usage-control-mcp": "^1.3.0",
|
|
141
|
+
"@modelcontextprotocol/client": "2.3.1",
|
|
142
|
+
"@modelcontextprotocol/server": "2.3.1"
|
|
116
143
|
}
|
|
117
144
|
}
|