mcp-prompt-optimizer 3.7.5 → 3.8.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.8.1] - 2026-07-29
9
+
10
+ ### Fixed
11
+ - **Security: the validation cache let any API key inherit a different key's cached identity.**
12
+ `~/.mcp-cloud-api-cache.json` is a single file per machine, not namespaced per key. Found live
13
+ while testing against a real registered key: validating key A succeeds and caches its result;
14
+ minutes later, validating a completely different, invalid key B gets a clean 401 from the
15
+ backend — but the client's fallback logic didn't check *which* key the cached entry belonged to,
16
+ and didn't check whether the error was a definitive rejection versus a network outage. It
17
+ silently returned key A's cached "valid" data and started the server authenticated as A's
18
+ account, having never actually validated B. Any string passed as `OPTIMIZER_API_KEY` within the
19
+ cache's 1-hour window (2 hours via the short-term fallback tier) would have worked, as long as
20
+ *some* real key had been validated on that machine recently.
21
+ Fixed two ways: (1) a cached entry is only used if its stored `apiKeyPrefix` matches the key
22
+ currently being validated, and (2) cache fallback is skipped entirely on a 4xx rejection —
23
+ it's reserved for genuine network/5xx outages of the *same* already-cached key, which was
24
+ always the intent (the surrounding code already distinguished 4xx from network errors for its
25
+ error-message hint text; the fallback branch above it just never used that distinction).
26
+ Added a network-free regression test (`tests/quick-test.js`) that seeds the cache with one
27
+ key's data, forces a 401 for a different key, and asserts the second key never inherits the
28
+ first's identity — and a second check confirming the legitimate same-key network-outage
29
+ fallback still works unchanged.
30
+
31
+ ## [3.8.0] - 2026-07-29
32
+
33
+ ### Added
34
+ - **`optimize_prompt` gains `reasoning_effort` and `execution_shape`**, the two Track C1 controls
35
+ that previously only reached `/api/v1/optimize` (the WebUI's endpoint) — `/api/v1/mcp/optimize`
36
+ (what this tool actually calls) never had them wired in at all, a pre-existing architectural
37
+ gap, not a regression. These matter *more* for a programmatic/agentic caller than a WebUI user:
38
+ there's no human watching each call to decide whether a prompt is worth paying for deeper
39
+ reasoning, or to notice a `multi_agent` request quietly running the LLM engine regardless of
40
+ the prompt's complexity. `multi_agent` is gated the same way on this endpoint as it is on
41
+ `/optimize` (`_gate_execution_shape`, downgraded to `direct` on tiers without repair access);
42
+ the response echoes back the *effective* shape that ran, and the formatter now surfaces a
43
+ downgrade when one happens. `stop_rule` (the third Track C1 control) is deliberately **not**
44
+ exposed here: this endpoint never runs the quick-eval-then-repair flow that gives `stop_rule`
45
+ any effect, so accepting it would advertise a guardrail that does nothing. Backend change
46
+ landed alongside this (`app/api/mcp_router.py`), verified with new endpoint tests covering the
47
+ free-tier downgrade, the pro-tier non-downgrade, and the team/enterprise-default non-downgrade
48
+ path (the last one guards against team keys getting silently capped if their tier lookup ever
49
+ comes back empty) — full backend suite (1740 passed) confirmed no regressions.
50
+ - **`generate_harness_bundle` gains `agent_read_only` and `agent_harness`**, mirroring the two
51
+ optional fields the backend's `HarnessBundleRequest` already accepts. `agent_harness` lets the
52
+ caller pick the generated agent.yaml's execution backend (`claude-sdk`/`codex`/`pi`) to match
53
+ whichever API key they actually have available wherever the bundle runs — without it, the
54
+ bundle silently defaults per-deploy-target (usually `claude-sdk`) and fails at runtime with a
55
+ missing-credential error if that's not the key the user holds. `agent_read_only` narrows the
56
+ generated subagent's tools to Read/Grep/Glob, for audit/review workflows that should never be
57
+ able to edit or execute anything. Both optional, both already validated server-side (a bad
58
+ `agent_harness` value gets a clear 422 listing valid choices).
59
+ - **`tests/e2e-stdio-smoke.js`**: black-box test that spawns the published binary as a real
60
+ subprocess and speaks JSON-RPC over stdio, exactly as an external MCP client would. Confirms
61
+ clean startup/shutdown behavior for missing/malformed/unregistered keys, including a live round
62
+ trip to the deployed backend. Full `tools/call` coverage requires `OPTIMIZER_API_KEY` set to a
63
+ real, backend-registered key — this package has no reachable mock/dev bypass (`developmentMode`
64
+ is hardcoded `false` in `index.js`; see the `dev`/`dev:mock` npm scripts, which are currently
65
+ dead for the same reason).
66
+
8
67
  ## [3.7.5] - 2026-07-18
9
68
 
10
69
  ### Fixed
package/index.js CHANGED
@@ -187,6 +187,16 @@ class MCPPromptOptimizer {
187
187
  description: "Narrative description of what a successful optimized output achieves (e.g. 'reader understands why churn drives flat revenue even with user growth')."
188
188
  }
189
189
  }
