From e78dbdc1f7b35323c8c997055a4115a174f9a50c Mon Sep 17 00:00:00 2001 From: Nagharjun Mathi Mariappan <64778259+Nagharjun17@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:09:53 -0400 Subject: [PATCH 1/2] Add Sentry adapter with four read-only tools --- packages/backend/src/adapters/catalog.ts | 2 + .../backend/src/adapters/intl/sentry.json | 174 ++++++++++++++++++ .../src/adapters/intl/sentry.live.spec.ts | 155 ++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 packages/backend/src/adapters/intl/sentry.json create mode 100644 packages/backend/src/adapters/intl/sentry.live.spec.ts diff --git a/packages/backend/src/adapters/catalog.ts b/packages/backend/src/adapters/catalog.ts index 9527f4b2..868e331e 100644 --- a/packages/backend/src/adapters/catalog.ts +++ b/packages/backend/src/adapters/catalog.ts @@ -150,6 +150,7 @@ import * as sapConcur from './intl/sap-concur.json'; import * as sapS4hanaCloud from './intl/sap-s4hana-cloud.json'; import * as savvycal from './intl/savvycal.json'; import * as sendgrid from './intl/sendgrid.json'; +import * as sentry from './intl/sentry.json'; import * as signwell from './intl/signwell.json'; import * as slab from './intl/slab.json'; import * as snov from './intl/snov.json'; @@ -444,6 +445,7 @@ const RAW_ADAPTERS: AdapterDefinition[] = [ sapS4hanaCloud as unknown as AdapterDefinition, savvycal as unknown as AdapterDefinition, sendgrid as unknown as AdapterDefinition, + sentry as unknown as AdapterDefinition, signwell as unknown as AdapterDefinition, slab as unknown as AdapterDefinition, snov as unknown as AdapterDefinition, diff --git a/packages/backend/src/adapters/intl/sentry.json b/packages/backend/src/adapters/intl/sentry.json new file mode 100644 index 00000000..fcf9df94 --- /dev/null +++ b/packages/backend/src/adapters/intl/sentry.json @@ -0,0 +1,174 @@ +{ + "slug": "sentry", + "name": "Sentry", + "description": "Inspect Sentry projects, issues, and error events.", + "instructions": "Connect to Sentry's hosted REST API to investigate projects, issues, and error events using four read-only tools.\n\nSetup: Create a Sentry personal authentication token under User settings > Personal Tokens with org:read and event:read scopes. Supply it as SENTRY_AUTH_TOKEN when configuring this connector. org:read permits listing organization projects; event:read permits reading issues and their events. Access remains limited by the token owner's permissions. Authentication uses the Authorization: Bearer header. This adapter targets https://sentry.io/api/0.\n\nWorkflow: Supply your organization ID or URL slug, such as acme. Use sentry_list_projects to discover project IDs and slugs, then sentry_list_issues to find issues. Pass the returned issue id, rather than shortId, to sentry_get_issue or sentry_list_issue_events. An issue groups related errors; events are individual occurrences. Set full=true when listing events to include full event bodies and stack traces; Sentry then caps the page size at 10.\n\nFiltering: sentry_list_issues defaults to unresolved issues when query is omitted. Use project to select one project and statsPeriod to narrow the time range, for example 24h or 14d.\n\nPagination: List tools return one page. Sentry supplies pagination cursors in HTTP Link headers, but AnythingMCP's REST engine returns only the response body. These tools cannot discover the next cursor. The cursor input accepts an externally obtained value; do not invent cursors or assume a returned page contains every result.", + "region": "intl", + "category": "monitoring", + "icon": "sentry", + "docsUrl": "https://docs.sentry.io/api/", + "requiredEnvVars": ["SENTRY_AUTH_TOKEN"], + "connector": { + "name": "Sentry API", + "type": "REST", + "baseUrl": "https://sentry.io/api/0", + "authType": "BEARER_TOKEN", + "authConfig": { + "token": "{{SENTRY_AUTH_TOKEN}}" + } + }, + "tools": [ + { + "name": "sentry_list_projects", + "description": "List one page of projects in a Sentry organization. Returns project IDs, slugs, names, and platforms. Optionally filter by project name or slug.", + "parameters": { + "type": "object", + "properties": { + "organization_id_or_slug": { + "type": "string", + "description": "Organization ID or URL slug, for example 'acme'." + }, + "query": { + "type": "string", + "description": "Filter projects by name or slug, for example 'backend'." + }, + "per_page": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Number of projects per page. Sentry defaults to 100; maximum 100." + }, + "cursor": { + "type": "string", + "description": "Externally supplied Sentry pagination cursor. Omit for the first page; this connector does not return pagination headers." + } + }, + "required": ["organization_id_or_slug"] + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{organization_id_or_slug}/projects/", + "queryParams": { + "query": "$query", + "per_page": "$per_page", + "cursor": "$cursor" + } + } + }, + { + "name": "sentry_list_issues", + "description": "List one page of issues in a Sentry organization. Returns issue IDs, titles, statuses, and occurrence counts. Use an issue ID with sentry_get_issue to investigate further.", + "parameters": { + "type": "object", + "properties": { + "organization_id_or_slug": { + "type": "string", + "description": "Organization ID or URL slug, for example 'acme'." + }, + "project": { + "type": "string", + "description": "Filter to one project ID or slug from sentry_list_projects. Omit for all accessible projects." + }, + "query": { + "type": "string", + "description": "Sentry issue search, for example 'is:unresolved'. Omit to use Sentry's default unresolved-issue filter." + }, + "statsPeriod": { + "type": "string", + "description": "Relative time range, for example '24h' or '14d'." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Maximum number of issues to return, up to 100." + }, + "cursor": { + "type": "string", + "description": "Externally supplied Sentry pagination cursor. Omit for the first page; this connector does not return pagination headers." + } + }, + "required": ["organization_id_or_slug"] + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{organization_id_or_slug}/issues/", + "queryParams": { + "project": "$project", + "query": "$query", + "statsPeriod": "$statsPeriod", + "limit": "$limit", + "cursor": "$cursor" + } + } + }, + { + "name": "sentry_get_issue", + "description": "Retrieve details for a Sentry issue, including status, occurrence statistics, and a summary of the latest event.", + "parameters": { + "type": "object", + "properties": { + "organization_id_or_slug": { + "type": "string", + "description": "Organization ID or URL slug, for example 'acme'." + }, + "issue_id": { + "type": "string", + "description": "Issue ID from the 'id' field returned by sentry_list_issues, for example '123456'. Use the ID, not the shortId such as 'BACKEND-42'." + } + }, + "required": ["organization_id_or_slug", "issue_id"] + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{organization_id_or_slug}/issues/{issue_id}/" + } + }, + { + "name": "sentry_list_issue_events", + "description": "List one page of error events belonging to a Sentry issue. Set full to true to include full event bodies and stack traces.", + "parameters": { + "type": "object", + "properties": { + "organization_id_or_slug": { + "type": "string", + "description": "Organization ID or URL slug, for example 'acme'." + }, + "issue_id": { + "type": "string", + "description": "Issue ID from sentry_list_issues, for example '123456'." + }, + "statsPeriod": { + "type": "string", + "description": "Relative time range, for example '24h' or '14d'." + }, + "full": { + "type": "boolean", + "description": "Include full event bodies and stack traces. When true, Sentry caps the page size at 10." + }, + "per_page": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Events per page. Default and maximum 100, capped at 10 when full is true." + }, + "cursor": { + "type": "string", + "description": "Externally supplied Sentry pagination cursor. Omit for the first page; this connector does not return pagination headers." + } + }, + "required": ["organization_id_or_slug", "issue_id"] + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{organization_id_or_slug}/issues/{issue_id}/events/", + "queryParams": { + "statsPeriod": "$statsPeriod", + "full": "$full", + "per_page": "$per_page", + "cursor": "$cursor" + } + } + } + ] +} diff --git a/packages/backend/src/adapters/intl/sentry.live.spec.ts b/packages/backend/src/adapters/intl/sentry.live.spec.ts new file mode 100644 index 00000000..7f766f41 --- /dev/null +++ b/packages/backend/src/adapters/intl/sentry.live.spec.ts @@ -0,0 +1,155 @@ +/** + * Opt-in live tests using a disposable Sentry project. + * + * Required environment variables: + * SENTRY_AUTH_TOKEN — personal token with org:read and event:read + * SENTRY_TEST_ORG — organization ID or slug + * SENTRY_TEST_PROJECT_ID — project containing the test issue + * SENTRY_TEST_ISSUE_ID — unresolved issue in that project + * SENTRY_TEST_EVENT_ID — exception event with a stack trace in that issue + * + * Use a small test project: list assertions inspect only the first page. + * The event must be among the first ten events returned for the issue. + * + * From the repository root: + * RUN_SENTRY_LIVE=1 npm test --workspace=packages/backend -- --runInBand --watch=false adapters/intl/sentry.live.spec.ts + * + * These tests only read existing data; they do not create test fixtures. + */ + +import { AxiosError } from "axios"; +import * as adapter from "./sentry.json"; +import { RestEngine } from "../../connectors/engines/rest.engine"; +import { OAuth2TokenService } from "../../connectors/engines/oauth2-token.service"; +import { LoginTokenService } from "../../connectors/engines/login-token.service"; + +// Normal test runs skip requests to Sentry. +const describeLive = + process.env.RUN_SENTRY_LIVE === "1" ? describe : describe.skip; + +describeLive("Sentry adapter — live API", () => { + const engine = new RestEngine( + {} as OAuth2TokenService, + {} as LoginTokenService, + ); + + beforeAll(() => { + for (const name of [ + "SENTRY_AUTH_TOKEN", + "SENTRY_TEST_ORG", + "SENTRY_TEST_PROJECT_ID", + "SENTRY_TEST_ISSUE_ID", + "SENTRY_TEST_EVENT_ID", + ]) { + if (!process.env[name]?.trim()) { + throw new Error(`Set ${name} before running Sentry live tests`); + } + } + }); + + async function callTool( + name: string, + params: Record, + ): Promise { + const tool = adapter.tools.find((item) => item.name === name); + if (!tool) throw new Error(`Missing adapter tool: ${name}`); + + try { + return await engine.execute( + { + ...adapter.connector, + authConfig: { token: process.env.SENTRY_AUTH_TOKEN }, + }, + tool.endpointMapping, + params, + ); + } catch (error) { + // Axios errors contain request headers, including the real token. + const status = + error instanceof AxiosError ? error.response?.status : undefined; + throw new Error( + `${name} failed: ${status ? `HTTP ${status}` : "request error"}`, + ); + } + } + + it("finds the configured test project", async () => { + const result = await callTool("sentry_list_projects", { + organization_id_or_slug: process.env.SENTRY_TEST_ORG, + per_page: 100, + }); + + expect(Array.isArray(result)).toBe(true); + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: process.env.SENTRY_TEST_PROJECT_ID, + }), + ]), + ); + }, 60000); + + it("finds the configured issue within its project", async () => { + const result = await callTool("sentry_list_issues", { + organization_id_or_slug: process.env.SENTRY_TEST_ORG, + project: process.env.SENTRY_TEST_PROJECT_ID, + query: "is:unresolved", + limit: 100, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: process.env.SENTRY_TEST_ISSUE_ID, + }), + ]), + ); + }, 60000); + + it("retrieves the configured issue and its project", async () => { + const result = await callTool("sentry_get_issue", { + organization_id_or_slug: process.env.SENTRY_TEST_ORG, + issue_id: process.env.SENTRY_TEST_ISSUE_ID, + }); + + expect(result).toEqual( + expect.objectContaining({ + id: process.env.SENTRY_TEST_ISSUE_ID, + project: expect.objectContaining({ + id: process.env.SENTRY_TEST_PROJECT_ID, + }), + }), + ); + }, 60000); + + it("retrieves the sample event with exception details", async () => { + const result = await callTool("sentry_list_issue_events", { + organization_id_or_slug: process.env.SENTRY_TEST_ORG, + issue_id: process.env.SENTRY_TEST_ISSUE_ID, + full: true, + per_page: 10, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + eventID: process.env.SENTRY_TEST_EVENT_ID, + entries: expect.arrayContaining([ + expect.objectContaining({ + type: "exception", + data: expect.objectContaining({ + values: expect.arrayContaining([ + expect.objectContaining({ + stacktrace: expect.objectContaining({ + frames: expect.any(Array), + }), + }), + ]), + }), + }), + ]), + }), + ]), + ); + }, 60000); +}); From 507a00e03c55762ed1922b2caa7de22cf6324bee Mon Sep 17 00:00:00 2001 From: Matteo Date: Thu, 17 Sep 2026 16:09:53 +0200 Subject: [PATCH 2/2] feat(sentry): org as an env var, real pagination, count bumps Finishing @Nagharjun17's adapter so it can go in. Their tools, descriptions and live spec; these are the three changes from the review. - SENTRY_ORG becomes an env var and goes into every path as {{SENTRY_ORG}}, so the model no longer has to know the organization slug on each call and cannot get it wrong. The parameter is gone from all four tools. - The three list tools opt into exposeHeaders, which #558 made possible after this PR was opened. Sentry paginates with a Link header, so each response now carries _pagination and the cursor descriptions say to follow nextCursor until it disappears. Before this, every list tool was exactly one page long, which the instructions had to apologise for. - The quoted adapter count moves 190 to 191 in the seven files the CI gate checks, which a new adapter always has to do. The base URL stays sentry.io rather than becoming {{SENTRY_BASE_URL}} as I first suggested: an optional placeholder that nobody fills in would be sent to the vendor as the literal string, which is a failure mode we hit elsewhere today. Self-hosters edit the connector's base URL after installing, and the instructions now say so. The live spec substitutes {{SENTRY_ORG}} itself, since it drives the engine directly and interpolation happens a layer above. --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/docker-publish.yml | 2 +- CITATION.cff | 2 +- README.md | 8 +-- glama.json | 2 +- package.json | 2 +- .../backend/src/adapters/intl/sentry.json | 69 ++++++++++--------- .../src/adapters/intl/sentry.live.spec.ts | 22 ++++-- server.json | 2 +- 9 files changed, 61 insertions(+), 50 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 633b0458..87242b3c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -5,7 +5,7 @@ contact_links: about: For anything that isn't a clear bug or feature request — please use Discussions so the answer helps everyone else too. - name: 📚 Documentation & Guides url: https://anythingmcp.com - about: Setup guides for all 190 adapters and every AI client, in seven languages. + about: Setup guides for all 191 adapters and every AI client, in seven languages. - name: 🔐 Security vulnerabilities url: https://github.com/HelpCode-ai/anythingmcp/blob/main/SECURITY.md about: Please do NOT open a public issue. Follow the security disclosure policy. diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6cc8c79c..fb4a1697 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -65,7 +65,7 @@ jobs: readme-filepath: ./README.md # Docker Hub caps this at 100 characters and shows it in search # results; the repo's was left at the truncated default. - short-description: "190 connectors + any REST/SOAP/GraphQL/SQL as MCP tools for Claude & ChatGPT. Self-hosted, AGPL." + short-description: "191 connectors + any REST/SOAP/GraphQL/SQL as MCP tools for Claude & ChatGPT. Self-hosted, AGPL." - name: Set Docker Hub categories env: diff --git a/CITATION.cff b/CITATION.cff index 4278f47a..9bf310d1 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,7 +13,7 @@ abstract: > server that exposes REST, SOAP/WSDL and GraphQL APIs, SQL/NoSQL databases and other MCP servers as tools for AI clients such as Claude, ChatGPT, Google Gemini, GitHub Copilot and Cursor, without writing code. It ships - 190 pre-built adapters (Deutsche Bahn, DATEV, weclapp, DHL, Shopware, + 191 pre-built adapters (Deutsche Bahn, DATEV, weclapp, DHL, Shopware, Personio, Handelsregister, etc.), a per-workspace knowledge graph served over MCP, per-tool response mapping, OAuth2/RBAC/SSO/SCIM governance, an audit log and a visual tool editor. diff --git a/README.md b/README.md index f42fd9fa..0f7a2dab 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

- AnythingMCP — 190 connectors, 20 of them with no API key. Your REST, SOAP/WSDL, GraphQL, SQL and MCP systems become tools for Claude, ChatGPT, Copilot and Gemini. + AnythingMCP — 191 connectors, 20 of them with no API key. Your REST, SOAP/WSDL, GraphQL, SQL and MCP systems become tools for Claude, ChatGPT, Copilot and Gemini.

AnythingMCP

Give Claude, ChatGPT and Copilot safe access to the software your company already runs.
- 190 ready adapters, any REST/SOAP/GraphQL/SQL system without code, on your own infrastructure — and it learns how your systems connect. + 191 ready adapters, any REST/SOAP/GraphQL/SQL system without code, on your own infrastructure — and it learns how your systems connect.

@@ -36,7 +36,7 @@ docker compose up -d # → http://localhost:3000 Three words appear throughout and mean three different things: -- an **adapter** is one of the 190 JSON definitions that ship in this repo — DATEV, weclapp, DHL, Deutsche Bahn, Shopware, Personio, Handelsregister and the rest. 20 of them need no API key at all; the others ask for your credentials at import. +- an **adapter** is one of the 191 JSON definitions that ship in this repo — DATEV, weclapp, DHL, Deutsche Bahn, Shopware, Personio, Handelsregister and the rest. 20 of them need no API key at all; the others ask for your credentials at import. - a **connector** is an adapter, or your own OpenAPI spec / Postman collection / WSDL / GraphQL endpoint / database, once you have configured it in your workspace. Anything you can point at, in minutes, without writing an MCP server. - an **MCP server** is the URL you hand to Claude. It exposes the connectors you assign to it, and nothing else. @@ -260,7 +260,7 @@ AI clients speak MCP, but your systems speak REST, SOAP, GraphQL and SQL. Writin ## The adapter catalog -190 adapters, exposing 1,800+ tools. **20 need no API key**; the rest ask for your credentials at import and the tools are available immediately. Every one has a setup guide on [anythingmcp.com/guides](https://anythingmcp.com/guides), in seven languages. +191 adapters, exposing 1,800+ tools. **20 need no API key**; the rest ask for your credentials at import and the tools are available immediately. Every one has a setup guide on [anythingmcp.com/guides](https://anythingmcp.com/guides), in seven languages. | Category | Examples | |---|---| diff --git a/glama.json b/glama.json index dd4813b7..32e23415 100644 --- a/glama.json +++ b/glama.json @@ -3,5 +3,5 @@ "maintainers": [ "keysersoft" ], - "description": "Give Claude, ChatGPT and Copilot safe access to the software your company already runs. 190 pre-built adapters (Deutsche Bahn, DATEV, weclapp, DHL, Shopware, Personio\u2026) plus any REST, SOAP/WSDL, GraphQL or SQL system as MCP tools, no code. Self-hosted, knowledge graph, per-tool response mapping, OAuth2/RBAC/SSO/audit. Open source under AGPL-3.0." + "description": "Give Claude, ChatGPT and Copilot safe access to the software your company already runs. 191 pre-built adapters (Deutsche Bahn, DATEV, weclapp, DHL, Shopware, Personio\u2026) plus any REST, SOAP/WSDL, GraphQL or SQL system as MCP tools, no code. Self-hosted, knowledge graph, per-tool response mapping, OAuth2/RBAC/SSO/audit. Open source under AGPL-3.0." } diff --git a/package.json b/package.json index e83c5c25..aa74e720 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "anythingmcp", "version": "0.9.0", - "description": "Give Claude, ChatGPT and Copilot safe access to the software your company already runs. 190 pre-built adapters plus any REST, SOAP/WSDL, GraphQL or SQL system as MCP tools, no code. Self-hosted, open source (AGPL-3.0).", + "description": "Give Claude, ChatGPT and Copilot safe access to the software your company already runs. 191 pre-built adapters plus any REST, SOAP/WSDL, GraphQL or SQL system as MCP tools, no code. Self-hosted, open source (AGPL-3.0).", "private": true, "license": "AGPL-3.0-only", "engines": { diff --git a/packages/backend/src/adapters/intl/sentry.json b/packages/backend/src/adapters/intl/sentry.json index fcf9df94..88e2effe 100644 --- a/packages/backend/src/adapters/intl/sentry.json +++ b/packages/backend/src/adapters/intl/sentry.json @@ -2,12 +2,15 @@ "slug": "sentry", "name": "Sentry", "description": "Inspect Sentry projects, issues, and error events.", - "instructions": "Connect to Sentry's hosted REST API to investigate projects, issues, and error events using four read-only tools.\n\nSetup: Create a Sentry personal authentication token under User settings > Personal Tokens with org:read and event:read scopes. Supply it as SENTRY_AUTH_TOKEN when configuring this connector. org:read permits listing organization projects; event:read permits reading issues and their events. Access remains limited by the token owner's permissions. Authentication uses the Authorization: Bearer header. This adapter targets https://sentry.io/api/0.\n\nWorkflow: Supply your organization ID or URL slug, such as acme. Use sentry_list_projects to discover project IDs and slugs, then sentry_list_issues to find issues. Pass the returned issue id, rather than shortId, to sentry_get_issue or sentry_list_issue_events. An issue groups related errors; events are individual occurrences. Set full=true when listing events to include full event bodies and stack traces; Sentry then caps the page size at 10.\n\nFiltering: sentry_list_issues defaults to unresolved issues when query is omitted. Use project to select one project and statsPeriod to narrow the time range, for example 24h or 14d.\n\nPagination: List tools return one page. Sentry supplies pagination cursors in HTTP Link headers, but AnythingMCP's REST engine returns only the response body. These tools cannot discover the next cursor. The cursor input accepts an externally obtained value; do not invent cursors or assume a returned page contains every result.", + "instructions": "Connect to Sentry's hosted REST API to investigate projects, issues, and error events using four read-only tools.\n\nSetup: Create a Sentry personal authentication token under User settings > Personal Tokens with org:read and event:read scopes. Supply it as SENTRY_AUTH_TOKEN, and your organization ID or URL slug (the `acme` in sentry.io/organizations/acme) as SENTRY_ORG, when configuring this connector. The organization goes into every path for you, so no tool asks the model for it. org:read permits listing organization projects; event:read permits reading issues and their events. Access remains limited by the token owner's permissions. Authentication uses the Authorization: Bearer header. This adapter targets https://sentry.io/api/0.\n\nWorkflow: Use sentry_list_projects to discover project IDs and slugs, then sentry_list_issues to find issues. Pass the returned issue id, rather than shortId, to sentry_get_issue or sentry_list_issue_events. An issue groups related errors; events are individual occurrences. Set full=true when listing events to include full event bodies and stack traces; Sentry then caps the page size at 10.\n\nFiltering: sentry_list_issues defaults to unresolved issues when query is omitted. Use project to select one project and statsPeriod to narrow the time range, for example 24h or 14d.\n\nPagination: the list tools ask for Sentry's Link header, so each response carries `_pagination` next to the body. Call again with cursor = `_pagination.nextCursor` and repeat until `_pagination` is absent, which means that was the last page. Never invent a cursor value.\n\nSelf-hosted Sentry: this adapter targets sentry.io. If you run your own Sentry, change the connector's base URL to your instance's /api/0 after installing; everything else is identical.", "region": "intl", "category": "monitoring", "icon": "sentry", "docsUrl": "https://docs.sentry.io/api/", - "requiredEnvVars": ["SENTRY_AUTH_TOKEN"], + "requiredEnvVars": [ + "SENTRY_AUTH_TOKEN", + "SENTRY_ORG" + ], "connector": { "name": "Sentry API", "type": "REST", @@ -24,10 +27,6 @@ "parameters": { "type": "object", "properties": { - "organization_id_or_slug": { - "type": "string", - "description": "Organization ID or URL slug, for example 'acme'." - }, "query": { "type": "string", "description": "Filter projects by name or slug, for example 'backend'." @@ -40,19 +39,22 @@ }, "cursor": { "type": "string", - "description": "Externally supplied Sentry pagination cursor. Omit for the first page; this connector does not return pagination headers." + "description": "Pagination cursor. Omit for the first page, then pass `_pagination.nextCursor` from the previous response and repeat until `_pagination` is absent, which means you are on the last page. Do not invent a cursor value." } - }, - "required": ["organization_id_or_slug"] + } }, "endpointMapping": { "method": "GET", - "path": "/organizations/{organization_id_or_slug}/projects/", + "path": "/organizations/{{SENTRY_ORG}}/projects/", "queryParams": { "query": "$query", "per_page": "$per_page", "cursor": "$cursor" - } + }, + "exposeHeaders": [ + "link", + "x-sentry-rate-limit-remaining" + ] } }, { @@ -61,10 +63,6 @@ "parameters": { "type": "object", "properties": { - "organization_id_or_slug": { - "type": "string", - "description": "Organization ID or URL slug, for example 'acme'." - }, "project": { "type": "string", "description": "Filter to one project ID or slug from sentry_list_projects. Omit for all accessible projects." @@ -85,21 +83,24 @@ }, "cursor": { "type": "string", - "description": "Externally supplied Sentry pagination cursor. Omit for the first page; this connector does not return pagination headers." + "description": "Pagination cursor. Omit for the first page, then pass `_pagination.nextCursor` from the previous response and repeat until `_pagination` is absent, which means you are on the last page. Do not invent a cursor value." } - }, - "required": ["organization_id_or_slug"] + } }, "endpointMapping": { "method": "GET", - "path": "/organizations/{organization_id_or_slug}/issues/", + "path": "/organizations/{{SENTRY_ORG}}/issues/", "queryParams": { "project": "$project", "query": "$query", "statsPeriod": "$statsPeriod", "limit": "$limit", "cursor": "$cursor" - } + }, + "exposeHeaders": [ + "link", + "x-sentry-rate-limit-remaining" + ] } }, { @@ -108,20 +109,18 @@ "parameters": { "type": "object", "properties": { - "organization_id_or_slug": { - "type": "string", - "description": "Organization ID or URL slug, for example 'acme'." - }, "issue_id": { "type": "string", "description": "Issue ID from the 'id' field returned by sentry_list_issues, for example '123456'. Use the ID, not the shortId such as 'BACKEND-42'." } }, - "required": ["organization_id_or_slug", "issue_id"] + "required": [ + "issue_id" + ] }, "endpointMapping": { "method": "GET", - "path": "/organizations/{organization_id_or_slug}/issues/{issue_id}/" + "path": "/organizations/{{SENTRY_ORG}}/issues/{issue_id}/" } }, { @@ -130,10 +129,6 @@ "parameters": { "type": "object", "properties": { - "organization_id_or_slug": { - "type": "string", - "description": "Organization ID or URL slug, for example 'acme'." - }, "issue_id": { "type": "string", "description": "Issue ID from sentry_list_issues, for example '123456'." @@ -154,20 +149,26 @@ }, "cursor": { "type": "string", - "description": "Externally supplied Sentry pagination cursor. Omit for the first page; this connector does not return pagination headers." + "description": "Pagination cursor. Omit for the first page, then pass `_pagination.nextCursor` from the previous response and repeat until `_pagination` is absent, which means you are on the last page. Do not invent a cursor value." } }, - "required": ["organization_id_or_slug", "issue_id"] + "required": [ + "issue_id" + ] }, "endpointMapping": { "method": "GET", - "path": "/organizations/{organization_id_or_slug}/issues/{issue_id}/events/", + "path": "/organizations/{{SENTRY_ORG}}/issues/{issue_id}/events/", "queryParams": { "statsPeriod": "$statsPeriod", "full": "$full", "per_page": "$per_page", "cursor": "$cursor" - } + }, + "exposeHeaders": [ + "link", + "x-sentry-rate-limit-remaining" + ] } } ] diff --git a/packages/backend/src/adapters/intl/sentry.live.spec.ts b/packages/backend/src/adapters/intl/sentry.live.spec.ts index 7f766f41..26373a58 100644 --- a/packages/backend/src/adapters/intl/sentry.live.spec.ts +++ b/packages/backend/src/adapters/intl/sentry.live.spec.ts @@ -3,7 +3,7 @@ * * Required environment variables: * SENTRY_AUTH_TOKEN — personal token with org:read and event:read - * SENTRY_TEST_ORG — organization ID or slug + * SENTRY_TEST_ORG — organization ID or slug, injected as the SENTRY_ORG env var * SENTRY_TEST_PROJECT_ID — project containing the test issue * SENTRY_TEST_ISSUE_ID — unresolved issue in that project * SENTRY_TEST_EVENT_ID — exception event with a stack trace in that issue @@ -47,6 +47,16 @@ describeLive("Sentry adapter — live API", () => { } }); + function withOrg(mapping: T): T { + return { + ...mapping, + path: mapping.path.replace( + /\{\{SENTRY_ORG\}\}/g, + String(process.env.SENTRY_TEST_ORG), + ), + }; + } + async function callTool( name: string, params: Record, @@ -60,7 +70,11 @@ describeLive("Sentry adapter — live API", () => { ...adapter.connector, authConfig: { token: process.env.SENTRY_AUTH_TOKEN }, }, - tool.endpointMapping, + // The organization lives in the path as {{SENTRY_ORG}} and is resolved + // from the connector's env vars at call time by DynamicMcpTools, not by + // the engine. These tests talk to the engine directly, so they have to + // do that substitution themselves. + withOrg(tool.endpointMapping), params, ); } catch (error) { @@ -75,7 +89,6 @@ describeLive("Sentry adapter — live API", () => { it("finds the configured test project", async () => { const result = await callTool("sentry_list_projects", { - organization_id_or_slug: process.env.SENTRY_TEST_ORG, per_page: 100, }); @@ -91,7 +104,6 @@ describeLive("Sentry adapter — live API", () => { it("finds the configured issue within its project", async () => { const result = await callTool("sentry_list_issues", { - organization_id_or_slug: process.env.SENTRY_TEST_ORG, project: process.env.SENTRY_TEST_PROJECT_ID, query: "is:unresolved", limit: 100, @@ -108,7 +120,6 @@ describeLive("Sentry adapter — live API", () => { it("retrieves the configured issue and its project", async () => { const result = await callTool("sentry_get_issue", { - organization_id_or_slug: process.env.SENTRY_TEST_ORG, issue_id: process.env.SENTRY_TEST_ISSUE_ID, }); @@ -124,7 +135,6 @@ describeLive("Sentry adapter — live API", () => { it("retrieves the sample event with exception details", async () => { const result = await callTool("sentry_list_issue_events", { - organization_id_or_slug: process.env.SENTRY_TEST_ORG, issue_id: process.env.SENTRY_TEST_ISSUE_ID, full: true, per_page: 10, diff --git a/server.json b/server.json index bde5f46f..3eb8545c 100644 --- a/server.json +++ b/server.json @@ -1,7 +1,7 @@ { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.HelpCode-ai/anythingmcp", - "description": "190 connectors + any REST/SOAP/GraphQL/SQL as MCP tools for Claude & ChatGPT. Self-hosted, AGPL.", + "description": "191 connectors + any REST/SOAP/GraphQL/SQL as MCP tools for Claude & ChatGPT. Self-hosted, AGPL.", "repository": { "url": "https://github.com/HelpCode-ai/anythingmcp", "source": "github"