rosentry-mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 RoSentry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # rosentry-mcp
2
+
3
+ MCP (Model Context Protocol) server for [RoSentry](https://rosentry.dev) - AI-powered error monitoring for Roblox games.
4
+
5
+ This package allows Claude and other MCP-compatible AI assistants to query, analyze, and manage errors from your Roblox games directly in conversation.
6
+
7
+ ## Features
8
+
9
+ - **Query Errors** - Fetch recent errors with filters (level, user, place)
10
+ - **Error Groups** - View deduplicated/grouped errors with occurrence counts
11
+ - **Trends Analysis** - See error frequency over time (1h, 24h, 7d, 30d)
12
+ - **Full-Text Search** - Search across error messages and stack traces
13
+ - **User Impact** - See which Roblox users are affected by specific errors
14
+ - **Status Management** - Resolve, ignore, or reopen error groups
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install -g rosentry-mcp
20
+ ```
21
+
22
+ Or use with npx (no install required):
23
+
24
+ ```bash
25
+ npx rosentry-mcp
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ ### Environment Variables
31
+
32
+ | Variable | Required | Description |
33
+ |----------|----------|-------------|
34
+ | `ROSENTRY_SUPABASE_URL` | Yes | Your Supabase project URL |
35
+ | `ROSENTRY_SUPABASE_KEY` | Yes | Supabase service role key |
36
+ | `ROSENTRY_API_KEY` | Yes | Your RoSentry project API key (`rs_...`) |
37
+
38
+ ### Claude Desktop Setup
39
+
40
+ Add to your Claude Desktop config file:
41
+
42
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
43
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "rosentry": {
49
+ "command": "npx",
50
+ "args": ["rosentry-mcp"],
51
+ "env": {
52
+ "ROSENTRY_SUPABASE_URL": "https://your-project.supabase.co",
53
+ "ROSENTRY_SUPABASE_KEY": "your-service-role-key",
54
+ "ROSENTRY_API_KEY": "rs_your_api_key"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Or if installed globally:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "rosentry": {
67
+ "command": "rosentry-mcp",
68
+ "env": {
69
+ "ROSENTRY_SUPABASE_URL": "https://your-project.supabase.co",
70
+ "ROSENTRY_SUPABASE_KEY": "your-service-role-key",
71
+ "ROSENTRY_API_KEY": "rs_your_api_key"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Available Tools
79
+
80
+ ### `get_recent_errors`
81
+ Fetch recent error events with full details including stack traces and context.
82
+
83
+ | Parameter | Type | Description |
84
+ |-----------|------|-------------|
85
+ | `limit` | number | Max errors to return (default: 20, max: 100) |
86
+ | `level` | string | Filter by level: `error`, `warn`, `info`, `debug` |
87
+ | `user_id` | number | Filter by Roblox UserId |
88
+ | `place_id` | number | Filter by Roblox PlaceId |
89
+ | `search` | string | Search term for error messages |
90
+
91
+ ### `get_error_groups`
92
+ Fetch grouped/deduplicated errors showing occurrence counts and status.
93
+
94
+ | Parameter | Type | Description |
95
+ |-----------|------|-------------|
96
+ | `limit` | number | Max groups to return (default: 20, max: 50) |
97
+ | `status` | string | Filter by status: `open`, `resolved`, `ignored` |
98
+ | `search` | string | Search term for error messages |
99
+
100
+ ### `get_error_group_details`
101
+ Get detailed info about a specific error group including individual events.
102
+
103
+ | Parameter | Type | Description |
104
+ |-----------|------|-------------|
105
+ | `group_id` | string | **Required.** UUID of the error group |
106
+ | `include_events` | boolean | Include individual events (default: true) |
107
+ | `events_limit` | number | Max events to include (default: 10) |
108
+
109
+ ### `get_error_trends`
110
+ Get error frequency trends over time.
111
+
112
+ | Parameter | Type | Description |
113
+ |-----------|------|-------------|
114
+ | `timeframe` | string | Time range: `1h`, `24h`, `7d`, `30d` (default: 24h) |
115
+ | `group_by` | string | Grouping: `hour` or `day` |
116
+
117
+ ### `search_errors`
118
+ Full-text search across error messages and stack traces.
119
+
120
+ | Parameter | Type | Description |
121
+ |-----------|------|-------------|
122
+ | `query` | string | **Required.** Search query |
123
+ | `limit` | number | Max results (default: 20) |
124
+
125
+ ### `get_affected_users`
126
+ Get Roblox users affected by a specific error group.
127
+
128
+ | Parameter | Type | Description |
129
+ |-----------|------|-------------|
130
+ | `group_id` | string | **Required.** UUID of the error group |
131
+ | `limit` | number | Max users to return (default: 50) |
132
+
133
+ ### `get_stats`
134
+ Get summary statistics for your project.
135
+
136
+ | Parameter | Type | Description |
137
+ |-----------|------|-------------|
138
+ | `timeframe` | string | Time range: `1h`, `24h`, `7d`, `30d` (default: 24h) |
139
+
140
+ Returns: Total errors, errors by level, open groups count, affected users, top 5 errors.
141
+
142
+ ### `update_error_status`
143
+ Update the status of an error group.
144
+
145
+ | Parameter | Type | Description |
146
+ |-----------|------|-------------|
147
+ | `group_id` | string | **Required.** UUID of the error group |
148
+ | `status` | string | **Required.** New status: `open`, `resolved`, `ignored` |
149
+
150
+ ## Example Usage with Claude
151
+
152
+ Once configured, you can ask Claude things like:
153
+
154
+ - *"What errors happened in the last hour?"*
155
+ - *"Show me the top errors affecting users"*
156
+ - *"Search for errors related to 'DataStore'"*
157
+ - *"How many users are affected by error group abc-123?"*
158
+ - *"Mark that error as resolved"*
159
+ - *"What's the error trend for the past week?"*
160
+
161
+ ## Development
162
+
163
+ ```bash
164
+ # Clone the repo
165
+ git clone https://github.com/rosentry/rosentry.git
166
+ cd rosentry/mcp
167
+
168
+ # Install dependencies
169
+ npm install
170
+
171
+ # Run in development
172
+ npm run dev
173
+
174
+ # Build for production
175
+ npm run build
176
+ ```
177
+
178
+ ## Related
179
+
180
+ - [RoSentry](https://rosentry.dev) - Error monitoring platform for Roblox
181
+ - [RoSentry Lua SDK](https://github.com/rosentry/rosentry/tree/main/sdk) - Roblox Lua SDK
182
+ - [Model Context Protocol](https://modelcontextprotocol.io) - MCP specification
183
+
184
+ ## License
185
+
186
+ MIT - see [LICENSE](./LICENSE)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { createClient } from "@supabase/supabase-js";
6
+ // Initialize Supabase client
7
+ const supabaseUrl = process.env.ROSENTRY_SUPABASE_URL;
8
+ const supabaseKey = process.env.ROSENTRY_SUPABASE_KEY;
9
+ const projectApiKey = process.env.ROSENTRY_API_KEY;
10
+ let supabase = null;
11
+ let projectId = null;
12
+ async function initializeClient() {
13
+ if (!supabaseUrl || !supabaseKey) {
14
+ throw new Error("Missing ROSENTRY_SUPABASE_URL or ROSENTRY_SUPABASE_KEY environment variables");
15
+ }
16
+ supabase = createClient(supabaseUrl, supabaseKey);
17
+ // If API key is provided, get the project ID
18
+ if (projectApiKey) {
19
+ const { data: project } = await supabase
20
+ .from("projects")
21
+ .select("id")
22
+ .eq("api_key", projectApiKey)
23
+ .single();
24
+ if (project) {
25
+ projectId = project.id;
26
+ }
27
+ }
28
+ }
29
+ // Tool definitions
30
+ const tools = [
31
+ {
32
+ name: "get_recent_errors",
33
+ description: "Fetch recent errors from RoSentry. Returns individual error events with full details including stack traces, user context, and metadata.",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {
37
+ limit: {
38
+ type: "number",
39
+ description: "Maximum number of errors to return (default: 20, max: 100)",
40
+ },
41
+ level: {
42
+ type: "string",
43
+ enum: ["error", "warn", "info", "debug"],
44
+ description: "Filter by error level",
45
+ },
46
+ user_id: {
47
+ type: "number",
48
+ description: "Filter by Roblox UserId",
49
+ },
50
+ place_id: {
51
+ type: "number",
52
+ description: "Filter by Roblox PlaceId",
53
+ },
54
+ search: {
55
+ type: "string",
56
+ description: "Search term to filter error messages",
57
+ },
58
+ },
59
+ },
60
+ },
61
+ {
62
+ name: "get_error_groups",
63
+ description: "Fetch grouped/deduplicated errors from RoSentry. Error groups combine similar errors by fingerprint, showing occurrence count and status.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ limit: {
68
+ type: "number",
69
+ description: "Maximum number of groups to return (default: 20, max: 50)",
70
+ },
71
+ status: {
72
+ type: "string",
73
+ enum: ["open", "resolved", "ignored"],
74
+ description: "Filter by group status",
75
+ },
76
+ search: {
77
+ type: "string",
78
+ description: "Search term to filter error messages",
79
+ },
80
+ },
81
+ },
82
+ },
83
+ {
84
+ name: "get_error_group_details",
85
+ description: "Get detailed information about a specific error group, including all individual error events in that group.",
86
+ inputSchema: {
87
+ type: "object",
88
+ properties: {
89
+ group_id: {
90
+ type: "string",
91
+ description: "The UUID of the error group",
92
+ },
93
+ include_events: {
94
+ type: "boolean",
95
+ description: "Include individual error events (default: true)",
96
+ },
97
+ events_limit: {
98
+ type: "number",
99
+ description: "Maximum number of events to include (default: 10)",
100
+ },
101
+ },
102
+ required: ["group_id"],
103
+ },
104
+ },
105
+ {
106
+ name: "get_error_trends",
107
+ description: "Get error frequency trends over time. Useful for understanding when errors spike or identifying patterns.",
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {
111
+ timeframe: {
112
+ type: "string",
113
+ enum: ["1h", "24h", "7d", "30d"],
114
+ description: "Time range for trends (default: 24h)",
115
+ },
116
+ group_by: {
117
+ type: "string",
118
+ enum: ["hour", "day"],
119
+ description: "How to group the data points",
120
+ },
121
+ },
122
+ },
123
+ },
124
+ {
125
+ name: "search_errors",
126
+ description: "Full-text search across all error messages and stack traces. Returns matching errors with context.",
127
+ inputSchema: {
128
+ type: "object",
129
+ properties: {
130
+ query: {
131
+ type: "string",
132
+ description: "Search query string",
133
+ },
134
+ limit: {
135
+ type: "number",
136
+ description: "Maximum results to return (default: 20)",
137
+ },
138
+ },
139
+ required: ["query"],
140
+ },
141
+ },
142
+ {
143
+ name: "get_affected_users",
144
+ description: "Get list of Roblox users affected by a specific error group. Helpful for understanding impact.",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ group_id: {
149
+ type: "string",
150
+ description: "The UUID of the error group",
151
+ },
152
+ limit: {
153
+ type: "number",
154
+ description: "Maximum users to return (default: 50)",
155
+ },
156
+ },
157
+ required: ["group_id"],
158
+ },
159
+ },
160
+ {
161
+ name: "get_stats",
162
+ description: "Get summary statistics for error monitoring - total errors, errors by level, affected users, and top errors.",
163
+ inputSchema: {
164
+ type: "object",
165
+ properties: {
166
+ timeframe: {
167
+ type: "string",
168
+ enum: ["1h", "24h", "7d", "30d"],
169
+ description: "Time range for stats (default: 24h)",
170
+ },
171
+ },
172
+ },
173
+ },
174
+ {
175
+ name: "update_error_status",
176
+ description: "Update the status of an error group (resolve, ignore, or reopen).",
177
+ inputSchema: {
178
+ type: "object",
179
+ properties: {
180
+ group_id: {
181
+ type: "string",
182
+ description: "The UUID of the error group",
183
+ },
184
+ status: {
185
+ type: "string",
186
+ enum: ["open", "resolved", "ignored"],
187
+ description: "New status for the error group",
188
+ },
189
+ },
190
+ required: ["group_id", "status"],
191
+ },
192
+ },
193
+ ];
194
+ // Tool handlers
195
+ async function handleGetRecentErrors(args) {
196
+ if (!supabase || !projectId) {
197
+ return { error: "Not connected to RoSentry" };
198
+ }
199
+ const limit = Math.min(args.limit ?? 20, 100);
200
+ let query = supabase
201
+ .from("errors")
202
+ .select("*")
203
+ .eq("project_id", projectId)
204
+ .order("timestamp", { ascending: false })
205
+ .limit(limit);
206
+ if (args.level)
207
+ query = query.eq("level", args.level);
208
+ if (args.user_id)
209
+ query = query.eq("user_id", args.user_id);
210
+ if (args.place_id)
211
+ query = query.eq("place_id", args.place_id);
212
+ if (args.search)
213
+ query = query.ilike("message", `%${args.search}%`);
214
+ const { data, error } = await query;
215
+ if (error)
216
+ return { error: error.message };
217
+ return { errors: data, count: data?.length ?? 0 };
218
+ }
219
+ async function handleGetErrorGroups(args) {
220
+ if (!supabase || !projectId) {
221
+ return { error: "Not connected to RoSentry" };
222
+ }
223
+ const limit = Math.min(args.limit ?? 20, 50);
224
+ let query = supabase
225
+ .from("error_groups")
226
+ .select("*")
227
+ .eq("project_id", projectId)
228
+ .order("last_seen", { ascending: false })
229
+ .limit(limit);
230
+ if (args.status)
231
+ query = query.eq("status", args.status);
232
+ if (args.search)
233
+ query = query.ilike("message", `%${args.search}%`);
234
+ const { data, error } = await query;
235
+ if (error)
236
+ return { error: error.message };
237
+ return { groups: data, count: data?.length ?? 0 };
238
+ }
239
+ async function handleGetErrorGroupDetails(args) {
240
+ if (!supabase || !projectId) {
241
+ return { error: "Not connected to RoSentry" };
242
+ }
243
+ // Get group
244
+ const { data: group, error: groupError } = await supabase
245
+ .from("error_groups")
246
+ .select("*")
247
+ .eq("id", args.group_id)
248
+ .eq("project_id", projectId)
249
+ .single();
250
+ if (groupError)
251
+ return { error: groupError.message };
252
+ if (!group)
253
+ return { error: "Error group not found" };
254
+ const result = { group };
255
+ // Get events if requested
256
+ if (args.include_events !== false) {
257
+ const { data: events } = await supabase
258
+ .from("errors")
259
+ .select("*")
260
+ .eq("error_group_id", args.group_id)
261
+ .order("timestamp", { ascending: false })
262
+ .limit(args.events_limit ?? 10);
263
+ result.events = events ?? [];
264
+ }
265
+ return result;
266
+ }
267
+ async function handleGetErrorTrends(args) {
268
+ if (!supabase || !projectId) {
269
+ return { error: "Not connected to RoSentry" };
270
+ }
271
+ const timeframe = args.timeframe ?? "24h";
272
+ const now = new Date();
273
+ let since;
274
+ switch (timeframe) {
275
+ case "1h":
276
+ since = new Date(now.getTime() - 60 * 60 * 1000);
277
+ break;
278
+ case "7d":
279
+ since = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
280
+ break;
281
+ case "30d":
282
+ since = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
283
+ break;
284
+ default:
285
+ since = new Date(now.getTime() - 24 * 60 * 60 * 1000);
286
+ }
287
+ const { data: errors } = await supabase
288
+ .from("errors")
289
+ .select("timestamp, level")
290
+ .eq("project_id", projectId)
291
+ .gte("timestamp", since.toISOString())
292
+ .order("timestamp", { ascending: true });
293
+ if (!errors)
294
+ return { trends: [], timeframe };
295
+ // Group by time bucket
296
+ const groupBy = args.group_by ?? (timeframe === "1h" ? "hour" : "day");
297
+ const buckets = {};
298
+ for (const error of errors) {
299
+ const date = new Date(error.timestamp);
300
+ let key;
301
+ if (groupBy === "hour") {
302
+ key = `${date.toISOString().slice(0, 13)}:00`;
303
+ }
304
+ else {
305
+ key = date.toISOString().slice(0, 10);
306
+ }
307
+ if (!buckets[key]) {
308
+ buckets[key] = { count: 0, errors: 0, warnings: 0 };
309
+ }
310
+ buckets[key].count++;
311
+ if (error.level === "error")
312
+ buckets[key].errors++;
313
+ if (error.level === "warn")
314
+ buckets[key].warnings++;
315
+ }
316
+ const trends = Object.entries(buckets).map(([time, data]) => ({
317
+ time,
318
+ ...data,
319
+ }));
320
+ return { trends, timeframe, group_by: groupBy };
321
+ }
322
+ async function handleSearchErrors(args) {
323
+ if (!supabase || !projectId) {
324
+ return { error: "Not connected to RoSentry" };
325
+ }
326
+ const limit = Math.min(args.limit ?? 20, 100);
327
+ // Search in messages and stack traces
328
+ const { data: messageMatches } = await supabase
329
+ .from("errors")
330
+ .select("*")
331
+ .eq("project_id", projectId)
332
+ .ilike("message", `%${args.query}%`)
333
+ .order("timestamp", { ascending: false })
334
+ .limit(limit);
335
+ const { data: stackMatches } = await supabase
336
+ .from("errors")
337
+ .select("*")
338
+ .eq("project_id", projectId)
339
+ .ilike("stack_trace", `%${args.query}%`)
340
+ .order("timestamp", { ascending: false })
341
+ .limit(limit);
342
+ // Combine and dedupe
343
+ const seen = new Set();
344
+ const results = [];
345
+ for (const error of [...(messageMatches ?? []), ...(stackMatches ?? [])]) {
346
+ if (!seen.has(error.id)) {
347
+ seen.add(error.id);
348
+ results.push(error);
349
+ }
350
+ if (results.length >= limit)
351
+ break;
352
+ }
353
+ return { results, count: results.length, query: args.query };
354
+ }
355
+ async function handleGetAffectedUsers(args) {
356
+ if (!supabase || !projectId) {
357
+ return { error: "Not connected to RoSentry" };
358
+ }
359
+ const limit = Math.min(args.limit ?? 50, 200);
360
+ const { data: errors } = await supabase
361
+ .from("errors")
362
+ .select("user_id, timestamp")
363
+ .eq("error_group_id", args.group_id)
364
+ .not("user_id", "is", null)
365
+ .order("timestamp", { ascending: false });
366
+ if (!errors)
367
+ return { users: [], count: 0 };
368
+ // Dedupe users and count occurrences
369
+ const userStats = {};
370
+ for (const error of errors) {
371
+ if (error.user_id) {
372
+ if (!userStats[error.user_id]) {
373
+ userStats[error.user_id] = { count: 0, last_seen: error.timestamp };
374
+ }
375
+ userStats[error.user_id].count++;
376
+ }
377
+ }
378
+ const users = Object.entries(userStats)
379
+ .map(([userId, stats]) => ({
380
+ user_id: parseInt(userId),
381
+ occurrence_count: stats.count,
382
+ last_seen: stats.last_seen,
383
+ }))
384
+ .sort((a, b) => b.occurrence_count - a.occurrence_count)
385
+ .slice(0, limit);
386
+ return { users, total_affected: Object.keys(userStats).length };
387
+ }
388
+ async function handleGetStats(args) {
389
+ if (!supabase || !projectId) {
390
+ return { error: "Not connected to RoSentry" };
391
+ }
392
+ const timeframe = args.timeframe ?? "24h";
393
+ const now = new Date();
394
+ let since;
395
+ switch (timeframe) {
396
+ case "1h":
397
+ since = new Date(now.getTime() - 60 * 60 * 1000);
398
+ break;
399
+ case "7d":
400
+ since = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
401
+ break;
402
+ case "30d":
403
+ since = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
404
+ break;
405
+ default:
406
+ since = new Date(now.getTime() - 24 * 60 * 60 * 1000);
407
+ }
408
+ // Get total errors
409
+ const { count: totalErrors } = await supabase
410
+ .from("errors")
411
+ .select("*", { count: "exact", head: true })
412
+ .eq("project_id", projectId)
413
+ .gte("timestamp", since.toISOString());
414
+ // Get errors by level
415
+ const { data: levelData } = await supabase
416
+ .from("errors")
417
+ .select("level")
418
+ .eq("project_id", projectId)
419
+ .gte("timestamp", since.toISOString());
420
+ const byLevel = (levelData ?? []).reduce((acc, { level }) => {
421
+ acc[level] = (acc[level] ?? 0) + 1;
422
+ return acc;
423
+ }, {});
424
+ // Get open groups
425
+ const { count: openGroups } = await supabase
426
+ .from("error_groups")
427
+ .select("*", { count: "exact", head: true })
428
+ .eq("project_id", projectId)
429
+ .eq("status", "open");
430
+ // Get unique users
431
+ const { data: userData } = await supabase
432
+ .from("errors")
433
+ .select("user_id")
434
+ .eq("project_id", projectId)
435
+ .gte("timestamp", since.toISOString())
436
+ .not("user_id", "is", null);
437
+ const uniqueUsers = new Set(userData?.map((e) => e.user_id)).size;
438
+ // Get top errors
439
+ const { data: topErrors } = await supabase
440
+ .from("error_groups")
441
+ .select("id, message, count, status")
442
+ .eq("project_id", projectId)
443
+ .eq("status", "open")
444
+ .order("count", { ascending: false })
445
+ .limit(5);
446
+ return {
447
+ timeframe,
448
+ total_errors: totalErrors ?? 0,
449
+ by_level: byLevel,
450
+ open_groups: openGroups ?? 0,
451
+ affected_users: uniqueUsers,
452
+ top_errors: topErrors ?? [],
453
+ };
454
+ }
455
+ async function handleUpdateErrorStatus(args) {
456
+ if (!supabase || !projectId) {
457
+ return { error: "Not connected to RoSentry" };
458
+ }
459
+ const { error } = await supabase
460
+ .from("error_groups")
461
+ .update({ status: args.status, updated_at: new Date().toISOString() })
462
+ .eq("id", args.group_id)
463
+ .eq("project_id", projectId);
464
+ if (error)
465
+ return { error: error.message };
466
+ return { success: true, group_id: args.group_id, new_status: args.status };
467
+ }
468
+ // Main server setup
469
+ async function main() {
470
+ await initializeClient();
471
+ const server = new Server({
472
+ name: "rosentry-mcp",
473
+ version: "0.1.0",
474
+ }, {
475
+ capabilities: {
476
+ tools: {},
477
+ },
478
+ });
479
+ // List tools handler
480
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
481
+ tools,
482
+ }));
483
+ // Call tool handler
484
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
485
+ const { name, arguments: args } = request.params;
486
+ try {
487
+ let result;
488
+ switch (name) {
489
+ case "get_recent_errors":
490
+ result = await handleGetRecentErrors(args);
491
+ break;
492
+ case "get_error_groups":
493
+ result = await handleGetErrorGroups(args);
494
+ break;
495
+ case "get_error_group_details":
496
+ result = await handleGetErrorGroupDetails(args);
497
+ break;
498
+ case "get_error_trends":
499
+ result = await handleGetErrorTrends(args);
500
+ break;
501
+ case "search_errors":
502
+ result = await handleSearchErrors(args);
503
+ break;
504
+ case "get_affected_users":
505
+ result = await handleGetAffectedUsers(args);
506
+ break;
507
+ case "get_stats":
508
+ result = await handleGetStats(args);
509
+ break;
510
+ case "update_error_status":
511
+ result = await handleUpdateErrorStatus(args);
512
+ break;
513
+ default:
514
+ return {
515
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
516
+ isError: true,
517
+ };
518
+ }
519
+ return {
520
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
521
+ };
522
+ }
523
+ catch (error) {
524
+ return {
525
+ content: [{ type: "text", text: `Error: ${error}` }],
526
+ isError: true,
527
+ };
528
+ }
529
+ });
530
+ // Connect via stdio
531
+ const transport = new StdioServerTransport();
532
+ await server.connect(transport);
533
+ console.error("RoSentry MCP server running");
534
+ }
535
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "rosentry-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP (Model Context Protocol) server for RoSentry - AI-powered error monitoring for Roblox",
5
+ "author": "RoSentry",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "bin": {
11
+ "rosentry-mcp": "dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsx src/index.ts",
21
+ "start": "node dist/index.js",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "model-context-protocol",
27
+ "rosentry",
28
+ "roblox",
29
+ "error-monitoring",
30
+ "sentry",
31
+ "claude",
32
+ "ai",
33
+ "debugging"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/rosentry/rosentry.git",
38
+ "directory": "mcp"
39
+ },
40
+ "homepage": "https://rosentry.dev",
41
+ "bugs": {
42
+ "url": "https://github.com/rosentry/rosentry/issues"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.0.0",
49
+ "@supabase/supabase-js": "^2.50.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.0.0",
53
+ "tsx": "^4.7.0",
54
+ "typescript": "^5.0.0"
55
+ }
56
+ }