product-spec-mcp 0.3.26 → 0.3.27
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 +25 -1
- package/package.json +1 -1
- package/workers/pm-intent-gate.mjs +42 -16
- package/workers/wrangler.toml.example +12 -7
package/dist/index.cjs
CHANGED
|
@@ -29682,7 +29682,7 @@ function registerProductSpecAssist(server) {
|
|
|
29682
29682
|
function createServer() {
|
|
29683
29683
|
const server = new McpServer({
|
|
29684
29684
|
name: "product-spec-mcp",
|
|
29685
|
-
version: "0.3.
|
|
29685
|
+
version: "0.3.27"
|
|
29686
29686
|
});
|
|
29687
29687
|
registerSpecInterrogate(server);
|
|
29688
29688
|
registerSpecCompile(server);
|
package/docs/online-pm-gate.md
CHANGED
|
@@ -41,11 +41,35 @@ wrangler kv namespace create PROMPT_CACHE
|
|
|
41
41
|
wrangler d1 create product-spec-prompt-samples
|
|
42
42
|
wrangler d1 execute product-spec-prompt-samples --file schema.sql
|
|
43
43
|
wrangler secret put GATE_TOKEN
|
|
44
|
-
wrangler secret put
|
|
44
|
+
wrangler secret put MIMO_API_KEY
|
|
45
45
|
wrangler secret put RATE_LIMIT_SALT
|
|
46
46
|
wrangler deploy
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Default LLM provider:
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[vars]
|
|
53
|
+
LLM_PROVIDER = "mimo"
|
|
54
|
+
LLM_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1"
|
|
55
|
+
LLM_MODEL = "mimo-v2.5"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To switch later to DeepSeek, change the Worker vars to:
|
|
59
|
+
|
|
60
|
+
```toml
|
|
61
|
+
[vars]
|
|
62
|
+
LLM_PROVIDER = "deepseek"
|
|
63
|
+
LLM_BASE_URL = "https://api.deepseek.com"
|
|
64
|
+
LLM_MODEL = "deepseek-chat"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then set:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
wrangler secret put DEEPSEEK_API_KEY
|
|
71
|
+
```
|
|
72
|
+
|
|
49
73
|
Runtime behavior:
|
|
50
74
|
|
|
51
75
|
- Prompt cache key: `cache:{model}:{promptHash}:pm-gate-v1`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "product-spec-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.27",
|
|
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",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const GATE_SCHEMA_VERSION = "pm-gate-v1";
|
|
2
|
-
const
|
|
2
|
+
const DEFAULT_PROVIDER = "mimo";
|
|
3
|
+
const DEFAULT_MIMO_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1";
|
|
4
|
+
const DEFAULT_MIMO_MODEL = "mimo-v2.5";
|
|
5
|
+
const DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
|
6
|
+
const DEFAULT_DEEPSEEK_MODEL = "deepseek-chat";
|
|
3
7
|
const DAILY_LIMIT = 3;
|
|
4
8
|
|
|
5
9
|
export default {
|
|
@@ -25,8 +29,8 @@ export default {
|
|
|
25
29
|
const telemetryMode = normalizeTelemetry(request.headers.get("x-product-spec-telemetry") || "off");
|
|
26
30
|
const message = String(body.message || "").slice(0, 500);
|
|
27
31
|
const messageHash = body.messageHash || await sha256(normalizeText(message));
|
|
28
|
-
const
|
|
29
|
-
const cacheKey = `cache:${model}:${messageHash}:${GATE_SCHEMA_VERSION}`;
|
|
32
|
+
const llm = resolveLlmConfig(env);
|
|
33
|
+
const cacheKey = `cache:${llm.provider}:${llm.model}:${messageHash}:${GATE_SCHEMA_VERSION}`;
|
|
30
34
|
const cached = await env.PROMPT_CACHE?.get(cacheKey, "json");
|
|
31
35
|
const ipKey = await rateLimitKey(request, env);
|
|
32
36
|
const resetAt = nextShanghaiMidnightIso();
|
|
@@ -41,8 +45,8 @@ export default {
|
|
|
41
45
|
decision: cached.decision,
|
|
42
46
|
llmGate: {
|
|
43
47
|
used: false,
|
|
44
|
-
provider:
|
|
45
|
-
model,
|
|
48
|
+
provider: llm.provider,
|
|
49
|
+
model: llm.model,
|
|
46
50
|
promptTokensApprox: cached.promptTokensApprox || 0,
|
|
47
51
|
completionTokensApprox: cached.completionTokensApprox || 0,
|
|
48
52
|
cacheHit: true,
|
|
@@ -66,7 +70,7 @@ export default {
|
|
|
66
70
|
});
|
|
67
71
|
return json({
|
|
68
72
|
decision: fallbackDecision(body.ruleDecision),
|
|
69
|
-
llmGate: { used: false, provider:
|
|
73
|
+
llmGate: { used: false, provider: llm.provider, model: llm.model, cacheHit: false },
|
|
70
74
|
rateLimit: { limit: DAILY_LIMIT, remaining: 0, resetAt },
|
|
71
75
|
privacy: privacyResult(telemetryMode),
|
|
72
76
|
}, 429);
|
|
@@ -79,7 +83,7 @@ export default {
|
|
|
79
83
|
let completionTokensApprox = 0;
|
|
80
84
|
let fallbackReason = "";
|
|
81
85
|
try {
|
|
82
|
-
const llmText = await
|
|
86
|
+
const llmText = await callOpenAiCompatible(llm, prompt);
|
|
83
87
|
completionTokensApprox = approxTokens(llmText);
|
|
84
88
|
llmDecision = sanitizeDecision(extractJson(llmText));
|
|
85
89
|
if (!llmDecision) fallbackReason = "invalid_llm_schema";
|
|
@@ -109,8 +113,8 @@ export default {
|
|
|
109
113
|
decision: finalDecision,
|
|
110
114
|
llmGate: {
|
|
111
115
|
used: Boolean(llmDecision),
|
|
112
|
-
provider:
|
|
113
|
-
model,
|
|
116
|
+
provider: llm.provider,
|
|
117
|
+
model: llm.model,
|
|
114
118
|
promptTokensApprox,
|
|
115
119
|
completionTokensApprox,
|
|
116
120
|
cacheHit: false,
|
|
@@ -159,16 +163,34 @@ function buildGatePrompt(message, rule, choices) {
|
|
|
159
163
|
});
|
|
160
164
|
}
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
function resolveLlmConfig(env) {
|
|
167
|
+
const provider = String(env.LLM_PROVIDER || DEFAULT_PROVIDER).toLowerCase();
|
|
168
|
+
if (provider === "deepseek") {
|
|
169
|
+
return {
|
|
170
|
+
provider,
|
|
171
|
+
baseUrl: env.LLM_BASE_URL || env.DEEPSEEK_BASE_URL || DEFAULT_DEEPSEEK_BASE_URL,
|
|
172
|
+
model: env.LLM_MODEL || env.DEEPSEEK_MODEL || DEFAULT_DEEPSEEK_MODEL,
|
|
173
|
+
apiKey: env.LLM_API_KEY || env.DEEPSEEK_API_KEY,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
provider: "mimo",
|
|
178
|
+
baseUrl: env.LLM_BASE_URL || env.MIMO_BASE_URL || DEFAULT_MIMO_BASE_URL,
|
|
179
|
+
model: env.LLM_MODEL || env.MIMO_MODEL || DEFAULT_MIMO_MODEL,
|
|
180
|
+
apiKey: env.LLM_API_KEY || env.MIMO_API_KEY,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function callOpenAiCompatible(llm, prompt) {
|
|
185
|
+
if (!llm.apiKey) throw new Error(`missing_${llm.provider}_api_key`);
|
|
186
|
+
const response = await fetch(`${normalizeBaseUrl(llm.baseUrl)}/chat/completions`, {
|
|
165
187
|
method: "POST",
|
|
166
188
|
headers: {
|
|
167
189
|
"content-type": "application/json",
|
|
168
|
-
authorization: `Bearer ${
|
|
190
|
+
authorization: `Bearer ${llm.apiKey}`,
|
|
169
191
|
},
|
|
170
192
|
body: JSON.stringify({
|
|
171
|
-
model,
|
|
193
|
+
model: llm.model,
|
|
172
194
|
temperature: 0.1,
|
|
173
195
|
max_tokens: 600,
|
|
174
196
|
messages: [
|
|
@@ -177,13 +199,17 @@ async function callDeepSeek(env, model, prompt) {
|
|
|
177
199
|
],
|
|
178
200
|
}),
|
|
179
201
|
});
|
|
180
|
-
if (!response.ok) throw new Error(
|
|
202
|
+
if (!response.ok) throw new Error(`${llm.provider}_http_${response.status}`);
|
|
181
203
|
const data = await response.json();
|
|
182
204
|
const content = data?.choices?.[0]?.message?.content;
|
|
183
|
-
if (typeof content !== "string" || !content.trim()) throw new Error(
|
|
205
|
+
if (typeof content !== "string" || !content.trim()) throw new Error(`${llm.provider}_empty_content`);
|
|
184
206
|
return content;
|
|
185
207
|
}
|
|
186
208
|
|
|
209
|
+
function normalizeBaseUrl(baseUrl) {
|
|
210
|
+
return String(baseUrl || "").replace(/\/+$/, "");
|
|
211
|
+
}
|
|
212
|
+
|
|
187
213
|
function extractJson(text) {
|
|
188
214
|
try {
|
|
189
215
|
return JSON.parse(text);
|
|
@@ -2,18 +2,23 @@ name = "product-spec-pm-intent-gate"
|
|
|
2
2
|
main = "pm-intent-gate.mjs"
|
|
3
3
|
compatibility_date = "2026-06-23"
|
|
4
4
|
|
|
5
|
-
kv_namespaces
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
[[kv_namespaces]]
|
|
6
|
+
binding = "PROMPT_CACHE"
|
|
7
|
+
id = "replace-with-kv-namespace-id"
|
|
8
8
|
|
|
9
|
-
d1_databases
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
[[d1_databases]]
|
|
10
|
+
binding = "PROMPT_SAMPLES"
|
|
11
|
+
database_name = "product-spec-prompt-samples"
|
|
12
|
+
database_id = "replace-with-d1-database-id"
|
|
12
13
|
|
|
13
14
|
[vars]
|
|
14
|
-
|
|
15
|
+
LLM_PROVIDER = "mimo"
|
|
16
|
+
LLM_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1"
|
|
17
|
+
LLM_MODEL = "mimo-v2.5"
|
|
15
18
|
|
|
16
19
|
# Secrets to set with wrangler:
|
|
17
20
|
# wrangler secret put GATE_TOKEN
|
|
21
|
+
# wrangler secret put MIMO_API_KEY
|
|
22
|
+
# Optional fallback/switch later:
|
|
18
23
|
# wrangler secret put DEEPSEEK_API_KEY
|
|
19
24
|
# wrangler secret put RATE_LIMIT_SALT
|