mcp-prompt-optimizer 3.7.4 → 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,76 @@ 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
+
67
+ ## [3.7.5] - 2026-07-18
68
+
69
+ ### Fixed
70
+ - **10 MCP tools were unconditionally broken**: `generate_agent_sop`, `transform_for_framework`, `generate_harness_bundle`, `explore_sop_approaches`, `get_prompt_by_slug`, `compile_prompt`, `list_template_versions`, `rollback_template`, `publish_template`, and `run_quick_evaluation` all called backend routes that only accepted a JWT — a credential this stdio client can never obtain (no browser, no login flow). Every call to these tools failed with an auth error, always. Backend now accepts API-key auth on these routes; verified the fix doesn't expand what a key is authorized to do (every route is scoped to the calling account's own data, same as routes that already worked).
71
+ - **Tier-upgrade messages never fired**: the 403-detection check looked for the literal string `'403'` inside the error body, which never appears there — only the HTTP status code carries it. Now checks the real status code, so upgrade prompts actually show up when a real tier gate is hit.
72
+ - **Fabricated fallback data**: `get_optimization_insights` and `get_real_time_status` silently returned hardcoded fake numbers (fake optimization counts, fake AG-UI metrics) on any backend error, indistinguishable from real account data. Both now say the data is unavailable instead of inventing it.
73
+ - **`formatRealTimeStatus` read the wrong fields**: it never matched the AG-UI status endpoint's actual response shape, even when the call succeeded.
74
+ - **`formatQuotaStatus` invented a fake `5000` quota limit** when the backend didn't report one.
75
+ - **Free-tier quota text said 7/month**; the real limit is 20.
76
+ - **Dead domain and wrong key-format references**: `promptoptimizer-blog.vercel.app` no longer resolves; some messages also claimed `sk-local-*` was a valid key format for this package (that's the sibling local package's prefix, not this one's).
77
+
8
78
  ## [3.7.4] - 2026-07-16
9
79
 
10
80
  ### 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')) {
@@ -1056,31 +1091,7 @@ class MCPPromptOptimizer {
1056
1091
  return { content: [{ type: "text", text: this.formatOptimizationInsights(result) }] };
1057
1092
 
1058
1093
  } catch (error) {
1059
- // Fallback to mock insights
1060
- const mockInsights = {
1061
- bayesian_status: {
1062
- optimization_active: true,
1063
- total_optimizations: 47,
1064
- improvement_rate: '23.5%',
1065
- confidence_score: 0.89
1066
- },
1067
- parameter_insights: {
1068
- most_effective_goals: ['clarity', 'technical_accuracy', 'analytical_depth'],
1069
- context_performance: {
1070
- 'code_generation': 0.92,
1071
- 'llm_interaction': 0.87,
1072
- 'technical_automation': 0.84
1073
- },
1074
- optimization_trends: 'Steady improvement in technical contexts'
1075
- },
1076
- recommendations: args.include_recommendations !== false ? [
1077
- 'Focus on technical_accuracy for code generation prompts',
1078
- 'Combine clarity with analytical_depth for best results',
1079
- 'Consider using structured_output context for data tasks'
1080
- ] : []
1081
- };
1082
-
1083
- return { content: [{ type: "text", text: this.formatOptimizationInsights(mockInsights) }] };
1094
+ return { content: [{ type: "text", text: `🧠 Optimization insights are unavailable right now (${error.message}). This is not your data — no insights were generated.` }] };
1084
1095
  }
1085
1096
  }
1086
1097
 
@@ -1095,22 +1106,7 @@ class MCPPromptOptimizer {
1095
1106
  return { content: [{ type: "text", text: this.formatRealTimeStatus(result) }] };
1096
1107
 
1097
1108
  } catch (error) {
1098
- const mockStatus = {
1099
- agui_status: 'available',
1100
- streaming_optimization: true,
1101
- websocket_support: true,
1102
- real_time_analytics: true,
1103
- active_optimizations: 3,
1104
- average_response_time: '1.2s',
1105
- features: {
1106
- live_optimization: true,
1107
- collaborative_editing: true,
1108
- instant_feedback: true,
1109
- performance_monitoring: true
1110
- }
1111
- };
1112
-
1113
- return { content: [{ type: "text", text: this.formatRealTimeStatus(mockStatus) }] };
1109
+ return { content: [{ type: "text", text: `⚡ AG-UI real-time status is unavailable right now (${error.message}).` }] };
1114
1110
  }
