#
tokens: 4159/50000 8/8 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
/node_modules
/build
.env.development.local
```

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

```markdown
# sentry-issue-mcp

[![中文文档](https://img.shields.io/badge/中文文档-查看-red)](README_cn.md)
[![English](https://img.shields.io/badge/English-Read-blue)](README.md)
[![smithery badge](https://smithery.ai/badge/@Leee62/sentry-issues-mcp)](https://smithery.ai/server/@Leee62/sentry-issues-mcp)

## Description

> ⚠️ 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.

This is a mcp for sentry issue.\
It supports 2 tools to get a issue or list of issues.\
U can let LLM analysis the Res, or u want to do.

## Feature

- EZ size
- EZ understand
- EZ tiny

## Tools

- get_single_event
  - get a event detail infos, tiny mode return stack info, huge mode return all info
  - inputs:
    - url_or_id: sentry event url or sentry event id
    - organization_id_or_slug: sentry organization id or slug, it can be undefined
    - project_id_or_slug: sentry project id or slug, it can be undefined
    - mode: tiny or huge, it can be undefined
- get_project_events
  - get list of events, tiny mode return id and title, huge mode return all info
  - inputs:
    - project_id_or_slug: sentry project id or slug
    - organization_id_or_slug: sentry organization id or slug, it can be undefined
    - mode: tiny or huge, it can be undefined

## QuickStart

this is MCP Server Config

```json
  "mcpServers": {
    "sentry-issue-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "sentry-issues-mcp@latest"
      ],
      "env": {
        "SENTRY_HOST": "<your_sentry_host>",
        "SENTRY_ORG": "<your_sentry_org>",
        "SENTRY_PROJ": "<your_sentry_proj>",
        "SENTRY_USER_TOKEN": "<your_sentry_user_token>"
      }
    }
  }
```

## Case

- Ask LLM to analysis one issue by url or id
  1. input "analysis the issue, and give me the reason of it, and tell me how to fix it, {sentry_issue_url}"
  2. if ur LLM is SMART🧠, it will call tools
  3. u will get result
- Ask LLM to find Today most dangerous issue (PS: default val of sentry time period is "24h")
  1. input "find today most dangerous issue, and give me the reason of it, and tell me how to fix it"
  2. if ur LLM is SMART🧠, it will call tools
  3. u will get result

## License

MIT

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

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

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

```

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

```yaml
# Smithery configuration file: https://smithery.ai/docs/build/project-config

startCommand:
  type: stdio
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (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 } })
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - sentryHost
      - sentryOrg
      - sentryProj
      - sentryUserToken
    properties:
      sentryHost:
        type: string
        description: Sentry host URL, e.g., https://sentry.io
      sentryOrg:
        type: string
        description: Sentry organization slug or ID
      sentryProj:
        type: string
        description: Sentry project slug or ID
      sentryUserToken:
        type: string
        description: Sentry user auth token
  exampleConfig:
    sentryHost: https://sentry.io
    sentryOrg: my-org
    sentryProj: my-project
    sentryUserToken: abcd1234efgh5678

```

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

```json
{
  "name": "sentry-issues-mcp",
  "version": "1.0.5",
  "description": "a mcp server to get sentry issue by id or url for LLM",
  "main": "build/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc && chmod 755 build/index.js",
    "start": "node build/index.js",
    "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"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Leee62/sentry-issues-mcp.git"
  },
  "bugs": {
    "url": "https://github.com/Leee62/sentry-issues-mcp/issues"
  },
  "homepage": "https://github.com/Leee62/sentry-issues-mcp#readme",
  "files": [
    "build"
  ],
  "bin": {
    "sentry-issues-mcp": "./build/index.js"
  },
  "keywords": [
    "mcp",
    "sentry",
    "llm",
    "modelcontextprotocol"
  ],
  "author": "lee62",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.9.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/node": "^22.14.1",
    "typescript": "^5.8.3"
  }
}

```

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

```typescript
/** get sentry event by <ID> */
export async function fetchSentryEvent<T>(
  eventId: string,
  organization_id_or_slug: string,
  project_id_or_slug: string
): Promise<T | null> {
  try {
    const issueRes = await fetch(
      `https://${process.env.SENTRY_HOST}/api/0/projects/${organization_id_or_slug}/${project_id_or_slug}/events/${eventId}/`,
      {
        method: "GET",
        headers: {
          Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
        },
      }
    );

    if (!issueRes.ok) {
      throw new Error(`HTTP error! status: ${issueRes.status}`);
    }
    return (await issueRes.json()) as T;
  } catch (error) {
    console.error("Error making request:", error);
    return null;
  }
}

/** get sentry events */
export async function fetchSentryEvents<T>(
  organization_id_or_slug: string,
  project_id_or_slug: string
): Promise<T | null> {
  try {
    const issueRes = await fetch(
      `https://${process.env.SENTRY_HOST}/api/0/projects/${organization_id_or_slug}/${project_id_or_slug}/events/`,
      {
        method: "GET",
        headers: {
          Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
        },
      }
    );

    if (!issueRes.ok) {
      throw new Error(`HTTP error! status: ${issueRes.status}`);
    }
    return (await issueRes.json()) as T;
  } catch (error) {
    console.error("Error making request:", error);
    return null;
  }
}

// /** get sentry issue by <ID> */
// export async function fetchSentryIssue<T>(
//   issueId: string,
//   organization_id_or_slug: string
// ): Promise<T | null> {
//   try {
//     const issueRes = await fetch(
//       `https://${process.env.SENTRY_HOST}/api/0/organizations/${organization_id_or_slug}/issues/${issueId}/`,
//       {
//         method: "GET",
//         headers: {
//           Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
//         },
//       }
//     );

//     if (!issueRes.ok) {
//       throw new Error(`HTTP error! status: ${issueRes.status}`);
//     }
//     return (await issueRes.json()) as T;
//   } catch (error) {
//     console.error("Error making request:", error);
//     return null;
//   }
// }

// /** get sentry issue */
// export async function fetchSentryIssues<T>(
//   project_id_or_slug: string,
//   organization_id_or_slug: string
// ): Promise<T | null> {
//   const path = project_id_or_slug
//     ? `projects/${organization_id_or_slug}/${project_id_or_slug}/issues/`
//     : `organizations/${organization_id_or_slug}/issues/`;

//   try {
//     const issueRes = await fetch(
//       `https://${process.env.SENTRY_HOST}/api/0/${path}`,
//       {
//         method: "GET",
//         headers: {
//           Authorization: `Bearer ${process.env.SENTRY_USER_TOKEN}`,
//         },
//       }
//     );

//     if (!issueRes.ok) {
//       throw new Error(`HTTP error! status: ${issueRes.status}`);
//     }
//     return (await issueRes.json()) as T;
//   } catch (error) {
//     console.error("Error making request:", error);
//     return null;
//   }
// }

```

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

```typescript
#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import {
  fetchSentryEvent,
  fetchSentryEvents,
  // fetchSentryIssue,
  // fetchSentryIssues,
} from "./fetcher";

/** Create server instance
 *
 * Watch out LLM TOKEN usage, too much information returned
 */
const server = new McpServer({
  name: "sentry-issue-mcp",
  version: "1.0.0",
});

/** Register tool for getting single event by id */
server.tool(
  "get_single_event",
  "get issue event by inputting sentry issue event url or sentry issue event id",
  {
    url_or_id: z
      .string()
      .describe("sentry issue event url or sentry issue event id"),
    organization_id_or_slug: z
      .string()
      .optional()
      .default(process.env.SENTRY_ORG as string)
      .describe("sentry organization id or slug, it can be undefined"),
    project_id_or_slug: z
      .string()
      .optional()
      .default(process.env.SENTRY_PROJ as string)
      .describe("sentry project name or slug, it can be undefined"),
    mode: z
      .enum(["tiny", "huge"])
      .optional()
      .default("tiny")
      .describe(
        "mode for output, it can be undefined, it used to control LLM token usage"
      ),
  },
  async ({ url_or_id, organization_id_or_slug, project_id_or_slug, mode }) => {
    let EVENT_ID = "";

    if (url_or_id.includes("http") || url_or_id.includes("https")) {
      EVENT_ID = url_or_id.match(/events\/([a-f0-9]+)/)?.[1] || "";
    } else {
      EVENT_ID = url_or_id;
    }

    if (!EVENT_ID) {
      return {
        content: [
          {
            type: "text",
            text: "Invalid Event ID",
          },
        ],
      };
    }

    const eventEventData = await fetchSentryEvent<{
      entries: any[];
    }>(EVENT_ID, organization_id_or_slug, project_id_or_slug);

    if (!eventEventData) {
      return {
        content: [
          {
            type: "text",
            text: "Failed to Get Event",
          },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(
            mode === "tiny" ? eventEventData.entries : eventEventData
          ),
        },
      ],
    };
  }
);

/** Register tool for getting events */
server.tool(
  "get_project_events",
  "get issue events by inputting sentry organization id or slug and sentry project name or slug",
  {
    organization_id_or_slug: z
      .string()
      .optional()
      .default(process.env.SENTRY_ORG as string)
      .describe("sentry organization id or slug, it can be undefined"),
    project_id_or_slug: z
      .string()
      .optional()
      .default(process.env.SENTRY_PROJ as string)
      .describe("sentry project name or slug, it can be undefined"),
    mode: z
      .enum(["tiny", "huge"])
      .optional()
      .default("tiny")
      .describe(
        "mode for output, it can be undefined, it used to control LLM token usage"
      ),
  },
  async ({ organization_id_or_slug, project_id_or_slug, mode }) => {
    const eventsData = await fetchSentryEvents<
      { id: string; title: string; dateCreated: string }[]
    >(organization_id_or_slug, project_id_or_slug);

    if (!eventsData) {
      return {
        content: [
          {
            type: "text",
            text: "Failed to Get Issue",
          },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(
            mode === "tiny"
              ? eventsData.map(({ id, title, dateCreated }) => ({
                  id,
                  title,
                  dateCreated,
                }))
              : eventsData
          ),
        },
      ],
    };
  }
);

// /** Register sentry tool for getting single issue by id */
// server.tool(
//   "get_single_issue",
//   "get issue by inputting sentry issue url or sentry issue id",
//   {
//     url_or_id: z.string().describe("sentry issue url or sentry issue id"),
//     organization_id_or_slug: z
//       .string()
//       .optional()
//       .describe("sentry organization id or slug, it can be undefined"),
//   },
//   async ({
//     url_or_id,
//     organization_id_or_slug = process.env.SENTRY_ORG as string,
//   }) => {
//     let ISSUE_ID = "";

//     if (url_or_id.includes("http") || url_or_id.includes("https")) {
//       ISSUE_ID = url_or_id.match(/issues\/(\d+)/)?.[1] || "";
//     } else {
//       ISSUE_ID = url_or_id;
//     }

//     if (!ISSUE_ID || Number.isNaN(Number(ISSUE_ID))) {
//       return {
//         content: [
//           {
//             type: "text",
//             text: "Invalid Issue ID",
//           },
//         ],
//       };
//     }

//     const issueData = await fetchSentryIssue(ISSUE_ID, organization_id_or_slug);

//     if (!issueData) {
//       return {
//         content: [
//           {
//             type: "text",
//             text: "Failed to Get Issue",
//           },
//         ],
//       };
//     }

//     return {
//       content: [
//         {
//           type: "text",
//           text: JSON.stringify(issueData),
//         },
//       ],
//     };
//   }
// );

// /** Register Sentry get issues (Watch out LLM TOKEN usage) */
// server.tool(
//   "get_project_issues",
//   "get issues by inputting",
//   {
//     organization_id_or_slug: z
//       .string()
//       .optional()
//       .describe("sentry organization id or slug, it can be undefined"),
//     project_id_or_slug: z
//       .string()
//       .optional()
//       .describe("sentry project name or slug, it can be undefined"),
//   },
//   async ({
//     project_id_or_slug = process.env.SENTRY_PROJ as string,
//     organization_id_or_slug = process.env.SENTRY_ORG as string,
//   }) => {
//     const issuesData = await fetchSentryIssues(
//       project_id_or_slug,
//       organization_id_or_slug
//     );

//     if (!issuesData) {
//       return {
//         content: [
//           {
//             type: "text",
//             text: "Failed to Get Issue",
//           },
//         ],
//       };
//     }

//     return {
//       content: [
//         {
//           type: "text",
//           text: JSON.stringify(issuesData),
//         },
//       ],
//     };
//   }
// );

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

```