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/catalog.ts b/packages/backend/src/adapters/catalog.ts index 3ee6efdb..5e20c9c3 100644 --- a/packages/backend/src/adapters/catalog.ts +++ b/packages/backend/src/adapters/catalog.ts @@ -152,6 +152,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'; @@ -458,6 +459,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..88e2effe --- /dev/null +++ b/packages/backend/src/adapters/intl/sentry.json @@ -0,0 +1,175 @@ +{ + "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, 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", + "SENTRY_ORG" + ], + "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": { + "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": "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." + } + } + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{{SENTRY_ORG}}/projects/", + "queryParams": { + "query": "$query", + "per_page": "$per_page", + "cursor": "$cursor" + }, + "exposeHeaders": [ + "link", + "x-sentry-rate-limit-remaining" + ] + } + }, + { + "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": { + "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": "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." + } + } + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{{SENTRY_ORG}}/issues/", + "queryParams": { + "project": "$project", + "query": "$query", + "statsPeriod": "$statsPeriod", + "limit": "$limit", + "cursor": "$cursor" + }, + "exposeHeaders": [ + "link", + "x-sentry-rate-limit-remaining" + ] + } + }, + { + "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": { + "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": [ + "issue_id" + ] + }, + "endpointMapping": { + "method": "GET", + "path": "/organizations/{{SENTRY_ORG}}/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": { + "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": "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": [ + "issue_id" + ] + }, + "endpointMapping": { + "method": "GET", + "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 new file mode 100644 index 00000000..26373a58 --- /dev/null +++ b/packages/backend/src/adapters/intl/sentry.live.spec.ts @@ -0,0 +1,165 @@ +/** + * 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, 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 + * + * 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`); + } + } + }); + + 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, + ): 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 }, + }, + // 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) { + // 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", { + 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", { + 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", { + 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", { + 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); +}); 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"