@wootsup/mcp 0.1.0 → 0.3.0
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 +148 -83
- package/README.md +31 -27
- package/SECURITY.md +15 -6
- package/dist/auth/keychain.d.ts +27 -1
- package/dist/auth/keychain.js +48 -2
- package/dist/auth/keychain.js.map +1 -1
- package/dist/cli-hint.d.ts +22 -0
- package/dist/cli-hint.js +55 -0
- package/dist/cli-hint.js.map +1 -0
- package/dist/index.js +97 -22
- package/dist/index.js.map +1 -1
- package/dist/install-skill.js +1 -1
- package/dist/modules/apimapper/cache.js +25 -17
- package/dist/modules/apimapper/cache.js.map +1 -1
- package/dist/modules/apimapper/client.d.ts +62 -1
- package/dist/modules/apimapper/client.js +555 -291
- package/dist/modules/apimapper/client.js.map +1 -1
- package/dist/modules/apimapper/connections.js +230 -75
- package/dist/modules/apimapper/connections.js.map +1 -1
- package/dist/modules/apimapper/credential-sanitizer.d.ts +5 -0
- package/dist/modules/apimapper/credential-sanitizer.js +60 -1
- package/dist/modules/apimapper/credential-sanitizer.js.map +1 -1
- package/dist/modules/apimapper/credentials.js +19 -47
- package/dist/modules/apimapper/credentials.js.map +1 -1
- package/dist/modules/apimapper/diagnose.js +21 -2
- package/dist/modules/apimapper/diagnose.js.map +1 -1
- package/dist/modules/apimapper/flows.js +60 -77
- package/dist/modules/apimapper/flows.js.map +1 -1
- package/dist/modules/apimapper/gateway/advanced-tool.js +56 -5
- package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
- package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
- package/dist/modules/apimapper/gateway/essentials.js +8 -1
- package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
- package/dist/modules/apimapper/get-skill.d.ts +1 -1
- package/dist/modules/apimapper/get-skill.js +44 -6
- package/dist/modules/apimapper/get-skill.js.map +1 -1
- package/dist/modules/apimapper/graph.js +40 -36
- package/dist/modules/apimapper/graph.js.map +1 -1
- package/dist/modules/apimapper/index.js +2 -0
- package/dist/modules/apimapper/index.js.map +1 -1
- package/dist/modules/apimapper/library.js +425 -83
- package/dist/modules/apimapper/library.js.map +1 -1
- package/dist/modules/apimapper/license.js +12 -36
- package/dist/modules/apimapper/license.js.map +1 -1
- package/dist/modules/apimapper/local-sources.js +20 -34
- package/dist/modules/apimapper/local-sources.js.map +1 -1
- package/dist/modules/apimapper/misc.js +13 -27
- package/dist/modules/apimapper/misc.js.map +1 -1
- package/dist/modules/apimapper/onboarding.d.ts +30 -1
- package/dist/modules/apimapper/onboarding.js +114 -19
- package/dist/modules/apimapper/onboarding.js.map +1 -1
- package/dist/modules/apimapper/schema.js +9 -18
- package/dist/modules/apimapper/schema.js.map +1 -1
- package/dist/modules/apimapper/settings.js +49 -52
- package/dist/modules/apimapper/settings.js.map +1 -1
- package/dist/modules/apimapper/sites-tools.d.ts +29 -0
- package/dist/modules/apimapper/sites-tools.js +165 -0
- package/dist/modules/apimapper/sites-tools.js.map +1 -0
- package/dist/modules/apimapper/tool-result.d.ts +46 -0
- package/dist/modules/apimapper/tool-result.js +63 -0
- package/dist/modules/apimapper/tool-result.js.map +1 -0
- package/dist/modules/apimapper/toolslist-size.d.ts +11 -10
- package/dist/modules/apimapper/toolslist-size.js +16 -14
- package/dist/modules/apimapper/toolslist-size.js.map +1 -1
- package/dist/modules/apimapper/types.d.ts +21 -0
- package/dist/modules/apimapper/types.js.map +1 -1
- package/dist/modules/apimapper/whitelist-drift.d.ts +85 -0
- package/dist/modules/apimapper/whitelist-drift.js +360 -0
- package/dist/modules/apimapper/whitelist-drift.js.map +1 -0
- package/dist/modules/apimapper/workflows.js +82 -27
- package/dist/modules/apimapper/workflows.js.map +1 -1
- package/dist/modules/apimapper/yootheme-binding.d.ts +35 -0
- package/dist/modules/apimapper/yootheme-binding.js +186 -0
- package/dist/modules/apimapper/yootheme-binding.js.map +1 -0
- package/dist/platform/index.d.ts +56 -0
- package/dist/platform/index.js +151 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/setup/detect-clients.d.ts +40 -1
- package/dist/setup/detect-clients.js +148 -1
- package/dist/setup/detect-clients.js.map +1 -1
- package/dist/setup/probe-handshake.js +40 -7
- package/dist/setup/probe-handshake.js.map +1 -1
- package/dist/setup/remove-config.d.ts +8 -0
- package/dist/setup/remove-config.js +145 -0
- package/dist/setup/remove-config.js.map +1 -0
- package/dist/setup/uninstall.d.ts +34 -0
- package/dist/setup/uninstall.js +147 -0
- package/dist/setup/uninstall.js.map +1 -0
- package/dist/setup-cli.d.ts +7 -0
- package/dist/setup-cli.js +29 -1
- package/dist/setup-cli.js.map +1 -1
- package/dist/sites/loader.d.ts +41 -0
- package/dist/sites/loader.js +119 -0
- package/dist/sites/loader.js.map +1 -0
- package/dist/sites/schema.d.ts +69 -0
- package/dist/sites/schema.js +71 -0
- package/dist/sites/schema.js.map +1 -0
- package/dist/sites/secret-resolver.d.ts +47 -0
- package/dist/sites/secret-resolver.js +150 -0
- package/dist/sites/secret-resolver.js.map +1 -0
- package/dist/skill-instructions.d.ts +1 -1
- package/dist/skill-instructions.js +5 -0
- package/dist/skill-instructions.js.map +1 -1
- package/dist/transports/stdio.js +4 -4
- package/dist/transports/stdio.js.map +1 -1
- package/dist/uninstall-skill.d.ts +27 -0
- package/dist/uninstall-skill.js +89 -0
- package/dist/uninstall-skill.js.map +1 -0
- package/docs/architecture.md +21 -21
- package/docs/customgraph-internal-migration.md +4 -4
- package/docs/security.md +2 -21
- package/docs/tools.md +40 -12
- package/manifest.json +77 -79
- package/package.json +68 -65
- package/skills/apimapper/SKILL.md +53 -7
- package/skills/apimapper/reference/conditional-style-multi-items.md +114 -0
- package/skills/apimapper/reference/jmespath-pitfalls.md +108 -0
- package/skills/apimapper/reference/joomla.md +1 -1
- package/skills/apimapper/reference/library-template-discovery.md +65 -0
- package/skills/apimapper/reference/merge-two-sources-on-key.md +99 -0
- package/skills/apimapper/reference/troubleshooting.md +20 -0
- package/skills/apimapper/reference/yootheme.md +1 -1
- package/dist/auth/oauth-provider.d.ts +0 -68
- package/dist/auth/oauth-provider.js +0 -232
- package/dist/auth/oauth-provider.js.map +0 -1
- package/dist/server-http.d.ts +0 -22
- package/dist/server-http.js +0 -159
- package/dist/server-http.js.map +0 -1
- package/dist/transports/http.d.ts +0 -29
- package/dist/transports/http.js +0 -267
- package/dist/transports/http.js.map +0 -1
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// src/modules/apimapper/whitelist-drift.ts — Wave-16 #88
|
|
2
|
+
//
|
|
3
|
+
// MCP↔PHP whitelist drift detector — shared harness.
|
|
4
|
+
//
|
|
5
|
+
// PURPOSE
|
|
6
|
+
// -------
|
|
7
|
+
// Every API Mapper MCP tool that mutates state POSTs/PUTs a JSON body to a
|
|
8
|
+
// WordPress REST route under /wp-json/api-mapper/v1/. If a tool puts a key in
|
|
9
|
+
// that body which the PHP handler does NOT read, one of two bad things happens:
|
|
10
|
+
//
|
|
11
|
+
// * the PHP layer silently DROPS the key (the tool advertises behaviour that
|
|
12
|
+
// never takes effect — e.g. an ordering option that does nothing), or
|
|
13
|
+
// * the PHP layer REJECTS the request (e.g. ConnectionAdminController's
|
|
14
|
+
// ALLOWED_PATCH_KEYS 422s an unknown patch key).
|
|
15
|
+
//
|
|
16
|
+
// Both are real defects, and both are invisible to the type system because the
|
|
17
|
+
// MCP and PHP sides live in different languages. This harness reconciles the
|
|
18
|
+
// two surfaces so a CI test fails the moment they drift.
|
|
19
|
+
//
|
|
20
|
+
// THE THREE INPUTS
|
|
21
|
+
// ----------------
|
|
22
|
+
// 1. The LIVE MCP tool surface — enumerated at runtime from the registered
|
|
23
|
+
// module (essentials forwarded to a real McpServer + advanced registry +
|
|
24
|
+
// the externally-registered profile/status tools). See collectLiveTools().
|
|
25
|
+
// 2. TOOL_WIRE_MAP — the hand-maintained bridge from each body-sending tool to
|
|
26
|
+
// the REST route it calls and the EXACT wire body keys it sends. The wire
|
|
27
|
+
// keys are NOT always the zod keys: several tools remap (e.g.
|
|
28
|
+
// local_source_query's zod `content_type` → wire `contentType`). A PHP scan
|
|
29
|
+
// cannot see this remap and the zod schema does not encode the route, so
|
|
30
|
+
// this bridge is curated. It is anti-rot-guarded: the test asserts it
|
|
31
|
+
// covers EXACTLY the live tool set (minus declared local tools), so a new
|
|
32
|
+
// or removed tool fails CI until the map is updated.
|
|
33
|
+
// 3. The PHP-accepted-params manifest (tests/fixtures/php-accepted-params.json)
|
|
34
|
+
// — route → accepted body keys, generated by scripts/dump-rest-whitelist.php
|
|
35
|
+
// from the real controllers and hand-reconciled. Plus `_allowed_drift`
|
|
36
|
+
// (per-tool justified exceptions) and `_local_tools` (tools with no REST
|
|
37
|
+
// route).
|
|
38
|
+
//
|
|
39
|
+
// The harness is pure + import-safe (no network, no env requirement) so it can
|
|
40
|
+
// be imported by both the vitest pin test and the standalone gate script.
|
|
41
|
+
import { readFileSync } from "node:fs";
|
|
42
|
+
import { fileURLToPath } from "node:url";
|
|
43
|
+
import { dirname, resolve } from "node:path";
|
|
44
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
45
|
+
import { apimapperRestModule } from "./index.js";
|
|
46
|
+
/**
|
|
47
|
+
* Curated bridge: body-sending tool → route + wire body keys.
|
|
48
|
+
*
|
|
49
|
+
* ONLY tools that send a request body appear here. Read-only (GET) tools and
|
|
50
|
+
* path/query-only tools carry no body-drift surface and are listed in the
|
|
51
|
+
* manifest's `_local_tools` (when they hit no route at all) or are simply
|
|
52
|
+
* absent (when they hit a GET route — GET routes never drift on body keys).
|
|
53
|
+
*
|
|
54
|
+
* `envelope` tools forward a free-form record (`patch` / `pipeline` / a settings
|
|
55
|
+
* map) whose individual keys are validated on the PHP side (e.g.
|
|
56
|
+
* ConnectionAdminController::ALLOWED_PATCH_KEYS). Their wrapper key is recorded
|
|
57
|
+
* so the test confirms the tool is accounted for; per-key validation lives in
|
|
58
|
+
* the PHP layer and is covered by PHP-side tests.
|
|
59
|
+
*/
|
|
60
|
+
export const TOOL_WIRE_MAP = {
|
|
61
|
+
// ── Connections ────────────────────────────────────────────────────
|
|
62
|
+
apimapper_connection_create: {
|
|
63
|
+
route: "POST /connections",
|
|
64
|
+
bodyKeys: [
|
|
65
|
+
"name",
|
|
66
|
+
"endpoint",
|
|
67
|
+
"method",
|
|
68
|
+
"auth_type",
|
|
69
|
+
"credential_id",
|
|
70
|
+
"items_path",
|
|
71
|
+
"cache_ttl",
|
|
72
|
+
"description",
|
|
73
|
+
],
|
|
74
|
+
kind: "structured",
|
|
75
|
+
},
|
|
76
|
+
apimapper_connection_update: {
|
|
77
|
+
route: "PUT /connections/{id}",
|
|
78
|
+
bodyKeys: ["patch"],
|
|
79
|
+
kind: "envelope",
|
|
80
|
+
},
|
|
81
|
+
apimapper_connection_recover: {
|
|
82
|
+
route: "PUT /connections/{id}",
|
|
83
|
+
bodyKeys: ["patch"],
|
|
84
|
+
kind: "envelope",
|
|
85
|
+
},
|
|
86
|
+
apimapper_connection_test: {
|
|
87
|
+
route: "POST /connections/test",
|
|
88
|
+
bodyKeys: ["connection_id"],
|
|
89
|
+
kind: "structured",
|
|
90
|
+
},
|
|
91
|
+
apimapper_connection_health_check: {
|
|
92
|
+
route: "POST /connections/health-check",
|
|
93
|
+
bodyKeys: ["connection_ids"],
|
|
94
|
+
kind: "structured",
|
|
95
|
+
},
|
|
96
|
+
apimapper_connection_pipeline_update: {
|
|
97
|
+
route: "PUT /connections/{id}/pipeline",
|
|
98
|
+
bodyKeys: ["pipeline"],
|
|
99
|
+
kind: "envelope",
|
|
100
|
+
},
|
|
101
|
+
// ── Credentials ────────────────────────────────────────────────────
|
|
102
|
+
apimapper_credential_create: {
|
|
103
|
+
route: "POST /credentials",
|
|
104
|
+
bodyKeys: [
|
|
105
|
+
"name",
|
|
106
|
+
"auth_type",
|
|
107
|
+
"auth_data",
|
|
108
|
+
"oauth_provider",
|
|
109
|
+
"provider",
|
|
110
|
+
"oauth_token_expires_at",
|
|
111
|
+
],
|
|
112
|
+
kind: "structured",
|
|
113
|
+
},
|
|
114
|
+
apimapper_credential_update: {
|
|
115
|
+
route: "PUT /credentials/{id}",
|
|
116
|
+
bodyKeys: ["patch"],
|
|
117
|
+
kind: "envelope",
|
|
118
|
+
},
|
|
119
|
+
apimapper_credential_link: {
|
|
120
|
+
route: "POST /oauth/authorize",
|
|
121
|
+
bodyKeys: ["existing_credential_id", "scopes"],
|
|
122
|
+
kind: "structured",
|
|
123
|
+
},
|
|
124
|
+
apimapper_oauth_authorize_begin: {
|
|
125
|
+
route: "POST /oauth/authorize",
|
|
126
|
+
bodyKeys: ["existing_credential_id", "scopes"],
|
|
127
|
+
kind: "structured",
|
|
128
|
+
},
|
|
129
|
+
// ── Flows ──────────────────────────────────────────────────────────
|
|
130
|
+
apimapper_flow_create: {
|
|
131
|
+
route: "POST /flows",
|
|
132
|
+
bodyKeys: ["name", "description", "nodes", "edges", "viewport"],
|
|
133
|
+
kind: "structured",
|
|
134
|
+
},
|
|
135
|
+
apimapper_flow_update: {
|
|
136
|
+
route: "PUT /flows/{id}",
|
|
137
|
+
bodyKeys: ["patch"],
|
|
138
|
+
kind: "envelope",
|
|
139
|
+
},
|
|
140
|
+
apimapper_flow_import_validate: {
|
|
141
|
+
route: "POST /flows/import/validate",
|
|
142
|
+
bodyKeys: ["bundle"],
|
|
143
|
+
kind: "envelope",
|
|
144
|
+
},
|
|
145
|
+
apimapper_flow_import: {
|
|
146
|
+
route: "POST /flows/import",
|
|
147
|
+
bodyKeys: ["bundle"],
|
|
148
|
+
kind: "envelope",
|
|
149
|
+
},
|
|
150
|
+
// ── Graph / Schema ─────────────────────────────────────────────────
|
|
151
|
+
apimapper_graph_preview: {
|
|
152
|
+
route: "POST /graph/preview",
|
|
153
|
+
bodyKeys: ["nodes", "edges", "targetNodeId", "forceRefresh"],
|
|
154
|
+
kind: "structured",
|
|
155
|
+
},
|
|
156
|
+
apimapper_graph_validate: {
|
|
157
|
+
route: "POST /graph/validate",
|
|
158
|
+
bodyKeys: ["nodes", "edges"],
|
|
159
|
+
kind: "structured",
|
|
160
|
+
},
|
|
161
|
+
apimapper_schema_profile: {
|
|
162
|
+
route: "POST /schema-profile",
|
|
163
|
+
bodyKeys: ["nodes", "edges", "target_node_id", "flow_id"],
|
|
164
|
+
kind: "structured",
|
|
165
|
+
},
|
|
166
|
+
// ── Local sources ──────────────────────────────────────────────────
|
|
167
|
+
apimapper_local_source_query: {
|
|
168
|
+
route: "POST /local-sources/query",
|
|
169
|
+
// Wire keys after the handler's zod→wire remap: content_type→contentType,
|
|
170
|
+
// orderby→sort, order→sortDirection (see #88 reconciliation).
|
|
171
|
+
bodyKeys: ["contentType", "filters", "limit", "offset", "sort", "sortDirection"],
|
|
172
|
+
kind: "structured",
|
|
173
|
+
},
|
|
174
|
+
// ── Cache / Settings ───────────────────────────────────────────────
|
|
175
|
+
apimapper_cache_invalidate: {
|
|
176
|
+
route: "POST /cache/admin/invalidate",
|
|
177
|
+
bodyKeys: ["scope", "payload"],
|
|
178
|
+
kind: "structured",
|
|
179
|
+
},
|
|
180
|
+
apimapper_settings_update: {
|
|
181
|
+
route: "POST /settings",
|
|
182
|
+
bodyKeys: ["patch"],
|
|
183
|
+
kind: "envelope",
|
|
184
|
+
},
|
|
185
|
+
// ── License ────────────────────────────────────────────────────────
|
|
186
|
+
apimapper_license_activate: {
|
|
187
|
+
route: "POST /license/activate",
|
|
188
|
+
bodyKeys: ["license_key", "email"],
|
|
189
|
+
kind: "structured",
|
|
190
|
+
},
|
|
191
|
+
apimapper_license_beta_channel: {
|
|
192
|
+
route: "POST /license/beta-channel",
|
|
193
|
+
bodyKeys: ["enabled"],
|
|
194
|
+
kind: "structured",
|
|
195
|
+
},
|
|
196
|
+
// ── Misc ───────────────────────────────────────────────────────────
|
|
197
|
+
apimapper_health_warnings_dismiss: {
|
|
198
|
+
route: "POST /admin/health-warnings/dismiss",
|
|
199
|
+
bodyKeys: ["id"],
|
|
200
|
+
kind: "structured",
|
|
201
|
+
},
|
|
202
|
+
apimapper_feedback_submit: {
|
|
203
|
+
route: "POST /feedback",
|
|
204
|
+
bodyKeys: ["type", "subject", "message", "email", "attachments", "idempotency_key"],
|
|
205
|
+
kind: "structured",
|
|
206
|
+
},
|
|
207
|
+
apimapper_release_download: {
|
|
208
|
+
route: "POST /releases/download",
|
|
209
|
+
bodyKeys: ["version", "product"],
|
|
210
|
+
kind: "structured",
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Resolve the committed fixture path. From this module
|
|
215
|
+
* (`<pkg>/{src,dist}/modules/apimapper/whitelist-drift.{ts,js}`) the package
|
|
216
|
+
* root is three directories up; the fixture is `tests/fixtures/...` under it.
|
|
217
|
+
* The depth is identical for the source layout (vitest/tsx) and the built
|
|
218
|
+
* `dist/` layout, so the same relative offset works in both.
|
|
219
|
+
*/
|
|
220
|
+
export function manifestPath() {
|
|
221
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
222
|
+
return resolve(here, "..", "..", "..", "tests", "fixtures", "php-accepted-params.json");
|
|
223
|
+
}
|
|
224
|
+
/** Narrowing guard for the parsed manifest JSON. */
|
|
225
|
+
function isManifest(value) {
|
|
226
|
+
if (typeof value !== "object" || value === null)
|
|
227
|
+
return false;
|
|
228
|
+
const v = value;
|
|
229
|
+
return (typeof v.routes === "object" &&
|
|
230
|
+
v.routes !== null &&
|
|
231
|
+
typeof v._allowed_drift === "object" &&
|
|
232
|
+
v._allowed_drift !== null &&
|
|
233
|
+
typeof v._local_tools === "object" &&
|
|
234
|
+
v._local_tools !== null);
|
|
235
|
+
}
|
|
236
|
+
/** Load + validate the committed PHP-accepted-params manifest. */
|
|
237
|
+
export function loadManifest(path = manifestPath()) {
|
|
238
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
239
|
+
if (!isManifest(raw)) {
|
|
240
|
+
throw new Error(`Invalid php-accepted-params manifest at ${path}`);
|
|
241
|
+
}
|
|
242
|
+
return raw;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Enumerate every registered MCP tool name. Mirrors the runtime tool surface:
|
|
246
|
+
* - module essentials forwarded to a real McpServer,
|
|
247
|
+
* - the gateway-captured advanced tools,
|
|
248
|
+
* - the profile / status tools registered in src/index.ts AFTER module load.
|
|
249
|
+
*
|
|
250
|
+
* The src/index.ts trio is appended explicitly: they are registered on the real
|
|
251
|
+
* server outside the module's register(), so a module-only enumeration would
|
|
252
|
+
* miss them. They are pure local/profile tools (no REST body) and live in
|
|
253
|
+
* `_local_tools`; listing them here keeps the coverage check honest.
|
|
254
|
+
*/
|
|
255
|
+
export function collectLiveTools() {
|
|
256
|
+
const server = new McpServer({ name: "drift-probe", version: "0.0.0" });
|
|
257
|
+
const names = new Set();
|
|
258
|
+
// Intercept registerTool so essentials forwarded to the real server are seen.
|
|
259
|
+
const realRegister = server.registerTool.bind(server);
|
|
260
|
+
const patched = server;
|
|
261
|
+
patched.registerTool = (name, config, cb) => {
|
|
262
|
+
names.add(name);
|
|
263
|
+
return realRegister(name, config, cb);
|
|
264
|
+
};
|
|
265
|
+
apimapperRestModule.register(server);
|
|
266
|
+
const advanced = apimapperRestModule.getAdvancedRegistry();
|
|
267
|
+
if (advanced) {
|
|
268
|
+
for (const name of advanced.keys())
|
|
269
|
+
names.add(name);
|
|
270
|
+
}
|
|
271
|
+
// Externally-registered (src/index.ts) — no REST body; covered by _local_tools.
|
|
272
|
+
for (const n of ["apimapper_use_profile", "apimapper_list_profiles", "apimapper_rest_modules_status"]) {
|
|
273
|
+
names.add(n);
|
|
274
|
+
}
|
|
275
|
+
return [...names].sort();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Compute the drift report from the live tool surface + the curated wire map +
|
|
279
|
+
* the PHP-accepted manifest. Pure — no I/O.
|
|
280
|
+
*/
|
|
281
|
+
export function computeDrift(liveTools, manifest) {
|
|
282
|
+
const drift = [];
|
|
283
|
+
const inverse = [];
|
|
284
|
+
const uncovered = [];
|
|
285
|
+
const live = new Set(liveTools);
|
|
286
|
+
// Coverage: every live tool must be in the wire map OR declared local.
|
|
287
|
+
for (const tool of liveTools) {
|
|
288
|
+
const inWireMap = Object.prototype.hasOwnProperty.call(TOOL_WIRE_MAP, tool);
|
|
289
|
+
const isLocal = Object.prototype.hasOwnProperty.call(manifest._local_tools, tool);
|
|
290
|
+
if (!inWireMap && !isLocal)
|
|
291
|
+
uncovered.push(tool);
|
|
292
|
+
}
|
|
293
|
+
// Anti-rot: nothing in the wire map / local map may reference a vanished tool.
|
|
294
|
+
const stale = [];
|
|
295
|
+
for (const tool of Object.keys(TOOL_WIRE_MAP)) {
|
|
296
|
+
if (!live.has(tool))
|
|
297
|
+
stale.push(tool);
|
|
298
|
+
}
|
|
299
|
+
for (const tool of Object.keys(manifest._local_tools)) {
|
|
300
|
+
if (!live.has(tool))
|
|
301
|
+
stale.push(tool);
|
|
302
|
+
}
|
|
303
|
+
// Forward direction: each wire body key must be accepted or allow-listed.
|
|
304
|
+
for (const [tool, binding] of Object.entries(TOOL_WIRE_MAP)) {
|
|
305
|
+
if (!live.has(tool))
|
|
306
|
+
continue; // reported as stale above
|
|
307
|
+
const accepted = new Set(manifest.routes[binding.route] ?? []);
|
|
308
|
+
const allowed = manifest._allowed_drift[tool] ?? {};
|
|
309
|
+
for (const key of binding.bodyKeys) {
|
|
310
|
+
if (accepted.has(key))
|
|
311
|
+
continue;
|
|
312
|
+
if (Object.prototype.hasOwnProperty.call(allowed, key))
|
|
313
|
+
continue;
|
|
314
|
+
drift.push({ tool, route: binding.route, key });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Inverse direction (advisory): PHP-accepted keys no tool sends.
|
|
318
|
+
//
|
|
319
|
+
// Built per route from the union of the STRUCTURED bindings' literal body
|
|
320
|
+
// keys. Routes that ANY envelope binding targets are skipped entirely: an
|
|
321
|
+
// envelope tool forwards a free-form record that can legitimately carry any
|
|
322
|
+
// of the route's accepted keys, so "no tool sends key X" is meaningless there
|
|
323
|
+
// (per-key validity for envelope routes is a PHP-side concern). The inverse
|
|
324
|
+
// check therefore only fires for routes whose tools send a fixed, literal key
|
|
325
|
+
// set — exactly where a PHP-only key really is unreachable from the MCP.
|
|
326
|
+
const sentByRoute = new Map();
|
|
327
|
+
const envelopeRoutes = new Set();
|
|
328
|
+
for (const binding of Object.values(TOOL_WIRE_MAP)) {
|
|
329
|
+
if (binding.kind === "envelope") {
|
|
330
|
+
envelopeRoutes.add(binding.route);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (!sentByRoute.has(binding.route))
|
|
334
|
+
sentByRoute.set(binding.route, new Set());
|
|
335
|
+
const set = sentByRoute.get(binding.route);
|
|
336
|
+
for (const k of binding.bodyKeys)
|
|
337
|
+
set.add(k);
|
|
338
|
+
}
|
|
339
|
+
for (const [route, accepted] of Object.entries(manifest.routes)) {
|
|
340
|
+
if (envelopeRoutes.has(route))
|
|
341
|
+
continue; // free-form record carries any key
|
|
342
|
+
const sent = sentByRoute.get(route);
|
|
343
|
+
if (!sent)
|
|
344
|
+
continue; // no structured tool targets this route with a body
|
|
345
|
+
const ignore = manifest._inverse_ignore?.[route] ?? {};
|
|
346
|
+
for (const key of accepted) {
|
|
347
|
+
if (sent.has(key))
|
|
348
|
+
continue;
|
|
349
|
+
if (Object.prototype.hasOwnProperty.call(ignore, key))
|
|
350
|
+
continue;
|
|
351
|
+
inverse.push({ route, key });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
drift.sort((a, b) => a.tool.localeCompare(b.tool) || a.key.localeCompare(b.key));
|
|
355
|
+
inverse.sort((a, b) => a.route.localeCompare(b.route) || a.key.localeCompare(b.key));
|
|
356
|
+
uncovered.sort();
|
|
357
|
+
stale.sort();
|
|
358
|
+
return { drift, inverse, uncovered, stale };
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=whitelist-drift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whitelist-drift.js","sourceRoot":"","sources":["../../../src/modules/apimapper/whitelist-drift.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,9 +56,20 @@ export function registerWorkflowTools(server) {
|
|
|
56
56
|
transform: TransformSpec.optional().describe("Optional Transform node"),
|
|
57
57
|
output: OutputSpec.describe("Output node config"),
|
|
58
58
|
compile: z.boolean().default(true).describe("Compile after save (= publish to YOOtheme)"),
|
|
59
|
+
autofix: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.default(true)
|
|
62
|
+
.describe("Defaults to true. When compile:true, auto-repair empty-output-schema compile errors " +
|
|
63
|
+
"by calling detect-schema then retrying compile so the new source compiles and " +
|
|
64
|
+
"appears. Pass false to opt out (verbatim old behaviour — no detect-schema retry). " +
|
|
65
|
+
"Mirrors `flow_full_recompile_publish` autofix."),
|
|
59
66
|
},
|
|
60
67
|
annotations: creating({ title: "Setup Flow with Sources", openWorld: true }),
|
|
61
|
-
}, async ({ name, description, sources, merge, transform, output, compile }, extra) => {
|
|
68
|
+
}, async ({ name, description, sources, merge, transform, output, compile, autofix }, extra) => {
|
|
69
|
+
// C (2026-06-04): autofix defaults to true. Treat `undefined` (omitted
|
|
70
|
+
// arg / unit-test harness that bypasses the schema default) as the
|
|
71
|
+
// on-default; only an explicit `autofix:false` opts out.
|
|
72
|
+
const autofixEnabled = autofix !== false;
|
|
62
73
|
// W3.5 — progress side-channel. `null` when the caller sent no
|
|
63
74
|
// progressToken; `progress?.report(...)` then no-ops. Total steps:
|
|
64
75
|
// build-graph + create + (compile when requested).
|
|
@@ -116,9 +127,29 @@ export function registerWorkflowTools(server) {
|
|
|
116
127
|
// does not include the full flow object.
|
|
117
128
|
let compiled = false;
|
|
118
129
|
let compileError;
|
|
130
|
+
const autofixActions = [];
|
|
119
131
|
if (compile) {
|
|
120
132
|
await progress?.report(2, totalSteps, "Compiling and publishing to YOOtheme…");
|
|
121
|
-
|
|
133
|
+
let compileR = await request(`/flows/${encodeURIComponent(flowId)}/compile`, { method: "POST" });
|
|
134
|
+
// F5 (W9b, 2026-05-29): autofix mirror of flow_full_recompile_publish.
|
|
135
|
+
// CODE_EMPTY_SCHEMA = 4 (PHP FlowCompilerException constant, frozen).
|
|
136
|
+
// On empty-schema error, call detect-schema then retry compile. This
|
|
137
|
+
// makes the composite tool self-healing on the most common compile
|
|
138
|
+
// failure (newly created flow has no output schema yet).
|
|
139
|
+
const COMPILER_CODE_EMPTY_SCHEMA = 4;
|
|
140
|
+
if (!compileR.success &&
|
|
141
|
+
autofixEnabled &&
|
|
142
|
+
compileR.errorBody?.compiler_error_code === COMPILER_CODE_EMPTY_SCHEMA) {
|
|
143
|
+
autofixActions.push("Detected empty output schema, running detect-schema");
|
|
144
|
+
const detectR = await request(`/flows/${encodeURIComponent(flowId)}/detect-schema`, { method: "POST" });
|
|
145
|
+
if (detectR.success) {
|
|
146
|
+
autofixActions.push("Schema detected, retrying compile");
|
|
147
|
+
compileR = await request(`/flows/${encodeURIComponent(flowId)}/compile`, { method: "POST" });
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
autofixActions.push(`Schema detection failed: ${detectR.error ?? "unknown"}; cannot retry compile`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
122
153
|
if (!compileR.success) {
|
|
123
154
|
compileError = compileR.error;
|
|
124
155
|
steps.push(`2. Compile FAILED: ${compileR.error}`);
|
|
@@ -132,7 +163,8 @@ export function registerWorkflowTools(server) {
|
|
|
132
163
|
}
|
|
133
164
|
else {
|
|
134
165
|
compiled = true;
|
|
135
|
-
|
|
166
|
+
const autofixSuffix = autofixActions.length > 0 ? " (autofix applied)" : "";
|
|
167
|
+
steps.push(`2. Compiled — flow visible in YOOtheme dropdown${autofixSuffix}`);
|
|
136
168
|
}
|
|
137
169
|
}
|
|
138
170
|
}
|
|
@@ -142,6 +174,7 @@ export function registerWorkflowTools(server) {
|
|
|
142
174
|
flow_id: flowId,
|
|
143
175
|
compiled: false,
|
|
144
176
|
compile_error: compileError,
|
|
177
|
+
autofix_actions: autofixActions.length > 0 ? autofixActions : undefined,
|
|
145
178
|
steps,
|
|
146
179
|
next_action: "investigate_compile_error",
|
|
147
180
|
cleanup: `Call apimapper_flow_delete with id="${flowId}" + confirm=true to roll back the orphan, OR fix node config and call apimapper_flow_compile again.`,
|
|
@@ -298,20 +331,30 @@ export function registerWorkflowTools(server) {
|
|
|
298
331
|
// so the source appears in YOOtheme immediately without a second MCP
|
|
299
332
|
// package (matches audit recommendation A1 P1).
|
|
300
333
|
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
334
|
+
// C (2026-06-04): the default is now `autofix:true`. Observed live in a
|
|
335
|
+
// Claude Desktop session: when the AI omits `autofix`, the old default
|
|
336
|
+
// (false) meant the YOOtheme schema cache was never flushed, so the source
|
|
337
|
+
// did NOT appear in the YOOtheme builder after publish. Flipping the default
|
|
338
|
+
// makes the autofix path (detect-schema retry + flush-yt) the normal path,
|
|
339
|
+
// so a plain `apimapper_flow_full_recompile_publish({id})` reliably publishes
|
|
340
|
+
// a visible source. `autofix:false` is now an explicit opt-out that restores
|
|
341
|
+
// the verbatim v2.0.7 behaviour (compile + admin-cache only, no detect-schema
|
|
342
|
+
// retry, no YT flush). The handler treats `autofix !== false` as enabled, so
|
|
343
|
+
// an omitted/undefined value behaves identically to the schema default at the
|
|
344
|
+
// real SDK call boundary. The PHP layer surfaces `compiler_error_code` on
|
|
345
|
+
// compile-error responses (FlowHandler.php) so this branch reads a numeric
|
|
346
|
+
// class rather than string-matching messages.
|
|
305
347
|
server.registerTool("apimapper_flow_full_recompile_publish", {
|
|
306
348
|
title: "Full Recompile + Cache Clear (Composite)",
|
|
307
|
-
description: "Compile a flow + invalidate admin cache
|
|
308
|
-
"to ensure the flow + schema are fresh. Each step's success
|
|
309
|
-
"and a cache-clear failure does NOT silently downgrade compile success. " +
|
|
310
|
-
"
|
|
311
|
-
"(empty output schema → re-detect; then retry compile)
|
|
312
|
-
"YOOtheme schema cache so the source becomes visible in YOOtheme
|
|
313
|
-
"
|
|
314
|
-
"
|
|
349
|
+
description: "Compile a flow + invalidate admin cache + flush the YOOtheme schema cache in one call. " +
|
|
350
|
+
"Use after upstream API changes to ensure the flow + schema are fresh. Each step's success " +
|
|
351
|
+
"is surfaced separately, and a cache-clear failure does NOT silently downgrade compile success. " +
|
|
352
|
+
"autofix defaults to true: it auto-repairs common compile errors " +
|
|
353
|
+
"(empty output schema → re-detect; then retry compile) AND flushes the " +
|
|
354
|
+
"YOOtheme schema cache so the source becomes visible in the YOOtheme builder — " +
|
|
355
|
+
"no second MCP package needed. Pass autofix:false to opt out (compile + admin-cache " +
|
|
356
|
+
"only, no detect-schema retry, no YT flush — the verbatim v2.0.7 behaviour)." +
|
|
357
|
+
"\n\nExample:\n apimapper_flow_full_recompile_publish({ id: 'flow_Z2fLg70M84' })",
|
|
315
358
|
inputSchema: {
|
|
316
359
|
// L-6 (W3F-5): accept either `id` (canonical, matches every other
|
|
317
360
|
// flow_* tool) or `flow_id` (legacy alias kept so existing callers
|
|
@@ -327,10 +370,11 @@ export function registerWorkflowTools(server) {
|
|
|
327
370
|
.describe("Deprecated alias for `id` — accepted for back-compat with pre-W3F-5 callers."),
|
|
328
371
|
autofix: z
|
|
329
372
|
.boolean()
|
|
330
|
-
.default(
|
|
331
|
-
.describe("When true: on empty-schema compile error, call detect-schema and retry
|
|
332
|
-
"On compile success, also flush the YOOtheme schema cache so the source
|
|
333
|
-
"in the YOOtheme builder.
|
|
373
|
+
.default(true)
|
|
374
|
+
.describe("When true (default): on empty-schema compile error, call detect-schema and retry " +
|
|
375
|
+
"compile. On compile success, also flush the YOOtheme schema cache so the source " +
|
|
376
|
+
"surfaces in the YOOtheme builder. Pass false to opt out — compile + admin-cache " +
|
|
377
|
+
"only, no detect-schema retry, no YT flush (the verbatim v2.0.7 behaviour)."),
|
|
334
378
|
},
|
|
335
379
|
annotations: mutating({ title: "Full Recompile & Publish Flow", openWorld: true }),
|
|
336
380
|
}, async ({ id, flow_id: flowIdAlias, autofix }, extra) => {
|
|
@@ -346,9 +390,15 @@ export function registerWorkflowTools(server) {
|
|
|
346
390
|
const steps = [];
|
|
347
391
|
const errors = [];
|
|
348
392
|
const autofixActions = [];
|
|
349
|
-
//
|
|
393
|
+
// C (2026-06-04): autofix defaults to true. Treat `undefined` (caller
|
|
394
|
+
// omitted the arg, and the unit-test harness bypasses the schema default)
|
|
395
|
+
// identically to the on-default, so only an explicit `autofix:false`
|
|
396
|
+
// opts out. This keeps the handler faithful to the documented default
|
|
397
|
+
// whether or not the SDK applied the Zod default before dispatch.
|
|
398
|
+
const autofixEnabled = autofix !== false;
|
|
399
|
+
// W3.5: progress side-channel. autofix adds a third stage
|
|
350
400
|
// (YOOtheme schema cache flush) so it's a 3-step pipeline.
|
|
351
|
-
const totalSteps =
|
|
401
|
+
const totalSteps = autofixEnabled ? 3 : 2;
|
|
352
402
|
const progress = extra ? createProgressReporter(extra) : null;
|
|
353
403
|
await progress?.report(0, totalSteps, "Recompiling flow…");
|
|
354
404
|
// Stage 1: compile (with optional autofix retry)
|
|
@@ -359,7 +409,7 @@ export function registerWorkflowTools(server) {
|
|
|
359
409
|
// because both share src/modules/core/src/Flow/FlowCompilerException.php).
|
|
360
410
|
const COMPILER_CODE_EMPTY_SCHEMA = 4;
|
|
361
411
|
if (!compileR.success &&
|
|
362
|
-
|
|
412
|
+
autofixEnabled &&
|
|
363
413
|
compileR.errorBody?.compiler_error_code === COMPILER_CODE_EMPTY_SCHEMA) {
|
|
364
414
|
autofixActions.push("Detected empty output schema, running detect-schema");
|
|
365
415
|
const detectR = await request(`/flows/${encodeURIComponent(flow_id)}/detect-schema`, { method: "POST" });
|
|
@@ -377,7 +427,7 @@ export function registerWorkflowTools(server) {
|
|
|
377
427
|
status: compileR.status,
|
|
378
428
|
errorCode: compileR.errorCode,
|
|
379
429
|
compiler_error_code: compileR.errorBody?.compiler_error_code,
|
|
380
|
-
context: { flow_id, stage: "compile", autofix:
|
|
430
|
+
context: { flow_id, stage: "compile", autofix: autofixEnabled },
|
|
381
431
|
autofix_actions: autofixActions.length > 0 ? autofixActions : undefined,
|
|
382
432
|
hint: hintFor(compileR.errorCode),
|
|
383
433
|
}, true);
|
|
@@ -406,7 +456,7 @@ export function registerWorkflowTools(server) {
|
|
|
406
456
|
// {flushed: false, error: <msg>} in the body when the buster threw.
|
|
407
457
|
// We treat the request-level success/failure as authoritative and
|
|
408
458
|
// record any flushed:false as a non-fatal warning step.
|
|
409
|
-
if (
|
|
459
|
+
if (autofixEnabled) {
|
|
410
460
|
await progress?.report(2, totalSteps, "Flushing YOOtheme schema cache…");
|
|
411
461
|
const ytR = await request("/cache/flush-yt", { method: "POST" });
|
|
412
462
|
if (ytR.success && ytR.data?.flushed !== false) {
|
|
@@ -434,11 +484,16 @@ export function registerWorkflowTools(server) {
|
|
|
434
484
|
errors: ok ? undefined : errors,
|
|
435
485
|
autofix_actions: autofixActions.length > 0 ? autofixActions : undefined,
|
|
436
486
|
next: ok
|
|
437
|
-
?
|
|
487
|
+
? autofixEnabled
|
|
438
488
|
? "Flow recompiled, admin cache invalidated, and YOOtheme schema cache flushed. The source should now be visible in the YOOtheme builder."
|
|
439
|
-
: "Flow is fresh in both admin + render layers. YOOtheme schema cache
|
|
489
|
+
: "Flow is fresh in both admin + render layers. You passed autofix:false, so the YOOtheme schema cache was NOT cleared by this tool; re-run without autofix:false (the default flushes it) or call yootheme_clear_cache via the YOOtheme MCP if the source does not appear in the builder."
|
|
440
490
|
: "Compile succeeded but a cache step had issues. Manually call apimapper_cache_invalidate scope=all if downstream still sees stale data.",
|
|
441
|
-
},
|
|
491
|
+
},
|
|
492
|
+
// Wave-5 F7 fix (2026-05-29): formatResult's 2nd positional is `isError`.
|
|
493
|
+
// Pre-fix we passed `ok` here, which inverted the meaning — ok:true was
|
|
494
|
+
// tagged as `<error>` envelope. Pass `!ok` so success-with-no-errors
|
|
495
|
+
// renders as success and partial (errors.length > 0) renders as error.
|
|
496
|
+
!ok, { maxChars: 2500 });
|
|
442
497
|
});
|
|
443
498
|
}
|
|
444
499
|
//# sourceMappingURL=workflows.js.map
|