Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/contributing/api-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,12 @@ fail to resolve for legacy mixed-case project slugs; keep them for display.

Organization discovery uses `/api/0/organizations/`; a limit over 100 follows
bounded pages through Sentry's Link cursors. Organization, team, and project
lists use `@sentry/api` operations to build GET paths and queries. The MCP
client still owns the request transport, token handling, retries, error
types, and Zod response validation. Public SaaS hosts use `sentry.io` to list
organizations across regions. Single-tenant hosts under `*.my.sentry.io` and
self-hosted instances keep their configured host for this request.
lists and most issue and event reads use `@sentry/api` operations to build GET
paths and queries. The MCP client still owns the request transport, token
handling, retries, error types, and Zod response validation. Public SaaS
hosts use `sentry.io` to list organizations across regions. Single-tenant
hosts under `*.my.sentry.io` and self-hosted instances keep their configured
host for this request.

User identity (`/api/0/auth/`, used by `whoami`) follows the same control-host
routing. Organization-scoped requests continue to use the configured host or
Expand Down
31 changes: 31 additions & 0 deletions packages/mcp-core/src/api-client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "@sentry/mcp-server-mocks";
import { HttpResponse, http } from "msw";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_SEARCH_ISSUES_PERIOD } from "../constants";
import { ConfigurationError } from "../errors";
import { parseSentryUrl } from "../internal/url-helpers";
import { SentryApiService } from "./client";
Expand Down Expand Up @@ -2043,6 +2044,36 @@ describe("organization SDK list pages", () => {
});
});

describe("listIssues request", () => {
afterEach(() => mswServer.resetHandlers());

it("sends an empty query so Sentry does not apply is:unresolved", async () => {
let params = new URLSearchParams();
mswServer.use(
http.get(
"https://sentry.io/api/0/organizations/test-org/issues/",
({ request }) => {
params = new URL(request.url).searchParams;
return HttpResponse.json([]);
},
),
);

await new SentryApiService({ accessToken: "test-token" }).listIssues({
organizationSlug: "test-org",
projectId: "123",
sortBy: "freq",
});

expect(params.get("query")).toBe("");
expect(params.get("limit")).toBe("10");
expect(params.get("sort")).toBe("freq");
expect(params.get("statsPeriod")).toBe(DEFAULT_SEARCH_ISSUES_PERIOD);
expect(params.getAll("project")).toEqual(["123"]);
expect(params.getAll("collapse")).toEqual(["unhandled"]);
});
});

describe("host configuration", () => {
it("should handle hostname without protocol", () => {
const apiService = new SentryApiService({ host: "sentry.io" });
Expand Down
Loading
Loading