mcp-server-sfmc 0.4.3 → 1.0.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/README.md +140 -31
- package/bundled/mcn-help/chunks.json +1 -0
- package/ci-templates/github-copilot-review-instructions.md +1 -1
- package/dist/conversion-rules.d.ts +149 -0
- package/dist/conversion-rules.d.ts.map +1 -0
- package/dist/conversion-rules.js +845 -0
- package/dist/conversion-rules.js.map +1 -0
- package/dist/index.js +832 -64
- package/dist/index.js.map +1 -1
- package/dist/mcn-help-search.d.ts +39 -0
- package/dist/mcn-help-search.d.ts.map +1 -0
- package/dist/mcn-help-search.js +88 -0
- package/dist/mcn-help-search.js.map +1 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -13,8 +13,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
14
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
-
import { sfmcLanguageService, validateAmpscript, validateSsjs, validateGtlBlocks, } from 'sfmc-language-lsp';
|
|
16
|
+
import { sfmcLanguageService, validateAmpscript, validateSsjs, validateGtlBlocks, isMcnSupported, getMcnApiVersion, getMcnNotes, extractAmpscriptFunctionCalls, } from 'sfmc-language-lsp';
|
|
17
17
|
import { getChunks, getMceHelpStats, searchMceHelp, } from './mce-help-search.js';
|
|
18
|
+
import { getMcnChunks, getMcnHelpStats, searchMcnHelp } from './mcn-help-search.js';
|
|
19
|
+
import { CLOUDPAGES_ONLY_FUNCTIONS, NON_MIGRATABLE_SSJS_PATTERNS, ssjsToAmpscript, ampscriptToSsjs, rewriteAmpForMcn, isSsjsBlockConvertible, } from './conversion-rules.js';
|
|
18
20
|
function projectPackageRoot() {
|
|
19
21
|
return path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
22
|
}
|
|
@@ -23,6 +25,17 @@ const pkg = JSON.parse(fs.readFileSync(path.join(projectPackageRoot(), 'package.
|
|
|
23
25
|
// Server instance
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
25
27
|
const SERVER_INSTRUCTIONS = 'This server provides authoritative SFMC language intelligence and bundled Salesforce Marketing Cloud product help.\n\n' +
|
|
28
|
+
'## Target platform — detect before writing or validating code\n\n' +
|
|
29
|
+
'Before writing, validating, converting, or completing any AMPscript or SSJS code:\n' +
|
|
30
|
+
'1. Call `detect_sfmc_platform` with the project root path.\n' +
|
|
31
|
+
' - Returns `"engagement"` (`.mcdevrc.json` found) → use `target: "engagement"`\n' +
|
|
32
|
+
' - Returns `"next"` (`sfdx-project.json` found) → use `target: "next"`\n' +
|
|
33
|
+
' - Returns `"unknown"` → ask the user: "Are you targeting Marketing Cloud Engagement (MCE) or Marketing Cloud Next (MCN)?"\n' +
|
|
34
|
+
'2. Pass the resolved target to all language tools (`validate_*`, `suggest_fix`, `get_*_completions`).\n' +
|
|
35
|
+
'3. When target is `"next"`: SSJS is **not supported** — do not generate SSJS. Only use AMPscript functions that are MCN-supported (check with `list_ampscript_functions` with `platform: "next"`).\n\n' +
|
|
36
|
+
'## When to call search_mcn_help\n\n' +
|
|
37
|
+
'**ALWAYS** call `search_mcn_help` before answering questions about Marketing Cloud Next **developer APIs**, ' +
|
|
38
|
+
'objects, flows, segments, transactional messages, REST/SOAP APIs, or AMPscript behavior differences in MCN.\n\n' +
|
|
26
39
|
'## When to call search_mce_help\n\n' +
|
|
27
40
|
'**ALWAYS** call `search_mce_help` before answering any question about the following product areas ' +
|
|
28
41
|
'(use the matching `product_focus` value for best results):\n\n' +
|
|
@@ -35,7 +48,7 @@ const SERVER_INSTRUCTIONS = 'This server provides authoritative SFMC language in
|
|
|
35
48
|
'| Subscriptions, sending limits, suspended accounts, Einstein features | `engagement` |\n' +
|
|
36
49
|
'| Advertising, Distributed Marketing, Marketing Cloud Connect | `engagement` |\n' +
|
|
37
50
|
'| Contact Builder, Audience Builder, Data Extensions | `engagement` |\n' +
|
|
38
|
-
'| Marketing Cloud Next
|
|
51
|
+
'| Marketing Cloud Next migration, admin, setup, or operational overview | `next` |\n' +
|
|
39
52
|
'| Marketing Cloud Personalization / Interaction Studio, real-time personalisation, A/B testing | `personalization` |\n' +
|
|
40
53
|
'| Salesforce Personalization | `personalization` |\n' +
|
|
41
54
|
'| Marketing Cloud Account Engagement / Pardot, B2B marketing automation, lead scoring | `account-engagement` |\n' +
|
|
@@ -45,7 +58,13 @@ const SERVER_INSTRUCTIONS = 'This server provides authoritative SFMC language in
|
|
|
45
58
|
'in the answer, and note when excerpts are incomplete.\n\n' +
|
|
46
59
|
'## When to call language tools\n\n' +
|
|
47
60
|
'For AMPscript/SSJS/GTL code tasks use `validate_*`, `lookup_*`, `review_change`, `suggest_fix`, etc. ' +
|
|
48
|
-
'Do **not** guess function signatures — call `lookup_ampscript_function` or `lookup_ssjs_function
|
|
61
|
+
'Do **not** guess function signatures — call `lookup_ampscript_function` or `lookup_ssjs_function`.\n\n' +
|
|
62
|
+
'## Unified help search shortcut\n\n' +
|
|
63
|
+
'When you already know the project root, prefer `search_help` over calling `search_mce_help` or ' +
|
|
64
|
+
'`search_mcn_help` directly. Pass `projectRoot` and `search_help` will auto-detect the platform ' +
|
|
65
|
+
'and route the query to the right doc index (or both for MCN). Only fall back to the individual ' +
|
|
66
|
+
'search tools when you need to scope by `product_focus` (MCE only) or when you are **certain** ' +
|
|
67
|
+
'which doc index to target.';
|
|
49
68
|
const server = new McpServer({ name: 'mcp-server-sfmc', version: pkg.version }, { instructions: SERVER_INSTRUCTIONS });
|
|
50
69
|
function detectLanguage(code, hint) {
|
|
51
70
|
if (hint === 'ssjs')
|
|
@@ -83,7 +102,8 @@ function formatDiagnostics(diagnostics) {
|
|
|
83
102
|
// Tool: validate_ampscript
|
|
84
103
|
// ---------------------------------------------------------------------------
|
|
85
104
|
server.tool('validate_ampscript', 'Validate AMPscript code for syntax errors, unknown functions, arity mismatches, and style issues. ' +
|
|
86
|
-
'Returns a list of diagnostics with line numbers and severity.'
|
|
105
|
+
'Returns a list of diagnostics with line numbers and severity. ' +
|
|
106
|
+
"Set target to 'next' to also report functions not supported in Marketing Cloud Next.", {
|
|
87
107
|
code: z.string().describe('The AMPscript code to validate. May include HTML context.'),
|
|
88
108
|
maxProblems: z
|
|
89
109
|
.number()
|
|
@@ -92,8 +112,15 @@ server.tool('validate_ampscript', 'Validate AMPscript code for syntax errors, un
|
|
|
92
112
|
.max(500)
|
|
93
113
|
.optional()
|
|
94
114
|
.describe('Maximum number of problems to return (default 100).'),
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
target: z
|
|
116
|
+
.enum(['engagement', 'next'])
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("Target platform. Use 'next' to flag AMPscript functions not supported in Marketing Cloud Next."),
|
|
119
|
+
}, ({ code, maxProblems, target }) => {
|
|
120
|
+
const settings = {
|
|
121
|
+
maxNumberOfProblems: maxProblems ?? 100,
|
|
122
|
+
targetPlatform: target,
|
|
123
|
+
};
|
|
97
124
|
const diagnostics = validateAmpscript(code, settings);
|
|
98
125
|
return {
|
|
99
126
|
content: [{ type: 'text', text: formatDiagnostics(diagnostics) }],
|
|
@@ -103,7 +130,8 @@ server.tool('validate_ampscript', 'Validate AMPscript code for syntax errors, un
|
|
|
103
130
|
// Tool: validate_ssjs
|
|
104
131
|
// ---------------------------------------------------------------------------
|
|
105
132
|
server.tool('validate_ssjs', 'Validate SSJS (Server-Side JavaScript) code for unsupported ES6+ syntax, missing Platform.Load, ' +
|
|
106
|
-
'and incorrect usage patterns. Returns diagnostics with line numbers.'
|
|
133
|
+
'and incorrect usage patterns. Returns diagnostics with line numbers. ' +
|
|
134
|
+
"Set target to 'next' to flag all SSJS as unsupported (SSJS is not available in Marketing Cloud Next).", {
|
|
107
135
|
code: z
|
|
108
136
|
.string()
|
|
109
137
|
.describe('The SSJS code to validate. May include <script runat="server"> tags.'),
|
|
@@ -114,8 +142,15 @@ server.tool('validate_ssjs', 'Validate SSJS (Server-Side JavaScript) code for un
|
|
|
114
142
|
.max(500)
|
|
115
143
|
.optional()
|
|
116
144
|
.describe('Maximum number of problems to return (default 100).'),
|
|
117
|
-
|
|
118
|
-
|
|
145
|
+
target: z
|
|
146
|
+
.enum(['engagement', 'next'])
|
|
147
|
+
.optional()
|
|
148
|
+
.describe("Target platform. Use 'next' to flag SSJS code as unsupported in Marketing Cloud Next."),
|
|
149
|
+
}, ({ code, maxProblems, target }) => {
|
|
150
|
+
const settings = {
|
|
151
|
+
maxNumberOfProblems: maxProblems ?? 100,
|
|
152
|
+
targetPlatform: target,
|
|
153
|
+
};
|
|
119
154
|
const diagnostics = validateSsjs(code, settings);
|
|
120
155
|
return {
|
|
121
156
|
content: [{ type: 'text', text: formatDiagnostics(diagnostics) }],
|
|
@@ -125,7 +160,8 @@ server.tool('validate_ssjs', 'Validate SSJS (Server-Side JavaScript) code for un
|
|
|
125
160
|
// Tool: validate_sfmc_html
|
|
126
161
|
// ---------------------------------------------------------------------------
|
|
127
162
|
server.tool('validate_sfmc_html', 'Validate an HTML file that contains embedded AMPscript and/or SSJS blocks. ' +
|
|
128
|
-
'Checks both languages and GTL template syntax.'
|
|
163
|
+
'Checks both languages and GTL template syntax. ' +
|
|
164
|
+
"Set target to 'next' to flag MCN-unsupported AMPscript functions and all SSJS as errors.", {
|
|
129
165
|
code: z
|
|
130
166
|
.string()
|
|
131
167
|
.describe('HTML source that may contain %%[ ]%%, %%= =%%, <script runat="server">, or {{ }} blocks.'),
|
|
@@ -136,9 +172,13 @@ server.tool('validate_sfmc_html', 'Validate an HTML file that contains embedded
|
|
|
136
172
|
.max(500)
|
|
137
173
|
.optional()
|
|
138
174
|
.describe('Maximum number of problems to return (default 100).'),
|
|
139
|
-
|
|
175
|
+
target: z
|
|
176
|
+
.enum(['engagement', 'next'])
|
|
177
|
+
.optional()
|
|
178
|
+
.describe("Target platform. Use 'next' to flag MCN-incompatible code (unsupported AMPscript functions and all SSJS)."),
|
|
179
|
+
}, ({ code, maxProblems, target }) => {
|
|
140
180
|
const limit = maxProblems ?? 100;
|
|
141
|
-
const settings = { maxNumberOfProblems: limit };
|
|
181
|
+
const settings = { maxNumberOfProblems: limit, targetPlatform: target };
|
|
142
182
|
const ampDiags = validateAmpscript(code, settings);
|
|
143
183
|
const ssjsDiags = validateSsjs(code, settings);
|
|
144
184
|
const gtlDiags = [];
|
|
@@ -166,14 +206,69 @@ server.tool('lookup_ampscript_function', 'Look up the signature, parameters, des
|
|
|
166
206
|
})
|
|
167
207
|
.join('\n');
|
|
168
208
|
const examples = fn.example ? '\n\nExample:\n' + fn.example : '';
|
|
209
|
+
// MCN compatibility badge
|
|
210
|
+
const fnMcnSince = fn.mcnSince ?? null;
|
|
211
|
+
const fnMcnNotes = fn.mcnNotes ?? null;
|
|
212
|
+
const mcnLine = fnMcnSince === null
|
|
213
|
+
? '\n\n❌ **Marketing Cloud Next:** Not supported'
|
|
214
|
+
: `\n\n✅ **Marketing Cloud Next:** Supported since API v${fnMcnSince}.0` +
|
|
215
|
+
(fnMcnNotes ? `\n> **MCN Note:** ${fnMcnNotes}` : '');
|
|
169
216
|
const text = `## ${fn.name}\n\n` +
|
|
170
217
|
`**Category:** ${fn.category ?? 'Unknown'}\n\n` +
|
|
171
218
|
`**Description:** ${fn.description ?? ''}\n\n` +
|
|
172
219
|
`**Parameters:**\n${params || ' (none)'}` +
|
|
173
|
-
examples
|
|
220
|
+
examples +
|
|
221
|
+
mcnLine;
|
|
174
222
|
return { content: [{ type: 'text', text }] };
|
|
175
223
|
});
|
|
176
224
|
// ---------------------------------------------------------------------------
|
|
225
|
+
// Tool: list_ampscript_functions
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
server.tool('list_ampscript_functions', 'List all AMPscript functions, optionally filtered by category and/or target platform. ' +
|
|
228
|
+
"Use platform: 'next' to return only functions supported in Marketing Cloud Next.", {
|
|
229
|
+
category: z
|
|
230
|
+
.string()
|
|
231
|
+
.optional()
|
|
232
|
+
.describe('Filter by function category (case-insensitive substring match), e.g. "data extension", "string", "date".'),
|
|
233
|
+
platform: z
|
|
234
|
+
.enum(['engagement', 'next'])
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Filter by target platform. Use 'next' to show only functions available in Marketing Cloud Next (API v67.0+)."),
|
|
237
|
+
}, ({ category, platform }) => {
|
|
238
|
+
const all = sfmcLanguageService.listAmpscriptFunctions();
|
|
239
|
+
let filtered = all;
|
|
240
|
+
if (platform === 'next') {
|
|
241
|
+
filtered = filtered.filter((f) => isMcnSupported(f.name));
|
|
242
|
+
}
|
|
243
|
+
if (category) {
|
|
244
|
+
const catLower = category.toLowerCase();
|
|
245
|
+
filtered = filtered.filter((f) => f.category?.toLowerCase().includes(catLower));
|
|
246
|
+
}
|
|
247
|
+
if (filtered.length === 0) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: 'text', text: 'No AMPscript functions match the filter.' }],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const platformHeader = platform === 'next'
|
|
253
|
+
? '*(Marketing Cloud Next — API v67.0+ supported only)*'
|
|
254
|
+
: '*(Marketing Cloud Engagement — all functions)*';
|
|
255
|
+
const rows = filtered
|
|
256
|
+
.map((f) => {
|
|
257
|
+
const mcnTag = getMcnApiVersion(f.name) === null ? '' : ' ✅';
|
|
258
|
+
return `- **${f.name}**${mcnTag} *(${f.category ?? 'Unknown'})*: ${f.description ?? ''}`;
|
|
259
|
+
})
|
|
260
|
+
.join('\n');
|
|
261
|
+
const legend = platform === 'next' ? '' : '\n\n*Legend: ✅ = supported in Marketing Cloud Next*';
|
|
262
|
+
return {
|
|
263
|
+
content: [
|
|
264
|
+
{
|
|
265
|
+
type: 'text',
|
|
266
|
+
text: `## AMPscript Functions ${platformHeader}\n\n${rows}${legend}`,
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
177
272
|
// Tool: lookup_ssjs_function
|
|
178
273
|
// ---------------------------------------------------------------------------
|
|
179
274
|
server.tool('lookup_ssjs_function', 'Look up the signature, parameters, and description for an SSJS function or method. ' +
|
|
@@ -280,7 +375,8 @@ server.tool('review_change', 'Review a code diff for SFMC (AMPscript, SSJS, or H
|
|
|
280
375
|
// Tool: suggest_fix
|
|
281
376
|
// ---------------------------------------------------------------------------
|
|
282
377
|
server.tool('suggest_fix', 'Generate a corrected version of SFMC code based on validation diagnostics. ' +
|
|
283
|
-
'Returns the original code with inline fix suggestions or a corrected replacement.'
|
|
378
|
+
'Returns the original code with inline fix suggestions or a corrected replacement. ' +
|
|
379
|
+
"Set target to 'next' to include MCN platform compatibility in the analysis.", {
|
|
284
380
|
code: z.string().describe('The SFMC code snippet to fix.'),
|
|
285
381
|
language: z
|
|
286
382
|
.enum(['ampscript', 'ssjs', 'html', 'auto'])
|
|
@@ -290,11 +386,15 @@ server.tool('suggest_fix', 'Generate a corrected version of SFMC code based on v
|
|
|
290
386
|
.string()
|
|
291
387
|
.optional()
|
|
292
388
|
.describe('Optional human description of the specific issue to fix.'),
|
|
293
|
-
|
|
389
|
+
target: z
|
|
390
|
+
.enum(['engagement', 'next'])
|
|
391
|
+
.optional()
|
|
392
|
+
.describe("Target platform. Use 'next' to flag MCN-incompatible functions and SSJS usage."),
|
|
393
|
+
}, ({ code, language = 'auto', issueDescription, target }) => {
|
|
294
394
|
const detectedLang = language === 'auto'
|
|
295
395
|
? detectLanguage(code)
|
|
296
396
|
: detectLanguage(code, language);
|
|
297
|
-
const settings = { maxNumberOfProblems: 50 };
|
|
397
|
+
const settings = { maxNumberOfProblems: 50, targetPlatform: target };
|
|
298
398
|
const doc = { text: code, languageId: detectedLang, uri: 'fix-target' };
|
|
299
399
|
const diagnostics = sfmcLanguageService.validate(doc, settings);
|
|
300
400
|
const lines = code.split('\n');
|
|
@@ -350,13 +450,27 @@ function getFixSuggestion(message, line, lang) {
|
|
|
350
450
|
// ---------------------------------------------------------------------------
|
|
351
451
|
// Tool: get_ampscript_completions
|
|
352
452
|
// ---------------------------------------------------------------------------
|
|
353
|
-
server.tool('get_ampscript_completions', 'Return a list of AMPscript function names, keywords, and variable names available at a given position in the code.'
|
|
453
|
+
server.tool('get_ampscript_completions', 'Return a list of AMPscript function names, keywords, and variable names available at a given position in the code. ' +
|
|
454
|
+
"Set target to 'next' to filter completions to only MCN-supported functions.", {
|
|
354
455
|
code: z.string().describe('The full AMPscript document text.'),
|
|
355
456
|
line: z.number().int().min(0).describe('Zero-based line number of the cursor position.'),
|
|
356
457
|
character: z.number().int().min(0).describe('Zero-based character offset within the line.'),
|
|
357
|
-
|
|
458
|
+
target: z
|
|
459
|
+
.enum(['engagement', 'next'])
|
|
460
|
+
.optional()
|
|
461
|
+
.describe("Target platform. Use 'next' to return only completions supported in Marketing Cloud Next."),
|
|
462
|
+
}, ({ code, line, character, target }) => {
|
|
358
463
|
const doc = { text: code, languageId: 'ampscript', uri: 'completions' };
|
|
359
|
-
|
|
464
|
+
let items = sfmcLanguageService.getCompletions(doc, { line, character });
|
|
465
|
+
if (target === 'next') {
|
|
466
|
+
items = items.filter((item) => {
|
|
467
|
+
const label = typeof item.label === 'string'
|
|
468
|
+
? item.label
|
|
469
|
+
: item.label.label;
|
|
470
|
+
// Keep keywords and variables (non-function entries); filter out MCN-unsupported functions
|
|
471
|
+
return !label.includes('(') || isMcnSupported(label.replace(/\(.*/, '').trim());
|
|
472
|
+
});
|
|
473
|
+
}
|
|
360
474
|
const formatted = items
|
|
361
475
|
.slice(0, 50)
|
|
362
476
|
.map((item) => {
|
|
@@ -367,13 +481,14 @@ server.tool('get_ampscript_completions', 'Return a list of AMPscript function na
|
|
|
367
481
|
})
|
|
368
482
|
.join('\n');
|
|
369
483
|
const total = items.length;
|
|
484
|
+
const platformNote = target === 'next' ? ' (Marketing Cloud Next — MCN-supported only)' : '';
|
|
370
485
|
return {
|
|
371
486
|
content: [
|
|
372
487
|
{
|
|
373
488
|
type: 'text',
|
|
374
489
|
text: total === 0
|
|
375
490
|
? 'No completions at this position (cursor is outside an AMPscript block).'
|
|
376
|
-
: `${total} completions available (showing up to 50):\n\n${formatted}`,
|
|
491
|
+
: `${total} completions available${platformNote} (showing up to 50):\n\n${formatted}`,
|
|
377
492
|
},
|
|
378
493
|
],
|
|
379
494
|
};
|
|
@@ -381,12 +496,27 @@ server.tool('get_ampscript_completions', 'Return a list of AMPscript function na
|
|
|
381
496
|
// ---------------------------------------------------------------------------
|
|
382
497
|
// Tool: get_ssjs_completions
|
|
383
498
|
// ---------------------------------------------------------------------------
|
|
384
|
-
server.tool('get_ssjs_completions', 'Return a list of SSJS Platform functions, WSProxy methods, and other SFMC-specific identifiers available for completion.'
|
|
499
|
+
server.tool('get_ssjs_completions', 'Return a list of SSJS Platform functions, WSProxy methods, and other SFMC-specific identifiers available for completion. ' +
|
|
500
|
+
"When target is 'next', returns an empty list with a note — SSJS is not supported in Marketing Cloud Next.", {
|
|
385
501
|
filter: z
|
|
386
502
|
.string()
|
|
387
503
|
.optional()
|
|
388
504
|
.describe('Optional prefix filter, e.g. "Platform.Function" or "WSProxy".'),
|
|
389
|
-
|
|
505
|
+
target: z
|
|
506
|
+
.enum(['engagement', 'next'])
|
|
507
|
+
.optional()
|
|
508
|
+
.describe("Target platform. Use 'next' to indicate MCN context — SSJS is not available in MCN."),
|
|
509
|
+
}, ({ filter, target }) => {
|
|
510
|
+
if (target === 'next') {
|
|
511
|
+
return {
|
|
512
|
+
content: [
|
|
513
|
+
{
|
|
514
|
+
type: 'text',
|
|
515
|
+
text: 'SSJS is not supported in Marketing Cloud Next (MCN). Use AMPscript instead. Call `get_ampscript_completions` with `target: "next"` for MCN-compatible function completions.',
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
390
520
|
const items = sfmcLanguageService.getSsjsCompletionCatalog();
|
|
391
521
|
const filtered = filter
|
|
392
522
|
? items.filter((item) => {
|
|
@@ -641,16 +771,198 @@ server.resource('mce-help-index', 'sfmc://mce/help-index', async () => {
|
|
|
641
771
|
};
|
|
642
772
|
});
|
|
643
773
|
// ---------------------------------------------------------------------------
|
|
774
|
+
// Resource: mcn-help index (bundled files)
|
|
775
|
+
// ---------------------------------------------------------------------------
|
|
776
|
+
server.resource('mcn-help-index', 'sfmc://mcn/help-index', async () => {
|
|
777
|
+
const chunks = getMcnChunks();
|
|
778
|
+
const files = [...new Set(chunks.map((c) => c.relativePath))].sort();
|
|
779
|
+
const stats = getMcnHelpStats();
|
|
780
|
+
const text = `# Bundled Marketing Cloud Next developer API docs (${stats.chunkCount} sections from ${stats.fileCount} files)\n\n` +
|
|
781
|
+
`## Files\n\n` +
|
|
782
|
+
files.map((f) => `- ${f}`).join('\n');
|
|
783
|
+
return {
|
|
784
|
+
contents: [{ uri: 'sfmc://mcn/help-index', mimeType: 'text/markdown', text }],
|
|
785
|
+
};
|
|
786
|
+
});
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
// Tool: detect_sfmc_platform
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
server.tool('detect_sfmc_platform', 'Detect the target SFMC platform for a project by checking for sentinel files. ' +
|
|
791
|
+
'Returns "engagement" if .mcdevrc.json is present, "next" if sfdx-project.json is present, ' +
|
|
792
|
+
'or "unknown" if neither is found. Call this before writing, validating, or converting code.', {
|
|
793
|
+
projectRoot: z.string().describe('Absolute path to the project root directory to inspect.'),
|
|
794
|
+
}, ({ projectRoot }) => {
|
|
795
|
+
if (fs.existsSync(path.join(projectRoot, '.mcdevrc.json'))) {
|
|
796
|
+
return { content: [{ type: 'text', text: 'engagement' }] };
|
|
797
|
+
}
|
|
798
|
+
if (fs.existsSync(path.join(projectRoot, 'sfdx-project.json'))) {
|
|
799
|
+
return { content: [{ type: 'text', text: 'next' }] };
|
|
800
|
+
}
|
|
801
|
+
return { content: [{ type: 'text', text: 'unknown' }] };
|
|
802
|
+
});
|
|
803
|
+
// ---------------------------------------------------------------------------
|
|
804
|
+
// Tool: search_mcn_help
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
server.tool('search_mcn_help', 'Search bundled Marketing Cloud Next developer API documentation. ' +
|
|
807
|
+
'Covers MCN objects, flows, segments, transactional messages, AMPscript behavior in MCN, ' +
|
|
808
|
+
'and REST/SOAP API references. Use this for MCN developer questions; ' +
|
|
809
|
+
'use search_mce_help for MCN migration or operational/admin questions.', {
|
|
810
|
+
query: z
|
|
811
|
+
.string()
|
|
812
|
+
.describe('The search query, e.g. "AMPscript FormatDate MCN", "transactional message API", "segment objects".'),
|
|
813
|
+
limit: z
|
|
814
|
+
.number()
|
|
815
|
+
.int()
|
|
816
|
+
.min(1)
|
|
817
|
+
.max(20)
|
|
818
|
+
.optional()
|
|
819
|
+
.describe('Maximum number of result chunks to return (default 5).'),
|
|
820
|
+
}, ({ query, limit = 5 }) => {
|
|
821
|
+
const hits = searchMcnHelp(query, limit);
|
|
822
|
+
if (hits.length === 0) {
|
|
823
|
+
return {
|
|
824
|
+
content: [
|
|
825
|
+
{
|
|
826
|
+
type: 'text',
|
|
827
|
+
text: `No MCN developer docs found for "${query}". The bundled index may not cover this topic — check the live Salesforce developer docs at https://developer.salesforce.com/docs/marketing/marketing-cloud-growth/`,
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const parts = hits.map(({ chunk }) => {
|
|
833
|
+
const header = `### ${chunk.heading}\n*Source: ${chunk.relativePath}*`;
|
|
834
|
+
return `${header}\n\n${chunk.body}`;
|
|
835
|
+
});
|
|
836
|
+
return {
|
|
837
|
+
content: [
|
|
838
|
+
{
|
|
839
|
+
type: 'text',
|
|
840
|
+
text: `# MCN Developer Docs — "${query}" (${hits.length} results)\n\n${parts.join('\n\n---\n\n')}`,
|
|
841
|
+
},
|
|
842
|
+
],
|
|
843
|
+
};
|
|
844
|
+
});
|
|
845
|
+
// ---------------------------------------------------------------------------
|
|
846
|
+
// Tool: search_help (unified wrapper — auto-detects platform)
|
|
847
|
+
// ---------------------------------------------------------------------------
|
|
848
|
+
server.tool('search_help', 'Unified help search that automatically detects the target platform (MCE or MCN) from the project ' +
|
|
849
|
+
'root and routes the query to the right bundled doc index. For MCN projects it searches both the ' +
|
|
850
|
+
'Marketing Cloud Next developer API reference (`search_mcn_help`) and the MCN operational/admin ' +
|
|
851
|
+
'help (`search_mce_help` with product_focus:"next") and merges the results. For MCE projects it ' +
|
|
852
|
+
'searches the full MCE help index (`search_mce_help` with product_focus:"any"). ' +
|
|
853
|
+
'Pass `projectRoot` to enable auto-detection, or set `target` explicitly to skip detection.', {
|
|
854
|
+
query: z.string().describe('Keywords or question text to search for.'),
|
|
855
|
+
projectRoot: z
|
|
856
|
+
.string()
|
|
857
|
+
.optional()
|
|
858
|
+
.describe('Absolute path to the project root directory. Used to auto-detect the platform by ' +
|
|
859
|
+
'checking for `.mcdevrc.json` (MCE) or `sfdx-project.json` (MCN). ' +
|
|
860
|
+
'Omit if you already know the target platform.'),
|
|
861
|
+
target: z
|
|
862
|
+
.enum(['engagement', 'next'])
|
|
863
|
+
.optional()
|
|
864
|
+
.describe('Override the detected platform. `engagement` → MCE help only; `next` → MCN developer ' +
|
|
865
|
+
'docs + MCN operational help. When both `projectRoot` and `target` are given, ' +
|
|
866
|
+
'`target` takes precedence.'),
|
|
867
|
+
limit: z
|
|
868
|
+
.number()
|
|
869
|
+
.int()
|
|
870
|
+
.min(1)
|
|
871
|
+
.max(20)
|
|
872
|
+
.optional()
|
|
873
|
+
.describe('Maximum total results to return across all searched indexes (default 8).'),
|
|
874
|
+
}, ({ query, projectRoot, target, limit = 8 }) => {
|
|
875
|
+
// Resolve effective platform
|
|
876
|
+
let platform = 'unknown';
|
|
877
|
+
if (target) {
|
|
878
|
+
platform = target;
|
|
879
|
+
}
|
|
880
|
+
else if (projectRoot) {
|
|
881
|
+
if (fs.existsSync(path.join(projectRoot, '.mcdevrc.json')))
|
|
882
|
+
platform = 'engagement';
|
|
883
|
+
else if (fs.existsSync(path.join(projectRoot, 'sfdx-project.json')))
|
|
884
|
+
platform = 'next';
|
|
885
|
+
}
|
|
886
|
+
const sections = [];
|
|
887
|
+
if (platform === 'next') {
|
|
888
|
+
// MCN: search both the developer API reference and the MCN operational/admin help
|
|
889
|
+
const devHits = searchMcnHelp(query, Math.ceil(limit / 2));
|
|
890
|
+
const opsHits = searchMceHelp(query, Math.floor(limit / 2), 'next');
|
|
891
|
+
if (devHits.length > 0) {
|
|
892
|
+
const parts = devHits.map(({ chunk }) => `### ${chunk.heading}\n*Source: ${chunk.relativePath}*\n\n${chunk.body}`);
|
|
893
|
+
sections.push(`## MCN Developer API Docs\n\n${parts.join('\n\n---\n\n')}`);
|
|
894
|
+
}
|
|
895
|
+
if (opsHits.length > 0) {
|
|
896
|
+
const parts = opsHits.map((h, i) => {
|
|
897
|
+
const excerpt = h.chunk.body.replaceAll(/\s+/g, ' ').slice(0, 520);
|
|
898
|
+
return (`### ${i + 1}. ${h.chunk.relativePath} — ${h.chunk.heading}\n` +
|
|
899
|
+
`**Score:** ${h.score}\n\n` +
|
|
900
|
+
`${excerpt}${h.chunk.body.length > 520 ? '…' : ''}`);
|
|
901
|
+
});
|
|
902
|
+
sections.push(`## MCN Operational / Admin Docs\n\n${parts.join('\n\n---\n\n')}`);
|
|
903
|
+
}
|
|
904
|
+
if (sections.length === 0) {
|
|
905
|
+
return {
|
|
906
|
+
content: [
|
|
907
|
+
{
|
|
908
|
+
type: 'text',
|
|
909
|
+
text: `No MCN results found for "${query}". ` +
|
|
910
|
+
'Check https://developer.salesforce.com/docs/marketing/marketing-cloud-growth/ for developer API content ' +
|
|
911
|
+
'or https://help.salesforce.com for operational guidance.',
|
|
912
|
+
},
|
|
913
|
+
],
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
// MCE (or unknown platform — search the full MCE help index)
|
|
919
|
+
const focus = 'any';
|
|
920
|
+
const hits = searchMceHelp(query, limit, focus);
|
|
921
|
+
if (hits.length === 0) {
|
|
922
|
+
const stats = getMceHelpStats();
|
|
923
|
+
const hint = stats.chunkCount === 0
|
|
924
|
+
? 'Bundled help index missing. Run `npm run bundle-mce-help` from the package folder.'
|
|
925
|
+
: `No results found for "${query}". Try broader keywords.`;
|
|
926
|
+
return { content: [{ type: 'text', text: hint }] };
|
|
927
|
+
}
|
|
928
|
+
const parts = hits.map((h, i) => {
|
|
929
|
+
const excerpt = h.chunk.body.replaceAll(/\s+/g, ' ').slice(0, 520);
|
|
930
|
+
return (`### ${i + 1}. ${h.chunk.relativePath} — ${h.chunk.heading}\n` +
|
|
931
|
+
`**Product:** ${h.chunk.productLabel}\n` +
|
|
932
|
+
`**Score:** ${h.score}\n\n` +
|
|
933
|
+
`${excerpt}${h.chunk.body.length > 520 ? '…' : ''}`);
|
|
934
|
+
});
|
|
935
|
+
sections.push(parts.join('\n\n---\n\n'));
|
|
936
|
+
}
|
|
937
|
+
const platformLabel = platform === 'next'
|
|
938
|
+
? 'Marketing Cloud Next'
|
|
939
|
+
: platform === 'engagement'
|
|
940
|
+
? 'Marketing Cloud Engagement'
|
|
941
|
+
: 'all products';
|
|
942
|
+
return {
|
|
943
|
+
content: [
|
|
944
|
+
{
|
|
945
|
+
type: 'text',
|
|
946
|
+
text: `# Help Search — "${query}" (${platformLabel})\n\n${sections.join('\n\n---\n\n')}`,
|
|
947
|
+
},
|
|
948
|
+
],
|
|
949
|
+
};
|
|
950
|
+
});
|
|
951
|
+
// ---------------------------------------------------------------------------
|
|
644
952
|
// Prompt: writeAmpscript
|
|
645
953
|
// ---------------------------------------------------------------------------
|
|
646
954
|
server.prompt('writeAmpscript', 'Generate AMPscript code for a specific task. Ensures correct syntax, proper use of delimiters, ' +
|
|
647
|
-
'and references to real SFMC functions.', {
|
|
955
|
+
'and references to real SFMC functions. Supports both Marketing Cloud Engagement and Next targets.', {
|
|
648
956
|
task: z.string().describe('Description of what the AMPscript code should do.'),
|
|
649
957
|
context: z
|
|
650
958
|
.string()
|
|
651
959
|
.optional()
|
|
652
960
|
.describe('Optional context about the email, landing page, or SFMC configuration.'),
|
|
653
|
-
|
|
961
|
+
target: z
|
|
962
|
+
.enum(['engagement', 'next'])
|
|
963
|
+
.optional()
|
|
964
|
+
.describe("Target platform. Use 'next' to restrict output to MCN-supported AMPscript functions only."),
|
|
965
|
+
}, ({ task, context, target }) => ({
|
|
654
966
|
messages: [
|
|
655
967
|
{
|
|
656
968
|
role: 'user',
|
|
@@ -658,7 +970,7 @@ server.prompt('writeAmpscript', 'Generate AMPscript code for a specific task. En
|
|
|
658
970
|
type: 'text',
|
|
659
971
|
text: [
|
|
660
972
|
'You are an expert Salesforce Marketing Cloud developer.',
|
|
661
|
-
|
|
973
|
+
`Generate AMPscript code for the following task. Target platform: **${target === 'next' ? 'Marketing Cloud Next (MCN)' : 'Marketing Cloud Engagement (MCE)'}**.`,
|
|
662
974
|
'',
|
|
663
975
|
'## Rules',
|
|
664
976
|
'- Use `%%[ ]%%` for block-level code and `%%= =%%` for inline output.',
|
|
@@ -668,6 +980,16 @@ server.prompt('writeAmpscript', 'Generate AMPscript code for a specific task. En
|
|
|
668
980
|
'- All function names are case-insensitive but conventionally PascalCase.',
|
|
669
981
|
'- Do NOT use ES6+ syntax (this is not JavaScript).',
|
|
670
982
|
'- Validate your output against the AMPscript function catalog.',
|
|
983
|
+
target === 'next'
|
|
984
|
+
? [
|
|
985
|
+
'',
|
|
986
|
+
'## Marketing Cloud Next (MCN) constraints',
|
|
987
|
+
'- Only use functions available in MCN (API v67.0+). Call `list_ampscript_functions` with `platform: "next"` to verify.',
|
|
988
|
+
'- FormatDate uses Java SimpleDateFormat patterns (not .NET). Example: `yyyy-MM-dd` instead of `yyyy-MM-dd`.',
|
|
989
|
+
'- Lookup requires an even number of search arguments (column/value pairs).',
|
|
990
|
+
'- StringToDate returns a locale-formatted string in MCN — do not chain it with FormatDate.',
|
|
991
|
+
].join('\n')
|
|
992
|
+
: '',
|
|
671
993
|
'',
|
|
672
994
|
`## Task`,
|
|
673
995
|
task,
|
|
@@ -683,37 +1005,55 @@ server.prompt('writeAmpscript', 'Generate AMPscript code for a specific task. En
|
|
|
683
1005
|
// Prompt: writeSsjs
|
|
684
1006
|
// ---------------------------------------------------------------------------
|
|
685
1007
|
server.prompt('writeSsjs', 'Generate SSJS (Server-Side JavaScript) code for a specific task. Ensures ES5-compatible syntax and ' +
|
|
686
|
-
'correct use of SFMC Platform APIs.', {
|
|
1008
|
+
'correct use of SFMC Platform APIs. If target is "next", redirects to AMPscript instead.', {
|
|
687
1009
|
task: z.string().describe('Description of what the SSJS code should do.'),
|
|
688
1010
|
context: z
|
|
689
1011
|
.string()
|
|
690
1012
|
.optional()
|
|
691
1013
|
.describe('Optional context about the SFMC environment or assets involved.'),
|
|
692
|
-
|
|
1014
|
+
target: z
|
|
1015
|
+
.enum(['engagement', 'next'])
|
|
1016
|
+
.optional()
|
|
1017
|
+
.describe("Target platform. If 'next', the prompt will explain that SSJS is not supported and suggest AMPscript alternatives."),
|
|
1018
|
+
}, ({ task, context, target }) => ({
|
|
693
1019
|
messages: [
|
|
694
1020
|
{
|
|
695
1021
|
role: 'user',
|
|
696
1022
|
content: {
|
|
697
1023
|
type: 'text',
|
|
698
|
-
text:
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1024
|
+
text: target === 'next'
|
|
1025
|
+
? [
|
|
1026
|
+
'⚠️ **Marketing Cloud Next (MCN) does not support SSJS.** SSJS is not available in MCN.',
|
|
1027
|
+
'',
|
|
1028
|
+
'Instead, use **AMPscript** — MCN supports a subset of AMPscript functions (API v67.0+).',
|
|
1029
|
+
'Call `list_ampscript_functions` with `platform: "next"` to see which functions are available.',
|
|
1030
|
+
'Then call `writeAmpscript` with `target: "next"` to generate MCN-compatible AMPscript code.',
|
|
1031
|
+
'',
|
|
1032
|
+
`## Original task (for AMPscript rewrite reference)`,
|
|
1033
|
+
task,
|
|
1034
|
+
context ? `\n## Context\n${context}` : '',
|
|
1035
|
+
]
|
|
1036
|
+
.filter(Boolean)
|
|
1037
|
+
.join('\n')
|
|
1038
|
+
: [
|
|
1039
|
+
'You are an expert Salesforce Marketing Cloud developer.',
|
|
1040
|
+
'Generate SSJS code for the following task.',
|
|
1041
|
+
'',
|
|
1042
|
+
'## Rules',
|
|
1043
|
+
'- SSJS runs in an ES5 engine. Use `var`, not `let`/`const`.',
|
|
1044
|
+
'- No arrow functions, template literals, destructuring, or `class`.',
|
|
1045
|
+
'- Wrap code in `<script runat="server">` ... `</script>`.',
|
|
1046
|
+
'- Use `Platform.Load("core", "1.1.5");` before accessing Core library objects.',
|
|
1047
|
+
'- Use `Platform.Function.*` for SFMC-specific functions (e.g. `Platform.Function.Lookup`).',
|
|
1048
|
+
'- For SOAP API calls, use WSProxy: `var prox = new Script.Util.WSProxy();`',
|
|
1049
|
+
'- Use `Platform.Response.Write()` to output content.',
|
|
1050
|
+
'',
|
|
1051
|
+
`## Task`,
|
|
1052
|
+
task,
|
|
1053
|
+
context ? `\n## Context\n${context}` : '',
|
|
1054
|
+
]
|
|
1055
|
+
.filter(Boolean)
|
|
1056
|
+
.join('\n'),
|
|
717
1057
|
},
|
|
718
1058
|
},
|
|
719
1059
|
],
|
|
@@ -721,17 +1061,25 @@ server.prompt('writeSsjs', 'Generate SSJS (Server-Side JavaScript) code for a sp
|
|
|
721
1061
|
// ---------------------------------------------------------------------------
|
|
722
1062
|
// Prompt: reviewSfmcCode
|
|
723
1063
|
// ---------------------------------------------------------------------------
|
|
724
|
-
server.prompt('reviewSfmcCode', 'Review SFMC code for correctness, best practices, and potential issues. Provides actionable feedback.'
|
|
1064
|
+
server.prompt('reviewSfmcCode', 'Review SFMC code for correctness, best practices, and potential issues. Provides actionable feedback. ' +
|
|
1065
|
+
"Set target to 'next' to also check for Marketing Cloud Next compatibility.", {
|
|
725
1066
|
code: z.string().describe('The SFMC code to review.'),
|
|
726
1067
|
language: z.enum(['ampscript', 'ssjs', 'html', 'auto']).optional(),
|
|
727
1068
|
focus: z
|
|
728
1069
|
.string()
|
|
729
1070
|
.optional()
|
|
730
1071
|
.describe('Optional focus area, e.g. "security", "performance", "data extension usage".'),
|
|
731
|
-
|
|
1072
|
+
target: z
|
|
1073
|
+
.enum(['engagement', 'next'])
|
|
1074
|
+
.optional()
|
|
1075
|
+
.describe("Target platform. Use 'next' to include MCN compatibility in the review (flags unsupported functions and SSJS)."),
|
|
1076
|
+
}, ({ code, language = 'auto', focus, target }) => {
|
|
732
1077
|
const detectedLang = language === 'auto'
|
|
733
1078
|
? detectLanguage(code)
|
|
734
1079
|
: detectLanguage(code, language);
|
|
1080
|
+
const platformNote = target === 'next'
|
|
1081
|
+
? '\n- **Marketing Cloud Next compatibility**: Flag any AMPscript functions not supported in MCN (API v67.0+), and any SSJS blocks (SSJS is not supported in MCN).'
|
|
1082
|
+
: '';
|
|
735
1083
|
return {
|
|
736
1084
|
messages: [
|
|
737
1085
|
{
|
|
@@ -739,7 +1087,11 @@ server.prompt('reviewSfmcCode', 'Review SFMC code for correctness, best practice
|
|
|
739
1087
|
content: {
|
|
740
1088
|
type: 'text',
|
|
741
1089
|
text: [
|
|
742
|
-
`You are an expert Salesforce Marketing Cloud developer reviewing ${detectedLang.toUpperCase()} code
|
|
1090
|
+
`You are an expert Salesforce Marketing Cloud developer reviewing ${detectedLang.toUpperCase()} code` +
|
|
1091
|
+
(target
|
|
1092
|
+
? ` for target platform **${target === 'next' ? 'Marketing Cloud Next (MCN)' : 'Marketing Cloud Engagement (MCE)'}**`
|
|
1093
|
+
: '') +
|
|
1094
|
+
'.',
|
|
743
1095
|
'Identify bugs, anti-patterns, performance issues, and security concerns.',
|
|
744
1096
|
focus ? `Focus especially on: ${focus}` : '',
|
|
745
1097
|
'',
|
|
@@ -756,14 +1108,14 @@ server.prompt('reviewSfmcCode', 'Review SFMC code for correctness, best practice
|
|
|
756
1108
|
'- Correct function names and argument counts',
|
|
757
1109
|
'- Correct comment syntax (/* */ only)',
|
|
758
1110
|
'- Proper variable declaration with @',
|
|
759
|
-
].join('\n')
|
|
1111
|
+
].join('\n') + platformNote
|
|
760
1112
|
: [
|
|
761
1113
|
'- No ES6+ syntax (var, not let/const; no arrow functions)',
|
|
762
1114
|
'- Platform.Load before Core library objects',
|
|
763
1115
|
'- Correct Platform.Function calls',
|
|
764
1116
|
'- WSProxy error handling',
|
|
765
1117
|
'- No sensitive data in logs or responses',
|
|
766
|
-
].join('\n'),
|
|
1118
|
+
].join('\n') + platformNote,
|
|
767
1119
|
]
|
|
768
1120
|
.filter(Boolean)
|
|
769
1121
|
.join('\n'),
|
|
@@ -775,8 +1127,10 @@ server.prompt('reviewSfmcCode', 'Review SFMC code for correctness, best practice
|
|
|
775
1127
|
// ---------------------------------------------------------------------------
|
|
776
1128
|
// Prompt: convertAmpscriptToSsjs
|
|
777
1129
|
// ---------------------------------------------------------------------------
|
|
778
|
-
server.prompt('convertAmpscriptToSsjs', 'Convert AMPscript code to equivalent SSJS, preserving business logic while adapting to SSJS APIs.'
|
|
779
|
-
|
|
1130
|
+
server.prompt('convertAmpscriptToSsjs', 'Convert AMPscript code to equivalent SSJS, preserving business logic while adapting to SSJS APIs. ' +
|
|
1131
|
+
'Calls the convertAmpscriptToSsjs tool first for deterministic rule-based conversion, ' +
|
|
1132
|
+
'then applies AI reasoning to handle any MANUAL_REWRITE_REQUIRED sections.', {
|
|
1133
|
+
ampscript: z.string().describe('The AMPscript code to convert to SSJS.'),
|
|
780
1134
|
}, ({ ampscript }) => ({
|
|
781
1135
|
messages: [
|
|
782
1136
|
{
|
|
@@ -784,19 +1138,25 @@ server.prompt('convertAmpscriptToSsjs', 'Convert AMPscript code to equivalent SS
|
|
|
784
1138
|
content: {
|
|
785
1139
|
type: 'text',
|
|
786
1140
|
text: [
|
|
787
|
-
'
|
|
1141
|
+
'You are an expert Salesforce Marketing Cloud developer converting AMPscript to SSJS.',
|
|
1142
|
+
'',
|
|
1143
|
+
'## Instructions',
|
|
1144
|
+
'1. Call the `convertAmpscriptToSsjs` **tool** with the code below to get a deterministic conversion.',
|
|
1145
|
+
'2. Review the `flaggedSections` in the result — these are constructs the tool could not convert automatically.',
|
|
1146
|
+
'3. For each flagged section, apply your expertise:',
|
|
1147
|
+
' - Email-specific functions (ContentArea, TreatAsContent, etc.) → use equivalent SSJS Content or Server.EscapeJavaScript approaches',
|
|
1148
|
+
' - Personalization strings (%%FirstName%% etc.) → use Platform.Variable/Recipient equivalents',
|
|
1149
|
+
' - AMPscript-only data lookups → translate to SSJS DataExtension / WSProxy where appropriate',
|
|
1150
|
+
'4. Produce a single final SSJS code block.',
|
|
1151
|
+
'5. Add a short change log as a bulleted list.',
|
|
788
1152
|
'',
|
|
789
|
-
'##
|
|
790
|
-
'-
|
|
791
|
-
'- AMPscript `LookupRows()` → `Platform.Function.LookupRows()` in SSJS',
|
|
792
|
-
'- AMPscript `@variable` → `var variable` in SSJS',
|
|
793
|
-
'- AMPscript `SET @x = value` → `var x = value;` in SSJS',
|
|
794
|
-
'- AMPscript `IF @x == "y" THEN` → `if (x === "y") {` in SSJS',
|
|
795
|
-
'- AMPscript `OUTPUT(CONCAT(...))` → `Platform.Response.Write(...)` in SSJS',
|
|
796
|
-
'- AMPscript `FOR @i = 1 TO 10 DO` → `for (var i = 1; i <= 10; i++) {` in SSJS',
|
|
797
|
-
'- Use `var`, not `let`/`const`. No arrow functions or template literals.',
|
|
1153
|
+
'## SSJS rules',
|
|
1154
|
+
'- Use `var`, not `let`/`const`. No arrow functions, template literals, or destructuring.',
|
|
798
1155
|
'- Wrap in `<script runat="server">...</script>`.',
|
|
799
|
-
'- Add `Platform.Load("
|
|
1156
|
+
'- Add `Platform.Load("Core", "1.1.5");` if using DataExtension, Rows, etc.',
|
|
1157
|
+
'- AMPscript functions → `Platform.Function.*` equivalents.',
|
|
1158
|
+
'- AMPscript `@variable` → bare `variable` in SSJS.',
|
|
1159
|
+
'- AMPscript `Output` / `OutputLine` → `Platform.Response.Write()`.',
|
|
800
1160
|
'',
|
|
801
1161
|
'## AMPscript to convert',
|
|
802
1162
|
'```ampscript',
|
|
@@ -869,6 +1229,414 @@ server.prompt('answerMceHowTo', 'Answer a Marketing Cloud **administration or se
|
|
|
869
1229
|
};
|
|
870
1230
|
});
|
|
871
1231
|
// ---------------------------------------------------------------------------
|
|
1232
|
+
// Tool: check_mcn_compatibility
|
|
1233
|
+
// ---------------------------------------------------------------------------
|
|
1234
|
+
server.tool('check_mcn_compatibility', 'Analyze one or more AMPscript/HTML files for Marketing Cloud Next (MCN) readiness. ' +
|
|
1235
|
+
'Returns an executive summary and a per-file, per-function report with migration difficulty. ' +
|
|
1236
|
+
'SSJS blocks that only use Platform.Function.* calls are classified as "Needs conversion" (not "Not migratable"). ' +
|
|
1237
|
+
'Use this tool before using rewrite_for_mcn.', {
|
|
1238
|
+
files: z
|
|
1239
|
+
.array(z.object({
|
|
1240
|
+
filename: z.string().describe('File name (e.g. "email-template.html").'),
|
|
1241
|
+
content: z.string().describe('Full file content to analyze.'),
|
|
1242
|
+
}))
|
|
1243
|
+
.describe('List of files to analyze.'),
|
|
1244
|
+
}, ({ files }) => {
|
|
1245
|
+
const results = [];
|
|
1246
|
+
for (const { filename, content } of files) {
|
|
1247
|
+
const ampFunctions = [];
|
|
1248
|
+
const ssjsBlocks = [];
|
|
1249
|
+
// 1. Extract and classify AMPscript function calls
|
|
1250
|
+
const callSites = extractAmpscriptFunctionCalls(content);
|
|
1251
|
+
for (const site of callSites) {
|
|
1252
|
+
const mcnSince = getMcnApiVersion(site.name);
|
|
1253
|
+
const notes = getMcnNotes(site.name);
|
|
1254
|
+
let status;
|
|
1255
|
+
let reason;
|
|
1256
|
+
if (mcnSince !== null && notes === null) {
|
|
1257
|
+
status = 'supported';
|
|
1258
|
+
reason = '—';
|
|
1259
|
+
}
|
|
1260
|
+
else if (mcnSince !== null && notes !== null) {
|
|
1261
|
+
status = 'needs-review';
|
|
1262
|
+
reason = notes;
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
status = 'not-supported';
|
|
1266
|
+
reason = 'No MCN equivalent';
|
|
1267
|
+
// Refine for CloudPages-specific functions
|
|
1268
|
+
if (CLOUDPAGES_ONLY_FUNCTIONS.has(site.name.toLowerCase())) {
|
|
1269
|
+
reason = `${site.name}() is a CloudPages-specific function (not available in MCN)`;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
ampFunctions.push({ name: site.name, line: site.line + 1, status, reason });
|
|
1273
|
+
}
|
|
1274
|
+
// 2. Detect and classify SSJS blocks
|
|
1275
|
+
const ssjsBlockPattern = /<script[^>]+runat=['"]?server['"]?[^>]*>([\s\S]*?)<\/script>/gi;
|
|
1276
|
+
let blockMatch;
|
|
1277
|
+
let blockIndex = 0;
|
|
1278
|
+
while ((blockMatch = ssjsBlockPattern.exec(content)) !== null) {
|
|
1279
|
+
blockIndex++;
|
|
1280
|
+
const blockCode = blockMatch[1];
|
|
1281
|
+
const lineApprox = content.slice(0, blockMatch.index).split('\n').length;
|
|
1282
|
+
// Check for non-migratable patterns
|
|
1283
|
+
let notMigratableReason = '';
|
|
1284
|
+
for (const { pattern, reason } of NON_MIGRATABLE_SSJS_PATTERNS) {
|
|
1285
|
+
pattern.lastIndex = 0;
|
|
1286
|
+
if (pattern.test(blockCode)) {
|
|
1287
|
+
notMigratableReason = reason;
|
|
1288
|
+
break;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (notMigratableReason) {
|
|
1292
|
+
ssjsBlocks.push({
|
|
1293
|
+
index: blockIndex,
|
|
1294
|
+
lineApprox,
|
|
1295
|
+
status: 'not-migratable',
|
|
1296
|
+
reason: notMigratableReason,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
ssjsBlocks.push({
|
|
1301
|
+
index: blockIndex,
|
|
1302
|
+
lineApprox,
|
|
1303
|
+
status: 'needs-conversion',
|
|
1304
|
+
reason: 'Contains only convertible SSJS patterns — use convertSsjsToAmpscript',
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
// 3. Assess per-file difficulty
|
|
1309
|
+
const hasCloudPagesFn = ampFunctions.some((f) => CLOUDPAGES_ONLY_FUNCTIONS.has(f.name.toLowerCase()) &&
|
|
1310
|
+
f.status === 'not-supported');
|
|
1311
|
+
const hasNotMigratableSsjs = ssjsBlocks.some((b) => b.status === 'not-migratable');
|
|
1312
|
+
const hasUnsupportedAmp = ampFunctions.some((f) => f.status === 'not-supported' &&
|
|
1313
|
+
!CLOUDPAGES_ONLY_FUNCTIONS.has(f.name.toLowerCase()));
|
|
1314
|
+
const hasConvertibleSsjs = ssjsBlocks.some((b) => b.status === 'needs-conversion');
|
|
1315
|
+
const hasNeedsReview = ampFunctions.some((f) => f.status === 'needs-review');
|
|
1316
|
+
let difficulty;
|
|
1317
|
+
if (hasCloudPagesFn || hasNotMigratableSsjs) {
|
|
1318
|
+
difficulty = 'not-migratable';
|
|
1319
|
+
}
|
|
1320
|
+
else if (hasUnsupportedAmp || hasConvertibleSsjs) {
|
|
1321
|
+
difficulty = 'significant';
|
|
1322
|
+
}
|
|
1323
|
+
else if (hasNeedsReview) {
|
|
1324
|
+
difficulty = 'minor';
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
difficulty = 'ready';
|
|
1328
|
+
}
|
|
1329
|
+
results.push({ filename, difficulty, ampFunctions, ssjsBlocks });
|
|
1330
|
+
}
|
|
1331
|
+
// 4. Build Markdown report
|
|
1332
|
+
const difficultyLabel = {
|
|
1333
|
+
ready: 'Ready',
|
|
1334
|
+
minor: 'Minor changes needed',
|
|
1335
|
+
significant: 'Significant rewrite required',
|
|
1336
|
+
'not-migratable': 'Not migratable',
|
|
1337
|
+
};
|
|
1338
|
+
const counts = {
|
|
1339
|
+
ready: results.filter((r) => r.difficulty === 'ready').length,
|
|
1340
|
+
minor: results.filter((r) => r.difficulty === 'minor').length,
|
|
1341
|
+
significant: results.filter((r) => r.difficulty === 'significant').length,
|
|
1342
|
+
notMigratable: results.filter((r) => r.difficulty === 'not-migratable').length,
|
|
1343
|
+
};
|
|
1344
|
+
const totalFiles = results.length;
|
|
1345
|
+
const effortLevels = counts.notMigratable > 0
|
|
1346
|
+
? 'Not possible'
|
|
1347
|
+
: counts.significant > 0
|
|
1348
|
+
? 'Hard'
|
|
1349
|
+
: counts.minor > 0
|
|
1350
|
+
? 'Medium'
|
|
1351
|
+
: 'Easy';
|
|
1352
|
+
const summaryLines = [
|
|
1353
|
+
'## MCN Compatibility Report',
|
|
1354
|
+
'',
|
|
1355
|
+
'### Executive Summary',
|
|
1356
|
+
`- ${counts.ready}/${totalFiles} files: Ready`,
|
|
1357
|
+
`- ${counts.minor}/${totalFiles} files: Minor changes needed`,
|
|
1358
|
+
`- ${counts.significant}/${totalFiles} files: Significant rewrite required`,
|
|
1359
|
+
`- ${counts.notMigratable}/${totalFiles} files: Not migratable`,
|
|
1360
|
+
'',
|
|
1361
|
+
`Overall migration effort: **${effortLevels}**`,
|
|
1362
|
+
'',
|
|
1363
|
+
'---',
|
|
1364
|
+
];
|
|
1365
|
+
const fileLines = [];
|
|
1366
|
+
for (const result of results) {
|
|
1367
|
+
const label = difficultyLabel[result.difficulty] ?? result.difficulty;
|
|
1368
|
+
fileLines.push('', `### ${result.filename} — ${label}`, '');
|
|
1369
|
+
if (result.ssjsBlocks.length > 0) {
|
|
1370
|
+
fileLines.push('**SSJS Blocks:**', '');
|
|
1371
|
+
for (const block of result.ssjsBlocks) {
|
|
1372
|
+
const icon = block.status === 'needs-conversion' ? '🔄' : '🚫';
|
|
1373
|
+
const statusLabel = block.status === 'needs-conversion' ? 'Needs conversion' : 'Not migratable';
|
|
1374
|
+
fileLines.push(`${icon} SSJS block ${block.index} (≈line ${block.lineApprox}): **${statusLabel}** — ${block.reason}`);
|
|
1375
|
+
}
|
|
1376
|
+
fileLines.push('');
|
|
1377
|
+
}
|
|
1378
|
+
if (result.ampFunctions.length > 0) {
|
|
1379
|
+
fileLines.push('| Function | Line | Status | Reason |', '|---|---|---|---|');
|
|
1380
|
+
for (const fn of result.ampFunctions) {
|
|
1381
|
+
const icon = fn.status === 'supported'
|
|
1382
|
+
? '✅'
|
|
1383
|
+
: fn.status === 'needs-review'
|
|
1384
|
+
? '⚠️'
|
|
1385
|
+
: '❌';
|
|
1386
|
+
const statusLabel = fn.status === 'supported'
|
|
1387
|
+
? 'Supported'
|
|
1388
|
+
: fn.status === 'needs-review'
|
|
1389
|
+
? 'Needs review'
|
|
1390
|
+
: 'Not supported';
|
|
1391
|
+
fileLines.push(`| ${fn.name} | ${fn.line} | ${icon} ${statusLabel} | ${fn.reason} |`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
else if (result.ssjsBlocks.length === 0) {
|
|
1395
|
+
fileLines.push('*No AMPscript functions or SSJS blocks found.*');
|
|
1396
|
+
}
|
|
1397
|
+
fileLines.push('', '---');
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
content: [
|
|
1401
|
+
{
|
|
1402
|
+
type: 'text',
|
|
1403
|
+
text: [...summaryLines, ...fileLines].join('\n'),
|
|
1404
|
+
},
|
|
1405
|
+
],
|
|
1406
|
+
};
|
|
1407
|
+
});
|
|
1408
|
+
// ---------------------------------------------------------------------------
|
|
1409
|
+
// Tool: rewrite_for_mcn
|
|
1410
|
+
// ---------------------------------------------------------------------------
|
|
1411
|
+
server.tool('rewrite_for_mcn', 'Deterministically rewrite AMPscript (and optionally SSJS) code for Marketing Cloud Next compatibility. ' +
|
|
1412
|
+
'Handles: FormatDate(StringToDate(x)) simplification, .NET→Java format strings, ' +
|
|
1413
|
+
'MCE-only function annotations, SSJS→AMPscript rule-based conversion. ' +
|
|
1414
|
+
'Flags complex constructs as MANUAL_REWRITE_REQUIRED. ' +
|
|
1415
|
+
'Use the rewrite_for_mcn PROMPT (not this tool) for AI-enhanced handling of MANUAL_REWRITE_REQUIRED sections.', {
|
|
1416
|
+
code: z.string().describe('The AMPscript or HTML code to rewrite for MCN.'),
|
|
1417
|
+
context: z
|
|
1418
|
+
.enum(['email', 'cloudpage', 'auto'])
|
|
1419
|
+
.optional()
|
|
1420
|
+
.default('auto')
|
|
1421
|
+
.describe("Content context. Use 'cloudpage' to immediately flag as not migratable. Default 'auto' detects context."),
|
|
1422
|
+
}, ({ code, context = 'auto' }) => {
|
|
1423
|
+
// CloudPage detection
|
|
1424
|
+
const isCloudPage = context === 'cloudpage' ||
|
|
1425
|
+
(context === 'auto' &&
|
|
1426
|
+
/\b(CloudPagesURL|RequestParameter|QueryParameter)\s*\(/i.test(code));
|
|
1427
|
+
if (isCloudPage) {
|
|
1428
|
+
return {
|
|
1429
|
+
content: [
|
|
1430
|
+
{
|
|
1431
|
+
type: 'text',
|
|
1432
|
+
text: JSON.stringify({
|
|
1433
|
+
rewrittenCode: code,
|
|
1434
|
+
changes: [],
|
|
1435
|
+
nonMigratableItems: [
|
|
1436
|
+
{
|
|
1437
|
+
line: 1,
|
|
1438
|
+
code: 'CloudPage context detected',
|
|
1439
|
+
reason: 'CloudPages (RequestParameter, QueryParameter, CloudPagesURL, Redirect) are not available in Marketing Cloud Next. This use case cannot be migrated.',
|
|
1440
|
+
},
|
|
1441
|
+
],
|
|
1442
|
+
difficulty: 'not-migratable',
|
|
1443
|
+
summary: 'CloudPage context — not available in Marketing Cloud Next',
|
|
1444
|
+
}),
|
|
1445
|
+
},
|
|
1446
|
+
],
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
// Rewrite AMPscript portions
|
|
1450
|
+
const ampResult = rewriteAmpForMcn(code, {
|
|
1451
|
+
isMcnSupportedFn: isMcnSupported,
|
|
1452
|
+
getMcnNotesFn: getMcnNotes,
|
|
1453
|
+
});
|
|
1454
|
+
let finalCode = ampResult.rewrittenCode;
|
|
1455
|
+
const allChanges = [...ampResult.changes];
|
|
1456
|
+
const allNonMigratable = [...ampResult.nonMigratableItems];
|
|
1457
|
+
// Convert SSJS blocks to AMPscript
|
|
1458
|
+
const ssjsPattern = /<script[^>]+runat=['"]?server['"]?[^>]*>[\s\S]*?<\/script>/gi;
|
|
1459
|
+
finalCode = finalCode.replaceAll(ssjsPattern, (ssjsBlock) => {
|
|
1460
|
+
if (!isSsjsBlockConvertible(ssjsBlock)) {
|
|
1461
|
+
allNonMigratable.push({
|
|
1462
|
+
line: 0,
|
|
1463
|
+
code: ssjsBlock.slice(0, 100),
|
|
1464
|
+
reason: 'SSJS block contains non-migratable constructs',
|
|
1465
|
+
});
|
|
1466
|
+
return (ssjsBlock +
|
|
1467
|
+
'\n%%-- MANUAL_REWRITE_REQUIRED: Non-migratable SSJS block above --%% ');
|
|
1468
|
+
}
|
|
1469
|
+
const ssjsResult = ssjsToAmpscript(ssjsBlock);
|
|
1470
|
+
for (const c of ssjsResult.changes) {
|
|
1471
|
+
allChanges.push({ line: c.line, type: 'rewritten', description: c.description });
|
|
1472
|
+
}
|
|
1473
|
+
for (const f of ssjsResult.flaggedSections) {
|
|
1474
|
+
allNonMigratable.push({ line: f.line, code: f.code, reason: f.reason });
|
|
1475
|
+
}
|
|
1476
|
+
return ssjsResult.convertedCode;
|
|
1477
|
+
});
|
|
1478
|
+
// Reassess difficulty
|
|
1479
|
+
const hasMigratable = allNonMigratable.length > 0;
|
|
1480
|
+
const difficulty = hasMigratable && allNonMigratable.some((i) => i.reason.includes('not-migratable'))
|
|
1481
|
+
? 'not-migratable'
|
|
1482
|
+
: ampResult.difficulty;
|
|
1483
|
+
const result = {
|
|
1484
|
+
rewrittenCode: finalCode,
|
|
1485
|
+
changes: allChanges,
|
|
1486
|
+
nonMigratableItems: allNonMigratable,
|
|
1487
|
+
difficulty,
|
|
1488
|
+
summary: ampResult.summary,
|
|
1489
|
+
};
|
|
1490
|
+
return {
|
|
1491
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1492
|
+
};
|
|
1493
|
+
});
|
|
1494
|
+
// ---------------------------------------------------------------------------
|
|
1495
|
+
// Prompt: rewrite_for_mcn
|
|
1496
|
+
// ---------------------------------------------------------------------------
|
|
1497
|
+
server.prompt('rewrite_for_mcn', 'Rewrite AMPscript/SSJS code to be compatible with Marketing Cloud Next. ' +
|
|
1498
|
+
'Calls the rewrite_for_mcn tool first for deterministic rewrites, then applies ' +
|
|
1499
|
+
'AI reasoning to handle any MANUAL_REWRITE_REQUIRED sections.', {
|
|
1500
|
+
code: z.string().describe('The AMPscript or HTML code to rewrite for MCN.'),
|
|
1501
|
+
context: z
|
|
1502
|
+
.enum(['email', 'cloudpage', 'auto'])
|
|
1503
|
+
.optional()
|
|
1504
|
+
.describe("Content context — use 'cloudpage' to immediately flag as not migratable."),
|
|
1505
|
+
}, ({ code, context = 'auto' }) => ({
|
|
1506
|
+
messages: [
|
|
1507
|
+
{
|
|
1508
|
+
role: 'user',
|
|
1509
|
+
content: {
|
|
1510
|
+
type: 'text',
|
|
1511
|
+
text: [
|
|
1512
|
+
'You are an expert Salesforce Marketing Cloud developer helping migrate code to Marketing Cloud Next (MCN).',
|
|
1513
|
+
'',
|
|
1514
|
+
'## Instructions',
|
|
1515
|
+
'1. Call the `rewrite_for_mcn` **tool** with the code and context below.',
|
|
1516
|
+
'2. Review the `nonMigratableItems` in the result.',
|
|
1517
|
+
'3. For each item marked `MANUAL_REWRITE_REQUIRED`, attempt an AI-driven conversion:',
|
|
1518
|
+
' - Complex loops → AMPscript FOR/NEXT blocks if possible',
|
|
1519
|
+
' - SSJS try/catch → AMPscript RaiseError() with conditional guards',
|
|
1520
|
+
' - Array methods (forEach, map, filter) → AMPscript FOR loops over RowSets',
|
|
1521
|
+
' - JSON.parse/stringify → BuildRowsetFromJson() / row operations',
|
|
1522
|
+
'4. Produce a single final rewritten code block.',
|
|
1523
|
+
'5. Include a concise change log as a bulleted list.',
|
|
1524
|
+
'',
|
|
1525
|
+
'## MCN rules to enforce',
|
|
1526
|
+
'- AMPscript only — no SSJS blocks (`<script runat="server">`) in the final output.',
|
|
1527
|
+
'- FormatDate() must use Java SimpleDateFormat strings, not .NET strings.',
|
|
1528
|
+
'- FormatDate(StringToDate(x), fmt) must be simplified to FormatDate(x, fmt).',
|
|
1529
|
+
'- Lookup() must have an even number of search column/value pairs.',
|
|
1530
|
+
'- Functions not in the MCN catalog must be replaced or removed.',
|
|
1531
|
+
'- CloudPages context (RequestParameter, QueryParameter, CloudPagesURL) → cannot migrate; explain why.',
|
|
1532
|
+
'',
|
|
1533
|
+
`## Context: ${context}`,
|
|
1534
|
+
'',
|
|
1535
|
+
'## Code to rewrite',
|
|
1536
|
+
'```',
|
|
1537
|
+
code,
|
|
1538
|
+
'```',
|
|
1539
|
+
].join('\n'),
|
|
1540
|
+
},
|
|
1541
|
+
},
|
|
1542
|
+
],
|
|
1543
|
+
}));
|
|
1544
|
+
// ---------------------------------------------------------------------------
|
|
1545
|
+
// Tool: convertSsjsToAmpscript
|
|
1546
|
+
// ---------------------------------------------------------------------------
|
|
1547
|
+
server.tool('convertSsjsToAmpscript', 'Deterministically convert SSJS (Server-Side JavaScript) code to AMPscript using rule-based transformations. ' +
|
|
1548
|
+
'Handles: Platform.Function.* → AMPscript equivalents, Platform.Variable.GetValue/SetValue → @variable, ' +
|
|
1549
|
+
'Platform.Response.Write → OutputLine, var declarations → SET, if/else → IF/ELSE/ENDIF. ' +
|
|
1550
|
+
'Flags JS-native constructs (try/catch, array methods, JSON, regex) as MANUAL_REWRITE_REQUIRED. ' +
|
|
1551
|
+
'Use the convertSsjsToAmpscript PROMPT for AI-enhanced handling of flagged sections.', {
|
|
1552
|
+
code: z
|
|
1553
|
+
.string()
|
|
1554
|
+
.describe('The SSJS code to convert (may include <script runat="server"> tags).'),
|
|
1555
|
+
}, ({ code }) => {
|
|
1556
|
+
const result = ssjsToAmpscript(code);
|
|
1557
|
+
return {
|
|
1558
|
+
content: [
|
|
1559
|
+
{
|
|
1560
|
+
type: 'text',
|
|
1561
|
+
text: JSON.stringify({
|
|
1562
|
+
convertedCode: result.convertedCode,
|
|
1563
|
+
changes: result.changes,
|
|
1564
|
+
flaggedSections: result.flaggedSections,
|
|
1565
|
+
}, null, 2),
|
|
1566
|
+
},
|
|
1567
|
+
],
|
|
1568
|
+
};
|
|
1569
|
+
});
|
|
1570
|
+
// ---------------------------------------------------------------------------
|
|
1571
|
+
// Prompt: convertSsjsToAmpscript
|
|
1572
|
+
// ---------------------------------------------------------------------------
|
|
1573
|
+
server.prompt('convertSsjsToAmpscript', 'Convert SSJS (Server-Side JavaScript) code to equivalent AMPscript. ' +
|
|
1574
|
+
'Calls the convertSsjsToAmpscript tool first for deterministic rule-based conversion, ' +
|
|
1575
|
+
'then applies AI reasoning to handle any MANUAL_REWRITE_REQUIRED sections.', {
|
|
1576
|
+
code: z.string().describe('The SSJS code to convert to AMPscript.'),
|
|
1577
|
+
}, ({ code }) => ({
|
|
1578
|
+
messages: [
|
|
1579
|
+
{
|
|
1580
|
+
role: 'user',
|
|
1581
|
+
content: {
|
|
1582
|
+
type: 'text',
|
|
1583
|
+
text: [
|
|
1584
|
+
'You are an expert Salesforce Marketing Cloud developer converting SSJS to AMPscript.',
|
|
1585
|
+
'',
|
|
1586
|
+
'## Instructions',
|
|
1587
|
+
'1. Call the `convertSsjsToAmpscript` **tool** with the code below to get a deterministic conversion.',
|
|
1588
|
+
'2. Review the `flaggedSections` in the result — these are constructs the tool could not convert automatically.',
|
|
1589
|
+
'3. For each flagged section, apply your expertise:',
|
|
1590
|
+
' - SSJS try/catch → use RaiseError() and conditional guards in AMPscript',
|
|
1591
|
+
' - Array .forEach/.map → AMPscript FOR @i = 1 TO RowCount(@rs) DO loops',
|
|
1592
|
+
' - JSON.parse/stringify → use BuildRowsetFromJson() or Field() on RowSets',
|
|
1593
|
+
' - HTTP calls → use HTTPGet() / HTTPPost() in AMPscript',
|
|
1594
|
+
' - Complex string manipulation → AMPscript string functions (Concat, Replace, Substring, etc.)',
|
|
1595
|
+
'4. Produce a single final AMPscript code block.',
|
|
1596
|
+
'5. Add a short change log as a bulleted list.',
|
|
1597
|
+
'',
|
|
1598
|
+
'## AMPscript syntax reminders',
|
|
1599
|
+
'- Blocks: `%%[ ... ]%%` — statements: `SET @x = expr`',
|
|
1600
|
+
'- Variables: `@varName` (no declaration needed except VAR)',
|
|
1601
|
+
'- Functions: PascalCase, no Platform.Function. prefix',
|
|
1602
|
+
'- Conditions: `IF cond THEN ... ELSEIF cond THEN ... ELSE ... ENDIF`',
|
|
1603
|
+
'- Loops: `FOR @i = 1 TO @count DO ... NEXT @i`',
|
|
1604
|
+
'- Output: `%%=Output(@x)=%%` (inline) or `%%[ OutputLine(x) ]%%` (block)',
|
|
1605
|
+
'',
|
|
1606
|
+
'## SSJS code to convert',
|
|
1607
|
+
'```javascript',
|
|
1608
|
+
code,
|
|
1609
|
+
'```',
|
|
1610
|
+
].join('\n'),
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
],
|
|
1614
|
+
}));
|
|
1615
|
+
// ---------------------------------------------------------------------------
|
|
1616
|
+
// Tool: convertAmpscriptToSsjs
|
|
1617
|
+
// ---------------------------------------------------------------------------
|
|
1618
|
+
server.tool('convertAmpscriptToSsjs', 'Deterministically convert AMPscript code to equivalent SSJS using rule-based transformations. ' +
|
|
1619
|
+
'Handles: SET @x → var x, IF/ELSEIF/ELSE/ENDIF → JS conditionals, FOR/NEXT → for loops, ' +
|
|
1620
|
+
'Output/OutputLine → Platform.Response.Write, AMPscript functions → Platform.Function equivalents. ' +
|
|
1621
|
+
'Flags AMPscript-only constructs (email-specific functions, personalization strings) as MANUAL_REWRITE_REQUIRED. ' +
|
|
1622
|
+
'Use the convertAmpscriptToSsjs PROMPT for AI-enhanced handling of flagged sections.', {
|
|
1623
|
+
code: z.string().describe('The AMPscript code to convert to SSJS.'),
|
|
1624
|
+
}, ({ code }) => {
|
|
1625
|
+
const result = ampscriptToSsjs(code);
|
|
1626
|
+
return {
|
|
1627
|
+
content: [
|
|
1628
|
+
{
|
|
1629
|
+
type: 'text',
|
|
1630
|
+
text: JSON.stringify({
|
|
1631
|
+
convertedCode: result.convertedCode,
|
|
1632
|
+
changes: result.changes,
|
|
1633
|
+
flaggedSections: result.flaggedSections,
|
|
1634
|
+
}, null, 2),
|
|
1635
|
+
},
|
|
1636
|
+
],
|
|
1637
|
+
};
|
|
1638
|
+
});
|
|
1639
|
+
// ---------------------------------------------------------------------------
|
|
872
1640
|
// Start
|
|
873
1641
|
// ---------------------------------------------------------------------------
|
|
874
1642
|
async function main() {
|