@wootsup/mcp 0.3.0 → 0.4.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 +14 -5
- package/dist/catalog/build-catalog.d.ts +31 -0
- package/dist/catalog/build-catalog.js +68 -0
- package/dist/catalog/build-catalog.js.map +1 -0
- package/dist/index.js +37 -5
- package/dist/index.js.map +1 -1
- package/dist/modules/apimapper/auto-layout.d.ts +21 -0
- package/dist/modules/apimapper/auto-layout.js +54 -0
- package/dist/modules/apimapper/auto-layout.js.map +1 -0
- package/dist/modules/apimapper/client.d.ts +54 -4
- package/dist/modules/apimapper/client.js +145 -14
- package/dist/modules/apimapper/client.js.map +1 -1
- package/dist/modules/apimapper/connections-format.d.ts +31 -1
- package/dist/modules/apimapper/connections-format.js +97 -5
- package/dist/modules/apimapper/connections-format.js.map +1 -1
- package/dist/modules/apimapper/connections.d.ts +9 -7
- package/dist/modules/apimapper/connections.js +225 -58
- package/dist/modules/apimapper/connections.js.map +1 -1
- package/dist/modules/apimapper/credentials.js +86 -14
- package/dist/modules/apimapper/credentials.js.map +1 -1
- package/dist/modules/apimapper/elicitation.d.ts +29 -0
- package/dist/modules/apimapper/elicitation.js +62 -0
- package/dist/modules/apimapper/elicitation.js.map +1 -1
- package/dist/modules/apimapper/example-extract.d.ts +13 -0
- package/dist/modules/apimapper/example-extract.js +111 -0
- package/dist/modules/apimapper/example-extract.js.map +1 -0
- package/dist/modules/apimapper/filter-operators.d.ts +24 -0
- package/dist/modules/apimapper/filter-operators.js +103 -0
- package/dist/modules/apimapper/filter-operators.js.map +1 -0
- package/dist/modules/apimapper/flows-format.js +92 -22
- package/dist/modules/apimapper/flows-format.js.map +1 -1
- package/dist/modules/apimapper/flows.d.ts +8 -7
- package/dist/modules/apimapper/flows.js +216 -44
- package/dist/modules/apimapper/flows.js.map +1 -1
- package/dist/modules/apimapper/gateway/advanced-read-tool.d.ts +9 -0
- package/dist/modules/apimapper/gateway/advanced-read-tool.js +172 -0
- package/dist/modules/apimapper/gateway/advanced-read-tool.js.map +1 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js +39 -130
- package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
- package/dist/modules/apimapper/gateway/collect-module-tools.d.ts +17 -0
- package/dist/modules/apimapper/gateway/collect-module-tools.js +44 -0
- package/dist/modules/apimapper/gateway/collect-module-tools.js.map +1 -0
- package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
- package/dist/modules/apimapper/gateway/essentials.js +19 -7
- package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
- package/dist/modules/apimapper/gateway/gateway-shared.d.ts +21 -0
- package/dist/modules/apimapper/gateway/gateway-shared.js +124 -0
- package/dist/modules/apimapper/gateway/gateway-shared.js.map +1 -0
- package/dist/modules/apimapper/gateway/test-support.d.ts +1 -17
- package/dist/modules/apimapper/gateway/test-support.js +4 -33
- package/dist/modules/apimapper/gateway/test-support.js.map +1 -1
- package/dist/modules/apimapper/get-skill-cores.d.ts +4 -0
- package/dist/modules/apimapper/get-skill-cores.js +220 -0
- package/dist/modules/apimapper/get-skill-cores.js.map +1 -0
- package/dist/modules/apimapper/get-skill.d.ts +1 -1
- package/dist/modules/apimapper/get-skill.js +30 -3
- package/dist/modules/apimapper/get-skill.js.map +1 -1
- package/dist/modules/apimapper/graph-builder.d.ts +85 -2
- package/dist/modules/apimapper/graph-builder.js +151 -15
- package/dist/modules/apimapper/graph-builder.js.map +1 -1
- package/dist/modules/apimapper/graph.js +115 -15
- package/dist/modules/apimapper/graph.js.map +1 -1
- package/dist/modules/apimapper/index.js +25 -13
- package/dist/modules/apimapper/index.js.map +1 -1
- package/dist/modules/apimapper/jmespath-test.d.ts +4 -0
- package/dist/modules/apimapper/jmespath-test.js +152 -0
- package/dist/modules/apimapper/jmespath-test.js.map +1 -0
- package/dist/modules/apimapper/library.js +131 -8
- package/dist/modules/apimapper/library.js.map +1 -1
- package/dist/modules/apimapper/list-footer.d.ts +27 -0
- package/dist/modules/apimapper/list-footer.js +57 -0
- package/dist/modules/apimapper/list-footer.js.map +1 -0
- package/dist/modules/apimapper/local-sources.js +88 -31
- package/dist/modules/apimapper/local-sources.js.map +1 -1
- package/dist/modules/apimapper/mcp-client-identity.d.ts +32 -0
- package/dist/modules/apimapper/mcp-client-identity.js +70 -0
- package/dist/modules/apimapper/mcp-client-identity.js.map +1 -0
- package/dist/modules/apimapper/merge-constants.d.ts +6 -0
- package/dist/modules/apimapper/merge-constants.js +26 -0
- package/dist/modules/apimapper/merge-constants.js.map +1 -0
- package/dist/modules/apimapper/node-schema.d.ts +52 -2
- package/dist/modules/apimapper/node-schema.js +95 -4
- package/dist/modules/apimapper/node-schema.js.map +1 -1
- package/dist/modules/apimapper/onboarding.d.ts +29 -0
- package/dist/modules/apimapper/onboarding.js +117 -9
- package/dist/modules/apimapper/onboarding.js.map +1 -1
- package/dist/modules/apimapper/read-cache.d.ts +16 -3
- package/dist/modules/apimapper/read-cache.js +59 -4
- package/dist/modules/apimapper/read-cache.js.map +1 -1
- package/dist/modules/apimapper/render/index.js +26 -5
- package/dist/modules/apimapper/render/index.js.map +1 -1
- package/dist/modules/apimapper/resource-id.d.ts +13 -0
- package/dist/modules/apimapper/resource-id.js +69 -0
- package/dist/modules/apimapper/resource-id.js.map +1 -0
- package/dist/modules/apimapper/tool-result.d.ts +20 -0
- package/dist/modules/apimapper/tool-result.js +67 -5
- package/dist/modules/apimapper/tool-result.js.map +1 -1
- package/dist/modules/apimapper/toolslist-size.d.ts +10 -10
- package/dist/modules/apimapper/toolslist-size.js +29 -18
- package/dist/modules/apimapper/toolslist-size.js.map +1 -1
- package/dist/modules/apimapper/types.d.ts +13 -0
- package/dist/modules/apimapper/types.js +1 -1
- package/dist/modules/apimapper/types.js.map +1 -1
- package/dist/modules/apimapper/whitelist-drift.js +16 -1
- package/dist/modules/apimapper/whitelist-drift.js.map +1 -1
- package/dist/modules/apimapper/workflows.js +221 -32
- package/dist/modules/apimapper/workflows.js.map +1 -1
- package/dist/modules/apimapper/yootheme-binding.js +103 -22
- package/dist/modules/apimapper/yootheme-binding.js.map +1 -1
- package/dist/platform/index.js +7 -0
- package/dist/platform/index.js.map +1 -1
- package/dist/proxy/bridge.d.ts +35 -0
- package/dist/proxy/bridge.js +129 -0
- package/dist/proxy/bridge.js.map +1 -0
- package/dist/proxy/mode.d.ts +9 -0
- package/dist/proxy/mode.js +20 -0
- package/dist/proxy/mode.js.map +1 -0
- package/dist/setup/probe-auth.d.ts +51 -0
- package/dist/setup/probe-auth.js +141 -0
- package/dist/setup/probe-auth.js.map +1 -0
- package/dist/setup-cli.d.ts +9 -0
- package/dist/setup-cli.js +34 -0
- package/dist/setup-cli.js.map +1 -1
- package/dist/sites/loader.d.ts +7 -0
- package/dist/sites/loader.js +16 -1
- package/dist/sites/loader.js.map +1 -1
- package/dist/skill-instructions.d.ts +14 -1
- package/dist/skill-instructions.js +30 -6
- package/dist/skill-instructions.js.map +1 -1
- package/manifest.json +2 -2
- package/package.json +3 -2
- package/skills/apimapper/SKILL.md +78 -3
- package/skills/apimapper/reference/dynamize-existing-layout.md +158 -0
- package/skills/apimapper/reference/jmespath-cookbook.md +241 -0
- package/skills/apimapper/reference/jmespath-pitfalls.md +81 -0
- package/skills/apimapper/reference/library-template-discovery.md +1 -1
- package/skills/apimapper/reference/merge-two-sources-on-key.md +117 -12
- package/skills/apimapper/reference/oauth.md +143 -52
- package/skills/apimapper/reference/troubleshooting.md +2 -2
- package/skills/apimapper/reference/yootheme-source-to-builder-handoff.md +348 -0
- package/skills/apimapper/reference/yootheme.md +75 -44
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatResult, tableResult, errorResult, readOnly, creating, mutating, destructive, pickFields, createProgressReporter,
|
|
2
|
+
import { formatResult, tableResult, errorResult, readOnly, creating, mutating, destructive, pickFields, createProgressReporter, } from "@getimo/mcp-toolkit";
|
|
3
3
|
import { request, hintFor, WP_BASE, WP_USER, authConfigured } from "./client.js";
|
|
4
4
|
import { restErrorResult } from "./tool-result.js";
|
|
5
5
|
import { toRows } from "./types.js";
|
|
@@ -7,7 +7,8 @@ import { unwrapEntity } from "./envelope.js";
|
|
|
7
7
|
import { ambiguityFallbackError, } from "./elicitation.js";
|
|
8
8
|
import { fetchWithTimeout } from "./diagnose.js";
|
|
9
9
|
import { filterByNameQuery } from "./node-schema.js";
|
|
10
|
-
import {
|
|
10
|
+
import { withOverflowFooter, truncatedByLimitNote } from "./list-footer.js";
|
|
11
|
+
import { CONNECTION_TABLE_COLUMNS, CONNECTION_FULL_COLUMNS, CONNECTION_COMPACT_COLUMNS, CONNECTION_LIST_NEXT_STEPS, RESOURCE_TABLE_COLUMNS, mapConnectionRow, mapConnectionRowFull, compactConnectionRow, mapResourceRow, buildConnectionDetail, buildConnectionTestStats, buildHealthCheckStats, buildHealthStats, } from "./connections-format.js";
|
|
11
12
|
// W3 Stage-2 hardening — applyTrim + extractResourceList live in the
|
|
12
13
|
// sibling connections-trim.ts (pure functions, ~200 lines). Imported for
|
|
13
14
|
// internal use by the connection_data + connection_resources handlers,
|
|
@@ -25,6 +26,7 @@ export { applyTrim, extractResourceList };
|
|
|
25
26
|
// formatter; connection_get / _update / _delete pass through unwrapEntity
|
|
26
27
|
// + the rich-card builder which already reads from the same row map.
|
|
27
28
|
import { normalizeConnectionFromWire } from "./normalizers.js";
|
|
29
|
+
import { normalizeResourceIdFields } from "./resource-id.js";
|
|
28
30
|
/**
|
|
29
31
|
* F-15 (W1.9) — downstream probe of api.wootsup.com from `apimapper_health`.
|
|
30
32
|
*
|
|
@@ -72,13 +74,16 @@ async function probeApimapperApi() {
|
|
|
72
74
|
/**
|
|
73
75
|
* Register the connection CRUD + probe + sample + pipeline tools.
|
|
74
76
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* Chat-first disambiguation (decision 2026-06-15): on genuine credential
|
|
78
|
+
* ambiguity `connection_create` returns a structured candidate-list error
|
|
79
|
+
* (the assistant asks in plain chat) rather than driving the native
|
|
80
|
+
* elicitation picker. The picker is reserved for OAuth authorization
|
|
81
|
+
* (`apimapper_oauth_authorize_begin` in credentials.ts), so this module no
|
|
82
|
+
* longer takes an elicitation capability.
|
|
83
|
+
*
|
|
84
|
+
* @param server the tool registrar (essentials forward, rest captured).
|
|
80
85
|
*/
|
|
81
|
-
export function registerConnectionTools(server
|
|
86
|
+
export function registerConnectionTools(server) {
|
|
82
87
|
// ── apimapper_health ───────────────────────────────────────────────
|
|
83
88
|
server.registerTool("apimapper_health", {
|
|
84
89
|
title: "API Mapper REST Health",
|
|
@@ -159,17 +164,28 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
159
164
|
// rc.10 A5 (2026-05-19) — description states the default limit
|
|
160
165
|
// already covers a typical install. The Maria-walkthrough log
|
|
161
166
|
// showed AI defaulting to limit:200/500 reflexively.
|
|
162
|
-
description: "List
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
+
description: "List API Mapper connections — a LEAN projection by default (id / name / source / host / health). " +
|
|
168
|
+
"The default limit of 25 already covers a typical install — most customers run with 5-25 " +
|
|
169
|
+
"connections, increase the limit only if you have a specific reason to expect more. " +
|
|
170
|
+
"Pass `fields: ['all']` for the full dump (endpoint URL, method, auth_type, credential_id), " +
|
|
171
|
+
"or a list of specific extra columns. Use apimapper_connection_get for full details of a single connection." +
|
|
172
|
+
"\n\nExample:\n apimapper_connection_list({}) // lean projection, up to 25 connections\n" +
|
|
173
|
+
" apimapper_connection_list({ source: 'user' }) // narrow to user-created only\n" +
|
|
174
|
+
" apimapper_connection_list({ fields: ['all'] }) // full detail dump",
|
|
167
175
|
inputSchema: {
|
|
168
176
|
source: z
|
|
169
177
|
.enum(["library", "demo", "user", "all"])
|
|
170
178
|
.default("all")
|
|
171
179
|
.describe("Filter by origin"),
|
|
172
|
-
limit: z.number().min(1).max(500).default(
|
|
180
|
+
limit: z.number().min(1).max(500).default(25).describe("Max items (1-500). Default 25 already covers a typical install — do not raise unless you have evidence the list is larger."),
|
|
181
|
+
// F198 — opt-in full detail. The default projection is the lean
|
|
182
|
+
// id/name/source/host/health set; `fields` widens it. `['all']` (or
|
|
183
|
+
// any non-empty array) returns the full endpoint/method/auth/cred dump.
|
|
184
|
+
fields: z
|
|
185
|
+
.array(z.string().min(1))
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Opt into extra columns. Omit for the lean default (id/name/source/host/health). " +
|
|
188
|
+
"Pass ['all'] for the full dump (endpoint URL, method, auth_type, credential_id)."),
|
|
173
189
|
// W1.18 (F-32) — case-insensitive substring filter applied
|
|
174
190
|
// in-memory AFTER the upstream fetch (no per-name REST call).
|
|
175
191
|
// Min 2 chars at the schema boundary blocks the single-char
|
|
@@ -181,7 +197,7 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
181
197
|
.describe("Case-insensitive substring filter on connection name. Applied in-memory after the upstream fetch — does NOT change the REST query. Must be 2+ characters; single-char probes are blocked. Combine with `source` for narrower results."),
|
|
182
198
|
},
|
|
183
199
|
annotations: readOnly({ title: "List Connections", openWorld: true }),
|
|
184
|
-
}, async ({ source, limit, name_query }) => {
|
|
200
|
+
}, async ({ source, limit, name_query, fields }) => {
|
|
185
201
|
const r = await request("/connections");
|
|
186
202
|
if (!r.success) {
|
|
187
203
|
return restErrorResult(r, { source, limit, name_query }, { message: "connection list failed" });
|
|
@@ -199,18 +215,41 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
199
215
|
// subset, not the haystack. See `filterByNameQuery` JSDoc for the
|
|
200
216
|
// case-folding + undefined-skip contract shared with flow_list.
|
|
201
217
|
items = filterByNameQuery(items, name_query);
|
|
218
|
+
// F198 — capture the matched total BEFORE the slice so the footer can
|
|
219
|
+
// report exactly how many rows were dropped by the limit.
|
|
220
|
+
const totalMatched = items.length;
|
|
202
221
|
items = items.slice(0, limit);
|
|
222
|
+
// F198 — opt into the full detail dump only when `fields` is supplied.
|
|
223
|
+
// Any non-empty `fields` array (typically ['all']) widens the table to
|
|
224
|
+
// the full column set; the default stays the lean projection.
|
|
225
|
+
const wantFull = Array.isArray(fields) && fields.length > 0;
|
|
226
|
+
// F198 — the honest "N more — filter with name_query or raise limit"
|
|
227
|
+
// note fires whenever the limit actually dropped matched rows. The
|
|
228
|
+
// compact-render note (withOverflowFooter) still rides along for the
|
|
229
|
+
// truncated-text case.
|
|
230
|
+
const overflowNote = truncatedByLimitNote(totalMatched, limit, "connections");
|
|
231
|
+
const baseFooter = overflowNote
|
|
232
|
+
? `${CONNECTION_LIST_NEXT_STEPS}\n${overflowNote}`
|
|
233
|
+
: CONNECTION_LIST_NEXT_STEPS;
|
|
203
234
|
// W3.1 — tableResult: ASCII table for the LLM + typed DataTable payload
|
|
204
|
-
// for the Rich Card.
|
|
205
|
-
//
|
|
206
|
-
// footer carries the next-step guidance.
|
|
235
|
+
// for the Rich Card. Default = lean projection (id/name/source/host/
|
|
236
|
+
// health); `fields` = full dump. IA-7: id stays llmOnly. IA-10: the
|
|
237
|
+
// footer carries the next-step guidance + the F198 overflow note.
|
|
207
238
|
return tableResult(toRows(items), {
|
|
208
|
-
columns: CONNECTION_TABLE_COLUMNS,
|
|
239
|
+
columns: wantFull ? CONNECTION_FULL_COLUMNS : CONNECTION_TABLE_COLUMNS,
|
|
209
240
|
compactColumns: CONNECTION_COMPACT_COLUMNS,
|
|
210
|
-
map: mapConnectionRow,
|
|
241
|
+
map: wantFull ? mapConnectionRowFull : mapConnectionRow,
|
|
211
242
|
compactMap: compactConnectionRow,
|
|
212
|
-
|
|
213
|
-
|
|
243
|
+
// F198 — the "N more" overflow note rides in the HEADER (rendered
|
|
244
|
+
// first, never truncated) so it survives even when a 21+ row table
|
|
245
|
+
// compacts to 2000 chars and drops the footer. The footer keeps the
|
|
246
|
+
// next-step guidance + the compact-render note for the non-overflow
|
|
247
|
+
// truncation case.
|
|
248
|
+
header: (n) => (overflowNote ? `${n} connections — ${overflowNote}` : `${n} connections`),
|
|
249
|
+
// Minor (2026-06-10): at 21+ rows the toolkit compacts the LLM-visible
|
|
250
|
+
// table to 2000 chars — append an honest "use filters" note so the
|
|
251
|
+
// agent does not mistake a truncated list for the full set.
|
|
252
|
+
footer: withOverflowFooter(baseFooter, items.length, "connections"),
|
|
214
253
|
});
|
|
215
254
|
});
|
|
216
255
|
// ── apimapper_connection_get ───────────────────────────────────────
|
|
@@ -255,26 +294,44 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
255
294
|
// W3.1 — detailResult: grouped key-value detail for the Rich Card.
|
|
256
295
|
// IA-7: opaque IDs are copyable code entries. IA-10: a dedicated
|
|
257
296
|
// "Next steps" group carries the follow-up calls.
|
|
258
|
-
|
|
297
|
+
// F9 (2026-06-09): run the same camelCase→snake_case wire bridge the
|
|
298
|
+
// list path applies (F-LS-04) — buildConnectionDetail reads
|
|
299
|
+
// c.auth_type/credential_id; the raw wire shape carries authType/
|
|
300
|
+
// credentialId, so without this the detail rendered auth "none"
|
|
301
|
+
// while the list showed the real auth for the SAME connection.
|
|
302
|
+
return buildConnectionDetail(id, normalizeConnectionFromWire(conn));
|
|
259
303
|
});
|
|
260
304
|
// ── apimapper_connection_create ────────────────────────────────────
|
|
261
305
|
server.registerTool("apimapper_connection_create", {
|
|
262
306
|
title: "Create Connection",
|
|
263
307
|
description: "Create a custom connection. **Use ONLY when no library template matches your target API.** " +
|
|
264
|
-
"First call `apimapper_library_featured()` or `apimapper_library_list({
|
|
308
|
+
"First call `apimapper_library_featured()` or `apimapper_library_list({ search: '<api-name>' })` (min 3 chars) — " +
|
|
265
309
|
"Google Sheets, Calendly, Notion, Airtable, GitHub, Pexels, Unsplash, OpenWeatherMap, REST Countries, " +
|
|
266
310
|
"Google Drive/Docs/Slides/Tasks all have ready-to-use templates with auto-configured auth and " +
|
|
267
311
|
"field-detection. Library activation via `apimapper_library_activate({ id })` is the canonical " +
|
|
268
312
|
"customer path; connection_create is the fallback for niche or unknown APIs. " +
|
|
269
313
|
"The server enforces this: a custom create on an API already covered by a curated template is " +
|
|
270
314
|
"blocked with a 409 naming the template to activate — pass `acknowledge_no_library: true` to " +
|
|
271
|
-
"intentionally override that block." +
|
|
315
|
+
"intentionally override that block. " +
|
|
316
|
+
// A16 (2026-06-10): the guard matches on the HOST, so a genuinely
|
|
317
|
+
// uncovered endpoint never blocks — do NOT set acknowledge_no_library
|
|
318
|
+
// pre-emptively.
|
|
319
|
+
"The guard matches on the request HOST against the curated library; a connection to an " +
|
|
320
|
+
"uncovered host is NEVER blocked and needs no `acknowledge_no_library` — only set that flag " +
|
|
321
|
+
"AFTER you actually receive a 409." +
|
|
322
|
+
"\n\nNote: connection auth_type spellings differ from apimapper_credential_create's: " +
|
|
323
|
+
"here it is `basic` / `oauth2`, whereas credential_create uses `basic_auth` / `oauth2_code` / `oauth2_cc`. " +
|
|
324
|
+
"Use the spelling for the tool you are calling." +
|
|
272
325
|
"\n\nExample:\n apimapper_connection_create({ name: 'Pexels API', endpoint: 'https://api.pexels.com/v1/search', method: 'GET', auth_type: 'bearer', items_path: 'photos' })",
|
|
273
326
|
inputSchema: {
|
|
274
327
|
name: z.string().min(1).describe('Connection name (e.g., "My Blog API")'),
|
|
275
328
|
endpoint: z.string().describe('Endpoint path or full URL (e.g., "https://api.example.com/posts")'),
|
|
276
329
|
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET").describe("HTTP method"),
|
|
277
|
-
auth_type: z
|
|
330
|
+
auth_type: z
|
|
331
|
+
.enum(["none", "api_key", "bearer", "basic", "oauth2"])
|
|
332
|
+
.default("none")
|
|
333
|
+
.describe("Auth type (snake_case wire key). " +
|
|
334
|
+
"NB: credential_create spells these `basic_auth` (here `basic`) and `oauth2_code`/`oauth2_cc` (here `oauth2`)."),
|
|
278
335
|
credential_id: z.string().optional().describe("Credential ID (snake_case wire key) if auth_type requires one"),
|
|
279
336
|
items_path: z.string().optional().describe('JSONPath to items array (snake_case wire key)'),
|
|
280
337
|
cache_ttl: z.number().int().min(0).default(3600).describe("Cache TTL in seconds (snake_case wire key)"),
|
|
@@ -288,9 +345,19 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
288
345
|
acknowledge_no_library: z.boolean().optional().describe("Set true ONLY when no library template fits and you intentionally need a custom connection. " +
|
|
289
346
|
"The server blocks custom creates on APIs that already have a curated library template; " +
|
|
290
347
|
"this flag is the audited override. Prefer apimapper_library_activate({ id }) when a template exists."),
|
|
348
|
+
// Phase 2.7 (change protocol): the action is IMPLICIT
|
|
349
|
+
// (`connection.created`) — only `summary` is needed; the handler
|
|
350
|
+
// defaults the action. Threaded into the POST body as
|
|
351
|
+
// `change: { summary }` (conditional, never a top-level key).
|
|
352
|
+
summary: z
|
|
353
|
+
.string()
|
|
354
|
+
.max(280)
|
|
355
|
+
.optional()
|
|
356
|
+
.describe('Optional one-line summary of this change for the change history ' +
|
|
357
|
+
'(e.g., "Added a Pexels photo connection").'),
|
|
291
358
|
},
|
|
292
359
|
annotations: creating({ title: "Create Connection", openWorld: true }),
|
|
293
|
-
}, async (input) => {
|
|
360
|
+
}, async ({ summary, ...input }) => {
|
|
294
361
|
// W3.6 — when the connection's auth_type needs a credential and none is
|
|
295
362
|
// given: exactly 1 stored credential → auto-pick; >1 → elicitChoice;
|
|
296
363
|
// elicitChoice null (unsupported client / declined) → structured
|
|
@@ -314,31 +381,39 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
314
381
|
resolvedInput = { ...input, credential_id: creds[0].id };
|
|
315
382
|
}
|
|
316
383
|
else {
|
|
384
|
+
// Chat-first disambiguation (decision 2026-06-15): the native
|
|
385
|
+
// elicitation picker renders as a clunky collapsed TUI in some
|
|
386
|
+
// hosts. For an ordinary credential pick the assistant asking in
|
|
387
|
+
// plain chat is nicer, so we skip the picker and return the
|
|
388
|
+
// structured candidate-list error directly — its `candidates`
|
|
389
|
+
// carry id+label, so the assistant sees the names and can ask.
|
|
390
|
+
// (The OAuth authorize path KEEPS the picker: explicit consent on
|
|
391
|
+
// WHICH credential to authorize is security-relevant.)
|
|
317
392
|
const candidates = creds.map((c) => ({
|
|
318
393
|
id: c.id,
|
|
319
394
|
label: c.name,
|
|
320
395
|
}));
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
paramName: "credential_id",
|
|
329
|
-
what: "credentials",
|
|
330
|
-
candidates,
|
|
331
|
-
extraDetails: { name: input.name, auth_type: input.auth_type },
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
resolvedInput = { ...input, credential_id: picked };
|
|
396
|
+
return ambiguityFallbackError({
|
|
397
|
+
code: "credential_ambiguous",
|
|
398
|
+
paramName: "credential_id",
|
|
399
|
+
what: "credentials",
|
|
400
|
+
candidates,
|
|
401
|
+
extraDetails: { name: input.name, auth_type: input.auth_type },
|
|
402
|
+
});
|
|
335
403
|
}
|
|
336
404
|
}
|
|
405
|
+
// Phase 2.7 — implicit action (`connection.created`); attach `change:
|
|
406
|
+
// { summary }` only when supplied so the body is unchanged otherwise.
|
|
407
|
+
// `summary` was destructured out of `input` above, so it never leaks as a
|
|
408
|
+
// top-level connection column.
|
|
409
|
+
const createBody = summary !== undefined
|
|
410
|
+
? { ...resolvedInput, change: { summary } }
|
|
411
|
+
: resolvedInput;
|
|
337
412
|
// PHP fromControllerResponse(_, 'connection', 201) → {success, connection:{…}}.
|
|
338
413
|
// Audit: F-A1-02.
|
|
339
414
|
const r = await request("/connections", {
|
|
340
415
|
method: "POST",
|
|
341
|
-
body: JSON.stringify(
|
|
416
|
+
body: JSON.stringify(createBody),
|
|
342
417
|
});
|
|
343
418
|
if (!r.success) {
|
|
344
419
|
// M1 (MCP-relay audit) — wire-shape parity with the admin-ui. On the
|
|
@@ -388,7 +463,7 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
388
463
|
name: conn?.name,
|
|
389
464
|
source: conn?.source,
|
|
390
465
|
next_steps: [
|
|
391
|
-
"
|
|
466
|
+
'Verify reachability via apimapper_advanced({ tool: "apimapper_connection_test", arguments: { id } }), then build a flow with apimapper_flow_setup_with_sources (or apimapper_advanced({ tool: "apimapper_flow_create" })).',
|
|
392
467
|
],
|
|
393
468
|
}, false, { maxChars: 2000 });
|
|
394
469
|
});
|
|
@@ -411,14 +486,28 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
411
486
|
// .strict() allow-list here risks rejecting a legit field the
|
|
412
487
|
// server accepts, so the schema stays permissive.
|
|
413
488
|
'Unknown keys are dropped by the server (column-whitelisted on write).'),
|
|
489
|
+
// Phase 2.7 (change protocol): the action is IMPLICIT
|
|
490
|
+
// (`connection.updated`) — only `summary` is needed; the handler
|
|
491
|
+
// defaults the action. Threaded into the PUT body as
|
|
492
|
+
// `change: { summary }` (sibling of the patch fields).
|
|
493
|
+
summary: z
|
|
494
|
+
.string()
|
|
495
|
+
.max(280)
|
|
496
|
+
.optional()
|
|
497
|
+
.describe('Optional one-line summary of this change (e.g., "Pointed the endpoint at /v2/posts").'),
|
|
414
498
|
},
|
|
415
499
|
annotations: mutating({ title: "Update Connection", openWorld: true }),
|
|
416
|
-
}, async ({ id, patch }) => {
|
|
500
|
+
}, async ({ id, patch, summary }) => {
|
|
501
|
+
// Phase 2.7 — implicit action (`connection.updated`); attach `change:
|
|
502
|
+
// { summary }` only when supplied so the body is unchanged otherwise.
|
|
503
|
+
const updateBody = summary !== undefined
|
|
504
|
+
? { ...patch, change: { summary } }
|
|
505
|
+
: patch;
|
|
417
506
|
// PHP fromControllerResponse(_, 'connection') → {success, connection:{…}}.
|
|
418
507
|
// Audit: F-A1-03.
|
|
419
508
|
const r = await request(`/connections/${encodeURIComponent(id)}`, {
|
|
420
509
|
method: "PUT",
|
|
421
|
-
body: JSON.stringify(
|
|
510
|
+
body: JSON.stringify(updateBody),
|
|
422
511
|
});
|
|
423
512
|
if (!r.success) {
|
|
424
513
|
return restErrorResult(r, { id }, { message: "connection update failed" });
|
|
@@ -432,7 +521,7 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
432
521
|
id: conn?.id,
|
|
433
522
|
name: conn?.name,
|
|
434
523
|
next_steps: [
|
|
435
|
-
|
|
524
|
+
'Re-test if endpoint/auth changed via apimapper_advanced({ tool: "apimapper_connection_test", arguments: { id } }); flows referencing this connection may need to be republished via apimapper_flow_full_recompile_publish.',
|
|
436
525
|
],
|
|
437
526
|
}, false, { maxChars: 2000 });
|
|
438
527
|
});
|
|
@@ -535,7 +624,7 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
535
624
|
name: conn?.name,
|
|
536
625
|
applied_patch_keys: Object.keys(patch),
|
|
537
626
|
next_steps: [
|
|
538
|
-
|
|
627
|
+
'Verify via apimapper_advanced({ tool: "apimapper_connection_get", arguments: { id } }); re-test via apimapper_advanced({ tool: "apimapper_connection_test", arguments: { id } }) before re-publishing flows.',
|
|
539
628
|
],
|
|
540
629
|
}, false, { maxChars: 2000 });
|
|
541
630
|
});
|
|
@@ -551,14 +640,29 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
551
640
|
.boolean()
|
|
552
641
|
.default(false)
|
|
553
642
|
.describe("Must be true to execute. On confirm:false, returns a preview."),
|
|
643
|
+
// Phase 2.7 (change protocol): the action is IMPLICIT
|
|
644
|
+
// (`connection.deleted`) — only `summary` is needed; the handler
|
|
645
|
+
// defaults the action. Attached to the DELETE body as
|
|
646
|
+
// `change: { summary }` (the delete otherwise has no body), and only on
|
|
647
|
+
// the real delete — the preview branch never records a change.
|
|
648
|
+
summary: z
|
|
649
|
+
.string()
|
|
650
|
+
.max(280)
|
|
651
|
+
.optional()
|
|
652
|
+
.describe('Optional one-line summary of this deletion (e.g., "Removed the unused Pexels connection").'),
|
|
554
653
|
},
|
|
555
654
|
annotations: destructive({ title: "Delete Connection", openWorld: true }),
|
|
556
|
-
}, async ({ id, confirm }) => {
|
|
655
|
+
}, async ({ id, confirm, summary }) => {
|
|
557
656
|
if (!confirm) {
|
|
558
657
|
// PHP fromControllerResponse(_, 'connection') wraps as
|
|
559
658
|
// {success, connection:{…}}. Unwrap defensively. Audit: F-A1-04.
|
|
560
659
|
const preview = await request(`/connections/${encodeURIComponent(id)}`);
|
|
561
|
-
const
|
|
660
|
+
const rawConn = preview.success ? unwrapEntity(preview.data, "connection") : undefined;
|
|
661
|
+
// F9 (2026-06-09): same wire-shape bridge as connection_get — the
|
|
662
|
+
// preview target read auth_type off the raw camelCase wire shape.
|
|
663
|
+
const conn = rawConn
|
|
664
|
+
? normalizeConnectionFromWire(rawConn)
|
|
665
|
+
: undefined;
|
|
562
666
|
return formatResult({
|
|
563
667
|
preview: true,
|
|
564
668
|
warning: "DESTRUCTIVE — Connection delete cannot be undone. Dependent flows will break.",
|
|
@@ -574,7 +678,15 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
574
678
|
instruction: "Ask user to confirm, then call again with confirm: true.",
|
|
575
679
|
});
|
|
576
680
|
}
|
|
577
|
-
|
|
681
|
+
// Phase 2.7 — implicit action (`connection.deleted`); attach `change:
|
|
682
|
+
// { summary }` only when supplied so the request is unchanged otherwise.
|
|
683
|
+
const r = await request(`/connections/${encodeURIComponent(id)}`, summary !== undefined
|
|
684
|
+
? {
|
|
685
|
+
method: "DELETE",
|
|
686
|
+
body: JSON.stringify({ change: { summary } }),
|
|
687
|
+
headers: { "Content-Type": "application/json" },
|
|
688
|
+
}
|
|
689
|
+
: { method: "DELETE" });
|
|
578
690
|
if (!r.success) {
|
|
579
691
|
return restErrorResult(r, { id }, { message: "connection delete failed" });
|
|
580
692
|
}
|
|
@@ -672,6 +784,11 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
672
784
|
"the response items in-memory — this does NOT trigger multi-page fetch upstream (the " +
|
|
673
785
|
"request is single-shot). Use `fields` to project each item down to a whitelist of leaf " +
|
|
674
786
|
"keys, cutting response size on sparse queries." +
|
|
787
|
+
"\n\nF144: if a library/template connection returns a 'Please configure …' / 'Please select an " +
|
|
788
|
+
"endpoint' diagnostic, the missing values are TEMPLATE FIELDS — supply them here via " +
|
|
789
|
+
"`template_fields` (e.g., { spreadsheet_id: '…' }) rather than re-creating the connection or " +
|
|
790
|
+
"starting a new OAuth flow. A connection whose template_fields are already resolved IS " +
|
|
791
|
+
"configured; inspect them with apimapper_connection_get({ id })." +
|
|
675
792
|
"\n\nExample:\n" +
|
|
676
793
|
" apimapper_connection_data({ id: 'con_abc123', limit: 10 }) // first 10 items\n" +
|
|
677
794
|
" apimapper_connection_data({ id: 'con_abc123', limit: 5, offset: 10 }) // skip 10, take 5\n" +
|
|
@@ -684,7 +801,11 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
684
801
|
.trim()
|
|
685
802
|
.min(1)
|
|
686
803
|
.optional()
|
|
687
|
-
.describe('
|
|
804
|
+
.describe('Named endpoint SELECTION — NOT a URL. This is the label of a configured endpoint on the ' +
|
|
805
|
+
'connection (e.g., "Scheduled Events", "Get Values"), the same names connection_create exposes ' +
|
|
806
|
+
'as endpoints[]. List them via apimapper_connection_get({ id }). Empty-string and ' +
|
|
807
|
+
'whitespace-only values are rejected at the schema boundary to prevent "?endpoint=" query ' +
|
|
808
|
+
"garbage; omit the field entirely to use the connection default."),
|
|
688
809
|
template_fields: z
|
|
689
810
|
.record(z.string(), z.union([z.string(), z.number().finite(), z.boolean()]))
|
|
690
811
|
.optional()
|
|
@@ -726,7 +847,11 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
726
847
|
// implicitly, but the explicit guard documents the contract and keeps
|
|
727
848
|
// TypeScript happy under the wider schema.
|
|
728
849
|
if (template_fields) {
|
|
729
|
-
|
|
850
|
+
// A5 (2026-06-10): normalize a pasted Drive/Sheets URL in
|
|
851
|
+
// template_fields.spreadsheet_id to the bare ID before it becomes a
|
|
852
|
+
// query param — the {{spreadsheet_id}} placeholder needs the bare id.
|
|
853
|
+
const normalizedFields = normalizeResourceIdFields(template_fields);
|
|
854
|
+
for (const [k, v] of Object.entries(normalizedFields)) {
|
|
730
855
|
params.set(k, typeof v === "string" ? v : String(v));
|
|
731
856
|
}
|
|
732
857
|
}
|
|
@@ -789,6 +914,25 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
789
914
|
? pickFields(item, fields)
|
|
790
915
|
: item);
|
|
791
916
|
}
|
|
917
|
+
// F198 (Desktop-E2E #2 forensics, 2026-06-12): a 0-row read used to ship
|
|
918
|
+
// the full {ok, shape, item_count, payload:[], body, content_type}
|
|
919
|
+
// envelope — pure overhead when there is nothing to show. Collapse it to
|
|
920
|
+
// a compact 1-liner that names the likely culprits (template_fields /
|
|
921
|
+
// range) and forwards the upstream diagnostic verbatim when present.
|
|
922
|
+
// Only the array-empty case collapses; an empty object/scalar payload is
|
|
923
|
+
// a different (rare) shape and keeps the full envelope.
|
|
924
|
+
if (Array.isArray(trim.payload) && trim.itemCount === 0) {
|
|
925
|
+
const diagnosticMsg = diagnostic && typeof diagnostic.message === "string"
|
|
926
|
+
? ` ${diagnostic.message}`
|
|
927
|
+
: "";
|
|
928
|
+
return formatResult({
|
|
929
|
+
ok: true,
|
|
930
|
+
item_count: 0,
|
|
931
|
+
note: "0 rows — check template_fields (e.g. spreadsheet_id / range) or the connection " +
|
|
932
|
+
"endpoint; sample the config with apimapper_connection_get({ id })." +
|
|
933
|
+
diagnosticMsg,
|
|
934
|
+
}, false, { maxChars: 600 });
|
|
935
|
+
}
|
|
792
936
|
return formatResult({
|
|
793
937
|
ok: true,
|
|
794
938
|
// F-38 — surface a stable, narrow shape enum instead of typeof.
|
|
@@ -817,12 +961,14 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
817
961
|
title: "List Connection Resources",
|
|
818
962
|
description: "List browseable resources on a connection (drive files, sheets, IG media). " +
|
|
819
963
|
"REST contract: `field` (resource-picker field name) + optional `query` filter." +
|
|
820
|
-
|
|
964
|
+
// F213: gateway-only tool — show the call routed THROUGH the read
|
|
965
|
+
// gateway so the first live invocation doesn't fail invalid_arguments.
|
|
966
|
+
"\n\nExample (gateway-routed):\n apimapper_advanced_read({ tool: \"apimapper_connection_resources\", arguments: { id: \"con_drive_xyz\", field: \"fileId\", query: \"budget\" } })",
|
|
821
967
|
inputSchema: {
|
|
822
968
|
id: z.string().describe("Connection ID. Use apimapper_connection_list to find."),
|
|
823
969
|
field: z
|
|
824
970
|
.string()
|
|
825
|
-
.describe('
|
|
971
|
+
.describe('REQUIRED — resource-picker field name (e.g., "spreadsheet_id", "drive_file_id"). REST wire-key: field.'),
|
|
826
972
|
// rc.10 A4 (2026-05-19): min(3) blocks the single-char "p", "j", "u"
|
|
827
973
|
// probing pattern observed in Maria-walkthrough logs.
|
|
828
974
|
// rc.13 W1.17 (F-29): Master-Doc-Drift — Master requested min(2), kept
|
|
@@ -868,21 +1014,29 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
868
1014
|
server.registerTool("apimapper_connection_pipeline_update", {
|
|
869
1015
|
title: "Update Connection Pipeline",
|
|
870
1016
|
description: "Update the connection's pipeline (endpoints, default_params, default_headers, headers, items_path, items_shape, body, body_type, graphql_variables, response_type)." +
|
|
1017
|
+
"\n\nWorks on connections WITHOUT a stored pipeline: the protocol keys version/enabled/steps are NOT required — send only the connection-column keys you care about (F184)." +
|
|
1018
|
+
"\n\nPARTIAL UPDATES ARE SAFE (F145) — send only the keys you want to change:" +
|
|
1019
|
+
"\n • Object-map keys (default_params, default_headers, headers, graphql_variables) MERGE into the stored map: changing one entry preserves the others. Pass a value of null to delete a single stored entry (e.g. { default_params: { stale_key: null } })." +
|
|
1020
|
+
"\n • Scalar keys (items_path, items_shape, body, body_type, response_type) replace their single value." +
|
|
1021
|
+
"\n • List keys (endpoints) are REPLACED wholesale (positional merge is ambiguous) — resend every endpoint you want to keep. If a replace drops endpoints, the response carries a `warnings` array naming what was removed." +
|
|
871
1022
|
"\n\nitems_shape values: 'flat' (default, list of objects), 'auto' (best-effort detection), 'headers_rows' (pivot 2D array [[h1,h2],[v1,v2],...] into named-object rows — use this for Google-Sheets-style responses), 'object_keys' (turn a keyed object into a list)." +
|
|
872
|
-
"\n\nExample:\n apimapper_connection_pipeline_update({ id: 'con_abc123', pipeline: { items_path: 'photos', items_shape: 'auto', default_params: { per_page: '20' } } })" +
|
|
1023
|
+
"\n\nExample (partial — preserves any other stored params):\n apimapper_connection_pipeline_update({ id: 'con_abc123', pipeline: { items_path: 'photos', items_shape: 'auto', default_params: { per_page: '20' } } })" +
|
|
873
1024
|
"\n\nGoogle-Sheets 2D array example:\n apimapper_connection_pipeline_update({ id: 'con_sheet', pipeline: { items_shape: 'headers_rows' } })",
|
|
874
1025
|
inputSchema: {
|
|
875
1026
|
id: z.string().describe("Connection ID. Use apimapper_connection_list to find."),
|
|
876
1027
|
pipeline: z
|
|
877
1028
|
.record(z.string(), z.unknown())
|
|
878
|
-
.describe('Pipeline JSON. Accepts connection-column keys: endpoints, default_params, default_headers, headers, items_path, items_shape, body, body_type, graphql_variables, response_type. ' +
|
|
1029
|
+
.describe('Pipeline JSON (partial allowed). Accepts connection-column keys: endpoints, default_params, default_headers, headers, items_path, items_shape, body, body_type, graphql_variables, response_type. ' +
|
|
1030
|
+
'Object-map keys (default_params/default_headers/headers/graphql_variables) MERGE into stored values (null deletes one entry); scalar keys replace; list keys (endpoints) replace wholesale and report dropped entries in a warnings array. ' +
|
|
879
1031
|
'items_shape options: "flat" | "auto" | "headers_rows" | "object_keys". ' +
|
|
880
1032
|
'Example: {"items_path":"data","items_shape":"headers_rows"}.'),
|
|
881
1033
|
},
|
|
882
1034
|
annotations: mutating({ title: "Update Connection Pipeline", openWorld: true }),
|
|
883
1035
|
}, async ({ id, pipeline }) => {
|
|
884
1036
|
// PHP ConnectionPipelineHandler uses fromControllerResponse(_, 'connection')
|
|
885
|
-
// → {success, connection:{…}}.
|
|
1037
|
+
// → {success, connection:{…}}. F145: a list-key (endpoints) full-replace
|
|
1038
|
+
// also carries a top-level `warnings` array naming dropped entries.
|
|
1039
|
+
// Unwrap defensively. Audit: F-A1-07.
|
|
886
1040
|
const r = await request(`/connections/${encodeURIComponent(id)}/pipeline`, {
|
|
887
1041
|
method: "PUT",
|
|
888
1042
|
body: JSON.stringify(pipeline),
|
|
@@ -891,7 +1045,20 @@ export function registerConnectionTools(server, elicitation) {
|
|
|
891
1045
|
return restErrorResult(r, { id }, { message: "connection pipeline update failed" });
|
|
892
1046
|
}
|
|
893
1047
|
const conn = unwrapEntity(r.data, "connection");
|
|
894
|
-
|
|
1048
|
+
// Surface F145 dropped-entry warnings so the agent never silently loses
|
|
1049
|
+
// endpoints on a partial replace.
|
|
1050
|
+
const warnings = r.data && typeof r.data === "object" && Array.isArray(r.data.warnings)
|
|
1051
|
+
? r.data.warnings
|
|
1052
|
+
: undefined;
|
|
1053
|
+
const payload = {
|
|
1054
|
+
updated: true,
|
|
1055
|
+
id: conn?.id,
|
|
1056
|
+
name: conn?.name,
|
|
1057
|
+
};
|
|
1058
|
+
if (warnings && warnings.length > 0) {
|
|
1059
|
+
payload.warnings = warnings;
|
|
1060
|
+
}
|
|
1061
|
+
return formatResult(payload, false, { maxChars: 2000 });
|
|
895
1062
|
});
|
|
896
1063
|
}
|
|
897
1064
|
//# sourceMappingURL=connections.js.map
|