scanwarp 0.2.0 → 0.3.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.
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ScanWarpAPI = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ class ScanWarpAPI {
9
+ client;
10
+ constructor(serverUrl, apiToken) {
11
+ this.client = axios_1.default.create({
12
+ baseURL: serverUrl,
13
+ headers: apiToken
14
+ ? {
15
+ Authorization: `Bearer ${apiToken}`,
16
+ }
17
+ : {},
18
+ });
19
+ }
20
+ async getMonitors(projectId) {
21
+ const response = await this.client.get('/monitors', {
22
+ params: { project_id: projectId },
23
+ });
24
+ return response.data.monitors || response.data;
25
+ }
26
+ async getIncidents(options) {
27
+ const response = await this.client.get('/incidents', {
28
+ params: {
29
+ project_id: options.projectId,
30
+ status: options.status,
31
+ severity: options.severity,
32
+ limit: options.limit,
33
+ },
34
+ });
35
+ return response.data.incidents || response.data;
36
+ }
37
+ async getIncident(incidentId) {
38
+ const response = await this.client.get(`/incidents/${incidentId}`);
39
+ return response.data;
40
+ }
41
+ async getEvents(options) {
42
+ const response = await this.client.get('/events', {
43
+ params: {
44
+ project_id: options.projectId,
45
+ type: options.type,
46
+ source: options.source,
47
+ severity: options.severity,
48
+ limit: options.limit,
49
+ },
50
+ });
51
+ return response.data;
52
+ }
53
+ async resolveIncident(incidentId) {
54
+ await this.client.post(`/incidents/${incidentId}/resolve`);
55
+ }
56
+ async getProviderStatus() {
57
+ const response = await this.client.get('/provider-status');
58
+ return response.data;
59
+ }
60
+ async getProject(name) {
61
+ const response = await this.client.get('/projects', {
62
+ params: { name },
63
+ });
64
+ const projects = response.data;
65
+ return projects.length > 0 ? projects[0] : null;
66
+ }
67
+ async getRecentTraces(options) {
68
+ const response = await this.client.get('/traces', {
69
+ params: {
70
+ project_id: options.projectId,
71
+ limit: options.limit,
72
+ status: options.status,
73
+ },
74
+ });
75
+ return response.data.traces || [];
76
+ }
77
+ async getTraceDetail(traceId) {
78
+ const response = await this.client.get(`/traces/${traceId}`);
79
+ return response.data.spans || [];
80
+ }
81
+ async getIncidentTraces(incidentId) {
82
+ const response = await this.client.get(`/incidents/${incidentId}/traces`);
83
+ return response.data.spans || [];
84
+ }
85
+ }
86
+ exports.ScanWarpAPI = ScanWarpAPI;
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const api_js_1 = require("./api.js");
8
+ const tools_js_1 = require("./tools.js");
9
+ // Parse command line arguments
10
+ function parseArgs() {
11
+ const args = process.argv.slice(2);
12
+ let serverUrl = process.env.SCANWARP_SERVER_URL || 'http://localhost:3000';
13
+ let apiToken = process.env.SCANWARP_API_TOKEN;
14
+ let projectId = process.env.SCANWARP_PROJECT_ID;
15
+ for (let i = 0; i < args.length; i++) {
16
+ if (args[i] === '--server' && args[i + 1]) {
17
+ serverUrl = args[i + 1];
18
+ i++;
19
+ }
20
+ else if (args[i] === '--token' && args[i + 1]) {
21
+ apiToken = args[i + 1];
22
+ i++;
23
+ }
24
+ else if (args[i] === '--project' && args[i + 1]) {
25
+ projectId = args[i + 1];
26
+ i++;
27
+ }
28
+ }
29
+ return { serverUrl, apiToken, projectId };
30
+ }
31
+ const config = parseArgs();
32
+ if (!config.serverUrl) {
33
+ console.error('❌ Error: SCANWARP_SERVER_URL is not configured. Pass --server or set SCANWARP_SERVER_URL env var.');
34
+ process.exit(1);
35
+ }
36
+ // Initialize API client
37
+ const api = new api_js_1.ScanWarpAPI(config.serverUrl, config.apiToken);
38
+ // Create MCP server
39
+ const server = new index_js_1.Server({
40
+ name: 'scanwarp',
41
+ version: '0.1.0',
42
+ }, {
43
+ capabilities: {
44
+ tools: {},
45
+ resources: {},
46
+ },
47
+ });
48
+ // List available tools
49
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
50
+ return {
51
+ tools: [
52
+ {
53
+ name: 'get_app_status',
54
+ description: 'Get overall health status of your application. Shows monitor status, active incidents, and provider health. Returns a concise plain English summary.',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ project_id: {
59
+ type: 'string',
60
+ description: 'Project ID to check status for',
61
+ },
62
+ },
63
+ required: ['project_id'],
64
+ },
65
+ },
66
+ {
67
+ name: 'get_incidents',
68
+ description: 'List incidents with their diagnosis and fix suggestions. Can filter by status (open/resolved) and severity (critical/warning/info).',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ project_id: {
73
+ type: 'string',
74
+ description: 'Project ID',
75
+ },
76
+ status: {
77
+ type: 'string',
78
+ description: 'Filter by status: open or resolved',
79
+ enum: ['open', 'resolved'],
80
+ },
81
+ severity: {
82
+ type: 'string',
83
+ description: 'Filter by severity: critical, warning, or info',
84
+ enum: ['critical', 'warning', 'info'],
85
+ },
86
+ limit: {
87
+ type: 'number',
88
+ description: 'Maximum number of incidents to return (default: 10)',
89
+ },
90
+ },
91
+ required: ['project_id'],
92
+ },
93
+ },
94
+ {
95
+ name: 'get_incident_detail',
96
+ description: 'Get full details for a specific incident including root cause, timeline, correlated events, and the complete fix prompt ready to use.',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ incident_id: {
101
+ type: 'string',
102
+ description: 'Incident ID',
103
+ },
104
+ },
105
+ required: ['incident_id'],
106
+ },
107
+ },
108
+ {
109
+ name: 'get_events',
110
+ description: 'Get recent events with optional filters. Useful for understanding patterns and troubleshooting.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: {
114
+ project_id: {
115
+ type: 'string',
116
+ description: 'Project ID',
117
+ },
118
+ type: {
119
+ type: 'string',
120
+ description: 'Filter by event type (error, slow, down, up)',
121
+ },
122
+ source: {
123
+ type: 'string',
124
+ description: 'Filter by source (monitor, vercel, stripe, github, supabase, provider-status)',
125
+ },
126
+ severity: {
127
+ type: 'string',
128
+ description: 'Filter by severity (critical, high, medium, low)',
129
+ },
130
+ limit: {
131
+ type: 'number',
132
+ description: 'Maximum number of events to return (default: 20)',
133
+ },
134
+ },
135
+ required: ['project_id'],
136
+ },
137
+ },
138
+ {
139
+ name: 'resolve_incident',
140
+ description: 'Mark an incident as resolved. This will trigger resolution notifications to configured channels.',
141
+ inputSchema: {
142
+ type: 'object',
143
+ properties: {
144
+ incident_id: {
145
+ type: 'string',
146
+ description: 'Incident ID to resolve',
147
+ },
148
+ },
149
+ required: ['incident_id'],
150
+ },
151
+ },
152
+ {
153
+ name: 'get_fix_prompt',
154
+ description: 'Get JUST the fix prompt for an incident - the exact text that can be used in an AI coding tool to fix the issue.',
155
+ inputSchema: {
156
+ type: 'object',
157
+ properties: {
158
+ incident_id: {
159
+ type: 'string',
160
+ description: 'Incident ID',
161
+ },
162
+ },
163
+ required: ['incident_id'],
164
+ },
165
+ },
166
+ {
167
+ name: 'get_recent_traces',
168
+ description: 'List recent request traces from OpenTelemetry instrumentation. Shows root spans with summary info (duration, span count, error status). Filter by status to find failing requests.',
169
+ inputSchema: {
170
+ type: 'object',
171
+ properties: {
172
+ project_id: {
173
+ type: 'string',
174
+ description: 'Project ID',
175
+ },
176
+ limit: {
177
+ type: 'number',
178
+ description: 'Maximum number of traces to return (default: 10)',
179
+ },
180
+ status: {
181
+ type: 'string',
182
+ description: 'Filter by trace status: "error" for traces with errors, "ok" for successful traces',
183
+ enum: ['error', 'ok'],
184
+ },
185
+ },
186
+ required: ['project_id'],
187
+ },
188
+ },
189
+ {
190
+ name: 'get_trace_detail',
191
+ description: 'Get the full request waterfall for a specific trace. Shows all spans as an indented tree with timing, status, and error details. Use this to understand exactly what happened during a request.',
192
+ inputSchema: {
193
+ type: 'object',
194
+ properties: {
195
+ trace_id: {
196
+ type: 'string',
197
+ description: 'Trace ID to inspect',
198
+ },
199
+ },
200
+ required: ['trace_id'],
201
+ },
202
+ },
203
+ {
204
+ name: 'get_trace_for_incident',
205
+ description: 'Get the most relevant trace data for an incident, combined with the AI diagnosis and fix prompt. Shows the request waterfall alongside the root cause analysis.',
206
+ inputSchema: {
207
+ type: 'object',
208
+ properties: {
209
+ incident_id: {
210
+ type: 'string',
211
+ description: 'Incident ID',
212
+ },
213
+ },
214
+ required: ['incident_id'],
215
+ },
216
+ },
217
+ ],
218
+ };
219
+ });
220
+ // Handle tool calls
221
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
222
+ const { name, arguments: args } = request.params;
223
+ try {
224
+ switch (name) {
225
+ case 'get_app_status': {
226
+ const projectId = args.project_id;
227
+ const result = await (0, tools_js_1.getAppStatus)(api, projectId);
228
+ return {
229
+ content: [{ type: 'text', text: result }],
230
+ };
231
+ }
232
+ case 'get_incidents': {
233
+ const { project_id, status, severity, limit } = args;
234
+ const result = await (0, tools_js_1.getIncidents)(api, project_id, {
235
+ status,
236
+ severity,
237
+ limit,
238
+ });
239
+ return {
240
+ content: [{ type: 'text', text: result }],
241
+ };
242
+ }
243
+ case 'get_incident_detail': {
244
+ const { incident_id } = args;
245
+ const result = await (0, tools_js_1.getIncidentDetail)(api, incident_id);
246
+ return {
247
+ content: [{ type: 'text', text: result }],
248
+ };
249
+ }
250
+ case 'get_events': {
251
+ const { project_id, type, source, severity, limit } = args;
252
+ const result = await (0, tools_js_1.getEvents)(api, project_id, {
253
+ type,
254
+ source,
255
+ severity,
256
+ limit,
257
+ });
258
+ return {
259
+ content: [{ type: 'text', text: result }],
260
+ };
261
+ }
262
+ case 'resolve_incident': {
263
+ const { incident_id } = args;
264
+ const result = await (0, tools_js_1.resolveIncident)(api, incident_id);
265
+ return {
266
+ content: [{ type: 'text', text: result }],
267
+ };
268
+ }
269
+ case 'get_fix_prompt': {
270
+ const { incident_id } = args;
271
+ const result = await (0, tools_js_1.getFixPrompt)(api, incident_id);
272
+ return {
273
+ content: [{ type: 'text', text: result }],
274
+ };
275
+ }
276
+ case 'get_recent_traces': {
277
+ const { project_id, limit, status } = args;
278
+ const result = await (0, tools_js_1.getRecentTraces)(api, project_id, { limit, status });
279
+ return {
280
+ content: [{ type: 'text', text: result }],
281
+ };
282
+ }
283
+ case 'get_trace_detail': {
284
+ const { trace_id } = args;
285
+ const result = await (0, tools_js_1.getTraceDetail)(api, trace_id);
286
+ return {
287
+ content: [{ type: 'text', text: result }],
288
+ };
289
+ }
290
+ case 'get_trace_for_incident': {
291
+ const { incident_id } = args;
292
+ const result = await (0, tools_js_1.getTraceForIncident)(api, incident_id);
293
+ return {
294
+ content: [{ type: 'text', text: result }],
295
+ };
296
+ }
297
+ default:
298
+ throw new Error(`Unknown tool: ${name}`);
299
+ }
300
+ }
301
+ catch (error) {
302
+ const errorMessage = error instanceof Error ? error.message : String(error);
303
+ return {
304
+ content: [{ type: 'text', text: `Error: ${errorMessage}` }],
305
+ isError: true,
306
+ };
307
+ }
308
+ });
309
+ // List resources
310
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
311
+ if (!config.projectId) {
312
+ return { resources: [] };
313
+ }
314
+ return {
315
+ resources: [
316
+ {
317
+ uri: 'scanwarp://status',
318
+ name: 'Application Status',
319
+ description: 'Current health status of your application including monitors, incidents, and provider status',
320
+ mimeType: 'text/plain',
321
+ },
322
+ {
323
+ uri: 'scanwarp://incidents',
324
+ name: 'Active Incidents',
325
+ description: 'List of currently active incidents requiring attention',
326
+ mimeType: 'text/plain',
327
+ },
328
+ ],
329
+ };
330
+ });
331
+ // Handle resource reads
332
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
333
+ const { uri } = request.params;
334
+ if (!config.projectId) {
335
+ return {
336
+ contents: [
337
+ {
338
+ uri,
339
+ mimeType: 'text/plain',
340
+ text: 'Error: SCANWARP_PROJECT_ID not configured. Pass --project or set SCANWARP_PROJECT_ID env var.',
341
+ },
342
+ ],
343
+ };
344
+ }
345
+ try {
346
+ switch (uri) {
347
+ case 'scanwarp://status': {
348
+ const status = await (0, tools_js_1.getAppStatus)(api, config.projectId);
349
+ return {
350
+ contents: [
351
+ {
352
+ uri,
353
+ mimeType: 'text/plain',
354
+ text: status,
355
+ },
356
+ ],
357
+ };
358
+ }
359
+ case 'scanwarp://incidents': {
360
+ const incidents = await (0, tools_js_1.getIncidents)(api, config.projectId, {
361
+ status: 'open',
362
+ });
363
+ return {
364
+ contents: [
365
+ {
366
+ uri,
367
+ mimeType: 'text/plain',
368
+ text: incidents,
369
+ },
370
+ ],
371
+ };
372
+ }
373
+ default:
374
+ throw new Error(`Unknown resource: ${uri}`);
375
+ }
376
+ }
377
+ catch (error) {
378
+ const errorMessage = error instanceof Error ? error.message : String(error);
379
+ return {
380
+ contents: [
381
+ {
382
+ uri,
383
+ mimeType: 'text/plain',
384
+ text: `Error: ${errorMessage}`,
385
+ },
386
+ ],
387
+ };
388
+ }
389
+ });
390
+ // Start the server
391
+ async function main() {
392
+ const transport = new stdio_js_1.StdioServerTransport();
393
+ await server.connect(transport);
394
+ console.error('ScanWarp MCP server running on stdio');
395
+ console.error(`Connected to: ${config.serverUrl}`);
396
+ if (config.projectId) {
397
+ console.error(`Default project: ${config.projectId}`);
398
+ }
399
+ }
400
+ main().catch((error) => {
401
+ console.error('Fatal error:', error);
402
+ process.exit(1);
403
+ });