diff --git a/package.json b/package.json
index 67e804572..9cbacd85b 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
"author": "Sentry",
"description": "Sentry MCP Server",
"homepage": "https://github.com/getsentry/sentry-mcp",
- "keywords": ["sentry"],
+ "keywords": [
+ "sentry"
+ ],
"bugs": {
"url": "https://github.com/getsentry/sentry-mcp/issues"
},
diff --git a/packages/mcp-cloudflare/package.json b/packages/mcp-cloudflare/package.json
index 97a33fbde..6995d7fc8 100644
--- a/packages/mcp-cloudflare/package.json
+++ b/packages/mcp-cloudflare/package.json
@@ -4,7 +4,9 @@
"private": true,
"type": "module",
"license": "FSL-1.1-ALv2",
- "files": ["./dist/*"],
+ "files": [
+ "./dist/*"
+ ],
"exports": {
".": {
"types": "./dist/index.d.ts",
diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts
index af97f1c6d..0f0432458 100644
--- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts
+++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts
@@ -313,7 +313,6 @@ async function handleAuthenticatedMcpRequest(
const organizationSlug = groups?.org || null;
const projectSlug = groups?.project || null;
-
// Check for experimental mode query parameter
const isExperimentalMode = url.searchParams.get("experimental") === "1";
@@ -451,7 +450,7 @@ async function handleAuthenticatedMcpRequest(
() =>
buildServer({
context: serverContext,
- experimentalMode: isExperimentalMode,
+ experimentalMode: isExperimentalMode,
sdkVersion: "v2",
}),
{
diff --git a/packages/mcp-core/package.json b/packages/mcp-core/package.json
index ec7ae7080..5812170be 100644
--- a/packages/mcp-core/package.json
+++ b/packages/mcp-core/package.json
@@ -11,7 +11,9 @@
"author": "Sentry",
"description": "Sentry MCP Core - Shared code for MCP transports",
"homepage": "https://github.com/getsentry/sentry-mcp",
- "keywords": ["sentry"],
+ "keywords": [
+ "sentry"
+ ],
"bugs": {
"url": "https://github.com/getsentry/sentry-mcp/issues"
},
@@ -19,7 +21,9 @@
"type": "git",
"url": "git@github.com:getsentry/sentry-mcp.git"
},
- "files": ["./dist/*"],
+ "files": [
+ "./dist/*"
+ ],
"exports": {
"./api-client": {
"types": "./dist/api-client/index.d.ts",
diff --git a/packages/mcp-core/src/api-client/client.test.ts b/packages/mcp-core/src/api-client/client.test.ts
index cea8ae942..264792d58 100644
--- a/packages/mcp-core/src/api-client/client.test.ts
+++ b/packages/mcp-core/src/api-client/client.test.ts
@@ -818,36 +818,19 @@ describe("listOrganizations", () => {
globalThis.fetch = originalFetch;
});
- it("should fetch from regions endpoint for SaaS", async () => {
- const mockRegionsResponse = {
- regions: [
- { name: "US", url: "https://us.sentry.io" },
- { name: "EU", url: "https://eu.sentry.io" },
- ],
- };
-
- const mockOrgsUs = [{ id: "1", slug: "org-us", name: "Org US" }];
- const mockOrgsEu = [{ id: "2", slug: "org-eu", name: "Org EU" }];
+ it("should fetch from the organizations endpoint on the root host for SaaS", async () => {
+ const mockOrgs = [
+ { id: "1", slug: "org-us", name: "Org US" },
+ { id: "2", slug: "org-eu", name: "Org EU" },
+ ];
let callCount = 0;
globalThis.fetch = vi.fn().mockImplementation((url: string) => {
callCount++;
- if (url.includes("/users/me/regions/")) {
- return Promise.resolve({
- ok: true,
- json: () => Promise.resolve(mockRegionsResponse),
- });
- }
- if (url.includes("us.sentry.io")) {
- return Promise.resolve({
- ok: true,
- json: () => Promise.resolve(mockOrgsUs),
- });
- }
- if (url.includes("eu.sentry.io")) {
+ if (url.includes("/organizations/")) {
return Promise.resolve({
ok: true,
- json: () => Promise.resolve(mockOrgsEu),
+ json: () => Promise.resolve(mockOrgs),
});
}
return Promise.reject(new Error("Unexpected URL"));
@@ -860,10 +843,15 @@ describe("listOrganizations", () => {
const result = await apiService.listOrganizations();
- expect(callCount).toBe(3); // 1 regions call + 2 org calls
+ expect(callCount).toBe(1); // Single call, no region fanout
expect(result).toHaveLength(2);
expect(result).toContainEqual(expect.objectContaining({ slug: "org-us" }));
expect(result).toContainEqual(expect.objectContaining({ slug: "org-eu" }));
+ // Region fanout is no longer used
+ expect(globalThis.fetch).not.toHaveBeenCalledWith(
+ expect.stringContaining("/users/me/regions/"),
+ expect.any(Object),
+ );
});
it("should fetch directly from organizations endpoint for self-hosted", async () => {
@@ -900,51 +888,6 @@ describe("listOrganizations", () => {
expect.any(Object),
);
});
-
- it("should fall back to direct organizations endpoint when regions endpoint returns 404 on SaaS", async () => {
- const mockOrgs = [
- { id: "1", slug: "org-1", name: "Organization 1" },
- { id: "2", slug: "org-2", name: "Organization 2" },
- ];
-
- globalThis.fetch = vi.fn().mockImplementation((url: string) => {
- if (url.includes("/users/me/regions/")) {
- return Promise.resolve({
- ok: false,
- status: 404,
- statusText: "Not Found",
- text: () => Promise.resolve(JSON.stringify({ detail: "Not found" })),
- });
- }
- if (url.includes("/organizations/")) {
- return Promise.resolve({
- ok: true,
- json: () => Promise.resolve(mockOrgs),
- });
- }
- return Promise.reject(new Error("Unexpected URL"));
- });
-
- const apiService = new SentryApiService({
- host: "sentry.io",
- accessToken: "test-token",
- });
-
- const result = await apiService.listOrganizations();
-
- expect(result).toHaveLength(2);
- expect(result).toEqual(mockOrgs);
-
- // Verify it tried regions first, then fell back to organizations
- expect(globalThis.fetch).toHaveBeenCalledWith(
- expect.stringContaining("/users/me/regions/"),
- expect.any(Object),
- );
- expect(globalThis.fetch).toHaveBeenCalledWith(
- expect.stringContaining("/organizations/"),
- expect.any(Object),
- );
- });
});
describe("host configuration", () => {
@@ -1136,7 +1079,7 @@ describe("Content-Type validation", () => {
);
});
- it("should handle HTML response from regions endpoint", async () => {
+ it("should handle HTML response from organizations endpoint", async () => {
const htmlContent = `
Login Required
diff --git a/packages/mcp-core/src/api-client/client.ts b/packages/mcp-core/src/api-client/client.ts
index dd5a7df2a..c11f3d4c3 100644
--- a/packages/mcp-core/src/api-client/client.ts
+++ b/packages/mcp-core/src/api-client/client.ts
@@ -66,7 +66,6 @@ import {
TraceMetaSchema,
TraceSchema,
UserSchema,
- UserRegionsSchema,
IssueAlertRuleListSchema,
MetricAlertRuleListSchema,
MetricAlertRuleSchema,
@@ -1498,13 +1497,11 @@ export class SentryApiService {
/**
* Lists all organizations accessible to the authenticated user.
*
- * Automatically handles multi-region queries by fetching from all
- * available regions and combining results.
+ * Queries the `/organizations/` endpoint on the root host.
*
* @param params Query parameters
* @param params.query Search query to filter organizations by name/slug
* @param params.limit Maximum number of organizations to return (defaults to 25)
- * @param opts Request options
* @returns Array of organizations across all accessible regions
*
* @example
@@ -1516,10 +1513,10 @@ export class SentryApiService {
* });
* ```
*/
- async listOrganizations(
- params?: { query?: string; limit?: number },
- opts?: RequestOptions,
- ): Promise {
+ async listOrganizations(params?: {
+ query?: string;
+ limit?: number;
+ }): Promise {
const limit = params?.limit ?? 25;
// Build query parameters
@@ -1531,50 +1528,14 @@ export class SentryApiService {
const queryString = queryParams.toString();
const path = `/organizations/?${queryString}`;
- // For self-hosted instances, the regions endpoint doesn't exist
- if (!this.isSaas()) {
- const body = await this.requestJSON(path, undefined, opts);
- return OrganizationListSchema.parse(body);
+ let host = undefined;
+ // For SaaS, always use the main sentry.io host, not regional hosts
+ if (this.isSaas()) {
+ host = "sentry.io";
}
- // For SaaS, try to use regions endpoint first
- try {
- // TODO: Sentry is currently not returning all orgs without hitting region endpoints
- // The regions endpoint only exists on the main API server, not on regional endpoints
- const regionsBody = await this.requestJSON(
- "/users/me/regions/",
- undefined,
- {}, // Don't pass opts to ensure we use the main host
- );
- const regionData = UserRegionsSchema.parse(regionsBody);
-
- const allOrganizations = (
- await Promise.all(
- regionData.regions.map(async (region) =>
- this.requestJSON(path, undefined, {
- ...opts,
- host: new URL(region.url).host,
- }),
- ),
- )
- )
- .map((data) => OrganizationListSchema.parse(data))
- .reduce((acc, curr) => acc.concat(curr), []);
-
- // Apply the limit after combining results from all regions
- return allOrganizations.slice(0, limit);
- } catch (error) {
- // If regions endpoint fails (e.g., older self-hosted versions identifying as sentry.io),
- // fall back to direct organizations endpoint
- if (error instanceof ApiNotFoundError) {
- // logger.info("Regions endpoint not found, falling back to direct organizations endpoint");
- const body = await this.requestJSON(path, undefined, opts);
- return OrganizationListSchema.parse(body);
- }
-
- // Re-throw other errors
- throw error;
- }
+ const body = await this.requestJSON(path, undefined, { host });
+ return OrganizationListSchema.parse(body);
}
/**
diff --git a/packages/mcp-core/src/api-client/schema.ts b/packages/mcp-core/src/api-client/schema.ts
index 8a370f18a..8e54c64c7 100644
--- a/packages/mcp-core/src/api-client/schema.ts
+++ b/packages/mcp-core/src/api-client/schema.ts
@@ -57,15 +57,6 @@ export const UserSchema = z
})
.passthrough();
-export const UserRegionsSchema = z.object({
- regions: z.array(
- z.object({
- name: z.string(),
- url: z.string().url(),
- }),
- ),
-});
-
/**
* Schema for Sentry organization API responses.
*
diff --git a/packages/mcp-core/src/tools/catalog-runtime/availability.ts b/packages/mcp-core/src/tools/catalog-runtime/availability.ts
index 930f27da1..9d04931cb 100644
--- a/packages/mcp-core/src/tools/catalog-runtime/availability.ts
+++ b/packages/mcp-core/src/tools/catalog-runtime/availability.ts
@@ -90,7 +90,6 @@ function isAllowedBySkills({
tool: ToolConfig;
context: ServerContext;
}): boolean {
-
const grantedSkills: Set | undefined = context.grantedSkills
? new Set(context.grantedSkills)
: undefined;
@@ -202,7 +201,6 @@ export function getToolsForMcpRegistration({
useDefaultSurfacePolicy,
});
-
return availableTools.filter(({ isTopLevel }) => isTopLevel);
}
diff --git a/packages/mcp-core/src/tools/catalog/find-organizations.test.ts b/packages/mcp-core/src/tools/catalog/find-organizations.test.ts
index d8f23f82d..8c5e84dd3 100644
--- a/packages/mcp-core/src/tools/catalog/find-organizations.test.ts
+++ b/packages/mcp-core/src/tools/catalog/find-organizations.test.ts
@@ -11,12 +11,7 @@ import findOrganizations from "./find-organizations.js";
function mockOrganizations(organizations: unknown[]) {
mswServer.use(
- http.get("https://sentry.io/api/0/users/me/regions/", () =>
- HttpResponse.json({
- regions: [{ name: "us", url: "https://us.sentry.io" }],
- }),
- ),
- http.get("https://us.sentry.io/api/0/organizations/", ({ request }) => {
+ http.get("https://sentry.io/api/0/organizations/", ({ request }) => {
expect(new URL(request.url).searchParams.get("per_page")).toBe("26");
return HttpResponse.json(organizations);
}),
diff --git a/packages/mcp-core/src/tools/catalog/find-organizations.ts b/packages/mcp-core/src/tools/catalog/find-organizations.ts
index d65e6d765..de916db03 100644
--- a/packages/mcp-core/src/tools/catalog/find-organizations.ts
+++ b/packages/mcp-core/src/tools/catalog/find-organizations.ts
@@ -47,8 +47,8 @@ export default defineTool({
},
outputSchema: findOrganizationsOutputSchema,
async handler(params, context: ServerContext) {
- // User data endpoints (like /users/me/regions/) should never use regionUrl
- // as they must always query the main API server, not region-specific servers
+ // Organizations are listed from the root host, which returns orgs across
+ // all regions, so no regionUrl is passed here.
const apiService = apiServiceFromContext(context);
const organizations = await apiService.listOrganizations({
query: params.query ?? undefined,
diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts
index 32b75b698..bfc59b353 100644
--- a/packages/mcp-server/src/index.ts
+++ b/packages/mcp-server/src/index.ts
@@ -304,7 +304,6 @@ async function main() {
(process.env.NODE_ENV !== "production" ? "development" : "production"),
});
-
// Log experimental mode status
if (cli.experimental) {
console.warn(
diff --git a/packages/mcp-test-client/src/mcp-url.ts b/packages/mcp-test-client/src/mcp-url.ts
index 14802fd07..856921a43 100644
--- a/packages/mcp-test-client/src/mcp-url.ts
+++ b/packages/mcp-test-client/src/mcp-url.ts
@@ -40,7 +40,6 @@ export function applyProtectedResourceFlags(
useExperimental?: boolean;
},
): URL {
-
if (options.useExperimental) {
protectedResourceUrl.searchParams.set("experimental", "1");
}