Skip to main content
Glama

Sentry MCP

Official
by getsentry
permissions.ts7.05 kB
/** * OAuth-style scope system for Sentry MCP Server * * Defines scopes for access control with hierarchical permissions. * Higher scopes include lower ones (e.g., write includes read). */ /** * Available scopes in the MCP server * These align with Sentry's API scopes where possible */ export type Scope = | "org:read" // Read organization information | "org:write" // Write organization information (includes read) | "org:admin" // Admin organization (includes write and read) | "project:read" // Read project information | "project:write" // Create/update projects (includes read) | "project:admin" // Delete projects (includes write and read) | "team:read" // Read team information | "team:write" // Create/update teams (includes read) | "team:admin" // Delete teams (includes write and read) | "member:read" // Read member information | "member:write" // Create/update members (includes read) | "member:admin" // Delete members (includes write and read) | "event:read" // Read events and issues | "event:write" // Update issues (includes read) | "event:admin" // Delete issues (includes write and read) | "project:releases" // Access release endpoints | "seer"; // Virtual scope: Use Seer analysis (not respected by upstream Sentry API) /** * Scope hierarchy - higher scopes include lower ones */ const SCOPE_HIERARCHY: Record<Scope, Set<Scope>> = { // Organization scopes "org:read": new Set(["org:read"]), "org:write": new Set(["org:read", "org:write"]), "org:admin": new Set(["org:read", "org:write", "org:admin"]), // Project scopes "project:read": new Set(["project:read"]), "project:write": new Set(["project:read", "project:write"]), "project:admin": new Set(["project:read", "project:write", "project:admin"]), // Team scopes "team:read": new Set(["team:read"]), "team:write": new Set(["team:read", "team:write"]), "team:admin": new Set(["team:read", "team:write", "team:admin"]), // Member scopes "member:read": new Set(["member:read"]), "member:write": new Set(["member:read", "member:write"]), "member:admin": new Set(["member:read", "member:write", "member:admin"]), // Event scopes "event:read": new Set(["event:read"]), "event:write": new Set(["event:read", "event:write"]), "event:admin": new Set(["event:read", "event:write", "event:admin"]), // Special scopes "project:releases": new Set(["project:releases"]), // Virtual scopes (not respected by upstream Sentry API) seer: new Set(["seer"]), }; /** * All available scopes as a readonly list */ export function getAvailableScopes(): ReadonlyArray<Scope> { return Object.keys(SCOPE_HIERARCHY) as ReadonlyArray<Scope>; } /** * All scopes available in the server, generated from the permission hierarchy. * Exported here to keep scope consumers lightweight and avoid importing other * unrelated constants. */ export const ALL_SCOPES: ReadonlyArray<Scope> = getAvailableScopes(); // Fast lookup set for validations export const ALL_SCOPES_SET = new Set<Scope>(ALL_SCOPES); /** * Expand a set of granted scopes to include all implied scopes */ export function expandScopes(grantedScopes: Set<Scope>): Set<Scope> { const expandedScopes = new Set<Scope>(); for (const scope of grantedScopes) { const implied = SCOPE_HIERARCHY[scope]; for (const s of implied) { expandedScopes.add(s); } } return expandedScopes; } /** * Human-readable descriptions of scopes */ export const SCOPE_DESCRIPTIONS: Record<Scope, string> = { "org:read": "View organization details", "org:write": "Modify organization details", "org:admin": "Delete organizations", "project:read": "View project information", "project:write": "Create and modify projects", "project:admin": "Delete projects", "team:read": "View team information", "team:write": "Create and modify teams", "team:admin": "Delete teams", "member:read": "View member information", "member:write": "Create and modify members", "member:admin": "Delete members", "event:read": "View events and issues", "event:write": "Update and manage issues", "event:admin": "Delete issues", "project:releases": "Access release information", seer: "Use Seer for issue analysis (may incur costs)", }; /** * Check if a set of scopes satisfies the required scopes */ export function hasRequiredScopes( grantedScopes: Set<Scope>, requiredScopes: Scope[], ): boolean { // Expand granted scopes to include implied scopes const expandedScopes = expandScopes(grantedScopes); return requiredScopes.every((scope) => expandedScopes.has(scope)); } /** * Check if a tool is allowed based on granted scopes */ export function isToolAllowed( requiredScopes: Scope[] | undefined, grantedScopes: Set<Scope>, ): boolean { // If no scopes are required, tool is always allowed if (!requiredScopes || requiredScopes.length === 0) { return true; } return hasRequiredScopes(grantedScopes, requiredScopes); } /** * Parse scopes from a comma-separated string */ /** * Parse scopes from a comma-separated string. * - Filters out invalid entries * - Logs a console.warn listing any invalid values */ /** * Parse scopes from an array of strings. * - Filters out invalid entries * - Logs a console.warn listing any invalid values */ /** * Generic scope parser: accepts a comma-separated string or an array. * Returns both valid and invalid tokens. No logging. */ export function parseScopes(input: unknown): { valid: Set<Scope>; invalid: string[]; } { let tokens: string[] = []; if (typeof input === "string") { tokens = input.split(","); } else if (Array.isArray(input)) { tokens = input.map((v) => (typeof v === "string" ? v : "")); } const valid = new Set<Scope>(); const invalid: string[] = []; for (const raw of tokens.map((s) => s.trim()).filter(Boolean)) { if (ALL_SCOPES_SET.has(raw as Scope)) { valid.add(raw as Scope); } else { invalid.push(raw); } } return { valid, invalid }; } /** * Strict validation helper for scope strings supplied via flags/env. * Returns both valid and invalid entries without side effects. */ // Deprecated: use parseScopes(scopesString) // export function validateScopes(scopesString: string): { valid: Set<Scope>; invalid: string[] } { ... } /** * Resolve final scopes from optional override/additive sets and provided defaults. * - If override is provided, it replaces defaults and is expanded * - Else if add is provided, it unions with defaults and is expanded * - Else returns undefined to indicate default handling upstream */ export function resolveScopes(options: { override?: Set<Scope>; add?: Set<Scope>; defaults: ReadonlyArray<Scope>; }): Set<Scope> | undefined { const { override, add, defaults } = options; if (override) { return expandScopes(override); } if (add) { const base = new Set<Scope>(defaults as ReadonlyArray<Scope>); for (const s of add) base.add(s); return expandScopes(base); } return undefined; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/getsentry/sentry-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server