@rustrak/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 +22 -0
- package/README.md +158 -0
- package/dist/index.js +459 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2026 Abian Suarez
|
|
5
|
+
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
7
|
+
of this license document, but changing it is not allowed.
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
|
10
|
+
it under the terms of the GNU General Public License as published by
|
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
(at your option) any later version.
|
|
13
|
+
|
|
14
|
+
This program is distributed in the hope that it will be useful,
|
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
GNU General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU General Public License
|
|
20
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
+
|
|
22
|
+
For the full license text, see: https://www.gnu.org/licenses/gpl-3.0.txt
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @rustrak/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for Rustrak. Lets AI assistants (Claude Desktop, Cursor, etc.) manage your error tracking directly — list projects, inspect issues, view stack traces, resolve errors, and manage tokens without leaving your AI tool.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **18 tools** covering projects, issues, events, tokens, and alert channels
|
|
8
|
+
- **stdio transport** — runs as a local process, no network port needed
|
|
9
|
+
- **Secure** — API token loaded from env vars, never passed as tool argument
|
|
10
|
+
- **Safe destructive actions** — `delete_issue` and `revoke_token` annotated with `destructiveHint`
|
|
11
|
+
- **Graceful errors** — all API errors returned as `isError: true` content, never thrown
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Generate an API token
|
|
16
|
+
|
|
17
|
+
In the Rustrak web UI: **Settings → Tokens → Create token**. Save the full token value — it is shown only once.
|
|
18
|
+
|
|
19
|
+
### 2. Configure in Claude Desktop
|
|
20
|
+
|
|
21
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"rustrak": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["-y", "@rustrak/mcp"],
|
|
29
|
+
"env": {
|
|
30
|
+
"RUSTRAK_API_URL": "https://your-rustrak-instance.example.com",
|
|
31
|
+
"RUSTRAK_API_TOKEN": "your-40-char-hex-token"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Local monorepo (development)
|
|
39
|
+
|
|
40
|
+
Build first, then configure with a `node` command pointing to the dist:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"rustrak": {
|
|
46
|
+
"command": "node",
|
|
47
|
+
"args": ["packages/mcp/dist/index.js"],
|
|
48
|
+
"env": {
|
|
49
|
+
"RUSTRAK_API_URL": "http://localhost:8080",
|
|
50
|
+
"RUSTRAK_API_TOKEN": "your-40-char-hex-token"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or use the project-level `.mcp.json` at the repo root (Claude Code picks this up automatically).
|
|
58
|
+
|
|
59
|
+
## Environment Variables
|
|
60
|
+
|
|
61
|
+
| Variable | Required | Default | Description |
|
|
62
|
+
|----------|----------|---------|-------------|
|
|
63
|
+
| `RUSTRAK_API_TOKEN` | ✅ | — | 40-char hex API token. Server exits if missing. |
|
|
64
|
+
| `RUSTRAK_API_URL` | ✅ | — | Base URL of your Rustrak server. Server exits if missing. |
|
|
65
|
+
|
|
66
|
+
## Available Tools
|
|
67
|
+
|
|
68
|
+
### Projects
|
|
69
|
+
| Tool | Description |
|
|
70
|
+
|------|-------------|
|
|
71
|
+
| `list_projects` | List all projects |
|
|
72
|
+
| `get_project` | Get project details including DSN |
|
|
73
|
+
| `create_project` | Create a new project |
|
|
74
|
+
|
|
75
|
+
### Issues
|
|
76
|
+
| Tool | Description |
|
|
77
|
+
|------|-------------|
|
|
78
|
+
| `list_issues` | List issues with filters (open / resolved / muted / all) |
|
|
79
|
+
| `get_issue` | Get a single issue with full details |
|
|
80
|
+
| `resolve_issue` | Mark an issue as resolved |
|
|
81
|
+
| `unresolve_issue` | Re-open a resolved issue |
|
|
82
|
+
| `mute_issue` | Mute an issue (silences alerts) |
|
|
83
|
+
| `delete_issue` | ⚠️ Permanently delete an issue and all its events |
|
|
84
|
+
|
|
85
|
+
### Events
|
|
86
|
+
| Tool | Description |
|
|
87
|
+
|------|-------------|
|
|
88
|
+
| `list_events` | List raw events for an issue (cursor pagination) |
|
|
89
|
+
| `get_event` | Get a single event with full Sentry envelope data |
|
|
90
|
+
|
|
91
|
+
### Tokens
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `list_tokens` | List API tokens (masked) |
|
|
95
|
+
| `create_token` | Create a new API token (full value shown once) |
|
|
96
|
+
| `revoke_token` | ⚠️ Permanently revoke an API token |
|
|
97
|
+
|
|
98
|
+
### Alerts
|
|
99
|
+
| Tool | Description |
|
|
100
|
+
|------|-------------|
|
|
101
|
+
| `list_alert_channels` | List notification channels (Slack, email, webhook) |
|
|
102
|
+
| `test_alert_channel` | Send a test notification to a channel |
|
|
103
|
+
| `list_alert_rules` | List alert rules for a project |
|
|
104
|
+
|
|
105
|
+
> ⚠️ Tools marked as destructive will prompt for confirmation in supported clients.
|
|
106
|
+
|
|
107
|
+
## Example prompts
|
|
108
|
+
|
|
109
|
+
Once connected, you can ask your AI assistant things like:
|
|
110
|
+
|
|
111
|
+
- _"List all unresolved issues in project 1"_
|
|
112
|
+
- _"Show me the stack trace for issue abc-123"_
|
|
113
|
+
- _"Resolve all TypeError issues from the last deployment"_
|
|
114
|
+
- _"Create a token called 'CI pipeline' and give me the value"_
|
|
115
|
+
- _"How many events does issue xyz have?"_
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Build
|
|
121
|
+
pnpm --filter @rustrak/mcp build
|
|
122
|
+
|
|
123
|
+
# Run tests (33 tests)
|
|
124
|
+
pnpm --filter @rustrak/mcp test
|
|
125
|
+
|
|
126
|
+
# Type check
|
|
127
|
+
pnpm --filter @rustrak/mcp typecheck
|
|
128
|
+
|
|
129
|
+
# Watch mode
|
|
130
|
+
pnpm --filter @rustrak/mcp dev
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Architecture
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
AI Client (Claude Desktop / Cursor)
|
|
137
|
+
│ stdio (JSON-RPC)
|
|
138
|
+
▼
|
|
139
|
+
┌─────────────────────┐
|
|
140
|
+
│ @rustrak/mcp │
|
|
141
|
+
│ McpServer │
|
|
142
|
+
│ ├── projects │
|
|
143
|
+
│ ├── issues │
|
|
144
|
+
│ ├── events │
|
|
145
|
+
│ ├── tokens │
|
|
146
|
+
│ └── alerts │
|
|
147
|
+
└──────────┬──────────┘
|
|
148
|
+
│ HTTP (Bearer token)
|
|
149
|
+
▼
|
|
150
|
+
┌─────────────────────┐
|
|
151
|
+
│ Rustrak Server │
|
|
152
|
+
│ (Rust/Actix-web) │
|
|
153
|
+
└─────────────────────┘
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
GPL-3.0
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { RustrakClient } from "@rustrak/client";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
const url = process.env["RUSTRAK_API_URL"];
|
|
10
|
+
if (!url) {
|
|
11
|
+
console.error(
|
|
12
|
+
"[rustrak-mcp] Missing required environment variable: RUSTRAK_API_URL. Set RUSTRAK_API_URL to the base URL of your Rustrak server."
|
|
13
|
+
);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const token = process.env["RUSTRAK_API_TOKEN"];
|
|
17
|
+
if (!token) {
|
|
18
|
+
console.error(
|
|
19
|
+
"[rustrak-mcp] Missing required environment variable: RUSTRAK_API_TOKEN. Set RUSTRAK_API_TOKEN to a valid Rustrak API token."
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
return { RUSTRAK_API_URL: url, RUSTRAK_API_TOKEN: token };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/server.ts
|
|
27
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
28
|
+
|
|
29
|
+
// src/tools/alerts.ts
|
|
30
|
+
import { z } from "zod";
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
import {
|
|
34
|
+
AuthenticationError,
|
|
35
|
+
NotFoundError,
|
|
36
|
+
RateLimitError,
|
|
37
|
+
RustrakError
|
|
38
|
+
} from "@rustrak/client";
|
|
39
|
+
function mcpError(text) {
|
|
40
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
41
|
+
}
|
|
42
|
+
function toMcpError(err) {
|
|
43
|
+
if (err instanceof NotFoundError) {
|
|
44
|
+
return mcpError(`Not found: ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
if (err instanceof RateLimitError) {
|
|
47
|
+
const after = err.retryAfter !== void 0 ? err.retryAfter : "?";
|
|
48
|
+
return mcpError(`Rate limited. Retry after: ${after}s`);
|
|
49
|
+
}
|
|
50
|
+
if (err instanceof AuthenticationError) {
|
|
51
|
+
return mcpError("Authentication failed. Check RUSTRAK_API_TOKEN.");
|
|
52
|
+
}
|
|
53
|
+
if (err instanceof RustrakError) {
|
|
54
|
+
return mcpError(`API error: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
return mcpError(`Unexpected error: ${String(err)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/tools/alerts.ts
|
|
60
|
+
function registerAlertTools(server2, client2) {
|
|
61
|
+
server2.registerTool(
|
|
62
|
+
"list_alert_channels",
|
|
63
|
+
{
|
|
64
|
+
description: "List all configured alert notification channels (Slack, email, webhook, etc.).",
|
|
65
|
+
inputSchema: {}
|
|
66
|
+
},
|
|
67
|
+
async () => {
|
|
68
|
+
try {
|
|
69
|
+
const result = await client2.alertChannels.list();
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
72
|
+
};
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return toMcpError(err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
server2.registerTool(
|
|
79
|
+
"test_alert_channel",
|
|
80
|
+
{
|
|
81
|
+
description: "Send a test notification to an alert channel to verify it is configured correctly.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
channel_id: z.number().int().describe("Alert channel ID to test")
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
async ({ channel_id }) => {
|
|
87
|
+
try {
|
|
88
|
+
const result = await client2.alertChannels.test(channel_id);
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
91
|
+
};
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return toMcpError(err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
server2.registerTool(
|
|
98
|
+
"list_alert_rules",
|
|
99
|
+
{
|
|
100
|
+
description: "List all alert rules configured for a project.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
project_id: z.number().int().describe("Project ID")
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
async ({ project_id }) => {
|
|
106
|
+
try {
|
|
107
|
+
const result = await client2.alertRules.list(project_id);
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return toMcpError(err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/tools/events.ts
|
|
119
|
+
import { z as z2 } from "zod";
|
|
120
|
+
function registerEventTools(server2, client2) {
|
|
121
|
+
server2.registerTool(
|
|
122
|
+
"list_events",
|
|
123
|
+
{
|
|
124
|
+
description: "List raw events for a specific issue. Events are individual error occurrences within a grouped issue.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
project_id: z2.number().int().describe("Project ID"),
|
|
127
|
+
issue_id: z2.string().describe("Issue ID"),
|
|
128
|
+
cursor: z2.string().optional().describe("Pagination cursor"),
|
|
129
|
+
order: z2.enum(["asc", "desc"]).optional().describe("Sort order (default: desc)")
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
async ({ project_id, issue_id, cursor, order }) => {
|
|
133
|
+
try {
|
|
134
|
+
const result = await client2.events.list(project_id, issue_id, {
|
|
135
|
+
cursor,
|
|
136
|
+
order
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
140
|
+
};
|
|
141
|
+
} catch (err) {
|
|
142
|
+
return toMcpError(err);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
server2.registerTool(
|
|
147
|
+
"get_event",
|
|
148
|
+
{
|
|
149
|
+
description: "Get full detail for a single event, including the complete Sentry envelope data.",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
project_id: z2.number().int().describe("Project ID"),
|
|
152
|
+
issue_id: z2.string().describe("Issue ID"),
|
|
153
|
+
event_id: z2.string().describe("Event ID")
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
async ({ project_id, issue_id, event_id }) => {
|
|
157
|
+
try {
|
|
158
|
+
const result = await client2.events.get(project_id, issue_id, event_id);
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
161
|
+
};
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return toMcpError(err);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/tools/issues.ts
|
|
170
|
+
import { z as z3 } from "zod";
|
|
171
|
+
function registerIssueTools(server2, client2) {
|
|
172
|
+
server2.registerTool(
|
|
173
|
+
"list_issues",
|
|
174
|
+
{
|
|
175
|
+
description: "List issues for a Rustrak project. Returns paginated grouped error occurrences.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
project_id: z3.number().int().describe("Project ID"),
|
|
178
|
+
page: z3.number().int().min(1).optional(),
|
|
179
|
+
per_page: z3.number().int().min(1).max(100).optional(),
|
|
180
|
+
filter: z3.enum(["open", "resolved", "muted", "all"]).optional().describe("Filter issues by state (default: open)")
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
async ({ project_id, page, per_page, filter }) => {
|
|
184
|
+
try {
|
|
185
|
+
const result = await client2.issues.list(project_id, {
|
|
186
|
+
page,
|
|
187
|
+
per_page,
|
|
188
|
+
filter
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
192
|
+
};
|
|
193
|
+
} catch (err) {
|
|
194
|
+
return toMcpError(err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
server2.registerTool(
|
|
199
|
+
"get_issue",
|
|
200
|
+
{
|
|
201
|
+
description: "Get a single issue by ID.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
project_id: z3.number().int().describe("Project ID"),
|
|
204
|
+
issue_id: z3.string().describe("Issue ID")
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
async ({ project_id, issue_id }) => {
|
|
208
|
+
try {
|
|
209
|
+
const result = await client2.issues.get(project_id, issue_id);
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
212
|
+
};
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return toMcpError(err);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
server2.registerTool(
|
|
219
|
+
"resolve_issue",
|
|
220
|
+
{
|
|
221
|
+
description: "Mark an issue as resolved.",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
project_id: z3.number().int().describe("Project ID"),
|
|
224
|
+
issue_id: z3.string().describe("Issue ID")
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
async ({ project_id, issue_id }) => {
|
|
228
|
+
try {
|
|
229
|
+
const result = await client2.issues.updateState(project_id, issue_id, {
|
|
230
|
+
is_resolved: true
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
234
|
+
};
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return toMcpError(err);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
server2.registerTool(
|
|
241
|
+
"unresolve_issue",
|
|
242
|
+
{
|
|
243
|
+
description: "Mark a resolved issue as unresolved (re-open it).",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
project_id: z3.number().int().describe("Project ID"),
|
|
246
|
+
issue_id: z3.string().describe("Issue ID")
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
async ({ project_id, issue_id }) => {
|
|
250
|
+
try {
|
|
251
|
+
const result = await client2.issues.updateState(project_id, issue_id, {
|
|
252
|
+
is_resolved: false
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
256
|
+
};
|
|
257
|
+
} catch (err) {
|
|
258
|
+
return toMcpError(err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
server2.registerTool(
|
|
263
|
+
"mute_issue",
|
|
264
|
+
{
|
|
265
|
+
description: "Mute an issue so it no longer triggers alerts.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
project_id: z3.number().int().describe("Project ID"),
|
|
268
|
+
issue_id: z3.string().describe("Issue ID")
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
async ({ project_id, issue_id }) => {
|
|
272
|
+
try {
|
|
273
|
+
const result = await client2.issues.updateState(project_id, issue_id, {
|
|
274
|
+
is_muted: true
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
278
|
+
};
|
|
279
|
+
} catch (err) {
|
|
280
|
+
return toMcpError(err);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
server2.registerTool(
|
|
285
|
+
"delete_issue",
|
|
286
|
+
{
|
|
287
|
+
description: "Permanently delete an issue and all its events.",
|
|
288
|
+
inputSchema: {
|
|
289
|
+
project_id: z3.number().int().describe("Project ID"),
|
|
290
|
+
issue_id: z3.string().describe("Issue ID")
|
|
291
|
+
},
|
|
292
|
+
annotations: { destructiveHint: true }
|
|
293
|
+
},
|
|
294
|
+
async ({ project_id, issue_id }) => {
|
|
295
|
+
try {
|
|
296
|
+
await client2.issues.delete(project_id, issue_id);
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{ type: "text", text: `Issue ${issue_id} deleted successfully.` }
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
} catch (err) {
|
|
303
|
+
return toMcpError(err);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/tools/projects.ts
|
|
310
|
+
import { z as z4 } from "zod";
|
|
311
|
+
function registerProjectTools(server2, client2) {
|
|
312
|
+
server2.registerTool(
|
|
313
|
+
"list_projects",
|
|
314
|
+
{
|
|
315
|
+
description: "List all Rustrak projects you have access to.",
|
|
316
|
+
inputSchema: {
|
|
317
|
+
page: z4.number().int().min(1).optional().describe("Page number"),
|
|
318
|
+
per_page: z4.number().int().min(1).max(100).optional().describe("Items per page")
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
async ({ page, per_page }) => {
|
|
322
|
+
try {
|
|
323
|
+
const result = await client2.projects.list({ page, per_page });
|
|
324
|
+
return {
|
|
325
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
326
|
+
};
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return toMcpError(err);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
server2.registerTool(
|
|
333
|
+
"get_project",
|
|
334
|
+
{
|
|
335
|
+
description: "Get details for a single Rustrak project.",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
project_id: z4.number().int().describe("Project ID")
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
async ({ project_id }) => {
|
|
341
|
+
try {
|
|
342
|
+
const result = await client2.projects.get(project_id);
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
345
|
+
};
|
|
346
|
+
} catch (err) {
|
|
347
|
+
return toMcpError(err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
server2.registerTool(
|
|
352
|
+
"create_project",
|
|
353
|
+
{
|
|
354
|
+
description: "Create a new Rustrak project.",
|
|
355
|
+
inputSchema: {
|
|
356
|
+
name: z4.string().min(1).describe("Project name"),
|
|
357
|
+
slug: z4.string().optional().describe("URL-safe slug (auto-generated if omitted)")
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
async ({ name, slug }) => {
|
|
361
|
+
try {
|
|
362
|
+
const result = await client2.projects.create({ name, slug });
|
|
363
|
+
return {
|
|
364
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
365
|
+
};
|
|
366
|
+
} catch (err) {
|
|
367
|
+
return toMcpError(err);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/tools/tokens.ts
|
|
374
|
+
import { z as z5 } from "zod";
|
|
375
|
+
function registerTokenTools(server2, client2) {
|
|
376
|
+
server2.registerTool(
|
|
377
|
+
"list_tokens",
|
|
378
|
+
{
|
|
379
|
+
description: "List all API tokens. Token values are masked \u2014 only the prefix is shown.",
|
|
380
|
+
inputSchema: {}
|
|
381
|
+
},
|
|
382
|
+
async () => {
|
|
383
|
+
try {
|
|
384
|
+
const result = await client2.tokens.list();
|
|
385
|
+
return {
|
|
386
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
387
|
+
};
|
|
388
|
+
} catch (err) {
|
|
389
|
+
return toMcpError(err);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
server2.registerTool(
|
|
394
|
+
"create_token",
|
|
395
|
+
{
|
|
396
|
+
description: "Create a new API token. The full token value is returned ONCE \u2014 save it immediately.",
|
|
397
|
+
inputSchema: {
|
|
398
|
+
description: z5.string().min(1).describe("Human-readable label for this token")
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
async ({ description }) => {
|
|
402
|
+
try {
|
|
403
|
+
const result = await client2.tokens.create({ description });
|
|
404
|
+
return {
|
|
405
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
406
|
+
};
|
|
407
|
+
} catch (err) {
|
|
408
|
+
return toMcpError(err);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
server2.registerTool(
|
|
413
|
+
"revoke_token",
|
|
414
|
+
{
|
|
415
|
+
description: "Permanently revoke an API token. This action cannot be undone.",
|
|
416
|
+
inputSchema: {
|
|
417
|
+
token_id: z5.number().int().describe("Token ID to revoke")
|
|
418
|
+
},
|
|
419
|
+
annotations: { destructiveHint: true }
|
|
420
|
+
},
|
|
421
|
+
async ({ token_id }) => {
|
|
422
|
+
try {
|
|
423
|
+
await client2.tokens.delete(token_id);
|
|
424
|
+
return {
|
|
425
|
+
content: [
|
|
426
|
+
{ type: "text", text: `Token ${token_id} revoked successfully.` }
|
|
427
|
+
]
|
|
428
|
+
};
|
|
429
|
+
} catch (err) {
|
|
430
|
+
return toMcpError(err);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/server.ts
|
|
437
|
+
function createServer(client2) {
|
|
438
|
+
const server2 = new McpServer({
|
|
439
|
+
name: "rustrak-mcp",
|
|
440
|
+
version: "0.1.0"
|
|
441
|
+
});
|
|
442
|
+
registerProjectTools(server2, client2);
|
|
443
|
+
registerIssueTools(server2, client2);
|
|
444
|
+
registerEventTools(server2, client2);
|
|
445
|
+
registerTokenTools(server2, client2);
|
|
446
|
+
registerAlertTools(server2, client2);
|
|
447
|
+
return server2;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/index.ts
|
|
451
|
+
var config = loadConfig();
|
|
452
|
+
var client = new RustrakClient({
|
|
453
|
+
baseUrl: config.RUSTRAK_API_URL,
|
|
454
|
+
token: config.RUSTRAK_API_TOKEN
|
|
455
|
+
});
|
|
456
|
+
var server = createServer(client);
|
|
457
|
+
var transport = new StdioServerTransport();
|
|
458
|
+
await server.connect(transport);
|
|
459
|
+
console.error("[rustrak-mcp] Server started");
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rustrak/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Rustrak error tracking — wraps @rustrak/client",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"rustrak-mcp": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"rustrak",
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"error-tracking",
|
|
29
|
+
"claude"
|
|
30
|
+
],
|
|
31
|
+
"license": "GPL-3.0",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
34
|
+
"zod": "4.4.3",
|
|
35
|
+
"@rustrak/client": "0.1.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "25.8.0",
|
|
39
|
+
"tsup": "8.5.1",
|
|
40
|
+
"typescript": "6.0.3",
|
|
41
|
+
"vitest": "4.1.6"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
48
|
+
}
|
|
49
|
+
}
|