@wootsup/mcp 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/LICENSE +21 -0
  3. package/README.md +179 -0
  4. package/SECURITY.md +163 -0
  5. package/dist/auth/keychain.d.ts +47 -0
  6. package/dist/auth/keychain.js +262 -0
  7. package/dist/auth/keychain.js.map +1 -0
  8. package/dist/auth/oauth-provider.d.ts +68 -0
  9. package/dist/auth/oauth-provider.js +232 -0
  10. package/dist/auth/oauth-provider.js.map +1 -0
  11. package/dist/auth/profiles.d.ts +52 -0
  12. package/dist/auth/profiles.js +200 -0
  13. package/dist/auth/profiles.js.map +1 -0
  14. package/dist/auth/token.d.ts +27 -0
  15. package/dist/auth/token.js +88 -0
  16. package/dist/auth/token.js.map +1 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.js +137 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/install-skill.d.ts +23 -0
  21. package/dist/install-skill.js +73 -0
  22. package/dist/install-skill.js.map +1 -0
  23. package/dist/modules/apimapper/cache.d.ts +2 -0
  24. package/dist/modules/apimapper/cache.js +71 -0
  25. package/dist/modules/apimapper/cache.js.map +1 -0
  26. package/dist/modules/apimapper/client.d.ts +85 -0
  27. package/dist/modules/apimapper/client.js +523 -0
  28. package/dist/modules/apimapper/client.js.map +1 -0
  29. package/dist/modules/apimapper/connections.d.ts +2 -0
  30. package/dist/modules/apimapper/connections.js +406 -0
  31. package/dist/modules/apimapper/connections.js.map +1 -0
  32. package/dist/modules/apimapper/credential-sanitizer.d.ts +7 -0
  33. package/dist/modules/apimapper/credential-sanitizer.js +70 -0
  34. package/dist/modules/apimapper/credential-sanitizer.js.map +1 -0
  35. package/dist/modules/apimapper/credentials.d.ts +2 -0
  36. package/dist/modules/apimapper/credentials.js +258 -0
  37. package/dist/modules/apimapper/credentials.js.map +1 -0
  38. package/dist/modules/apimapper/diagnose.d.ts +18 -0
  39. package/dist/modules/apimapper/diagnose.js +305 -0
  40. package/dist/modules/apimapper/diagnose.js.map +1 -0
  41. package/dist/modules/apimapper/flows.d.ts +2 -0
  42. package/dist/modules/apimapper/flows.js +372 -0
  43. package/dist/modules/apimapper/flows.js.map +1 -0
  44. package/dist/modules/apimapper/get-skill.d.ts +4 -0
  45. package/dist/modules/apimapper/get-skill.js +88 -0
  46. package/dist/modules/apimapper/get-skill.js.map +1 -0
  47. package/dist/modules/apimapper/graph-builder.d.ts +47 -0
  48. package/dist/modules/apimapper/graph-builder.js +117 -0
  49. package/dist/modules/apimapper/graph-builder.js.map +1 -0
  50. package/dist/modules/apimapper/graph.d.ts +2 -0
  51. package/dist/modules/apimapper/graph.js +117 -0
  52. package/dist/modules/apimapper/graph.js.map +1 -0
  53. package/dist/modules/apimapper/index.d.ts +2 -0
  54. package/dist/modules/apimapper/index.js +43 -0
  55. package/dist/modules/apimapper/index.js.map +1 -0
  56. package/dist/modules/apimapper/inspect.d.ts +20 -0
  57. package/dist/modules/apimapper/inspect.js +86 -0
  58. package/dist/modules/apimapper/inspect.js.map +1 -0
  59. package/dist/modules/apimapper/library.d.ts +2 -0
  60. package/dist/modules/apimapper/library.js +237 -0
  61. package/dist/modules/apimapper/library.js.map +1 -0
  62. package/dist/modules/apimapper/license.d.ts +2 -0
  63. package/dist/modules/apimapper/license.js +142 -0
  64. package/dist/modules/apimapper/license.js.map +1 -0
  65. package/dist/modules/apimapper/local-sources.d.ts +2 -0
  66. package/dist/modules/apimapper/local-sources.js +123 -0
  67. package/dist/modules/apimapper/local-sources.js.map +1 -0
  68. package/dist/modules/apimapper/misc.d.ts +2 -0
  69. package/dist/modules/apimapper/misc.js +149 -0
  70. package/dist/modules/apimapper/misc.js.map +1 -0
  71. package/dist/modules/apimapper/node-schema.d.ts +217 -0
  72. package/dist/modules/apimapper/node-schema.js +218 -0
  73. package/dist/modules/apimapper/node-schema.js.map +1 -0
  74. package/dist/modules/apimapper/normalizers.d.ts +13 -0
  75. package/dist/modules/apimapper/normalizers.js +37 -0
  76. package/dist/modules/apimapper/normalizers.js.map +1 -0
  77. package/dist/modules/apimapper/onboarding.d.ts +51 -0
  78. package/dist/modules/apimapper/onboarding.js +201 -0
  79. package/dist/modules/apimapper/onboarding.js.map +1 -0
  80. package/dist/modules/apimapper/schema.d.ts +2 -0
  81. package/dist/modules/apimapper/schema.js +84 -0
  82. package/dist/modules/apimapper/schema.js.map +1 -0
  83. package/dist/modules/apimapper/settings.d.ts +2 -0
  84. package/dist/modules/apimapper/settings.js +157 -0
  85. package/dist/modules/apimapper/settings.js.map +1 -0
  86. package/dist/modules/apimapper/skill-resources.d.ts +4 -0
  87. package/dist/modules/apimapper/skill-resources.js +85 -0
  88. package/dist/modules/apimapper/skill-resources.js.map +1 -0
  89. package/dist/modules/apimapper/types.d.ts +111 -0
  90. package/dist/modules/apimapper/types.js +14 -0
  91. package/dist/modules/apimapper/types.js.map +1 -0
  92. package/dist/modules/apimapper/use-profile.d.ts +34 -0
  93. package/dist/modules/apimapper/use-profile.js +176 -0
  94. package/dist/modules/apimapper/use-profile.js.map +1 -0
  95. package/dist/modules/apimapper/workflows.d.ts +2 -0
  96. package/dist/modules/apimapper/workflows.js +301 -0
  97. package/dist/modules/apimapper/workflows.js.map +1 -0
  98. package/dist/platform/index.d.ts +71 -0
  99. package/dist/platform/index.js +377 -0
  100. package/dist/platform/index.js.map +1 -0
  101. package/dist/server-http.d.ts +22 -0
  102. package/dist/server-http.js +159 -0
  103. package/dist/server-http.js.map +1 -0
  104. package/dist/setup/detect-clients.d.ts +39 -0
  105. package/dist/setup/detect-clients.js +152 -0
  106. package/dist/setup/detect-clients.js.map +1 -0
  107. package/dist/setup/probe-handshake.d.ts +26 -0
  108. package/dist/setup/probe-handshake.js +159 -0
  109. package/dist/setup/probe-handshake.js.map +1 -0
  110. package/dist/setup/write-config.d.ts +25 -0
  111. package/dist/setup/write-config.js +247 -0
  112. package/dist/setup/write-config.js.map +1 -0
  113. package/dist/setup-cli.d.ts +49 -0
  114. package/dist/setup-cli.js +292 -0
  115. package/dist/setup-cli.js.map +1 -0
  116. package/dist/skill-instructions.d.ts +10 -0
  117. package/dist/skill-instructions.js +68 -0
  118. package/dist/skill-instructions.js.map +1 -0
  119. package/dist/transports/http.d.ts +29 -0
  120. package/dist/transports/http.js +267 -0
  121. package/dist/transports/http.js.map +1 -0
  122. package/dist/transports/stdio.d.ts +9 -0
  123. package/dist/transports/stdio.js +19 -0
  124. package/dist/transports/stdio.js.map +1 -0
  125. package/docs/architecture.md +140 -0
  126. package/docs/customgraph-internal-migration.md +210 -0
  127. package/docs/security.md +126 -0
  128. package/docs/tools.md +230 -0
  129. package/manifest.json +76 -0
  130. package/package.json +61 -0
  131. package/skills/apimapper/SKILL.md +57 -0
  132. package/skills/apimapper/reference/joomla.md +85 -0
  133. package/skills/apimapper/reference/oauth.md +94 -0
  134. package/skills/apimapper/reference/troubleshooting.md +123 -0
  135. package/skills/apimapper/reference/yootheme.md +96 -0
