product-spec-mcp 0.3.32 → 0.3.34
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/index.cjs +1 -1
- package/docs/online-pm-gate.md +29 -1
- package/package.json +1 -1
- package/workers/pm-intent-gate.mjs +20 -13
- package/workers/wrangler.toml.example +1 -0
package/dist/index.cjs
CHANGED
|
@@ -29976,7 +29976,7 @@ function registerProductSpecAssist(server) {
|
|
|
29976
29976
|
function createServer() {
|
|
29977
29977
|
const server = new McpServer({
|
|
29978
29978
|
name: "product-spec-mcp",
|
|
29979
|
-
version: "0.3.
|
|
29979
|
+
version: "0.3.34"
|
|
29980
29980
|
});
|
|
29981
29981
|
registerSpecInterrogate(server);
|
|
29982
29982
|
registerSpecCompile(server);
|
package/docs/online-pm-gate.md
CHANGED
|
@@ -53,6 +53,7 @@ Default LLM provider:
|
|
|
53
53
|
LLM_PROVIDER = "mimo"
|
|
54
54
|
LLM_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1"
|
|
55
55
|
LLM_MODEL = "mimo-v2.5"
|
|
56
|
+
DAILY_LLM_LIMIT = "20"
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
To switch later to DeepSeek, change the Worker vars to:
|
|
@@ -74,9 +75,36 @@ Runtime behavior:
|
|
|
74
75
|
|
|
75
76
|
- Prompt cache key: `cache:{model}:{promptHash}:pm-gate-v1`
|
|
76
77
|
- Cache TTL: 7 days
|
|
77
|
-
- LLM quota:
|
|
78
|
+
- LLM quota: `DAILY_LLM_LIMIT` non-cached LLM decisions per IP per Shanghai calendar day. Default: 20.
|
|
78
79
|
- User message sent to LLM: max 500 characters
|
|
79
80
|
- LLM max output tokens: 600
|
|
80
81
|
- LLM temperature: 0.1
|
|
81
82
|
|
|
83
|
+
## Change LLM Daily Quota
|
|
84
|
+
|
|
85
|
+
`DAILY_LLM_LIMIT` controls the number of non-cached LLM gate calls allowed per IP per Shanghai calendar day. It is a Worker runtime variable, not an npm package setting.
|
|
86
|
+
|
|
87
|
+
Default:
|
|
88
|
+
|
|
89
|
+
```toml
|
|
90
|
+
DAILY_LLM_LIMIT = "20"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To change it from local config:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cd /Users/george/Documents/product-spec-mcp/workers
|
|
97
|
+
# edit DAILY_LLM_LIMIT in wrangler.toml
|
|
98
|
+
npx wrangler deploy
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
To change it from Cloudflare Dashboard:
|
|
102
|
+
|
|
103
|
+
1. Open Worker `product-spec-pm-intent-gate`.
|
|
104
|
+
2. Go to Variables and Secrets.
|
|
105
|
+
3. Edit plaintext variable `DAILY_LLM_LIMIT`.
|
|
106
|
+
4. Save/deploy the Worker configuration.
|
|
107
|
+
|
|
108
|
+
Changing only this quota does not require an npm release. npm only needs to be published when the package code, bundled Worker file, or documentation should be distributed to npm users.
|
|
109
|
+
|
|
82
110
|
If the Worker is unreachable, rate-limited, returns invalid JSON, or returns invalid enum fields, the local MCP falls back to the local PM Gate decision.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "product-spec-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"description": "MCP Server for product specification - requirement interrogation, architecture decision, UI translation, debug guidance, and acceptance generation",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -4,7 +4,7 @@ const DEFAULT_MIMO_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1";
|
|
|
4
4
|
const DEFAULT_MIMO_MODEL = "mimo-v2.5";
|
|
5
5
|
const DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
|
6
6
|
const DEFAULT_DEEPSEEK_MODEL = "deepseek-chat";
|
|
7
|
-
const
|
|
7
|
+
const DEFAULT_DAILY_LIMIT = 20;
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
async fetch(request, env) {
|
|
@@ -34,6 +34,7 @@ export default {
|
|
|
34
34
|
const cached = await env.PROMPT_CACHE?.get(cacheKey, "json");
|
|
35
35
|
const ipKey = await rateLimitKey(request, env);
|
|
36
36
|
const resetAt = nextShanghaiMidnightIso();
|
|
37
|
+
const dailyLimit = resolveDailyLimit(env);
|
|
37
38
|
|
|
38
39
|
if (cached?.decision) {
|
|
39
40
|
await maybeStoreSample(env, telemetryMode, body, cached.decision, cached.decision, {
|
|
@@ -52,15 +53,15 @@ export default {
|
|
|
52
53
|
cacheHit: true,
|
|
53
54
|
},
|
|
54
55
|
rateLimit: {
|
|
55
|
-
limit:
|
|
56
|
-
remaining: await remainingForKey(env, ipKey),
|
|
56
|
+
limit: dailyLimit,
|
|
57
|
+
remaining: await remainingForKey(env, ipKey, dailyLimit),
|
|
57
58
|
resetAt,
|
|
58
59
|
},
|
|
59
60
|
privacy: privacyResult(telemetryMode),
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
const limit = await consumeLimit(env, ipKey, resetAt);
|
|
64
|
+
const limit = await consumeLimit(env, ipKey, resetAt, dailyLimit);
|
|
64
65
|
if (!limit.allowed) {
|
|
65
66
|
await maybeStoreSample(env, telemetryMode, body, null, body.ruleDecision || {}, {
|
|
66
67
|
llmUsed: 0,
|
|
@@ -71,7 +72,7 @@ export default {
|
|
|
71
72
|
return json({
|
|
72
73
|
decision: fallbackDecision(body.ruleDecision),
|
|
73
74
|
llmGate: { used: false, provider: llm.provider, model: llm.model, cacheHit: false },
|
|
74
|
-
rateLimit: { limit:
|
|
75
|
+
rateLimit: { limit: dailyLimit, remaining: 0, resetAt },
|
|
75
76
|
privacy: privacyResult(telemetryMode),
|
|
76
77
|
}, 429);
|
|
77
78
|
}
|
|
@@ -121,7 +122,7 @@ export default {
|
|
|
121
122
|
...(fallbackReason ? { fallbackReason } : {}),
|
|
122
123
|
},
|
|
123
124
|
rateLimit: {
|
|
124
|
-
limit:
|
|
125
|
+
limit: dailyLimit,
|
|
125
126
|
remaining: limit.remaining,
|
|
126
127
|
resetAt,
|
|
127
128
|
},
|
|
@@ -195,6 +196,12 @@ function resolveLlmConfig(env) {
|
|
|
195
196
|
};
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
function resolveDailyLimit(env) {
|
|
200
|
+
const parsed = Number(env.DAILY_LLM_LIMIT || DEFAULT_DAILY_LIMIT);
|
|
201
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_DAILY_LIMIT;
|
|
202
|
+
return Math.floor(parsed);
|
|
203
|
+
}
|
|
204
|
+
|
|
198
205
|
async function callOpenAiCompatible(llm, prompt) {
|
|
199
206
|
if (!llm.apiKey) throw new Error(`missing_${llm.provider}_api_key`);
|
|
200
207
|
const response = await fetch(`${normalizeBaseUrl(llm.baseUrl)}/chat/completions`, {
|
|
@@ -343,20 +350,20 @@ function fallbackDecision(ruleDecision) {
|
|
|
343
350
|
};
|
|
344
351
|
}
|
|
345
352
|
|
|
346
|
-
async function consumeLimit(env, key, resetAt) {
|
|
347
|
-
if (!env.PROMPT_CACHE) return { allowed: true, remaining:
|
|
353
|
+
async function consumeLimit(env, key, resetAt, dailyLimit) {
|
|
354
|
+
if (!env.PROMPT_CACHE) return { allowed: true, remaining: dailyLimit - 1 };
|
|
348
355
|
const current = Number(await env.PROMPT_CACHE.get(key) || "0");
|
|
349
|
-
if (current >=
|
|
356
|
+
if (current >= dailyLimit) return { allowed: false, remaining: 0 };
|
|
350
357
|
const next = current + 1;
|
|
351
358
|
const resetSeconds = Math.max(60, Math.floor((new Date(resetAt).getTime() - Date.now()) / 1000));
|
|
352
359
|
await env.PROMPT_CACHE.put(key, String(next), { expirationTtl: resetSeconds });
|
|
353
|
-
return { allowed: true, remaining: Math.max(0,
|
|
360
|
+
return { allowed: true, remaining: Math.max(0, dailyLimit - next) };
|
|
354
361
|
}
|
|
355
362
|
|
|
356
|
-
async function remainingForKey(env, key) {
|
|
357
|
-
if (!env.PROMPT_CACHE) return
|
|
363
|
+
async function remainingForKey(env, key, dailyLimit) {
|
|
364
|
+
if (!env.PROMPT_CACHE) return dailyLimit;
|
|
358
365
|
const current = Number(await env.PROMPT_CACHE.get(key) || "0");
|
|
359
|
-
return Math.max(0,
|
|
366
|
+
return Math.max(0, dailyLimit - current);
|
|
360
367
|
}
|
|
361
368
|
|
|
362
369
|
async function rateLimitKey(request, env) {
|