@velaro/mcp-server 0.6.103 → 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/server.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/velaro/velaro-admin",
9
9
  "source": "github"
10
10
  },
11
- "version": "0.6.0",
11
+ "version": "1.0.0",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@velaro/mcp-server",
17
- "version": "0.6.0",
17
+ "version": "1.0.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
@@ -0,0 +1,138 @@
1
+ // Derives the MCP `title` and `annotations` (readOnlyHint, destructiveHint, idempotentHint,
2
+ // openWorldHint) for every tool from its name, so no tool can ship without them.
3
+ // Claude and ChatGPT directory reviews require all four hints set explicitly to true/false.
4
+ // Fail-safe: a name matching no rule is treated as a non-read-only, destructive, non-idempotent write.
5
+ // Guard: scripts/validate-mcp-tool-annotations.js (CI) asserts every tool resolves and spot-checks the rules.
6
+
7
+ const tokens = (name) => name.split('_');
8
+
9
+ // Tokens that mark a tool as read-only unless the name is in MUTATING.
10
+ const READ_TOKENS = new Set([
11
+ 'list', 'get', 'search', 'status', 'diagnose', 'diagnostics', 'health', 'preview', 'lookup',
12
+ 'timeline', 'explain', 'audit', 'check', 'verify', 'summary', 'snapshot', 'benchmarks', 'stats',
13
+ 'usage', 'errors', 'insights', 'report', 'overview', 'runbook', 'evidence', 'calculator', 'eligible',
14
+ 'config', 'urls',
15
+ ]);
16
+
17
+ // Names that contain a read-looking token but change state (checked before READ_TOKENS).
18
+ const MUTATING = new Set([
19
+ 'report_schedule_upsert', 'report_subscription_update', 'report_subscription_delete', 'report_export_start',
20
+ 'report_backfill', 'report_data_export', 'executive_insights_subscribe', 'velaro_diagnostic_start',
21
+ 'apple_test_control', 'kb_capture_screenshot', 'business_action_route', 'business_action_execute',
22
+ 'netsuite_cti_generate_url', 'netsuite_m2m_init', 'netsuite_m2m_save', 'index_integrity_repair',
23
+ 'klaviyo_migration_run', 'sitesync_blob_push', 'sitesync_blob_import', 'chat_intelligence_draft_article',
24
+ 'teams_phone_set_config', 'webflow_set_config', 'notion_set_config', 'monday_set_field_mapping',
25
+ 'teams_push_settings_update', 'teams_channel_config_update', 'routing_team_update_config',
26
+ 'pickup_update_config', 'payment_recovery_update_config', 'recruiting_sync_jobs',
27
+ 'ai_config_set_reply_format', 'ai_model_set_site_thinking', 'ai_model_set_site_pin',
28
+ 'entitlement_reconcile', 'entitlement_sync_stripe', 'entitlement_backfill_subscriptions',
29
+ 'calendly_configure', 'acuity_configure', 'ms_bookings_configure',
30
+ ]);
31
+
32
+ // Names that look like writes but only read (checked before the write rules).
33
+ const READ_ONLY_EXPLICIT = new Set([
34
+ 'appointments_find_open_slots', 'appointments_list', 'compliance_redaction_test', 'compliance_status',
35
+ 'compliance_signatures', 'compliance_redaction_events', 'compliance_redaction_status',
36
+ 'shopify_widget_activation_url', 'klaviyo_savings_calculator', 'routing_simulate_inactivity',
37
+ 'outlook_calendar_get_available_range', 'ms_bookings_check_availability_range', 'copilot_studio_test',
38
+ 'notion_test_token', 'teams_phone_test_config', 'webflow_test_config', 'magento_test_connection',
39
+ 'outlook_calendar_test_connection', 'scraper_verify_token', 'bigcommerce_check_refundable',
40
+ 'sitesync_blob_list', 'kb_advise_knowledge_strategy', 'index_move_status', 'index_mixed_content',
41
+ 'index_search', 'netsuite_m2m_verify', 'bot_skill_usage', 'bot_full_diagnostic', 'bot_recent_errors',
42
+ 'logs_loggly_search', 'logs_appinsights_query', 'velaro_logs_search', 'executive_brief', 'friction_radar',
43
+ 'tour_insights', 'kpi_snapshot', 'site_info', 'case_board', 'case_list_types', 'ai_spend_report',
44
+ 'agent_availability_report', 'missed_chat_diagnostics', 'sse_visitor_diagnostic', 'signalr_diagnostics',
45
+ 'ivr_agent_availability', 'contactlist_get', 'ai_model_resolver_map', 'ai_model_effective_for_site',
46
+ 'calendly_get_status', 'acuity_get_status', 'outlook_calendar_status', 'ms_bookings_status',
47
+ 'service_level_report', 'call_activity_report', 'callrail_get_attributions',
48
+ 'product_sync_status', 'report_export_status', 'report_data_export', 'coupon_grant_list', 'coupon_grant_get',
49
+ 'automation_draft_list', 'focus_get_draft_pregeneration_report', 'teams_push_settings_get',
50
+ 'call_center_team_breakdown', 'call_center_channel_breakdown', 'outbound_activity_for_contact',
51
+ 'integration_skill_logs', 'support_integration_logs', 'site_integration_logs', 'magento_activity_logs',
52
+ 'support_site_deployments', 'support_schedule_adherence', 'support_site_agents', 'support_site_activity',
53
+ 'chat_intelligence_themes', 'chat_intelligence_recommendations', 'index_move_preview', 'index_split_preview',
54
+ 'canned_learning_suggestions', 'canned_learning_teams', 'canned_learning_edit_summary',
55
+ 'chat_topics_group_suggestions', 'chat_topics_suggestions', 'enterprise_reports', 'image_analysis_limits',
56
+ 'avatar_session_readiness', 'recruiting_ats_failures', 'jobboard_application_history',
57
+ 'recruiting_download_resume', 'routing_inactivity_transfers', 'entitlement_enterprise_reports_package',
58
+ 'compliance_attestation', 'livefluence_conversion_advisor', 'copilot_studio_ask',
59
+ ]);
60
+
61
+ // Write tools that are additive even though their name carries a destructive-looking token.
62
+ const NON_DESTRUCTIVE_EXPLICIT = new Set([
63
+ 'safety_settings_test', 'safety_need_help', 'canned_learning_record_edit', 'hangfire_retry_job',
64
+ 'netsuite_suiteapp_download', 'chat_topics_reclassify',
65
+ ]);
66
+
67
+ const DESTRUCTIVE_TOKENS = new Set([
68
+ 'delete', 'remove', 'revoke', 'disconnect', 'deprovision', 'reset', 'cancel', 'expire', 'purge',
69
+ 'rollback', 'dismiss', 'update', 'set', 'upsert', 'edit', 'toggle', 'replace', 'restore', 'adjust',
70
+ 'execute', 'push', 'move', 'split', 'repair', 'lock', 'override', 'configure', 'reschedule', 'migrate',
71
+ 'backfill', 'reconcile', 'ingest', 'reindex', 'manage', 'control', 'apply',
72
+ ]);
73
+
74
+ // Write tools that only add or trigger; they never remove or overwrite existing data.
75
+ const ADDITIVE_TOKENS = new Set([
76
+ 'create', 'add', 'send', 'notify', 'book', 'start', 'run', 'trigger', 'generate', 'invite', 'init',
77
+ 'retry', 'replay', 'route', 'grant', 'capture', 'draft', 'seed', 'sync', 'mark', 'subscribe', 'request',
78
+ 'crawl', 'provision', 'approve', 'comp', 'export', 'import', 'sign', 'card',
79
+ ]);
80
+
81
+ const NOT_IDEMPOTENT_TOKENS = new Set([
82
+ 'create', 'add', 'send', 'notify', 'book', 'start', 'run', 'trigger', 'generate', 'toggle', 'execute',
83
+ 'route', 'retry', 'replay', 'push', 'grant', 'init', 'invite', 'capture', 'draft', 'crawl', 'card',
84
+ 'subscribe', 'request', 'import', 'sign', 'approve', 'adjust', 'migrate', 'ingest', 'apply', 'rollback',
85
+ 'reschedule', 'cancel',
86
+ ]);
87
+
88
+ // Tools that reach a system outside the caller's Velaro account (third-party APIs, email/SMS/chat delivery, CRMs).
89
+ const OPEN_WORLD = new RegExp('^(' + [
90
+ 'calendly', 'acuity', 'outlook', 'ms_bookings', 'bigcommerce', 'shopify', 'magento', 'sap', 'netsuite',
91
+ 'monday', 'notion', 'teams', 'slack', 'webflow', 'klaviyo', 'callrail', 'copilot_studio', 'scraper',
92
+ 'site_scraper', 'apple', 'mobile_ota', 'code_push', 'jobboard', 'recruiting', 'payment_recovery',
93
+ 'pickup_notify', 'email_queue_replay', 'email_inbox', 'entitlement_sync_stripe', 'logs_loggly',
94
+ 'logs_appinsights', 'livefluence', 'telephony', 'kb_capture_screenshot', 'business_action',
95
+ 'sitesync', 'email_delivery', 'campaign_sender_auth', 'ivr_update_line',
96
+ ].join('|') + ')');
97
+
98
+ const ACRONYMS = {
99
+ kb: 'KB', ai: 'AI', ivr: 'IVR', sap: 'SAP', crm: 'CRM', sse: 'SSE', api: 'API', cti: 'CTI', m2m: 'M2M',
100
+ ota: 'OTA', url: 'URL', urls: 'URLs', id: 'ID', roi: 'ROI', kpi: 'KPI', ms: 'Microsoft', csat: 'CSAT',
101
+ sms: 'SMS', qa: 'QA', mcp: 'MCP', uuid: 'UUID', cli: 'CLI', smtp: 'SMTP', dns: 'DNS', oauth: 'OAuth',
102
+ netsuite: 'NetSuite', hangfire: 'Hangfire', signalr: 'SignalR', bigcommerce: 'BigCommerce',
103
+ livefluence: 'Livefluence', sitesync: 'SiteSync', jobboard: 'Job Board', callrail: 'CallRail',
104
+ wa: 'WhatsApp', velaro: 'Velaro', loggly: 'Loggly', appinsights: 'App Insights', sla: 'SLA',
105
+ };
106
+
107
+ export function titleFor(name) {
108
+ return tokens(name)
109
+ .map((t) => ACRONYMS[t] ?? t.charAt(0).toUpperCase() + t.slice(1))
110
+ .join(' ');
111
+ }
112
+
113
+ export function annotationsFor(name) {
114
+ const t = tokens(name);
115
+ const has = (set) => t.some((x) => set.has(x));
116
+ const readOnly = READ_ONLY_EXPLICIT.has(name)
117
+ || (!MUTATING.has(name) && has(READ_TOKENS) && !has(DESTRUCTIVE_TOKENS) && !has(ADDITIVE_TOKENS));
118
+ const openWorldHint = OPEN_WORLD.test(name);
119
+ if (readOnly) {
120
+ return { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint };
121
+ }
122
+ const additive = NON_DESTRUCTIVE_EXPLICIT.has(name) || (has(ADDITIVE_TOKENS) && !has(DESTRUCTIVE_TOKENS));
123
+ return {
124
+ readOnlyHint: false,
125
+ destructiveHint: !additive,
126
+ idempotentHint: !has(NOT_IDEMPOTENT_TOKENS),
127
+ openWorldHint,
128
+ };
129
+ }
130
+
131
+ // Returns the tool with `title` and `annotations` filled in; a hand-written value on the tool wins.
132
+ export function withAnnotations(tool) {
133
+ return {
134
+ ...tool,
135
+ title: tool.title ?? titleFor(tool.name),
136
+ annotations: { ...annotationsFor(tool.name), ...(tool.annotations ?? {}) },
137
+ };
138
+ }
@@ -0,0 +1,70 @@
1
+ // Shared helpers for the workflow schedule-priority tools (velaro MCP). Messaging owns the REST
2
+ // endpoints under /Workflows/schedule/*; the site always comes from auth, never a parameter.
3
+ // An older messaging without the endpoints answers 404/501 -> we return a friendly message.
4
+
5
+ export const SCHEDULE_NOT_AVAILABLE = "Workflow scheduling is not available on this site's version.";
6
+
7
+ export const SCHEDULE_ACTIONS = { SwitchWorkflow: 3 };
8
+
9
+ export function buildResolvePath({ trigger, channel, at, tz } = {}) {
10
+ const q = new URLSearchParams();
11
+ if (trigger) q.set('trigger', trigger);
12
+ if (channel) q.set('channel', channel);
13
+ if (at) q.set('at', at);
14
+ if (tz) q.set('tz', tz);
15
+ return `/Workflows/schedule/resolve?${q}`;
16
+ }
17
+
18
+ export function buildTimelinePath({ from, to, trigger, channel } = {}) {
19
+ const q = new URLSearchParams();
20
+ if (from) q.set('from', from);
21
+ if (to) q.set('to', to);
22
+ if (trigger) q.set('trigger', trigger);
23
+ if (channel) q.set('channel', channel);
24
+ return `/Workflows/schedule/timeline?${q}`;
25
+ }
26
+
27
+ export const VALIDATE_PATH = '/Workflows/schedule/validate';
28
+ export const BANNER_PATH = '/Workflows/schedule/banner';
29
+ export const OVERRIDE_PATH = '/ScheduleOverride';
30
+
31
+ // Normalizes the action to the int messaging expects (accepts 'SwitchWorkflow' or 3).
32
+ export function buildOverrideBody(args = {}) {
33
+ const { action, targetWorkflowId, ...rest } = args;
34
+ const body = { ...rest };
35
+ if (action !== undefined) body.action = typeof action === 'string' && action in SCHEDULE_ACTIONS ? SCHEDULE_ACTIONS[action] : action;
36
+ if (targetWorkflowId !== undefined) body.targetWorkflowId = targetWorkflowId;
37
+ return body;
38
+ }
39
+
40
+ // Messaging's POST /Workflows/schedule/validate takes { trigger, channel, override } (one draft override).
41
+ export function buildValidateBody({ trigger, channel, override } = {}) {
42
+ const body = { trigger: trigger || 'conversation_opened' };
43
+ if (channel) body.channel = channel;
44
+ if (override) body.override = buildOverrideBody(override);
45
+ return body;
46
+ }
47
+
48
+ export function buildBannerPath({ trigger, channel } = {}) {
49
+ const q = new URLSearchParams();
50
+ if (trigger) q.set('trigger', trigger);
51
+ if (channel) q.set('channel', channel);
52
+ return `${BANNER_PATH}?${q}`;
53
+ }
54
+
55
+ export function buildOverrideListPath({ action } = {}) {
56
+ if (action === undefined || action === '') return OVERRIDE_PATH;
57
+ const n = typeof action === 'string' && action in SCHEDULE_ACTIONS ? SCHEDULE_ACTIONS[action] : action;
58
+ return `${OVERRIDE_PATH}?action=${encodeURIComponent(n)}`;
59
+ }
60
+
61
+ // Runs fn(); a 404/501 (error message contains "-> 404:" / "-> 501:" or err.status) maps to the friendly string.
62
+ export async function tolerant(fn) {
63
+ try {
64
+ return await fn();
65
+ } catch (err) {
66
+ const status = err?.status ?? Number(/->\s*(\d{3})/.exec(err?.message ?? '')?.[1]);
67
+ if (status === 404 || status === 501) return SCHEDULE_NOT_AVAILABLE;
68
+ throw err;
69
+ }
70
+ }