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 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.26"
29685
+ version: "0.3.27"
29686
29686
  });
29687
29687
  registerSpecInterrogate(server);
29688
29688
  registerSpecCompile(server);
@@ -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 DEEPSEEK_API_KEY
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.26",
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 DEFAULT_MODEL = "deepseek-chat";
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 model = env.DEEPSEEK_MODEL || DEFAULT_MODEL;
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: "deepseek",
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: "deepseek", model, cacheHit: false },
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 callDeepSeek(env, model, prompt);
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: "deepseek",
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
- async function callDeepSeek(env, model, prompt) {
163
- if (!env.DEEPSEEK_API_KEY) throw new Error("missing_deepseek_api_key");
164
- const response = await fetch("https://api.deepseek.com/chat/completions", {
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 ${env.DEEPSEEK_API_KEY}`,
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(`deepseek_http_${response.status}`);
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("deepseek_empty_content");
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
- { binding = "PROMPT_CACHE", id = "replace-with-kv-namespace-id" }
7
- ]
5
+ [[kv_namespaces]]
6
+ binding = "PROMPT_CACHE"
7
+ id = "replace-with-kv-namespace-id"
8
8
 
9
- d1_databases = [
10
- { binding = "PROMPT_SAMPLES", database_name = "product-spec-prompt-samples", database_id = "replace-with-d1-database-id" }
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
- DEEPSEEK_MODEL = "deepseek-chat"
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