#
tokens: 5599/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README_cn.md
├── README.md
├── smithery.yaml
├── src
│   ├── fetcher.ts
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | /node_modules
2 | /build
3 | .env.development.local
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # sentry-issue-mcp
 2 | 
 3 | [![中文文档](https://img.shields.io/badge/中文文档-查看-red)](README_cn.md)
 4 | [![English](https://img.shields.io/badge/English-Read-blue)](README.md)
 5 | [![smithery badge](https://smithery.ai/badge/@Leee62/sentry-issues-mcp)](https://smithery.ai/server/@Leee62/sentry-issues-mcp)
 6 | 
 7 | ## Description
 8 | 
 9 | > ⚠️ Getting issues is deprecated as of version 1.0.5. The response structure is so similar to the event API that it's considered redundant.
10 | 
11 | This is a mcp for sentry issue.\
12 | It supports 2 tools to get a issue or list of issues.\
13 | U can let LLM analysis the Res, or u want to do.
14 | 
15 | ## Feature
16 | 
17 | - EZ size
18 | - EZ understand
19 | - EZ tiny
20 | 
21 | ## Tools
22 | 
23 | - get_single_event
24 |   - get a event detail infos, tiny mode return stack info, huge mode return all info
25 |   - inputs:
26 |     - url_or_id: sentry event url or sentry event id
27 |     - organization_id_or_slug: sentry organization id or slug, it can be undefined
28 |     - project_id_or_slug: sentry project id or slug, it can be undefined
29 |     - mode: tiny or huge, it can be undefined
30 | - get_project_events
31 |   - get list of events, tiny mode return id and title, huge mode return all info
32 |   - inputs:
33 |     - project_id_or_slug: sentry project id or slug
34 |     - organization_id_or_slug: sentry organization id or slug, it can be undefined
35 |     - mode: tiny or huge, it can be undefined
36 | 
37 | ## QuickStart
38 | 
39 | this is MCP Server Config
40 | 
41 | ```json
42 |   "mcpServers": {
43 |     "sentry-issue-mcp": {
44 |       "type": "stdio",
45 |       "command": "npx",
46 |       "args": [
47 |         "-y",
48 |         "sentry-issues-mcp@latest"
49 |       ],
50 |       "env": {
51 |         "SENTRY_HOST": "<your_sentry_host>",
52 |         "SENTRY_ORG": "<your_sentry_org>",
53 |         "SENTRY_PROJ": "<your_sentry_proj>",
54 |         "SENTRY_USER_TOKEN": "<your_sentry_user_token>"
55 |       }
56 |     }
57 |   }
58 | ```
59 | 
60 | ## Case
61 | 
62 | - Ask LLM to analysis one issue by url or id
63 |   1. input "analysis the issue, and give me the reason of it, and tell me how to fix it, {sentry_issue_url}"
64 |   2. if ur LLM is SMART🧠, it will call tools
65 |   3. u will get result
66 | - Ask LLM to find Today most dangerous issue (PS: default val of sentry time period is "24h")
67 |   1. input "find today most dangerous issue, and give me the reason of it, and tell me how to fix it"
68 |   2. if ur LLM is SMART🧠, it will call tools
69 |   3. u will get result
70 | 
71 | ## License
72 | 
73 | MIT
74 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config
 2 | # syntax=docker/dockerfile:1
 3 | FROM node:lts-alpine
 4 | WORKDIR /app
 5 | # Install dependencies without running prepare scripts
 6 | COPY package.json package-lock.json ./
 7 | RUN npm ci --ignore-scripts
 8 | # Copy source code
 9 | COPY . .
10 | # Build the project
11 | RUN npm run build
12 | # Default command to start the MCP server
13 | CMD ["node", "build/index.js"]
14 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   commandFunction:
 6 |     # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
 7 |     |-
 8 |     (config) => ({ command: 'node', args: ['build/index.js'], env: { SENTRY_HOST: config.sentryHost, SENTRY_ORG: config.sentryOrg, SENTRY_PROJ: config.sentryProj, SENTRY_USER_TOKEN: config.sentryUserToken } })
 9 |   configSchema:
10 |     # JSON Schema defining the configuration options for the MCP.
11 |     type: object
12 |     required:
13 |       - sentryHost
14 |       - sentryOrg
15 |       - sentryProj
16 |       - sentryUserToken
17 |     properties:
18 |       sentryHost:
19 |         type: string
20 |         description: Sentry host URL, e.g., https://sentry.io
21 |       sentryOrg:
22 |         type: string
23 |         description: Sentry organization slug or ID
24 |       sentryProj:
25 |         type: string
26 |         description: Sentry project slug or ID
27 |       sentryUserToken:
28 |         type: string
29 |         description: Sentry user auth token
30 |   exampleConfig:
31 |     sentryHost: https://sentry.io
32 |     sentryOrg: my-org
33 |     sentryProj: my-project
34 |     sentryUserToken: abcd1234efgh5678
35 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "sentry-issues-mcp",
 3 |   "version": "1.0.5",
 4 |   "description": "a mcp server to get sentry issue by id or url for LLM",
 5 |   "main": "build/index.js",
 6 |   "scripts": {
 7 |     "test": "echo \"Error: no test specified\" && exit 1",
 8 |     "build": "tsc && chmod 755 build/index.js",
 9 |     "start": "node build/index.js",
10 |     "debug": "npm run build && npx -y @modelcontextprotocol/inspector -e SENTRY_PROJ=<your_sentry_proj> -e SENTRY_HOST=<your_sentry_host> -e SENTRY_ORG=<your_sentry_org> -e SENTRY_USER_TOKEN=<your_sentry_user_token> node ./build/index.js"
11 |   },
12 |   "repository": {
13 |     "type": "git",
14 |     "url": "git+https://github.com/Leee62/sentry-issues-mcp.git"
15 |   },
16 |   "bugs": {
17 |     "url": "https://github.com/Leee62/sentry-issues-mcp/issues"
18 |   },
19 |   "homepage": "https://github.com/Leee62/sentry-issues-mcp#readme",
20 |   "files": [
21 |     "build"
22 |   ],
23 |   "bin": {
24 |     "sentry-issues-mcp": "./build/index.js"
25 |   },
26 |   "keywords": [
27 |     "mcp",
28 |     "sentry",
29 |     "llm",
30 |     "modelcontextprotocol"
31 |   ],
32 |   "author": "lee62",
33 |   "license": "MIT",
34 |   "dependencies": {
35 |     "@modelcontextprotocol/sdk": "^1.9.0",
36 |     "zod": "^3.24.2"
37 |   },
38 |   "devDependencies": {
39 |     "@types/node": "^22.14.1",
40 |     "typescript": "^5.8.3"
41 |   }
42 | }
43 | 
```

--------------------------------------------------------------------------------
/src/fetcher.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /** get sentry event by <ID> */
  2 | export async function fetchSentryEvent<T>(
  3 |   eventId: string,
  4 |   organization_id_or_slug: string,
  5 |   project_id_or_slug: string
  6 | ): Promise<T | null> {
  7 |   try {
  8 |     const issueRes = await fetch(
  9 |       `https://${process.env.SENTRY_HOST}/api/0/projects/${organization_id_or_slug}/${project_id_or_slug}/events/${eventId}/`,
 10 |       {
 11 |         method: "GET",
 12 |         headers: {
 13 |           Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
 14 |         },
 15 |       }
 16 |     );
 17 | 
 18 |     if (!issueRes.ok) {
 19 |       throw new Error(`HTTP error! status: ${issueRes.status}`);
 20 |     }
 21 |     return (await issueRes.json()) as T;
 22 |   } catch (error) {
 23 |     console.error("Error making request:", error);
 24 |     return null;
 25 |   }
 26 | }
 27 | 
 28 | /** get sentry events */
 29 | export async function fetchSentryEvents<T>(
 30 |   organization_id_or_slug: string,
 31 |   project_id_or_slug: string
 32 | ): Promise<T | null> {
 33 |   try {
 34 |     const issueRes = await fetch(
 35 |       `https://${process.env.SENTRY_HOST}/api/0/projects/${organization_id_or_slug}/${project_id_or_slug}/events/`,
 36 |       {
 37 |         method: "GET",
 38 |         headers: {
 39 |           Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
 40 |         },
 41 |       }
 42 |     );
 43 | 
 44 |     if (!issueRes.ok) {
 45 |       throw new Error(`HTTP error! status: ${issueRes.status}`);
 46 |     }
 47 |     return (await issueRes.json()) as T;
 48 |   } catch (error) {
 49 |     console.error("Error making request:", error);
 50 |     return null;
 51 |   }
 52 | }
 53 | 
 54 | // /** get sentry issue by <ID> */
 55 | // export async function fetchSentryIssue<T>(
 56 | //   issueId: string,
 57 | //   organization_id_or_slug: string
 58 | // ): Promise<T | null> {
 59 | //   try {
 60 | //     const issueRes = await fetch(
 61 | //       `https://${process.env.SENTRY_HOST}/api/0/organizations/${organization_id_or_slug}/issues/${issueId}/`,
 62 | //       {
 63 | //         method: "GET",
 64 | //         headers: {
 65 | //           Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
 66 | //         },
 67 | //       }
 68 | //     );
 69 | 
 70 | //     if (!issueRes.ok) {
 71 | //       throw new Error(`HTTP error! status: ${issueRes.status}`);
 72 | //     }
 73 | //     return (await issueRes.json()) as T;
 74 | //   } catch (error) {
 75 | //     console.error("Error making request:", error);
 76 | //     return null;
 77 | //   }
 78 | // }
 79 | 
 80 | // /** get sentry issue */
 81 | // export async function fetchSentryIssues<T>(
 82 | //   project_id_or_slug: string,
 83 | //   organization_id_or_slug: string
 84 | // ): Promise<T | null> {
 85 | //   const path = project_id_or_slug
 86 | //     ? `projects/${organization_id_or_slug}/${project_id_or_slug}/issues/`
 87 | //     : `organizations/${organization_id_or_slug}/issues/`;
 88 | 
 89 | //   try {
 90 | //     const issueRes = await fetch(
 91 | //       `https://${process.env.SENTRY_HOST}/api/0/${path}`,
 92 | //       {
 93 | //         method: "GET",
 94 | //         headers: {
 95 | //           Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
 96 | //         },
 97 | //       }
 98 | //     );
 99 | 
100 | //     if (!issueRes.ok) {
101 | //       throw new Error(`HTTP error! status: ${issueRes.status}`);
102 | //     }
103 | //     return (await issueRes.json()) as T;
104 | //   } catch (error) {
105 | //     console.error("Error making request:", error);
106 | //     return null;
107 | //   }
108 | // }
109 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import { z } from "zod";
  6 | import {
  7 |   fetchSentryEvent,
  8 |   fetchSentryEvents,
  9 |   // fetchSentryIssue,
 10 |   // fetchSentryIssues,
 11 | } from "./fetcher";
 12 | 
 13 | /** Create server instance
 14 |  *
 15 |  * Watch out LLM TOKEN usage, too much information returned
 16 |  */
 17 | const server = new McpServer({
 18 |   name: "sentry-issue-mcp",
 19 |   version: "1.0.0",
 20 | });
 21 | 
 22 | /** Register tool for getting single event by id */
 23 | server.tool(
 24 |   "get_single_event",
 25 |   "get issue event by inputting sentry issue event url or sentry issue event id",
 26 |   {
 27 |     url_or_id: z
 28 |       .string()
 29 |       .describe("sentry issue event url or sentry issue event id"),
 30 |     organization_id_or_slug: z
 31 |       .string()
 32 |       .optional()
 33 |       .default(process.env.SENTRY_ORG as string)
 34 |       .describe("sentry organization id or slug, it can be undefined"),
 35 |     project_id_or_slug: z
 36 |       .string()
 37 |       .optional()
 38 |       .default(process.env.SENTRY_PROJ as string)
 39 |       .describe("sentry project name or slug, it can be undefined"),
 40 |     mode: z
 41 |       .enum(["tiny", "huge"])
 42 |       .optional()
 43 |       .default("tiny")
 44 |       .describe(
 45 |         "mode for output, it can be undefined, it used to control LLM token usage"
 46 |       ),
 47 |   },
 48 |   async ({ url_or_id, organization_id_or_slug, project_id_or_slug, mode }) => {
 49 |     let EVENT_ID = "";
 50 | 
 51 |     if (url_or_id.includes("http") || url_or_id.includes("https")) {
 52 |       EVENT_ID = url_or_id.match(/events\/([a-f0-9]+)/)?.[1] || "";
 53 |     } else {
 54 |       EVENT_ID = url_or_id;
 55 |     }
 56 | 
 57 |     if (!EVENT_ID) {
 58 |       return {
 59 |         content: [
 60 |           {
 61 |             type: "text",
 62 |             text: "Invalid Event ID",
 63 |           },
 64 |         ],
 65 |       };
 66 |     }
 67 | 
 68 |     const eventEventData = await fetchSentryEvent<{
 69 |       entries: any[];
 70 |     }>(EVENT_ID, organization_id_or_slug, project_id_or_slug);
 71 | 
 72 |     if (!eventEventData) {
 73 |       return {
 74 |         content: [
 75 |           {
 76 |             type: "text",
 77 |             text: "Failed to Get Event",
 78 |           },
 79 |         ],
 80 |       };
 81 |     }
 82 | 
 83 |     return {
 84 |       content: [
 85 |         {
 86 |           type: "text",
 87 |           text: JSON.stringify(
 88 |             mode === "tiny" ? eventEventData.entries : eventEventData
 89 |           ),
 90 |         },
 91 |       ],
 92 |     };
 93 |   }
 94 | );
 95 | 
 96 | /** Register tool for getting events */
 97 | server.tool(
 98 |   "get_project_events",
 99 |   "get issue events by inputting sentry organization id or slug and sentry project name or slug",
100 |   {
101 |     organization_id_or_slug: z
102 |       .string()
103 |       .optional()
104 |       .default(process.env.SENTRY_ORG as string)
105 |       .describe("sentry organization id or slug, it can be undefined"),
106 |     project_id_or_slug: z
107 |       .string()
108 |       .optional()
109 |       .default(process.env.SENTRY_PROJ as string)
110 |       .describe("sentry project name or slug, it can be undefined"),
111 |     mode: z
112 |       .enum(["tiny", "huge"])
113 |       .optional()
114 |       .default("tiny")
115 |       .describe(
116 |         "mode for output, it can be undefined, it used to control LLM token usage"
117 |       ),
118 |   },
119 |   async ({ organization_id_or_slug, project_id_or_slug, mode }) => {
120 |     const eventsData = await fetchSentryEvents<
121 |       { id: string; title: string; dateCreated: string }[]
122 |     >(organization_id_or_slug, project_id_or_slug);
123 | 
124 |     if (!eventsData) {
125 |       return {
126 |         content: [
127 |           {
128 |             type: "text",
129 |             text: "Failed to Get Issue",
130 |           },
131 |         ],
132 |       };
133 |     }
134 | 
135 |     return {
136 |       content: [
137 |         {
138 |           type: "text",
139 |           text: JSON.stringify(
140 |             mode === "tiny"
141 |               ? eventsData.map(({ id, title, dateCreated }) => ({
142 |                   id,
143 |                   title,
144 |                   dateCreated,
145 |                 }))
146 |               : eventsData
147 |           ),
148 |         },
149 |       ],
150 |     };
151 |   }
152 | );
153 | 
154 | // /** Register sentry tool for getting single issue by id */
155 | // server.tool(
156 | //   "get_single_issue",
157 | //   "get issue by inputting sentry issue url or sentry issue id",
158 | //   {
159 | //     url_or_id: z.string().describe("sentry issue url or sentry issue id"),
160 | //     organization_id_or_slug: z
161 | //       .string()
162 | //       .optional()
163 | //       .describe("sentry organization id or slug, it can be undefined"),
164 | //   },
165 | //   async ({
166 | //     url_or_id,
167 | //     organization_id_or_slug = process.env.SENTRY_ORG as string,
168 | //   }) => {
169 | //     let ISSUE_ID = "";
170 | 
171 | //     if (url_or_id.includes("http") || url_or_id.includes("https")) {
172 | //       ISSUE_ID = url_or_id.match(/issues\/(\d+)/)?.[1] || "";
173 | //     } else {
174 | //       ISSUE_ID = url_or_id;
175 | //     }
176 | 
177 | //     if (!ISSUE_ID || Number.isNaN(Number(ISSUE_ID))) {
178 | //       return {
179 | //         content: [
180 | //           {
181 | //             type: "text",
182 | //             text: "Invalid Issue ID",
183 | //           },
184 | //         ],
185 | //       };
186 | //     }
187 | 
188 | //     const issueData = await fetchSentryIssue(ISSUE_ID, organization_id_or_slug);
189 | 
190 | //     if (!issueData) {
191 | //       return {
192 | //         content: [
193 | //           {
194 | //             type: "text",
195 | //             text: "Failed to Get Issue",
196 | //           },
197 | //         ],
198 | //       };
199 | //     }
200 | 
201 | //     return {
202 | //       content: [
203 | //         {
204 | //           type: "text",
205 | //           text: JSON.stringify(issueData),
206 | //         },
207 | //       ],
208 | //     };
209 | //   }
210 | // );
211 | 
212 | // /** Register Sentry get issues (Watch out LLM TOKEN usage) */
213 | // server.tool(
214 | //   "get_project_issues",
215 | //   "get issues by inputting",
216 | //   {
217 | //     organization_id_or_slug: z
218 | //       .string()
219 | //       .optional()
220 | //       .describe("sentry organization id or slug, it can be undefined"),
221 | //     project_id_or_slug: z
222 | //       .string()
223 | //       .optional()
224 | //       .describe("sentry project name or slug, it can be undefined"),
225 | //   },
226 | //   async ({
227 | //     project_id_or_slug = process.env.SENTRY_PROJ as string,
228 | //     organization_id_or_slug = process.env.SENTRY_ORG as string,
229 | //   }) => {
230 | //     const issuesData = await fetchSentryIssues(
231 | //       project_id_or_slug,
232 | //       organization_id_or_slug
233 | //     );
234 | 
235 | //     if (!issuesData) {
236 | //       return {
237 | //         content: [
238 | //           {
239 | //             type: "text",
240 | //             text: "Failed to Get Issue",
241 | //           },
242 | //         ],
243 | //       };
244 | //     }
245 | 
246 | //     return {
247 | //       content: [
248 | //         {
249 | //           type: "text",
250 | //           text: JSON.stringify(issuesData),
251 | //         },
252 | //       ],
253 | //     };
254 | //   }
255 | // );
256 | 
257 | async function main() {
258 |   const transport = new StdioServerTransport();
259 |   await server.connect(transport);
260 | }
261 | 
262 | main().catch((error) => {
263 |   console.error("Fatal error in main():", error);
264 |   process.exit(1);
265 | });
266 | 
```