@@ -0,0 +1,111 @@
1
+ export interface Connection {
2
+ id: string;
3
+ name: string;
4
+ source: string;
5
+ description?: string | null;
6
+ endpoint?: string;
7
+ method?: string;
8
+ auth_type?: string;
9
+ credential_id?: string | null;
10
+ items_path?: string | null;
11
+ cache_ttl?: string | number;
12
+ headers?: Array<{
13
+ name: string;
14
+ value: string;
15
+ }>;
16
+ body?: string | null;
17
+ params?: Array<{
18
+ name: string;
19
+ value: string;
20
+ }>;
21
+ default_params?: Array<{
22
+ name: string;
23
+ value: string;
24
+ }>;
25
+ fields?: unknown[];
26
+ endpoints?: Array<{
27
+ id?: string;
28
+ name?: string;
29
+ endpoint?: string;
30
+ method?: string;
31
+ }>;
32
+ template_fields?: Record<string, unknown>;
33
+ origin_hash?: string;
34
+ origin_id?: string | null;
35
+ }
36
+ export interface Credential {
37
+ id: string;
38
+ name: string;
39
+ auth_type: string;
40
+ oauth_provider?: string | null;
41
+ oauth_token_expires_at?: string | null;
42
+ provider?: string | null;
43
+ created_at?: string;
44
+ updated_at?: string;
45
+ }
46
+ export interface FlowNode {
47
+ id: string;
48
+ type: string;
49
+ position?: {
50
+ x: number;
51
+ y: number;
52
+ };
53
+ data?: Record<string, unknown>;
54
+ [k: string]: unknown;
55
+ }
56
+ export interface FlowEdge {
57
+ id: string;
58
+ source: string;
59
+ target: string;
60
+ sourceHandle?: string;
61
+ targetHandle?: string;
62
+ [k: string]: unknown;
63
+ }
64
+ export interface Flow {
65
+ id: string;
66
+ name: string;
67
+ description?: string | null;
68
+ source?: string;
69
+ is_compiled?: boolean;
70
+ node_count?: number;
71
+ node_types?: string[];
72
+ connection_ids?: string[];
73
+ nodes?: FlowNode[];
74
+ edges?: FlowEdge[];
75
+ viewport?: {
76
+ x: number;
77
+ y: number;
78
+ zoom: number;
79
+ };
80
+ version?: number;
81
+ updated_at?: string;
82
+ created_at?: string;
83
+ }
84
+ export interface LibraryItem {
85
+ id: string;
86
+ slug?: string;
87
+ name: string;
88
+ category?: string;
89
+ provider?: string;
90
+ description?: string;
91
+ is_activated?: boolean;
92
+ logo_svg?: string;
93
+ logo_hex?: string;
94
+ }
95
+ export interface NodeSnapshot {
96
+ id: string;
97
+ flow_id?: string;
98
+ node_id?: string;
99
+ type?: string;
100
+ item_count?: number;
101
+ created_at?: string;
102
+ }
103
+ export interface LicenseStatus {
104
+ status: string;
105
+ tier?: string;
106
+ expires_at?: string | null;
107
+ beta_channel?: boolean;
108
+ features?: Record<string, unknown>;
109
+ activation_count?: number;
110
+ max_activations?: number;
111
+ }
@@ -0,0 +1,14 @@
1
+ // src/modules/apimapper/types.ts — Canonical wire-format types for API Mapper REST.
2
+ //
3
+ // Important: API Mapper backend uses MIXED case conventions on the wire:
4
+ // - Connection wire format = **snake_case** (auth_type, credential_id, cache_ttl, items_path).
5
+ // Verified: admin-ui/src/api/wordpress.ts uses keysToSnakeCase before POST/PUT,
6
+ // ConnectionHandler.php:18 header comment "returned in snake_case".
7
+ // - Credential wire format = snake_case (auth_type, auth_data, oauth_provider)
8
+ // - Graph wire format = camelCase (targetNodeId, forceRefresh)
9
+ // - Library wire format = snake_case (extra_fields, library_connection)
10
+ // - LocalSource wire fmt = camelCase (contentType) with "platform/type" id ("wordpress/posts")
11
+ //
12
+ // These types reflect the actual REST contract verified against the PHP controllers.
13
+ export {};
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modules/apimapper/types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,EAAE;AACF,yEAAyE;AACzE,iGAAiG;AACjG,oFAAoF;AACpF,wEAAwE;AACxE,iFAAiF;AACjF,sEAAsE;AACtE,6EAA6E;AAC7E,mGAAmG;AACnG,EAAE;AACF,qFAAqF"}
@@ -0,0 +1,34 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Keychain } from "../../auth/keychain.js";
3
+ import type { Profile, ProfileStore } from "../../auth/profiles.js";
4
+ interface ToolRegistrar {
5
+ registerTool(name: string, config: unknown, handler: unknown): void;
6
+ }
7
+ export interface IdentityProbeResult {
8
+ plugin_version: string;
9
+ plugin_hash: string;
10
+ capabilities: string[];
11
+ site_url: string;
12
+ }
13
+ export type ProbeIdentityFn = (profile: Profile, token: string) => Promise<IdentityProbeResult>;
14
+ export interface UseProfileDeps {
15
+ store: ProfileStore;
16
+ keychain: Keychain;
17
+ /**
18
+ * Identity probe — calls the site's /identity endpoint and returns the
19
+ * normalised payload. Injected so tests can stub network calls and so the
20
+ * real impl can swap Platform clients.
21
+ */
22
+ probeIdentity?: ProbeIdentityFn;
23
+ }
24
+ /**
25
+ * Default identity probe — builds a Platform client from the profile + token
26
+ * and hits /identity. Returns the normalised IdentityProbeResult shape.
27
+ *
28
+ * The endpoint contract (see src/modules/admin-wordpress/.../IdentityController):
29
+ * { plugin_version: string, plugin_hash: string, capabilities: string[],
30
+ * site_url: string }
31
+ */
32
+ export declare function defaultProbeIdentity(profile: Profile, token: string): Promise<IdentityProbeResult>;
33
+ export declare function registerUseProfileTool(server: ToolRegistrar | McpServer, deps: UseProfileDeps): void;
34
+ export {};
@@ -0,0 +1,176 @@
1
+ // src/modules/apimapper/use-profile.ts — Phase 5.3.
2
+ //
3
+ // Single MCP tool `apimapper_use_profile`. Switches the active profile in the
4
+ // ProfileStore, then (unless `noProbe: true`) probes the site's /identity
5
+ // endpoint to confirm reachability + plugin install + signature.
6
+ //
7
+ // Plugin-hash drift between the stored `lastVerifiedIdentity.plugin_hash` and
8
+ // the live probe response is a soft warning (returned in `warnings[]`) — the
9
+ // activation still succeeds and the stored hash is refreshed. A network
10
+ // failure during probe IS a hard error and the active pointer does NOT move.
11
+ //
12
+ // Dependencies (store/keychain/probe) are injected for testability — the
13
+ // real wire-through happens in src/index.ts where the server is constructed.
14
+ import { z } from "zod";
15
+ import { formatResult, readOnly } from "@getimo/mcp-toolkit";
16
+ import { createPlatform, } from "../../platform/index.js";
17
+ import { createPlatformClient } from "./client.js";
18
+ // ── Real probe (used in production) ──────────────────────────────────────
19
+ /**
20
+ * Default identity probe — builds a Platform client from the profile + token
21
+ * and hits /identity. Returns the normalised IdentityProbeResult shape.
22
+ *
23
+ * The endpoint contract (see src/modules/admin-wordpress/.../IdentityController):
24
+ * { plugin_version: string, plugin_hash: string, capabilities: string[],
25
+ * site_url: string }
26
+ */
27
+ export async function defaultProbeIdentity(profile, token) {
28
+ const platform = createPlatform({
29
+ kind: profile.platform,
30
+ baseUrl: profile.siteUrl,
31
+ token,
32
+ });
33
+ const client = createPlatformClient(platform);
34
+ const r = await client.request("identity");
35
+ if (!r.success) {
36
+ throw new Error(r.error ?? "identity probe failed");
37
+ }
38
+ return normaliseIdentity(r.data);
39
+ }
40
+ function normaliseIdentity(data) {
41
+ if (!data || typeof data !== "object") {
42
+ throw new Error("identity response missing object body");
43
+ }
44
+ const o = data;
45
+ const plugin_version = typeof o.plugin_version === "string" ? o.plugin_version : "";
46
+ const plugin_hash = typeof o.plugin_hash === "string" ? o.plugin_hash : "";
47
+ const site_url = typeof o.site_url === "string" ? o.site_url : "";
48
+ const caps = Array.isArray(o.capabilities)
49
+ ? o.capabilities.filter((c) => typeof c === "string")
50
+ : [];
51
+ if (!plugin_version || !plugin_hash) {
52
+ throw new Error("identity response missing required fields");
53
+ }
54
+ return {
55
+ plugin_version,
56
+ plugin_hash,
57
+ site_url,
58
+ capabilities: caps,
59
+ };
60
+ }
61
+ // ── Tool registrar ───────────────────────────────────────────────────────
62
+ export function registerUseProfileTool(server, deps) {
63
+ const probe = deps.probeIdentity ?? defaultProbeIdentity;
64
+ // Cast through ToolRegistrar so tests can pass a minimal stub but the real
65
+ // McpServer also satisfies the interface.
66
+ const registrar = server;
67
+ registrar.registerTool("apimapper_use_profile", {
68
+ title: "Switch Active Profile",
69
+ description: "Activate a configured site profile and probe its identity endpoint to confirm " +
70
+ "the site is reachable and the plugin signature matches. Plugin-hash drift is " +
71
+ "returned as a warning (not an error). Use `noProbe: true` to skip the network " +
72
+ "probe for offline activation." +
73
+ "\n\nExample:\n apimapper_use_profile({ name: 'dev-wp', noProbe: false })",
74
+ inputSchema: {
75
+ name: z
76
+ .string()
77
+ .min(1)
78
+ .describe('Profile name to activate (e.g., "dev-wp", "client-x-prod"). Use apimapper_list_profiles to discover.'),
79
+ noProbe: z
80
+ .boolean()
81
+ .default(false)
82
+ .describe("Skip the live /identity probe. Use for offline activation when the site is unreachable."),
83
+ },
84
+ annotations: readOnly(),
85
+ }, async (args) => {
86
+ const { name, noProbe = false } = args;
87
+ // ── 1. Profile lookup ────────────────────────────────────────────
88
+ const profile = await deps.store.get(name);
89
+ if (!profile) {
90
+ return formatResult({
91
+ error: `Profile "${name}" not found`,
92
+ context: { name },
93
+ hint: "Use apimapper_list_profiles to see configured profiles, or " +
94
+ "run the setup CLI to add a new one.",
95
+ }, true);
96
+ }
97
+ // ── 2. Token lookup ──────────────────────────────────────────────
98
+ const token = await deps.keychain.get(profile.keychainRef);
99
+ if (!token) {
100
+ return formatResult({
101
+ error: `Token missing from keychain for profile "${name}" (ref: ${profile.keychainRef})`,
102
+ context: { name, keychainRef: profile.keychainRef },
103
+ hint: "The profile metadata exists but the underlying token is gone. " +
104
+ "Re-run the setup CLI to recreate the keychain entry.",
105
+ }, true);
106
+ }
107
+ // ── 3. Offline path ─────────────────────────────────────────────
108
+ if (noProbe) {
109
+ await deps.store.setActive(name);
110
+ return formatResult({
111
+ activated: true,
112
+ profile: {
113
+ name: profile.name,
114
+ siteUrl: profile.siteUrl,
115
+ platform: profile.platform,
116
+ },
117
+ noProbe: true,
118
+ warnings: [
119
+ "Identity probe skipped (noProbe=true). Plugin reachability not verified.",
120
+ ],
121
+ }, false, { maxChars: 2500 });
122
+ }
123
+ // ── 4. Probe identity ───────────────────────────────────────────
124
+ let identity;
125
+ try {
126
+ identity = await probe(profile, token);
127
+ }
128
+ catch (e) {
129
+ const msg = e instanceof Error ? e.message : String(e);
130
+ return formatResult({
131
+ error: `Identity probe failed for "${name}": ${msg}`,
132
+ context: { name, siteUrl: profile.siteUrl, platform: profile.platform },
133
+ hint: "Verify the site is reachable and the plugin is installed/active. " +
134
+ "Use `noProbe: true` to switch profile without probing.",
135
+ }, true);
136
+ }
137
+ // ── 5. Hash-match check ────────────────────────────────────────
138
+ const storedHash = profile.lastVerifiedIdentity?.plugin_hash;
139
+ const warnings = [];
140
+ let hashMatch = true;
141
+ if (storedHash && storedHash !== identity.plugin_hash) {
142
+ hashMatch = false;
143
+ warnings.push(`Plugin hash drift: stored=${storedHash} live=${identity.plugin_hash}. ` +
144
+ "Plugin was reinstalled / upgraded / tampered with since last activation. " +
145
+ "Stored hash has been refreshed.");
146
+ }
147
+ // ── 6. Persist refreshed identity + setActive ──────────────────
148
+ await deps.store.update(name, {
149
+ lastVerifiedIdentity: {
150
+ plugin_version: identity.plugin_version,
151
+ plugin_hash: identity.plugin_hash,
152
+ capabilities: identity.capabilities,
153
+ },
154
+ });
155
+ await deps.store.setActive(name);
156
+ const payload = {
157
+ activated: true,
158
+ profile: {
159
+ name: profile.name,
160
+ siteUrl: profile.siteUrl,
161
+ platform: profile.platform,
162
+ },
163
+ identity: {
164
+ plugin_version: identity.plugin_version,
165
+ plugin_hash: identity.plugin_hash,
166
+ capabilities: identity.capabilities,
167
+ site_url: identity.site_url,
168
+ },
169
+ hashMatch,
170
+ };
171
+ if (warnings.length > 0)
172
+ payload.warnings = warnings;
173
+ return formatResult(payload, false, { maxChars: 3000 });
174
+ });
175
+ }
176
+ //# sourceMappingURL=use-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-profile.js","sourceRoot":"","sources":["../../../src/modules/apimapper/use-profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerWorkflowTools(server: McpServer): void;
@@ -0,0 +1,301 @@
1
+ import { z } from "zod";
2
+ import { formatResult, creating, mutating } from "@getimo/mcp-toolkit";
3
+ import { request, hintFor } from "./client.js";
4
+ import { buildFlowGraph } from "./graph-builder.js";
5
+ import { normalizeMergeStrategy } from "./normalizers.js";
6
+ const SourceSpec = z.object({
7
+ connection: z.string().describe("Connection ID. Use apimapper_connection_list to find."),
8
+ endpoint: z.string().optional().describe('Endpoint id (e.g., "Scheduled Events")'),
9
+ template_fields: z
10
+ .record(z.string(), z.string())
11
+ .optional()
12
+ .describe('Template field values (e.g., {"user_uri":"...","spreadsheet_id":"..."})'),
13
+ items_path: z.string().optional().describe("Override items_path"),
14
+ label: z.string().optional().describe("Display label for the source node"),
15
+ });
16
+ const MergeSpec = z.object({
17
+ strategy: z.enum(["join", "concat", "append"]).describe('Merge strategy ("append" alias for "concat")'),
18
+ joinKey: z.string().optional().describe("Left-side join key field (required for strategy=join)"),
19
+ joinKeyRight: z.string().optional().describe("Right-side join key field (defaults to joinKey)"),
20
+ joinType: z.enum(["left", "inner"]).default("left").describe("Join type"),
21
+ });
22
+ const TransformSpec = z.object({
23
+ expression: z.string().default("[*]").describe('JMESPath expression (e.g., "[*]" or "[*].{title:name,id:id}")'),
24
+ });
25
+ const OutputSpec = z.object({
26
+ type: z.enum(["yootheme", "shortcode"]).default("yootheme").describe("Output type"),
27
+ name: z.string().min(1).describe("Source name shown in YOOtheme dropdown"),
28
+ slug: z.string().optional().describe('Source slug (auto-generated if omitted)'),
29
+ });
30
+ export function registerWorkflowTools(server) {
31
+ // ── apimapper_flow_setup_with_sources ──────────────────────────────
32
+ server.registerTool("apimapper_flow_setup_with_sources", {
33
+ title: "Setup Multi-Source Flow (Composite)",
34
+ description: "Compose a complete flow from connections in one call. Sequence: " +
35
+ "(1) build graph via buildFlowGraph() — pure function, deterministic layout. " +
36
+ "(2) POST /flows (creates row). " +
37
+ "(3) POST /flows/{id}/compile (publishes to YOOtheme). " +
38
+ "Returns flow_id + per-step status. On partial failure, returns cleanup hint." +
39
+ "\n\nExample:\n apimapper_flow_setup_with_sources({ name: 'Pexels + IG Gallery', sources: [{ connection: 'con_pexels' }, { connection: 'con_instagram' }], merge: { strategy: 'concat' }, output: { type: 'yootheme', name: 'Gallery' } })",
40
+ inputSchema: {
41
+ name: z.string().min(1).describe('Flow name (appears in YOOtheme dropdown)'),
42
+ description: z.string().optional().describe("Optional description"),
43
+ sources: z.array(SourceSpec).min(1).describe("1..N source connections"),
44
+ merge: MergeSpec.optional().describe("Merge config — required if sources.length >= 2"),
45
+ transform: TransformSpec.optional().describe("Optional Transform node"),
46
+ output: OutputSpec.describe("Output node config"),
47
+ compile: z.boolean().default(true).describe("Compile after save (= publish to YOOtheme)"),
48
+ },
49
+ annotations: creating(),
50
+ }, async ({ name, description, sources, merge, transform, output, compile }) => {
51
+ // Validate via buildFlowGraph (it throws on invalid combos)
52
+ let graph;
53
+ try {
54
+ graph = buildFlowGraph({
55
+ sources,
56
+ merge: merge,
57
+ transform: transform,
58
+ output: output,
59
+ });
60
+ }
61
+ catch (e) {
62
+ return formatResult({
63
+ error: e instanceof Error ? e.message : String(e),
64
+ context: { source_count: sources.length, has_merge: !!merge },
65
+ hint: "Check the validation rules in buildFlowGraph — typically missing merge for multi-source, or missing joinKey for strategy=join.",
66
+ }, true);
67
+ }
68
+ // Step 1: create
69
+ const createR = await request("/flows", {
70
+ method: "POST",
71
+ body: JSON.stringify({
72
+ name,
73
+ description,
74
+ nodes: graph.nodes,
75
+ edges: graph.edges,
76
+ viewport: graph.viewport,
77
+ }),
78
+ });
79
+ if (!createR.success || !createR.data?.id) {
80
+ return formatResult({
81
+ error: createR.error || "create returned no flow id",
82
+ status: createR.status,
83
+ errorCode: createR.errorCode,
84
+ context: { name, stage: "create" },
85
+ hint: hintFor(createR.errorCode),
86
+ }, true);
87
+ }
88
+ const flowId = createR.data.id;
89
+ const steps = [`1. Flow created: ${flowId}`];
90
+ // Step 2: compile (optional)
91
+ let compiled = false;
92
+ let compileError;
93
+ if (compile) {
94
+ const compileR = await request(`/flows/${encodeURIComponent(flowId)}/compile`, { method: "POST" });
95
+ if (!compileR.success) {
96
+ compileError = compileR.error;
97
+ steps.push(`2. Compile FAILED: ${compileR.error}`);
98
+ }
99
+ else {
100
+ const dataObj = compileR.data && typeof compileR.data === "object" ? compileR.data : {};
101
+ const errors = Array.isArray(dataObj.errors) ? dataObj.errors : [];
102
+ const flow = dataObj.flow ?? dataObj;
103
+ if (errors.length > 0 || flow.is_compiled === false) {
104
+ compileError = `compile errors: ${JSON.stringify(errors)}`;
105
+ steps.push("2. Compile FAILED with validation errors");
106
+ }
107
+ else {
108
+ compiled = true;
109
+ steps.push("2. Compiled — flow visible in YOOtheme dropdown");
110
+ }
111
+ }
112
+ }
113
+ if (compile && !compiled) {
114
+ return formatResult({
115
+ partial_success: true,
116
+ flow_id: flowId,
117
+ compiled: false,
118
+ compile_error: compileError,
119
+ steps,
120
+ next_action: "investigate_compile_error",
121
+ cleanup: `Call apimapper_flow_delete with id="${flowId}" + confirm=true to roll back the orphan, OR fix node config and call apimapper_flow_compile again.`,
122
+ hint: "Compile errors usually mean missing joinKey, invalid JMESPath, or missing connection. Use apimapper_flow_get to inspect node data.",
123
+ }, true);
124
+ }
125
+ return formatResult({
126
+ created: true,
127
+ flow_id: flowId,
128
+ name: createR.data.name,
129
+ compiled,
130
+ source_count: sources.length,
131
+ merge_strategy: merge ? normalizeMergeStrategy(merge.strategy) : null,
132
+ steps,
133
+ next: compiled
134
+ ? "Use apimapper_graph_preview to test items, or bind in YOOtheme Builder."
135
+ : "Call apimapper_flow_compile to publish.",
136
+ }, false, { maxChars: 3000 });
137
+ });
138
+ // ── apimapper_flow_change_merge_strategy ───────────────────────────
139
+ server.registerTool("apimapper_flow_change_merge_strategy", {
140
+ title: "Change Flow Merge Strategy (Composite)",
141
+ description: "Toggle a flow's merge-node strategy and re-compile. Preserves joinKey/joinKeyRight " +
142
+ "across switches — works around a known UI bug where the strategy toggle wipes joinKey. " +
143
+ "See ~/Projekte/getimo/40-Plans/active/2026-05-18-mcp-apimapper-rest-full-coverage-design.md." +
144
+ "\n\nExample:\n apimapper_flow_change_merge_strategy({ flow_id: 'flow_Z2fLg70M84', strategy: 'join', joinKey: 'id' })",
145
+ inputSchema: {
146
+ flow_id: z.string().describe("Flow ID. Use apimapper_flow_list to find."),
147
+ strategy: z.enum(["join", "concat", "append"]).describe("New merge strategy"),
148
+ joinKey: z.string().optional().describe("Left-side join key (required for strategy=join)"),
149
+ joinKeyRight: z.string().optional().describe("Right-side join key (defaults to joinKey)"),
150
+ joinType: z.enum(["left", "inner"]).optional().describe("Join type override"),
151
+ compile: z.boolean().default(true).describe("Compile after save"),
152
+ },
153
+ annotations: mutating(),
154
+ }, async ({ flow_id, strategy, joinKey, joinKeyRight, joinType, compile }) => {
155
+ const getR = await request(`/flows/${encodeURIComponent(flow_id)}`);
156
+ if (!getR.success || !getR.data) {
157
+ return formatResult({
158
+ error: getR.error || "flow not found",
159
+ status: getR.status,
160
+ errorCode: getR.errorCode,
161
+ context: { flow_id, stage: "fetch" },
162
+ hint: hintFor(getR.errorCode),
163
+ }, true);
164
+ }
165
+ const flow = getR.data;
166
+ if (!Array.isArray(flow.nodes)) {
167
+ return formatResult({
168
+ error: "flow.nodes is not an array",
169
+ context: { flow_id, type: typeof flow.nodes },
170
+ hint: "Flow data shape unexpected. Use apimapper_flow_get to inspect.",
171
+ }, true);
172
+ }
173
+ const mergeNode = flow.nodes.find((n) => n.type === "merge");
174
+ if (!mergeNode) {
175
+ return formatResult({
176
+ error: "no merge node in flow",
177
+ context: { flow_id, node_types: flow.nodes.map((n) => n.type) },
178
+ hint: "This flow has no merge node — strategy change not applicable.",
179
+ }, true);
180
+ }
181
+ const compiledStrategy = normalizeMergeStrategy(strategy);
182
+ const oldData = (mergeNode.data || {});
183
+ const newData = {
184
+ ...oldData,
185
+ strategy: compiledStrategy,
186
+ label: `Merge: ${compiledStrategy === "concat" ? "Concat" : "Join"}`,
187
+ };
188
+ if (compiledStrategy === "join") {
189
+ newData.joinKey = joinKey || oldData.joinKey || "";
190
+ newData.joinKeyRight = joinKeyRight || oldData.joinKeyRight;
191
+ if (joinType)
192
+ newData.joinType = joinType;
193
+ if (!newData.joinKey) {
194
+ return formatResult({
195
+ error: "joinKey required for strategy=join (and not present on existing node)",
196
+ context: { flow_id, existing_data: oldData },
197
+ hint: "Pass joinKey explicitly.",
198
+ }, true);
199
+ }
200
+ }
201
+ mergeNode.data = newData;
202
+ const putR = await request(`/flows/${encodeURIComponent(flow_id)}`, {
203
+ method: "PUT",
204
+ body: JSON.stringify({ nodes: flow.nodes }),
205
+ });
206
+ if (!putR.success) {
207
+ return formatResult({
208
+ error: putR.error,
209
+ status: putR.status,
210
+ errorCode: putR.errorCode,
211
+ context: { flow_id, stage: "update" },
212
+ hint: hintFor(putR.errorCode),
213
+ }, true);
214
+ }
215
+ let compiled = false;
216
+ let compileError;
217
+ if (compile) {
218
+ const compileR = await request(`/flows/${encodeURIComponent(flow_id)}/compile`, { method: "POST" });
219
+ if (compileR.success)
220
+ compiled = true;
221
+ else
222
+ compileError = compileR.error;
223
+ }
224
+ if (compile && !compiled) {
225
+ return formatResult({
226
+ partial_success: true,
227
+ flow_id,
228
+ strategy: compiledStrategy,
229
+ joinKey: newData.joinKey,
230
+ joinKeyRight: newData.joinKeyRight,
231
+ compiled: false,
232
+ compile_error: compileError,
233
+ next_action: "investigate_compile_error_or_revert",
234
+ cleanup: "Option A: fix the merge config (e.g., correct joinKey field name) and call apimapper_flow_compile. " +
235
+ "Option B: revert via apimapper_flow_change_merge_strategy back to the previous strategy. " +
236
+ "The flow PUT succeeded — node data is updated but the flow won't appear in YOOtheme until compile passes.",
237
+ hint: "Compile errors usually mean wrong joinKey field name. Use apimapper_flow_get to inspect node data.",
238
+ }, true);
239
+ }
240
+ return formatResult({
241
+ updated: true,
242
+ flow_id,
243
+ strategy: compiledStrategy,
244
+ joinKey: newData.joinKey,
245
+ joinKeyRight: newData.joinKeyRight,
246
+ compiled,
247
+ compile_error: compileError,
248
+ }, false, { maxChars: 2500 });
249
+ });
250
+ // ── apimapper_flow_full_recompile_publish ──────────────────────────
251
+ server.registerTool("apimapper_flow_full_recompile_publish", {
252
+ title: "Full Recompile + Cache Clear (Composite)",
253
+ description: "Compile a flow + invalidate admin cache in one call. Use after upstream API changes " +
254
+ "to ensure the flow + schema are fresh. Each step's success is surfaced separately " +
255
+ "— a cache-clear failure does NOT silently downgrade compile success." +
256
+ "\n\nExample:\n apimapper_flow_full_recompile_publish({ flow_id: 'flow_Z2fLg70M84' })",
257
+ inputSchema: {
258
+ flow_id: z.string().describe("Flow ID. Use apimapper_flow_list to find."),
259
+ },
260
+ annotations: mutating(),
261
+ }, async ({ flow_id }) => {
262
+ const steps = [];
263
+ const errors = [];
264
+ // Stage 1: compile
265
+ const compileR = await request(`/flows/${encodeURIComponent(flow_id)}/compile`, { method: "POST" });
266
+ if (!compileR.success) {
267
+ return formatResult({
268
+ error: compileR.error,
269
+ status: compileR.status,
270
+ errorCode: compileR.errorCode,
271
+ context: { flow_id, stage: "compile" },
272
+ hint: hintFor(compileR.errorCode),
273
+ }, true);
274
+ }
275
+ steps.push("1. Flow compiled");
276
+ // Stage 2: cache invalidate (admin cache — admin-ui + render cache)
277
+ const cacheR = await request("/cache/admin/invalidate", {
278
+ method: "POST",
279
+ body: JSON.stringify({ scope: "flow", id: flow_id }),
280
+ });
281
+ if (cacheR.success) {
282
+ steps.push("2. Admin cache invalidated");
283
+ }
284
+ else {
285
+ errors.push({ stage: "cache", error: cacheR.error || "unknown" });
286
+ steps.push(`2. Admin cache invalidation FAILED: ${cacheR.error}`);
287
+ }
288
+ const ok = errors.length === 0;
289
+ return formatResult({
290
+ ok,
291
+ partial: !ok,
292
+ flow_id,
293
+ steps,
294
+ errors: ok ? undefined : errors,
295
+ next: ok
296
+ ? "Flow is fresh in both admin + render layers. YOOtheme schema cache is NOT cleared by this tool — clear it via the YOOtheme MCP (yootheme_clear_cache) if needed."
297
+ : "Compile succeeded but cache step had issues. Manually call apimapper_cache_invalidate scope=all if downstream still sees stale data.",
298
+ }, ok, { maxChars: 2500 });
299
+ });
300
+ }
301
+ //# sourceMappingURL=workflows.js.map