cubeapm-mcp 1.3.0 → 1.3.1

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/dist/index.js +88 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25,6 +25,90 @@ const server = new McpServer({
25
25
  // ============================================
26
26
  // LOGS APIs
27
27
  // ============================================
28
+ // Convert RFC3339 string or Unix timestamp (s/ms/μs/ns) to nanoseconds.
29
+ // CubeAPM's prod UI embeds time in queries as `_time:[ns_start, ns_end)`,
30
+ // so we mirror that wire format rather than relying on start/end form params.
31
+ function toNanoseconds(t) {
32
+ if (/^\d+$/.test(t)) {
33
+ const n = BigInt(t);
34
+ if (n < 10000000000n)
35
+ return (n * 1000000000n).toString(); // seconds
36
+ if (n < 10000000000000n)
37
+ return (n * 1000000n).toString(); // milliseconds
38
+ if (n < 10000000000000000n)
39
+ return (n * 1000n).toString(); // microseconds
40
+ return n.toString(); // nanoseconds
41
+ }
42
+ const ms = new Date(t).getTime();
43
+ if (Number.isNaN(ms))
44
+ throw new Error(`Invalid timestamp: ${t}`);
45
+ return (BigInt(ms) * 1000000n).toString();
46
+ }
47
+ // Find the index of the first top-level pipe operator in a LogsQL query,
48
+ // ignoring `|` inside quoted strings (regex alternation like `=~"a|b"`),
49
+ // brace groups `{...}`, or parens `(...)`. Returns -1 if no pipe found.
50
+ function findFirstTopLevelPipe(query) {
51
+ let inDouble = false;
52
+ let inSingle = false;
53
+ let braceDepth = 0;
54
+ let parenDepth = 0;
55
+ for (let i = 0; i < query.length; i++) {
56
+ const c = query[i];
57
+ const prev = i > 0 ? query[i - 1] : "";
58
+ // Honour backslash escapes inside strings (e.g. `"shopify\\-prod"`).
59
+ if (prev === "\\")
60
+ continue;
61
+ if (inDouble) {
62
+ if (c === '"')
63
+ inDouble = false;
64
+ continue;
65
+ }
66
+ if (inSingle) {
67
+ if (c === "'")
68
+ inSingle = false;
69
+ continue;
70
+ }
71
+ if (c === '"') {
72
+ inDouble = true;
73
+ continue;
74
+ }
75
+ if (c === "'") {
76
+ inSingle = true;
77
+ continue;
78
+ }
79
+ if (c === "{") {
80
+ braceDepth++;
81
+ continue;
82
+ }
83
+ if (c === "}") {
84
+ braceDepth--;
85
+ continue;
86
+ }
87
+ if (c === "(") {
88
+ parenDepth++;
89
+ continue;
90
+ }
91
+ if (c === ")") {
92
+ parenDepth--;
93
+ continue;
94
+ }
95
+ if (c === "|" && braceDepth === 0 && parenDepth === 0)
96
+ return i;
97
+ }
98
+ return -1;
99
+ }
100
+ // Inject `_time:[start_ns,end_ns)` before the first top-level pipe operator
101
+ // (or at end if none). Quote/brace/paren aware to handle regex alternations
102
+ // like `=~"shopify-latest|shopify-prod"` without splitting mid-string.
103
+ function injectTimeFilter(query, startNs, endNs) {
104
+ const timeFilter = `_time:[${startNs},${endNs})`;
105
+ const pipeIdx = findFirstTopLevelPipe(query);
106
+ if (pipeIdx === -1)
107
+ return `${query.trim()} ${timeFilter}`.trim();
108
+ const head = query.slice(0, pipeIdx).trim();
109
+ const tail = query.slice(pipeIdx);
110
+ return `${head} ${timeFilter} ${tail}`;
111
+ }
28
112
  server.tool("query_logs", `Query logs from CubeAPM using LogsQL syntax (VictoriaLogs compatible). Returns log entries matching the query.
29
113
 
30
114
  IMPORTANT - Log Label Discovery:
@@ -79,10 +163,11 @@ Example queries:
79
163
  end: z.string().describe("End time in RFC3339 format or Unix timestamp in seconds"),
80
164
  limit: z.number().optional().default(100).describe("Maximum log entries to return (default: 100)"),
81
165
  }, async ({ query, start, end, limit }) => {
166
+ const startNs = toNanoseconds(start);
167
+ const endNs = toNanoseconds(end);
168
+ const finalQuery = injectTimeFilter(query, startNs, endNs);
82
169
  const params = new URLSearchParams({
83
- query,
84
- start,
85
- end,
170
+ query: finalQuery,
86
171
  limit: String(limit),
87
172
  });
88
173
  const response = await fetch(`${queryBaseUrl}/api/logs/select/logsql/query`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cubeapm-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "MCP server for CubeAPM - Query traces, metrics, and logs from your observability platform using AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",