1115
1111
  }
1116
1112
 
@@ -1346,6 +1342,8 @@ class MCPPromptOptimizer {
1346
1342
  user_goal: args.goal,
1347
1343
  sop_content: args.sop_content || "",
1348
1344
  };
1345
+ if (args.agent_read_only) payload.agent_read_only = true;
1346
+ if (args.agent_harness) payload.agent_harness = args.agent_harness;
1349
1347
 
1350
1348
  // If session_id provided, first fetch session artifacts for sop_content
1351
1349
  if (args.session_id) {
@@ -1434,8 +1432,8 @@ class MCPPromptOptimizer {
1434
1432
  }]
1435
1433
  };
1436
1434
  } catch (error) {
1437
- if (error.message && error.message.includes('403')) {
1438
- return { content: [{ type: "text", text: "Error: SOP exploration requires Innovator tier. Upgrade at /pricing." }] };
1435
+ if (error.statusCode === 403) {
1436
+ return { content: [{ type: "text", text: `Error: SOP exploration requires Innovator tier. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1439
1437
  }
1440
1438
  throw new Error(`Failed to explore SOP approaches: ${error.message}`);
1441
1439
  }
@@ -1450,9 +1448,8 @@ class MCPPromptOptimizer {
1450
1448
  output += `**Body:**\n\`\`\`\n${result.optimized_prompt || result.body || ''}\n\`\`\`\n`;
1451
1449
  return { content: [{ type: "text", text: output }] };
1452
1450
  } catch (error) {
1453
- const msg = error.message || '';
1454
- if (msg.includes('403') || msg.includes('TIER')) {
1455
- return { content: [{ type: "text", text: `Upgrade required: runtime prompt delivery requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${msg}` }] };
1451
+ if (error.statusCode === 403) {
1452
+ return { content: [{ type: "text", text: `Upgrade required: runtime prompt delivery requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1456
1453
  }
1457
1454
  throw new Error(`Failed to fetch prompt by slug: ${error.message}`);
1458
1455
  }
@@ -1465,9 +1462,8 @@ class MCPPromptOptimizer {
1465
1462
  const compiled = result.compiled_prompt || result.body || JSON.stringify(result, null, 2);
1466
1463
  return { content: [{ type: "text", text: `# Compiled Prompt\n\n\`\`\`\n${compiled}\n\`\`\`\n` }] };
1467
1464
  } catch (error) {
1468
- const msg = error.message || '';
1469
- if (msg.includes('403') || msg.includes('TIER')) {
1470
- return { content: [{ type: "text", text: `Upgrade required: prompt compilation requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${msg}` }] };
1465
+ if (error.statusCode === 403) {
1466
+ return { content: [{ type: "text", text: `Upgrade required: prompt compilation requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1471
1467
  }
1472
1468
  throw new Error(`Failed to compile prompt: ${error.message}`);
1473
1469
  }
@@ -1493,9 +1489,8 @@ class MCPPromptOptimizer {
1493
1489
  }
1494
1490
  return { content: [{ type: "text", text: output }] };
1495
1491
  } catch (error) {
1496
- const msg = error.message || '';
1497
- if (msg.includes('403') || msg.includes('TIER')) {
1498
- return { content: [{ type: "text", text: `Upgrade required: template versioning requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${msg}` }] };
1492
+ if (error.statusCode === 403) {
1493
+ return { content: [{ type: "text", text: `Upgrade required: template versioning requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1499
1494
  }
1500
1495
  throw new Error(`Failed to list template versions: ${error.message}`);
1501
1496
  }
@@ -1509,9 +1504,8 @@ class MCPPromptOptimizer {
1509
1504
  const msg = result.message || `Template rolled back to version ${args.version_number}`;
1510
1505
  return { content: [{ type: "text", text: `# ✅ Rollback Complete\n\n${msg}\n\n**Template ID:** \`${args.template_id}\`\n**Version:** ${args.version_number}` }] };
1511
1506
  } catch (error) {
1512
- const msg = error.message || '';
1513
- if (msg.includes('403') || msg.includes('TIER')) {
1514
- return { content: [{ type: "text", text: `Upgrade required: template rollback requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${msg}` }] };
1507
+ if (error.statusCode === 403) {
1508
+ return { content: [{ type: "text", text: `Upgrade required: template rollback requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1515
1509
  }
1516
1510
  throw new Error(`Failed to rollback template: ${error.message}`);
1517
1511
  }
@@ -1524,9 +1518,8 @@ class MCPPromptOptimizer {
1524
1518
  const msg = result.message || `Template ${args.template_id} published successfully`;
1525
1519
  return { content: [{ type: "text", text: `# ✅ Template Published\n\n${msg}\n\nThe template is now available for runtime delivery via \`get_prompt_by_slug\`.` }] };
1526
1520
  } catch (error) {
1527
- const msg = error.message || '';
1528
- if (msg.includes('403') || msg.includes('TIER')) {
1529
- return { content: [{ type: "text", text: `Upgrade required: template publishing requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${msg}` }] };
1521
+ if (error.statusCode === 403) {
1522
+ return { content: [{ type: "text", text: `Upgrade required: template publishing requires Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1530
1523
  }
1531
1524
  throw new Error(`Failed to publish template: ${error.message}`);
1532
1525
  }
@@ -1556,9 +1549,8 @@ class MCPPromptOptimizer {
1556
1549
  }
1557
1550
  return { content: [{ type: "text", text: output }] };
1558
1551
  } catch (error) {
1559
- const msg = error.message || '';
1560
- if (msg.includes('403') || msg.includes('TIER')) {
1561
- return { content: [{ type: "text", text: `Upgrade required: evaluations require Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${msg}` }] };
1552
+ if (error.statusCode === 403) {
1553
+ return { content: [{ type: "text", text: `Upgrade required: evaluations require Pro tier or higher. Upgrade at /pricing.\n\nBackend error: ${error.message}` }] };
1562
1554
  }
1563
1555
  throw new Error(`Failed to run quick evaluation: ${error.message}`);
1564
1556
  }
@@ -1631,7 +1623,9 @@ class MCPPromptOptimizer {
1631
1623
  } catch {
1632
1624
  errorMessage = `HTTP ${res.statusCode}: ${responseData}`;
1633
1625
  }
1634
- reject(new Error(errorMessage));
1626
+ const httpError = new Error(errorMessage);
1627
+ httpError.statusCode = res.statusCode;
1628
+ reject(httpError);
1635
1629
  }
1636
1630
  } catch (parseError) {
1637
1631
  reject(new Error(`Invalid response format: ${parseError.message}`));
@@ -1698,6 +1692,11 @@ class MCPPromptOptimizer {
1698
1692
  if (result.metadata?.routing_score != null) {
1699
1693
  output += `**Routing Score:** ${result.metadata.routing_score.toFixed(3)} (${result.metadata?.routing_tier || 'unknown'})\n`;
1700
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
+ }
1701
1700
  if (!result.rules_based && !result.fallback_mode && result.metadata?.model_used) {
1702
1701
  output += `**Model:** ${result.metadata.model_used}\n`;
1703
1702
  }
@@ -1787,7 +1786,7 @@ class MCPPromptOptimizer {
1787
1786
  output += `2. Generate your API key at https://promptoptimizer.xyz/dashboard\n`;
1788
1787
  output += `3. Run in your terminal:\n\n`;
1789
1788
  output += `\`\`\`\nnpx mcp-prompt-optimizer connect\n\`\`\`\n\n`;
1790
- output += `You get **7 LLM optimizations/month free**. Upgrade anytime for more.\n`;
1789
+ output += `You get **20 LLM optimizations/month free**. Upgrade anytime for more.\n`;
1791
1790
  }
1792
1791
 
1793
1792
  return output;
@@ -1801,25 +1800,30 @@ class MCPPromptOptimizer {
1801
1800
  output += `**Usage:** 🟢 Unlimited\n`;
1802
1801
  } else {
1803
1802
  const used = quota.used || 0;
1804
- const limit = quota.limit || 5000;
1803
+ const limit = quota.limit;
1804
+
1805
+ if (limit === undefined || limit === null) {
1806
+ output += `**Usage:** ${used} used (limit unavailable — backend did not report a quota limit)\n`;
1807
+ } else {
1805
1808
  const percentage = limit > 0 ? ((used / limit) * 100).toFixed(1) : 0;
1806
-
1809
+
1807
1810
  let statusIcon = '🟢';
1808
1811
  if (percentage >= 90) statusIcon = '🔴';
1809
1812
  else if (percentage >= 75) statusIcon = '🟡';
1810
-
1813
+
1811
1814
  output += `**Usage:** ${statusIcon} ${used}/${limit} (${percentage}%)\n`;
1812
1815
 
1813
1816
  const remaining = limit - used;
1814
1817
  if (remaining <= 0) {
1815
1818
  output += `\n❌ **Quota Exhausted** — You have no optimizations remaining this month.\n`;
1816
- output += `Upgrade at https://promptoptimizer.xyz/local-license\n`;
1819
+ output += `Upgrade at https://promptoptimizer.xyz/pricing\n`;
1817
1820
  output += `*(Quota resets at the start of your next billing cycle)*\n`;
1818
1821
  } else if (percentage >= 90) {
1819
- output += `\n⚠️ **Critical** — ${remaining} optimization${remaining === 1 ? '' : 's'} remaining. Upgrade at https://promptoptimizer.xyz/local-license\n`;
1822
+ output += `\n⚠️ **Critical** — ${remaining} optimization${remaining === 1 ? '' : 's'} remaining. Upgrade at https://promptoptimizer.xyz/pricing\n`;
1820
1823
  } else if (percentage >= 75) {
1821
1824
  output += `\n⚠️ **Warning** — Approaching your monthly limit.\n`;
1822
1825
  }
1826
+ }
1823
1827
  }
1824
1828
 
1825
1829
  output += `\n## ✨ **Available Features**\n`;
@@ -1841,9 +1845,9 @@ class MCPPromptOptimizer {
1841
1845
  }
1842
1846
 
1843
1847
  output += `\n## 🔗 **Account Management**\n`;
1844
- output += `- Dashboard: https://promptoptimizer-blog.vercel.app/dashboard\n`;
1845
- output += `- Analytics: https://promptoptimizer-blog.vercel.app/analytics\n`;
1846
- output += `- Upgrade: https://promptoptimizer.xyz/local-license\n`;
1848
+ output += `- Dashboard: https://promptoptimizer.xyz/dashboard\n`;
1849
+ output += `- Analytics: https://promptoptimizer.xyz/analytics\n`;
1850
+ output += `- Upgrade: https://promptoptimizer.xyz/pricing\n`;
1847
1851
 
1848
1852
  return output;
1849
1853
  }
@@ -1949,41 +1953,40 @@ class MCPPromptOptimizer {
1949
1953
  }
1950
1954
 
1951
1955
  output += `## 🔗 **Advanced Analytics**\n`;
1952
- output += `- Full Analytics: https://promptoptimizer-blog.vercel.app/analytics\n`;
1953
- output += `- Performance Dashboard: https://promptoptimizer-blog.vercel.app/dashboard\n`;
1956
+ output += `- Full Analytics: https://promptoptimizer.xyz/analytics\n`;
1957
+ output += `- Performance Dashboard: https://promptoptimizer.xyz/dashboard\n`;
1954
1958
 
1955
1959
  return output;
1956
1960
  }
1957
1961
 
1958
1962
  formatRealTimeStatus(status) {
1959
1963
  let output = `# ⚡ AG-UI Real-Time Status\n\n`;
1960
-
1964
+ const metrics = status.metrics || {};
1965
+
1961
1966
  output += `## 🚀 **Service Status**\n`;
1962
- output += `- **AG-UI Status:** ${status.agui_status === 'available' ? '🟢 Available' : '🔴 Unavailable'}\n`;
1963
- output += `- **Streaming Optimization:** ${status.streaming_optimization ? '✅ Enabled' : '❌ Disabled'}\n`;
1964
- output += `- **WebSocket Support:** ${status.websocket_support ? '✅ Enabled' : '❌ Disabled'}\n`;
1965
- output += `- **Real-time Analytics:** ${status.real_time_analytics ? '✅ Enabled' : '❌ Disabled'}\n\n`;
1966
-
1967
- if (status.active_optimizations !== undefined) {
1968
- output += `## 📈 **Current Activity**\n`;
1969
- output += `- **Active Optimizations:** ${status.active_optimizations}\n`;
1970
- output += `- **Average Response Time:** ${status.average_response_time}\n\n`;
1967
+ output += `- **AG-UI Status:** ${status.status === 'healthy' ? '🟢 Healthy' : '🔴 Degraded'}\n\n`;
1968
+
1969
+ output += `## 📈 **Current Activity**\n`;
1970
+ output += `- **Active Sessions:** ${metrics.active_sessions ?? 'unknown'}\n`;
1971
+ output += `- **Total Connections:** ${metrics.total_connections ?? 'unknown'}\n`;
1972
+ output += `- **Total Optimizations:** ${metrics.total_optimizations ?? 'unknown'}\n`;
1973
+ if (metrics.uptime_seconds !== undefined) {
1974
+ output += `- **Uptime:** ${Math.round(metrics.uptime_seconds)}s\n`;
1971
1975
  }
1972
-
1973
- if (status.features) {
1974
- const features = status.features;
1976
+ output += `\n`;
1977
+
1978
+ if (metrics.features_enabled) {
1975
1979
  output += `## ⚡ **Available Features**\n`;
1976
- if (features.live_optimization) output += `✅ Live Optimization\n`;
1977
- if (features.collaborative_editing) output += `✅ Collaborative Editing\n`;
1978
- if (features.instant_feedback) output += `✅ Instant Feedback\n`;
1979
- if (features.performance_monitoring) output += `✅ Performance Monitoring\n`;
1980
+ for (const [feature, enabled] of Object.entries(metrics.features_enabled)) {
1981
+ if (enabled) output += `✅ ${feature}\n`;
1982
+ }
1980
1983
  output += `\n`;
1981
1984
  }
1982
-
1985
+
1983
1986
  output += `## 🔗 **Real-Time Access**\n`;
1984
- output += `- Live Dashboard: https://promptoptimizer-blog.vercel.app/live\n`;
1987
+ output += `- Live Dashboard: https://promptoptimizer.xyz/live\n`;
1985
1988
  output += `- WebSocket Endpoint: Available via API\n`;
1986
-
1989
+
1987
1990
  return output;
1988
1991
  }
1989
1992
 
@@ -2001,7 +2004,7 @@ async function startValidatedMCPServer() {
2001
2004
  try {
2002
2005
  const apiKey = process.env.OPTIMIZER_API_KEY;
2003
2006
  if (!apiKey) {
2004
- console.error('❌ API key required. Get one at https://promptoptimizer.xyz/local-license');
2007
+ console.error('❌ API key required. Get one free at https://promptoptimizer.xyz/dashboard');
2005
2008
  process.exit(1);
2006
2009
  }
2007
2010
 
@@ -2179,7 +2182,7 @@ async function runConnectWizard() {
2179
2182
  }
2180
2183
  if (ok > 0) {
2181
2184
  console.log('\n👉 Restart your MCP client(s) to activate LLM optimization.');
2182
- console.log(' Free plan: 7 LLM optimizations/month.');
2185
+ console.log(' Free plan: 20 LLM optimizations/month.');
2183
2186
  console.log(' Upgrade at https://promptoptimizer.xyz/pricing\n');
2184
2187
  }
2185
2188
  }
@@ -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
  }
@@ -593,7 +601,7 @@ class CloudApiKeyManager {
593
601
 
594
602
  throw new Error(
595
603
  'API key required. Set the OPTIMIZER_API_KEY environment variable.\n' +
596
- 'Get your API key at: https://promptoptimizer.xyz/local-license'
604
+ 'Get your API key at: https://promptoptimizer.xyz/dashboard'
597
605
  );
598
606
  }
599
607
 
@@ -10,7 +10,7 @@ async function checkStatus() {
10
10
  if (!apiKey) {
11
11
  console.error('❌ No API key found');
12
12
  console.log('\n📝 Set your API key to check status:');
13
- console.log(' export OPTIMIZER_API_KEY=sk-local-your-key-here'); // Aligned with free tier/development
13
+ console.log(' export OPTIMIZER_API_KEY=sk-opt-your-key-here');
14
14
  if (developmentMode) {
15
15
  console.log('\n🧪 Development Mode Options:');
16
16
  console.log(' export OPTIMIZER_API_KEY=sk-dev-test-key');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-prompt-optimizer",
3
- "version": "3.7.4",
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"
@@ -124,8 +125,8 @@
124
125
  "required_keys": [
125
126
  {
126
127
  "name": "OPTIMIZER_API_KEY",
127
- "format": "sk-opt-*, sk-team-*, sk-dev-*, or sk-local-*",
128
- "description": "Cloud API key from promptoptimizer.xyz/local-license or development key for testing",
128
+ "format": "sk-opt-*, sk-team-*, or sk-dev-*",
129
+ "description": "Cloud API key from promptoptimizer.xyz/dashboard or development key for testing",
129
130
  "required": true
130
131
  }
131
132
  ],
@@ -195,6 +196,19 @@
195
196
  "last_sync": "2026-06-01T00:00:00Z"
196
197
  },
197
198
  "release_notes": {
199
+ "v3.7.5": {
200
+ "major_features": [
201
+ "Fixed: 10 MCP tools (generate_agent_sop, transform_for_framework, generate_harness_bundle, explore_sop_approaches, get_prompt_by_slug, compile_prompt, list_template_versions, rollback_template, publish_template, run_quick_evaluation) called backend routes that only accepted a JWT, which this stdio client can never obtain — every call failed unconditionally. Backend now accepts API-key auth on these routes too.",
202
+ "Fixed: the tier-upgrade message for these tools never fired because the code checked for the literal string '403' inside the error body, which never appears there (that's the HTTP status code, not body text). Now checks the real status code.",
203
+ "Fixed: get_optimization_insights and get_real_time_status fell back to hardcoded fake data (fabricated optimization counts, fake AG-UI metrics) on any backend error, presented as real account data. Both now report unavailability honestly instead.",
204
+ "Fixed: formatRealTimeStatus was never reading the AG-UI status endpoint's actual response shape, even on success.",
205
+ "Fixed: formatQuotaStatus invented a fake 5000 quota limit when the backend didn't report one.",
206
+ "Fixed: free-tier quota text said 7/month; actual limit is 20.",
207
+ "Fixed: dead promptoptimizer-blog.vercel.app domain and wrong sk-local-* key-format claims (that's the sibling local package's prefix, not this one's) across index.js, check-status.js, and package.json."
208
+ ],
209
+ "breaking_changes": [],
210
+ "migration_guide": "No migration required. Users who called any of the 10 previously-broken MCP tools will now get real results instead of an auth error."
211
+ },
198
212
  "v3.7.4": {
199
213
  "major_features": [
200
214
  "Fixed: 6 of 8 documented CLI commands (check-status, validate-key, diagnose, clear-cache, help, version) were not implemented in the argv dispatcher and silently started the blocking MCP server instead, hanging the terminal. Now wired to their lib/*.js implementations.",