@velaro/mcp-server 0.6.98 → 0.6.100
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/package.json +1 -1
- package/server.js +161 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.100",
|
|
4
4
|
"description": "Velaro MCP server — connect Claude and other AI agents directly to your Velaro account: KB, workflows, bots, conversations, contacts, routing, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
* VELARO_MCP_MODE=http PORT=3000 node server.js
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
+
import { describeRawMeta, fetchRetry429 } from './log-search.js';
|
|
43
44
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
44
45
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
45
46
|
import {
|
|
@@ -119,11 +120,12 @@ function parseDuration(s) {
|
|
|
119
120
|
return n * 24 * 3600 * 1000;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
async function messagingApi(path, opts = {}) {
|
|
123
|
+
async function messagingApi(path, opts = {}) { // opts.timeoutMs: log searches pass 40000 (server scan budget 12 s + 6 s AI search)
|
|
123
124
|
if (!MESSAGING_API) throw new Error(
|
|
124
125
|
'VELARO_MESSAGING_API is not configured. Add "VELARO_MESSAGING_API": "https://velaro-messaging-api-staging.azurewebsites.net" to your MCP env config.'
|
|
125
126
|
);
|
|
126
|
-
|
|
127
|
+
// 429 = the server's per-instance scan gate is full: retry honouring Retry-After (GETs only; a retried POST could repeat a write).
|
|
128
|
+
const init = () => ({
|
|
127
129
|
method: opts.method ?? 'GET',
|
|
128
130
|
headers: {
|
|
129
131
|
Authorization: authHeader(),
|
|
@@ -131,8 +133,9 @@ async function messagingApi(path, opts = {}) {
|
|
|
131
133
|
...(opts.body ? { 'Content-Type': 'application/json' } : {}),
|
|
132
134
|
},
|
|
133
135
|
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
134
|
-
signal: AbortSignal.timeout(20000),
|
|
136
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 20000),
|
|
135
137
|
});
|
|
138
|
+
const res = (opts.method ?? 'GET') === 'GET' ? await fetchRetry429(`${MESSAGING_API}${path}`, init) : await fetch(`${MESSAGING_API}${path}`, init());
|
|
136
139
|
if (!res.ok) {
|
|
137
140
|
const text = await res.text().catch(() => '');
|
|
138
141
|
throw new Error(`Velaro Logger ${path} -> ${res.status}: ${text.slice(0, 300)}`);
|
|
@@ -845,12 +848,12 @@ const TOOLS = [
|
|
|
845
848
|
{
|
|
846
849
|
name: 'canned_learning_suggestions',
|
|
847
850
|
description: 'List AI-proposed canned message improvements awaiting human review (learned from low ratings and never-used messages). Each row shows currentText, proposedText, the reason, the signals it learned from, and a review state (pending_review, approved, rejected, rolled_back). Intelligence Suite entitlement required.',
|
|
848
|
-
inputSchema: { type: 'object', properties: { state: { type: 'string', description: 'pending_review (default), approved, rejected, rolled_back, or all' } } },
|
|
851
|
+
inputSchema: { type: 'object', properties: { state: { type: 'string', description: 'pending_review (default), approved, rejected, rolled_back, or all' }, teamId: { type: 'number', description: 'Only suggestions for canned responses on this team. Team managers only ever see their own teams.' } } },
|
|
849
852
|
},
|
|
850
853
|
{
|
|
851
854
|
name: 'canned_learning_generate',
|
|
852
|
-
description: 'Analyze this site canned message usage and ratings and queue AI-proposed improvements for human review. Nothing changes until approved. Capped at 5 messages per run and one run per day; text is PII-redacted before it reaches the model.',
|
|
853
|
-
inputSchema: { type: 'object', properties: {} },
|
|
855
|
+
description: 'Analyze this site canned message usage and ratings and queue AI-proposed improvements for human review. Nothing changes until approved. Capped at 5 messages per run and one run per day (per team when teamId is given); text is PII-redacted before it reaches the model. Team managers must pass a teamId they manage.',
|
|
856
|
+
inputSchema: { type: 'object', properties: { teamId: { type: 'number', description: 'Only generate for canned responses on this team' } } },
|
|
854
857
|
},
|
|
855
858
|
{
|
|
856
859
|
name: 'canned_learning_approve',
|
|
@@ -865,6 +868,11 @@ const TOOLS = [
|
|
|
865
868
|
{
|
|
866
869
|
name: 'canned_learning_edit_summary',
|
|
867
870
|
description: 'Which canned messages agents edit most before sending (last 90 days): canned response id, shortcut, edit count and average similarity of the sent text to the original. This is the edited-before-send evidence the weekly canned message learning job uses. Intelligence Suite entitlement required.',
|
|
871
|
+
inputSchema: { type: 'object', properties: { teamId: { type: 'number', description: 'Only canned responses on this team' } } },
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: 'canned_learning_teams',
|
|
875
|
+
description: 'Teams the caller can review canned message learning for: every site team for a site supervisor, only managed teams for a team manager. Use the ids as teamId on the other canned_learning tools.',
|
|
868
876
|
inputSchema: { type: 'object', properties: {} },
|
|
869
877
|
},
|
|
870
878
|
{
|
|
@@ -2152,6 +2160,62 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2152
2160
|
required: ['fromIndex', 'toIndex'],
|
|
2153
2161
|
},
|
|
2154
2162
|
},
|
|
2163
|
+
{
|
|
2164
|
+
name: 'safety_events_list',
|
|
2165
|
+
description: "List the site's safety events (emergency, emergency_unconfirmed, therapy_setting_change, fail_closed, output_guardrail_block, visitor_report, off_topic_attempt). Needs the EnableTroubleshootingKnowledge entitlement. Wraps GET SafetyEvents; the site comes from the caller auth, never an argument.",
|
|
2166
|
+
inputSchema: {
|
|
2167
|
+
type: 'object',
|
|
2168
|
+
properties: {
|
|
2169
|
+
from: { type: 'string', description: 'ISO start date/time' },
|
|
2170
|
+
to: { type: 'string', description: 'ISO end date/time' },
|
|
2171
|
+
type: { type: 'string', description: 'One event type' },
|
|
2172
|
+
status: { type: 'string', enum: ['open', 'acknowledged'] },
|
|
2173
|
+
take: { type: 'number', description: 'Page size, default 25' },
|
|
2174
|
+
skip: { type: 'number', description: 'Rows to skip, default 0' },
|
|
2175
|
+
},
|
|
2176
|
+
},
|
|
2177
|
+
},
|
|
2178
|
+
{
|
|
2179
|
+
name: 'safety_events_summary',
|
|
2180
|
+
description: 'Per-day and total safety event counts by type for the last N days (default 7). Wraps GET SafetyEvents/summary.',
|
|
2181
|
+
inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Default 7, e.g. 30' } } },
|
|
2182
|
+
},
|
|
2183
|
+
{
|
|
2184
|
+
name: 'safety_event_ack',
|
|
2185
|
+
description: 'Mark one safety event as reviewed. Wraps POST SafetyEvents/{id}/acknowledge.',
|
|
2186
|
+
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
|
|
2187
|
+
},
|
|
2188
|
+
{
|
|
2189
|
+
name: 'safety_settings_get',
|
|
2190
|
+
description: 'Get the safety alert settings: enabled, emailRecipients, smsRecipients, immediateTypes, digest (off|daily|weekly), threshold {count, windowMinutes}. Wraps GET SafetyEvents/settings.',
|
|
2191
|
+
inputSchema: { type: 'object', properties: {} },
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
name: 'safety_settings_set',
|
|
2195
|
+
description: 'Change the safety alert settings. Only the fields you pass change; the rest keep their current value. Wraps PUT SafetyEvents/settings (reads the current settings first).',
|
|
2196
|
+
inputSchema: {
|
|
2197
|
+
type: 'object',
|
|
2198
|
+
properties: {
|
|
2199
|
+
enabled: { type: 'boolean' },
|
|
2200
|
+
emailRecipients: { type: 'array', items: { type: 'string' } },
|
|
2201
|
+
smsRecipients: { type: 'array', items: { type: 'string' }, description: 'E.164 numbers, e.g. +15551234567' },
|
|
2202
|
+
immediateTypes: { type: 'array', items: { type: 'string' }, description: 'Event types that alert right away' },
|
|
2203
|
+
digest: { type: 'string', enum: ['off', 'daily', 'weekly'] },
|
|
2204
|
+
thresholdCount: { type: 'number', description: 'Alert when this many events happen inside the window. 0 turns it off.' },
|
|
2205
|
+
thresholdWindowMinutes: { type: 'number', description: '1 to 10080' },
|
|
2206
|
+
},
|
|
2207
|
+
},
|
|
2208
|
+
},
|
|
2209
|
+
{
|
|
2210
|
+
name: 'safety_settings_test',
|
|
2211
|
+
description: 'Send a test safety alert to the configured recipients. Wraps POST SafetyEvents/settings/test.',
|
|
2212
|
+
inputSchema: { type: 'object', properties: {} },
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
name: 'safety_need_help',
|
|
2216
|
+
description: 'Tell the Velaro team the bot is going off the rails and you need help now. Wraps POST SafetyEvents/need-help.',
|
|
2217
|
+
inputSchema: { type: 'object', properties: { message: { type: 'string', description: 'What is happening' } }, required: ['message'] },
|
|
2218
|
+
},
|
|
2155
2219
|
{
|
|
2156
2220
|
name: 'index_move_status',
|
|
2157
2221
|
description: 'Status of one move (moveId) or the recent moves (omit moveId): status, per-document progress, canResume/canAbort/canUndo. Wraps GET IndexMove/Moves[/{id}].',
|
|
@@ -2864,6 +2928,25 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2864
2928
|
},
|
|
2865
2929
|
},
|
|
2866
2930
|
|
|
2931
|
+
// ── Chat Window launcher copy (per-page launcher label + greeting) ─────────
|
|
2932
|
+
{
|
|
2933
|
+
name: 'chat_window_list',
|
|
2934
|
+
description: 'List chat windows for this site with the launcher label (launcherText) and greeting. Point a Select Chat Window workflow node or Determine Experience Bundle rule at a window to show its label and greeting on specific pages.',
|
|
2935
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
2936
|
+
},
|
|
2937
|
+
{
|
|
2938
|
+
name: 'chat_window_set_launcher_copy',
|
|
2939
|
+
description: 'Set the launcher label (the text pill on the chat button, max 60 chars) and/or the opening greeting (max 256 chars) on one chat window. Omitted fields are left unchanged; an empty string clears the field and the widget falls back to its default. For per-page copy, create one chat window per page type (and per language) and select it with a Select Chat Window workflow node.',
|
|
2940
|
+
inputSchema: {
|
|
2941
|
+
type: 'object',
|
|
2942
|
+
properties: {
|
|
2943
|
+
id: { type: 'number', description: 'Chat window ID (see chat_window_list)' },
|
|
2944
|
+
launcherText: { type: 'string', description: 'Launcher label, e.g. "Troubleshoot my device"' },
|
|
2945
|
+
greeting: { type: 'string', description: 'Opening greeting shown when the window opens' },
|
|
2946
|
+
},
|
|
2947
|
+
required: ['id'],
|
|
2948
|
+
},
|
|
2949
|
+
},
|
|
2867
2950
|
// ── Page Widget Containers ────────────────────────────────────────────────
|
|
2868
2951
|
{
|
|
2869
2952
|
name: 'widget_container_list',
|
|
@@ -6340,15 +6423,17 @@ async function handleTool(name, args) {
|
|
|
6340
6423
|
if (args.confirm) return await messagingPost('/ChatIntelligenceTopics/analysis', { confirm: true });
|
|
6341
6424
|
return { estimate: await messagingGet('/ChatIntelligenceTopics/analysis/estimate'), cached: await messagingGet('/ChatIntelligenceTopics/analysis') };
|
|
6342
6425
|
case 'canned_learning_suggestions':
|
|
6343
|
-
return await messagingGet(`/CannedLearning/suggestions?state=${encodeURIComponent(args.state ?? 'pending_review')}`);
|
|
6426
|
+
return await messagingGet(`/CannedLearning/suggestions?state=${encodeURIComponent(args.state ?? 'pending_review')}${args.teamId != null ? `&teamId=${encodeURIComponent(args.teamId)}` : ''}`);
|
|
6344
6427
|
case 'canned_learning_generate':
|
|
6345
|
-
return await messagingPost('/CannedLearning/generate', {});
|
|
6428
|
+
return await messagingPost('/CannedLearning/generate', { teamId: args.teamId ?? null });
|
|
6429
|
+
case 'canned_learning_teams':
|
|
6430
|
+
return await messagingGet('/CannedLearning/teams');
|
|
6346
6431
|
case 'canned_learning_approve':
|
|
6347
6432
|
return await messagingPost(`/CannedLearning/suggestions/${args.suggestionId}/approve`, { editedText: args.editedText ?? null });
|
|
6348
6433
|
case 'canned_learning_reject':
|
|
6349
6434
|
return await messagingPost(`/CannedLearning/suggestions/${args.suggestionId}/reject`, {});
|
|
6350
6435
|
case 'canned_learning_edit_summary':
|
|
6351
|
-
return await messagingGet(
|
|
6436
|
+
return await messagingGet(`/CannedLearning/edit-events${args.teamId != null ? `?teamId=${encodeURIComponent(args.teamId)}` : ''}`);
|
|
6352
6437
|
case 'canned_learning_record_edit':
|
|
6353
6438
|
return await messagingPost('/CannedLearning/edit-events', { cannedResponseId: args.cannedResponseId, finalText: args.finalText });
|
|
6354
6439
|
case 'canned_learning_rollback':
|
|
@@ -7569,9 +7654,18 @@ async function handleTool(name, args) {
|
|
|
7569
7654
|
const from1h = new Date(Date.now() - 3600000).toISOString();
|
|
7570
7655
|
const velaroLoggerP = safeJson(async () => {
|
|
7571
7656
|
if (!MESSAGING_API) return { _error: 'VELARO_MESSAGING_API not set — add it to MCP env config' };
|
|
7572
|
-
|
|
7573
|
-
const
|
|
7574
|
-
|
|
7657
|
+
// Two honest reads (Opus review 2026-10-14): ERRORS use the server-side level=ERROR filter; PATTERNS are the WF-AI-ROUTE / PERF-SLOW regex (no level).
|
|
7658
|
+
const f = encodeURIComponent(from1h);
|
|
7659
|
+
// One at a time: the server runs at most 2 exact scans per instance.
|
|
7660
|
+
const errRes = await messagingApi(`/superadmin/logs/search?level=ERROR&from=${f}&take=30`, { timeoutMs: 40000 });
|
|
7661
|
+
const patRes = await messagingApi(`/superadmin/logs/search?q=${encodeURIComponent('WF-AI-ROUTE|PERF-SLOW')}®ex=true&from=${f}&take=30`, { timeoutMs: 40000 });
|
|
7662
|
+
const byId = new Map();
|
|
7663
|
+
for (const e of [...extractLogEntries(errRes), ...extractLogEntries(patRes)]) byId.set(e.id ?? `${e.timestamp}|${e.message}`, e);
|
|
7664
|
+
const found = [...byId.values()].sort((x, y) => (Date.parse(y.timestamp ?? y.LogTimestamp) || 0) - (Date.parse(x.timestamp ?? x.LogTimestamp) || 0));
|
|
7665
|
+
const verified = (r) => !!(r && !Array.isArray(r) && r.truncated === false);
|
|
7666
|
+
found._verified = verified(errRes) && verified(patRes);
|
|
7667
|
+
found._note = `errors: ${describeRawMeta(errRes)} | patterns: ${describeRawMeta(patRes)}`;
|
|
7668
|
+
return found;
|
|
7575
7669
|
});
|
|
7576
7670
|
|
|
7577
7671
|
// ── Layer 4b: Loggly ERROR + SLOW + AI-routing logs (last 1h) — SECONDARY (migrating away)
|
|
@@ -7647,9 +7741,11 @@ async function handleTool(name, args) {
|
|
|
7647
7741
|
if (velaroLogs?._error) {
|
|
7648
7742
|
out.push(section('Velaro Logger (1h) [PRIMARY]', [` ⚠ ${velaroLogs._error}`]));
|
|
7649
7743
|
} else if (!Array.isArray(velaroLogs) || !velaroLogs.length) {
|
|
7650
|
-
out.push(section('Velaro Logger (1h) [PRIMARY]',
|
|
7744
|
+
out.push(section('Velaro Logger (1h) [PRIMARY]', velaroLogs?._verified
|
|
7745
|
+
? [` ✅ No errors, slow responses, or AI routing events (${velaroLogs._note})`]
|
|
7746
|
+
: [` ⚠ No matches, but the scan is NOT verified complete: ${velaroLogs?._note ?? 'no scan metadata'}. Use velaro_logs_search before concluding all is well.`]));
|
|
7651
7747
|
} else {
|
|
7652
|
-
out.push(section(`Velaro Logger (1h) [PRIMARY] — ${velaroLogs.length} entries`, velaroLogs.map(fmtVelaroEntry)));
|
|
7748
|
+
out.push(section(`Velaro Logger (1h) [PRIMARY] — ${velaroLogs.length} entries${velaroLogs._verified ? '' : ` (scan not verified complete: ${velaroLogs._note})`}`, velaroLogs.map(fmtVelaroEntry)));
|
|
7653
7749
|
}
|
|
7654
7750
|
|
|
7655
7751
|
// ── Loggly (secondary — being phased out; check if Velaro Logger is empty/errored)
|
|
@@ -7755,6 +7851,35 @@ async function handleTool(name, args) {
|
|
|
7755
7851
|
const r = await messagingPost('/IndexMove/Split/Execute', body);
|
|
7756
7852
|
return JSON.stringify(r, null, 2);
|
|
7757
7853
|
}
|
|
7854
|
+
case 'safety_events_list': {
|
|
7855
|
+
const q = new URLSearchParams();
|
|
7856
|
+
for (const k of ['from', 'to', 'type', 'status']) if (args[k]) q.set(k, String(args[k]));
|
|
7857
|
+
q.set('take', String(args.take ?? 25));
|
|
7858
|
+
q.set('skip', String(args.skip ?? 0));
|
|
7859
|
+
return JSON.stringify(await messagingGet(`/SafetyEvents?${q}`), null, 2);
|
|
7860
|
+
}
|
|
7861
|
+
case 'safety_events_summary':
|
|
7862
|
+
return JSON.stringify(await messagingGet(`/SafetyEvents/summary?days=${Number(args.days) || 7}`), null, 2);
|
|
7863
|
+
case 'safety_event_ack':
|
|
7864
|
+
return JSON.stringify(await messagingPost(`/SafetyEvents/${encodeURIComponent(args.id)}/acknowledge`), null, 2);
|
|
7865
|
+
case 'safety_settings_get':
|
|
7866
|
+
return JSON.stringify(await messagingGet('/SafetyEvents/settings'), null, 2);
|
|
7867
|
+
case 'safety_settings_set': {
|
|
7868
|
+
const cur = (await messagingGet('/SafetyEvents/settings')) ?? {};
|
|
7869
|
+
const next = { ...cur };
|
|
7870
|
+
for (const k of ['enabled', 'emailRecipients', 'smsRecipients', 'immediateTypes', 'digest']) if (args[k] !== undefined) next[k] = args[k];
|
|
7871
|
+
if (args.thresholdCount !== undefined || args.thresholdWindowMinutes !== undefined) {
|
|
7872
|
+
next.threshold = {
|
|
7873
|
+
count: args.thresholdCount ?? cur.threshold?.count ?? 0,
|
|
7874
|
+
windowMinutes: args.thresholdWindowMinutes ?? cur.threshold?.windowMinutes ?? 60,
|
|
7875
|
+
};
|
|
7876
|
+
}
|
|
7877
|
+
return JSON.stringify(await messagingPut('/SafetyEvents/settings', next), null, 2);
|
|
7878
|
+
}
|
|
7879
|
+
case 'safety_settings_test':
|
|
7880
|
+
return JSON.stringify(await messagingPost('/SafetyEvents/settings/test'), null, 2);
|
|
7881
|
+
case 'safety_need_help':
|
|
7882
|
+
return JSON.stringify(await messagingPost('/SafetyEvents/need-help', { message: args.message }), null, 2);
|
|
7758
7883
|
case 'index_move_status': {
|
|
7759
7884
|
const r = await messagingGet(args.moveId
|
|
7760
7885
|
? `/IndexMove/Moves/${encodeURIComponent(args.moveId)}`
|
|
@@ -8611,9 +8736,11 @@ async function handleTool(name, args) {
|
|
|
8611
8736
|
if (args.environment) params.set('environment', args.environment);
|
|
8612
8737
|
if (args.correlationId) params.set('correlationId', args.correlationId);
|
|
8613
8738
|
if (args.regex) params.set('regex', 'true');
|
|
8614
|
-
const
|
|
8615
|
-
|
|
8616
|
-
const
|
|
8739
|
+
const resp = await messagingApi(`/superadmin/logs/search?${params}`, { timeoutMs: 40000 });
|
|
8740
|
+
const entries = extractLogEntries(resp);
|
|
8741
|
+
const meta = describeRawMeta(resp); // searchPath/scannedRows/truncated: a 0 or short list is never ambiguous
|
|
8742
|
+
if (!entries.length) return `No results. ${meta}`;
|
|
8743
|
+
const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} UTC. ${meta}\n`];
|
|
8617
8744
|
for (const e of entries) {
|
|
8618
8745
|
const ts = new Date(e.timestamp ?? e.LogTimestamp).toISOString().replace('T', ' ').slice(0, 19);
|
|
8619
8746
|
const lvl = (e.Level ?? e.level ?? '?').padEnd(5);
|
|
@@ -8707,6 +8834,23 @@ async function handleTool(name, args) {
|
|
|
8707
8834
|
).join('\n');
|
|
8708
8835
|
}
|
|
8709
8836
|
|
|
8837
|
+
case 'chat_window_list': {
|
|
8838
|
+
const r = (await messagingGet('/ChatWindow/list')) ?? [];
|
|
8839
|
+
return JSON.stringify(r.map((w) => ({
|
|
8840
|
+
id: w.id, name: w.name, language: w.languageConfig?.language,
|
|
8841
|
+
launcherText: w.styleConfig?.launcherText ?? null, greeting: w.languageConfig?.greeting ?? null,
|
|
8842
|
+
})), null, 2);
|
|
8843
|
+
}
|
|
8844
|
+
|
|
8845
|
+
case 'chat_window_set_launcher_copy': {
|
|
8846
|
+
if (!args.id) throw new Error('id is required');
|
|
8847
|
+
const body = {};
|
|
8848
|
+
if (args.launcherText !== undefined) body.launcherText = args.launcherText;
|
|
8849
|
+
if (args.greeting !== undefined) body.greeting = args.greeting;
|
|
8850
|
+
if (!Object.keys(body).length) throw new Error('provide launcherText and/or greeting');
|
|
8851
|
+
return JSON.stringify(await messagingPut(`/ChatWindow/${args.id}/launcher-copy`, body), null, 2);
|
|
8852
|
+
}
|
|
8853
|
+
|
|
8710
8854
|
case 'widget_container_get': {
|
|
8711
8855
|
const c = await api('GET', `/PageWidgetContainers/${args.id}`);
|
|
8712
8856
|
return JSON.stringify(c, null, 2);
|