@wootsup/mcp 0.1.0-rc.9 → 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 +36 -32
- 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.d.ts +19 -0
- package/dist/index.js +163 -22
- package/dist/index.js.map +1 -1
- package/dist/install-skill.js +1 -1
- package/dist/modules/apimapper/cache.d.ts +2 -2
- package/dist/modules/apimapper/cache.js +119 -29
- package/dist/modules/apimapper/cache.js.map +1 -1
- package/dist/modules/apimapper/client.d.ts +102 -1
- package/dist/modules/apimapper/client.js +631 -297
- package/dist/modules/apimapper/client.js.map +1 -1
- package/dist/modules/apimapper/connections-format.d.ts +51 -0
- package/dist/modules/apimapper/connections-format.js +261 -0
- package/dist/modules/apimapper/connections-format.js.map +1 -0
- package/dist/modules/apimapper/connections-trim.d.ts +82 -0
- package/dist/modules/apimapper/connections-trim.js +224 -0
- package/dist/modules/apimapper/connections-trim.js.map +1 -0
- package/dist/modules/apimapper/connections.d.ts +14 -2
- package/dist/modules/apimapper/connections.js +612 -153
- 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-format.d.ts +21 -0
- package/dist/modules/apimapper/credentials-format.js +145 -0
- package/dist/modules/apimapper/credentials-format.js.map +1 -0
- package/dist/modules/apimapper/credentials.d.ts +12 -2
- package/dist/modules/apimapper/credentials.js +226 -73
- package/dist/modules/apimapper/credentials.js.map +1 -1
- package/dist/modules/apimapper/diagnose.d.ts +54 -2
- package/dist/modules/apimapper/diagnose.js +213 -12
- package/dist/modules/apimapper/diagnose.js.map +1 -1
- package/dist/modules/apimapper/elicitation.d.ts +54 -0
- package/dist/modules/apimapper/elicitation.js +90 -0
- package/dist/modules/apimapper/elicitation.js.map +1 -0
- package/dist/modules/apimapper/flows-format.d.ts +50 -0
- package/dist/modules/apimapper/flows-format.js +318 -0
- package/dist/modules/apimapper/flows-format.js.map +1 -0
- package/dist/modules/apimapper/flows.d.ts +13 -2
- package/dist/modules/apimapper/flows.js +312 -122
- package/dist/modules/apimapper/flows.js.map +1 -1
- package/dist/modules/apimapper/gateway/advanced-tool.d.ts +9 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js +265 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -0
- package/dist/modules/apimapper/gateway/capturing-server.d.ts +81 -0
- package/dist/modules/apimapper/gateway/capturing-server.js +87 -0
- package/dist/modules/apimapper/gateway/capturing-server.js.map +1 -0
- package/dist/modules/apimapper/gateway/essentials.d.ts +4 -0
- package/dist/modules/apimapper/gateway/essentials.js +35 -0
- package/dist/modules/apimapper/gateway/essentials.js.map +1 -0
- package/dist/modules/apimapper/gateway/test-support.d.ts +17 -0
- package/dist/modules/apimapper/gateway/test-support.js +43 -0
- package/dist/modules/apimapper/gateway/test-support.js.map +1 -0
- package/dist/modules/apimapper/get-skill.d.ts +3 -3
- package/dist/modules/apimapper/get-skill.js +47 -7
- package/dist/modules/apimapper/get-skill.js.map +1 -1
- package/dist/modules/apimapper/graph-builder.js +1 -1
- package/dist/modules/apimapper/graph-builder.js.map +1 -1
- package/dist/modules/apimapper/graph.d.ts +2 -2
- package/dist/modules/apimapper/graph.js +170 -35
- package/dist/modules/apimapper/graph.js.map +1 -1
- package/dist/modules/apimapper/index.d.ts +17 -1
- package/dist/modules/apimapper/index.js +68 -17
- package/dist/modules/apimapper/index.js.map +1 -1
- package/dist/modules/apimapper/inspect.d.ts +3 -2
- package/dist/modules/apimapper/inspect.js +97 -13
- package/dist/modules/apimapper/inspect.js.map +1 -1
- package/dist/modules/apimapper/library.d.ts +2 -2
- package/dist/modules/apimapper/library.js +665 -80
- package/dist/modules/apimapper/library.js.map +1 -1
- package/dist/modules/apimapper/license-format.d.ts +22 -0
- package/dist/modules/apimapper/license-format.js +149 -0
- package/dist/modules/apimapper/license-format.js.map +1 -0
- package/dist/modules/apimapper/license.d.ts +16 -2
- package/dist/modules/apimapper/license.js +62 -38
- package/dist/modules/apimapper/license.js.map +1 -1
- package/dist/modules/apimapper/local-sources.d.ts +2 -2
- package/dist/modules/apimapper/local-sources.js +44 -30
- package/dist/modules/apimapper/local-sources.js.map +1 -1
- package/dist/modules/apimapper/misc.d.ts +30 -2
- package/dist/modules/apimapper/misc.js +114 -49
- package/dist/modules/apimapper/misc.js.map +1 -1
- package/dist/modules/apimapper/node-schema.d.ts +52 -0
- package/dist/modules/apimapper/node-schema.js +70 -2
- package/dist/modules/apimapper/node-schema.js.map +1 -1
- package/dist/modules/apimapper/normalizers.d.ts +1 -0
- package/dist/modules/apimapper/normalizers.js +51 -0
- package/dist/modules/apimapper/normalizers.js.map +1 -1
- package/dist/modules/apimapper/onboarding.d.ts +78 -3
- package/dist/modules/apimapper/onboarding.js +428 -26
- package/dist/modules/apimapper/onboarding.js.map +1 -1
- package/dist/modules/apimapper/read-cache.d.ts +31 -2
- package/dist/modules/apimapper/read-cache.js +20 -6
- package/dist/modules/apimapper/read-cache.js.map +1 -1
- package/dist/modules/apimapper/render/_shared.d.ts +24 -0
- package/dist/modules/apimapper/render/_shared.js +84 -0
- package/dist/modules/apimapper/render/_shared.js.map +1 -0
- package/dist/modules/apimapper/render/dag.d.ts +18 -0
- package/dist/modules/apimapper/render/dag.js +70 -0
- package/dist/modules/apimapper/render/dag.js.map +1 -0
- package/dist/modules/apimapper/render/index.d.ts +2 -0
- package/dist/modules/apimapper/render/index.js +112 -0
- package/dist/modules/apimapper/render/index.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/chart-bar.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/chart-bar.js +70 -0
- package/dist/modules/apimapper/render/renderers/chart-bar.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/chart-line.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/chart-line.js +71 -0
- package/dist/modules/apimapper/render/renderers/chart-line.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/diff.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/diff.js +154 -0
- package/dist/modules/apimapper/render/renderers/diff.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/flow-diagram.d.ts +1 -0
- package/dist/modules/apimapper/render/renderers/flow-diagram.js +180 -0
- package/dist/modules/apimapper/render/renderers/flow-diagram.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/json-tree.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/json-tree.js +87 -0
- package/dist/modules/apimapper/render/renderers/json-tree.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/schema-diagram.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/schema-diagram.js +83 -0
- package/dist/modules/apimapper/render/renderers/schema-diagram.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/table.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/table.js +75 -0
- package/dist/modules/apimapper/render/renderers/table.js.map +1 -0
- package/dist/modules/apimapper/render/schemas.d.ts +23 -0
- package/dist/modules/apimapper/render/schemas.js +56 -0
- package/dist/modules/apimapper/render/schemas.js.map +1 -0
- package/dist/modules/apimapper/render/secret-masking.d.ts +5 -0
- package/dist/modules/apimapper/render/secret-masking.js +51 -0
- package/dist/modules/apimapper/render/secret-masking.js.map +1 -0
- package/dist/modules/apimapper/render/sidecar.d.ts +21 -0
- package/dist/modules/apimapper/render/sidecar.js +66 -0
- package/dist/modules/apimapper/render/sidecar.js.map +1 -0
- package/dist/modules/apimapper/render/token-cap.d.ts +21 -0
- package/dist/modules/apimapper/render/token-cap.js +57 -0
- package/dist/modules/apimapper/render/token-cap.js.map +1 -0
- package/dist/modules/apimapper/schema.d.ts +2 -2
- package/dist/modules/apimapper/schema.js +92 -33
- package/dist/modules/apimapper/schema.js.map +1 -1
- package/dist/modules/apimapper/settings-format.d.ts +23 -0
- package/dist/modules/apimapper/settings-format.js +135 -0
- package/dist/modules/apimapper/settings-format.js.map +1 -0
- package/dist/modules/apimapper/settings.d.ts +2 -2
- package/dist/modules/apimapper/settings.js +100 -42
- 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/skill-resources.d.ts +2 -2
- package/dist/modules/apimapper/skill-resources.js.map +1 -1
- package/dist/modules/apimapper/token-baseline.harness.d.ts +91 -0
- package/dist/modules/apimapper/token-baseline.harness.js +291 -0
- package/dist/modules/apimapper/token-baseline.harness.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 +56 -0
- package/dist/modules/apimapper/toolslist-size.js +192 -0
- package/dist/modules/apimapper/toolslist-size.js.map +1 -0
- package/dist/modules/apimapper/types.d.ts +44 -8
- package/dist/modules/apimapper/types.js +26 -1
- package/dist/modules/apimapper/types.js.map +1 -1
- package/dist/modules/apimapper/use-profile.d.ts +21 -0
- package/dist/modules/apimapper/use-profile.js +56 -2
- package/dist/modules/apimapper/use-profile.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.d.ts +2 -2
- package/dist/modules/apimapper/workflows.js +202 -20
- 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 +195 -7
- 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 +60 -0
- package/dist/setup-cli.js +155 -5
- 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 +22 -22
- package/docs/customgraph-internal-migration.md +4 -4
- package/docs/security.md +2 -21
- package/docs/tools.md +40 -12
- package/manifest.json +77 -70
- package/package.json +68 -60
- 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/render.md +132 -0
- package/skills/apimapper/reference/troubleshooting.md +21 -1
- 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
|
@@ -1,22 +1,171 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatResult,
|
|
2
|
+
import { formatResult, tableResult, detailResult, errorResult, readOnly, mutating, destructive, } from "@getimo/mcp-toolkit";
|
|
3
3
|
import { request, hintFor } from "./client.js";
|
|
4
|
+
import { restErrorResult } from "./tool-result.js";
|
|
4
5
|
import { unwrapEntity } from "./envelope.js";
|
|
6
|
+
import { toRows } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Wave-6 R1 (2026-05-29): Treat any non-"none" / non-empty auth_type as
|
|
9
|
+
* "credential required". The set is intentionally inclusive — the worker
|
|
10
|
+
* library uses both canonical (`oauth2_code`, `api_key`, `bearer`, `basic`)
|
|
11
|
+
* and legacy (`oauth`, `apiKey`) values; we don't try to enumerate them.
|
|
12
|
+
*/
|
|
13
|
+
function templateRequiresCredential(authType) {
|
|
14
|
+
if (typeof authType !== "string")
|
|
15
|
+
return false;
|
|
16
|
+
const t = authType.toLowerCase().trim();
|
|
17
|
+
if (t === "" || t === "none")
|
|
18
|
+
return false;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wave-6 R1: read the provider slug from a library template. Tolerates
|
|
23
|
+
* both top-level `provider` and `auth_scheme.provider`.
|
|
24
|
+
*/
|
|
25
|
+
function templateProvider(tpl) {
|
|
26
|
+
if (typeof tpl.provider === "string" && tpl.provider !== "")
|
|
27
|
+
return tpl.provider;
|
|
28
|
+
const auth = tpl.auth_scheme;
|
|
29
|
+
if (auth && typeof auth === "object") {
|
|
30
|
+
const p = auth.provider;
|
|
31
|
+
if (typeof p === "string" && p !== "")
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Wave-6 R1: read auth_type from a library template, normalising the
|
|
38
|
+
* pre-2.0 alias `oauth → oauth2_code`.
|
|
39
|
+
*/
|
|
40
|
+
function templateAuthType(tpl) {
|
|
41
|
+
let t = tpl.auth_type;
|
|
42
|
+
if (typeof t !== "string" || t === "") {
|
|
43
|
+
const auth = tpl.auth_scheme;
|
|
44
|
+
if (auth && typeof auth === "object") {
|
|
45
|
+
const at = auth.type;
|
|
46
|
+
if (typeof at === "string" && at !== "")
|
|
47
|
+
t = at;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (typeof t !== "string" || t === "")
|
|
51
|
+
return undefined;
|
|
52
|
+
if (t === "oauth")
|
|
53
|
+
return "oauth2_code";
|
|
54
|
+
if (t === "apiKey")
|
|
55
|
+
return "api_key";
|
|
56
|
+
return t;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wave-6 R1: credential lookup helpers shared with credentials.ts. Reads
|
|
60
|
+
* `oauth_provider` first, then `provider`.
|
|
61
|
+
*/
|
|
62
|
+
function credentialProviderSlug(c) {
|
|
63
|
+
return c.oauth_provider ?? c.provider ?? undefined;
|
|
64
|
+
}
|
|
65
|
+
/** F-14: best-effort JSON.stringify length. Falls back to 0 on cycle. */
|
|
66
|
+
function computeCatalogSize(catalog) {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(catalog).length;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* F-14: derive item count from common catalog shapes (items[] / connections[]).
|
|
76
|
+
* Falls back to total field, or 0 if nothing detectable.
|
|
77
|
+
*/
|
|
78
|
+
function computeCatalogItemCount(catalog, totalFallback) {
|
|
79
|
+
if (catalog && typeof catalog === "object") {
|
|
80
|
+
const co = catalog;
|
|
81
|
+
if (Array.isArray(co.items))
|
|
82
|
+
return co.items.length;
|
|
83
|
+
if (Array.isArray(co.connections))
|
|
84
|
+
return co.connections.length;
|
|
85
|
+
}
|
|
86
|
+
return typeof totalFallback === "number" ? totalFallback : 0;
|
|
87
|
+
}
|
|
88
|
+
// ── Shared table shape for every library list tool ────────────────────
|
|
89
|
+
// IA-7: NAME is the human-readable label. ID is opaque but the LLM needs it
|
|
90
|
+
// for follow-up calls (library_activate / library_connection_detail), so it
|
|
91
|
+
// stays in the ASCII text — `llmOnly: true` keeps it out of the Rich Card UI.
|
|
92
|
+
const LIBRARY_TABLE_COLUMNS = [
|
|
93
|
+
{ key: "id", label: "ID", width: 28, llmOnly: true },
|
|
94
|
+
{ key: "name", label: "NAME", width: 32 },
|
|
95
|
+
{ key: "provider", label: "PROVIDER", width: 14 },
|
|
96
|
+
{ key: "category", label: "CATEGORY", width: 14 },
|
|
97
|
+
{ key: "activated", label: "ACT", width: 4 },
|
|
98
|
+
];
|
|
99
|
+
// T1 (W3.2): reduced column set for 21+ items — id (llmOnly) + name + one key
|
|
100
|
+
// field (activation status). PROVIDER/CATEGORY are dropped at compact level.
|
|
101
|
+
const LIBRARY_COMPACT_COLUMNS = [
|
|
102
|
+
{ key: "id", label: "ID", width: 28, llmOnly: true },
|
|
103
|
+
{ key: "name", label: "NAME", width: 32 },
|
|
104
|
+
{ key: "activated", label: "ACT", width: 4 },
|
|
105
|
+
];
|
|
106
|
+
/** Standard row map: full LibraryItem → table row. */
|
|
107
|
+
function mapLibraryRow(i) {
|
|
108
|
+
return {
|
|
109
|
+
id: String(i.id ?? ""),
|
|
110
|
+
name: String(i.name ?? ""),
|
|
111
|
+
provider: i.provider ? String(i.provider) : "—",
|
|
112
|
+
category: i.category ? String(i.category) : "—",
|
|
113
|
+
activated: i.is_activated ? "✓" : "✗",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** T1 compact row map: only the columns LIBRARY_COMPACT_COLUMNS renders. */
|
|
117
|
+
function compactLibraryRow(i) {
|
|
118
|
+
return {
|
|
119
|
+
id: String(i.id ?? ""),
|
|
120
|
+
name: String(i.name ?? ""),
|
|
121
|
+
activated: i.is_activated ? "✓" : "✗",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// IA-10: actionable next-step guidance, kept in the table footer so it is
|
|
125
|
+
// VISIBLE in the LLM-readable text of every migrated list tool.
|
|
126
|
+
const LIST_NEXT_STEPS = "Next: apimapper_library_activate({ id }) to activate a template · " +
|
|
127
|
+
"apimapper_advanced({ tool: 'apimapper_library_connection_detail', arguments: { id } }) for full template config.";
|
|
5
128
|
export function registerLibraryTools(server) {
|
|
6
129
|
// ── apimapper_library_list ─────────────────────────────────────────
|
|
130
|
+
// rc.10 (2026-05-19) — schema + description hardened against the
|
|
131
|
+
// discovery-thrash pattern observed in Maria-walkthrough logs (29
|
|
132
|
+
// calls including 12 per-category iterations and 5 single-char
|
|
133
|
+
// search probes over a catalog of just 34 templates). Two guard
|
|
134
|
+
// rails: (1) wire-level zod constraints reject the worst probes,
|
|
135
|
+
// (2) description states the catalog is small enough that one
|
|
136
|
+
// unfiltered call returns everything.
|
|
7
137
|
server.registerTool("apimapper_library_list", {
|
|
8
138
|
title: "List Library Items",
|
|
9
|
-
description: "List
|
|
10
|
-
"
|
|
139
|
+
description: "List connection templates shipping with API Mapper. The catalog is " +
|
|
140
|
+
"small (~30-50 templates) — a SINGLE CALL without filters returns the " +
|
|
141
|
+
"full set in one round-trip. Do NOT iterate per category and do NOT " +
|
|
142
|
+
"probe with short search strings; call apimapper_library_categories " +
|
|
143
|
+
"first if you need an overview of available slugs, then filter the " +
|
|
144
|
+
"table you already have client-side." +
|
|
145
|
+
"\n\nServer-side filters (sent as query params, server returns pre-filtered):\n" +
|
|
146
|
+
" - category: narrow by category slug (e.g. 'media', 'productivity')\n" +
|
|
147
|
+
" - search: substring match on template name/description\n" +
|
|
148
|
+
"\nClient-side filters (applied AFTER fetch in this MCP process):\n" +
|
|
149
|
+
" - provider: narrow by upstream provider (e.g. 'google', 'meta')\n" +
|
|
150
|
+
" - activated: narrow to currently-activated or not-activated\n" +
|
|
151
|
+
"\nImplication: When combined with `limit`, the total reflects the " +
|
|
152
|
+
"post-server filter — if you set limit:50 + provider:meta and the " +
|
|
153
|
+
"server returns 50 mixed-provider items, you may see <50 after the " +
|
|
154
|
+
"client filter. Re-call with a higher limit or omit the client-side " +
|
|
155
|
+
"filter to see the full server-side result." +
|
|
156
|
+
"\n\nExamples:\n" +
|
|
157
|
+
" apimapper_library_list({}) // returns the whole catalog\n" +
|
|
158
|
+
" apimapper_library_list({ category: 'media' }) // narrow to one category (server-side)\n" +
|
|
159
|
+
" apimapper_library_list({ provider: 'google', limit: 200 }) // raise limit because provider filter is client-side",
|
|
11
160
|
inputSchema: {
|
|
12
|
-
category: z.string().optional().describe('Filter by category slug
|
|
161
|
+
category: z.string().optional().describe('Filter by category slug. Use apimapper_library_categories to discover slugs — do NOT loop over every category, the result of an unfiltered list({}) already covers them all.'),
|
|
13
162
|
provider: z.string().optional().describe('Filter by provider (e.g., "google", "meta")'),
|
|
14
163
|
activated: z.enum(["any", "yes", "no"]).default("any").describe("Filter by activation status"),
|
|
15
|
-
search: z.string().optional().describe("Free-text search"),
|
|
16
|
-
page: z.number().min(1).default(1).describe("Pagination page (1-based)"),
|
|
17
|
-
limit: z.number().min(1).max(500).default(100).describe("Max items (1-500)"),
|
|
164
|
+
search: z.string().min(3).optional().describe("Free-text search. Must be 3+ characters — single-char probes are blocked because the catalog is too small to need typeahead."),
|
|
165
|
+
page: z.number().min(1).max(2).default(1).describe("Pagination page (1-based). Capped at 2 — the catalog rarely fills more than one page at the default limit, iterating further yields empty results."),
|
|
166
|
+
limit: z.number().min(1).max(500).default(100).describe("Max items per page (1-500). The default of 100 already exceeds the full catalog — increase only if you have a specific reason."),
|
|
18
167
|
},
|
|
19
|
-
annotations: readOnly(),
|
|
168
|
+
annotations: readOnly({ title: "List Library Connections", openWorld: true }),
|
|
20
169
|
}, async ({ category, provider, activated, search, page, limit }) => {
|
|
21
170
|
const params = new URLSearchParams();
|
|
22
171
|
if (category)
|
|
@@ -27,13 +176,7 @@ export function registerLibraryTools(server) {
|
|
|
27
176
|
params.set("limit", String(limit));
|
|
28
177
|
const r = await request(`/library?${params.toString()}`);
|
|
29
178
|
if (!r.success) {
|
|
30
|
-
return
|
|
31
|
-
error: r.error,
|
|
32
|
-
status: r.status,
|
|
33
|
-
errorCode: r.errorCode,
|
|
34
|
-
context: { category, provider, activated, search, page, limit },
|
|
35
|
-
hint: hintFor(r.errorCode),
|
|
36
|
-
}, true);
|
|
179
|
+
return restErrorResult(r, { category, provider, activated, search, page, limit }, { message: "library list failed" });
|
|
37
180
|
}
|
|
38
181
|
// F-SEED-02: PHP returns `connections`; tolerate legacy `items`.
|
|
39
182
|
let items = Array.isArray(r.data?.connections)
|
|
@@ -47,22 +190,13 @@ export function registerLibraryTools(server) {
|
|
|
47
190
|
items = items.filter((i) => i.is_activated);
|
|
48
191
|
if (activated === "no")
|
|
49
192
|
items = items.filter((i) => !i.is_activated);
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
activated: i.is_activated ? "✓" : "✗",
|
|
56
|
-
})), {
|
|
57
|
-
columns: [
|
|
58
|
-
{ key: "id", label: "ID", width: 28 },
|
|
59
|
-
{ key: "name", label: "NAME", width: 32 },
|
|
60
|
-
{ key: "provider", label: "PROVIDER", width: 14 },
|
|
61
|
-
{ key: "category", label: "CATEGORY", width: 14 },
|
|
62
|
-
{ key: "activated", label: "ACT", width: 4 },
|
|
63
|
-
],
|
|
193
|
+
return tableResult(toRows(items), {
|
|
194
|
+
columns: LIBRARY_TABLE_COLUMNS,
|
|
195
|
+
compactColumns: LIBRARY_COMPACT_COLUMNS,
|
|
196
|
+
map: mapLibraryRow,
|
|
197
|
+
compactMap: compactLibraryRow,
|
|
64
198
|
header: (n) => `${n} library items (page ${page})`,
|
|
65
|
-
footer:
|
|
199
|
+
footer: LIST_NEXT_STEPS,
|
|
66
200
|
});
|
|
67
201
|
});
|
|
68
202
|
// ── apimapper_library_categories ───────────────────────────────────
|
|
@@ -79,11 +213,11 @@ export function registerLibraryTools(server) {
|
|
|
79
213
|
"below exist." +
|
|
80
214
|
"\n\nExample:\n apimapper_library_categories({})",
|
|
81
215
|
inputSchema: {},
|
|
82
|
-
annotations: readOnly(),
|
|
216
|
+
annotations: readOnly({ title: "List Library Categories", openWorld: true }),
|
|
83
217
|
}, async () => {
|
|
84
218
|
const r = await request("/library/categories");
|
|
85
219
|
if (!r.success) {
|
|
86
|
-
return
|
|
220
|
+
return restErrorResult(r, undefined, { message: "library categories failed" });
|
|
87
221
|
}
|
|
88
222
|
const cats = Array.isArray(r.data?.categories) ? r.data.categories : [];
|
|
89
223
|
const rows = cats
|
|
@@ -93,76 +227,171 @@ export function registerLibraryTools(server) {
|
|
|
93
227
|
name: typeof c.name === "string" ? c.name : "",
|
|
94
228
|
count: typeof c.count === "number" ? c.count : 0,
|
|
95
229
|
}));
|
|
96
|
-
const total = rows.reduce((sum,
|
|
97
|
-
return
|
|
230
|
+
const total = rows.reduce((sum, row) => sum + row.count, 0);
|
|
231
|
+
return tableResult(toRows(rows), {
|
|
98
232
|
columns: [
|
|
99
|
-
|
|
233
|
+
// IA-7: SLUG is the opaque identifier the LLM needs for
|
|
234
|
+
// library_list({category}); NAME is the human label.
|
|
235
|
+
{ key: "id", label: "SLUG", width: 16, llmOnly: true },
|
|
100
236
|
{ key: "name", label: "NAME", width: 28 },
|
|
101
|
-
{ key: "count", label: "COUNT", width: 6 },
|
|
237
|
+
{ key: "count", label: "COUNT", width: 6, align: "right" },
|
|
102
238
|
],
|
|
103
239
|
header: (n) => `${n} library categories (${total} templates total)`,
|
|
104
|
-
|
|
105
|
-
|
|
240
|
+
// rc.10 A2 (2026-05-19) — footer explicitly states a single
|
|
241
|
+
// unfiltered list({}) returns the whole catalog (the old wording
|
|
242
|
+
// caused the Maria-walkthrough AI to iterate every slug — 12
|
|
243
|
+
// calls). IA-10 (W3.9): the footer also carries the next step.
|
|
244
|
+
footer: "Authoritative list of every customer-visible category. The total above is small — " +
|
|
245
|
+
"do NOT iterate library_list({category:'…'}) once per slug.\n" +
|
|
246
|
+
"Next: apimapper_library_list({}) returns the complete catalog in one call; filter by category client-side.",
|
|
106
247
|
});
|
|
107
248
|
});
|
|
108
249
|
// ── apimapper_library_featured ─────────────────────────────────────
|
|
109
250
|
server.registerTool("apimapper_library_featured", {
|
|
110
251
|
title: "List Featured Library Items",
|
|
111
|
-
description: "
|
|
252
|
+
description: "List the curated, featured connection templates — the MANDATORY first " +
|
|
253
|
+
"call before building any new API integration. Use this to discover whether " +
|
|
254
|
+
"a ready-made template (with OAuth wizard, auto-header detection, and a " +
|
|
255
|
+
"curated YOOtheme schema) already exists for the API you need. " +
|
|
256
|
+
"Keywords: featured, recommended, starter, popular templates, pre-built, catalog landing. " +
|
|
257
|
+
"When NOT to use: to search the WHOLE catalog by name use apimapper_library_list; " +
|
|
258
|
+
"to read one template's full contract use apimapper_library_connection_detail; " +
|
|
259
|
+
"only fall back to apimapper_connection_create when no template matches." +
|
|
112
260
|
"\n\nExample:\n apimapper_library_featured({})",
|
|
113
261
|
inputSchema: {
|
|
114
262
|
limit: z.number().min(1).max(100).default(12).describe("Max items (1-100)"),
|
|
115
263
|
},
|
|
116
|
-
annotations: readOnly(),
|
|
264
|
+
annotations: readOnly({ title: "List Featured Library Connections", openWorld: true }),
|
|
117
265
|
}, async ({ limit }) => {
|
|
118
266
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
119
267
|
const r = await request(`/library/featured?${params.toString()}`);
|
|
120
268
|
if (!r.success) {
|
|
121
|
-
return
|
|
269
|
+
return restErrorResult(r, { limit }, { message: "library featured failed" });
|
|
122
270
|
}
|
|
123
271
|
const items = Array.isArray(r.data?.connections)
|
|
124
272
|
? r.data.connections
|
|
125
273
|
: Array.isArray(r.data?.items)
|
|
126
274
|
? r.data.items
|
|
127
275
|
: [];
|
|
128
|
-
return
|
|
276
|
+
return tableResult(toRows(items), {
|
|
277
|
+
columns: LIBRARY_TABLE_COLUMNS,
|
|
278
|
+
compactColumns: LIBRARY_COMPACT_COLUMNS,
|
|
279
|
+
map: mapLibraryRow,
|
|
280
|
+
compactMap: compactLibraryRow,
|
|
281
|
+
header: (n) => `${n} featured library items`,
|
|
282
|
+
footer: "Subset of the full catalog — NOT the complete library. " +
|
|
283
|
+
"Use apimapper_library_list({}) for the entire catalog.\n" +
|
|
284
|
+
LIST_NEXT_STEPS,
|
|
285
|
+
});
|
|
129
286
|
});
|
|
130
287
|
// ── apimapper_library_popular ──────────────────────────────────────
|
|
131
288
|
server.registerTool("apimapper_library_popular", {
|
|
132
289
|
title: "List Popular Library Items",
|
|
133
|
-
description: "
|
|
290
|
+
description: "List the most-activated connection templates across all users — a " +
|
|
291
|
+
"social-proof ranking of what other people actually wire up. Use to " +
|
|
292
|
+
"discover proven, in-demand integrations when you are exploring options. " +
|
|
293
|
+
"Keywords: popular, trending, most-used, most-activated, top templates. " +
|
|
294
|
+
"When NOT to use: for the editorial short-list use apimapper_library_featured; " +
|
|
295
|
+
"to search by API name use apimapper_library_list; to see what THIS user has " +
|
|
296
|
+
"already activated use apimapper_library_activated." +
|
|
134
297
|
"\n\nExample:\n apimapper_library_popular({})",
|
|
135
298
|
inputSchema: {
|
|
136
299
|
limit: z.number().min(1).max(100).default(12).describe("Max items (1-100)"),
|
|
137
300
|
},
|
|
138
|
-
annotations: readOnly(),
|
|
301
|
+
annotations: readOnly({ title: "List Popular Library Connections", openWorld: true }),
|
|
139
302
|
}, async ({ limit }) => {
|
|
140
303
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
141
304
|
const r = await request(`/library/popular?${params.toString()}`);
|
|
142
305
|
if (!r.success) {
|
|
143
|
-
return
|
|
306
|
+
return restErrorResult(r, { limit }, { message: "library popular failed" });
|
|
144
307
|
}
|
|
145
308
|
const items = Array.isArray(r.data?.connections)
|
|
146
309
|
? r.data.connections
|
|
147
310
|
: Array.isArray(r.data?.items)
|
|
148
311
|
? r.data.items
|
|
149
312
|
: [];
|
|
150
|
-
return
|
|
313
|
+
return tableResult(toRows(items), {
|
|
314
|
+
columns: LIBRARY_TABLE_COLUMNS,
|
|
315
|
+
compactColumns: LIBRARY_COMPACT_COLUMNS,
|
|
316
|
+
map: mapLibraryRow,
|
|
317
|
+
compactMap: compactLibraryRow,
|
|
318
|
+
header: (n) => `${n} popular library items`,
|
|
319
|
+
footer: "Subset of the full catalog — NOT the complete library. " +
|
|
320
|
+
"Use apimapper_library_list({}) for the entire catalog.\n" +
|
|
321
|
+
LIST_NEXT_STEPS,
|
|
322
|
+
});
|
|
151
323
|
});
|
|
152
324
|
// ── apimapper_library_catalog ──────────────────────────────────────
|
|
325
|
+
// NOT migrated to a structuredContent builder: the response is a
|
|
326
|
+
// multi-section object (items + categories + featured + popular), which
|
|
327
|
+
// is neither a flat list (tableResult) nor a single-entity detail
|
|
328
|
+
// (detailResult). formatResult preserves the nested structure, and the
|
|
329
|
+
// F-14 _meta.size_bytes / item_count already give the AI the
|
|
330
|
+
// size-decision signal a Rich Card would otherwise add.
|
|
153
331
|
server.registerTool("apimapper_library_catalog", {
|
|
154
332
|
title: "Get Full Library Catalog",
|
|
155
333
|
description: "Fetch the entire library catalog (all categories + items + featured/popular in one shot). " +
|
|
156
|
-
"Heavy —
|
|
157
|
-
"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
334
|
+
"Heavy — pass `template_id` to narrow the response to a single template's full record " +
|
|
335
|
+
"(skips the 8000-char truncation cap) or call apimapper_library_connection_detail for the " +
|
|
336
|
+
"rendered detail view." +
|
|
337
|
+
"\n\nExamples:\n" +
|
|
338
|
+
" apimapper_library_catalog({}) // full catalog, may truncate at 8000 chars\n" +
|
|
339
|
+
" apimapper_library_catalog({ template_id: 'google-sheets' }) // single template, full record",
|
|
340
|
+
inputSchema: {
|
|
341
|
+
// Wave-5 F12 (2026-05-29): cold-AI #3 hit 8k truncation on the
|
|
342
|
+
// unfiltered catalog and could not retrieve a target template's
|
|
343
|
+
// computed_fields / default_params. Filtering server-side via the
|
|
344
|
+
// same /library/connections/{id} endpoint that library_connection_detail
|
|
345
|
+
// uses gives the AI the FULL record without exceeding maxChars.
|
|
346
|
+
template_id: z
|
|
347
|
+
.string()
|
|
348
|
+
.optional()
|
|
349
|
+
.describe("Filter the catalog response to ONE template's full record (e.g. 'google-sheets'). " +
|
|
350
|
+
"Returns the connection template payload directly under the `connection` key " +
|
|
351
|
+
"(same shape as /library/connections/{id}) — skips the 8000-char truncation cap " +
|
|
352
|
+
"that applies to the unfiltered multi-section response."),
|
|
353
|
+
},
|
|
354
|
+
annotations: readOnly({ title: "Get Library Catalog", openWorld: true }),
|
|
355
|
+
}, async ({ template_id }) => {
|
|
356
|
+
// Wave-5 F12: single-template filter routes through the per-connection
|
|
357
|
+
// endpoint to skip the maxChars:8000 truncation that swallows mid-size
|
|
358
|
+
// records in the multi-section catalog response.
|
|
359
|
+
if (typeof template_id === "string" && template_id !== "") {
|
|
360
|
+
const r = await request(`/library/connections/${encodeURIComponent(template_id)}`);
|
|
361
|
+
if (!r.success) {
|
|
362
|
+
return restErrorResult(r, { template_id }, { message: "library catalog (filtered) failed" });
|
|
363
|
+
}
|
|
364
|
+
const connection = unwrapEntity(r.data, "connection") ?? {};
|
|
365
|
+
// DATA-LOW (Wave-B 2026-06-03): the data-level diagnostics key is named
|
|
366
|
+
// `meta` (not `_meta`) to avoid colliding in naming with the
|
|
367
|
+
// protocol-level `_meta` on the result object. This is text content, so
|
|
368
|
+
// the rename is harmless to clients but removes maintainer confusion.
|
|
369
|
+
return formatResult({
|
|
370
|
+
connection,
|
|
371
|
+
meta: {
|
|
372
|
+
filtered_by: { template_id },
|
|
373
|
+
source_endpoint: `/library/connections/${template_id}`,
|
|
374
|
+
},
|
|
375
|
+
}, false, { maxChars: 16000 });
|
|
376
|
+
}
|
|
161
377
|
const r = await request("/library/catalog");
|
|
162
378
|
if (!r.success) {
|
|
163
|
-
return
|
|
379
|
+
return restErrorResult(r, undefined, { message: "library catalog failed" });
|
|
164
380
|
}
|
|
165
|
-
|
|
381
|
+
const catalog = r.data?.catalog ?? r.data ?? {};
|
|
382
|
+
// F-14: surface payload size + item count so the AI can decide whether
|
|
383
|
+
// to call this heavy tool again, or call a narrower endpoint.
|
|
384
|
+
const sizeBytes = computeCatalogSize(catalog);
|
|
385
|
+
const itemCount = computeCatalogItemCount(catalog, r.data?.total);
|
|
386
|
+
// DATA-LOW (Wave-B 2026-06-03): `meta` (not `_meta`) — see the filtered
|
|
387
|
+
// branch above. Avoids naming-collision with the protocol-level `_meta`.
|
|
388
|
+
return formatResult({
|
|
389
|
+
...catalog,
|
|
390
|
+
meta: {
|
|
391
|
+
size_bytes: sizeBytes,
|
|
392
|
+
item_count: itemCount,
|
|
393
|
+
},
|
|
394
|
+
}, false, { maxChars: 8000 });
|
|
166
395
|
});
|
|
167
396
|
// ── apimapper_library_connection_detail ────────────────────────────
|
|
168
397
|
server.registerTool("apimapper_library_connection_detail", {
|
|
@@ -172,49 +401,98 @@ export function registerLibraryTools(server) {
|
|
|
172
401
|
inputSchema: {
|
|
173
402
|
id: z.string().describe("Library item ID. Use apimapper_library_list to find."),
|
|
174
403
|
},
|
|
175
|
-
annotations: readOnly(),
|
|
404
|
+
annotations: readOnly({ title: "Get Library Connection Detail", openWorld: true }),
|
|
176
405
|
}, async ({ id }) => {
|
|
177
406
|
const r = await request(`/library/connections/${encodeURIComponent(id)}`);
|
|
178
407
|
if (!r.success) {
|
|
179
|
-
return
|
|
408
|
+
return restErrorResult(r, { id }, { message: "library connection detail failed" });
|
|
180
409
|
}
|
|
181
410
|
if (!r.data || (typeof r.data === "object" && Object.keys(r.data).length === 0)) {
|
|
182
|
-
return
|
|
411
|
+
return errorResult({
|
|
412
|
+
message: "library item not found",
|
|
413
|
+
code: r.status ? String(r.status) : "not_found",
|
|
414
|
+
suggestion: hintFor("not_found"),
|
|
415
|
+
details: { id },
|
|
416
|
+
});
|
|
183
417
|
}
|
|
184
418
|
// F-A4-02: PHP returns `{success, connection: {...}}`. Use the shared
|
|
185
419
|
// envelope helper so we tolerate both wrapped + flat shapes.
|
|
186
420
|
const connection = unwrapEntity(r.data, "connection") ?? {};
|
|
187
|
-
return
|
|
421
|
+
return buildConnectionDetail(id, connection);
|
|
188
422
|
});
|
|
189
423
|
// ── apimapper_library_activated ────────────────────────────────────
|
|
190
424
|
server.registerTool("apimapper_library_activated", {
|
|
191
425
|
title: "List Activated Library Items",
|
|
192
|
-
description: "List library
|
|
426
|
+
description: "List the library templates THIS site has already activated (each one " +
|
|
427
|
+
"has a backing connection). Use to check what is already wired up before " +
|
|
428
|
+
"activating a duplicate, or to find the connection that came from a template. " +
|
|
429
|
+
"Keywords: activated, installed, my templates, already connected, in use. " +
|
|
430
|
+
"When NOT to use: to browse templates you could activate use " +
|
|
431
|
+
"apimapper_library_featured / apimapper_library_list; to activate one use " +
|
|
432
|
+
"apimapper_library_activate; to deactivate (deletes the connection) use " +
|
|
433
|
+
"apimapper_library_deactivate." +
|
|
193
434
|
"\n\nExample:\n apimapper_library_activated({})",
|
|
194
435
|
inputSchema: {},
|
|
195
|
-
annotations: readOnly(),
|
|
436
|
+
annotations: readOnly({ title: "List Activated Library Connections", openWorld: true }),
|
|
196
437
|
}, async () => {
|
|
197
438
|
const r = await request("/library/activated");
|
|
198
439
|
if (!r.success) {
|
|
199
|
-
return
|
|
440
|
+
return restErrorResult(r, undefined, { message: "library activated failed" });
|
|
200
441
|
}
|
|
201
442
|
// F-A4-01: PHP returns `connections`; tolerate legacy `items`.
|
|
202
|
-
const items = Array.isArray(r.data?.connections)
|
|
443
|
+
const items = (Array.isArray(r.data?.connections)
|
|
203
444
|
? r.data.connections
|
|
204
445
|
: Array.isArray(r.data?.items)
|
|
205
446
|
? r.data.items
|
|
206
|
-
: [];
|
|
207
|
-
return
|
|
447
|
+
: []);
|
|
448
|
+
return tableResult(items, {
|
|
449
|
+
columns: LIBRARY_TABLE_COLUMNS,
|
|
450
|
+
compactColumns: LIBRARY_COMPACT_COLUMNS,
|
|
451
|
+
// activated list rows are always activated — force the ✓ marker.
|
|
452
|
+
map: (i) => ({ ...mapLibraryRow(i), activated: "✓" }),
|
|
453
|
+
compactMap: (i) => ({ ...compactLibraryRow(i), activated: "✓" }),
|
|
454
|
+
header: (n) => `${n} activated library items`,
|
|
455
|
+
footer: "Library templates the user has activated.\n" +
|
|
456
|
+
"Next: apimapper_advanced({ tool: 'apimapper_library_deactivate', arguments: { id, confirm: true } }) to remove one · " +
|
|
457
|
+
"apimapper_connection_list({}) to inspect the underlying connections.",
|
|
458
|
+
});
|
|
208
459
|
});
|
|
209
460
|
// ── apimapper_library_activate ─────────────────────────────────────
|
|
461
|
+
// Wave-6 R1 (2026-05-29): For auth-protected templates (most APIs), the
|
|
462
|
+
// tool now auto-resolves `credential_id` when the user has exactly ONE
|
|
463
|
+
// OAuth credential matching the template's provider — and fails loudly
|
|
464
|
+
// (structured `credential_required` / `credential_ambiguous`) instead of
|
|
465
|
+
// landing a broken connection (empty endpoint, auth_type:none) when no
|
|
466
|
+
// unique match exists. Cold-AI #4 root cause: agents called
|
|
467
|
+
// `library_activate({id, extra_fields})` without a `credential_id`, the
|
|
468
|
+
// server-side defensive hydration silently fell through, and the resulting
|
|
469
|
+
// connection produced empty data the AI couldn't debug from the response.
|
|
210
470
|
server.registerTool("apimapper_library_activate", {
|
|
211
471
|
title: "Activate Library Template",
|
|
212
|
-
description: "Activate a library template — creates a new connection.
|
|
213
|
-
"
|
|
214
|
-
"
|
|
472
|
+
description: "Activate a library template — creates a new connection. " +
|
|
473
|
+
"\n\n**FOR AUTH-PROTECTED TEMPLATES (most APIs incl. Google Sheets, Notion, " +
|
|
474
|
+
"Airtable, Pexels, OpenWeatherMap, Calendly, GitHub):** you MUST link a " +
|
|
475
|
+
"credential. The tool auto-links when exactly ONE matching credential exists " +
|
|
476
|
+
"for the template's provider; otherwise it fails with `credential_required` " +
|
|
477
|
+
"or `credential_ambiguous`. Pre-flight: call " +
|
|
478
|
+
"`apimapper_credential_list({})` and confirm a credential exists for the " +
|
|
479
|
+
"provider — if not, create one via `apimapper_oauth_authorize_begin` (OAuth) " +
|
|
480
|
+
"or `apimapper_advanced({tool:'apimapper_credential_create',arguments:{…}})` " +
|
|
481
|
+
"(api_key/bearer). " +
|
|
482
|
+
"\n\nREST contract: `extra_fields` (template placeholder values) + optional " +
|
|
483
|
+
"`library_connection` (template metadata override) + optional `credential_id`." +
|
|
484
|
+
"\n\nExamples:\n" +
|
|
485
|
+
" apimapper_library_activate({ id: 'pexels', credential_id: 'cred_pexels', extra_fields: { api_key: '…' } })\n" +
|
|
486
|
+
" apimapper_library_activate({ id: 'google-sheets', extra_fields: { spreadsheet_id: '1AbC…' } }) // auto-links the unique Google OAuth credential",
|
|
215
487
|
inputSchema: {
|
|
216
488
|
id: z.string().describe("Library item ID. Use apimapper_library_list to find."),
|
|
217
|
-
credential_id: z
|
|
489
|
+
credential_id: z
|
|
490
|
+
.string()
|
|
491
|
+
.optional()
|
|
492
|
+
.describe("Credential ID. REQUIRED for auth-protected templates unless exactly " +
|
|
493
|
+
"one matching credential exists (then auto-linked). Use " +
|
|
494
|
+
"apimapper_credential_list({}) to find one, or " +
|
|
495
|
+
"apimapper_oauth_authorize_begin({provider}) to create one."),
|
|
218
496
|
extra_fields: z
|
|
219
497
|
.record(z.string(), z.unknown())
|
|
220
498
|
.optional()
|
|
@@ -224,27 +502,159 @@ export function registerLibraryTools(server) {
|
|
|
224
502
|
.optional()
|
|
225
503
|
.describe('Optional template metadata override (rename connection, override base_url, etc.). ' +
|
|
226
504
|
'REST key: library_connection.'),
|
|
505
|
+
// F1 (2026-06-08): one credential can back MULTIPLE connections (one per
|
|
506
|
+
// resource — e.g. several Airtable bases). The backend reuses an
|
|
507
|
+
// existing connection only when the SAME resource (extra_fields) is
|
|
508
|
+
// requested AND that connection is healthy. Pass `force_new: true` to
|
|
509
|
+
// skip reuse entirely and always create a fresh connection (a distinct
|
|
510
|
+
// resource on the same credential). REST key: force_new.
|
|
511
|
+
force_new: z
|
|
512
|
+
.boolean()
|
|
513
|
+
.optional()
|
|
514
|
+
.describe("Force creation of a NEW connection even if a matching one exists " +
|
|
515
|
+
"(default: false → the backend reuses a healthy connection for the " +
|
|
516
|
+
"same resource). Set true when wiring a second/different resource " +
|
|
517
|
+
"on the same credential. REST key: force_new."),
|
|
227
518
|
},
|
|
228
|
-
|
|
229
|
-
|
|
519
|
+
// W1.26 (IA-1): activate is idempotent — re-activating the same template
|
|
520
|
+
// for the same resource yields the same connection (the backend returns
|
|
521
|
+
// the existing row instead of duplicating). mutating() is the correct
|
|
522
|
+
// semantic class.
|
|
523
|
+
annotations: mutating({ title: "Activate Library Connection", openWorld: true }),
|
|
524
|
+
}, async ({ id, credential_id, extra_fields, library_connection, force_new }) => {
|
|
525
|
+
// Wave-6 R1 (2026-05-29): Credential auto-resolution gate.
|
|
526
|
+
// When `credential_id` is missing AND the template has a non-none auth_type,
|
|
527
|
+
// fetch the template + credential list, try to auto-link a unique match,
|
|
528
|
+
// and fail-loud with a structured error when 0 or >1 candidates exist.
|
|
529
|
+
// The auto-link logic mirrors `apimapper_oauth_authorize_begin` (which
|
|
530
|
+
// resolves an OAuth credential by provider) but applies to ANY auth_type
|
|
531
|
+
// (api_key, bearer, basic_auth, oauth2_*).
|
|
532
|
+
let resolvedCredentialId = credential_id;
|
|
533
|
+
let autoLinkedFrom;
|
|
534
|
+
if (!resolvedCredentialId) {
|
|
535
|
+
// Fetch template metadata to discover its auth scheme.
|
|
536
|
+
const tr = await request(`/library/connections/${encodeURIComponent(id)}`);
|
|
537
|
+
if (tr.success && tr.data) {
|
|
538
|
+
const tplRaw = unwrapEntity(tr.data, "connection") ?? {};
|
|
539
|
+
const tplAuthType = templateAuthType(tplRaw);
|
|
540
|
+
const tplProvider = templateProvider(tplRaw);
|
|
541
|
+
if (templateRequiresCredential(tplAuthType)) {
|
|
542
|
+
// Fetch credentials sanitised — we only need id/name/provider/auth_type.
|
|
543
|
+
const cr = await request("/credentials", {}, { sanitize: true });
|
|
544
|
+
if (!cr.success) {
|
|
545
|
+
return restErrorResult(cr, { id, template_provider: tplProvider, template_auth_type: tplAuthType }, {
|
|
546
|
+
message: "credential lookup failed during activation",
|
|
547
|
+
// Per-site nuance: a domain code fallback + a fixed
|
|
548
|
+
// activation-recovery suggestion override hintFor().
|
|
549
|
+
code: "credential_lookup_failed",
|
|
550
|
+
suggestion: "Retry, or pass an explicit `credential_id`. " +
|
|
551
|
+
"Use apimapper_credential_list({}) to find one.",
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
const allCreds = Array.isArray(cr.data?.credentials) ? cr.data.credentials : [];
|
|
555
|
+
// Match by provider first; if template has no provider, fall back to auth_type.
|
|
556
|
+
const candidates = tplProvider
|
|
557
|
+
? allCreds.filter((c) => credentialProviderSlug(c)?.toLowerCase() === tplProvider.toLowerCase())
|
|
558
|
+
: allCreds.filter((c) => c.auth_type === tplAuthType);
|
|
559
|
+
if (candidates.length === 0) {
|
|
560
|
+
const providerHint = tplProvider ?? tplAuthType ?? "the template's provider";
|
|
561
|
+
return errorResult({
|
|
562
|
+
message: `Template "${id}" requires authentication (${tplAuthType}) but no matching ` +
|
|
563
|
+
`credential exists for "${providerHint}".`,
|
|
564
|
+
code: "credential_required",
|
|
565
|
+
suggestion: `Create a credential first:\n` +
|
|
566
|
+
` • OAuth: apimapper_oauth_authorize_begin({ provider: "${tplProvider ?? "<provider>"}" })\n` +
|
|
567
|
+
` • api_key/bearer/basic: apimapper_advanced({ tool: "apimapper_credential_create", arguments: { auth_type: "${tplAuthType}", ... } })\n` +
|
|
568
|
+
`Then retry apimapper_library_activate with the new credential_id.`,
|
|
569
|
+
details: {
|
|
570
|
+
id,
|
|
571
|
+
template_provider: tplProvider,
|
|
572
|
+
template_auth_type: tplAuthType,
|
|
573
|
+
available_credentials: allCreds.map((c) => ({
|
|
574
|
+
id: c.id,
|
|
575
|
+
name: c.name,
|
|
576
|
+
provider: credentialProviderSlug(c),
|
|
577
|
+
auth_type: c.auth_type,
|
|
578
|
+
})),
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
if (candidates.length > 1) {
|
|
583
|
+
return errorResult({
|
|
584
|
+
message: `Template "${id}" requires authentication and ${candidates.length} candidate ` +
|
|
585
|
+
`credentials exist for "${tplProvider ?? tplAuthType}". Pass an explicit credential_id.`,
|
|
586
|
+
code: "credential_ambiguous",
|
|
587
|
+
suggestion: "Re-call apimapper_library_activate with one of the credential_ids below " +
|
|
588
|
+
"(see `details.candidates`).",
|
|
589
|
+
details: {
|
|
590
|
+
id,
|
|
591
|
+
template_provider: tplProvider,
|
|
592
|
+
template_auth_type: tplAuthType,
|
|
593
|
+
candidates: candidates.map((c) => ({
|
|
594
|
+
id: c.id,
|
|
595
|
+
name: c.name,
|
|
596
|
+
provider: credentialProviderSlug(c),
|
|
597
|
+
auth_type: c.auth_type,
|
|
598
|
+
})),
|
|
599
|
+
},
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
// Exactly one match → auto-link.
|
|
603
|
+
const picked = candidates[0];
|
|
604
|
+
resolvedCredentialId = picked.id;
|
|
605
|
+
autoLinkedFrom = {
|
|
606
|
+
provider: credentialProviderSlug(picked),
|
|
607
|
+
credentialName: picked.name,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// Note: if template fetch fails or auth_type is "none"/missing, we
|
|
612
|
+
// fall through to the original (no credential) activation path. The
|
|
613
|
+
// server-side validator will still reject missing required extra_fields.
|
|
614
|
+
}
|
|
230
615
|
const body = {};
|
|
231
|
-
if (
|
|
232
|
-
body.credential_id =
|
|
616
|
+
if (resolvedCredentialId)
|
|
617
|
+
body.credential_id = resolvedCredentialId;
|
|
233
618
|
if (extra_fields)
|
|
234
619
|
body.extra_fields = extra_fields;
|
|
235
620
|
if (library_connection)
|
|
236
621
|
body.library_connection = library_connection;
|
|
622
|
+
// F1 (2026-06-08): only send force_new when explicitly true — keep the
|
|
623
|
+
// request body minimal so a default activate stays byte-identical to the
|
|
624
|
+
// pre-F1 wire shape (the backend defaults to reuse).
|
|
625
|
+
if (force_new === true)
|
|
626
|
+
body.force_new = true;
|
|
237
627
|
const r = await request(`/library/${encodeURIComponent(id)}/activate`, {
|
|
238
628
|
method: "POST",
|
|
239
629
|
body: JSON.stringify(body),
|
|
240
630
|
});
|
|
241
631
|
if (!r.success) {
|
|
242
|
-
return
|
|
632
|
+
return restErrorResult(r, { id }, { message: "library activate failed" });
|
|
243
633
|
}
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
634
|
+
// F1 (2026-06-08): the backend is now the SOLE authority on reuse vs.
|
|
635
|
+
// create vs. heal. One credential can back MANY connections (one per
|
|
636
|
+
// resource); the backend reuses a connection only when the SAME resource
|
|
637
|
+
// is requested AND that connection is healthy, and it NEVER mutates a
|
|
638
|
+
// healthy row. It reports its decision via three booleans on the response:
|
|
639
|
+
//
|
|
640
|
+
// reused:true, mutated:false → existing healthy connection
|
|
641
|
+
// returned unchanged.
|
|
642
|
+
// reused:true, healed:true, mutated:true → existing connection was
|
|
643
|
+
// corrupt; the backend repaired
|
|
644
|
+
// it in place.
|
|
645
|
+
// (no reused / reused:false) → a brand-new connection was
|
|
646
|
+
// created (new resource or
|
|
647
|
+
// force_new:true).
|
|
648
|
+
//
|
|
649
|
+
// The MCP layer no longer re-derives a mismatch and rejects after the
|
|
650
|
+
// fact (the old `connection_reused_with_stale_state` path assumed a
|
|
651
|
+
// mutation that the backend no longer performs). We simply INTERPRET the
|
|
652
|
+
// backend's booleans into a clear, human-readable status string.
|
|
653
|
+
//
|
|
654
|
+
// F-A4-03: PHP returns `{success, connection: {id, ...}, reused?, healed?,
|
|
655
|
+
// mutated?, flow?}`. Read the nested `connection` directly (we DO NOT use
|
|
656
|
+
// unwrapEntity here because its fall-back to `data` would surface the
|
|
657
|
+
// wrong `id` if the envelope key is missing).
|
|
248
658
|
const dataObj = r.data && typeof r.data === "object" ? r.data : {};
|
|
249
659
|
const conn = (dataObj.connection && typeof dataObj.connection === "object")
|
|
250
660
|
? dataObj.connection
|
|
@@ -254,16 +664,55 @@ export function registerLibraryTools(server) {
|
|
|
254
664
|
(typeof dataObj.connectionId === "string" ? dataObj.connectionId : undefined) ||
|
|
255
665
|
null;
|
|
256
666
|
const reused = typeof dataObj.reused === "boolean" ? dataObj.reused : undefined;
|
|
667
|
+
const healed = typeof dataObj.healed === "boolean" ? dataObj.healed : undefined;
|
|
668
|
+
const mutated = typeof dataObj.mutated === "boolean" ? dataObj.mutated : undefined;
|
|
257
669
|
const flowSummary = dataObj.flow && typeof dataObj.flow === "object"
|
|
258
670
|
? dataObj.flow
|
|
259
671
|
: undefined;
|
|
672
|
+
// F1: derive a single status + note from the backend's decision booleans.
|
|
673
|
+
let status;
|
|
674
|
+
let note;
|
|
675
|
+
if (reused === true && healed === true) {
|
|
676
|
+
status = "reused_healed";
|
|
677
|
+
note =
|
|
678
|
+
"Reused (healed): an existing connection for this resource was corrupt " +
|
|
679
|
+
"and the backend repaired it in place. No duplicate was created.";
|
|
680
|
+
}
|
|
681
|
+
else if (reused === true) {
|
|
682
|
+
status = "reused";
|
|
683
|
+
note =
|
|
684
|
+
"Reused (unchanged): a healthy connection already existed for this " +
|
|
685
|
+
"resource and was returned as-is — no changes were made. Pass " +
|
|
686
|
+
"force_new:true to create a separate connection for a different resource.";
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
status = "activated";
|
|
690
|
+
note = "Connection created; use apimapper_connection_list to find the new id.";
|
|
691
|
+
}
|
|
260
692
|
return formatResult({
|
|
261
693
|
activated: true,
|
|
694
|
+
status,
|
|
262
695
|
id,
|
|
263
696
|
connection_id: connectionId,
|
|
264
|
-
|
|
697
|
+
// Wave-6 R1 — surface auto-link decision when the tool resolved
|
|
698
|
+
// the credential for the agent. Helps the AI confirm which
|
|
699
|
+
// credential ended up wired in (vs. a previously stored one).
|
|
700
|
+
...(autoLinkedFrom
|
|
701
|
+
? {
|
|
702
|
+
auto_linked_credential: {
|
|
703
|
+
credential_id: resolvedCredentialId,
|
|
704
|
+
provider: autoLinkedFrom.provider,
|
|
705
|
+
credential_name: autoLinkedFrom.credentialName,
|
|
706
|
+
},
|
|
707
|
+
}
|
|
708
|
+
: {}),
|
|
709
|
+
// F1 — echo the backend's decision booleans verbatim so the agent
|
|
710
|
+
// can branch on them too (only when the backend supplied them).
|
|
711
|
+
...(reused !== undefined ? { reused } : {}),
|
|
712
|
+
...(healed !== undefined ? { healed } : {}),
|
|
713
|
+
...(mutated !== undefined ? { mutated } : {}),
|
|
265
714
|
flow: flowSummary,
|
|
266
|
-
note
|
|
715
|
+
note,
|
|
267
716
|
}, false, { maxChars: 3000 });
|
|
268
717
|
});
|
|
269
718
|
// ── apimapper_library_deactivate ───────────────────────────────────
|
|
@@ -279,7 +728,7 @@ export function registerLibraryTools(server) {
|
|
|
279
728
|
.default(false)
|
|
280
729
|
.describe("Must be true to execute. Ask user for confirmation first."),
|
|
281
730
|
},
|
|
282
|
-
annotations: destructive(),
|
|
731
|
+
annotations: destructive({ title: "Deactivate Library Connection", openWorld: true }),
|
|
283
732
|
}, async ({ id, confirm }) => {
|
|
284
733
|
if (!confirm) {
|
|
285
734
|
return formatResult({
|
|
@@ -293,9 +742,145 @@ export function registerLibraryTools(server) {
|
|
|
293
742
|
method: "DELETE",
|
|
294
743
|
});
|
|
295
744
|
if (!r.success) {
|
|
296
|
-
return
|
|
745
|
+
return restErrorResult(r, { id }, { message: "library deactivate failed" });
|
|
297
746
|
}
|
|
298
747
|
return formatResult({ deactivated: true, id }, false, { maxChars: 1500 });
|
|
299
748
|
});
|
|
300
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Build a structured detail view for one library connection template.
|
|
752
|
+
* IA-7: technical IDs are copyable entries; IA-10: a dedicated "Next steps"
|
|
753
|
+
* group carries the actionable follow-up calls (visible in the LLM text).
|
|
754
|
+
*/
|
|
755
|
+
function buildConnectionDetail(id, connection) {
|
|
756
|
+
const str = (v) => typeof v === "string" ? v : typeof v === "number" || typeof v === "boolean" ? String(v) : null;
|
|
757
|
+
const overviewAll = [
|
|
758
|
+
{ key: "id", label: "ID", value: str(connection.id) ?? id, format: "code", copyable: true },
|
|
759
|
+
{ key: "name", label: "Name", value: str(connection.name) },
|
|
760
|
+
{ key: "provider", label: "Provider", value: str(connection.provider) },
|
|
761
|
+
{ key: "category", label: "Category", value: str(connection.category) },
|
|
762
|
+
{ key: "description", label: "Description", value: str(connection.description) },
|
|
763
|
+
];
|
|
764
|
+
const overview = overviewAll.filter((e) => e.value !== null);
|
|
765
|
+
// Endpoints arrive as an array of objects. Friction-3 fix (Task #46,
|
|
766
|
+
// 2026-05-29): expand each endpoint to surface its NAME (not "Endpoint 1")
|
|
767
|
+
// and the list of required/optional extra_fields the template declares —
|
|
768
|
+
// those are the values an AI agent must pass into `library_activate({extra_fields})`.
|
|
769
|
+
const endpoints = Array.isArray(connection.endpoints) ? connection.endpoints : [];
|
|
770
|
+
const templateExtraFields = Array.isArray(connection.extra_fields)
|
|
771
|
+
? connection.extra_fields
|
|
772
|
+
: [];
|
|
773
|
+
const endpointEntries = [
|
|
774
|
+
{ key: "endpoint_count", label: "Endpoints", value: endpoints.length },
|
|
775
|
+
...endpoints.flatMap((ep, idx) => {
|
|
776
|
+
const epObj = ep && typeof ep === "object" ? ep : {};
|
|
777
|
+
const name = str(epObj.name) ?? str(epObj.id) ?? `endpoint ${idx + 1}`;
|
|
778
|
+
const description = str(epObj.description) ?? str(epObj.label);
|
|
779
|
+
const path = str(epObj.path);
|
|
780
|
+
const method = str(epObj.method);
|
|
781
|
+
const entries = [
|
|
782
|
+
{
|
|
783
|
+
key: `endpoint_${idx}_name`,
|
|
784
|
+
label: ` ${idx + 1}. Name`,
|
|
785
|
+
value: name,
|
|
786
|
+
format: "code",
|
|
787
|
+
copyable: true,
|
|
788
|
+
},
|
|
789
|
+
];
|
|
790
|
+
if (description) {
|
|
791
|
+
entries.push({
|
|
792
|
+
key: `endpoint_${idx}_description`,
|
|
793
|
+
label: " Description",
|
|
794
|
+
value: description,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
if (path || method) {
|
|
798
|
+
entries.push({
|
|
799
|
+
key: `endpoint_${idx}_route`,
|
|
800
|
+
label: " Route",
|
|
801
|
+
value: `${method ?? "GET"} ${path ?? "—"}`,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
return entries;
|
|
805
|
+
}),
|
|
806
|
+
];
|
|
807
|
+
// Friction-3 (Task #46): list required + optional extra_fields. AI agents
|
|
808
|
+
// need to know which keys MUST appear in the `extra_fields` argument of
|
|
809
|
+
// library_activate.
|
|
810
|
+
const extraFieldEntries = [];
|
|
811
|
+
if (templateExtraFields.length > 0) {
|
|
812
|
+
for (const ef of templateExtraFields) {
|
|
813
|
+
const efName = str(ef.name);
|
|
814
|
+
if (!efName)
|
|
815
|
+
continue;
|
|
816
|
+
const required = ef.required === true;
|
|
817
|
+
const efType = str(ef.type) ?? "string";
|
|
818
|
+
// Wave-8 C1 (2026-05-29): featured-connections templates ship the
|
|
819
|
+
// human-readable description under `help_text` (not `description`).
|
|
820
|
+
// The label-fallback was kept but now ranks below help_text so
|
|
821
|
+
// google-sheets' "Choose from your recent spreadsheets or paste a URL"
|
|
822
|
+
// surfaces instead of just "Spreadsheet".
|
|
823
|
+
const efDesc = str(ef.help_text) ??
|
|
824
|
+
str(ef.description) ??
|
|
825
|
+
str(ef.label) ??
|
|
826
|
+
"";
|
|
827
|
+
const efExample = str(ef.example) ?? str(ef.placeholder) ?? "";
|
|
828
|
+
const valueParts = [];
|
|
829
|
+
valueParts.push(`type=${efType}`);
|
|
830
|
+
if (required)
|
|
831
|
+
valueParts.push("REQUIRED");
|
|
832
|
+
else
|
|
833
|
+
valueParts.push("optional");
|
|
834
|
+
if (efExample)
|
|
835
|
+
valueParts.push(`e.g. "${efExample}"`);
|
|
836
|
+
if (efDesc)
|
|
837
|
+
valueParts.push(efDesc);
|
|
838
|
+
extraFieldEntries.push({
|
|
839
|
+
key: `extra_${efName}`,
|
|
840
|
+
label: ` ${efName}`,
|
|
841
|
+
value: valueParts.join(" · "),
|
|
842
|
+
format: "code",
|
|
843
|
+
copyable: true,
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const auth = connection.auth_scheme && typeof connection.auth_scheme === "object"
|
|
848
|
+
? connection.auth_scheme
|
|
849
|
+
: undefined;
|
|
850
|
+
const authEntries = auth
|
|
851
|
+
? [{ key: "auth_type", label: "Auth scheme", value: str(auth.type) ?? "configured", format: "badge" }]
|
|
852
|
+
: [];
|
|
853
|
+
return detailResult({
|
|
854
|
+
title: str(connection.name) ?? id,
|
|
855
|
+
subtitle: str(connection.provider) ?? undefined,
|
|
856
|
+
badge: connection.is_activated
|
|
857
|
+
? { text: "Activated", variant: "success" }
|
|
858
|
+
: { text: "Not activated", variant: "secondary" },
|
|
859
|
+
groups: [
|
|
860
|
+
{ label: "Template", entries: overview },
|
|
861
|
+
...(endpointEntries.length > 0 ? [{ label: "Endpoints", entries: endpointEntries }] : []),
|
|
862
|
+
...(extraFieldEntries.length > 0
|
|
863
|
+
? [{ label: "Required/Optional extra_fields", entries: extraFieldEntries }]
|
|
864
|
+
: []),
|
|
865
|
+
...(authEntries.length > 0 ? [{ label: "Authentication", entries: authEntries }] : []),
|
|
866
|
+
{
|
|
867
|
+
// IA-10 (W3.9): actionable next steps in a dedicated group — visible
|
|
868
|
+
// in the LLM-readable detail text.
|
|
869
|
+
label: "Next steps",
|
|
870
|
+
entries: [
|
|
871
|
+
{
|
|
872
|
+
key: "activate",
|
|
873
|
+
label: "Activate",
|
|
874
|
+
value: `apimapper_library_activate({ id: '${id}' }) — creates a connection from this template`,
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
key: "credentials",
|
|
878
|
+
label: "Auth-protected?",
|
|
879
|
+
value: "apimapper_credential_list({}) to find a credential_id, then pass it to library_activate",
|
|
880
|
+
},
|
|
881
|
+
],
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
});
|
|
885
|
+
}
|
|
301
886
|
//# sourceMappingURL=library.js.map
|