190
+ },
191
+ reasoning_effort: {
192
+ type: "string",
193
+ enum: ["minimal", "standard", "deep"],
194
+ description: "How much reasoning to apply: 'minimal' biases toward faster/cheaper routing, 'standard' is the default, 'deep' biases toward the LLM tier for maximum analysis. Most useful when calling this tool programmatically in a loop or pipeline, where there's no human watching each call to decide whether it's worth paying for more depth."
195
+ },
196
+ execution_shape: {
197
+ type: "string",
198
+ enum: ["direct", "hybrid", "multi_agent"],
199
+ description: "Execution style, independent of the tier the prompt would normally route to: 'direct' is single-pass, 'hybrid' adds rules+LLM verification, 'multi_agent' forces plan-and-execute with sub-agents regardless of the prompt's complexity. 'multi_agent' is downgraded to 'direct' on tiers without repair access — the response echoes back whichever one actually ran, so you can detect a downgrade."
190
200
  }
191
201
  },
192
202
  required: ["prompt"]
@@ -442,6 +452,24 @@ class MCPPromptOptimizer {
442
452
  sop_content: {
443
453
  type: "string",
444
454
  description: "The SOP content to base the harness on (required if no session_id)."
455
+ },
456
+ agent_read_only: {
457
+ type: "boolean",
458
+ description: (
459
+ "Narrow the generated Claude Code subagent's tools to Read/Grep/Glob only. "
460
+ + "Set this for audit/review workflows that should never edit or execute anything. "
461
+ + "Default: false (full capability)."
462
+ )
463
+ },
464
+ agent_harness: {
465
+ type: "string",
466
+ enum: ["claude-sdk", "codex", "pi"],
467
+ description: (
468
+ "Execution backend for the generated agent.yaml: 'claude-sdk' (needs ANTHROPIC_API_KEY), "
469
+ + "'codex' (needs OPENROUTER_API_KEY or OPENAI_API_KEY), or 'pi' (needs PI_API_KEY). "
470
+ + "Set this to match the API key available wherever the bundle will actually run — "
471
+ + "the default is per-deploy-target (usually claude-sdk) and won't know which key you have."
472
+ )
445
473
  }
446
474
  },
447
475
  required: ["goal"]
@@ -931,10 +959,17 @@ class MCPPromptOptimizer {
931
959
  }
932
960
  }
933
961
 
962
+ if (args.reasoning_effort) {
963
+ optimizationPayload.reasoning_effort = args.reasoning_effort;
964
+ }
965
+ if (args.execution_shape) {
966
+ optimizationPayload.execution_shape = args.execution_shape;
967
+ }
968
+
934
969
  const result = await this.callBackendAPI(ENDPOINTS.OPTIMIZE, optimizationPayload);
935
-
970
+
936
971
  const enableBayesian = args.enable_bayesian !== false && this.bayesianOptimizationEnabled;
937
- return { content: [{ type: "text", text: this.formatOptimizationResult(result, { detectedContext, enableBayesian }) }] };
972
+ return { content: [{ type: "text", text: this.formatOptimizationResult(result, { detectedContext, enableBayesian, requestedExecutionShape: args.execution_shape }) }] };
938
973
 
