mcp-prompt-optimizer 3.7.5 → 3.8.2
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 +69 -0
- package/index.js +58 -3
- package/lib/api-key-manager.js +29 -21
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,75 @@ 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.2] - 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Surface CE structural warnings in tool output.** The backend `/context-engineer/transform`
|
|
12
|
+
and `/context-engineer/generate-skill-package` responses now carry a non-fatal `warnings`
|
|
13
|
+
array (dropped SOP stages, duplicated tables, fabricated headings). `transform_for_framework`
|
|
14
|
+
and `generate_skill_package` previously discarded it, so a structurally degraded artifact
|
|
15
|
+
shipped silently. Both now render a `## ⚠️ Structural Warnings` section when the array is
|
|
16
|
+
non-empty. No change when it is empty (the normal case) — output is byte-identical.
|
|
17
|
+
|
|
18
|
+
## [3.8.1] - 2026-07-29
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Security: the validation cache let any API key inherit a different key's cached identity.**
|
|
22
|
+
`~/.mcp-cloud-api-cache.json` is a single file per machine, not namespaced per key. Found live
|
|
23
|
+
while testing against a real registered key: validating key A succeeds and caches its result;
|
|
24
|
+
minutes later, validating a completely different, invalid key B gets a clean 401 from the
|
|
25
|
+
backend — but the client's fallback logic didn't check *which* key the cached entry belonged to,
|
|
26
|
+
and didn't check whether the error was a definitive rejection versus a network outage. It
|
|
27
|
+
silently returned key A's cached "valid" data and started the server authenticated as A's
|
|
28
|
+
account, having never actually validated B. Any string passed as `OPTIMIZER_API_KEY` within the
|
|
29
|
+
cache's 1-hour window (2 hours via the short-term fallback tier) would have worked, as long as
|
|
30
|
+
*some* real key had been validated on that machine recently.
|
|
31
|
+
Fixed two ways: (1) a cached entry is only used if its stored `apiKeyPrefix` matches the key
|
|
32
|
+
currently being validated, and (2) cache fallback is skipped entirely on a 4xx rejection —
|
|
33
|
+
it's reserved for genuine network/5xx outages of the *same* already-cached key, which was
|
|
34
|
+
always the intent (the surrounding code already distinguished 4xx from network errors for its
|
|
35
|
+
error-message hint text; the fallback branch above it just never used that distinction).
|
|
36
|
+
Added a network-free regression test (`tests/quick-test.js`) that seeds the cache with one
|
|
37
|
+
key's data, forces a 401 for a different key, and asserts the second key never inherits the
|
|
38
|
+
first's identity — and a second check confirming the legitimate same-key network-outage
|
|
39
|
+
fallback still works unchanged.
|
|
40
|
+
|
|
41
|
+
## [3.8.0] - 2026-07-29
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **`optimize_prompt` gains `reasoning_effort` and `execution_shape`**, the two Track C1 controls
|
|
45
|
+
that previously only reached `/api/v1/optimize` (the WebUI's endpoint) — `/api/v1/mcp/optimize`
|
|
46
|
+
(what this tool actually calls) never had them wired in at all, a pre-existing architectural
|
|
47
|
+
gap, not a regression. These matter *more* for a programmatic/agentic caller than a WebUI user:
|
|
48
|
+
there's no human watching each call to decide whether a prompt is worth paying for deeper
|
|
49
|
+
reasoning, or to notice a `multi_agent` request quietly running the LLM engine regardless of
|
|
50
|
+
the prompt's complexity. `multi_agent` is gated the same way on this endpoint as it is on
|
|
51
|
+
`/optimize` (`_gate_execution_shape`, downgraded to `direct` on tiers without repair access);
|
|
52
|
+
the response echoes back the *effective* shape that ran, and the formatter now surfaces a
|
|
53
|
+
downgrade when one happens. `stop_rule` (the third Track C1 control) is deliberately **not**
|
|
54
|
+
exposed here: this endpoint never runs the quick-eval-then-repair flow that gives `stop_rule`
|
|
55
|
+
any effect, so accepting it would advertise a guardrail that does nothing. Backend change
|
|
56
|
+
landed alongside this (`app/api/mcp_router.py`), verified with new endpoint tests covering the
|
|
57
|
+
free-tier downgrade, the pro-tier non-downgrade, and the team/enterprise-default non-downgrade
|
|
58
|
+
path (the last one guards against team keys getting silently capped if their tier lookup ever
|
|
59
|
+
comes back empty) — full backend suite (1740 passed) confirmed no regressions.
|
|
60
|
+
- **`generate_harness_bundle` gains `agent_read_only` and `agent_harness`**, mirroring the two
|
|
61
|
+
optional fields the backend's `HarnessBundleRequest` already accepts. `agent_harness` lets the
|
|
62
|
+
caller pick the generated agent.yaml's execution backend (`claude-sdk`/`codex`/`pi`) to match
|
|
63
|
+
whichever API key they actually have available wherever the bundle runs — without it, the
|
|
64
|
+
bundle silently defaults per-deploy-target (usually `claude-sdk`) and fails at runtime with a
|
|
65
|
+
missing-credential error if that's not the key the user holds. `agent_read_only` narrows the
|
|
66
|
+
generated subagent's tools to Read/Grep/Glob, for audit/review workflows that should never be
|
|
67
|
+
able to edit or execute anything. Both optional, both already validated server-side (a bad
|
|
68
|
+
`agent_harness` value gets a clear 422 listing valid choices).
|
|
69
|
+
- **`tests/e2e-stdio-smoke.js`**: black-box test that spawns the published binary as a real
|
|
70
|
+
subprocess and speaks JSON-RPC over stdio, exactly as an external MCP client would. Confirms
|
|
71
|
+
clean startup/shutdown behavior for missing/malformed/unregistered keys, including a live round
|
|
72
|
+
trip to the deployed backend. Full `tools/call` coverage requires `OPTIMIZER_API_KEY` set to a
|
|
73
|
+
real, backend-registered key — this package has no reachable mock/dev bypass (`developmentMode`
|
|
74
|
+
is hardcoded `false` in `index.js`; see the `dev`/`dev:mock` npm scripts, which are currently
|
|
75
|
+
dead for the same reason).
|
|
76
|
+
|
|
8
77
|
## [3.7.5] - 2026-07-18
|
|
9
78
|
|
|
10
79
|
### 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')) {
|
|
@@ -1254,7 +1289,7 @@ class MCPPromptOptimizer {
|
|
|
1254
1289
|
sop_content: args.sop_content, goal: args.goal, framework: args.framework
|
|
1255
1290
|
});
|
|
1256
1291
|
const code = result.code || result.content || result.result || JSON.stringify(result, null, 2);
|
|
1257
|
-
return { content: [{ type: "text", text: `# ${args.framework} Implementation\n\n\`\`\`python\n${code}\n
|
|
1292
|
+
return { content: [{ type: "text", text: `# ${args.framework} Implementation\n\n\`\`\`python\n${code}\n\`\`\`${this._formatWarnings(result)}\n\n---\n*Transformed by MCP Prompt Optimizer CE*` }] };
|
|
1258
1293
|
} catch (error) {
|
|
1259
1294
|
throw new Error(`Failed to transform: ${error.message}`);
|
|
1260
1295
|
}
|
|
@@ -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) {
|
|
@@ -1529,10 +1566,23 @@ class MCPPromptOptimizer {
|
|
|
1529
1566
|
} else {
|
|
1530
1567
|
sections.push('\n```json\n' + JSON.stringify(result, null, 2) + '\n```');
|
|
1531
1568
|
}
|
|
1569
|
+
const warn = this._formatWarnings(result);
|
|
1570
|
+
if (warn) sections.push(warn);
|
|
1532
1571
|
sections.push('\n---\n*Generated by MCP Prompt Optimizer CE*');
|
|
1533
1572
|
return sections.join('\n');
|
|
1534
1573
|
}
|
|
1535
1574
|
|
|
1575
|
+
// Backend CE responses carry a non-fatal `warnings` array (dropped SOP
|
|
1576
|
+
// stages, duplicated tables, fabricated headings). Surface it so a degraded
|
|
1577
|
+
// artifact travels with the reason instead of shipping silently. Returns ''
|
|
1578
|
+
// when there is nothing to report.
|
|
1579
|
+
_formatWarnings(result) {
|
|
1580
|
+
const w = result && (result.warnings || result.validation_warnings);
|
|
1581
|
+
if (!Array.isArray(w) || w.length === 0) return '';
|
|
1582
|
+
return `\n\n## ⚠️ Structural Warnings\n\n${w.map(x => `- ${x}`).join('\n')}\n\n` +
|
|
1583
|
+
`_The artifact is delivered as-is; review these before use._`;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1536
1586
|
_buildUrl(path) {
|
|
1537
1587
|
return `${this.backendUrl}${path}`;
|
|
1538
1588
|
}
|
|
@@ -1655,6 +1705,11 @@ class MCPPromptOptimizer {
|
|
|
1655
1705
|
if (result.metadata?.routing_score != null) {
|
|
1656
1706
|
output += `**Routing Score:** ${result.metadata.routing_score.toFixed(3)} (${result.metadata?.routing_tier || 'unknown'})\n`;
|
|
1657
1707
|
}
|
|
1708
|
+
if (result.execution_shape && context.requestedExecutionShape && result.execution_shape !== context.requestedExecutionShape) {
|
|
1709
|
+
output += `**Execution Shape:** \`${result.execution_shape}\` *(requested \`${context.requestedExecutionShape}\`, downgraded — your tier doesn't have repair access)*\n`;
|
|
1710
|
+
} else if (result.reasoning_effort && result.reasoning_effort !== 'standard') {
|
|
1711
|
+
output += `**Reasoning Effort:** \`${result.reasoning_effort}\`\n`;
|
|
1712
|
+
}
|
|
1658
1713
|
if (!result.rules_based && !result.fallback_mode && result.metadata?.model_used) {
|
|
1659
1714
|
output += `**Model:** ${result.metadata.model_used}\n`;
|
|
1660
1715
|
}
|
package/lib/api-key-manager.js
CHANGED
|
@@ -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
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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.
|
|
3
|
+
"version": "3.8.2",
|
|
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,8 @@
|
|
|
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",
|
|
29
|
+
"test:prod-canary": "node tests/prod-canary.js",
|
|
28
30
|
"pretest": "npm run health-check",
|
|
29
31
|
"prepublishOnly": "npm run test:quick",
|
|
30
32
|
"version": "echo 'Updating version...' && npm run test:quick"
|