Sentry MCP Server
by codyde
#!/usr/bin/env ts-node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fetch from "node-fetch";
import {
SentryProject,
ShortIdResolutionResponse,
EventDetailsResponse,
SentryProjectCreationResponse,
SentryClientKey,
SentryErrorEvent,
SentryProjectIssue,
SentryIssueDetailsResponse,
SentryReplay,
SentrySetupResponse,
} from "./types";
const SENTRY_AUTH = process.env.SENTRY_AUTH;
if (!SENTRY_AUTH) {
console.error("Error: SENTRY_AUTH environment variable is required");
process.exit(1);
}
const server = new McpServer({
name: "Sentry",
version: "1.0.0",
});
server.tool(
"list_projects",
"List accessible Sentry projects. View project slugs, IDs, status, settings, features, and organization details.",
{
organization_slug: z
.string()
.describe("The slug of the organization to list projects from"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
organization_slug,
view,
format,
}: {
organization_slug: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
const apiUrl: string = `https://sentry.io/api/0/organizations/${organization_slug}/projects/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to fetch projects: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const projects: SentryProject[] = await response.json();
console.error(
"DEBUG: Fetched projects:",
JSON.stringify(projects, null, 2)
);
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
if (view === "detailed") {
// Create a detailed markdown table
output = "# Sentry Projects\n\n";
output +=
"| ID | Name | Slug | Platform | Teams | Environments | Features |\n";
output +=
"|----|------|------|----------|-------|--------------|----------|\n";
for (const project of projects) {
const teams: string = project.teams
.map((team) => team.name)
.join(", ");
const environments: string =
project.environments?.join(", ") || "None";
const features: string = project.features?.join(", ") || "None";
output += `| ${project.id} | ${project.name} | ${project.slug} | ${
project.platform || "N/A"
} | ${teams} | ${environments} | ${features} |\n`;
}
// Add summary information
output += `\n## Summary\n\n`;
output += `Total Projects: ${projects.length}\n`;
} else {
// Create a summary markdown list
output = "# Sentry Projects\n\n";
for (const project of projects) {
output += `- **${project.name}** (${project.slug}): ID ${project.id}\n`;
}
output += `\nTotal Projects: ${projects.length}`;
}
} else {
// Plain text format
if (view === "detailed") {
output = "Sentry Projects:\n\n";
for (const project of projects) {
output += `ID: ${project.id}\n`;
output += `Name: ${project.name}\n`;
output += `Slug: ${project.slug}\n`;
output += `Platform: ${project.platform || "N/A"}\n`;
output += `Teams: ${project.teams
.map((team) => team.name)
.join(", ")}\n`;
output += `Environments: ${
project.environments?.join(", ") || "None"
}\n`;
output += `Features: ${project.features?.join(", ") || "None"}\n\n`;
}
output += `Total Projects: ${projects.length}`;
} else {
output = "Sentry Projects:\n\n";
for (const project of projects) {
output += `${project.name} (${project.slug}): ID ${project.id}\n`;
}
output += `\nTotal Projects: ${projects.length}`;
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error fetching projects: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"resolve_short_id",
"Retrieve details about an issue using its short ID. Maps short IDs to issue details, project context, and status.",
{
organization_slug: z
.string()
.describe("The slug of the organization the issue belongs to"),
short_id: z
.string()
.describe("The short ID of the issue to resolve (e.g., PROJECT-123)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
organization_slug,
short_id,
format,
}: {
organization_slug: string;
short_id: string;
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Resolving short ID:", short_id);
console.error("DEBUG: For organization:", organization_slug);
console.error("DEBUG: Format:", format);
// Construct the URL for the Sentry API
const apiUrl: string = `https://sentry.io/api/0/organizations/${organization_slug}/shortids/${short_id}/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to resolve short ID: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const data: ShortIdResolutionResponse = await response.json();
console.error(
"DEBUG: Resolved short ID data:",
JSON.stringify(data, null, 2)
);
// Format the output based on the format
let output: string = "";
if (format === "markdown") {
output = `# Issue Details: ${data.shortId}\n\n`;
// Issue information section
output += `## Issue Information\n\n`;
output += `- **Title**: ${data.group.title}\n`;
output += `- **Status**: ${data.group.status}\n`;
output += `- **Level**: ${data.group.level}\n`;
output += `- **First Seen**: ${data.group.firstSeen}\n`;
output += `- **Last Seen**: ${data.group.lastSeen}\n`;
output += `- **Event Count**: ${data.group.count}\n`;
output += `- **User Count**: ${data.group.userCount}\n`;
output += `- **Culprit**: ${data.group.culprit}\n`;
// Project information section
output += `\n## Project Information\n\n`;
output += `- **Project**: ${data.group.project.name} (${data.group.project.slug})\n`;
output += `- **Project ID**: ${data.group.project.id}\n`;
output += `- **Organization**: ${data.organizationSlug}\n`;
// Links section
output += `\n## Links\n\n`;
output += `- **Permalink**: [${data.group.permalink}](${data.group.permalink})\n`;
} else {
// Plain text format
output = `Issue Details: ${data.shortId}\n\n`;
// Issue information section
output += `Issue Information:\n\n`;
output += `Title: ${data.group.title}\n`;
output += `Status: ${data.group.status}\n`;
output += `Level: ${data.group.level}\n`;
output += `First Seen: ${data.group.firstSeen}\n`;
output += `Last Seen: ${data.group.lastSeen}\n`;
output += `Event Count: ${data.group.count}\n`;
output += `User Count: ${data.group.userCount}\n`;
output += `Culprit: ${data.group.culprit}\n`;
// Project information section
output += `\nProject Information:\n\n`;
output += `Project: ${data.group.project.name} (${data.group.project.slug})\n`;
output += `Project ID: ${data.group.project.id}\n`;
output += `Organization: ${data.organizationSlug}\n`;
// Links section
output += `\nLinks:\n\n`;
output += `Permalink: ${data.group.permalink}\n`;
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error resolving short ID: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"get_sentry_event",
"Retrieve a specific Sentry event from an issue. Requires issue ID/URL and event ID.",
{
issue_id_or_url: z
.string()
.describe("Either a full Sentry issue URL or just the numeric issue ID"),
event_id: z.string().describe("The specific event ID to retrieve"),
organization_slug: z
.string()
.describe("The slug of the organization the issue belongs to"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
issue_id_or_url,
event_id,
view,
organization_slug,
format,
}: {
issue_id_or_url: string;
event_id: string;
organization_slug: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Retrieving event:", event_id);
console.error("DEBUG: From issue:", issue_id_or_url);
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Extract organization slug from the issue URL if provided
let organizationSlug = "";
if (issue_id_or_url.includes("sentry.io")) {
// Parse the URL to extract organization slug
const urlParts = issue_id_or_url.split("/");
const orgIndex = urlParts.indexOf("sentry.io") + 1;
if (orgIndex < urlParts.length) {
organizationSlug = urlParts[orgIndex];
}
} else {
// If not a URL, assume it's just the organization slug
// We need to determine the organization from environment variables or other means
organizationSlug = organization_slug;
}
console.error("DEBUG: Organization slug:", organizationSlug);
console.error("DEBUG: Issue ID:", issue_id_or_url);
// Construct the URL for the Sentry API
const apiUrl: string = `https://sentry.io/api/0/organizations/${organizationSlug}/eventids/${event_id}/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to retrieve event: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const data: EventDetailsResponse = await response.json();
console.error("DEBUG: Event data:", JSON.stringify(data, null, 2));
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
output = `# Event Details: ${data.event.eventID}\n\n`;
if (view === "detailed") {
// Event information section
output += `## Event Information\n\n`;
output += `- **Title**: ${
data.event.title || data.event.metadata.title
}\n`;
output += `- **Platform**: ${data.event.platform}\n`;
output += `- **Date Created**: ${data.event.dateCreated}\n`;
output += `- **Date Received**: ${data.event.dateReceived}\n`;
output += `- **Size**: ${data.event.size} bytes\n`;
output += `- **Type**: ${data.event.type}\n`;
// Tags section
if (data.event.tags && data.event.tags.length > 0) {
output += `\n## Tags\n\n`;
for (const tag of data.event.tags) {
output += `- **${tag.key}**: ${tag.value}\n`;
}
}
// User information section
if (data.event.user) {
output += `\n## User Information\n\n`;
output += `- **ID**: ${data.event.user.id}\n`;
if (data.event.user.name)
output += `- **Name**: ${data.event.user.name}\n`;
if (data.event.user.email)
output += `- **Email**: ${data.event.user.email}\n`;
if (data.event.user.username)
output += `- **Username**: ${data.event.user.username}\n`;
if (data.event.user.ip_address)
output += `- **IP Address**: ${data.event.user.ip_address}\n`;
}
// Request information section
const requestEntry = data.event.entries.find(
(entry) => entry.type === "request"
);
if (requestEntry) {
output += `\n## Request Information\n\n`;
if (requestEntry.data.url)
output += `- **URL**: ${requestEntry.data.url}\n`;
if (requestEntry.data.method)
output += `- **Method**: ${requestEntry.data.method}\n`;
if (
requestEntry.data.headers &&
requestEntry.data.headers.length > 0
) {
output += `\n### Headers\n\n`;
for (const [key, value] of requestEntry.data.headers) {
output += `- **${key}**: ${value}\n`;
}
}
}
// Context information
if (
data.event.context &&
Object.keys(data.event.context).length > 0
) {
output += `\n## Context\n\n`;
output += "```json\n";
output += JSON.stringify(data.event.context, null, 2);
output += "\n```\n";
}
// Project information section
output += `\n## Project Information\n\n`;
output += `- **Organization**: ${data.organizationSlug}\n`;
output += `- **Project**: ${data.projectSlug}\n`;
output += `- **Group ID**: ${data.groupId}\n`;
} else {
// Summary view
output += `## Summary\n\n`;
output += `- **Title**: ${
data.event.title || data.event.metadata.title
}\n`;
output += `- **Platform**: ${data.event.platform}\n`;
output += `- **Date Created**: ${data.event.dateCreated}\n`;
output += `- **Organization**: ${data.organizationSlug}\n`;
output += `- **Project**: ${data.projectSlug}\n`;
// Add a few important tags if available
const levelTag = data.event.tags.find((tag) => tag.key === "level");
const releaseTag = data.event.tags.find(
(tag) => tag.key === "release"
);
if (levelTag) output += `- **Level**: ${levelTag.value}\n`;
if (releaseTag) output += `- **Release**: ${releaseTag.value}\n`;
if (data.event.user) {
output += `- **User**: ${
data.event.user.email ||
data.event.user.username ||
data.event.user.id
}\n`;
}
}
} else {
// Plain text format
output = `Event Details: ${data.event.eventID}\n\n`;
if (view === "detailed") {
// Event information section
output += `Event Information:\n\n`;
output += `Title: ${data.event.title || data.event.metadata.title}\n`;
output += `Platform: ${data.event.platform}\n`;
output += `Date Created: ${data.event.dateCreated}\n`;
output += `Date Received: ${data.event.dateReceived}\n`;
output += `Size: ${data.event.size} bytes\n`;
output += `Type: ${data.event.type}\n`;
// Tags section
if (data.event.tags && data.event.tags.length > 0) {
output += `\nTags:\n\n`;
for (const tag of data.event.tags) {
output += `${tag.key}: ${tag.value}\n`;
}
}
// User information section
if (data.event.user) {
output += `\nUser Information:\n\n`;
output += `ID: ${data.event.user.id}\n`;
if (data.event.user.name)
output += `Name: ${data.event.user.name}\n`;
if (data.event.user.email)
output += `Email: ${data.event.user.email}\n`;
if (data.event.user.username)
output += `Username: ${data.event.user.username}\n`;
if (data.event.user.ip_address)
output += `IP Address: ${data.event.user.ip_address}\n`;
}
// Request information section
const requestEntry = data.event.entries.find(
(entry) => entry.type === "request"
);
if (requestEntry) {
output += `\nRequest Information:\n\n`;
if (requestEntry.data.url)
output += `URL: ${requestEntry.data.url}\n`;
if (requestEntry.data.method)
output += `Method: ${requestEntry.data.method}\n`;
if (
requestEntry.data.headers &&
requestEntry.data.headers.length > 0
) {
output += `\nHeaders:\n\n`;
for (const [key, value] of requestEntry.data.headers) {
output += `${key}: ${value}\n`;
}
}
}
// Context information
if (
data.event.context &&
Object.keys(data.event.context).length > 0
) {
output += `\nContext:\n\n`;
output += JSON.stringify(data.event.context, null, 2);
output += "\n";
}
// Project information section
output += `\nProject Information:\n\n`;
output += `Organization: ${data.organizationSlug}\n`;
output += `Project: ${data.projectSlug}\n`;
output += `Group ID: ${data.groupId}\n`;
} else {
// Summary view
output += `Summary:\n\n`;
output += `Title: ${data.event.title || data.event.metadata.title}\n`;
output += `Platform: ${data.event.platform}\n`;
output += `Date Created: ${data.event.dateCreated}\n`;
output += `Organization: ${data.organizationSlug}\n`;
output += `Project: ${data.projectSlug}\n`;
// Add a few important tags if available
const levelTag = data.event.tags.find((tag) => tag.key === "level");
const releaseTag = data.event.tags.find(
(tag) => tag.key === "release"
);
if (levelTag) output += `Level: ${levelTag.value}\n`;
if (releaseTag) output += `Release: ${releaseTag.value}\n`;
if (data.event.user) {
output += `User: ${
data.event.user.email ||
data.event.user.username ||
data.event.user.id
}\n`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error retrieving event: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"list_error_events_in_project",
"List error events from a specific Sentry project. View recent errors, frequency patterns, and occurrence timestamps.",
{
organization_slug: z
.string()
.describe("The slug of the organization the project belongs to"),
project_slug: z
.string()
.describe("The slug of the project to list events from"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
organization_slug,
project_slug,
view,
format,
}: {
organization_slug: string;
project_slug: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Listing events for project:", project_slug);
console.error("DEBUG: In organization:", organization_slug);
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Construct the URL for the Sentry API
const apiUrl: string = `https://sentry.io/api/0/projects/${organization_slug}/${project_slug}/events/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to fetch events: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const events: SentryErrorEvent[] = await response.json();
console.error("DEBUG: Fetched events:", JSON.stringify(events, null, 2));
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
if (view === "detailed") {
// Create a detailed markdown view
output = `# Error Events for Project: ${project_slug}\n\n`;
if (events.length === 0) {
output += "No events found for this project.\n";
} else {
for (let i = 0; i < events.length; i++) {
const event = events[i];
output += `## Event ${i + 1}: ${event.title}\n\n`;
output += `- **Event ID**: ${event.eventID}\n`;
output += `- **Group ID**: ${event.groupID}\n`;
output += `- **Date Created**: ${event.dateCreated}\n`;
output += `- **Platform**: ${event.platform}\n`;
output += `- **Type**: ${event["event.type"]}\n`;
output += `- **Location**: ${event.location}\n`;
output += `- **Culprit**: ${event.culprit}\n`;
// Add tags section if there are tags
if (event.tags && event.tags.length > 0) {
output += `\n### Tags\n\n`;
output += `| Key | Value |\n`;
output += `|-----|-------|\n`;
for (const tag of event.tags) {
output += `| ${tag.key} | ${tag.value} |\n`;
}
}
// Add user information if available
if (event.user) {
output += `\n### User Information\n\n`;
if (event.user.id) output += `- **ID**: ${event.user.id}\n`;
if (event.user.email)
output += `- **Email**: ${event.user.email}\n`;
if (event.user.username)
output += `- **Username**: ${event.user.username}\n`;
if (event.user.ip_address)
output += `- **IP Address**: ${event.user.ip_address}\n`;
}
output += `\n---\n\n`;
}
// Add summary information
output += `## Summary\n\n`;
output += `Total Events: ${events.length}\n`;
}
} else {
// Create a summary markdown list
output = `# Error Events for Project: ${project_slug}\n\n`;
if (events.length === 0) {
output += "No events found for this project.\n";
} else {
for (const event of events) {
const level =
event.tags?.find((tag) => tag.key === "level")?.value ||
"unknown";
const environment =
event.tags?.find((tag) => tag.key === "environment")?.value ||
"unknown";
output += `- **${event.title}** (ID: ${event.eventID})\n`;
output += ` - Level: ${level}, Environment: ${environment}, Platform: ${event.platform}\n`;
output += ` - Date: ${event.dateCreated}\n\n`;
}
output += `Total Events: ${events.length}`;
}
}
} else {
// Plain text format
if (view === "detailed") {
output = `Error Events for Project: ${project_slug}\n\n`;
if (events.length === 0) {
output += "No events found for this project.\n";
} else {
for (let i = 0; i < events.length; i++) {
const event = events[i];
output += `Event ${i + 1}: ${event.title}\n\n`;
output += `Event ID: ${event.eventID}\n`;
output += `Group ID: ${event.groupID}\n`;
output += `Date Created: ${event.dateCreated}\n`;
output += `Platform: ${event.platform}\n`;
output += `Type: ${event["event.type"]}\n`;
output += `Location: ${event.location}\n`;
output += `Culprit: ${event.culprit}\n`;
// Add tags section if there are tags
if (event.tags && event.tags.length > 0) {
output += `\nTags:\n\n`;
for (const tag of event.tags) {
output += `${tag.key}: ${tag.value}\n`;
}
}
// Add user information if available
if (event.user) {
output += `\nUser Information:\n\n`;
if (event.user.id) output += `ID: ${event.user.id}\n`;
if (event.user.email) output += `Email: ${event.user.email}\n`;
if (event.user.username)
output += `Username: ${event.user.username}\n`;
if (event.user.ip_address)
output += `IP Address: ${event.user.ip_address}\n`;
}
output += `\n---\n\n`;
}
// Add summary information
output += `Summary:\n\n`;
output += `Total Events: ${events.length}\n`;
}
} else {
// Create a summary plain text list
output = `Error Events for Project: ${project_slug}\n\n`;
if (events.length === 0) {
output += "No events found for this project.\n";
} else {
for (const event of events) {
const level =
event.tags?.find((tag) => tag.key === "level")?.value ||
"unknown";
const environment =
event.tags?.find((tag) => tag.key === "environment")?.value ||
"unknown";
output += `${event.title} (ID: ${event.eventID})\n`;
output += `Level: ${level}, Environment: ${environment}, Platform: ${event.platform}\n`;
output += `Date: ${event.dateCreated}\n\n`;
}
output += `Total Events: ${events.length}`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error listing events: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"create_project",
"Create a new project in Sentry. Track deployments, releases, and health metrics.",
{
organization_slug: z
.string()
.describe("The slug of the organization to create the project in"),
team_slug: z
.string()
.describe("The slug of the team to associate the project with"),
name: z.string().describe("The name of the project to create"),
platform: z
.string()
.optional()
.describe(
"The platform for the project (e.g., python, javascript, etc.)"
),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
organization_slug,
team_slug,
name,
platform,
view,
format,
}: {
organization_slug: string;
team_slug: string;
name: string;
platform?: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Creating project:", name);
console.error("DEBUG: In organization:", organization_slug);
console.error("DEBUG: For team:", team_slug);
console.error("DEBUG: Platform:", platform || "not specified");
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Construct the URL for the Sentry API to create a project
const apiUrl: string = `https://sentry.io/api/0/teams/${organization_slug}/${team_slug}/projects/`;
// Prepare the request body
const requestBody: any = {
name: name,
};
// Add platform if specified
if (platform) {
requestBody.platform = platform;
}
// Make the API request to create the project
const response = await fetch(apiUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to create project: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const projectData: SentryProjectCreationResponse = await response.json();
console.error(
"DEBUG: Created project data:",
JSON.stringify(projectData, null, 2)
);
// Now fetch the client keys for the newly created project
const keysApiUrl: string = `https://sentry.io/api/0/projects/${organization_slug}/${projectData.slug}/keys/`;
// Make the API request to get client keys
const keysResponse = await fetch(keysApiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the keys request was successful
if (!keysResponse.ok) {
const errorText: string = await keysResponse.text();
console.error(
"DEBUG: Keys API request failed:",
keysResponse.status,
errorText
);
// Still return project data but with a warning about keys
let output: string = "";
if (format === "markdown") {
output = `# Project Created: ${projectData.name}\n\n`;
output += `**Warning**: Failed to fetch client keys: ${keysResponse.status} ${keysResponse.statusText}\n\n`;
// Add project details...
} else {
output = `Project Created: ${projectData.name}\n\n`;
output += `Warning: Failed to fetch client keys: ${keysResponse.status} ${keysResponse.statusText}\n\n`;
// Add project details...
}
// Return the output with warning
return {
content: [
{
type: "text",
text: output,
},
],
};
}
// Parse the keys response
const clientKeys: SentryClientKey[] = await keysResponse.json();
console.error(
"DEBUG: Client keys data:",
JSON.stringify(clientKeys, null, 2)
);
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
output = `# Project Created: ${projectData.name}\n\n`;
if (view === "detailed") {
// Project information section
output += `## Project Information\n\n`;
output += `- **ID**: ${projectData.id}\n`;
output += `- **Name**: ${projectData.name}\n`;
output += `- **Slug**: ${projectData.slug}\n`;
output += `- **Platform**: ${
projectData.platform || "Not specified"
}\n`;
output += `- **Date Created**: ${projectData.dateCreated}\n`;
output += `- **Status**: ${projectData.status}\n`;
// Features section
output += `\n## Features\n\n`;
output += `- ${projectData.features.join("\n- ")}\n`;
// Client Keys section
output += `\n## Client Keys\n\n`;
for (let i = 0; i < clientKeys.length; i++) {
const key = clientKeys[i];
output += `### Key ${i + 1}: ${key.name}\n\n`;
output += `- **ID**: ${key.id}\n`;
output += `- **Public Key**: ${key.public}\n`;
output += `- **Secret Key**: ${key.secret}\n`;
output += `- **Project ID**: ${key.projectId}\n`;
output += `- **Is Active**: ${key.isActive}\n`;
output += `- **Date Created**: ${key.dateCreated}\n\n`;
output += `#### DSN Information\n\n`;
output += `- **Public DSN**: \`${key.dsn.public}\`\n`;
output += `- **Secret DSN**: \`${key.dsn.secret}\`\n`;
output += `- **CSP Endpoint**: \`${key.dsn.csp}\`\n`;
output += `- **Security Endpoint**: \`${key.dsn.security}\`\n`;
output += `- **Minidump Endpoint**: \`${key.dsn.minidump}\`\n`;
output += `- **CDN URL**: \`${key.dsn.cdn}\`\n\n`;
}
} else {
// Summary view
output += `## Project Summary\n\n`;
output += `- **ID**: ${projectData.id}\n`;
output += `- **Name**: ${projectData.name}\n`;
output += `- **Slug**: ${projectData.slug}\n`;
output += `- **Platform**: ${
projectData.platform || "Not specified"
}\n\n`;
output += `## Client Keys Summary\n\n`;
for (let i = 0; i < clientKeys.length; i++) {
const key = clientKeys[i];
output += `### Key ${i + 1}: ${key.name}\n\n`;
output += `- **Public DSN**: \`${key.dsn.public}\`\n\n`;
}
}
} else {
// Plain text format
output = `Project Created: ${projectData.name}\n\n`;
if (view === "detailed") {
// Project information section
output += `Project Information:\n\n`;
output += `ID: ${projectData.id}\n`;
output += `Name: ${projectData.name}\n`;
output += `Slug: ${projectData.slug}\n`;
output += `Platform: ${projectData.platform || "Not specified"}\n`;
output += `Date Created: ${projectData.dateCreated}\n`;
output += `Status: ${projectData.status}\n`;
// Features section
output += `\nFeatures:\n\n`;
for (const feature of projectData.features) {
output += `- ${feature}\n`;
}
// Client Keys section
output += `\nClient Keys:\n\n`;
for (let i = 0; i < clientKeys.length; i++) {
const key = clientKeys[i];
output += `Key ${i + 1}: ${key.name}\n\n`;
output += `ID: ${key.id}\n`;
output += `Public Key: ${key.public}\n`;
output += `Secret Key: ${key.secret}\n`;
output += `Project ID: ${key.projectId}\n`;
output += `Is Active: ${key.isActive}\n`;
output += `Date Created: ${key.dateCreated}\n\n`;
output += `DSN Information:\n\n`;
output += `Public DSN: ${key.dsn.public}\n`;
output += `Secret DSN: ${key.dsn.secret}\n`;
output += `CSP Endpoint: ${key.dsn.csp}\n`;
output += `Security Endpoint: ${key.dsn.security}\n`;
output += `Minidump Endpoint: ${key.dsn.minidump}\n`;
output += `CDN URL: ${key.dsn.cdn}\n\n`;
}
} else {
// Summary view
output += `Project Summary:\n\n`;
output += `ID: ${projectData.id}\n`;
output += `Name: ${projectData.name}\n`;
output += `Slug: ${projectData.slug}\n`;
output += `Platform: ${projectData.platform || "Not specified"}\n\n`;
output += `Client Keys Summary:\n\n`;
for (let i = 0; i < clientKeys.length; i++) {
const key = clientKeys[i];
output += `Key ${i + 1}: ${key.name}\n\n`;
output += `Public DSN: ${key.dsn.public}\n\n`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error creating project: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"list_project_issues",
"List issues from a Sentry project. Monitor issue status, severity, frequency, and timing.",
{
organization_slug: z
.string()
.describe("The slug of the organization the project belongs to"),
project_slug: z
.string()
.describe("The slug of the project to list issues from"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
organization_slug,
project_slug,
view,
format,
}: {
organization_slug: string;
project_slug: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Listing issues for project:", project_slug);
console.error("DEBUG: In organization:", organization_slug);
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Construct the URL for the Sentry API
const apiUrl: string = `https://sentry.io/api/0/projects/${organization_slug}/${project_slug}/issues/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to fetch project issues: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const issues: SentryProjectIssue[] = await response.json();
console.error("DEBUG: Fetched issues:", JSON.stringify(issues, null, 2));
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
if (view === "detailed") {
// Create a detailed markdown view
output = `# Issues for Project: ${project_slug}\n\n`;
if (issues.length === 0) {
output += "No issues found for this project.\n";
} else {
// Create a table for the issues
output += `| ID | Short ID | Title | Status | Level | First Seen | Last Seen | Events | Users |\n`;
output += `|----|----------|-------|--------|-------|------------|-----------|--------|-------|\n`;
for (const issue of issues) {
output += `| ${issue.id} | ${issue.shortId} | ${issue.title} | ${issue.status} | ${issue.level} | ${issue.firstSeen} | ${issue.lastSeen} | ${issue.count} | ${issue.userCount} |\n`;
}
// Add detailed information for each issue
output += `\n## Issue Details\n\n`;
for (let i = 0; i < issues.length; i++) {
const issue = issues[i];
output += `### Issue ${i + 1}: ${issue.title}\n\n`;
output += `- **ID**: ${issue.id}\n`;
output += `- **Short ID**: ${issue.shortId}\n`;
output += `- **Status**: ${issue.status}\n`;
output += `- **Level**: ${issue.level}\n`;
output += `- **First Seen**: ${issue.firstSeen}\n`;
output += `- **Last Seen**: ${issue.lastSeen}\n`;
output += `- **Event Count**: ${issue.count}\n`;
output += `- **User Count**: ${issue.userCount}\n`;
output += `- **Culprit**: ${issue.culprit}\n`;
output += `- **Permalink**: [${issue.permalink}](${issue.permalink})\n`;
// Add 24h stats if available
if (
issue.stats &&
issue.stats["24h"] &&
issue.stats["24h"].length > 0
) {
output += `\n#### 24-Hour Event Distribution\n\n`;
output += `| Timestamp | Count |\n`;
output += `|-----------|-------|\n`;
for (const [timestamp, count] of issue.stats["24h"]) {
const date = new Date(timestamp * 1000);
output += `| ${date.toISOString()} | ${count} |\n`;
}
}
output += `\n---\n\n`;
}
// Add summary information
output += `## Summary\n\n`;
output += `Total Issues: ${issues.length}\n`;
}
} else {
// Create a summary markdown list
output = `# Issues for Project: ${project_slug}\n\n`;
if (issues.length === 0) {
output += "No issues found for this project.\n";
} else {
for (const issue of issues) {
output += `- **${issue.title}** (${issue.shortId})\n`;
output += ` - Status: ${issue.status}, Level: ${issue.level}, Events: ${issue.count}\n`;
output += ` - First seen: ${issue.firstSeen}, Last seen: ${issue.lastSeen}\n\n`;
}
output += `Total Issues: ${issues.length}`;
}
}
} else {
// Plain text format
if (view === "detailed") {
output = `Issues for Project: ${project_slug}\n\n`;
if (issues.length === 0) {
output += "No issues found for this project.\n";
} else {
for (let i = 0; i < issues.length; i++) {
const issue = issues[i];
output += `Issue ${i + 1}: ${issue.title}\n\n`;
output += `ID: ${issue.id}\n`;
output += `Short ID: ${issue.shortId}\n`;
output += `Status: ${issue.status}\n`;
output += `Level: ${issue.level}\n`;
output += `First Seen: ${issue.firstSeen}\n`;
output += `Last Seen: ${issue.lastSeen}\n`;
output += `Event Count: ${issue.count}\n`;
output += `User Count: ${issue.userCount}\n`;
output += `Culprit: ${issue.culprit}\n`;
output += `Permalink: ${issue.permalink}\n`;
// Add 24h stats if available
if (
issue.stats &&
issue.stats["24h"] &&
issue.stats["24h"].length > 0
) {
output += `\n24-Hour Event Distribution:\n\n`;
for (const [timestamp, count] of issue.stats["24h"]) {
const date = new Date(timestamp * 1000);
output += `${date.toISOString()}: ${count}\n`;
}
}
output += `\n---\n\n`;
}
// Add summary information
output += `Summary:\n\n`;
output += `Total Issues: ${issues.length}\n`;
}
} else {
// Create a summary plain text list
output = `Issues for Project: ${project_slug}\n\n`;
if (issues.length === 0) {
output += "No issues found for this project.\n";
} else {
for (const issue of issues) {
output += `${issue.title} (${issue.shortId})\n`;
output += `Status: ${issue.status}, Level: ${issue.level}, Events: ${issue.count}\n`;
output += `First seen: ${issue.firstSeen}, Last seen: ${issue.lastSeen}\n\n`;
}
output += `Total Issues: ${issues.length}`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error listing project issues: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"list_issue_events",
"List events for a specific Sentry issue. Analyze event details, metadata, and patterns.",
{
organization_slug: z
.string()
.describe("The slug of the organization the issue belongs to"),
issue_id: z.string().describe("The ID of the issue to list events for"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
organization_slug,
issue_id,
view,
format,
}: {
organization_slug: string;
issue_id: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Listing events for issue:", issue_id);
console.error("DEBUG: In organization:", organization_slug);
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Construct the URL for the Sentry API
const apiUrl: string = `https://sentry.io/api/0/organizations/${organization_slug}/issues/${issue_id}/events/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to fetch issue events: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const events: SentryErrorEvent[] = await response.json();
console.error("DEBUG: Fetched events:", JSON.stringify(events, null, 2));
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
if (view === "detailed") {
// Create a detailed markdown view
output = `# Events for Issue: ${issue_id}\n\n`;
if (events.length === 0) {
output += "No events found for this issue.\n";
} else {
// Create a table for the events
output += `| Event ID | Title | Platform | Date Created | Location | Culprit |\n`;
output += `|----------|-------|----------|--------------|----------|----------|\n`;
for (const event of events) {
output += `| ${event.eventID} | ${event.title} | ${
event.platform
} | ${event.dateCreated} | ${event.location || "N/A"} | ${
event.culprit || "N/A"
} |\n`;
}
// Add detailed information for each event
output += `\n## Event Details\n\n`;
for (let i = 0; i < events.length; i++) {
const event = events[i];
output += `### Event ${i + 1}: ${event.title}\n\n`;
output += `- **Event ID**: ${event.eventID}\n`;
output += `- **Group ID**: ${event.groupID}\n`;
output += `- **Date Created**: ${event.dateCreated}\n`;
output += `- **Platform**: ${event.platform}\n`;
output += `- **Type**: ${event["event.type"]}\n`;
output += `- **Location**: ${event.location || "N/A"}\n`;
output += `- **Culprit**: ${event.culprit || "N/A"}\n`;
output += `- **Project ID**: ${event.projectID}\n`;
// Add tags section if there are tags
if (event.tags && event.tags.length > 0) {
output += `\n#### Tags\n\n`;
output += `| Key | Value |\n`;
output += `|-----|-------|\n`;
for (const tag of event.tags) {
output += `| ${tag.key} | ${tag.value} |\n`;
}
}
// Add user information if available
if (event.user) {
output += `\n#### User Information\n\n`;
if (event.user.id) output += `- **ID**: ${event.user.id}\n`;
if (event.user.email)
output += `- **Email**: ${event.user.email}\n`;
if (event.user.username)
output += `- **Username**: ${event.user.username}\n`;
if (event.user.ip_address)
output += `- **IP Address**: ${event.user.ip_address}\n`;
}
output += `\n---\n\n`;
}
// Add summary information
output += `## Summary\n\n`;
output += `Total Events: ${events.length}\n`;
}
} else {
// Create a summary markdown list
output = `# Events for Issue: ${issue_id}\n\n`;
if (events.length === 0) {
output += "No events found for this issue.\n";
} else {
for (const event of events) {
const level =
event.tags?.find((tag) => tag.key === "level")?.value ||
"unknown";
const environment =
event.tags?.find((tag) => tag.key === "environment")?.value ||
"unknown";
output += `- **Event ID**: ${event.eventID}\n`;
output += ` - Title: ${event.title}\n`;
output += ` - Level: ${level}, Environment: ${environment}, Platform: ${event.platform}\n`;
output += ` - Date: ${event.dateCreated}\n\n`;
}
output += `Total Events: ${events.length}`;
}
}
} else {
// Plain text format
if (view === "detailed") {
output = `Events for Issue: ${issue_id}\n\n`;
if (events.length === 0) {
output += "No events found for this issue.\n";
} else {
for (let i = 0; i < events.length; i++) {
const event = events[i];
output += `Event ${i + 1}: ${event.title}\n\n`;
output += `Event ID: ${event.eventID}\n`;
output += `Group ID: ${event.groupID}\n`;
output += `Date Created: ${event.dateCreated}\n`;
output += `Platform: ${event.platform}\n`;
output += `Type: ${event["event.type"]}\n`;
output += `Location: ${event.location || "N/A"}\n`;
output += `Culprit: ${event.culprit || "N/A"}\n`;
output += `Project ID: ${event.projectID}\n`;
// Add tags section if there are tags
if (event.tags && event.tags.length > 0) {
output += `\nTags:\n\n`;
for (const tag of event.tags) {
output += `${tag.key}: ${tag.value}\n`;
}
}
// Add user information if available
if (event.user) {
output += `\nUser Information:\n\n`;
if (event.user.id) output += `ID: ${event.user.id}\n`;
if (event.user.email) output += `Email: ${event.user.email}\n`;
if (event.user.username)
output += `Username: ${event.user.username}\n`;
if (event.user.ip_address)
output += `IP Address: ${event.user.ip_address}\n`;
}
output += `\n---\n\n`;
}
// Add summary information
output += `Summary:\n\n`;
output += `Total Events: ${events.length}\n`;
}
} else {
// Create a summary plain text list
output = `Events for Issue: ${issue_id}\n\n`;
if (events.length === 0) {
output += "No events found for this issue.\n";
} else {
for (const event of events) {
const level =
event.tags?.find((tag) => tag.key === "level")?.value ||
"unknown";
const environment =
event.tags?.find((tag) => tag.key === "environment")?.value ||
"unknown";
output += `Event ID: ${event.eventID}\n`;
output += `Title: ${event.title}\n`;
output += `Level: ${level}, Environment: ${environment}, Platform: ${event.platform}\n`;
output += `Date: ${event.dateCreated}\n\n`;
}
output += `Total Events: ${events.length}`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error listing issue events: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"get_sentry_issue",
"Retrieve and analyze a Sentry issue. Accepts issue URL or ID.",
{
issue_id_or_url: z
.string()
.describe("Either a full Sentry issue URL or just the numeric issue ID"),
organization_slug: z
.string()
.describe("The slug of the organization the issue belongs to"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
},
async ({
issue_id_or_url,
organization_slug,
view,
format,
}: {
issue_id_or_url: string;
organization_slug: string;
view: "summary" | "detailed";
format: "plain" | "markdown";
}) => {
try {
// Debug input
console.error("DEBUG: Retrieving issue:", issue_id_or_url);
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Parse the issue ID from URL if provided
let organizationSlug = "";
if (issue_id_or_url.startsWith("http")) {
// Extract organization slug and issue ID from URL
const urlPattern =
/https:\/\/([\w-]+)\.sentry\.io\/(?:organizations\/)?([\w-]+)?(?:\/)?(?:issues\/)?(\d+)/;
const match = issue_id_or_url.match(urlPattern);
if (!match) {
organizationSlug = organization_slug;
} else {
organizationSlug = match[1];
issue_id_or_url = match[3];
}
} else {
organizationSlug = organization_slug;
}
console.error("DEBUG: Organization slug:", organizationSlug);
console.error("DEBUG: Issue ID:", issue_id_or_url);
// Construct the URL for the Sentry API
const apiUrl: string = `https://sentry.io/api/0/organizations/${organizationSlug}/issues/${issue_id_or_url}/`;
// Make the API request
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
// Check if the request was successful
if (!response.ok) {
const errorText: string = await response.text();
console.error("DEBUG: API request failed:", response.status, errorText);
return {
content: [
{
type: "text",
text: `Failed to fetch issue details: ${response.status} ${response.statusText}\n${errorText}`,
},
],
isError: true,
};
}
// Parse the response
const issue: SentryIssueDetailsResponse = await response.json();
console.error("DEBUG: Fetched issue:", JSON.stringify(issue, null, 2));
// Format the output based on the view type and format
let output: string = "";
if (format === "markdown") {
if (view === "detailed") {
// Create a detailed markdown view
output = `# Issue: ${issue.title}\n\n`;
// Basic issue information
output += `## Overview\n\n`;
output += `- **ID**: ${issue.id}\n`;
output += `- **Short ID**: ${issue.shortId}\n`;
output += `- **Status**: ${issue.status}\n`;
output += `- **Level**: ${issue.level}\n`;
output += `- **First Seen**: ${issue.firstSeen}\n`;
output += `- **Last Seen**: ${issue.lastSeen}\n`;
output += `- **Event Count**: ${issue.count}\n`;
output += `- **User Count**: ${issue.userCount}\n`;
output += `- **Culprit**: ${issue.culprit}\n`;
output += `- **Permalink**: [${issue.permalink}](${issue.permalink})\n`;
// Project information
output += `\n## Project\n\n`;
output += `- **Name**: ${issue.project.name}\n`;
output += `- **ID**: ${issue.project.id}\n`;
output += `- **Slug**: ${issue.project.slug}\n`;
// Release information if available
if (issue.firstRelease) {
output += `\n## First Release\n\n`;
output += `- **Version**: ${issue.firstRelease.version}\n`;
output += `- **Short Version**: ${issue.firstRelease.shortVersion}\n`;
output += `- **Date Created**: ${issue.firstRelease.dateCreated}\n`;
output += `- **First Event**: ${issue.firstRelease.firstEvent}\n`;
output += `- **Last Event**: ${issue.firstRelease.lastEvent}\n`;
if (
issue.firstRelease.projects &&
issue.firstRelease.projects.length > 0
) {
output += `- **Projects**:\n`;
for (const project of issue.firstRelease.projects) {
output += ` - ${project.name} (${project.slug})\n`;
}
}
}
// Activity information
if (issue.activity && issue.activity.length > 0) {
output += `\n## Activity\n\n`;
output += `| Type | Date | User |\n`;
output += `|------|------|------|\n`;
for (const activity of issue.activity) {
const user = activity.user ? activity.user.name : "System";
output += `| ${activity.type} | ${activity.dateCreated} | ${user} |\n`;
}
}
// Tags if available
if (issue.tags && issue.tags.length > 0) {
output += `\n## Tags\n\n`;
output += `| Key | Value |\n`;
output += `|-----|-------|\n`;
for (const tag of issue.tags) {
output += `| ${tag.key} | ${tag.value} |\n`;
}
}
// 24h stats
if (
issue.stats &&
issue.stats["24h"] &&
issue.stats["24h"].length > 0
) {
output += `\n## 24-Hour Event Distribution\n\n`;
output += `| Timestamp | Count |\n`;
output += `|-----------|-------|\n`;
for (const [timestamp, count] of issue.stats["24h"]) {
const date = new Date(timestamp * 1000);
output += `| ${date.toISOString()} | ${count} |\n`;
}
}
// 30d stats
if (
issue.stats &&
issue.stats["30d"] &&
issue.stats["30d"].length > 0
) {
output += `\n## 30-Day Event Distribution\n\n`;
output += `| Date | Count |\n`;
output += `|------|-------|\n`;
for (const [timestamp, count] of issue.stats["30d"]) {
const date = new Date(timestamp * 1000);
output += `| ${date.toISOString().split("T")[0]} | ${count} |\n`;
}
}
} else {
// Create a summary markdown view
output = `# Issue: ${issue.title}\n\n`;
output += `**Short ID**: ${issue.shortId}\n`;
output += `**Status**: ${issue.status}, **Level**: ${issue.level}\n`;
output += `**First Seen**: ${issue.firstSeen}, **Last Seen**: ${issue.lastSeen}\n`;
output += `**Events**: ${issue.count}, **Users Affected**: ${issue.userCount}\n`;
output += `**Project**: ${issue.project.name}\n`;
output += `**Permalink**: [${issue.permalink}](${issue.permalink})\n`;
// Include a small 24h chart summary
if (
issue.stats &&
issue.stats["24h"] &&
issue.stats["24h"].length > 0
) {
let total24h = 0;
for (const [_, count] of issue.stats["24h"]) {
total24h += count;
}
output += `\n**24-Hour Event Count**: ${total24h}\n`;
}
}
} else {
// Plain text format
if (view === "detailed") {
output = `Issue: ${issue.title}\n\n`;
// Basic issue information
output += `Overview:\n\n`;
output += `ID: ${issue.id}\n`;
output += `Short ID: ${issue.shortId}\n`;
output += `Status: ${issue.status}\n`;
output += `Level: ${issue.level}\n`;
output += `First Seen: ${issue.firstSeen}\n`;
output += `Last Seen: ${issue.lastSeen}\n`;
output += `Event Count: ${issue.count}\n`;
output += `User Count: ${issue.userCount}\n`;
output += `Culprit: ${issue.culprit}\n`;
output += `Permalink: ${issue.permalink}\n`;
// Project information
output += `\nProject:\n\n`;
output += `Name: ${issue.project.name}\n`;
output += `ID: ${issue.project.id}\n`;
output += `Slug: ${issue.project.slug}\n`;
// Release information if available
if (issue.firstRelease) {
output += `\nFirst Release:\n\n`;
output += `Version: ${issue.firstRelease.version}\n`;
output += `Short Version: ${issue.firstRelease.shortVersion}\n`;
output += `Date Created: ${issue.firstRelease.dateCreated}\n`;
output += `First Event: ${issue.firstRelease.firstEvent}\n`;
output += `Last Event: ${issue.firstRelease.lastEvent}\n`;
if (
issue.firstRelease.projects &&
issue.firstRelease.projects.length > 0
) {
output += `Projects:\n`;
for (const project of issue.firstRelease.projects) {
output += ` - ${project.name} (${project.slug})\n`;
}
}
}
// Activity information
if (issue.activity && issue.activity.length > 0) {
output += `\nActivity:\n\n`;
for (const activity of issue.activity) {
const user = activity.user ? activity.user.name : "System";
output += `Type: ${activity.type}, Date: ${activity.dateCreated}, User: ${user}\n`;
}
}
// Tags if available
if (issue.tags && issue.tags.length > 0) {
output += `\nTags:\n\n`;
for (const tag of issue.tags) {
output += `${tag.key} | ${tag.value}\n`;
}
}
// 24h stats
if (
issue.stats &&
issue.stats["24h"] &&
issue.stats["24h"].length > 0
) {
output += `\n24-Hour Event Distribution:\n\n`;
for (const [timestamp, count] of issue.stats["24h"]) {
const date = new Date(timestamp * 1000);
output += `${date.toISOString()} | ${count}\n`;
}
}
// 30d stats
if (
issue.stats &&
issue.stats["30d"] &&
issue.stats["30d"].length > 0
) {
output += `\n30-Day Event Distribution:\n\n`;
for (const [timestamp, count] of issue.stats["30d"]) {
const date = new Date(timestamp * 1000);
output += `${date.toISOString().split("T")[0]}: ${count}\n`;
}
}
} else {
// Create a summary plain text view
output = `Issue: ${issue.title}\n\n`;
output += `Short ID: ${issue.shortId}\n`;
output += `Status: ${issue.status}, Level: ${issue.level}\n`;
output += `First Seen: ${issue.firstSeen}, Last Seen: ${issue.lastSeen}\n`;
output += `Events: ${issue.count}, Users Affected: ${issue.userCount}\n`;
output += `Project: ${issue.project.name}\n`;
output += `Permalink: ${issue.permalink}\n`;
// Include a small 24h chart summary
if (
issue.stats &&
issue.stats["24h"] &&
issue.stats["24h"].length > 0
) {
let total24h = 0;
for (const [_, count] of issue.stats["24h"]) {
total24h += count;
}
output += `\n24-Hour Event Count: ${total24h}\n`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error retrieving issue details: ${error.message}`,
},
],
isError: true,
};
}
}
);
server.tool(
"list_organization_replays",
"List replays from a Sentry organization. Monitor user sessions, interactions, errors, and experience issues.",
{
organization_slug: z
.string()
.describe("The slug of the organization to list replays from"),
project_ids: z
.array(z.string())
.optional()
.describe("Optional array of project IDs to filter replays by"),
environment: z
.string()
.optional()
.describe("Optional environment to filter replays by"),
stats_period: z
.string()
.optional()
.describe(
"Optional time range in format <number><unit> (e.g., '1d' for one day). Units: m (minutes), h (hours), d (days), w (weeks)"
),
start: z
.string()
.optional()
.describe(
"Optional start of time range (UTC ISO8601 or epoch seconds). Use with 'end' instead of 'stats_period'"
),
end: z
.string()
.optional()
.describe(
"Optional end of time range (UTC ISO8601 or epoch seconds). Use with 'start' instead of 'stats_period'"
),
sort: z.string().optional().describe("Optional field to sort results by"),
query: z
.string()
.optional()
.describe("Optional structured query string to filter results"),
per_page: z
.number()
.optional()
.describe("Optional limit on number of results to return"),
cursor: z.string().optional().describe("Optional cursor for pagination"),
format: z
.enum(["plain", "markdown"])
.default("markdown")
.describe("Output format (default: markdown)"),
view: z
.enum(["summary", "detailed"])
.default("detailed")
.describe("View type (default: detailed)"),
},
async ({
organization_slug,
project_ids,
environment,
stats_period,
start,
end,
sort,
query,
per_page,
cursor,
format,
view,
}) => {
try {
// Debug input
console.error(
"DEBUG: Listing replays for organization:",
organization_slug
);
console.error("DEBUG: Project IDs:", project_ids);
console.error("DEBUG: Environment:", environment);
console.error("DEBUG: Time range:", stats_period || `${start} to ${end}`);
console.error("DEBUG: View:", view);
console.error("DEBUG: Format:", format);
// Build query parameters
const queryParams = new URLSearchParams();
// Add time range parameters
if (stats_period) {
queryParams.append("statsPeriod", stats_period);
} else if (start && end) {
queryParams.append("start", start);
queryParams.append("end", end);
}
// Add project filter if provided
if (project_ids && project_ids.length > 0) {
project_ids.forEach((id) => queryParams.append("project", id));
}
// Add environment filter if provided
if (environment) {
queryParams.append("environment", environment);
}
// Add sort parameter if provided
if (sort) {
queryParams.append("sort", sort);
}
// Add query filter if provided
if (query) {
queryParams.append("query", query);
}
// Add pagination parameters if provided
if (per_page) {
queryParams.append("per_page", per_page.toString());
}
if (cursor) {
queryParams.append("cursor", cursor);
}
// Add fields parameter to get all relevant fields
const fields = [
"activity",
"browser",
"count_dead_clicks",
"count_errors",
"count_rage_clicks",
"count_segments",
"count_urls",
"device",
"dist",
"duration",
"environment",
"error_ids",
"finished_at",
"id",
"is_archived",
"os",
"platform",
"project_id",
"releases",
"sdk",
"started_at",
"tags",
"trace_ids",
"urls",
"user",
"clicks",
"info_ids",
"warning_ids",
"count_warnings",
"count_infos",
"has_viewed",
];
fields.forEach((field) => queryParams.append("field", field));
// Build the URL with query parameters
const queryString = queryParams.toString();
const url = `https://sentry.io/api/0/organizations/${organization_slug}/replays/${
queryString ? `?${queryString}` : ""
}`;
console.error("DEBUG: Request URL:", url);
// Fetch replays from the Sentry API
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${SENTRY_AUTH}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Failed to fetch replays: ${response.status} ${response.statusText} - ${errorText}`
);
}
// Parse the response
const responseData = await response.json();
const replays = responseData.data || [];
console.error("DEBUG: Found replays:", replays.length);
let output = "";
// Add filter information to output
const filterInfo: string[] = [];
if (project_ids && project_ids.length > 0) {
filterInfo.push(`Projects: ${project_ids.join(", ")}`);
}
if (environment) {
filterInfo.push(`Environment: ${environment}`);
}
if (stats_period) {
filterInfo.push(`Time Range: Last ${stats_period}`);
} else if (start && end) {
filterInfo.push(`Time Range: ${start} to ${end}`);
}
const filterText =
filterInfo.length > 0
? ` (Filtered by: ${filterInfo.join(" | ")})`
: "";
if (format === "markdown") {
if (view === "detailed") {
// Create a detailed markdown view
output = `# Replays for Organization: ${organization_slug}${filterText}\n\n`;
// Summary table
output += `## Summary\n\n`;
output += `| ID | Project ID | Started | Duration | Browser | Platform | Environment | Errors |\n`;
output += `|:---|:-----------|:--------|:---------|:--------|:---------|:------------|:-------|\n`;
for (const replay of replays) {
const startedDate = new Date(replay.started_at).toLocaleString();
const duration = `${Math.floor(replay.duration / 60)}m ${
replay.duration % 60
}s`;
const browser = replay.browser
? `${replay.browser.name} ${replay.browser.version}`
: "N/A";
output += `| ${replay.id} | ${replay.project_id} | ${startedDate} | ${duration} | ${browser} | ${replay.platform} | ${replay.environment} | ${replay.count_errors} |\n`;
}
// Pagination information if cursor is provided
if (cursor) {
output += `\n*Note: This is a paginated result. Use the cursor parameter for the next page.*\n\n`;
}
output += `\n## Replay Details\n\n`;
for (let i = 0; i < replays.length; i++) {
const replay = replays[i];
const startedDate = new Date(replay.started_at).toLocaleString();
const finishedDate = replay.finished_at
? new Date(replay.finished_at).toLocaleString()
: "N/A";
const duration = `${Math.floor(replay.duration / 60)}m ${
replay.duration % 60
}s`;
output += `### Replay ${i + 1}: ${replay.id}\n\n`;
output += `- **Project ID**: ${replay.project_id}\n`;
output += `- **Started**: ${startedDate}\n`;
output += `- **Finished**: ${finishedDate}\n`;
output += `- **Duration**: ${duration}\n`;
output += `- **Environment**: ${replay.environment}\n`;
output += `- **Platform**: ${replay.platform}\n`;
output += `- **Activity**: ${replay.activity}\n`;
output += `- **Viewed**: ${replay.has_viewed ? "Yes" : "No"}\n`;
// User information if available
if (replay.user) {
output += `- **User**: ${
replay.user.display_name || replay.user.username
} (${replay.user.email})\n`;
}
// Browser information if available
if (replay.browser) {
output += `- **Browser**: ${replay.browser.name} ${replay.browser.version}\n`;
}
// OS information if available
if (replay.os) {
output += `- **OS**: ${replay.os.name} ${replay.os.version}\n`;
}
// Device information if available
if (replay.device) {
output += `- **Device**: ${replay.device.name} (${replay.device.brand} ${replay.device.model})\n`;
}
// SDK information
output += `- **SDK**: ${replay.sdk.name} ${replay.sdk.version}\n`;
// Interaction metrics
output += `- **Dead Clicks**: ${replay.count_dead_clicks}\n`;
output += `- **Rage Clicks**: ${replay.count_rage_clicks}\n`;
output += `- **Errors**: ${replay.count_errors}\n`;
// Include warnings and infos if available
if ("count_warnings" in replay) {
output += `- **Warnings**: ${(replay as any).count_warnings}\n`;
}
if ("count_infos" in replay) {
output += `- **Infos**: ${(replay as any).count_infos}\n`;
}
output += `- **URLs Visited**: ${replay.count_urls}\n`;
// URLs if available
if (replay.urls && replay.urls.length > 0) {
output += `- **URLs**:\n`;
for (const url of replay.urls) {
output += ` - ${url}\n`;
}
}
// Error IDs if available
if (replay.error_ids && replay.error_ids.length > 0) {
output += `- **Error IDs**:\n`;
for (const errorId of replay.error_ids) {
output += ` - ${errorId}\n`;
}
}
// Warning IDs if available
if (
"warning_ids" in replay &&
(replay as any).warning_ids &&
(replay as any).warning_ids.length > 0
) {
output += `- **Warning IDs**:\n`;
for (const warningId of (replay as any).warning_ids) {
output += ` - ${warningId}\n`;
}
}
// Info IDs if available
if (
"info_ids" in replay &&
(replay as any).info_ids &&
(replay as any).info_ids.length > 0
) {
output += `- **Info IDs**:\n`;
for (const infoId of (replay as any).info_ids) {
output += ` - ${infoId}\n`;
}
}
// Trace IDs if available
if (replay.trace_ids && replay.trace_ids.length > 0) {
output += `- **Trace IDs**:\n`;
for (const traceId of replay.trace_ids) {
output += ` - ${traceId}\n`;
}
}
// Releases if available
if (replay.releases && replay.releases.length > 0) {
output += `- **Releases**:\n`;
for (const release of replay.releases) {
output += ` - ${release}\n`;
}
}
// Tags if available
if (replay.tags && Object.keys(replay.tags).length > 0) {
output += `- **Tags**:\n`;
for (const [key, values] of Object.entries(replay.tags)) {
output += ` - ${key}: ${(values as string[]).join(", ")}\n`;
}
}
output += `\n`;
}
} else {
// Create a summary markdown view
output = `# Replays for Organization: ${organization_slug}${filterText}\n\n`;
output += `| ID | Project ID | Started | Duration | Browser | Platform | Environment | Errors |\n`;
output += `|:---|:-----------|:--------|:---------|:--------|:---------|:------------|:-------|\n`;
for (const replay of replays) {
const startedDate = new Date(replay.started_at).toLocaleString();
const duration = `${Math.floor(replay.duration / 60)}m ${
replay.duration % 60
}s`;
const browser = replay.browser
? `${replay.browser.name} ${replay.browser.version}`
: "N/A";
output += `| ${replay.id} | ${replay.project_id} | ${startedDate} | ${duration} | ${browser} | ${replay.platform} | ${replay.environment} | ${replay.count_errors} |\n`;
}
// Pagination information if cursor is provided
if (cursor) {
output += `\n*Note: This is a paginated result. Use the cursor parameter for the next page.*\n`;
}
}
} else {
// Plain text format
if (view === "detailed") {
output = `Replays for Organization: ${organization_slug}${filterText}\n\n`;
for (let i = 0; i < replays.length; i++) {
const replay = replays[i];
const startedDate = new Date(replay.started_at).toLocaleString();
const finishedDate = replay.finished_at
? new Date(replay.finished_at).toLocaleString()
: "N/A";
const duration = `${Math.floor(replay.duration / 60)}m ${
replay.duration % 60
}s`;
output += `Replay ${i + 1}: ${replay.id}\n`;
output += `Project ID: ${replay.project_id}\n`;
output += `Started: ${startedDate}\n`;
output += `Finished: ${finishedDate}\n`;
output += `Duration: ${duration}\n`;
output += `Environment: ${replay.environment}\n`;
output += `Platform: ${replay.platform}\n`;
output += `Activity: ${replay.activity}\n`;
output += `Viewed: ${replay.has_viewed ? "Yes" : "No"}\n`;
// User information if available
if (replay.user) {
output += `User: ${
replay.user.display_name || replay.user.username
} (${replay.user.email})\n`;
}
// Browser information if available
if (replay.browser) {
output += `Browser: ${replay.browser.name} ${replay.browser.version}\n`;
}
// OS information if available
if (replay.os) {
output += `OS: ${replay.os.name} ${replay.os.version}\n`;
}
// Device information if available
if (replay.device) {
output += `Device: ${replay.device.name} (${replay.device.brand} ${replay.device.model})\n`;
}
// SDK information
output += `SDK: ${replay.sdk.name} ${replay.sdk.version}\n`;
// Interaction metrics
output += `Dead Clicks: ${replay.count_dead_clicks}\n`;
output += `Rage Clicks: ${replay.count_rage_clicks}\n`;
output += `Errors: ${replay.count_errors}\n`;
// Include warnings and infos if available
if ("count_warnings" in replay) {
output += `Warnings: ${(replay as any).count_warnings}\n`;
}
if ("count_infos" in replay) {
output += `Infos: ${(replay as any).count_infos}\n`;
}
output += `URLs Visited: ${replay.count_urls}\n`;
// URLs if available
if (replay.urls && replay.urls.length > 0) {
output += `URLs:\n`;
for (const url of replay.urls) {
output += ` ${url}\n`;
}
}
// Error IDs if available
if (replay.error_ids && replay.error_ids.length > 0) {
output += `Error IDs:\n`;
for (const errorId of replay.error_ids) {
output += ` ${errorId}\n`;
}
}
// Warning IDs if available
if (
"warning_ids" in replay &&
(replay as any).warning_ids &&
(replay as any).warning_ids.length > 0
) {
output += `Warning IDs:\n`;
for (const warningId of (replay as any).warning_ids) {
output += ` ${warningId}\n`;
}
}
// Info IDs if available
if (
"info_ids" in replay &&
(replay as any).info_ids &&
(replay as any).info_ids.length > 0
) {
output += `Info IDs:\n`;
for (const infoId of (replay as any).info_ids) {
output += ` ${infoId}\n`;
}
}
// Trace IDs if available
if (replay.trace_ids && replay.trace_ids.length > 0) {
output += `Trace IDs:\n`;
for (const traceId of replay.trace_ids) {
output += ` ${traceId}\n`;
}
}
// Releases if available
if (replay.releases && replay.releases.length > 0) {
output += `Releases:\n`;
for (const release of replay.releases) {
output += ` ${release}\n`;
}
}
// Tags if available
if (replay.tags && Object.keys(replay.tags).length > 0) {
output += `Tags:\n`;
for (const [key, values] of Object.entries(replay.tags)) {
output += ` ${key}: ${(values as string[]).join(", ")}\n`;
}
}
output += `\n`;
}
// Pagination information if cursor is provided
if (cursor) {
output += `Note: This is a paginated result. Use the cursor parameter for the next page.\n`;
}
} else {
// Create a summary plain text view
output = `Replays for Organization: ${organization_slug}${filterText}\n\n`;
for (const replay of replays) {
const startedDate = new Date(replay.started_at).toLocaleString();
const duration = `${Math.floor(replay.duration / 60)}m ${
replay.duration % 60
}s`;
const browser = replay.browser
? `${replay.browser.name} ${replay.browser.version}`
: "N/A";
output += `ID: ${replay.id}, Project ID: ${replay.project_id}, Started: ${startedDate}, Duration: ${duration}, Browser: ${browser}, Platform: ${replay.platform}, Environment: ${replay.environment}, Errors: ${replay.count_errors}\n`;
}
// Pagination information if cursor is provided
if (cursor) {
output += `\nNote: This is a paginated result. Use the cursor parameter for the next page.\n`;
}
}
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error listing replays: ${error.message}`,
},
],
};
}
}
);
server.tool(
"setup_sentry",
"Set up Sentry for a project returning a dsn and instructions for setup.",
{
organization_slug: z.string().describe("The slug of the organization to create the project in"),
team_slug: z.string().describe("The slug of the team to associate the project with"),
project_name: z.string().describe("The name of the project to create"),
environment: z.string().optional().describe("Optional environment name (e.g., production, staging, development)"),
format: z.enum(["plain", "markdown"]).default("markdown").describe("Output format (default: markdown)")
},
async ({ organization_slug, team_slug, project_name, environment, format }: {
organization_slug: string;
team_slug: string;
project_name: string;
environment?: string;
format: "plain" | "markdown"
}) => {
try {
// Debug input
console.error('DEBUG: Setting up Sentry for project:', project_name);
console.error('DEBUG: In organization:', organization_slug);
console.error('DEBUG: For team:', team_slug);
console.error('DEBUG: Environment:', environment);
// Step 1: Create the project
const createProjectUrl = `https://sentry.io/api/0/teams/${organization_slug}/${team_slug}/projects/`;
const createProjectResponse = await fetch(createProjectUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${SENTRY_AUTH}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: project_name
})
});
if (!createProjectResponse.ok) {
const errorText = await createProjectResponse.text();
console.error('DEBUG: Project creation failed:', createProjectResponse.status, errorText);
return {
content: [{
type: "text",
text: `Failed to create Sentry project: ${createProjectResponse.status} ${createProjectResponse.statusText}\n${errorText}`
}],
isError: true
};
}
const projectData: SentryProjectCreationResponse = await createProjectResponse.json();
console.error('DEBUG: Project created:', JSON.stringify(projectData, null, 2));
// Step 2: Get the client keys (DSN)
const clientKeysUrl = `https://sentry.io/api/0/projects/${organization_slug}/${projectData.slug}/keys/`;
const clientKeysResponse = await fetch(clientKeysUrl, {
method: 'GET',
headers: {
'Authorization': `Bearer ${SENTRY_AUTH}`,
'Content-Type': 'application/json'
}
});
if (!clientKeysResponse.ok) {
const errorText = await clientKeysResponse.text();
console.error('DEBUG: Client keys fetch failed:', clientKeysResponse.status, errorText);
return {
content: [{
type: "text",
text: `Failed to fetch client keys: ${clientKeysResponse.status} ${clientKeysResponse.statusText}\n${errorText}`
}],
isError: true
};
}
const clientKeys: SentryClientKey[] = await clientKeysResponse.json();
console.error('DEBUG: Client keys fetched:', JSON.stringify(clientKeys, null, 2));
if (clientKeys.length === 0) {
return {
content: [{
type: "text",
text: `No client keys found for the project. Please check the project settings.`
}],
isError: true
};
}
const dsn = clientKeys[0].dsn.public;
// Prepare setup response
const setupResponse: SentrySetupResponse = {
projectName: projectData.name,
projectSlug: projectData.slug,
projectId: projectData.id,
dsn: dsn,
installationInstructions: {
generic: `Sentry.init({
dsn: "${dsn}",
${environment ? `environment: "${environment}",` : ''}
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring
tracesSampleRate: 1.0,
});`
}
};
// Format the output
let output = '';
if (format === 'markdown') {
output = `# Sentry Project Setup: ${setupResponse.projectName}\n\n`;
output += `## Project Information\n\n`;
output += `- **Project Name**: ${setupResponse.projectName}\n`;
output += `- **Project Slug**: ${setupResponse.projectSlug}\n`;
if (environment) {
output += `- **Environment**: ${environment}\n`;
}
output += `- **DSN**: \`${setupResponse.dsn}\`\n\n`;
output += `## Installation Instructions\n\n`;
output += `## Next Steps\n\n`;
output += `1. Choose the appropriate SDK for your platform and follow the installation instructions above.\n`;
output += `2. Configure additional options as needed for your specific use case.\n`;
output += `3. Test your integration by triggering a test event.\n`;
output += `4. Visit your [Sentry dashboard](https://sentry.io/organizations/${organization_slug}/issues/) to view and manage your errors.\n`;
} else {
// Plain text format
output = `Sentry Project Setup: ${setupResponse.projectName}\n\n`;
output += `Project Information:\n`;
output += `- Project Name: ${setupResponse.projectName}\n`;
output += `- Project Slug: ${setupResponse.projectSlug}\n`;
if (environment) {
output += `- Environment: ${environment}\n`;
}
output += `- DSN: ${setupResponse.dsn}\n\n`;
output += `Installation Instructions:\n\n`;
output += `Generic Setup:\n`;
output += setupResponse.installationInstructions.generic.trim();
output += "\n\n";
output += `Next Steps:\n`;
output += `1. Choose the appropriate SDK for your platform and follow the installation instructions above.\n`;
output += `2. Configure additional options as needed for your specific use case.\n`;
output += `3. Test your integration by triggering a test event.\n`;
output += `4. Visit your Sentry dashboard to view and manage your errors: https://sentry.io/organizations/${organization_slug}/issues/\n`;
}
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error: any) {
console.error("DEBUG: Caught error:", error);
return {
content: [
{
type: "text",
text: `Error listing replays: ${error.message}`,
},
],
};
}
}
);
async function main(): Promise<void> {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Sentry MCP Server running");
} catch (error: any) {
throw error;
}
}
main().catch((error: Error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});