@velaro/mcp-server 0.6.102 → 0.6.104

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.
@@ -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
+ }