@velaro/mcp-server 0.6.97 → 0.6.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +27 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.97",
3
+ "version": "0.6.99",
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
- const res = await fetch(`${MESSAGING_API}${path}`, {
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)}`);
@@ -7569,9 +7572,18 @@ async function handleTool(name, args) {
7569
7572
  const from1h = new Date(Date.now() - 3600000).toISOString();
7570
7573
  const velaroLoggerP = safeJson(async () => {
7571
7574
  if (!MESSAGING_API) return { _error: 'VELARO_MESSAGING_API not set — add it to MCP env config' };
7572
- const q = encodeURIComponent('ERROR|WF-AI-ROUTE|PERF-SLOW');
7573
- const res = await messagingApi(`/superadmin/logs/search?q=${q}&from=${encodeURIComponent(from1h)}&take=30&regex=true`);
7574
- return extractLogEntries(res);
7575
+ // 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).
7576
+ const f = encodeURIComponent(from1h);
7577
+ // One at a time: the server runs at most 2 exact scans per instance.
7578
+ const errRes = await messagingApi(`/superadmin/logs/search?level=ERROR&from=${f}&take=30`, { timeoutMs: 40000 });
7579
+ const patRes = await messagingApi(`/superadmin/logs/search?q=${encodeURIComponent('WF-AI-ROUTE|PERF-SLOW')}&regex=true&from=${f}&take=30`, { timeoutMs: 40000 });
7580
+ const byId = new Map();
7581
+ for (const e of [...extractLogEntries(errRes), ...extractLogEntries(patRes)]) byId.set(e.id ?? `${e.timestamp}|${e.message}`, e);
7582
+ const found = [...byId.values()].sort((x, y) => (Date.parse(y.timestamp ?? y.LogTimestamp) || 0) - (Date.parse(x.timestamp ?? x.LogTimestamp) || 0));
7583
+ const verified = (r) => !!(r && !Array.isArray(r) && r.truncated === false);
7584
+ found._verified = verified(errRes) && verified(patRes);
7585
+ found._note = `errors: ${describeRawMeta(errRes)} | patterns: ${describeRawMeta(patRes)}`;
7586
+ return found;
7575
7587
  });
7576
7588
 
7577
7589
  // ── Layer 4b: Loggly ERROR + SLOW + AI-routing logs (last 1h) — SECONDARY (migrating away)
@@ -7647,9 +7659,11 @@ async function handleTool(name, args) {
7647
7659
  if (velaroLogs?._error) {
7648
7660
  out.push(section('Velaro Logger (1h) [PRIMARY]', [` ⚠ ${velaroLogs._error}`]));
7649
7661
  } else if (!Array.isArray(velaroLogs) || !velaroLogs.length) {
7650
- out.push(section('Velaro Logger (1h) [PRIMARY]', [' ✅ No errors, slow responses, or AI routing events']));
7662
+ out.push(section('Velaro Logger (1h) [PRIMARY]', velaroLogs?._verified
7663
+ ? [` ✅ No errors, slow responses, or AI routing events (${velaroLogs._note})`]
7664
+ : [` ⚠ No matches, but the scan is NOT verified complete: ${velaroLogs?._note ?? 'no scan metadata'}. Use velaro_logs_search before concluding all is well.`]));
7651
7665
  } else {
7652
- out.push(section(`Velaro Logger (1h) [PRIMARY] — ${velaroLogs.length} entries`, velaroLogs.map(fmtVelaroEntry)));
7666
+ out.push(section(`Velaro Logger (1h) [PRIMARY] — ${velaroLogs.length} entries${velaroLogs._verified ? '' : ` (scan not verified complete: ${velaroLogs._note})`}`, velaroLogs.map(fmtVelaroEntry)));
7653
7667
  }
7654
7668
 
7655
7669
  // ── Loggly (secondary — being phased out; check if Velaro Logger is empty/errored)
@@ -8611,9 +8625,11 @@ async function handleTool(name, args) {
8611
8625
  if (args.environment) params.set('environment', args.environment);
8612
8626
  if (args.correlationId) params.set('correlationId', args.correlationId);
8613
8627
  if (args.regex) params.set('regex', 'true');
8614
- const entries = extractLogEntries(await messagingApi(`/superadmin/logs/search?${params}`));
8615
- if (!entries.length) return 'No results.';
8616
- const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} UTC\n`];
8628
+ const resp = await messagingApi(`/superadmin/logs/search?${params}`, { timeoutMs: 40000 });
8629
+ const entries = extractLogEntries(resp);
8630
+ const meta = describeRawMeta(resp); // searchPath/scannedRows/truncated: a 0 or short list is never ambiguous
8631
+ if (!entries.length) return `No results. ${meta}`;
8632
+ const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} UTC. ${meta}\n`];
8617
8633
  for (const e of entries) {
8618
8634
  const ts = new Date(e.timestamp ?? e.LogTimestamp).toISOString().replace('T', ' ').slice(0, 19);
8619
8635
  const lvl = (e.Level ?? e.level ?? '?').padEnd(5);