939
974
  } catch (error) {
940
975
  if (error.message.includes('Network') || error.message.includes('DNS') || error.message.includes('timeout') || error.message.includes('Connection')) {
@@ -1307,6 +1342,8 @@ class MCPPromptOptimizer {
1307
1342
  user_goal: args.goal,
1308
1343
  sop_content: args.sop_content || "",
1309
1344
  };
1345
+ if (args.agent_read_only) payload.agent_read_only = true;
1346
+ if (args.agent_harness) payload.agent_harness = args.agent_harness;
1310
1347
 
1311
1348
  // If session_id provided, first fetch session artifacts for sop_content
1312
1349
  if (args.session_id) {
@@ -1655,6 +1692,11 @@ class MCPPromptOptimizer {
1655
1692
  if (result.metadata?.routing_score != null) {
1656
1693
  output += `**Routing Score:** ${result.metadata.routing_score.toFixed(3)} (${result.metadata?.routing_tier || 'unknown'})\n`;
1657
1694
  }
1695
+ if (result.execution_shape && context.requestedExecutionShape && result.execution_shape !== context.requestedExecutionShape) {
1696
+ output += `**Execution Shape:** \`${result.execution_shape}\` *(requested \`${context.requestedExecutionShape}\`, downgraded — your tier doesn't have repair access)*\n`;
1697
+ } else if (result.reasoning_effort && result.reasoning_effort !== 'standard') {
1698
+ output += `**Reasoning Effort:** \`${result.reasoning_effort}\`\n`;
1699
+ }
1658
1700
  if (!result.rules_based && !result.fallback_mode && result.metadata?.model_used) {
1659
1701
  output += `**Model:** ${result.metadata.model_used}\n`;
1660
1702
  }
@@ -220,30 +220,38 @@ class CloudApiKeyManager {
220
220
  this.log(`Backend validation failed: ${error.message}`, 'warn');
221
221
  await this.updateNetworkHealth(false, error.message);
222
222
 
223
- // Enhanced fallback strategy
224
- const cachedValidation = await this.getCachedValidation();
225
-
226
- if (cachedValidation && !this.isCacheExpired(cachedValidation)) {
227
- this.log('Using cached API key validation', 'warn');
228
- return cachedValidation.data;
229
- }
230
-
231
- // SECURITY: Limited fallback for brief network issues only (2 hours max)
232
- if (cachedValidation && !this.isFallbackCacheExpired(cachedValidation)) {
233
- this.log('Using short-term fallback cache due to network issues', 'warn');
234
- const fallbackData = cachedValidation.data;
235
- fallbackData.fallback_mode = true;
236
- fallbackData.network_issue = error.message;
237
- fallbackData.expires_soon = true;
238
- return fallbackData;
223
+ // A 4xx means the backend rejected THIS key itself — not a connectivity
224
+ // problem. Falling back to a cache entry here would mean any key,
225
+ // valid or not, silently inherits whatever key was last cached on this
226
+ // machine (cacheFile is a single global file, not namespaced per key).
227
+ // Cache fallback exists only for genuine network/5xx outages of the
228
+ // SAME key that was already validated and cached — never for a
229
+ // definitive rejection, and never for a cache entry from a different key.
230
+ const isClientError = error.statusCode >= 400 && error.statusCode < 500;
231
+
232
+ if (!isClientError) {
233
+ const cachedValidation = await this.getCachedValidation();
234
+ const cacheMatchesThisKey = cachedValidation
235
+ && cachedValidation.apiKeyPrefix === this.apiKey.substring(0, 20) + '...';
236
+
237
+ if (cacheMatchesThisKey && !this.isCacheExpired(cachedValidation)) {
238
+ this.log('Using cached API key validation', 'warn');
239
+ return cachedValidation.data;
240
+ }
241
+
242
+ // SECURITY: Limited fallback for brief network issues only (2 hours max)
243
+ if (cacheMatchesThisKey && !this.isFallbackCacheExpired(cachedValidation)) {
244
+ this.log('Using short-term fallback cache due to network issues', 'warn');
245
+ const fallbackData = cachedValidation.data;
246
+ fallbackData.fallback_mode = true;
247
+ fallbackData.network_issue = error.message;
248
+ fallbackData.expires_soon = true;
249
+ return fallbackData;
250
+ }
239
251
  }
240
252
 
241
253
  // SECURITY: Offline mode removed - backend validation required
242
- // No cache fallback beyond 2 hours
243
-
244
- // A 4xx means the backend rejected the key itself, not a connectivity
245
- // problem — telling the user to check their internet is wrong there.
246
- const isClientError = error.statusCode >= 400 && error.statusCode < 500;
254
+ // No cache fallback beyond 2 hours, and none at all for a 4xx rejection.
247
255
  const hint = isClientError ? '' : ' Please check your internet connection.';
248
256
  throw new Error(`API key validation failed: ${error.message}.${hint}`);
249
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-prompt-optimizer",
3
- "version": "3.7.5",
3
+ "version": "3.8.1",
4
4
  "description": "Professional cloud-based MCP server for AI-powered prompt optimization with intelligent context detection, Bayesian optimization, AG-UI real-time optimization, template auto-save, optimization insights, personal model configuration via WebUI, team collaboration, enterprise-grade features, production resilience, and startup validation. Universal compatibility with Claude Desktop, Cursor, Windsurf, and 17+ MCP clients.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "test:runner": "node tests/test-runner.js",
26
26
  "test:simple": "node tests/simple-test.js",
27
27
  "test:contract": "node tests/contract-check.js",
28
+ "test:e2e": "node tests/e2e-stdio-smoke.js",
28
29
  "pretest": "npm run health-check",
29
30
  "prepublishOnly": "npm run test:quick",
30
31
  "version": "echo 'Updating version...' && npm run test:quick"