diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 00000000..2680c33b --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,86 @@ +name: Publish to PyPI + +on: + workflow_dispatch: + release: + types: [published] + +concurrency: + group: pypi-publish-${{ github.event.release.tag_name || github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + publish: + name: Publish openhands-extensions to PyPI + if: > + github.event_name == 'workflow_dispatch' || + !github.event.release.prerelease + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Extract version + id: extract_version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + if [ "${{ github.event_name }}" = "release" ]; then + VERSION="${RELEASE_TAG#v}" + else + VERSION=$(python - <<'PY' + import tomllib + from pathlib import Path + + print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]) + PY + ) + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "📦 Version: $VERSION" + + - name: Install uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 + with: + version: latest + python-version: '3.12' + + - name: Validate package version matches release tag + if: github.event_name == 'release' + env: + VERSION: ${{ steps.extract_version.outputs.version }} + run: | + PACKAGE_VERSION=$(python - <<'PY' + import tomllib + from pathlib import Path + + print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]) + PY + ) + + echo "Package version: $PACKAGE_VERSION" + echo "Release tag version: $VERSION" + if [ "$PACKAGE_VERSION" != "$VERSION" ]; then + echo "Error: pyproject.toml version ($PACKAGE_VERSION) doesn't match release tag ($VERSION)" + exit 1 + fi + + - name: Build package + run: uv build + + - name: Publish to PyPI + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN_OPENHANDS }} + run: | + if [ -z "${UV_PUBLISH_TOKEN:-}" ]; then + echo "❌ Missing secret PYPI_TOKEN_OPENHANDS" + exit 1 + fi + + uv publish --token "$UV_PUBLISH_TOKEN" --check-url https://pypi.org/simple/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 308e5572..3490e7f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,6 @@ name: Tests +# NOTE: pull_request (not pull_request_target) so checks run against the PR head. on: pull_request: branches: ["*"] @@ -19,6 +20,11 @@ jobs: enable-cache: true python-version: "3.12" + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Run tests run: uv run --group test pytest tests/ env: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f1c1e588..bcd05228 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.5.0" + ".": "0.6.0" } diff --git a/AGENTS.md b/AGENTS.md index 9d35cf4d..d14211fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,7 +90,7 @@ When editing or adding skills in this repo, follow these rules (and add new skil - Keep formatting consistent across skills. - If you change a skill’s behavior or scope, update its `README.md` (if present) accordingly. - If you change top-level documentation, ensure links still resolve. -- `integrations/catalog/*.json` and `integrations/index.js` are the source of truth consumed by `@openhands/extensions`; agent-canvas and integrations-hub import this package directly, so integration marketplace fixes belong here rather than in app-local constants. When upstream MCP projects move repos, verify both `docsUrl` and the connection option (`transport`, `command`/`args`, or URL), not just links. +- `integrations/catalog/*.json` is the single hand-authored source of truth consumed by `@openhands/extensions`; adding or editing an integration should require changing exactly one JSON file in that directory. Do not reintroduce `integrations/integration-catalog.json`, separate provider files, per-language catalog duplicates, or provider-specific runtime code. Run `npm run build:integrations` after catalog edits to regenerate `integrations/catalog-index.js`, which statically imports each individual JSON file for the JS package. The Python package includes the same individual JSON files via wheel data and reads them directly. Agent-canvas and integrations-hub import this package directly, so integration marketplace fixes belong here rather than in app-local constants. When upstream MCP projects move repos, verify both `docsUrl` and the connection option (`transport`, `command`/`args`, or URL), not just links. JS exposes `listIntegrationCatalog({ mcp, oauth })` / `getIntegrationCatalogEntry`; Python mirrors with `list_integration_catalog(mcp=, oauth=)` / `get_integration_catalog_entry`. The legacy provider-catalog compatibility layer and `managedConnectorMigration` / `legacyScopeBundles` / `canonicalServerUrl` / `errorHints` mechanisms were removed intentionally; providers declare only standard OAuth config as integration data. - For Python test runs, prefer `uv sync --group test` followed by `uv run pytest -q`; the full suite depends on `openhands-sdk`, which is not available in the base environment. - Agent-driven plugins (for example `plugins/pr-review` and `plugins/release-notes`) use `uv run --with openhands-sdk --with openhands-tools ...` and require an `LLM_API_KEY` in addition to `GITHUB_TOKEN`. - For OpenHands Cloud API guidance, automations, and CLI integration, use `plugins/openhands`. It is the canonical unified OpenHands plugin covering the V1 Cloud API, Automations API, and CLI. The individual skills (`skills/openhands-api`, `skills/openhands-automation`) are also available standalone. diff --git a/README.md b/README.md index e07b615a..2db20339 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,33 @@ import marketplace from "@openhands/extensions/marketplaces/openhands-extensions See [`integrations/README.md`](integrations/README.md), [`automations/README.md`](automations/README.md), and [`MIGRATION.md`](MIGRATION.md) for catalog-specific details. +### Python Package + +The integration catalog is also published as a Python package (`openhands-extensions`) so Python services read the same catalog data as JS consumers. The single source of truth is the hand-authored JSON asset `integrations/integration-catalog.json` (NOT generated from any `.mjs`/`.js` source). Each per-integration entry also exists as `integrations/catalog/.json`; a CI parity test asserts the two never drift. Both the JS package (`@openhands/extensions/integrations`) and the Python package read that same JSON asset at runtime, so the two language bindings can never drift. + +The catalog is one array where each entry can carry oauth and/or mcp/http `connectionOptions`. `supportsOauth` / `supportsMcp` flags are derived at runtime. Use `listIntegrationCatalog({ mcp, oauth })` (JS) or `list_integration_catalog(mcp=, oauth=)` (Python) to filter by connector type - for example only integrations that support an oauth connector. + +```python +from openhands_extensions import ( + list_integration_catalog, # listIntegrationCatalog({ mcp, oauth }) + get_integration_catalog_entry, # getIntegrationCatalogEntry(id) + INTEGRATION_CATALOG_SNAPSHOT, # { integrations } +) + +all_integrations = list_integration_catalog() +oauth_integrations = list_integration_catalog(oauth=True) # only entries with an oauth connector +mcp_integrations = list_integration_catalog(mcp=True) # only entries with an mcp connector +hubspot = get_integration_catalog_entry("hubspot") +``` + +Install from git (the hub backend consumes it this way): + +```bash +pip install git+https://github.com/OpenHands/extensions.git +``` + +The JS and Python versions are kept in lock-step by `release-please` and guarded by `tests/test_version_alignment.py`. + ## Extensions Catalog diff --git a/integrations/README.md b/integrations/README.md index 0f1fe70e..dd5253bd 100644 --- a/integrations/README.md +++ b/integrations/README.md @@ -4,10 +4,20 @@ This directory contains curated integration metadata for OpenHands clients. Most current entries are MCP-backed, but the schema also supports HTTP/OpenAPI integrations so clients can consume one source of truth. -- `catalog/*.json` contains one source file per direct integration entry. -- `index.js` assembles and exports the catalog for Node.js and bundlers. +- `catalog/.json` is the hand-authored source of truth. Add or edit an + integration by changing exactly one file in that directory. +- `catalog-index.js` is generated from `catalog/*.json` so the JavaScript + package can statically import every individual JSON file without an aggregate + catalog JSON asset. +- The Python package includes the same individual `catalog/*.json` files and + reads them directly. +- `index.js` derives the `supportsMcp`/`supportsOauth` filters from the + canonical connection options at read time. - `index.d.ts` contains the public TypeScript shape. +Each integration carries its OAuth/MCP connection data directly. Do not add a +separate provider catalog or per-language provider data. + Consumers can import the package export: ```js @@ -22,18 +32,18 @@ old package exports were removed rather than kept as aliases. - Import `INTEGRATION_CATALOG` from `@openhands/extensions/integrations` instead of `MCP_CATALOG` from `@openhands/extensions/mcps`. -- Import logo mappings from `@openhands/extensions/integrations/logos` - instead of `@openhands/extensions/mcps/logos`. +- Read serializable logo metadata from each `IntegrationCatalogEntry` (`logoUrl`, + `iconBg`, and `iconColor`) instead of importing React-specific logo maps. - Use `IntegrationCatalogEntry` instead of `McpCatalogEntry`. - Read MCP configuration from `entry.connectionOptions[]`. Direct MCP entries have `provider: "mcp"` and a `transport`; entries may expose multiple options such as `id: "oauth"` for a hosted OAuth MCP endpoint and `id: -"api"` for an API-key or stdio fallback. -- Use `entry.defaultConnectionOptionId` to choose the preferred option. +"api"` for an API-key or stdio fallback. The first option is the preferred + default. - Automation catalog entries now use `requiredIntegrationIds` instead of `requiredMcpIds`. The `mcps` API was intentionally broken because it was pre-release and had not been adopted as a stable public surface. -The catalog intentionally stores only serializable data. Client applications are responsible for mapping entries to UI-specific icons or styling. +The catalog intentionally stores only serializable data, including language-agnostic logo URLs and optional presentation colors. Client applications can render those fields directly while keeping any purely UI-specific styling local. diff --git a/integrations/catalog-index.js b/integrations/catalog-index.js new file mode 100644 index 00000000..a5cafc78 --- /dev/null +++ b/integrations/catalog-index.js @@ -0,0 +1,171 @@ +// This file is auto-generated by scripts/build-integration-catalog.mjs. +// Do not edit it manually. To update it after changing integrations/catalog/*.json, +// run: npm run build:integrations + +import entry0 from "./catalog/github.json" with { type: "json" }; +import entry1 from "./catalog/slack.json" with { type: "json" }; +import entry2 from "./catalog/tavily.json" with { type: "json" }; +import entry3 from "./catalog/linear.json" with { type: "json" }; +import entry4 from "./catalog/notion.json" with { type: "json" }; +import entry5 from "./catalog/ordinal.json" with { type: "json" }; +import entry6 from "./catalog/elevenlabs.json" with { type: "json" }; +import entry7 from "./catalog/bitbucket.json" with { type: "json" }; +import entry8 from "./catalog/gitlab.json" with { type: "json" }; +import entry9 from "./catalog/xero.json" with { type: "json" }; +import entry10 from "./catalog/quickbooks.json" with { type: "json" }; +import entry11 from "./catalog/mailchimp.json" with { type: "json" }; +import entry12 from "./catalog/pipedrive.json" with { type: "json" }; +import entry13 from "./catalog/freshdesk.json" with { type: "json" }; +import entry14 from "./catalog/servicenow.json" with { type: "json" }; +import entry15 from "./catalog/okta.json" with { type: "json" }; +import entry16 from "./catalog/plaid.json" with { type: "json" }; +import entry17 from "./catalog/netlify.json" with { type: "json" }; +import entry18 from "./catalog/vercel.json" with { type: "json" }; +import entry19 from "./catalog/supabase.json" with { type: "json" }; +import entry20 from "./catalog/posthog.json" with { type: "json" }; +import entry21 from "./catalog/sentry.json" with { type: "json" }; +import entry22 from "./catalog/datadog.json" with { type: "json" }; +import entry23 from "./catalog/canva.json" with { type: "json" }; +import entry24 from "./catalog/miro.json" with { type: "json" }; +import entry25 from "./catalog/webflow.json" with { type: "json" }; +import entry26 from "./catalog/zoom.json" with { type: "json" }; +import entry27 from "./catalog/discord.json" with { type: "json" }; +import entry28 from "./catalog/shopify.json" with { type: "json" }; +import entry29 from "./catalog/stripe.json" with { type: "json" }; +import entry30 from "./catalog/intercom.json" with { type: "json" }; +import entry31 from "./catalog/zendesk.json" with { type: "json" }; +import entry32 from "./catalog/hubspot.json" with { type: "json" }; +import entry33 from "./catalog/salesforce.json" with { type: "json" }; +import entry34 from "./catalog/sharepoint.json" with { type: "json" }; +import entry35 from "./catalog/onedrive.json" with { type: "json" }; +import entry36 from "./catalog/microsoft-teams.json" with { type: "json" }; +import entry37 from "./catalog/microsoft-outlook.json" with { type: "json" }; +import entry38 from "./catalog/box.json" with { type: "json" }; +import entry39 from "./catalog/dropbox.json" with { type: "json" }; +import entry40 from "./catalog/airtable.json" with { type: "json" }; +import entry41 from "./catalog/monday.json" with { type: "json" }; +import entry42 from "./catalog/clickup.json" with { type: "json" }; +import entry43 from "./catalog/trello.json" with { type: "json" }; +import entry44 from "./catalog/asana.json" with { type: "json" }; +import entry45 from "./catalog/confluence.json" with { type: "json" }; +import entry46 from "./catalog/jira.json" with { type: "json" }; +import entry47 from "./catalog/google-calendar.json" with { type: "json" }; +import entry48 from "./catalog/gmail.json" with { type: "json" }; +import entry49 from "./catalog/google-sheets.json" with { type: "json" }; +import entry50 from "./catalog/google-drive.json" with { type: "json" }; +import entry51 from "./catalog/figma.json" with { type: "json" }; +import entry52 from "./catalog/google-docs.json" with { type: "json" }; +import entry53 from "./catalog/apify.json" with { type: "json" }; +import entry54 from "./catalog/atlassian.json" with { type: "json" }; +import entry55 from "./catalog/brave-search.json" with { type: "json" }; +import entry56 from "./catalog/browser-mcp.json" with { type: "json" }; +import entry57 from "./catalog/clickhouse.json" with { type: "json" }; +import entry58 from "./catalog/cloudflare-bindings.json" with { type: "json" }; +import entry59 from "./catalog/cloudflare-browser-rendering.json" with { type: "json" }; +import entry60 from "./catalog/cloudflare-builds.json" with { type: "json" }; +import entry61 from "./catalog/cloudflare-docs.json" with { type: "json" }; +import entry62 from "./catalog/cloudflare-observability.json" with { type: "json" }; +import entry63 from "./catalog/deepwiki.json" with { type: "json" }; +import entry64 from "./catalog/everything.json" with { type: "json" }; +import entry65 from "./catalog/exa.json" with { type: "json" }; +import entry66 from "./catalog/fetch.json" with { type: "json" }; +import entry67 from "./catalog/filesystem.json" with { type: "json" }; +import entry68 from "./catalog/firecrawl.json" with { type: "json" }; +import entry69 from "./catalog/git.json" with { type: "json" }; +import entry70 from "./catalog/huggingface.json" with { type: "json" }; +import entry71 from "./catalog/kagi.json" with { type: "json" }; +import entry72 from "./catalog/memory.json" with { type: "json" }; +import entry73 from "./catalog/mongodb.json" with { type: "json" }; +import entry74 from "./catalog/neon.json" with { type: "json" }; +import entry75 from "./catalog/obsidian.json" with { type: "json" }; +import entry76 from "./catalog/paypal.json" with { type: "json" }; +import entry77 from "./catalog/playwright.json" with { type: "json" }; +import entry78 from "./catalog/redis.json" with { type: "json" }; +import entry79 from "./catalog/resend.json" with { type: "json" }; +import entry80 from "./catalog/sequential-thinking.json" with { type: "json" }; +import entry81 from "./catalog/time.json" with { type: "json" }; + +export const INTEGRATION_CATALOG_ENTRIES = [ + entry0, + entry1, + entry2, + entry3, + entry4, + entry5, + entry6, + entry7, + entry8, + entry9, + entry10, + entry11, + entry12, + entry13, + entry14, + entry15, + entry16, + entry17, + entry18, + entry19, + entry20, + entry21, + entry22, + entry23, + entry24, + entry25, + entry26, + entry27, + entry28, + entry29, + entry30, + entry31, + entry32, + entry33, + entry34, + entry35, + entry36, + entry37, + entry38, + entry39, + entry40, + entry41, + entry42, + entry43, + entry44, + entry45, + entry46, + entry47, + entry48, + entry49, + entry50, + entry51, + entry52, + entry53, + entry54, + entry55, + entry56, + entry57, + entry58, + entry59, + entry60, + entry61, + entry62, + entry63, + entry64, + entry65, + entry66, + entry67, + entry68, + entry69, + entry70, + entry71, + entry72, + entry73, + entry74, + entry75, + entry76, + entry77, + entry78, + entry79, + entry80, + entry81, +]; diff --git a/integrations/catalog/airtable.json b/integrations/catalog/airtable.json index 76b80334..c7b4fb40 100644 --- a/integrations/catalog/airtable.json +++ b/integrations/catalog/airtable.json @@ -2,18 +2,39 @@ "id": "airtable", "name": "Airtable", "description": "List bases, query records, and update fields across your Airtable workspace.", - "docsUrl": "https://github.com/domdomegg/airtable-mcp-server", - "iconBg": "#FCB400", - "iconColor": "var(--oh-surface-deep)", - "keywords": [ - "spreadsheet", - "database", - "records", - "bases" + "categories": [ + "Database", + "Operations" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", + "appUrl": "https://airtable.com", + "docsUrl": "https://github.com/domdomegg/airtable-mcp-server", + "notes": "Very common internal-tools and operations automation surface.", + "popularityRank": 17, "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://airtable.com/oauth2/v1/authorize", + "tokenUrl": "https://airtable.com/oauth2/v1/token", + "scopes": [ + "schema.bases:read", + "data.records:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.airtable.com/v0", + "defaultTool": { + "name": "list_bases", + "description": "List Airtable bases the connected user granted access to.", + "method": "GET", + "path": "/meta/bases" + } + } + }, { "id": "api", "provider": "mcp", @@ -40,5 +61,14 @@ "strategy": "api_key" } } + ], + "iconBg": "#FCB400", + "logoUrl": "https://cdn.simpleicons.org/airtable/000000", + "iconColor": "var(--oh-surface-deep)", + "keywords": [ + "spreadsheet", + "database", + "records", + "bases" ] } diff --git a/integrations/catalog/apify.json b/integrations/catalog/apify.json index 42b29574..4a91c4db 100644 --- a/integrations/catalog/apify.json +++ b/integrations/catalog/apify.json @@ -10,8 +10,6 @@ "crawl", "actors" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/asana.json b/integrations/catalog/asana.json new file mode 100644 index 00000000..e422f431 --- /dev/null +++ b/integrations/catalog/asana.json @@ -0,0 +1,38 @@ +{ + "id": "asana", + "name": "Asana", + "description": "Task management, projects, and work tracking.", + "categories": [ + "Project management", + "Operations" + ], + "appUrl": "https://asana.com", + "docsUrl": "https://developers.asana.com/docs/oauth", + "notes": "Broad business adoption and straightforward OAuth app model.", + "popularityRank": 13, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://app.asana.com/-/oauth_authorize", + "tokenUrl": "https://app.asana.com/-/oauth_token", + "scopes": [ + "tasks:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://app.asana.com/api/1.0", + "defaultTool": { + "name": "list_tasks", + "description": "List tasks visible to the connected Asana user.", + "method": "GET", + "path": "/tasks" + } + } + } + ] +} diff --git a/integrations/catalog/atlassian.json b/integrations/catalog/atlassian.json index 489dd218..39d35542 100644 --- a/integrations/catalog/atlassian.json +++ b/integrations/catalog/atlassian.json @@ -4,6 +4,7 @@ "description": "Search Jira issues and Confluence pages via Atlassian's hosted MCP server.", "docsUrl": "https://www.atlassian.com/platform/remote-mcp-server", "iconBg": "#0052CC", + "logoUrl": "https://cdn.simpleicons.org/atlassian/FFFFFF", "keywords": [ "jira", "confluence", @@ -11,20 +12,16 @@ "wiki", "issues" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://mcp.atlassian.com/v1/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://mcp.atlassian.com/v1/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2" } } ] diff --git a/integrations/catalog/bitbucket.json b/integrations/catalog/bitbucket.json new file mode 100644 index 00000000..c7b7c05e --- /dev/null +++ b/integrations/catalog/bitbucket.json @@ -0,0 +1,39 @@ +{ + "id": "bitbucket", + "name": "Bitbucket", + "description": "Repositories, pull requests, and Atlassian engineering workflows.", + "categories": [ + "Engineering", + "Source control" + ], + "appUrl": "https://bitbucket.org", + "docsUrl": "https://developer.atlassian.com/cloud/bitbucket/oauth-2/", + "notes": "Popular in Atlassian-centric teams and complements Jira/Confluence.", + "popularityRank": 50, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://bitbucket.org/site/oauth2/authorize", + "tokenUrl": "https://bitbucket.org/site/oauth2/access_token", + "scopes": [ + "account", + "repository" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.bitbucket.org/2.0", + "defaultTool": { + "name": "get_current_user", + "description": "Fetch the authenticated Bitbucket user profile.", + "method": "GET", + "path": "/user" + } + } + } + ] +} diff --git a/integrations/catalog/box.json b/integrations/catalog/box.json new file mode 100644 index 00000000..dccbb03c --- /dev/null +++ b/integrations/catalog/box.json @@ -0,0 +1,39 @@ +{ + "id": "box", + "name": "Box", + "description": "Enterprise file storage, metadata, and collaboration.", + "categories": [ + "Storage", + "Enterprise" + ], + "appUrl": "https://www.box.com", + "docsUrl": "https://developer.box.com/guides/authentication/oauth2/", + "notes": "Strong enterprise document-management footprint.", + "popularityRank": 19, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://account.box.com/api/oauth2/authorize", + "tokenUrl": "https://api.box.com/oauth2/token", + "scopes": [ + "root_readonly", + "item_read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.box.com/2.0", + "defaultTool": { + "name": "list_root_items", + "description": "List files and folders in the Box root folder.", + "method": "GET", + "path": "/folders/0/items" + } + } + } + ] +} diff --git a/integrations/catalog/brave-search.json b/integrations/catalog/brave-search.json index 073bd891..c195c12b 100644 --- a/integrations/catalog/brave-search.json +++ b/integrations/catalog/brave-search.json @@ -4,12 +4,11 @@ "description": "Privacy-first web and local search using the Brave Search API.", "docsUrl": "https://github.com/brave/brave-search-mcp-server", "iconBg": "#FB542B", + "logoUrl": "https://cdn.simpleicons.org/brave/FFFFFF", "keywords": [ "search", "web" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/browser-mcp.json b/integrations/catalog/browser-mcp.json index 6587285f..0fd280bf 100644 --- a/integrations/catalog/browser-mcp.json +++ b/integrations/catalog/browser-mcp.json @@ -10,9 +10,6 @@ "chrome", "playwright" ], - "kind": "mcp", - "runtimeAvailability": "local", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/canva.json b/integrations/catalog/canva.json new file mode 100644 index 00000000..a3092e70 --- /dev/null +++ b/integrations/catalog/canva.json @@ -0,0 +1,35 @@ +{ + "id": "canva", + "name": "Canva", + "description": "Design content, brand assets, and marketing collateral workflows.", + "categories": [ + "Design", + "Marketing" + ], + "appUrl": "https://www.canva.com", + "docsUrl": "https://www.canva.dev/docs/connect/oauth/", + "notes": "Broad creator and marketing adoption with OAuth-based apps.", + "popularityRank": 34, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "tokenUrl": "https://api.canva.com/rest/v1/oauth/token", + "scopes": [] + } + }, + "http": { + "apiBaseUrl": "https://api.canva.com/rest/v1", + "defaultTool": { + "name": "list_designs", + "description": "List Canva designs available to the connected user.", + "method": "GET", + "path": "/designs" + } + } + } + ] +} diff --git a/integrations/catalog/clickhouse.json b/integrations/catalog/clickhouse.json index 4d6e64ee..2baeac3c 100644 --- a/integrations/catalog/clickhouse.json +++ b/integrations/catalog/clickhouse.json @@ -4,6 +4,7 @@ "description": "Run analytical SQL queries against a ClickHouse cluster.", "docsUrl": "https://github.com/ClickHouse/mcp-clickhouse", "iconBg": "#FFFF00", + "logoUrl": "https://cdn.simpleicons.org/clickhouse/000000", "iconColor": "var(--oh-surface-deep)", "keywords": [ "analytics", @@ -11,8 +12,6 @@ "database", "sql" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/clickup.json b/integrations/catalog/clickup.json new file mode 100644 index 00000000..1b1c2e3e --- /dev/null +++ b/integrations/catalog/clickup.json @@ -0,0 +1,36 @@ +{ + "id": "clickup", + "name": "ClickUp", + "description": "Tasks, docs, goals, and workflow automation.", + "categories": [ + "Project management", + "Operations" + ], + "appUrl": "https://clickup.com", + "docsUrl": "https://clickup.com/api/developer-portal/authentication", + "notes": "Large install base and broad operations coverage.", + "popularityRank": 15, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://app.clickup.com/api", + "tokenUrl": "https://api.clickup.com/api/v2/oauth/token", + "scopes": [] + } + }, + "http": { + "apiBaseUrl": "https://api.clickup.com/api/v2", + "defaultTool": { + "name": "list_workspaces", + "description": "List ClickUp workspaces available to the connected user.", + "method": "GET", + "path": "/team" + } + } + } + ] +} diff --git a/integrations/catalog/cloudflare-bindings.json b/integrations/catalog/cloudflare-bindings.json index af110520..df1ea980 100644 --- a/integrations/catalog/cloudflare-bindings.json +++ b/integrations/catalog/cloudflare-bindings.json @@ -4,6 +4,7 @@ "description": "Inspect and manage KV, D1, R2, and Durable Object bindings on your Cloudflare account.", "docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/", "iconBg": "#F38020", + "logoUrl": "https://cdn.simpleicons.org/cloudflare/FFFFFF", "keywords": [ "cloudflare", "workers", @@ -12,20 +13,16 @@ "r2", "durable objects" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://bindings.mcp.cloudflare.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://bindings.mcp.cloudflare.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2" } } ] diff --git a/integrations/catalog/cloudflare-browser-rendering.json b/integrations/catalog/cloudflare-browser-rendering.json index b198d786..313061b4 100644 --- a/integrations/catalog/cloudflare-browser-rendering.json +++ b/integrations/catalog/cloudflare-browser-rendering.json @@ -4,26 +4,23 @@ "description": "Fetch and screenshot pages using Cloudflare's hosted browser rendering.", "docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/", "iconBg": "#F38020", + "logoUrl": "https://cdn.simpleicons.org/cloudflare/FFFFFF", "keywords": [ "cloudflare", "browser", "rendering", "screenshots" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://browser.mcp.cloudflare.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://browser.mcp.cloudflare.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2" } } ] diff --git a/integrations/catalog/cloudflare-builds.json b/integrations/catalog/cloudflare-builds.json index c24383b2..6c128969 100644 --- a/integrations/catalog/cloudflare-builds.json +++ b/integrations/catalog/cloudflare-builds.json @@ -1,9 +1,10 @@ { "id": "cloudflare-builds", "name": "Cloudflare Builds", - "description": "Inspect Workers Builds — logs, statuses, and rerun failed deploys.", + "description": "Inspect Workers Builds \u2014 logs, statuses, and rerun failed deploys.", "docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/", "iconBg": "#F38020", + "logoUrl": "https://cdn.simpleicons.org/cloudflare/FFFFFF", "keywords": [ "cloudflare", "workers", @@ -11,20 +12,16 @@ "builds", "deploys" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://builds.mcp.cloudflare.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://builds.mcp.cloudflare.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2" } } ] diff --git a/integrations/catalog/cloudflare-docs.json b/integrations/catalog/cloudflare-docs.json index 711711e5..6aac3cd8 100644 --- a/integrations/catalog/cloudflare-docs.json +++ b/integrations/catalog/cloudflare-docs.json @@ -4,26 +4,23 @@ "description": "Search and reference Cloudflare's developer documentation directly from the agent.", "docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/", "iconBg": "#F38020", + "logoUrl": "https://cdn.simpleicons.org/cloudflare/FFFFFF", "keywords": [ "cloudflare", "docs", "reference", "workers" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://docs.mcp.cloudflare.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://docs.mcp.cloudflare.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "none" } } ] diff --git a/integrations/catalog/cloudflare-observability.json b/integrations/catalog/cloudflare-observability.json index fd666d0d..3c09599e 100644 --- a/integrations/catalog/cloudflare-observability.json +++ b/integrations/catalog/cloudflare-observability.json @@ -4,6 +4,7 @@ "description": "Tail Workers logs and query observability data from your Cloudflare account.", "docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/", "iconBg": "#F38020", + "logoUrl": "https://cdn.simpleicons.org/cloudflare/FFFFFF", "keywords": [ "cloudflare", "logs", @@ -11,20 +12,16 @@ "observability", "workers" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://observability.mcp.cloudflare.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://observability.mcp.cloudflare.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2" } } ] diff --git a/integrations/catalog/confluence.json b/integrations/catalog/confluence.json new file mode 100644 index 00000000..cd932477 --- /dev/null +++ b/integrations/catalog/confluence.json @@ -0,0 +1,38 @@ +{ + "id": "confluence", + "name": "Confluence", + "description": "Wiki pages, spaces, and internal documentation search.", + "categories": [ + "Documentation", + "Knowledge base" + ], + "appUrl": "https://www.atlassian.com/software/confluence", + "docsUrl": "https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/", + "notes": "Natural companion to Jira for software teams.", + "popularityRank": 11, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://auth.atlassian.com/authorize", + "tokenUrl": "https://auth.atlassian.com/oauth/token", + "scopes": [ + "read:page:confluence" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.atlassian.com/ex/confluence/{cloudId}", + "defaultTool": { + "name": "list_spaces", + "description": "List Confluence spaces available to the connected user.", + "method": "GET", + "path": "/wiki/api/v2/spaces" + } + } + } + ] +} diff --git a/integrations/catalog/datadog.json b/integrations/catalog/datadog.json new file mode 100644 index 00000000..6ef82e3c --- /dev/null +++ b/integrations/catalog/datadog.json @@ -0,0 +1,34 @@ +{ + "id": "datadog", + "name": "Datadog", + "description": "Logs, metrics, monitors, incidents, and observability workflows.", + "categories": [ + "Observability", + "Operations" + ], + "appUrl": "https://www.datadoghq.com", + "docsUrl": "https://docs.datadoghq.com/bits_ai/mcp_server/setup/", + "notes": "Uses Datadog's official hosted MCP server. The default registration targets the US1 endpoint and can be edited for other Datadog sites.", + "popularityRank": 35, + "connectionOptions": [ + { + "id": "oauth", + "provider": "mcp", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://app.datadoghq.com/oauth2/v1/authorize", + "tokenUrl": "https://api.datadoghq.com/oauth2/v1/token", + "scopes": [ + "dashboards_read", + "monitors_read" + ] + } + }, + "transport": { + "kind": "shttp", + "url": "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp" + } + } + ] +} diff --git a/integrations/catalog/deepwiki.json b/integrations/catalog/deepwiki.json index ee3a8efe..f7a97d9e 100644 --- a/integrations/catalog/deepwiki.json +++ b/integrations/catalog/deepwiki.json @@ -12,20 +12,16 @@ "docs", "qa" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://mcp.deepwiki.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://mcp.deepwiki.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "none" } } ] diff --git a/integrations/catalog/discord.json b/integrations/catalog/discord.json new file mode 100644 index 00000000..aa34139e --- /dev/null +++ b/integrations/catalog/discord.json @@ -0,0 +1,39 @@ +{ + "id": "discord", + "name": "Discord", + "description": "Guilds, channels, messages, and community operations.", + "categories": [ + "Communication", + "Community" + ], + "appUrl": "https://discord.com", + "docsUrl": "https://discord.com/developers/docs/topics/oauth2", + "notes": "Useful for community automation and bot-assisted workflows.", + "popularityRank": 30, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://discord.com/oauth2/authorize", + "tokenUrl": "https://discord.com/api/oauth2/token", + "scopes": [ + "identify", + "guilds" + ] + } + }, + "http": { + "apiBaseUrl": "https://discord.com/api/v10", + "defaultTool": { + "name": "list_guilds", + "description": "List Discord guilds available to the connected user.", + "method": "GET", + "path": "/users/@me/guilds" + } + } + } + ] +} diff --git a/integrations/catalog/dropbox.json b/integrations/catalog/dropbox.json new file mode 100644 index 00000000..e8633744 --- /dev/null +++ b/integrations/catalog/dropbox.json @@ -0,0 +1,38 @@ +{ + "id": "dropbox", + "name": "Dropbox", + "description": "Cloud files, folders, content access, and sharing.", + "categories": [ + "Storage", + "Documents" + ], + "appUrl": "https://www.dropbox.com", + "docsUrl": "https://developers.dropbox.com/oauth-guide", + "notes": "Popular file automation target with mature OAuth support.", + "popularityRank": 18, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://www.dropbox.com/oauth2/authorize", + "tokenUrl": "https://api.dropboxapi.com/oauth2/token", + "scopes": [ + "files.metadata.read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.dropboxapi.com/2", + "defaultTool": { + "name": "list_root_folder", + "description": "List entries in the root Dropbox folder.", + "method": "POST", + "path": "/files/list_folder" + } + } + } + ] +} diff --git a/integrations/catalog/elevenlabs.json b/integrations/catalog/elevenlabs.json index bd84422a..afbe7bd4 100644 --- a/integrations/catalog/elevenlabs.json +++ b/integrations/catalog/elevenlabs.json @@ -2,41 +2,40 @@ "id": "elevenlabs", "name": "ElevenLabs", "description": "Generate speech, clone voices, and transcribe audio via ElevenLabs.", - "docsUrl": "https://elevenlabs.io/docs/api-reference/mcp", - "iconBg": "var(--oh-color-base)", - "keywords": [ - "tts", - "speech", - "voice", - "audio" + "categories": [ + "Audio", + "AI" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", + "appUrl": "https://elevenlabs.io", + "docsUrl": "https://elevenlabs.io/docs/api-reference/mcp", + "notes": "Official API-key connector backed by ElevenLabs' published OpenAPI spec.", + "popularityRank": 51, "connectionOptions": [ { "id": "api", - "provider": "mcp", - "transport": { - "kind": "stdio", - "serverName": "elevenlabs", - "command": "uvx", - "args": [ - "elevenlabs-mcp" + "provider": "http", + "auth": { + "strategy": "api_key", + "authModes": [ + "api_key" ], - "envFields": [ - { - "key": "ELEVENLABS_API_KEY", - "label": "ElevenLabs API key", - "type": "password", - "required": true, - "helperText": "API key from your ElevenLabs account settings.", - "helperLink": "https://elevenlabs.io/app/settings/api-keys" - } - ] + "credentialLabel": "ElevenLabs API key", + "credentialPlaceholder": "Paste your ElevenLabs API key", + "credentialHelp": "Personal or workspace ElevenLabs API key sent in the xi-api-key header.", + "apiKeyHeaderName": "xi-api-key" }, - "auth": { - "strategy": "api_key" + "http": { + "apiBaseUrl": "https://api.elevenlabs.io", + "openApiUrl": "https://api.elevenlabs.io/openapi.json" } } + ], + "iconBg": "var(--oh-color-base)", + "logoUrl": "https://cdn.simpleicons.org/elevenlabs/FFFFFF", + "keywords": [ + "tts", + "speech", + "voice", + "audio" ] } diff --git a/integrations/catalog/everything.json b/integrations/catalog/everything.json index 78404b14..3868735a 100644 --- a/integrations/catalog/everything.json +++ b/integrations/catalog/everything.json @@ -9,8 +9,6 @@ "test", "reference" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/exa.json b/integrations/catalog/exa.json index f48473c7..62aae0ae 100644 --- a/integrations/catalog/exa.json +++ b/integrations/catalog/exa.json @@ -10,8 +10,6 @@ "research", "neural" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/fetch.json b/integrations/catalog/fetch.json index 9c702611..1d671a42 100644 --- a/integrations/catalog/fetch.json +++ b/integrations/catalog/fetch.json @@ -10,8 +10,6 @@ "url", "scrape" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/figma.json b/integrations/catalog/figma.json index 61d1e6bc..b1283546 100644 --- a/integrations/catalog/figma.json +++ b/integrations/catalog/figma.json @@ -2,17 +2,41 @@ "id": "figma", "name": "Figma", "description": "Read Figma frames, components, and styles to ground UI work in your designs.", - "docsUrl": "https://github.com/GLips/Figma-Context-MCP", - "iconBg": "var(--oh-surface)", - "keywords": [ - "design", - "ui", - "frames", - "components" + "categories": [ + "Design", + "Frontend" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", + "appUrl": "https://www.figma.com", + "docsUrl": "https://github.com/GLips/Figma-Context-MCP", + "notes": "", + "popularityRank": 5, "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://www.figma.com/oauth", + "tokenUrl": "https://api.figma.com/v1/oauth/token", + "scopes": [ + "current_user:read", + "file_content:read", + "file_metadata:read", + "projects:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.figma.com", + "defaultTool": { + "name": "get_file", + "description": "Fetch a Figma file by key.", + "method": "GET", + "path": "/v1/files/{fileKey}" + } + } + }, { "id": "api", "provider": "mcp", @@ -40,5 +64,13 @@ "strategy": "api_key" } } + ], + "iconBg": "var(--oh-surface)", + "logoUrl": "https://cdn.simpleicons.org/figma/FFFFFF", + "keywords": [ + "design", + "ui", + "frames", + "components" ] } diff --git a/integrations/catalog/filesystem.json b/integrations/catalog/filesystem.json index ca31e03a..3f3f1238 100644 --- a/integrations/catalog/filesystem.json +++ b/integrations/catalog/filesystem.json @@ -10,9 +10,6 @@ "disk" ], "installHint": "Each path is exposed read/write. Add as many as you need, separated by spaces.", - "kind": "mcp", - "runtimeAvailability": "local", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/firecrawl.json b/integrations/catalog/firecrawl.json index 108aff23..6ebd568d 100644 --- a/integrations/catalog/firecrawl.json +++ b/integrations/catalog/firecrawl.json @@ -10,8 +10,6 @@ "web", "markdown" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/freshdesk.json b/integrations/catalog/freshdesk.json new file mode 100644 index 00000000..cca18fba --- /dev/null +++ b/integrations/catalog/freshdesk.json @@ -0,0 +1,38 @@ +{ + "id": "freshdesk", + "name": "Freshdesk", + "description": "Support tickets, customer records, and help desk workflows.", + "categories": [ + "Support", + "Operations" + ], + "appUrl": "https://www.freshworks.com/freshdesk/", + "docsUrl": "https://developers.freshdesk.com/api/#authentication", + "notes": "Popular support platform for SMB and mid-market teams.", + "popularityRank": 44, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://{domain}.freshdesk.com/oauth/authorize", + "tokenUrl": "https://{domain}.freshdesk.com/oauth/token", + "scopes": [ + "tickets_view" + ] + } + }, + "http": { + "apiBaseUrl": "https://{domain}.freshdesk.com/api/v2", + "defaultTool": { + "name": "list_tickets", + "description": "List Freshdesk tickets for the connected account.", + "method": "GET", + "path": "/tickets" + } + } + } + ] +} diff --git a/integrations/catalog/git.json b/integrations/catalog/git.json index 824d8330..973ebd30 100644 --- a/integrations/catalog/git.json +++ b/integrations/catalog/git.json @@ -11,9 +11,6 @@ "blame" ], "installHint": "Runs the official Python server via uvx — no setup beyond the path.", - "kind": "mcp", - "runtimeAvailability": "local", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/github.json b/integrations/catalog/github.json index 0471932b..a934ca83 100644 --- a/integrations/catalog/github.json +++ b/integrations/catalog/github.json @@ -2,19 +2,43 @@ "id": "github", "name": "GitHub", "description": "Search code, manage issues and pull requests, and inspect repos via the GitHub API.", - "docsUrl": "https://github.com/github/github-mcp-server", - "iconBg": "var(--oh-surface)", - "keywords": [ - "git", - "pr", - "repo", - "issues", - "code" + "categories": [ + "Engineering", + "Source control" ], + "appUrl": "https://github.com", + "docsUrl": "https://github.com/github/github-mcp-server", + "notes": "Current managed connector works with bearer tokens today; GitHub OAuth is a strong candidate for a future one-click connect flow.", "popularityRank": 100, - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ + { + "id": "oauth", + "provider": "mcp", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://github.com/login/oauth/authorize", + "tokenUrl": "https://github.com/login/oauth/access_token", + "scopes": [ + "read:user", + "repo" + ] + } + }, + "transport": { + "kind": "shttp", + "url": "https://api.githubcopilot.com/mcp/" + }, + "http": { + "apiBaseUrl": "https://api.github.com", + "defaultTool": { + "name": "get_authenticated_user", + "description": "Fetch the authenticated GitHub user profile.", + "method": "GET", + "path": "/user" + } + } + }, { "id": "api", "provider": "mcp", @@ -31,5 +55,14 @@ "saveCredentialAsSecretByDefault": true } } + ], + "iconBg": "var(--oh-surface)", + "logoUrl": "https://cdn.simpleicons.org/github/FFFFFF", + "keywords": [ + "git", + "pr", + "repo", + "issues", + "code" ] } diff --git a/integrations/catalog/gitlab.json b/integrations/catalog/gitlab.json new file mode 100644 index 00000000..cfd32959 --- /dev/null +++ b/integrations/catalog/gitlab.json @@ -0,0 +1,38 @@ +{ + "id": "gitlab", + "name": "GitLab", + "description": "Repositories, issues, merge requests, pipelines, and DevSecOps.", + "categories": [ + "Engineering", + "Source control" + ], + "appUrl": "https://about.gitlab.com", + "docsUrl": "https://docs.gitlab.com/integration/oauth_provider/", + "notes": "Major Git hosting platform and natural expansion beyond GitHub.", + "popularityRank": 49, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://gitlab.com/oauth/authorize", + "tokenUrl": "https://gitlab.com/oauth/token", + "scopes": [ + "read_api" + ] + } + }, + "http": { + "apiBaseUrl": "https://gitlab.com/api/v4", + "defaultTool": { + "name": "get_current_user", + "description": "Fetch the authenticated GitLab user profile.", + "method": "GET", + "path": "/user" + } + } + } + ] +} diff --git a/integrations/catalog/gmail.json b/integrations/catalog/gmail.json new file mode 100644 index 00000000..64794491 --- /dev/null +++ b/integrations/catalog/gmail.json @@ -0,0 +1,38 @@ +{ + "id": "gmail", + "name": "Gmail", + "description": "Mailbox search, drafting, sending, and thread triage.", + "categories": [ + "Communication", + "Email" + ], + "appUrl": "https://workspace.google.com/products/gmail/", + "docsUrl": "https://developers.google.com/identity/protocols/oauth2", + "notes": "Popular agent workflow target for inbox triage and drafting.", + "popularityRank": 8, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", + "tokenUrl": "https://oauth2.googleapis.com/token", + "scopes": [ + "https://www.googleapis.com/auth/gmail.readonly" + ] + } + }, + "http": { + "apiBaseUrl": "https://gmail.googleapis.com", + "defaultTool": { + "name": "list_messages", + "description": "List Gmail messages for the authenticated user.", + "method": "GET", + "path": "/gmail/v1/users/me/messages" + } + } + } + ] +} diff --git a/integrations/catalog/google-calendar.json b/integrations/catalog/google-calendar.json new file mode 100644 index 00000000..c026bb13 --- /dev/null +++ b/integrations/catalog/google-calendar.json @@ -0,0 +1,38 @@ +{ + "id": "google-calendar", + "name": "Google Calendar", + "description": "Calendar search, event scheduling, and meeting coordination.", + "categories": [ + "Calendar", + "Scheduling" + ], + "appUrl": "https://workspace.google.com/products/calendar/", + "docsUrl": "https://developers.google.com/identity/protocols/oauth2", + "notes": "Strong personal productivity use case with mature OAuth flows.", + "popularityRank": 9, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", + "tokenUrl": "https://oauth2.googleapis.com/token", + "scopes": [ + "https://www.googleapis.com/auth/calendar.readonly" + ] + } + }, + "http": { + "apiBaseUrl": "https://www.googleapis.com/calendar/v3", + "defaultTool": { + "name": "list_events", + "description": "List events from the user's primary Google Calendar.", + "method": "GET", + "path": "/calendars/primary/events" + } + } + } + ] +} diff --git a/integrations/catalog/google-docs.json b/integrations/catalog/google-docs.json new file mode 100644 index 00000000..b44323fc --- /dev/null +++ b/integrations/catalog/google-docs.json @@ -0,0 +1,38 @@ +{ + "id": "google-docs", + "name": "Google Docs", + "description": "Docs authoring and Google Workspace document automation.", + "categories": [ + "Documents", + "Knowledge base" + ], + "appUrl": "https://workspace.google.com/products/docs/", + "docsUrl": "https://developers.google.com/identity/protocols/oauth2", + "notes": "Current managed connector accepts a Google access token manually; a full OAuth connect flow can remove token copy/paste.", + "popularityRank": 2, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", + "tokenUrl": "https://oauth2.googleapis.com/token", + "scopes": [ + "https://www.googleapis.com/auth/documents.readonly" + ] + } + }, + "http": { + "apiBaseUrl": "https://docs.googleapis.com", + "defaultTool": { + "name": "get_document", + "description": "Fetch a Google Docs document by ID.", + "method": "GET", + "path": "/v1/documents/{documentId}" + } + } + } + ] +} diff --git a/integrations/catalog/google-drive.json b/integrations/catalog/google-drive.json new file mode 100644 index 00000000..6c452a08 --- /dev/null +++ b/integrations/catalog/google-drive.json @@ -0,0 +1,38 @@ +{ + "id": "google-drive", + "name": "Google Drive", + "description": "File search, metadata, folders, and document discovery.", + "categories": [ + "Storage", + "Documents" + ], + "appUrl": "https://workspace.google.com/products/drive/", + "docsUrl": "https://developers.google.com/identity/protocols/oauth2", + "notes": "Natural expansion of the Google Workspace surface beyond Google Docs.", + "popularityRank": 6, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", + "tokenUrl": "https://oauth2.googleapis.com/token", + "scopes": [ + "https://www.googleapis.com/auth/drive.metadata.readonly" + ] + } + }, + "http": { + "apiBaseUrl": "https://www.googleapis.com/drive/v3", + "defaultTool": { + "name": "list_files", + "description": "List Drive files visible to the connected user.", + "method": "GET", + "path": "/files" + } + } + } + ] +} diff --git a/integrations/catalog/google-sheets.json b/integrations/catalog/google-sheets.json new file mode 100644 index 00000000..cd78da86 --- /dev/null +++ b/integrations/catalog/google-sheets.json @@ -0,0 +1,38 @@ +{ + "id": "google-sheets", + "name": "Google Sheets", + "description": "Spreadsheet reads, writes, formulas, and reporting workflows.", + "categories": [ + "Spreadsheets", + "Analytics" + ], + "appUrl": "https://workspace.google.com/products/sheets/", + "docsUrl": "https://developers.google.com/identity/protocols/oauth2", + "notes": "High-value automation surface for agent-driven reporting and structured edits.", + "popularityRank": 7, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", + "tokenUrl": "https://oauth2.googleapis.com/token", + "scopes": [ + "https://www.googleapis.com/auth/spreadsheets.readonly" + ] + } + }, + "http": { + "apiBaseUrl": "https://sheets.googleapis.com", + "defaultTool": { + "name": "get_spreadsheet", + "description": "Fetch spreadsheet metadata and sheets by ID.", + "method": "GET", + "path": "/v4/spreadsheets/{spreadsheetId}" + } + } + } + ] +} diff --git a/integrations/catalog/hubspot.json b/integrations/catalog/hubspot.json new file mode 100644 index 00000000..cfb9a82d --- /dev/null +++ b/integrations/catalog/hubspot.json @@ -0,0 +1,34 @@ +{ + "id": "hubspot", + "name": "HubSpot", + "description": "CRM, marketing, tickets, and customer lifecycle workflows.", + "categories": [ + "CRM", + "Marketing" + ], + "appUrl": "https://www.hubspot.com", + "docsUrl": "https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server", + "notes": "Uses HubSpot's official remote MCP server plus MCP auth apps with PKCE.", + "popularityRank": 25, + "connectionOptions": [ + { + "id": "oauth", + "provider": "mcp", + "auth": { + "strategy": "oauth2", + "credentialHelp": "Use the client ID and secret from a HubSpot MCP auth app (Development → MCP Auth Apps). Standard HubSpot OAuth apps and private apps will not authenticate with mcp.hubspot.com.", + "oauth": { + "authorizationUrl": "https://mcp.hubspot.com/oauth/authorize/user", + "tokenUrl": "https://mcp.hubspot.com/oauth/v3/token", + "scopes": [], + "pkce": true, + "clientAuthentication": "body" + } + }, + "transport": { + "kind": "shttp", + "url": "https://mcp.hubspot.com" + } + } + ] +} diff --git a/integrations/catalog/huggingface.json b/integrations/catalog/huggingface.json index 2e1d32bb..5b503185 100644 --- a/integrations/catalog/huggingface.json +++ b/integrations/catalog/huggingface.json @@ -4,6 +4,7 @@ "description": "Search models, datasets, and Spaces on the Hugging Face Hub from your agent.", "docsUrl": "https://huggingface.co/docs/huggingface_hub/en/guides/mcp", "iconBg": "#FFD21E", + "logoUrl": "https://cdn.simpleicons.org/huggingface/000000", "iconColor": "#000000", "keywords": [ "ml", @@ -12,8 +13,6 @@ "ai", "hub" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/intercom.json b/integrations/catalog/intercom.json new file mode 100644 index 00000000..149219ac --- /dev/null +++ b/integrations/catalog/intercom.json @@ -0,0 +1,39 @@ +{ + "id": "intercom", + "name": "Intercom", + "description": "Customer support, conversations, inboxes, and CRM context.", + "categories": [ + "Support", + "CRM" + ], + "appUrl": "https://www.intercom.com", + "docsUrl": "https://developers.intercom.com/building-apps/docs/authentication-types", + "notes": "Useful for customer-facing assistant workflows.", + "popularityRank": 27, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://app.intercom.com/oauth", + "tokenUrl": "https://api.intercom.io/auth/eagle/token", + "scopes": [ + "read_users", + "read_conversations" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.intercom.io", + "defaultTool": { + "name": "list_contacts", + "description": "List Intercom contacts for the connected workspace.", + "method": "GET", + "path": "/contacts" + } + } + } + ] +} diff --git a/integrations/catalog/jira.json b/integrations/catalog/jira.json new file mode 100644 index 00000000..88820637 --- /dev/null +++ b/integrations/catalog/jira.json @@ -0,0 +1,38 @@ +{ + "id": "jira", + "name": "Jira", + "description": "Issue tracking, sprint workflows, and engineering program management.", + "categories": [ + "Project management", + "Engineering" + ], + "appUrl": "https://www.atlassian.com/software/jira", + "docsUrl": "https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/", + "notes": "Very common MCP target for software teams and ticket automation.", + "popularityRank": 10, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://auth.atlassian.com/authorize", + "tokenUrl": "https://auth.atlassian.com/oauth/token", + "scopes": [ + "read:jira-work" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.atlassian.com/ex/jira/{cloudId}", + "defaultTool": { + "name": "list_projects", + "description": "List Jira projects available to the connected user.", + "method": "GET", + "path": "/rest/api/3/project/search" + } + } + } + ] +} diff --git a/integrations/catalog/kagi.json b/integrations/catalog/kagi.json index 138a0487..f4b43b1e 100644 --- a/integrations/catalog/kagi.json +++ b/integrations/catalog/kagi.json @@ -1,17 +1,16 @@ { "id": "kagi", "name": "Kagi Search", - "description": "Paid, privacy-first search with high signal-to-noise — great for research.", + "description": "Paid, privacy-first search with high signal-to-noise \u2014 great for research.", "docsUrl": "https://github.com/kagisearch/kagimcp", "iconBg": "#FFB319", + "logoUrl": "https://cdn.simpleicons.org/kagi/000000", "iconColor": "var(--oh-surface-deep)", "keywords": [ "search", "web", "privacy" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/linear.json b/integrations/catalog/linear.json index 62800499..fd18cc0d 100644 --- a/integrations/catalog/linear.json +++ b/integrations/catalog/linear.json @@ -2,31 +2,62 @@ "id": "linear", "name": "Linear", "description": "Browse and update Linear issues, cycles, and projects from the agent.", - "docsUrl": "https://linear.app/changelog/2025-05-01-mcp", - "iconBg": "#5E6AD2", - "keywords": [ - "issues", - "project management", - "tasks", - "tickets" + "categories": [ + "Project management", + "Engineering" ], + "appUrl": "https://linear.app", + "docsUrl": "https://linear.app/docs/mcp", + "notes": "Popular among modern product engineering teams and already useful to this repo's users.", "popularityRank": 86, - "installHint": "Linear's hosted MCP server uses your Linear OAuth login — no key required.", - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://linear.app/oauth/authorize", + "tokenUrl": "https://api.linear.app/oauth/token", + "scopes": [ + "read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.linear.app", + "defaultTool": { + "name": "list_issues", + "description": "Query issues from Linear via GraphQL.", + "method": "POST", + "path": "/graphql" + } + } + }, + { + "id": "api-key", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://mcp.linear.app/sse", + "kind": "shttp", + "url": "https://mcp.linear.app/mcp", "apiKeyOptional": true }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "bearer", + "apiKeyOptional": true, + "credentialLabel": "Linear API key", + "credentialPlaceholder": "Paste your Linear API key", + "credentialHelp": "Optional when the endpoint accepts your OAuth session; otherwise sent as Authorization: Bearer ." } } - ] + ], + "iconBg": "#5E6AD2", + "logoUrl": "https://cdn.simpleicons.org/linear/FFFFFF", + "keywords": [ + "issues", + "project management", + "tasks", + "tickets" + ], + "installHint": "Authenticate with a Linear API key (Linear \u2192 Settings \u2192 Security & access) \u2014 sent as a Bearer token. Optional when the endpoint accepts your OAuth session." } diff --git a/integrations/catalog/mailchimp.json b/integrations/catalog/mailchimp.json new file mode 100644 index 00000000..1297df13 --- /dev/null +++ b/integrations/catalog/mailchimp.json @@ -0,0 +1,38 @@ +{ + "id": "mailchimp", + "name": "Mailchimp", + "description": "Campaigns, audiences, automations, and email marketing.", + "categories": [ + "Marketing", + "Email" + ], + "appUrl": "https://mailchimp.com", + "docsUrl": "https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/", + "notes": "Popular marketing automation target.", + "popularityRank": 46, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.mailchimp.com/oauth2/authorize", + "tokenUrl": "https://login.mailchimp.com/oauth2/token", + "scopes": [ + "audiences:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://{dc}.api.mailchimp.com/3.0", + "defaultTool": { + "name": "list_audiences", + "description": "List Mailchimp audiences for the connected account.", + "method": "GET", + "path": "/lists" + } + } + } + ] +} diff --git a/integrations/catalog/memory.json b/integrations/catalog/memory.json index cd10d222..2b50a4fb 100644 --- a/integrations/catalog/memory.json +++ b/integrations/catalog/memory.json @@ -10,8 +10,6 @@ "state", "persistence" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/microsoft-outlook.json b/integrations/catalog/microsoft-outlook.json new file mode 100644 index 00000000..e3aff831 --- /dev/null +++ b/integrations/catalog/microsoft-outlook.json @@ -0,0 +1,38 @@ +{ + "id": "microsoft-outlook", + "name": "Microsoft Outlook", + "description": "Mail, calendar, contacts, and meeting workflows through Microsoft Graph.", + "categories": [ + "Email", + "Calendar" + ], + "appUrl": "https://www.microsoft.com/microsoft-365/outlook/email-and-calendar-software-microsoft-outlook", + "docsUrl": "https://learn.microsoft.com/graph/auth-v2-user", + "notes": "High-value Microsoft Graph integration surface for enterprise users.", + "popularityRank": 20, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", + "scopes": [ + "Mail.Read" + ] + } + }, + "http": { + "apiBaseUrl": "https://graph.microsoft.com/v1.0", + "defaultTool": { + "name": "list_messages", + "description": "List Outlook messages for the signed-in user.", + "method": "GET", + "path": "/me/messages" + } + } + } + ] +} diff --git a/integrations/catalog/microsoft-teams.json b/integrations/catalog/microsoft-teams.json new file mode 100644 index 00000000..5f52fc5f --- /dev/null +++ b/integrations/catalog/microsoft-teams.json @@ -0,0 +1,38 @@ +{ + "id": "microsoft-teams", + "name": "Microsoft Teams", + "description": "Chats, channels, meetings, and collaboration via Microsoft Graph.", + "categories": [ + "Communication", + "Enterprise" + ], + "appUrl": "https://www.microsoft.com/microsoft-teams/group-chat-software", + "docsUrl": "https://learn.microsoft.com/graph/auth-v2-user", + "notes": "Common enterprise chat target alongside Outlook.", + "popularityRank": 21, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", + "scopes": [ + "Team.ReadBasic.All" + ] + } + }, + "http": { + "apiBaseUrl": "https://graph.microsoft.com/v1.0", + "defaultTool": { + "name": "list_teams", + "description": "List Microsoft Teams joined by the signed-in user.", + "method": "GET", + "path": "/me/joinedTeams" + } + } + } + ] +} diff --git a/integrations/catalog/miro.json b/integrations/catalog/miro.json new file mode 100644 index 00000000..ca7e7d06 --- /dev/null +++ b/integrations/catalog/miro.json @@ -0,0 +1,38 @@ +{ + "id": "miro", + "name": "Miro", + "description": "Whiteboards, diagrams, notes, and workshop collaboration.", + "categories": [ + "Whiteboard", + "Collaboration" + ], + "appUrl": "https://miro.com", + "docsUrl": "https://developers.miro.com/docs/rest-api-build-your-first-oauth-app", + "notes": "Strong visual collaboration target for design and product teams.", + "popularityRank": 33, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://miro.com/oauth/authorize", + "tokenUrl": "https://api.miro.com/v1/oauth/token", + "scopes": [ + "boards:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.miro.com/v2", + "defaultTool": { + "name": "list_boards", + "description": "List Miro boards available to the connected user.", + "method": "GET", + "path": "/boards" + } + } + } + ] +} diff --git a/integrations/catalog/monday.json b/integrations/catalog/monday.json new file mode 100644 index 00000000..189c9d20 --- /dev/null +++ b/integrations/catalog/monday.json @@ -0,0 +1,38 @@ +{ + "id": "monday", + "name": "Monday.com", + "description": "Boards, automations, and work management across teams.", + "categories": [ + "Project management", + "Operations" + ], + "appUrl": "https://monday.com", + "docsUrl": "https://developer.monday.com/apps/docs/oauth", + "notes": "High-demand operations platform with rich board APIs.", + "popularityRank": 16, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://auth.monday.com/oauth2/authorize", + "tokenUrl": "https://auth.monday.com/oauth2/token", + "scopes": [ + "boards:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.monday.com/v2", + "defaultTool": { + "name": "list_boards", + "description": "Query boards from Monday.com.", + "method": "POST", + "path": "/" + } + } + } + ] +} diff --git a/integrations/catalog/mongodb.json b/integrations/catalog/mongodb.json index 0c23004d..d1edfc97 100644 --- a/integrations/catalog/mongodb.json +++ b/integrations/catalog/mongodb.json @@ -4,13 +4,12 @@ "description": "Query MongoDB collections, inspect schemas, and run aggregation pipelines.", "docsUrl": "https://www.mongodb.com/docs/mcp-server/", "iconBg": "#00684A", + "logoUrl": "https://cdn.simpleicons.org/mongodb/FFFFFF", "keywords": [ "database", "nosql", "atlas" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/neon.json b/integrations/catalog/neon.json index 0050ef66..f3554699 100644 --- a/integrations/catalog/neon.json +++ b/integrations/catalog/neon.json @@ -10,8 +10,6 @@ "postgres", "serverless" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/netlify.json b/integrations/catalog/netlify.json new file mode 100644 index 00000000..494168c8 --- /dev/null +++ b/integrations/catalog/netlify.json @@ -0,0 +1,38 @@ +{ + "id": "netlify", + "name": "Netlify", + "description": "Sites, builds, deploy previews, and web operations.", + "categories": [ + "Deployment", + "Developer tools" + ], + "appUrl": "https://www.netlify.com", + "docsUrl": "https://docs.netlify.com/api/get-started/#oauth-applications", + "notes": "Common alternative hosting/deployment automation target.", + "popularityRank": 40, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://app.netlify.com/authorize", + "tokenUrl": "https://api.netlify.com/oauth/token", + "scopes": [ + "sites:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.netlify.com/api/v1", + "defaultTool": { + "name": "list_sites", + "description": "List Netlify sites available to the connected user.", + "method": "GET", + "path": "/sites" + } + } + } + ] +} diff --git a/integrations/catalog/notion.json b/integrations/catalog/notion.json index 5c8d3161..774c2f14 100644 --- a/integrations/catalog/notion.json +++ b/integrations/catalog/notion.json @@ -2,19 +2,37 @@ "id": "notion", "name": "Notion", "description": "Read and edit Notion pages, databases, and blocks via Notion's MCP server.", - "docsUrl": "https://developers.notion.com/docs/mcp", - "iconBg": "#FFFFFF", - "iconColor": "#000000", - "keywords": [ - "docs", - "notes", - "wiki", - "knowledge base" + "categories": [ + "Knowledge base", + "Documentation" ], + "appUrl": "https://www.notion.so", + "docsUrl": "https://developers.notion.com/docs/mcp", + "notes": "", "popularityRank": 82, - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://api.notion.com/v1/oauth/authorize", + "tokenUrl": "https://api.notion.com/v1/oauth/token", + "scopes": [] + } + }, + "http": { + "apiBaseUrl": "https://api.notion.com", + "openApiUrl": "https://developers.notion.com/openapi.json", + "defaultTool": { + "name": "post_search", + "description": "Search pages and databases in the connected Notion workspace.", + "method": "POST", + "path": "/v1/search" + } + } + }, { "id": "api", "provider": "mcp", @@ -42,5 +60,14 @@ "strategy": "api_key" } } + ], + "iconBg": "#FFFFFF", + "logoUrl": "https://cdn.simpleicons.org/notion/000000", + "iconColor": "#000000", + "keywords": [ + "docs", + "notes", + "wiki", + "knowledge base" ] } diff --git a/integrations/catalog/obsidian.json b/integrations/catalog/obsidian.json index 7c85f6d5..dd34260f 100644 --- a/integrations/catalog/obsidian.json +++ b/integrations/catalog/obsidian.json @@ -4,15 +4,13 @@ "description": "Read and edit Markdown notes inside an Obsidian vault on the local disk.", "docsUrl": "https://github.com/MarkusPfundstein/mcp-obsidian", "iconBg": "#7C3AED", + "logoUrl": "https://cdn.simpleicons.org/obsidian/FFFFFF", "keywords": [ "notes", "knowledge", "markdown", "vault" ], - "kind": "mcp", - "runtimeAvailability": "local", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/okta.json b/integrations/catalog/okta.json new file mode 100644 index 00000000..e56db6de --- /dev/null +++ b/integrations/catalog/okta.json @@ -0,0 +1,38 @@ +{ + "id": "okta", + "name": "Okta", + "description": "Identity, users, applications, and access administration.", + "categories": [ + "Identity", + "Enterprise" + ], + "appUrl": "https://www.okta.com", + "docsUrl": "https://developer.okta.com/docs/guides/implement-oauth-for-okta/main/", + "notes": "High-value admin and security automation surface.", + "popularityRank": 42, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://{yourOktaDomain}/oauth2/v1/authorize", + "tokenUrl": "https://{yourOktaDomain}/oauth2/v1/token", + "scopes": [ + "okta.users.read" + ] + } + }, + "http": { + "apiBaseUrl": "https://{yourOktaDomain}/api/v1", + "defaultTool": { + "name": "list_users", + "description": "List Okta users for the connected org.", + "method": "GET", + "path": "/users" + } + } + } + ] +} diff --git a/integrations/catalog/onedrive.json b/integrations/catalog/onedrive.json new file mode 100644 index 00000000..ec4f2dde --- /dev/null +++ b/integrations/catalog/onedrive.json @@ -0,0 +1,38 @@ +{ + "id": "onedrive", + "name": "OneDrive", + "description": "Cloud file access and document workflows through Microsoft Graph.", + "categories": [ + "Storage", + "Documents" + ], + "appUrl": "https://www.microsoft.com/microsoft-365/onedrive/online-cloud-storage", + "docsUrl": "https://learn.microsoft.com/graph/auth-v2-user", + "notes": "Useful for enterprise file automation and search.", + "popularityRank": 22, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", + "scopes": [ + "Files.Read" + ] + } + }, + "http": { + "apiBaseUrl": "https://graph.microsoft.com/v1.0", + "defaultTool": { + "name": "list_drive_items", + "description": "List OneDrive items in the root folder.", + "method": "GET", + "path": "/me/drive/root/children" + } + } + } + ] +} diff --git a/integrations/catalog/ordinal.json b/integrations/catalog/ordinal.json new file mode 100644 index 00000000..96b8295e --- /dev/null +++ b/integrations/catalog/ordinal.json @@ -0,0 +1,33 @@ +{ + "id": "ordinal", + "name": "Ordinal", + "description": "Social media posts, profiles, analytics, labels, approvals, and engagements.", + "categories": [ + "Social media", + "Marketing" + ], + "appUrl": "https://app.tryordinal.com", + "docsUrl": "https://docs.tryordinal.com/api/mcp", + "logoUrl": "https://app.tryordinal.com/favicon.ico", + "notes": "Official MCP server backed by Ordinal's API with API-key (Bearer) auth.", + "popularityRank": 52, + "connectionOptions": [ + { + "id": "api", + "provider": "mcp", + "auth": { + "strategy": "bearer", + "authModes": [ + "bearer" + ], + "credentialLabel": "Ordinal API key", + "credentialPlaceholder": "Paste your Ordinal API key", + "credentialHelp": "API key from your Ordinal workspace settings, sent as a Bearer token in the Authorization header." + }, + "transport": { + "kind": "shttp", + "url": "https://app.tryordinal.com/api/mcp" + } + } + ] +} diff --git a/integrations/catalog/paypal.json b/integrations/catalog/paypal.json index b56b30f4..ab7d6425 100644 --- a/integrations/catalog/paypal.json +++ b/integrations/catalog/paypal.json @@ -4,25 +4,22 @@ "description": "Manage transactions and merchant data via PayPal's MCP server.", "docsUrl": "https://developer.paypal.com/tools/mcp-server/", "iconBg": "#003087", + "logoUrl": "https://cdn.simpleicons.org/paypal/FFFFFF", "keywords": [ "payments", "billing", "finance" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { - "id": "none", + "id": "oauth", "provider": "mcp", "transport": { - "kind": "sse", - "url": "https://mcp.paypal.com/sse", - "apiKeyOptional": true + "kind": "shttp", + "url": "https://mcp.paypal.com/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2" } } ] diff --git a/integrations/catalog/pipedrive.json b/integrations/catalog/pipedrive.json new file mode 100644 index 00000000..361c84e8 --- /dev/null +++ b/integrations/catalog/pipedrive.json @@ -0,0 +1,39 @@ +{ + "id": "pipedrive", + "name": "Pipedrive", + "description": "Deals, contacts, and sales pipeline management.", + "categories": [ + "CRM", + "Sales" + ], + "appUrl": "https://www.pipedrive.com", + "docsUrl": "https://pipedrive.readme.io/docs/marketplace-oauth-authorization", + "notes": "Common CRM choice with solid OAuth story.", + "popularityRank": 45, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://oauth.pipedrive.com/oauth/authorize", + "tokenUrl": "https://oauth.pipedrive.com/oauth/token", + "scopes": [ + "deals:read", + "contacts:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.pipedrive.com/v1", + "defaultTool": { + "name": "list_persons", + "description": "List Pipedrive people visible to the connected user.", + "method": "GET", + "path": "/persons" + } + } + } + ] +} diff --git a/integrations/catalog/plaid.json b/integrations/catalog/plaid.json new file mode 100644 index 00000000..174e4ff5 --- /dev/null +++ b/integrations/catalog/plaid.json @@ -0,0 +1,31 @@ +{ + "id": "plaid", + "name": "Plaid", + "description": "Financial account connectivity and transaction data flows.", + "categories": [ + "Finance", + "Payments" + ], + "appUrl": "https://plaid.com", + "docsUrl": "https://plaid.com/docs/auth/oauth/", + "notes": "Strong fintech workflow candidate, although much of Plaid's OAuth guidance is a special-case token exchange rather than a generic end-user SaaS OAuth connector.", + "popularityRank": 41, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2" + }, + "http": { + "apiBaseUrl": "https://production.plaid.com", + "defaultTool": { + "name": "get_accounts", + "description": "Fetch accounts for a connected Plaid item.", + "method": "POST", + "path": "/accounts/get" + } + } + } + ] +} diff --git a/integrations/catalog/playwright.json b/integrations/catalog/playwright.json index 7c030dd2..205fd249 100644 --- a/integrations/catalog/playwright.json +++ b/integrations/catalog/playwright.json @@ -11,8 +11,6 @@ "testing", "scrape" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/posthog.json b/integrations/catalog/posthog.json new file mode 100644 index 00000000..3ecef6d0 --- /dev/null +++ b/integrations/catalog/posthog.json @@ -0,0 +1,38 @@ +{ + "id": "posthog", + "name": "PostHog", + "description": "Product analytics, feature flags, and session insights.", + "categories": [ + "Analytics", + "Product" + ], + "appUrl": "https://posthog.com", + "docsUrl": "https://posthog.com/docs/apps/build/oauth", + "notes": "Relevant for product analytics and experimentation workflows.", + "popularityRank": 37, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://us.posthog.com/oauth/authorize", + "tokenUrl": "https://us.posthog.com/oauth/token", + "scopes": [ + "project:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://us.posthog.com/api", + "defaultTool": { + "name": "list_projects", + "description": "List PostHog projects available to the connected user.", + "method": "GET", + "path": "/projects/" + } + } + } + ] +} diff --git a/integrations/catalog/quickbooks.json b/integrations/catalog/quickbooks.json new file mode 100644 index 00000000..b4bc71e8 --- /dev/null +++ b/integrations/catalog/quickbooks.json @@ -0,0 +1,38 @@ +{ + "id": "quickbooks", + "name": "QuickBooks", + "description": "Accounting, invoices, customers, and financial operations.", + "categories": [ + "Finance", + "Accounting" + ], + "appUrl": "https://quickbooks.intuit.com", + "docsUrl": "https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0", + "notes": "Frequent finance automation request with OAuth-based apps.", + "popularityRank": 47, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://appcenter.intuit.com/connect/oauth2", + "tokenUrl": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer", + "scopes": [ + "com.intuit.quickbooks.accounting" + ] + } + }, + "http": { + "apiBaseUrl": "https://sandbox-quickbooks.api.intuit.com/v3/company/{companyId}", + "defaultTool": { + "name": "list_customers", + "description": "Query QuickBooks customers for the connected company.", + "method": "GET", + "path": "/query?query=SELECT%20*%20FROM%20Customer%20MAXRESULTS%2010" + } + } + } + ] +} diff --git a/integrations/catalog/redis.json b/integrations/catalog/redis.json index 3b4185d0..1448ac01 100644 --- a/integrations/catalog/redis.json +++ b/integrations/catalog/redis.json @@ -4,14 +4,12 @@ "description": "Get, set, scan, and inspect data on a Redis or Redis-compatible instance.", "docsUrl": "https://github.com/redis/mcp-redis", "iconBg": "#DC382D", + "logoUrl": "https://cdn.simpleicons.org/redis/FFFFFF", "keywords": [ "cache", "kv", "database" ], - "kind": "mcp", - "runtimeAvailability": "local", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/resend.json b/integrations/catalog/resend.json index af4c3ff3..fea74b55 100644 --- a/integrations/catalog/resend.json +++ b/integrations/catalog/resend.json @@ -4,13 +4,12 @@ "description": "Send transactional and marketing emails via the Resend API.", "docsUrl": "https://resend.com/docs/mcp", "iconBg": "var(--oh-surface-deep)", + "logoUrl": "https://cdn.simpleicons.org/resend/FFFFFF", "keywords": [ "email", "transactional", "smtp" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/salesforce.json b/integrations/catalog/salesforce.json new file mode 100644 index 00000000..5c3adb8e --- /dev/null +++ b/integrations/catalog/salesforce.json @@ -0,0 +1,38 @@ +{ + "id": "salesforce", + "name": "Salesforce", + "description": "CRM records, accounts, opportunities, and sales operations.", + "categories": [ + "CRM", + "Sales" + ], + "appUrl": "https://www.salesforce.com", + "docsUrl": "https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5", + "notes": "Very common enterprise CRM and agent-assist target.", + "popularityRank": 24, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.salesforce.com/services/oauth2/authorize", + "tokenUrl": "https://login.salesforce.com/services/oauth2/token", + "scopes": [ + "api" + ] + } + }, + "http": { + "apiBaseUrl": "https://{instance}.salesforce.com/services/data/v60.0", + "defaultTool": { + "name": "list_accounts", + "description": "List Salesforce accounts from the connected org.", + "method": "GET", + "path": "/sobjects/Account" + } + } + } + ] +} diff --git a/integrations/catalog/sentry.json b/integrations/catalog/sentry.json index fda7577b..c0d47357 100644 --- a/integrations/catalog/sentry.json +++ b/integrations/catalog/sentry.json @@ -2,29 +2,67 @@ "id": "sentry", "name": "Sentry", "description": "Triage issues, inspect events, and run Seer fixes against your Sentry org.", - "docsUrl": "https://docs.sentry.io/product/sentry-mcp/", - "iconBg": "#362D59", - "keywords": [ - "errors", - "observability", - "monitoring", - "crash" + "categories": [ + "Observability", + "Engineering" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", + "appUrl": "https://sentry.io", + "docsUrl": "https://docs.sentry.io/product/sentry-mcp/", + "notes": "Natural target for engineering support agents.", + "popularityRank": 36, "connectionOptions": [ { - "id": "none", + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://sentry.io/oauth/authorize", + "tokenUrl": "https://sentry.io/oauth/token", + "scopes": [ + "project:read", + "event:read", + "org:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://sentry.io/api/0", + "defaultTool": { + "name": "list_organizations", + "description": "List Sentry organizations available to the connected user.", + "method": "GET", + "path": "/organizations/" + } + } + }, + { + "id": "oauth-mcp", "provider": "mcp", "transport": { "kind": "shttp", - "url": "https://mcp.sentry.dev/mcp", - "apiKeyOptional": true + "url": "https://mcp.sentry.dev/mcp" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://sentry.io/oauth/authorize", + "tokenUrl": "https://sentry.io/oauth/token", + "scopes": [ + "project:read", + "event:read", + "org:read" + ] + } } } + ], + "iconBg": "#362D59", + "logoUrl": "https://cdn.simpleicons.org/sentry/FFFFFF", + "keywords": [ + "errors", + "observability", + "monitoring", + "crash" ] } diff --git a/integrations/catalog/sequential-thinking.json b/integrations/catalog/sequential-thinking.json index 5a14bdca..4d821e2f 100644 --- a/integrations/catalog/sequential-thinking.json +++ b/integrations/catalog/sequential-thinking.json @@ -9,8 +9,6 @@ "planning", "thoughts" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/servicenow.json b/integrations/catalog/servicenow.json new file mode 100644 index 00000000..23417f8f --- /dev/null +++ b/integrations/catalog/servicenow.json @@ -0,0 +1,39 @@ +{ + "id": "servicenow", + "name": "ServiceNow", + "description": "Tickets, incidents, service catalogs, and enterprise workflows.", + "categories": [ + "ITSM", + "Enterprise" + ], + "appUrl": "https://www.servicenow.com", + "docsUrl": "https://www.servicenow.com/docs/bundle/xanadu-platform-security/page/administer/security/concept/oauth-concept.html", + "notes": "Strong enterprise IT operations use case.", + "popularityRank": 43, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://{instance}.service-now.com/oauth_auth.do", + "tokenUrl": "https://{instance}.service-now.com/oauth_token.do", + "scopes": [ + "useraccount", + "table.read" + ] + } + }, + "http": { + "apiBaseUrl": "https://{instance}.service-now.com/api/now/v1", + "defaultTool": { + "name": "list_incidents", + "description": "List ServiceNow incidents for the connected instance.", + "method": "GET", + "path": "/table/incident" + } + } + } + ] +} diff --git a/integrations/catalog/sharepoint.json b/integrations/catalog/sharepoint.json new file mode 100644 index 00000000..8b16a56c --- /dev/null +++ b/integrations/catalog/sharepoint.json @@ -0,0 +1,38 @@ +{ + "id": "sharepoint", + "name": "SharePoint", + "description": "Sites, document libraries, lists, and intranet content.", + "categories": [ + "Knowledge base", + "Enterprise" + ], + "appUrl": "https://www.microsoft.com/microsoft-365/sharepoint/collaboration", + "docsUrl": "https://learn.microsoft.com/graph/auth-v2-user", + "notes": "Frequently requested for enterprise knowledge retrieval.", + "popularityRank": 23, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token", + "scopes": [ + "Sites.Read.All" + ] + } + }, + "http": { + "apiBaseUrl": "https://graph.microsoft.com/v1.0", + "defaultTool": { + "name": "get_root_site", + "description": "Fetch the SharePoint root site through Microsoft Graph.", + "method": "GET", + "path": "/sites/root" + } + } + } + ] +} diff --git a/integrations/catalog/shopify.json b/integrations/catalog/shopify.json new file mode 100644 index 00000000..bf746493 --- /dev/null +++ b/integrations/catalog/shopify.json @@ -0,0 +1,38 @@ +{ + "id": "shopify", + "name": "Shopify", + "description": "Storefronts, products, orders, and ecommerce operations.", + "categories": [ + "Ecommerce", + "Operations" + ], + "appUrl": "https://www.shopify.com", + "docsUrl": "https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/authorization-code-grant", + "notes": "A top ecommerce platform and strong MCP candidate.", + "popularityRank": 29, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://{shop}.myshopify.com/admin/oauth/authorize", + "tokenUrl": "https://{shop}.myshopify.com/admin/oauth/access_token", + "scopes": [ + "read_products" + ] + } + }, + "http": { + "apiBaseUrl": "https://{shop}.myshopify.com/admin/api/2025-01", + "defaultTool": { + "name": "list_products", + "description": "List Shopify products for the connected store.", + "method": "GET", + "path": "/products.json" + } + } + } + ] +} diff --git a/integrations/catalog/slack.json b/integrations/catalog/slack.json index 70aa0f6b..e9f05421 100644 --- a/integrations/catalog/slack.json +++ b/integrations/catalog/slack.json @@ -2,18 +2,49 @@ "id": "slack", "name": "Slack", "description": "Read channels, post messages, and search workspace history from your agent.", - "docsUrl": "https://github.com/zencoderai/slack-mcp-server", - "iconBg": "#4A154B", - "keywords": [ - "chat", - "messaging", - "team" + "categories": [ + "Communication", + "Operations" ], + "appUrl": "https://slack.com", + "docsUrl": "https://github.com/zencoderai/slack-mcp-server", + "notes": "Uses Slack's official hosted MCP server with confidential OAuth user-token auth.", "popularityRank": 95, - "installHint": "Create a Slack app with the required scopes, then paste its bot token below.", - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ + { + "id": "oauth", + "provider": "mcp", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://slack.com/oauth/v2_user/authorize", + "tokenUrl": "https://slack.com/api/oauth.v2.user.access", + "scopes": [ + "search:read.public", + "search:read.private", + "search:read.mpim", + "search:read.im", + "search:read.files", + "search:read.users", + "chat:write", + "channels:history", + "groups:history", + "mpim:history", + "im:history", + "canvases:read", + "canvases:write", + "users:read", + "users:read.email" + ], + "pkce": true, + "clientAuthentication": "body" + } + }, + "transport": { + "kind": "shttp", + "url": "https://mcp.slack.com/mcp" + } + }, { "id": "api", "provider": "mcp", @@ -48,5 +79,13 @@ "strategy": "api_key" } } - ] + ], + "iconBg": "#4A154B", + "logoUrl": "https://cdn.simpleicons.org/slack/FFFFFF", + "keywords": [ + "chat", + "messaging", + "team" + ], + "installHint": "Create a Slack app with the required scopes, then paste its bot token below." } diff --git a/integrations/catalog/stripe.json b/integrations/catalog/stripe.json index 62430b85..be3508e8 100644 --- a/integrations/catalog/stripe.json +++ b/integrations/catalog/stripe.json @@ -2,29 +2,59 @@ "id": "stripe", "name": "Stripe", "description": "Query customers, payments, subscriptions, and invoices via Stripe's hosted MCP server.", - "docsUrl": "https://stripe.com/docs/mcp", - "iconBg": "#635BFF", - "keywords": [ - "payments", - "billing", - "subscriptions", - "finance" + "categories": [ + "Payments", + "Finance" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", + "appUrl": "https://stripe.com", + "docsUrl": "https://stripe.com/docs/mcp", + "notes": "High-value fintech automation target with mature OAuth patterns.", + "popularityRank": 28, "connectionOptions": [ { - "id": "none", + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://connect.stripe.com/oauth/authorize", + "tokenUrl": "https://connect.stripe.com/oauth/token", + "scopes": [ + "read_only" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.stripe.com/v1", + "defaultTool": { + "name": "list_customers", + "description": "List Stripe customers from the connected account.", + "method": "GET", + "path": "/customers" + } + } + }, + { + "id": "api", "provider": "mcp", "transport": { "kind": "shttp", - "url": "https://mcp.stripe.com/", - "apiKeyOptional": true + "url": "https://mcp.stripe.com/" }, "auth": { - "strategy": "none", - "apiKeyOptional": true + "strategy": "bearer", + "credentialLabel": "Stripe restricted key", + "credentialPlaceholder": "rk_live_...", + "credentialHelp": "Create a Stripe restricted key for MCP access and paste it here." } } + ], + "iconBg": "#635BFF", + "logoUrl": "https://cdn.simpleicons.org/stripe/FFFFFF", + "keywords": [ + "payments", + "billing", + "subscriptions", + "finance" ] } diff --git a/integrations/catalog/supabase.json b/integrations/catalog/supabase.json index 0992e1bf..4ef8130e 100644 --- a/integrations/catalog/supabase.json +++ b/integrations/catalog/supabase.json @@ -2,17 +2,36 @@ "id": "supabase", "name": "Supabase", "description": "Query and manage your Supabase project, including database, auth, and storage.", - "docsUrl": "https://supabase.com/docs/guides/getting-started/mcp", - "iconBg": "#3ECF8E", - "keywords": [ - "database", - "auth", - "storage", - "postgres" + "categories": [ + "Database", + "Developer tools" ], - "kind": "mcp", - "defaultConnectionOptionId": "api", + "appUrl": "https://supabase.com", + "docsUrl": "https://supabase.com/docs/guides/getting-started/mcp", + "notes": "Popular developer platform with broad automation value and a documented management-API OAuth integration flow.", + "popularityRank": 38, "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://api.supabase.com/v1/oauth/authorize", + "tokenUrl": "https://api.supabase.com/v1/oauth/token", + "scopes": [] + } + }, + "http": { + "apiBaseUrl": "https://api.supabase.com/v1", + "defaultTool": { + "name": "list_projects", + "description": "List Supabase projects available to the connected user.", + "method": "GET", + "path": "/projects" + } + } + }, { "id": "api", "provider": "mcp", @@ -39,5 +58,13 @@ "strategy": "api_key" } } + ], + "iconBg": "#3ECF8E", + "logoUrl": "https://cdn.simpleicons.org/supabase/FFFFFF", + "keywords": [ + "database", + "auth", + "storage", + "postgres" ] } diff --git a/integrations/catalog/tavily.json b/integrations/catalog/tavily.json index 5d6af852..8eedacff 100644 --- a/integrations/catalog/tavily.json +++ b/integrations/catalog/tavily.json @@ -12,8 +12,6 @@ ], "popularityRank": 90, "installHint": "Paste your Tavily API key - the official tavily-mcp package runs via npx.", - "kind": "mcp", - "defaultConnectionOptionId": "api", "connectionOptions": [ { "id": "api", diff --git a/integrations/catalog/time.json b/integrations/catalog/time.json index 681ccde6..c9ab5733 100644 --- a/integrations/catalog/time.json +++ b/integrations/catalog/time.json @@ -9,8 +9,6 @@ "timezone", "date" ], - "kind": "mcp", - "defaultConnectionOptionId": "none", "connectionOptions": [ { "id": "none", diff --git a/integrations/catalog/trello.json b/integrations/catalog/trello.json new file mode 100644 index 00000000..899c603c --- /dev/null +++ b/integrations/catalog/trello.json @@ -0,0 +1,31 @@ +{ + "id": "trello", + "name": "Trello", + "description": "Boards, cards, checklists, and lightweight project planning.", + "categories": [ + "Project management", + "Operations" + ], + "appUrl": "https://trello.com", + "docsUrl": "https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/", + "notes": "Useful for SMB and cross-functional task boards, but Trello's public API auth is a special-case flow rather than a straightforward generic OAuth2 connector.", + "popularityRank": 14, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2" + }, + "http": { + "apiBaseUrl": "https://api.trello.com/1", + "defaultTool": { + "name": "list_boards", + "description": "List Trello boards visible to the authenticated member.", + "method": "GET", + "path": "/members/me/boards" + } + } + } + ] +} diff --git a/integrations/catalog/vercel.json b/integrations/catalog/vercel.json new file mode 100644 index 00000000..7862f494 --- /dev/null +++ b/integrations/catalog/vercel.json @@ -0,0 +1,38 @@ +{ + "id": "vercel", + "name": "Vercel", + "description": "Deployments, projects, domains, and preview environment workflows.", + "categories": [ + "Developer tools", + "Deployment" + ], + "appUrl": "https://vercel.com", + "docsUrl": "https://vercel.com/docs/rest-api#oauth-apps", + "notes": "Especially relevant to this repo's deployment model.", + "popularityRank": 39, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://vercel.com/oauth/authorize", + "tokenUrl": "https://api.vercel.com/v2/oauth/access_token", + "scopes": [ + "project:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.vercel.com", + "defaultTool": { + "name": "list_projects", + "description": "List Vercel projects available to the connected user.", + "method": "GET", + "path": "/v9/projects" + } + } + } + ] +} diff --git a/integrations/catalog/webflow.json b/integrations/catalog/webflow.json new file mode 100644 index 00000000..69d995c1 --- /dev/null +++ b/integrations/catalog/webflow.json @@ -0,0 +1,34 @@ +{ + "id": "webflow", + "name": "Webflow", + "description": "CMS items, sites, and web publishing workflows.", + "categories": [ + "CMS", + "Marketing" + ], + "appUrl": "https://webflow.com", + "docsUrl": "https://developers.webflow.com/mcp/reference/getting-started", + "notes": "Uses Webflow's official hosted MCP server and deployment-scoped MCP OAuth registration so each user can authorize their own sites and workspaces.", + "popularityRank": 32, + "connectionOptions": [ + { + "id": "oauth", + "provider": "mcp", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://mcp.webflow.com/oauth/authorize", + "tokenUrl": "https://mcp.webflow.com/oauth/token", + "scopes": [], + "pkce": true, + "clientAuthentication": "none", + "registrationUrl": "https://mcp.webflow.com/oauth/register" + } + }, + "transport": { + "kind": "shttp", + "url": "https://mcp.webflow.com/mcp" + } + } + ] +} diff --git a/integrations/catalog/xero.json b/integrations/catalog/xero.json new file mode 100644 index 00000000..de2b907c --- /dev/null +++ b/integrations/catalog/xero.json @@ -0,0 +1,38 @@ +{ + "id": "xero", + "name": "Xero", + "description": "Accounting, contacts, invoices, and bookkeeping workflows.", + "categories": [ + "Finance", + "Accounting" + ], + "appUrl": "https://www.xero.com", + "docsUrl": "https://developer.xero.com/documentation/guides/oauth2/overview/", + "notes": "Important accounting platform in many regions.", + "popularityRank": 48, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://login.xero.com/identity/connect/authorize", + "tokenUrl": "https://identity.xero.com/connect/token", + "scopes": [ + "accounting.contacts.read" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.xero.com/api.xro/2.0", + "defaultTool": { + "name": "list_contacts", + "description": "List Xero contacts for the connected tenant.", + "method": "GET", + "path": "/Contacts" + } + } + } + ] +} diff --git a/integrations/catalog/zendesk.json b/integrations/catalog/zendesk.json new file mode 100644 index 00000000..0a662fee --- /dev/null +++ b/integrations/catalog/zendesk.json @@ -0,0 +1,38 @@ +{ + "id": "zendesk", + "name": "Zendesk", + "description": "Support tickets, customers, agents, and help desk operations.", + "categories": [ + "Support", + "Operations" + ], + "appUrl": "https://www.zendesk.com", + "docsUrl": "https://developer.zendesk.com/documentation/apps/getting-started/oauth/", + "notes": "Strong support automation use case for agents.", + "popularityRank": 26, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://{subdomain}.zendesk.com/oauth2/authorize", + "tokenUrl": "https://{subdomain}.zendesk.com/oauth2/token", + "scopes": [ + "tickets:read" + ] + } + }, + "http": { + "apiBaseUrl": "https://{subdomain}.zendesk.com/api/v2", + "defaultTool": { + "name": "list_tickets", + "description": "List Zendesk tickets for the connected account.", + "method": "GET", + "path": "/tickets" + } + } + } + ] +} diff --git a/integrations/catalog/zoom.json b/integrations/catalog/zoom.json new file mode 100644 index 00000000..7252853a --- /dev/null +++ b/integrations/catalog/zoom.json @@ -0,0 +1,38 @@ +{ + "id": "zoom", + "name": "Zoom", + "description": "Meetings, recordings, webinars, and scheduling operations.", + "categories": [ + "Meetings", + "Calendar" + ], + "appUrl": "https://zoom.us", + "docsUrl": "https://developers.zoom.us/docs/integrations/oauth/", + "notes": "Frequently requested for scheduling and meeting summaries.", + "popularityRank": 31, + "connectionOptions": [ + { + "id": "oauth", + "provider": "http", + "auth": { + "strategy": "oauth2", + "oauth": { + "authorizationUrl": "https://zoom.us/oauth/authorize", + "tokenUrl": "https://zoom.us/oauth/token", + "scopes": [ + "meeting:read:user" + ] + } + }, + "http": { + "apiBaseUrl": "https://api.zoom.us/v2", + "defaultTool": { + "name": "list_meetings", + "description": "List Zoom meetings for the connected user.", + "method": "GET", + "path": "/users/me/meetings" + } + } + } + ] +} diff --git a/integrations/index.d.ts b/integrations/index.d.ts index 85fc85ee..a10a5933 100644 --- a/integrations/index.d.ts +++ b/integrations/index.d.ts @@ -88,52 +88,9 @@ export interface IntegrationConnectionOption { auth: IntegrationAuthConfig; } -export interface OAuthProviderRegistrationDefaults { - provider?: IntegrationProvider; - authModes?: IntegrationAuthStrategy[]; - authStrategy?: IntegrationAuthStrategy; - credentialLabel?: string; - credentialPlaceholder?: string; - credentialHelp?: string; - apiKeyHeaderName?: string; - apiBaseUrl?: string; - serverUrl?: string; - openApiUrl?: string; - authorizationUrl?: string; - tokenUrl?: string; - scopes?: string[]; - optionalScopes?: string[]; - toolScopes?: string[]; - scopeSeparator?: "space" | "comma"; - pkce?: boolean; - clientAuthentication?: "basic" | "body" | "none"; - registrationUrl?: string; - additionalAuthorizationParams?: Record; - additionalTokenParams?: Record; - toolName?: string; - toolDescription?: string; - requestMethod?: string; - requestPath?: string; -} - -export interface OAuthProviderCatalogOption { - slug: string; - name: string; - description: string; - categories: string[]; - authStrategy: IntegrationAuthStrategy; - availability: "oauth_ready" | "manual_token" | "planned"; - managedConnectorSlug?: string; - appUrl?: string; - docsUrl?: string; - notes: string; - popularityRank: number; - registrationDefaults?: OAuthProviderRegistrationDefaults; -} export interface IntegrationCatalogEntry { id: string; - kind: IntegrationProvider; name: string; description: string; categories?: string[]; @@ -142,28 +99,35 @@ export interface IntegrationCatalogEntry { notes?: string; iconBg?: string; iconColor?: string; + logoUrl?: string; keywords?: string[]; popularityRank?: number; - runtimeAvailability?: "all" | "local"; - catalogStatus?: "oauth_ready" | "manual_token" | "planned"; - managedConnectorSlug?: string; - authStrategy?: IntegrationAuthStrategy; installHint?: string; - defaultConnectionOptionId?: string; connectionOptions: IntegrationConnectionOption[]; - registrationDefaults?: OAuthProviderRegistrationDefaults; } -export const INTEGRATION_CATALOG: IntegrationCatalogEntry[]; -export function listOAuthProviderCatalog(): OAuthProviderCatalogOption[]; -export function getOAuthProviderRegistrationDefaults( - slug: string, -): OAuthProviderRegistrationDefaults | undefined; - -export const hubspotMcpServerUrl: string; -export const hubspotMcpAuthorizationUrl: string; -export const hubspotMcpTokenUrl: string; -export const hubspotRequiredScopes: readonly string[]; -export const hubspotOptionalScopes: readonly string[]; +/** + * Filter for {@link listIntegrationCatalog}. Each dimension is tri-state: + * `true` keeps only entries that support that connector type, `false` keeps + * only entries that do not, and `undefined` leaves that dimension unfiltered. + */ +export interface IntegrationCatalogFilter { + /** Filter on whether the entry exposes at least one `mcp` connector. */ + mcp?: boolean; + /** Filter on whether the entry exposes at least one `oauth2` connector. */ + oauth?: boolean; +} +export const INTEGRATION_CATALOG: IntegrationCatalogEntry[]; +/** + * Return the full integration catalog, optionally filtered by connector type. + * Reads the generated static import index over `integrations/catalog/.json`. + * Returns the cached array; callers must treat it as read-only. + */ +export function listIntegrationCatalog( + filter?: IntegrationCatalogFilter, +): IntegrationCatalogEntry[]; +export function getIntegrationCatalogEntry( + id: string, +): IntegrationCatalogEntry | undefined; export default INTEGRATION_CATALOG; diff --git a/integrations/index.js b/integrations/index.js index 99632b50..175cf226 100644 --- a/integrations/index.js +++ b/integrations/index.js @@ -1,212 +1,34 @@ -import airtable from "./catalog/airtable.json" with { type: "json" }; -import apify from "./catalog/apify.json" with { type: "json" }; -import atlassian from "./catalog/atlassian.json" with { type: "json" }; -import brave_search from "./catalog/brave-search.json" with { type: "json" }; -import browser_mcp from "./catalog/browser-mcp.json" with { type: "json" }; -import clickhouse from "./catalog/clickhouse.json" with { type: "json" }; -import cloudflare_bindings from "./catalog/cloudflare-bindings.json" with { type: "json" }; -import cloudflare_browser_rendering from "./catalog/cloudflare-browser-rendering.json" with { type: "json" }; -import cloudflare_builds from "./catalog/cloudflare-builds.json" with { type: "json" }; -import cloudflare_docs from "./catalog/cloudflare-docs.json" with { type: "json" }; -import cloudflare_observability from "./catalog/cloudflare-observability.json" with { type: "json" }; -import deepwiki from "./catalog/deepwiki.json" with { type: "json" }; -import elevenlabs from "./catalog/elevenlabs.json" with { type: "json" }; -import everything from "./catalog/everything.json" with { type: "json" }; -import exa from "./catalog/exa.json" with { type: "json" }; -import fetch from "./catalog/fetch.json" with { type: "json" }; -import figma from "./catalog/figma.json" with { type: "json" }; -import filesystem from "./catalog/filesystem.json" with { type: "json" }; -import firecrawl from "./catalog/firecrawl.json" with { type: "json" }; -import git from "./catalog/git.json" with { type: "json" }; -import github from "./catalog/github.json" with { type: "json" }; -import huggingface from "./catalog/huggingface.json" with { type: "json" }; -import kagi from "./catalog/kagi.json" with { type: "json" }; -import linear from "./catalog/linear.json" with { type: "json" }; -import memory from "./catalog/memory.json" with { type: "json" }; -import mongodb from "./catalog/mongodb.json" with { type: "json" }; -import neon from "./catalog/neon.json" with { type: "json" }; -import notion from "./catalog/notion.json" with { type: "json" }; -import obsidian from "./catalog/obsidian.json" with { type: "json" }; -import paypal from "./catalog/paypal.json" with { type: "json" }; -import playwright from "./catalog/playwright.json" with { type: "json" }; -import redis from "./catalog/redis.json" with { type: "json" }; -import resend from "./catalog/resend.json" with { type: "json" }; -import sentry from "./catalog/sentry.json" with { type: "json" }; -import sequential_thinking from "./catalog/sequential-thinking.json" with { type: "json" }; -import slack from "./catalog/slack.json" with { type: "json" }; -import stripe from "./catalog/stripe.json" with { type: "json" }; -import supabase from "./catalog/supabase.json" with { type: "json" }; -import tavily from "./catalog/tavily.json" with { type: "json" }; -import time from "./catalog/time.json" with { type: "json" }; -import { listOAuthProviderCatalog } from "./oauth-provider-catalog.js"; -export { listOAuthProviderCatalog } from "./oauth-provider-catalog.js"; -export { - getOAuthProviderRegistrationDefaults, - hubspotMcpAuthorizationUrl, - hubspotMcpServerUrl, - hubspotMcpTokenUrl, - hubspotOptionalScopes, - hubspotRequiredScopes, -} from "./oauth-provider-registration-defaults.js"; - -const DIRECT_INTEGRATIONS = [ - airtable, - apify, - atlassian, - brave_search, - browser_mcp, - clickhouse, - cloudflare_bindings, - cloudflare_browser_rendering, - cloudflare_builds, - cloudflare_docs, - cloudflare_observability, - deepwiki, - elevenlabs, - everything, - exa, - fetch, - figma, - filesystem, - firecrawl, - git, - github, - huggingface, - kagi, - linear, - memory, - mongodb, - neon, - notion, - obsidian, - paypal, - playwright, - redis, - resend, - sentry, - sequential_thinking, - slack, - stripe, - supabase, - tavily, - time, -]; - -const optionIdForDefaults = (defaults) => { - const strategy = defaults?.authStrategy ?? (defaults?.authorizationUrl || defaults?.tokenUrl ? "oauth2" : "oauth2"); - if (strategy === "oauth2") return "oauth"; - if (strategy === "none") return "none"; - return "api"; -}; - -const providerConnectionOption = (provider) => { - const defaults = provider.registrationDefaults; - if (!defaults) return null; - const option = { - id: optionIdForDefaults(defaults), - provider: defaults.provider ?? (defaults.serverUrl ? "mcp" : "http"), - auth: { - strategy: defaults.authStrategy ?? provider.authStrategy ?? "oauth2", - authModes: defaults.authModes, - credentialLabel: defaults.credentialLabel, - credentialPlaceholder: defaults.credentialPlaceholder, - credentialHelp: defaults.credentialHelp, - apiKeyHeaderName: defaults.apiKeyHeaderName, - oauth: defaults.authorizationUrl || defaults.tokenUrl ? { - authorizationUrl: defaults.authorizationUrl, - tokenUrl: defaults.tokenUrl, - scopes: defaults.scopes ?? [], - optionalScopes: defaults.optionalScopes, - toolScopes: defaults.toolScopes, - scopeSeparator: defaults.scopeSeparator, - pkce: defaults.pkce, - clientAuthentication: defaults.clientAuthentication, - registrationUrl: defaults.registrationUrl, - additionalAuthorizationParams: defaults.additionalAuthorizationParams, - additionalTokenParams: defaults.additionalTokenParams, - } : undefined, - }, - }; - - if (option.provider === "mcp") { - option.transport = { kind: "shttp", url: defaults.serverUrl }; - } else { - option.http = { - apiBaseUrl: defaults.apiBaseUrl, - openApiUrl: defaults.openApiUrl, - defaultTool: defaults.toolName ? { - name: defaults.toolName, - description: defaults.toolDescription, - method: defaults.requestMethod, - path: defaults.requestPath, - scopes: defaults.toolScopes, - } : undefined, - }; - } - - return option; -}; - -const providerIntegration = (provider) => { - const option = providerConnectionOption(provider); - return { - id: provider.slug, - kind: option?.provider ?? "http", - name: provider.name, - description: provider.description, - categories: provider.categories, - appUrl: provider.appUrl, - docsUrl: provider.docsUrl, - notes: provider.notes, - catalogStatus: provider.availability, - managedConnectorSlug: provider.managedConnectorSlug, - authStrategy: provider.authStrategy, - popularityRank: provider.popularityRank, - registrationDefaults: provider.registrationDefaults, - ...(option ? { defaultConnectionOptionId: option.id, connectionOptions: [option] } : { connectionOptions: [] }), - }; -}; - -const mergeOptions = (left = [], right = []) => { - const options = new Map(); - for (const option of left) options.set(option.id, option); - for (const option of right) { - if (!options.has(option.id)) options.set(option.id, option); - } - return [...options.values()]; -}; - -const mergeIntegration = (base, override) => { - const connectionOptions = mergeOptions(base.connectionOptions, override.connectionOptions); - return { - ...base, - ...override, - catalogStatus: base.catalogStatus ?? override.catalogStatus, - managedConnectorSlug: base.managedConnectorSlug ?? override.managedConnectorSlug, - authStrategy: base.authStrategy ?? override.authStrategy, - registrationDefaults: base.registrationDefaults ?? override.registrationDefaults, - connectionOptions, - defaultConnectionOptionId: - base.defaultConnectionOptionId ?? override.defaultConnectionOptionId ?? connectionOptions[0]?.id, - }; +/** + * Runtime integration catalog. + * + * The source of truth is the hand-authored `integrations/catalog/.json` + * directory. `catalog-index.js` is generated from that directory so the JS + * package can statically import each JSON file without an aggregate JSON asset. + */ +import { INTEGRATION_CATALOG_ENTRIES } from "./catalog-index.js"; + +const INTEGRATIONS = INTEGRATION_CATALOG_ENTRIES; +const INTEGRATION_BY_ID = new Map(INTEGRATIONS.map((entry) => [entry.id, entry])); + +const entrySupportsMcp = (entry) => + entry.connectionOptions.some((option) => option.provider === "mcp"); + +const entrySupportsOauth = (entry) => + entry.connectionOptions.some((option) => option.auth?.strategy === "oauth2"); + +export const listIntegrationCatalog = (filter) => { + if (!filter) return INTEGRATIONS; + const { mcp, oauth } = filter; + if (mcp === undefined && oauth === undefined) return INTEGRATIONS; + return INTEGRATIONS.filter((entry) => { + const mcpOk = mcp === undefined || entrySupportsMcp(entry) === mcp; + const oauthOk = oauth === undefined || entrySupportsOauth(entry) === oauth; + return mcpOk && oauthOk; + }); }; -const entriesById = new Map(); -for (const provider of listOAuthProviderCatalog().map(providerIntegration)) { - entriesById.set(provider.id, provider); -} -for (const direct of DIRECT_INTEGRATIONS) { - entriesById.set( - direct.id, - entriesById.has(direct.id) - ? mergeIntegration(entriesById.get(direct.id), direct) - : direct, - ); -} +export const getIntegrationCatalogEntry = (id) => INTEGRATION_BY_ID.get(id); -export const INTEGRATION_CATALOG = [...entriesById.values()].sort((a, b) => { - const rankDelta = (b.popularityRank ?? 0) - (a.popularityRank ?? 0); - return rankDelta || a.name.localeCompare(b.name); -}); +export const INTEGRATION_CATALOG = INTEGRATIONS; export default INTEGRATION_CATALOG; diff --git a/integrations/logos.d.ts b/integrations/logos.d.ts deleted file mode 100644 index f551a07b..00000000 --- a/integrations/logos.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ReactNode } from "react"; - -export const INTEGRATION_FALLBACK_LOGO: ReactNode; -export const INTEGRATION_LOGOS: Record; -export const INTEGRATION_LOGO_IDS: Set; diff --git a/integrations/logos.js b/integrations/logos.js deleted file mode 100644 index 81e94f48..00000000 --- a/integrations/logos.js +++ /dev/null @@ -1,94 +0,0 @@ -import { createElement } from "react"; -import { - BookOpen, - Bot, - Brain, - Clock, - Database, - Flame, - Folder, - GitBranch, - Globe, - ListTree, - MousePointerClick, - Search, - Sparkles, - Telescope, - TestTube, -} from "lucide-react"; -import { - SiAirtable, - SiAtlassian, - SiBrave, - SiClickhouse, - SiCloudflare, - SiElevenlabs, - SiFigma, - SiGithub, - SiHuggingface, - SiKagi, - SiLinear, - SiMongodb, - SiNotion, - SiObsidian, - SiPaypal, - SiRedis, - SiResend, - SiSentry, - SiSlack, - SiStripe, - SiSupabase, -} from "react-icons/si"; - -const LOGO = "h-5 w-5"; - -const simpleIcon = (Icon) => createElement(Icon, { className: LOGO }); -const lucideIcon = (Icon) => - createElement(Icon, { className: LOGO, strokeWidth: 2.25 }); - -export const INTEGRATION_FALLBACK_LOGO = lucideIcon(Bot); - -export const INTEGRATION_LOGOS = { - github: simpleIcon(SiGithub), - slack: simpleIcon(SiSlack), - tavily: createElement(Search, { className: LOGO, strokeWidth: 2.5 }), - linear: simpleIcon(SiLinear), - notion: simpleIcon(SiNotion), - atlassian: simpleIcon(SiAtlassian), - sentry: simpleIcon(SiSentry), - stripe: simpleIcon(SiStripe), - paypal: simpleIcon(SiPaypal), - "cloudflare-docs": simpleIcon(SiCloudflare), - "cloudflare-bindings": simpleIcon(SiCloudflare), - "cloudflare-observability": simpleIcon(SiCloudflare), - huggingface: simpleIcon(SiHuggingface), - deepwiki: simpleIcon(BookOpen), - git: lucideIcon(GitBranch), - "brave-search": simpleIcon(SiBrave), - exa: lucideIcon(Telescope), - firecrawl: lucideIcon(Flame), - apify: lucideIcon(Bot), - fetch: lucideIcon(Globe), - "browser-mcp": lucideIcon(MousePointerClick), - playwright: lucideIcon(TestTube), - supabase: simpleIcon(SiSupabase), - neon: lucideIcon(Database), - mongodb: simpleIcon(SiMongodb), - redis: simpleIcon(SiRedis), - filesystem: lucideIcon(Folder), - memory: lucideIcon(Brain), - "sequential-thinking": lucideIcon(ListTree), - time: lucideIcon(Clock), - everything: lucideIcon(Sparkles), - figma: simpleIcon(SiFigma), - airtable: simpleIcon(SiAirtable), - obsidian: simpleIcon(SiObsidian), - elevenlabs: simpleIcon(SiElevenlabs), - resend: simpleIcon(SiResend), - "cloudflare-builds": simpleIcon(SiCloudflare), - "cloudflare-browser-rendering": simpleIcon(SiCloudflare), - kagi: simpleIcon(SiKagi), - clickhouse: simpleIcon(SiClickhouse), -}; - -export const INTEGRATION_LOGO_IDS = new Set(Object.keys(INTEGRATION_LOGOS)); diff --git a/integrations/oauth-provider-catalog.js b/integrations/oauth-provider-catalog.js deleted file mode 100644 index 4d240bde..00000000 --- a/integrations/oauth-provider-catalog.js +++ /dev/null @@ -1,553 +0,0 @@ -import { getOAuthProviderRegistrationDefaults } from "./oauth-provider-registration-defaults.js"; -const provider = (popularityRank, option) => { - const registrationDefaults = getOAuthProviderRegistrationDefaults(option.slug); - - return { - ...option, - authStrategy: option.authStrategy ?? registrationDefaults?.authStrategy ?? "oauth2", - popularityRank, - registrationDefaults, - }; -}; - -const oauthProviderCatalog = [ - provider(1, { - slug: "github", - name: "GitHub", - description: "Source control, issues, pull requests, and developer workflows.", - categories: ["Engineering", "Source control"], - availability: "manual_token", - managedConnectorSlug: "github", - appUrl: "https://github.com", - docsUrl: "https://docs.github.com/apps/oauth-apps/building-oauth-apps", - notes: "Current managed connector works with bearer tokens today; GitHub OAuth is a strong candidate for a future one-click connect flow.", - }), - provider(2, { - slug: "google-docs", - name: "Google Docs", - description: "Docs authoring and Google Workspace document automation.", - categories: ["Documents", "Knowledge base"], - availability: "manual_token", - managedConnectorSlug: "google-docs", - appUrl: "https://workspace.google.com/products/docs/", - docsUrl: "https://developers.google.com/identity/protocols/oauth2", - notes: "Current managed connector accepts a Google access token manually; a full OAuth connect flow can remove token copy/paste.", - }), - provider(3, { - slug: "slack", - name: "Slack", - description: "Channels, messaging, workflows, canvases, and operational collaboration.", - categories: ["Communication", "Operations"], - availability: "oauth_ready", - managedConnectorSlug: "slack", - appUrl: "https://slack.com", - docsUrl: "https://docs.slack.dev/ai/slack-mcp-server", - notes: "Uses Slack's official hosted MCP server with confidential OAuth user-token auth.", - }), - provider(4, { - slug: "notion", - name: "Notion", - description: "Workspace search, pages, databases, and knowledge management.", - categories: ["Knowledge base", "Documentation"], - availability: "oauth_ready", - managedConnectorSlug: "notion", - appUrl: "https://www.notion.so", - docsUrl: "https://developers.notion.com/docs/authorization", - notes: "", - }), - provider(5, { - slug: "figma", - name: "Figma", - description: "Design files, nodes, and developer handoff automation.", - categories: ["Design", "Frontend"], - availability: "oauth_ready", - managedConnectorSlug: "figma", - appUrl: "https://www.figma.com", - docsUrl: "https://www.figma.com/developers/api#oauth2", - notes: "", - }), - provider(6, { - slug: "google-drive", - name: "Google Drive", - description: "File search, metadata, folders, and document discovery.", - categories: ["Storage", "Documents"], - availability: "planned", - appUrl: "https://workspace.google.com/products/drive/", - docsUrl: "https://developers.google.com/identity/protocols/oauth2", - notes: "Natural expansion of the Google Workspace surface beyond Google Docs.", - }), - provider(7, { - slug: "google-sheets", - name: "Google Sheets", - description: "Spreadsheet reads, writes, formulas, and reporting workflows.", - categories: ["Spreadsheets", "Analytics"], - availability: "planned", - appUrl: "https://workspace.google.com/products/sheets/", - docsUrl: "https://developers.google.com/identity/protocols/oauth2", - notes: "High-value automation surface for agent-driven reporting and structured edits.", - }), - provider(8, { - slug: "gmail", - name: "Gmail", - description: "Mailbox search, drafting, sending, and thread triage.", - categories: ["Communication", "Email"], - availability: "planned", - appUrl: "https://workspace.google.com/products/gmail/", - docsUrl: "https://developers.google.com/identity/protocols/oauth2", - notes: "Popular agent workflow target for inbox triage and drafting.", - }), - provider(9, { - slug: "google-calendar", - name: "Google Calendar", - description: "Calendar search, event scheduling, and meeting coordination.", - categories: ["Calendar", "Scheduling"], - availability: "planned", - appUrl: "https://workspace.google.com/products/calendar/", - docsUrl: "https://developers.google.com/identity/protocols/oauth2", - notes: "Strong personal productivity use case with mature OAuth flows.", - }), - provider(10, { - slug: "jira", - name: "Jira", - description: "Issue tracking, sprint workflows, and engineering program management.", - categories: ["Project management", "Engineering"], - availability: "planned", - appUrl: "https://www.atlassian.com/software/jira", - docsUrl: "https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/", - notes: "Very common MCP target for software teams and ticket automation.", - }), - provider(11, { - slug: "confluence", - name: "Confluence", - description: "Wiki pages, spaces, and internal documentation search.", - categories: ["Documentation", "Knowledge base"], - availability: "planned", - appUrl: "https://www.atlassian.com/software/confluence", - docsUrl: "https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/", - notes: "Natural companion to Jira for software teams.", - }), - provider(12, { - slug: "linear", - name: "Linear", - description: "Issue tracking, roadmap planning, and product engineering workflows.", - categories: ["Project management", "Engineering"], - availability: "planned", - appUrl: "https://linear.app", - docsUrl: "https://linear.app/developers/oauth-authentication", - notes: "Popular among modern product engineering teams and already useful to this repo's users.", - }), - provider(13, { - slug: "asana", - name: "Asana", - description: "Task management, projects, and work tracking.", - categories: ["Project management", "Operations"], - availability: "planned", - appUrl: "https://asana.com", - docsUrl: "https://developers.asana.com/docs/oauth", - notes: "Broad business adoption and straightforward OAuth app model.", - }), - provider(14, { - slug: "trello", - name: "Trello", - description: "Boards, cards, checklists, and lightweight project planning.", - categories: ["Project management", "Operations"], - availability: "planned", - appUrl: "https://trello.com", - docsUrl: "https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/", - notes: "Useful for SMB and cross-functional task boards, but Trello's public API auth is a special-case flow rather than a straightforward generic OAuth2 connector.", - }), - provider(15, { - slug: "clickup", - name: "ClickUp", - description: "Tasks, docs, goals, and workflow automation.", - categories: ["Project management", "Operations"], - availability: "planned", - appUrl: "https://clickup.com", - docsUrl: "https://clickup.com/api/developer-portal/authentication", - notes: "Large install base and broad operations coverage.", - }), - provider(16, { - slug: "monday", - name: "Monday.com", - description: "Boards, automations, and work management across teams.", - categories: ["Project management", "Operations"], - availability: "planned", - appUrl: "https://monday.com", - docsUrl: "https://developer.monday.com/apps/docs/oauth", - notes: "High-demand operations platform with rich board APIs.", - }), - provider(17, { - slug: "airtable", - name: "Airtable", - description: "Bases, records, linked data, and workflow automation.", - categories: ["Database", "Operations"], - availability: "planned", - appUrl: "https://airtable.com", - docsUrl: "https://airtable.com/developers/web/api/oauth-reference", - notes: "Very common internal-tools and operations automation surface.", - }), - provider(18, { - slug: "dropbox", - name: "Dropbox", - description: "Cloud files, folders, content access, and sharing.", - categories: ["Storage", "Documents"], - availability: "planned", - appUrl: "https://www.dropbox.com", - docsUrl: "https://developers.dropbox.com/oauth-guide", - notes: "Popular file automation target with mature OAuth support.", - }), - provider(19, { - slug: "box", - name: "Box", - description: "Enterprise file storage, metadata, and collaboration.", - categories: ["Storage", "Enterprise"], - availability: "planned", - appUrl: "https://www.box.com", - docsUrl: "https://developer.box.com/guides/authentication/oauth2/", - notes: "Strong enterprise document-management footprint.", - }), - provider(20, { - slug: "microsoft-outlook", - name: "Microsoft Outlook", - description: "Mail, calendar, contacts, and meeting workflows through Microsoft Graph.", - categories: ["Email", "Calendar"], - availability: "planned", - appUrl: "https://www.microsoft.com/microsoft-365/outlook/email-and-calendar-software-microsoft-outlook", - docsUrl: "https://learn.microsoft.com/graph/auth-v2-user", - notes: "High-value Microsoft Graph integration surface for enterprise users.", - }), - provider(21, { - slug: "microsoft-teams", - name: "Microsoft Teams", - description: "Chats, channels, meetings, and collaboration via Microsoft Graph.", - categories: ["Communication", "Enterprise"], - availability: "planned", - appUrl: "https://www.microsoft.com/microsoft-teams/group-chat-software", - docsUrl: "https://learn.microsoft.com/graph/auth-v2-user", - notes: "Common enterprise chat target alongside Outlook.", - }), - provider(22, { - slug: "onedrive", - name: "OneDrive", - description: "Cloud file access and document workflows through Microsoft Graph.", - categories: ["Storage", "Documents"], - availability: "planned", - appUrl: "https://www.microsoft.com/microsoft-365/onedrive/online-cloud-storage", - docsUrl: "https://learn.microsoft.com/graph/auth-v2-user", - notes: "Useful for enterprise file automation and search.", - }), - provider(23, { - slug: "sharepoint", - name: "SharePoint", - description: "Sites, document libraries, lists, and intranet content.", - categories: ["Knowledge base", "Enterprise"], - availability: "planned", - appUrl: "https://www.microsoft.com/microsoft-365/sharepoint/collaboration", - docsUrl: "https://learn.microsoft.com/graph/auth-v2-user", - notes: "Frequently requested for enterprise knowledge retrieval.", - }), - provider(24, { - slug: "salesforce", - name: "Salesforce", - description: "CRM records, accounts, opportunities, and sales operations.", - categories: ["CRM", "Sales"], - availability: "planned", - appUrl: "https://www.salesforce.com", - docsUrl: "https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5", - notes: "Very common enterprise CRM and agent-assist target.", - }), - provider(25, { - slug: "hubspot", - name: "HubSpot", - description: "CRM, marketing, tickets, and customer lifecycle workflows.", - categories: ["CRM", "Marketing"], - availability: "oauth_ready", - managedConnectorSlug: "hubspot", - appUrl: "https://www.hubspot.com", - docsUrl: - "https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server", - notes: "Uses HubSpot's official remote MCP server plus MCP auth apps with PKCE.", - }), - provider(26, { - slug: "zendesk", - name: "Zendesk", - description: "Support tickets, customers, agents, and help desk operations.", - categories: ["Support", "Operations"], - availability: "planned", - appUrl: "https://www.zendesk.com", - docsUrl: "https://developer.zendesk.com/documentation/apps/getting-started/oauth/", - notes: "Strong support automation use case for agents.", - }), - provider(27, { - slug: "intercom", - name: "Intercom", - description: "Customer support, conversations, inboxes, and CRM context.", - categories: ["Support", "CRM"], - availability: "planned", - appUrl: "https://www.intercom.com", - docsUrl: "https://developers.intercom.com/building-apps/docs/authentication-types", - notes: "Useful for customer-facing assistant workflows.", - }), - provider(28, { - slug: "stripe", - name: "Stripe", - description: "Payments, customers, invoices, and subscription operations.", - categories: ["Payments", "Finance"], - availability: "planned", - appUrl: "https://stripe.com", - docsUrl: "https://docs.stripe.com/connect/oauth-reference", - notes: "High-value fintech automation target with mature OAuth patterns.", - }), - provider(29, { - slug: "shopify", - name: "Shopify", - description: "Storefronts, products, orders, and ecommerce operations.", - categories: ["Ecommerce", "Operations"], - availability: "planned", - appUrl: "https://www.shopify.com", - docsUrl: "https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/authorization-code-grant", - notes: "A top ecommerce platform and strong MCP candidate.", - }), - provider(30, { - slug: "discord", - name: "Discord", - description: "Guilds, channels, messages, and community operations.", - categories: ["Communication", "Community"], - availability: "planned", - appUrl: "https://discord.com", - docsUrl: "https://discord.com/developers/docs/topics/oauth2", - notes: "Useful for community automation and bot-assisted workflows.", - }), - provider(31, { - slug: "zoom", - name: "Zoom", - description: "Meetings, recordings, webinars, and scheduling operations.", - categories: ["Meetings", "Calendar"], - availability: "planned", - appUrl: "https://zoom.us", - docsUrl: "https://developers.zoom.us/docs/integrations/oauth/", - notes: "Frequently requested for scheduling and meeting summaries.", - }), - provider(32, { - slug: "webflow", - name: "Webflow", - description: "CMS items, sites, and web publishing workflows.", - categories: ["CMS", "Marketing"], - availability: "oauth_ready", - managedConnectorSlug: "webflow", - appUrl: "https://webflow.com", - docsUrl: "https://developers.webflow.com/mcp/reference/getting-started", - notes: - "Uses Webflow's official hosted MCP server and deployment-scoped MCP OAuth registration so each user can authorize their own sites and workspaces.", - }), - provider(33, { - slug: "miro", - name: "Miro", - description: "Whiteboards, diagrams, notes, and workshop collaboration.", - categories: ["Whiteboard", "Collaboration"], - availability: "planned", - appUrl: "https://miro.com", - docsUrl: "https://developers.miro.com/docs/rest-api-build-your-first-oauth-app", - notes: "Strong visual collaboration target for design and product teams.", - }), - provider(34, { - slug: "canva", - name: "Canva", - description: "Design content, brand assets, and marketing collateral workflows.", - categories: ["Design", "Marketing"], - availability: "planned", - appUrl: "https://www.canva.com", - docsUrl: "https://www.canva.dev/docs/connect/oauth/", - notes: "Broad creator and marketing adoption with OAuth-based apps.", - }), - provider(35, { - slug: "datadog", - name: "Datadog", - description: "Logs, metrics, monitors, incidents, and observability workflows.", - categories: ["Observability", "Operations"], - availability: "oauth_ready", - managedConnectorSlug: "datadog", - appUrl: "https://www.datadoghq.com", - docsUrl: "https://docs.datadoghq.com/bits_ai/mcp_server/setup/", - notes: - "Uses Datadog's official hosted MCP server. The default registration targets the US1 endpoint and can be edited for other Datadog sites.", - }), - provider(36, { - slug: "sentry", - name: "Sentry", - description: "Errors, releases, issue ownership, and incident investigation.", - categories: ["Observability", "Engineering"], - availability: "planned", - appUrl: "https://sentry.io", - docsUrl: "https://docs.sentry.io/api/guides/oauth/", - notes: "Natural target for engineering support agents.", - }), - provider(37, { - slug: "posthog", - name: "PostHog", - description: "Product analytics, feature flags, and session insights.", - categories: ["Analytics", "Product"], - availability: "planned", - appUrl: "https://posthog.com", - docsUrl: "https://posthog.com/docs/apps/build/oauth", - notes: "Relevant for product analytics and experimentation workflows.", - }), - provider(38, { - slug: "supabase", - name: "Supabase", - description: "Projects, databases, auth, and storage administration.", - categories: ["Database", "Developer tools"], - availability: "planned", - appUrl: "https://supabase.com", - docsUrl: "https://supabase.com/docs/guides/integrations/build-a-supabase-oauth-integration", - notes: "Popular developer platform with broad automation value and a documented management-API OAuth integration flow.", - }), - provider(39, { - slug: "vercel", - name: "Vercel", - description: "Deployments, projects, domains, and preview environment workflows.", - categories: ["Developer tools", "Deployment"], - availability: "planned", - appUrl: "https://vercel.com", - docsUrl: "https://vercel.com/docs/rest-api#oauth-apps", - notes: "Especially relevant to this repo's deployment model.", - }), - provider(40, { - slug: "netlify", - name: "Netlify", - description: "Sites, builds, deploy previews, and web operations.", - categories: ["Deployment", "Developer tools"], - availability: "planned", - appUrl: "https://www.netlify.com", - docsUrl: "https://docs.netlify.com/api/get-started/#oauth-applications", - notes: "Common alternative hosting/deployment automation target.", - }), - provider(41, { - slug: "plaid", - name: "Plaid", - description: "Financial account connectivity and transaction data flows.", - categories: ["Finance", "Payments"], - availability: "planned", - appUrl: "https://plaid.com", - docsUrl: "https://plaid.com/docs/auth/oauth/", - notes: "Strong fintech workflow candidate, although much of Plaid's OAuth guidance is a special-case token exchange rather than a generic end-user SaaS OAuth connector.", - }), - provider(42, { - slug: "okta", - name: "Okta", - description: "Identity, users, applications, and access administration.", - categories: ["Identity", "Enterprise"], - availability: "planned", - appUrl: "https://www.okta.com", - docsUrl: "https://developer.okta.com/docs/guides/implement-oauth-for-okta/main/", - notes: "High-value admin and security automation surface.", - }), - provider(43, { - slug: "servicenow", - name: "ServiceNow", - description: "Tickets, incidents, service catalogs, and enterprise workflows.", - categories: ["ITSM", "Enterprise"], - availability: "planned", - appUrl: "https://www.servicenow.com", - docsUrl: "https://www.servicenow.com/docs/bundle/xanadu-platform-security/page/administer/security/concept/oauth-concept.html", - notes: "Strong enterprise IT operations use case.", - }), - provider(44, { - slug: "freshdesk", - name: "Freshdesk", - description: "Support tickets, customer records, and help desk workflows.", - categories: ["Support", "Operations"], - availability: "planned", - appUrl: "https://www.freshworks.com/freshdesk/", - docsUrl: "https://developers.freshdesk.com/api/#authentication", - notes: "Popular support platform for SMB and mid-market teams.", - }), - provider(45, { - slug: "pipedrive", - name: "Pipedrive", - description: "Deals, contacts, and sales pipeline management.", - categories: ["CRM", "Sales"], - availability: "planned", - appUrl: "https://www.pipedrive.com", - docsUrl: "https://pipedrive.readme.io/docs/marketplace-oauth-authorization", - notes: "Common CRM choice with solid OAuth story.", - }), - provider(46, { - slug: "mailchimp", - name: "Mailchimp", - description: "Campaigns, audiences, automations, and email marketing.", - categories: ["Marketing", "Email"], - availability: "planned", - appUrl: "https://mailchimp.com", - docsUrl: "https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/", - notes: "Popular marketing automation target.", - }), - provider(47, { - slug: "quickbooks", - name: "QuickBooks", - description: "Accounting, invoices, customers, and financial operations.", - categories: ["Finance", "Accounting"], - availability: "planned", - appUrl: "https://quickbooks.intuit.com", - docsUrl: "https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0", - notes: "Frequent finance automation request with OAuth-based apps.", - }), - provider(48, { - slug: "xero", - name: "Xero", - description: "Accounting, contacts, invoices, and bookkeeping workflows.", - categories: ["Finance", "Accounting"], - availability: "planned", - appUrl: "https://www.xero.com", - docsUrl: "https://developer.xero.com/documentation/guides/oauth2/overview/", - notes: "Important accounting platform in many regions.", - }), - provider(49, { - slug: "gitlab", - name: "GitLab", - description: "Repositories, issues, merge requests, pipelines, and DevSecOps.", - categories: ["Engineering", "Source control"], - availability: "planned", - appUrl: "https://about.gitlab.com", - docsUrl: "https://docs.gitlab.com/integration/oauth_provider/", - notes: "Major Git hosting platform and natural expansion beyond GitHub.", - }), - provider(50, { - slug: "bitbucket", - name: "Bitbucket", - description: "Repositories, pull requests, and Atlassian engineering workflows.", - categories: ["Engineering", "Source control"], - availability: "planned", - appUrl: "https://bitbucket.org", - docsUrl: "https://developer.atlassian.com/cloud/bitbucket/oauth-2/", - notes: "Popular in Atlassian-centric teams and complements Jira/Confluence.", - }), - provider(51, { - slug: "elevenlabs", - name: "ElevenLabs", - description: "Text to speech, voices, transcription, audio generation, and agents.", - categories: ["Audio", "AI"], - authStrategy: "api_key", - availability: "manual_token", - managedConnectorSlug: "elevenlabs", - appUrl: "https://elevenlabs.io", - docsUrl: "https://elevenlabs.io/docs/api-reference/introduction", - notes: - "Official API-key connector backed by ElevenLabs' published OpenAPI spec.", - }), - provider(52, { - slug: "ordinal", - name: "Ordinal", - description: "Social media posts, profiles, analytics, labels, approvals, and engagements.", - categories: ["Social media", "Marketing"], - authStrategy: "bearer", - availability: "manual_token", - managedConnectorSlug: "ordinal", - appUrl: "https://app.tryordinal.com", - docsUrl: "https://docs.tryordinal.com/api/mcp", - notes: - "Official MCP server backed by Ordinal's API with API-key (Bearer) auth.", - }), -]; - -export const listOAuthProviderCatalog = () => oauthProviderCatalog; diff --git a/integrations/oauth-provider-registration-defaults.js b/integrations/oauth-provider-registration-defaults.js deleted file mode 100644 index ff6c4a4d..00000000 --- a/integrations/oauth-provider-registration-defaults.js +++ /dev/null @@ -1,610 +0,0 @@ -const googleAuthorizationUrl = "https://accounts.google.com/o/oauth2/v2/auth"; -const googleTokenUrl = "https://oauth2.googleapis.com/token"; -const microsoftAuthorizationUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; -const microsoftTokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; -const atlassianAuthorizationUrl = "https://auth.atlassian.com/authorize"; -const atlassianTokenUrl = "https://auth.atlassian.com/oauth/token"; - -const googleWorkspacePreset = ( - apiBaseUrl, - scopes, - toolName, - toolDescription, - requestPath, - requestMethod = "GET", -) => ({ - apiBaseUrl, - authorizationUrl: googleAuthorizationUrl, - tokenUrl: googleTokenUrl, - scopes, - toolName, - toolDescription, - requestMethod, - requestPath, -}); - -const microsoftGraphPreset = ( - scopes, - toolName, - toolDescription, - requestPath, -) => ({ - apiBaseUrl: "https://graph.microsoft.com/v1.0", - authorizationUrl: microsoftAuthorizationUrl, - tokenUrl: microsoftTokenUrl, - scopes, - toolName, - toolDescription, - requestMethod: "GET", - requestPath, -}); - -const atlassianPreset = ( - apiBaseUrl, - scopes, - toolName, - toolDescription, - requestPath, -) => ({ - apiBaseUrl, - authorizationUrl: atlassianAuthorizationUrl, - tokenUrl: atlassianTokenUrl, - scopes, - toolName, - toolDescription, - requestMethod: "GET", - requestPath, -}); - -export const hubspotMcpServerUrl = "https://mcp.hubspot.com"; -export const hubspotMcpAuthorizationUrl = `${hubspotMcpServerUrl}/oauth/authorize/user`; -export const hubspotMcpTokenUrl = `${hubspotMcpServerUrl}/oauth/v3/token`; - -const officialManagedMcpServerUrls = { - github: "https://api.githubcopilot.com/mcp/", - hubspot: hubspotMcpServerUrl, - slack: "https://mcp.slack.com/mcp", - webflow: "https://mcp.webflow.com/mcp", - datadog: "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp", - ordinal: "https://app.tryordinal.com/api/mcp", -}; - -export const hubspotRequiredScopes = [ - "oauth", - "crm.objects.contacts.read", -]; - -export const hubspotOptionalScopes = [ - "crm.objects.contacts.write", - "crm.objects.companies.read", - "crm.objects.companies.write", - "crm.objects.deals.read", - "crm.objects.deals.write", - "tickets", - "crm.objects.owners.read", - "crm.schemas.contacts.read", - "crm.schemas.companies.read", - "crm.schemas.deals.read", -]; - -const registrationDefaults = { - github: { - apiBaseUrl: "https://api.github.com", - authorizationUrl: "https://github.com/login/oauth/authorize", - tokenUrl: "https://github.com/login/oauth/access_token", - scopes: ["read:user", "repo"], - toolName: "get_authenticated_user", - toolDescription: "Fetch the authenticated GitHub user profile.", - requestMethod: "GET", - requestPath: "/user", - }, - "google-docs": googleWorkspacePreset( - "https://docs.googleapis.com", - ["https://www.googleapis.com/auth/documents.readonly"], - "get_document", - "Fetch a Google Docs document by ID.", - "/v1/documents/{documentId}", - ), - slack: { - provider: "mcp", - authorizationUrl: "https://slack.com/oauth/v2_user/authorize", - tokenUrl: "https://slack.com/api/oauth.v2.user.access", - scopes: [ - "search:read.public", - "search:read.private", - "search:read.mpim", - "search:read.im", - "search:read.files", - "search:read.users", - "chat:write", - "channels:history", - "groups:history", - "mpim:history", - "im:history", - "canvases:read", - "canvases:write", - "users:read", - "users:read.email", - ], - pkce: true, - clientAuthentication: "body", - }, - notion: { - provider: "http", - apiBaseUrl: "https://api.notion.com", - openApiUrl: "https://developers.notion.com/openapi.json", - authorizationUrl: "https://api.notion.com/v1/oauth/authorize", - tokenUrl: "https://api.notion.com/v1/oauth/token", - scopes: [], - toolName: "post_search", - toolDescription: "Search pages and databases in the connected Notion workspace.", - requestMethod: "POST", - requestPath: "/v1/search", - }, - figma: { - apiBaseUrl: "https://api.figma.com", - authorizationUrl: "https://www.figma.com/oauth", - tokenUrl: "https://api.figma.com/v1/oauth/token", - scopes: [ - "current_user:read", - "file_content:read", - "file_metadata:read", - "projects:read", - ], - toolName: "get_file", - toolDescription: "Fetch a Figma file by key.", - requestMethod: "GET", - requestPath: "/v1/files/{fileKey}", - }, - elevenlabs: { - provider: "http", - authModes: ["api_key"], - authStrategy: "api_key", - credentialLabel: "ElevenLabs API key", - credentialPlaceholder: "Paste your ElevenLabs API key", - credentialHelp: - "Personal or workspace ElevenLabs API key sent in the xi-api-key header.", - apiKeyHeaderName: "xi-api-key", - apiBaseUrl: "https://api.elevenlabs.io", - openApiUrl: "https://api.elevenlabs.io/openapi.json", - }, - "google-drive": googleWorkspacePreset( - "https://www.googleapis.com/drive/v3", - ["https://www.googleapis.com/auth/drive.metadata.readonly"], - "list_files", - "List Drive files visible to the connected user.", - "/files", - ), - "google-sheets": googleWorkspacePreset( - "https://sheets.googleapis.com", - ["https://www.googleapis.com/auth/spreadsheets.readonly"], - "get_spreadsheet", - "Fetch spreadsheet metadata and sheets by ID.", - "/v4/spreadsheets/{spreadsheetId}", - ), - gmail: googleWorkspacePreset( - "https://gmail.googleapis.com", - ["https://www.googleapis.com/auth/gmail.readonly"], - "list_messages", - "List Gmail messages for the authenticated user.", - "/gmail/v1/users/me/messages", - ), - "google-calendar": googleWorkspacePreset( - "https://www.googleapis.com/calendar/v3", - ["https://www.googleapis.com/auth/calendar.readonly"], - "list_events", - "List events from the user's primary Google Calendar.", - "/calendars/primary/events", - ), - jira: atlassianPreset( - "https://api.atlassian.com/ex/jira/{cloudId}", - ["read:jira-work"], - "list_projects", - "List Jira projects available to the connected user.", - "/rest/api/3/project/search", - ), - confluence: atlassianPreset( - "https://api.atlassian.com/ex/confluence/{cloudId}", - ["read:page:confluence"], - "list_spaces", - "List Confluence spaces available to the connected user.", - "/wiki/api/v2/spaces", - ), - linear: { - apiBaseUrl: "https://api.linear.app", - authorizationUrl: "https://linear.app/oauth/authorize", - tokenUrl: "https://api.linear.app/oauth/token", - scopes: ["read"], - toolName: "list_issues", - toolDescription: "Query issues from Linear via GraphQL.", - requestMethod: "POST", - requestPath: "/graphql", - }, - asana: { - apiBaseUrl: "https://app.asana.com/api/1.0", - authorizationUrl: "https://app.asana.com/-/oauth_authorize", - tokenUrl: "https://app.asana.com/-/oauth_token", - scopes: ["tasks:read"], - toolName: "list_tasks", - toolDescription: "List tasks visible to the connected Asana user.", - requestMethod: "GET", - requestPath: "/tasks", - }, - trello: { - apiBaseUrl: "https://api.trello.com/1", - toolName: "list_boards", - toolDescription: "List Trello boards visible to the authenticated member.", - requestMethod: "GET", - requestPath: "/members/me/boards", - }, - clickup: { - apiBaseUrl: "https://api.clickup.com/api/v2", - authorizationUrl: "https://app.clickup.com/api", - tokenUrl: "https://api.clickup.com/api/v2/oauth/token", - scopes: [], - toolName: "list_workspaces", - toolDescription: "List ClickUp workspaces available to the connected user.", - requestMethod: "GET", - requestPath: "/team", - }, - monday: { - apiBaseUrl: "https://api.monday.com/v2", - authorizationUrl: "https://auth.monday.com/oauth2/authorize", - tokenUrl: "https://auth.monday.com/oauth2/token", - scopes: ["boards:read"], - toolName: "list_boards", - toolDescription: "Query boards from Monday.com.", - requestMethod: "POST", - requestPath: "/", - }, - airtable: { - apiBaseUrl: "https://api.airtable.com/v0", - authorizationUrl: "https://airtable.com/oauth2/v1/authorize", - tokenUrl: "https://airtable.com/oauth2/v1/token", - scopes: ["schema.bases:read", "data.records:read"], - toolName: "list_bases", - toolDescription: "List Airtable bases the connected user granted access to.", - requestMethod: "GET", - requestPath: "/meta/bases", - }, - dropbox: { - apiBaseUrl: "https://api.dropboxapi.com/2", - authorizationUrl: "https://www.dropbox.com/oauth2/authorize", - tokenUrl: "https://api.dropboxapi.com/oauth2/token", - scopes: ["files.metadata.read"], - toolName: "list_root_folder", - toolDescription: "List entries in the root Dropbox folder.", - requestMethod: "POST", - requestPath: "/files/list_folder", - }, - box: { - apiBaseUrl: "https://api.box.com/2.0", - authorizationUrl: "https://account.box.com/api/oauth2/authorize", - tokenUrl: "https://api.box.com/oauth2/token", - scopes: ["root_readonly", "item_read"], - toolName: "list_root_items", - toolDescription: "List files and folders in the Box root folder.", - requestMethod: "GET", - requestPath: "/folders/0/items", - }, - "microsoft-outlook": microsoftGraphPreset( - ["Mail.Read"], - "list_messages", - "List Outlook messages for the signed-in user.", - "/me/messages", - ), - "microsoft-teams": microsoftGraphPreset( - ["Team.ReadBasic.All"], - "list_teams", - "List Microsoft Teams joined by the signed-in user.", - "/me/joinedTeams", - ), - onedrive: microsoftGraphPreset( - ["Files.Read"], - "list_drive_items", - "List OneDrive items in the root folder.", - "/me/drive/root/children", - ), - sharepoint: microsoftGraphPreset( - ["Sites.Read.All"], - "get_root_site", - "Fetch the SharePoint root site through Microsoft Graph.", - "/sites/root", - ), - salesforce: { - apiBaseUrl: "https://{instance}.salesforce.com/services/data/v60.0", - authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize", - tokenUrl: "https://login.salesforce.com/services/oauth2/token", - scopes: ["api"], - toolName: "list_accounts", - toolDescription: "List Salesforce accounts from the connected org.", - requestMethod: "GET", - requestPath: "/sobjects/Account", - }, - hubspot: { - provider: "mcp", - authorizationUrl: hubspotMcpAuthorizationUrl, - tokenUrl: hubspotMcpTokenUrl, - clientAuthentication: "body", - pkce: true, - scopes: [], - credentialHelp: - "Use the client ID and secret from a HubSpot MCP auth app (Development → MCP Auth Apps). Standard HubSpot OAuth apps and private apps will not authenticate with mcp.hubspot.com.", - }, - zendesk: { - apiBaseUrl: "https://{subdomain}.zendesk.com/api/v2", - authorizationUrl: "https://{subdomain}.zendesk.com/oauth2/authorize", - tokenUrl: "https://{subdomain}.zendesk.com/oauth2/token", - scopes: ["tickets:read"], - toolName: "list_tickets", - toolDescription: "List Zendesk tickets for the connected account.", - requestMethod: "GET", - requestPath: "/tickets", - }, - intercom: { - apiBaseUrl: "https://api.intercom.io", - authorizationUrl: "https://app.intercom.com/oauth", - tokenUrl: "https://api.intercom.io/auth/eagle/token", - scopes: ["read_users", "read_conversations"], - toolName: "list_contacts", - toolDescription: "List Intercom contacts for the connected workspace.", - requestMethod: "GET", - requestPath: "/contacts", - }, - stripe: { - apiBaseUrl: "https://api.stripe.com/v1", - authorizationUrl: "https://connect.stripe.com/oauth/authorize", - tokenUrl: "https://connect.stripe.com/oauth/token", - scopes: ["read_only"], - toolName: "list_customers", - toolDescription: "List Stripe customers from the connected account.", - requestMethod: "GET", - requestPath: "/customers", - }, - shopify: { - apiBaseUrl: "https://{shop}.myshopify.com/admin/api/2025-01", - authorizationUrl: "https://{shop}.myshopify.com/admin/oauth/authorize", - tokenUrl: "https://{shop}.myshopify.com/admin/oauth/access_token", - scopes: ["read_products"], - toolName: "list_products", - toolDescription: "List Shopify products for the connected store.", - requestMethod: "GET", - requestPath: "/products.json", - }, - discord: { - apiBaseUrl: "https://discord.com/api/v10", - authorizationUrl: "https://discord.com/oauth2/authorize", - tokenUrl: "https://discord.com/api/oauth2/token", - scopes: ["identify", "guilds"], - toolName: "list_guilds", - toolDescription: "List Discord guilds available to the connected user.", - requestMethod: "GET", - requestPath: "/users/@me/guilds", - }, - zoom: { - apiBaseUrl: "https://api.zoom.us/v2", - authorizationUrl: "https://zoom.us/oauth/authorize", - tokenUrl: "https://zoom.us/oauth/token", - scopes: ["meeting:read:user"], - toolName: "list_meetings", - toolDescription: "List Zoom meetings for the connected user.", - requestMethod: "GET", - requestPath: "/users/me/meetings", - }, - webflow: { - provider: "mcp", - authorizationUrl: "https://mcp.webflow.com/oauth/authorize", - tokenUrl: "https://mcp.webflow.com/oauth/token", - registrationUrl: "https://mcp.webflow.com/oauth/register", - clientAuthentication: "none", - pkce: true, - scopes: [], - }, - miro: { - apiBaseUrl: "https://api.miro.com/v2", - authorizationUrl: "https://miro.com/oauth/authorize", - tokenUrl: "https://api.miro.com/v1/oauth/token", - scopes: ["boards:read"], - toolName: "list_boards", - toolDescription: "List Miro boards available to the connected user.", - requestMethod: "GET", - requestPath: "/boards", - }, - canva: { - apiBaseUrl: "https://api.canva.com/rest/v1", - tokenUrl: "https://api.canva.com/rest/v1/oauth/token", - scopes: [], - toolName: "list_designs", - toolDescription: "List Canva designs available to the connected user.", - requestMethod: "GET", - requestPath: "/designs", - }, - datadog: { - provider: "mcp", - authorizationUrl: "https://app.datadoghq.com/oauth2/v1/authorize", - tokenUrl: "https://api.datadoghq.com/oauth2/v1/token", - scopes: ["dashboards_read", "monitors_read"], - }, - sentry: { - apiBaseUrl: "https://sentry.io/api/0", - authorizationUrl: "https://sentry.io/oauth/authorize", - tokenUrl: "https://sentry.io/oauth/token", - scopes: ["project:read", "event:read", "org:read"], - toolName: "list_organizations", - toolDescription: "List Sentry organizations available to the connected user.", - requestMethod: "GET", - requestPath: "/organizations/", - }, - posthog: { - apiBaseUrl: "https://us.posthog.com/api", - authorizationUrl: "https://us.posthog.com/oauth/authorize", - tokenUrl: "https://us.posthog.com/oauth/token", - scopes: ["project:read"], - toolName: "list_projects", - toolDescription: "List PostHog projects available to the connected user.", - requestMethod: "GET", - requestPath: "/projects/", - }, - supabase: { - apiBaseUrl: "https://api.supabase.com/v1", - authorizationUrl: "https://api.supabase.com/v1/oauth/authorize", - tokenUrl: "https://api.supabase.com/v1/oauth/token", - scopes: [], - toolName: "list_projects", - toolDescription: "List Supabase projects available to the connected user.", - requestMethod: "GET", - requestPath: "/projects", - }, - vercel: { - apiBaseUrl: "https://api.vercel.com", - authorizationUrl: "https://vercel.com/oauth/authorize", - tokenUrl: "https://api.vercel.com/v2/oauth/access_token", - scopes: ["project:read"], - toolName: "list_projects", - toolDescription: "List Vercel projects available to the connected user.", - requestMethod: "GET", - requestPath: "/v9/projects", - }, - netlify: { - apiBaseUrl: "https://api.netlify.com/api/v1", - authorizationUrl: "https://app.netlify.com/authorize", - tokenUrl: "https://api.netlify.com/oauth/token", - scopes: ["sites:read"], - toolName: "list_sites", - toolDescription: "List Netlify sites available to the connected user.", - requestMethod: "GET", - requestPath: "/sites", - }, - plaid: { - apiBaseUrl: "https://production.plaid.com", - toolName: "get_accounts", - toolDescription: "Fetch accounts for a connected Plaid item.", - requestMethod: "POST", - requestPath: "/accounts/get", - }, - - okta: { - apiBaseUrl: "https://{yourOktaDomain}/api/v1", - authorizationUrl: "https://{yourOktaDomain}/oauth2/v1/authorize", - tokenUrl: "https://{yourOktaDomain}/oauth2/v1/token", - scopes: ["okta.users.read"], - toolName: "list_users", - toolDescription: "List Okta users for the connected org.", - requestMethod: "GET", - requestPath: "/users", - }, - servicenow: { - apiBaseUrl: "https://{instance}.service-now.com/api/now/v1", - authorizationUrl: "https://{instance}.service-now.com/oauth_auth.do", - tokenUrl: "https://{instance}.service-now.com/oauth_token.do", - scopes: ["useraccount", "table.read"], - toolName: "list_incidents", - toolDescription: "List ServiceNow incidents for the connected instance.", - requestMethod: "GET", - requestPath: "/table/incident", - }, - freshdesk: { - apiBaseUrl: "https://{domain}.freshdesk.com/api/v2", - authorizationUrl: "https://{domain}.freshdesk.com/oauth/authorize", - tokenUrl: "https://{domain}.freshdesk.com/oauth/token", - scopes: ["tickets_view"], - toolName: "list_tickets", - toolDescription: "List Freshdesk tickets for the connected account.", - requestMethod: "GET", - requestPath: "/tickets", - }, - pipedrive: { - apiBaseUrl: "https://api.pipedrive.com/v1", - authorizationUrl: "https://oauth.pipedrive.com/oauth/authorize", - tokenUrl: "https://oauth.pipedrive.com/oauth/token", - scopes: ["deals:read", "contacts:read"], - toolName: "list_persons", - toolDescription: "List Pipedrive people visible to the connected user.", - requestMethod: "GET", - requestPath: "/persons", - }, - mailchimp: { - apiBaseUrl: "https://{dc}.api.mailchimp.com/3.0", - authorizationUrl: "https://login.mailchimp.com/oauth2/authorize", - tokenUrl: "https://login.mailchimp.com/oauth2/token", - scopes: ["audiences:read"], - toolName: "list_audiences", - toolDescription: "List Mailchimp audiences for the connected account.", - requestMethod: "GET", - requestPath: "/lists", - }, - quickbooks: { - apiBaseUrl: "https://sandbox-quickbooks.api.intuit.com/v3/company/{companyId}", - authorizationUrl: "https://appcenter.intuit.com/connect/oauth2", - tokenUrl: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer", - scopes: ["com.intuit.quickbooks.accounting"], - toolName: "list_customers", - toolDescription: "Query QuickBooks customers for the connected company.", - requestMethod: "GET", - requestPath: "/query?query=SELECT%20*%20FROM%20Customer%20MAXRESULTS%2010", - }, - xero: { - apiBaseUrl: "https://api.xero.com/api.xro/2.0", - authorizationUrl: "https://login.xero.com/identity/connect/authorize", - tokenUrl: "https://identity.xero.com/connect/token", - scopes: ["accounting.contacts.read"], - toolName: "list_contacts", - toolDescription: "List Xero contacts for the connected tenant.", - requestMethod: "GET", - requestPath: "/Contacts", - }, - gitlab: { - apiBaseUrl: "https://gitlab.com/api/v4", - authorizationUrl: "https://gitlab.com/oauth/authorize", - tokenUrl: "https://gitlab.com/oauth/token", - scopes: ["read_api"], - toolName: "get_current_user", - toolDescription: "Fetch the authenticated GitLab user profile.", - requestMethod: "GET", - requestPath: "/user", - }, - bitbucket: { - apiBaseUrl: "https://api.bitbucket.org/2.0", - authorizationUrl: "https://bitbucket.org/site/oauth2/authorize", - tokenUrl: "https://bitbucket.org/site/oauth2/access_token", - scopes: ["account", "repository"], - toolName: "get_current_user", - toolDescription: "Fetch the authenticated Bitbucket user profile.", - requestMethod: "GET", - requestPath: "/user", - }, - ordinal: { - provider: "mcp", - authModes: ["bearer"], - authStrategy: "bearer", - credentialLabel: "Ordinal API key", - credentialPlaceholder: "Paste your Ordinal API key", - credentialHelp: - "API key from your Ordinal workspace settings, sent as a Bearer token in the Authorization header.", - }, -}; - -export const getOAuthProviderRegistrationDefaults = (slug) => { - const defaults = registrationDefaults[slug]; - if (!defaults) { - return undefined; - } - - const officialMcpServerUrl = officialManagedMcpServerUrls[slug]; - const provider = - defaults.provider ?? - (officialMcpServerUrl - ? "mcp" - : defaults.apiBaseUrl || defaults.openApiUrl - ? "http" - : undefined); - - return { - ...defaults, - provider, - serverUrl: - provider === "mcp" - ? defaults.serverUrl ?? officialMcpServerUrl - : defaults.serverUrl, - }; -}; diff --git a/package.json b/package.json index 37ee6b25..5d28ecd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openhands/extensions", - "version": "0.5.0", + "version": "0.6.0", "description": "Public OpenHands extension catalogs for skills, plugins, integrations, and automation templates.", "license": "MIT", "type": "module", @@ -9,7 +9,9 @@ "url": "https://github.com/OpenHands/extensions" }, "scripts": { - "build:skills": "node scripts/build-skills-catalog.mjs" + "build:skills": "node scripts/build-skills-catalog.mjs", + "build:integrations": "node scripts/build-integration-catalog.mjs", + "build": "npm run build:skills && npm run build:integrations" }, "engines": { "node": ">=18.20.0" @@ -39,11 +41,6 @@ "import": "./integrations/index.js", "default": "./integrations/index.js" }, - "./integrations/logos": { - "types": "./integrations/logos.d.ts", - "import": "./integrations/logos.js", - "default": "./integrations/logos.js" - }, "./integrations/catalog/*.json": "./integrations/catalog/*.json", "./skills": { "types": "./skills/index.d.ts", diff --git a/pyproject.toml b/pyproject.toml index 0348eef3..bd1d5506 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,26 @@ [project] -name = "extensions" -version = "0.5.0" -description = "OpenHands extensions, plugins, and skills" +name = "openhands-extensions" +version = "0.6.0" +description = "OpenHands extensions, plugins, and skills (Python bindings for the integration catalog)" requires-python = ">=3.12" +[build-system] +requires = ["hatchling>=1.21"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["python/openhands_extensions"] + +[tool.hatch.build.targets.wheel.force-include] +"integrations/catalog" = "openhands_extensions/catalog" + [dependency-groups] test = [ "pytest>=8.0", "requests>=2.31", "openhands-sdk>=0.3", ] + +[tool.pytest.ini_options] +pythonpath = ["python"] +testpaths = ["tests"] diff --git a/python/openhands_extensions/__init__.py b/python/openhands_extensions/__init__.py new file mode 100644 index 00000000..11185bb6 --- /dev/null +++ b/python/openhands_extensions/__init__.py @@ -0,0 +1,24 @@ +"""Python bindings for the OpenHands extensions catalogs. + +Mirrors the JavaScript package (``@openhands/extensions``): both read the same +hand-authored ``integrations/catalog/.json`` files. The JavaScript package +uses a generated static import index; the Python package loads the packaged +individual JSON files directly. See ``integrations.py`` for the catalog API, +including filtering by connector type (mcp / oauth). +""" + +from __future__ import annotations + +from ._version import __version__ +from .integrations import ( + INTEGRATION_CATALOG_SNAPSHOT, + get_integration_catalog_entry, + list_integration_catalog, +) + +__all__ = [ + "INTEGRATION_CATALOG_SNAPSHOT", + "__version__", + "get_integration_catalog_entry", + "list_integration_catalog", +] diff --git a/python/openhands_extensions/_version.py b/python/openhands_extensions/_version.py new file mode 100644 index 00000000..360f7ff3 --- /dev/null +++ b/python/openhands_extensions/_version.py @@ -0,0 +1,24 @@ +"""Package version, derived from installed package metadata. + +The single source of truth is ``pyproject.toml``'s ``[project].version`` +(which ``release-please`` bumps together with ``package.json`` via +``release-please-config.json`` -> ``extra-files``). At runtime we read the +installed distribution metadata via ``importlib.metadata`` so there is no +second hand-maintained version string. ``_FALLBACK_VERSION`` is only used +when the package is imported without being installed (e.g. straight off a +source checkout with no build), and ``tests/test_version_alignment.py`` +asserts it stays in lock-step with ``pyproject.toml`` / ``package.json``. +""" + +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError, version + +#: Fallback used only when the package is not installed (no dist metadata). +#: Kept in lock-step with pyproject.toml/package.json by the version test. +_FALLBACK_VERSION = "0.6.0" + +try: + __version__: str = version("openhands-extensions") +except PackageNotFoundError: # not installed (e.g. raw source checkout) + __version__ = _FALLBACK_VERSION diff --git a/python/openhands_extensions/integrations.py b/python/openhands_extensions/integrations.py new file mode 100644 index 00000000..c22f45db --- /dev/null +++ b/python/openhands_extensions/integrations.py @@ -0,0 +1,92 @@ +"""Python bindings for the OpenHands extensions integration catalog. + +The source of truth is the hand-authored ``integrations/catalog/.json`` +directory. Wheels include those individual JSON files directly; no aggregate +catalog JSON is authored or packaged. +""" + +from __future__ import annotations + +import copy +import json +from functools import lru_cache +from importlib import resources +from pathlib import Path +from typing import Any, Iterable + +__all__ = [ + "INTEGRATION_CATALOG_SNAPSHOT", + "get_integration_catalog_entry", + "list_integration_catalog", +] + + +def _repo_catalog_dir() -> Path: + return Path(__file__).resolve().parents[2] / "integrations" / "catalog" + + +def _catalog_files() -> Iterable[Any]: + packaged = resources.files(__package__).joinpath("catalog") + if packaged.is_dir(): + return sorted( + (path for path in packaged.iterdir() if path.name.endswith(".json")), + key=lambda path: path.name, + ) + + repo_catalog = _repo_catalog_dir() + return sorted(repo_catalog.glob("*.json"), key=lambda path: path.name) + + +def _read_json(path: Any) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +@lru_cache(maxsize=1) +def _integrations() -> tuple[dict[str, Any], ...]: + entries = [_read_json(path) for path in _catalog_files()] + entries.sort( + key=lambda entry: (-(entry.get("popularityRank") if entry.get("popularityRank") is not None else -1), entry["id"]), + ) + return tuple(entries) + + +@lru_cache(maxsize=1) +def _integration_by_id() -> dict[str, dict[str, Any]]: + return {entry["id"]: entry for entry in _integrations()} + + +def _entry_supports_mcp(entry: dict[str, Any]) -> bool: + return any(option.get("provider") == "mcp" for option in entry.get("connectionOptions", [])) + + +def _entry_supports_oauth(entry: dict[str, Any]) -> bool: + return any( + option.get("auth", {}).get("strategy") == "oauth2" + for option in entry.get("connectionOptions", []) + ) + + +def list_integration_catalog( + mcp: bool | None = None, + oauth: bool | None = None, +) -> list[dict[str, Any]]: + """Return the integration catalog, optionally filtered by connector type.""" + result = [] + for entry in _integrations(): + if mcp is not None and _entry_supports_mcp(entry) != mcp: + continue + if oauth is not None and _entry_supports_oauth(entry) != oauth: + continue + result.append(copy.deepcopy(entry)) + return result + + +def get_integration_catalog_entry(id: str) -> dict[str, Any] | None: + """Return one integration catalog entry by id, or ``None``.""" + entry = _integration_by_id().get(id) + return copy.deepcopy(entry) if entry is not None else None + + +INTEGRATION_CATALOG_SNAPSHOT: dict[str, Any] = { + "integrations": copy.deepcopy(list(_integrations())) +} diff --git a/release-please-config.json b/release-please-config.json index cb37e2a8..065f9d46 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,7 +9,8 @@ "include-component-in-tag": false, "skip-changelog": true, "extra-files": [ - { "type": "toml", "path": "pyproject.toml", "jsonpath": "$.project.version" } + { "type": "toml", "path": "pyproject.toml", "jsonpath": "$.project.version" }, + "python/openhands_extensions/_version.py" ] } } diff --git a/scripts/build-integration-catalog.mjs b/scripts/build-integration-catalog.mjs new file mode 100644 index 00000000..6dd42789 --- /dev/null +++ b/scripts/build-integration-catalog.mjs @@ -0,0 +1,45 @@ +import { readdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; + +const root = process.cwd(); +const catalogDir = path.join(root, "integrations", "catalog"); +const outputPath = path.join(root, "integrations", "catalog-index.js"); + +const files = (await readdir(catalogDir)) + .filter((file) => file.endsWith(".json")) + .sort((left, right) => left.localeCompare(right)); + +const integrations = await Promise.all( + files.map(async (file) => ({ + file, + entry: JSON.parse(await readFile(path.join(catalogDir, file), "utf8")), + })), +); + +integrations.sort((left, right) => { + const rank = (right.entry.popularityRank ?? -1) - (left.entry.popularityRank ?? -1); + return rank || left.entry.id.localeCompare(right.entry.id); +}); + +const indexedIntegrations = integrations.map((integration, index) => ({ + ...integration, + importName: `entry${index}`, +})); + +const imports = indexedIntegrations + .map(({ file, importName }) => `import ${importName} from "./catalog/${file}" with { type: "json" };`) + .join("\n"); +const entries = indexedIntegrations.map(({ importName }) => ` ${importName},`).join("\n"); + +const header = `// This file is auto-generated by scripts/build-integration-catalog.mjs. +// Do not edit it manually. To update it after changing integrations/catalog/*.json, +// run: npm run build:integrations + +`; +const body = `${header}${imports} + +export const INTEGRATION_CATALOG_ENTRIES = [ +${entries} +]; +`; +await writeFile(outputPath, body); diff --git a/skills/index.js b/skills/index.js index 855ee2b5..6adfaedf 100644 --- a/skills/index.js +++ b/skills/index.js @@ -341,7 +341,7 @@ export const SKILLS_CATALOG = [ "agent-sdk", "/sdk" ], - "content": "# OpenHands Software Agent SDK\n\nAll SDK documentation lives at .\n\nFor the full topic index, fetch and read\nthe \"OpenHands Software Agent SDK\" section.\n\n## Quick reference\n\nInstall: `pip install openhands-sdk openhands-tools`\n\n```python\nimport os\n\nfrom openhands.sdk import LLM, Agent, Conversation, Tool\nfrom openhands.tools.file_editor import FileEditorTool\nfrom openhands.tools.task_tracker import TaskTrackerTool\nfrom openhands.tools.terminal import TerminalTool\n\n\nllm = LLM(\n model=os.getenv(\"LLM_MODEL\", \"gpt-5.5\"),\n api_key=os.getenv(\"LLM_API_KEY\"),\n base_url=os.getenv(\"LLM_BASE_URL\", None),\n)\n\nagent = Agent(\n llm=llm,\n tools=[\n Tool(name=TerminalTool.name),\n Tool(name=FileEditorTool.name),\n Tool(name=TaskTrackerTool.name),\n ],\n)\n\ncwd = os.getcwd()\nconversation = Conversation(agent=agent, workspace=cwd)\n\nconversation.send_message(\"Write 3 facts about the current project into FACTS.txt.\")\nconversation.run()\nprint(\"All done!\")\n```\n\n## Core classes (`openhands.sdk`)\n\n| Class | Purpose |\n|---|---|\n| [`Agent`](https://docs.openhands.dev/sdk/arch/agent.md) | Reasoning-action loop |\n| [`Condenser`](https://docs.openhands.dev/sdk/arch/condenser.md) | Conversation history compression system |\n| [`Conversation`](https://docs.openhands.dev/sdk/arch/conversation.md) | Conversation orchestration system |\n| [`Event`](https://docs.openhands.dev/sdk/arch/events.md) | Typed event framework |\n| [`LLM`](https://docs.openhands.dev/sdk/arch/llm.md) | Provider-agnostic language model interface |\n| [`SecurityAnalyzer`](https://docs.openhands.dev/sdk/arch/security.md) | Action security analysis and validation |\n| [`Skill`](https://docs.openhands.dev/sdk/arch/skill.md) | Reusable prompt system |\n| [`Tool / ToolDefinition`](https://docs.openhands.dev/sdk/arch/tool-system.md) | Action-observation tool framework |\n| [`Workspace`](https://docs.openhands.dev/sdk/arch/workspace.md) | Execution environment abstraction |\n\n## API reference\n\n[`openhands.sdk.agent`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.agent.md), [`openhands.sdk.conversation`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.conversation.md), [`openhands.sdk.event`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.event.md), [`openhands.sdk.llm`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.llm.md), [`openhands.sdk.security`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.security.md), [`openhands.sdk.tool`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.tool.md), [`openhands.sdk.utils`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.utils.md), [`openhands.sdk.workspace`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.workspace.md)\n\n## Guides\n\n- [ACP Agent](https://docs.openhands.dev/sdk/guides/agent-acp.md): Delegate to an ACP-compatible server (Claude Code, Gemini CLI, etc.) instead of calling an LLM directly.\n- [Agent Settings](https://docs.openhands.dev/sdk/guides/agent-settings.md): Configure, serialize, and recreate agents from structured settings.\n- [Agent Skills & Context](https://docs.openhands.dev/sdk/guides/skill.md): Skills add specialized behaviors, domain knowledge, and context-aware triggers to your agent through structured prompts.\n- [API-based Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/api-sandbox.md): Connect to hosted API-based agent server for fully managed infrastructure.\n- [Apptainer Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/apptainer-sandbox.md): Run agent server in rootless Apptainer containers for HPC and shared computing environments.\n- [Ask Agent Questions](https://docs.openhands.dev/sdk/guides/convo-ask-agent.md): Get sidebar replies from the agent during conversation execution without interrupting the main flow.\n- [Assign Reviews](https://docs.openhands.dev/sdk/guides/github-workflows/assign-reviews.md): Automate PR management with intelligent reviewer assignment and workflow notifications using OpenHands Agent\n- [Browser Session Recording](https://docs.openhands.dev/sdk/guides/browser-session-recording.md): Record and replay your agent's browser sessions using rrweb.\n- [Browser Use](https://docs.openhands.dev/sdk/guides/agent-browser-use.md): Enable web browsing and interaction capabilities for your agent.\n- [Context Condenser](https://docs.openhands.dev/sdk/guides/context-condenser.md): Manage agent memory by condensing conversation history to save tokens.\n- [Conversation with Async](https://docs.openhands.dev/sdk/guides/convo-async.md): Use async/await for concurrent agent operations and non-blocking execution.\n- [Creating Custom Agent](https://docs.openhands.dev/sdk/guides/agent-custom.md): Learn how to design specialized agents with custom tool sets\n- [Critic (Experimental)](https://docs.openhands.dev/sdk/guides/critic.md): Real-time evaluation of agent actions using an LLM-based critic model, with built-in iterative refinement.\n- [Custom Tools](https://docs.openhands.dev/sdk/guides/custom-tools.md): Tools define what agents can do. The SDK includes built-in tools for common operations and supports creating custom tools for specialized needs.\n- [Custom Tools with Remote Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/custom-tools.md): Learn how to use custom tools with a remote agent server by building a custom base image that includes your tool implementations.\n- [Custom Visualizer](https://docs.openhands.dev/sdk/guides/convo-custom-visualizer.md): Customize conversation visualization by creating custom visualizers or configuring the default visualizer.\n- [Docker Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox.md): Run agent server in isolated Docker containers for security and reproducibility.\n- [Exception Handling](https://docs.openhands.dev/sdk/guides/llm-error-handling.md): Provider‑agnostic exceptions raised by the SDK and recommended patterns for handling them.\n- [FAQ](https://docs.openhands.dev/sdk/faq.md): Frequently asked questions about the OpenHands SDK\n- [File-Based Agents](https://docs.openhands.dev/sdk/guides/agent-file-based.md): Define specialized sub-agents as simple Markdown files with YAML frontmatter — no Python code required.\n- [Fork a Conversation](https://docs.openhands.dev/sdk/guides/convo-fork.md): Branch off an existing conversation for follow-up exploration without contaminating the original.\n- [Getting Started](https://docs.openhands.dev/sdk/getting-started.md): Install the OpenHands SDK and build AI agents that write software.\n- [GPT-5 Preset (ApplyPatchTool)](https://docs.openhands.dev/sdk/guides/llm-gpt5-preset.md): Use the GPT-5 preset to build an agent that swaps the standard FileEditorTool for ApplyPatchTool.\n- [Hello World](https://docs.openhands.dev/sdk/guides/hello-world.md): The simplest possible OpenHands agent - configure an LLM, create an agent, and complete a task.\n- [Hooks](https://docs.openhands.dev/sdk/guides/hooks.md): Use lifecycle hooks to observe, log, and customize agent execution.\n- [Image Input](https://docs.openhands.dev/sdk/guides/llm-image-input.md): Send images to multimodal agents for vision-based tasks and analysis.\n- [Interactive Terminal](https://docs.openhands.dev/sdk/guides/agent-interactive-terminal.md): Enable agents to interact with terminal applications like ipython, python REPL, and other interactive CLI tools.\n- [Iterative Refinement](https://docs.openhands.dev/sdk/guides/iterative-refinement.md): Implement iterative refinement workflows where agents refine their work based on critique feedback until quality thresholds are met.\n- [LLM Fallback Strategy](https://docs.openhands.dev/sdk/guides/llm-fallback.md): Automatically try alternate LLMs when the primary model fails with a transient error.\n- [LLM Profile Store](https://docs.openhands.dev/sdk/guides/llm-profile-store.md): Save, load, and manage reusable LLM configurations so you never repeat setup code again.\n- [LLM Registry](https://docs.openhands.dev/sdk/guides/llm-registry.md): Dynamically select and configure language models using the LLM registry.\n- [LLM Streaming](https://docs.openhands.dev/sdk/guides/llm-streaming.md): Stream LLM responses token-by-token for real-time display and interactive user experiences.\n- [LLM Subscriptions](https://docs.openhands.dev/sdk/guides/llm-subscriptions.md): Use your ChatGPT Plus/Pro subscription to access Codex models without consuming API credits.\n- [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Run agents through a local HTTP server with RemoteConversation for client-server architecture.\n- [Metrics Tracking](https://docs.openhands.dev/sdk/guides/metrics.md): Track token usage, costs, and latency metrics for your agents.\n- [Model Context Protocol](https://docs.openhands.dev/sdk/guides/mcp.md): Model Context Protocol (MCP) enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically.\n- [Model Routing](https://docs.openhands.dev/sdk/guides/llm-routing.md): Route agent's LLM requests to different models.\n- [Observability & Tracing](https://docs.openhands.dev/sdk/guides/observability.md): Enable OpenTelemetry tracing to monitor and debug your agent's execution with tools like Laminar, MLflow, Honeycomb, or any OTLP-compatible backend.\n- [OpenHands Cloud Workspace](https://docs.openhands.dev/sdk/guides/agent-server/cloud-workspace.md): Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance.\n- [Overview](https://docs.openhands.dev/sdk/guides/agent-server/overview.md): Run agents on remote servers with isolated workspaces for production deployments.\n- [Parallel Tool Execution](https://docs.openhands.dev/sdk/guides/parallel-tool-execution.md): Execute multiple tools concurrently within a single LLM response to improve throughput for independent operations.\n- [Pause and Resume](https://docs.openhands.dev/sdk/guides/convo-pause-and-resume.md): Pause agent execution, perform operations, and resume without losing state.\n- [Persistence](https://docs.openhands.dev/sdk/guides/convo-persistence.md): Save and restore conversation state for multi-session workflows.\n- [Plugins](https://docs.openhands.dev/sdk/guides/plugins.md): Plugins bundle skills, hooks, MCP servers, agents, and commands into reusable packages that extend agent capabilities.\n- [PR Review](https://docs.openhands.dev/sdk/guides/github-workflows/pr-review.md): Use OpenHands Agent to generate meaningful pull request review\n- [Reasoning](https://docs.openhands.dev/sdk/guides/llm-reasoning.md): Access model reasoning traces from Anthropic extended thinking and OpenAI responses API.\n- [Secret Registry](https://docs.openhands.dev/sdk/guides/secrets.md): Provide environment variables and secrets to agent workspace securely.\n- [Security & Action Confirmation](https://docs.openhands.dev/sdk/guides/security.md): Control agent action execution through confirmation policy and security analyzer.\n- [Send Message While Running](https://docs.openhands.dev/sdk/guides/convo-send-message-while-running.md): Interrupt running agents to provide additional context or corrections.\n- [Software Agent SDK](https://docs.openhands.dev/sdk.md): Build AI agents that write software. A clean, modular SDK with production-ready tools.\n- [Stuck Detector](https://docs.openhands.dev/sdk/guides/agent-stuck-detector.md): Detect and handle stuck agents automatically with timeout mechanisms.\n- [Sub-Agent Delegation](https://docs.openhands.dev/sdk/guides/agent-delegation.md): Enable parallel task execution by delegating work to multiple sub-agents that run independently and return consolidated results.\n- [Task Tool Set](https://docs.openhands.dev/sdk/guides/task-tool-set.md): Delegate complex work to specialized sub-agents that run synchronously and return results to the parent agent.\n- [Theory of Mind (TOM) Agent](https://docs.openhands.dev/sdk/guides/agent-tom-agent.md): Enable your agent to understand user intent and preferences through Theory of Mind capabilities, providing personalized guidance based on user modeling.\n- [TODO Management](https://docs.openhands.dev/sdk/guides/github-workflows/todo-management.md): Implement TODOs using OpenHands Agent\n\n## Examples\n\nSource: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples)\n\n### [`01_standalone_sdk/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk)\n\n- [`01_hello_world.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/01_hello_world.py)\n- [`02_custom_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/02_custom_tools.py)\n- [`03_activate_skill.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/03_activate_skill.py)\n- [`04_confirmation_mode_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/04_confirmation_mode_example.py)\n- [`05_use_llm_registry.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/05_use_llm_registry.py)\n- [`06_interactive_terminal_w_reasoning.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/06_interactive_terminal_w_reasoning.py)\n- [`07_mcp_integration.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/07_mcp_integration.py)\n- [`08_mcp_with_oauth.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/08_mcp_with_oauth.py)\n- [`09_pause_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/09_pause_example.py)\n- [`10_persistence.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/10_persistence.py)\n- [`11_async.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/11_async.py)\n- [`12_custom_secrets.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/12_custom_secrets.py)\n- [`13_get_llm_metrics.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/13_get_llm_metrics.py)\n- [`14_context_condenser.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/14_context_condenser.py)\n- [`15_browser_use.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/15_browser_use.py)\n- [`16_llm_security_analyzer.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/16_llm_security_analyzer.py)\n- [`17_image_input.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/17_image_input.py)\n- [`18_send_message_while_processing.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/18_send_message_while_processing.py)\n- [`19_llm_routing.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/19_llm_routing.py)\n- [`20_stuck_detector.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/20_stuck_detector.py)\n- [`21_generate_extraneous_conversation_costs.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/21_generate_extraneous_conversation_costs.py)\n- [`22_anthropic_thinking.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/22_anthropic_thinking.py)\n- [`23_responses_reasoning.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/23_responses_reasoning.py)\n- [`24_planning_agent_workflow.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/24_planning_agent_workflow.py)\n- [`25_agent_delegation.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/25_agent_delegation.py)\n- [`26_custom_visualizer.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/26_custom_visualizer.py)\n- [`27_observability_laminar.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/27_observability_laminar.py)\n- [`28_ask_agent_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/28_ask_agent_example.py)\n- [`29_llm_streaming.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/29_llm_streaming.py)\n- [`30_tom_agent.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/30_tom_agent.py)\n- [`31_iterative_refinement.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/31_iterative_refinement.py)\n- [`32_configurable_security_policy.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/32_configurable_security_policy.py)\n- [`33_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/33_hooks)\n- [`34_critic_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/34_critic_example.py)\n- [`35_subscription_login.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/35_subscription_login.py)\n- [`36_event_json_to_openai_messages.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/36_event_json_to_openai_messages.py)\n- [`37_llm_profile_store`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/37_llm_profile_store)\n- [`38_browser_session_recording.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/38_browser_session_recording.py)\n- [`39_llm_fallback.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/39_llm_fallback.py)\n- [`40_acp_agent_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/40_acp_agent_example.py)\n- [`41_task_tool_set.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/41_task_tool_set.py)\n- [`42_file_based_subagents.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/42_file_based_subagents.py)\n- [`44_model_switching_in_convo.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/44_model_switching_in_convo.py)\n- [`45_parallel_tool_execution.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/45_parallel_tool_execution.py)\n- [`46_agent_settings.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/46_agent_settings.py)\n- [`47_defense_in_depth_security.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/47_defense_in_depth_security.py)\n- [`48_conversation_fork.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/48_conversation_fork.py)\n- [`49_switch_llm_tool.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/49_switch_llm_tool.py)\n- [`50_async_cancellation.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/50_async_cancellation.py)\n- [`51_agent_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/51_agent_hooks)\n- [`52_dynamic_workflow.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/52_dynamic_workflow.py)\n- [`53_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/53_client_defined_tools.py)\n- [`54_goal_completion_loop.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/54_goal_completion_loop.py)\n\n### [`02_remote_agent_server/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server)\n\n- [`01_convo_with_local_agent_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/01_convo_with_local_agent_server.py)\n- [`02_convo_with_docker_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/02_convo_with_docker_sandboxed_server.py)\n- [`03_browser_use_with_docker_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/03_browser_use_with_docker_sandboxed_server.py)\n- [`04_convo_with_api_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/04_convo_with_api_sandboxed_server.py)\n- [`05_vscode_with_docker_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/05_vscode_with_docker_sandboxed_server.py)\n- [`06_custom_tool`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/06_custom_tool)\n- [`07_convo_with_cloud_workspace.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/07_convo_with_cloud_workspace.py)\n- [`08_convo_with_apptainer_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/08_convo_with_apptainer_sandboxed_server.py)\n- [`09_acp_agent_with_remote_runtime.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py)\n- [`10_cloud_workspace_share_credentials.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py)\n- [`11_conversation_fork.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/11_conversation_fork.py)\n- [`12_settings_and_secrets_api.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/12_settings_and_secrets_api.py)\n- [`13_workspace_get_llm.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/13_workspace_get_llm.py)\n- [`14_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/14_client_defined_tools.py)\n- [`15_openai_compatible_gateway.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/15_openai_compatible_gateway.py)\n- [`16_deferred_init.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/16_deferred_init.py)\n- [`hook_scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/hook_scripts)\n- [`scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/scripts)\n\n### [`03_github_workflows/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows)\n\n- [`01_basic_action`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/01_basic_action)\n- [`02_pr_review`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/02_pr_review)\n- [`03_todo_management`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/03_todo_management)\n- [`04_datadog_debugging`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/04_datadog_debugging)\n- [`05_posthog_debugging`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/05_posthog_debugging)\n\n### [`04_llm_specific_tools/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/04_llm_specific_tools)\n\n- [`01_gpt5_apply_patch_preset.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py)\n- [`02_gemini_file_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/04_llm_specific_tools/02_gemini_file_tools.py)\n\n### [`05_skills_and_plugins/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins)\n\n- [`01_loading_agentskills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/01_loading_agentskills)\n- [`02_loading_plugins`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/02_loading_plugins)\n- [`03_managing_installed_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/03_managing_installed_skills)\n- [`04_mixed_marketplace_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/04_mixed_marketplace_skills)" + "content": "# OpenHands Software Agent SDK\n\nAll SDK documentation lives at .\n\nFor the full topic index, fetch and read\nthe \"OpenHands Software Agent SDK\" section.\n\n## Quick reference\n\nInstall: `pip install openhands-sdk openhands-tools`\n\n```python\nimport os\n\nfrom openhands.sdk import LLM, Agent, Conversation, Tool\nfrom openhands.tools.file_editor import FileEditorTool\nfrom openhands.tools.task_tracker import TaskTrackerTool\nfrom openhands.tools.terminal import TerminalTool\n\n\nllm = LLM(\n model=os.getenv(\"LLM_MODEL\", \"gpt-5.5\"),\n api_key=os.getenv(\"LLM_API_KEY\"),\n base_url=os.getenv(\"LLM_BASE_URL\", None),\n)\n\nagent = Agent(\n llm=llm,\n tools=[\n Tool(name=TerminalTool.name),\n Tool(name=FileEditorTool.name),\n Tool(name=TaskTrackerTool.name),\n ],\n)\n\ncwd = os.getcwd()\nconversation = Conversation(agent=agent, workspace=cwd)\n\nconversation.send_message(\"Write 3 facts about the current project into FACTS.txt.\")\nconversation.run()\nprint(\"All done!\")\n```\n\n## Core classes (`openhands.sdk`)\n\n| Class | Purpose |\n|---|---|\n| [`Agent`](https://docs.openhands.dev/sdk/arch/agent.md) | Reasoning-action loop |\n| [`Condenser`](https://docs.openhands.dev/sdk/arch/condenser.md) | Conversation history compression system |\n| [`Conversation`](https://docs.openhands.dev/sdk/arch/conversation.md) | Conversation orchestration system |\n| [`Event`](https://docs.openhands.dev/sdk/arch/events.md) | Typed event framework |\n| [`LLM`](https://docs.openhands.dev/sdk/arch/llm.md) | Provider-agnostic language model interface |\n| [`SecurityAnalyzer`](https://docs.openhands.dev/sdk/arch/security.md) | Action security analysis and validation |\n| [`Skill`](https://docs.openhands.dev/sdk/arch/skill.md) | Reusable prompt system |\n| [`Tool / ToolDefinition`](https://docs.openhands.dev/sdk/arch/tool-system.md) | Action-observation tool framework |\n| [`Workspace`](https://docs.openhands.dev/sdk/arch/workspace.md) | Execution environment abstraction |\n\n## API reference\n\n[`openhands.sdk.agent`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.agent.md), [`openhands.sdk.conversation`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.conversation.md), [`openhands.sdk.event`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.event.md), [`openhands.sdk.llm`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.llm.md), [`openhands.sdk.security`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.security.md), [`openhands.sdk.tool`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.tool.md), [`openhands.sdk.utils`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.utils.md), [`openhands.sdk.workspace`](https://docs.openhands.dev/sdk/api-reference/openhands.sdk.workspace.md)\n\n## Guides\n\n- [ACP Agent](https://docs.openhands.dev/sdk/guides/agent-acp.md): Delegate to an ACP-compatible server (Claude Code, Gemini CLI, etc.) instead of calling an LLM directly.\n- [Agent Settings](https://docs.openhands.dev/sdk/guides/agent-settings.md): Configure, serialize, and recreate agents from structured settings.\n- [Agent Skills & Context](https://docs.openhands.dev/sdk/guides/skill.md): Skills add specialized behaviors, domain knowledge, and context-aware triggers to your agent through structured prompts.\n- [API-based Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/api-sandbox.md): Connect to hosted API-based agent server for fully managed infrastructure.\n- [Apptainer Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/apptainer-sandbox.md): Run agent server in rootless Apptainer containers for HPC and shared computing environments.\n- [Ask Agent Questions](https://docs.openhands.dev/sdk/guides/convo-ask-agent.md): Get sidebar replies from the agent during conversation execution without interrupting the main flow.\n- [Assign Reviews](https://docs.openhands.dev/sdk/guides/github-workflows/assign-reviews.md): Automate PR management with intelligent reviewer assignment and workflow notifications using OpenHands Agent\n- [Browser Session Recording](https://docs.openhands.dev/sdk/guides/browser-session-recording.md): Record and replay your agent's browser sessions using rrweb.\n- [Browser Use](https://docs.openhands.dev/sdk/guides/agent-browser-use.md): Enable web browsing and interaction capabilities for your agent.\n- [Context Condenser](https://docs.openhands.dev/sdk/guides/context-condenser.md): Manage agent memory by condensing conversation history to save tokens.\n- [Conversation Goals](https://docs.openhands.dev/sdk/guides/agent-server/conversation-goals.md): Add a resumable goal strategy to a normal agent-server conversation.\n- [Conversation with Async](https://docs.openhands.dev/sdk/guides/convo-async.md): Use async/await for concurrent agent operations and non-blocking execution.\n- [Creating Custom Agent](https://docs.openhands.dev/sdk/guides/agent-custom.md): Learn how to design specialized agents with custom tool sets\n- [Critic (Experimental)](https://docs.openhands.dev/sdk/guides/critic.md): Real-time evaluation of agent actions using an LLM-based critic model, with built-in iterative refinement.\n- [Custom Tools](https://docs.openhands.dev/sdk/guides/custom-tools.md): Tools define what agents can do. The SDK includes built-in tools for common operations and supports creating custom tools for specialized needs.\n- [Custom Tools with Remote Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/custom-tools.md): Learn how to use custom tools with a remote agent server by building a custom base image that includes your tool implementations.\n- [Custom Visualizer](https://docs.openhands.dev/sdk/guides/convo-custom-visualizer.md): Customize conversation visualization by creating custom visualizers or configuring the default visualizer.\n- [Deferred Init (Warm-Pool)](https://docs.openhands.dev/sdk/guides/agent-server/deferred-init.md): Pre-warm agent-server pods before a user is matched, then activate them at runtime with POST /api/init.\n- [Docker Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox.md): Run agent server in isolated Docker containers for security and reproducibility.\n- [Exception Handling](https://docs.openhands.dev/sdk/guides/llm-error-handling.md): Provider‑agnostic exceptions raised by the SDK and recommended patterns for handling them.\n- [FAQ](https://docs.openhands.dev/sdk/faq.md): Frequently asked questions about the OpenHands SDK\n- [File-Based Agents](https://docs.openhands.dev/sdk/guides/agent-file-based.md): Define specialized sub-agents as simple Markdown files with YAML frontmatter — no Python code required.\n- [Fork a Conversation](https://docs.openhands.dev/sdk/guides/convo-fork.md): Branch off an existing conversation for follow-up exploration without contaminating the original.\n- [Getting Started](https://docs.openhands.dev/sdk/getting-started.md): Install the OpenHands SDK and build AI agents that write software.\n- [Goal Completion Loop](https://docs.openhands.dev/sdk/guides/convo-goal.md): Drive a conversation toward a verifiable objective with a judge-driven, self-continuing completion loop.\n- [GPT-5 Preset (ApplyPatchTool)](https://docs.openhands.dev/sdk/guides/llm-gpt5-preset.md): Use the GPT-5 preset to build an agent that swaps the standard FileEditorTool for ApplyPatchTool.\n- [Hello World](https://docs.openhands.dev/sdk/guides/hello-world.md): The simplest possible OpenHands agent - configure an LLM, create an agent, and complete a task.\n- [Hooks](https://docs.openhands.dev/sdk/guides/hooks.md): Use lifecycle hooks to observe, log, and customize agent execution.\n- [Image Input](https://docs.openhands.dev/sdk/guides/llm-image-input.md): Send images to multimodal agents for vision-based tasks and analysis.\n- [Interactive Terminal](https://docs.openhands.dev/sdk/guides/agent-interactive-terminal.md): Enable agents to interact with terminal applications like ipython, python REPL, and other interactive CLI tools.\n- [Iterative Refinement](https://docs.openhands.dev/sdk/guides/iterative-refinement.md): Implement iterative refinement workflows where agents refine their work based on critique feedback until quality thresholds are met.\n- [LLM Fallback Strategy](https://docs.openhands.dev/sdk/guides/llm-fallback.md): Automatically try alternate LLMs when the primary model fails with a transient error.\n- [LLM Profile Store](https://docs.openhands.dev/sdk/guides/llm-profile-store.md): Save, load, and manage reusable LLM configurations so you never repeat setup code again.\n- [LLM Registry](https://docs.openhands.dev/sdk/guides/llm-registry.md): Dynamically select and configure language models using the LLM registry.\n- [LLM Streaming](https://docs.openhands.dev/sdk/guides/llm-streaming.md): Stream LLM responses token-by-token for real-time display and interactive user experiences.\n- [LLM Subscriptions](https://docs.openhands.dev/sdk/guides/llm-subscriptions.md): Use your ChatGPT Plus/Pro subscription to access Codex models without consuming API credits.\n- [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Install and run an OpenHands Agent Server on your machine, then connect to it from the SDK.\n- [Metrics Tracking](https://docs.openhands.dev/sdk/guides/metrics.md): Track token usage, costs, and latency metrics for your agents.\n- [Model Context Protocol](https://docs.openhands.dev/sdk/guides/mcp.md): Model Context Protocol (MCP) enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically.\n- [Model Routing](https://docs.openhands.dev/sdk/guides/llm-routing.md): Route agent's LLM requests to different models.\n- [Observability & Tracing](https://docs.openhands.dev/sdk/guides/observability.md): Enable OpenTelemetry tracing to monitor and debug your agent's execution with tools like Laminar, MLflow, Honeycomb, or any OTLP-compatible backend.\n- [OpenAI-Compatible Endpoint](https://docs.openhands.dev/sdk/guides/agent-server/openai-gateway.md): Call an OpenHands agent-server through the OpenAI Chat Completions protocol.\n- [OpenHands Cloud Workspace](https://docs.openhands.dev/sdk/guides/agent-server/cloud-workspace.md): Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance.\n- [Overview](https://docs.openhands.dev/sdk/guides/agent-server/overview.md): Run agents on remote servers with isolated workspaces for production deployments.\n- [Parallel Tool Execution](https://docs.openhands.dev/sdk/guides/parallel-tool-execution.md): Execute multiple tools concurrently within a single LLM response to improve throughput for independent operations.\n- [Pause and Resume](https://docs.openhands.dev/sdk/guides/convo-pause-and-resume.md): Pause agent execution, perform operations, and resume without losing state.\n- [Persistence](https://docs.openhands.dev/sdk/guides/convo-persistence.md): Save and restore conversation state for multi-session workflows.\n- [Plugins](https://docs.openhands.dev/sdk/guides/plugins.md): Plugins bundle skills, hooks, MCP servers, agents, and commands into reusable packages that extend agent capabilities.\n- [PR Review](https://docs.openhands.dev/sdk/guides/github-workflows/pr-review.md): Use OpenHands Agent to generate meaningful pull request review\n- [Reasoning](https://docs.openhands.dev/sdk/guides/llm-reasoning.md): Access model reasoning traces from Anthropic extended thinking and OpenAI responses API.\n- [Secret Registry](https://docs.openhands.dev/sdk/guides/secrets.md): Provide environment variables and secrets to agent workspace securely.\n- [Security & Action Confirmation](https://docs.openhands.dev/sdk/guides/security.md): Control agent action execution through confirmation policy and security analyzer.\n- [Send Message While Running](https://docs.openhands.dev/sdk/guides/convo-send-message-while-running.md): Interrupt running agents to provide additional context or corrections.\n- [Software Agent SDK](https://docs.openhands.dev/sdk.md): Build AI agents that write software. A clean, modular SDK with production-ready tools.\n- [Stuck Detector](https://docs.openhands.dev/sdk/guides/agent-stuck-detector.md): Detect and handle stuck agents automatically with timeout mechanisms.\n- [Sub-Agent Delegation](https://docs.openhands.dev/sdk/guides/agent-delegation.md): Enable parallel task execution by delegating work to multiple sub-agents that run independently and return consolidated results.\n- [Task Tool Set](https://docs.openhands.dev/sdk/guides/task-tool-set.md): Delegate complex work to specialized sub-agents that run synchronously and return results to the parent agent.\n- [Theory of Mind (TOM) Agent](https://docs.openhands.dev/sdk/guides/agent-tom-agent.md): Enable your agent to understand user intent and preferences through Theory of Mind capabilities, providing personalized guidance based on user modeling.\n- [TODO Management](https://docs.openhands.dev/sdk/guides/github-workflows/todo-management.md): Implement TODOs using OpenHands Agent\n\n## Examples\n\nSource: [`examples/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples)\n\n### [`01_standalone_sdk/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk)\n\n- [`01_hello_world.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/01_hello_world.py)\n- [`02_custom_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/02_custom_tools.py)\n- [`03_activate_skill.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/03_activate_skill.py)\n- [`04_confirmation_mode_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/04_confirmation_mode_example.py)\n- [`05_use_llm_registry.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/05_use_llm_registry.py)\n- [`06_interactive_terminal_w_reasoning.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/06_interactive_terminal_w_reasoning.py)\n- [`07_mcp_integration.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/07_mcp_integration.py)\n- [`08_mcp_with_oauth.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/08_mcp_with_oauth.py)\n- [`09_pause_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/09_pause_example.py)\n- [`10_persistence.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/10_persistence.py)\n- [`11_async.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/11_async.py)\n- [`12_custom_secrets.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/12_custom_secrets.py)\n- [`13_get_llm_metrics.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/13_get_llm_metrics.py)\n- [`14_context_condenser.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/14_context_condenser.py)\n- [`15_browser_use.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/15_browser_use.py)\n- [`16_llm_security_analyzer.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/16_llm_security_analyzer.py)\n- [`17_image_input.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/17_image_input.py)\n- [`18_send_message_while_processing.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/18_send_message_while_processing.py)\n- [`19_llm_routing.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/19_llm_routing.py)\n- [`20_stuck_detector.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/20_stuck_detector.py)\n- [`21_generate_extraneous_conversation_costs.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/21_generate_extraneous_conversation_costs.py)\n- [`22_anthropic_thinking.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/22_anthropic_thinking.py)\n- [`23_responses_reasoning.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/23_responses_reasoning.py)\n- [`24_planning_agent_workflow.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/24_planning_agent_workflow.py)\n- [`25_agent_delegation.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/25_agent_delegation.py)\n- [`26_custom_visualizer.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/26_custom_visualizer.py)\n- [`27_observability_laminar.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/27_observability_laminar.py)\n- [`28_ask_agent_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/28_ask_agent_example.py)\n- [`29_llm_streaming.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/29_llm_streaming.py)\n- [`30_tom_agent.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/30_tom_agent.py)\n- [`31_iterative_refinement.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/31_iterative_refinement.py)\n- [`32_configurable_security_policy.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/32_configurable_security_policy.py)\n- [`33_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/33_hooks)\n- [`34_critic_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/34_critic_example.py)\n- [`35_subscription_login.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/35_subscription_login.py)\n- [`36_event_json_to_openai_messages.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/36_event_json_to_openai_messages.py)\n- [`37_llm_profile_store`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/37_llm_profile_store)\n- [`38_browser_session_recording.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/38_browser_session_recording.py)\n- [`39_llm_fallback.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/39_llm_fallback.py)\n- [`40_acp_agent_example.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/40_acp_agent_example.py)\n- [`41_task_tool_set.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/41_task_tool_set.py)\n- [`42_file_based_subagents.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/42_file_based_subagents.py)\n- [`44_model_switching_in_convo.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/44_model_switching_in_convo.py)\n- [`45_parallel_tool_execution.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/45_parallel_tool_execution.py)\n- [`46_agent_settings.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/46_agent_settings.py)\n- [`47_defense_in_depth_security.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/47_defense_in_depth_security.py)\n- [`48_conversation_fork.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/48_conversation_fork.py)\n- [`49_switch_llm_tool.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/49_switch_llm_tool.py)\n- [`50_async_cancellation.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/50_async_cancellation.py)\n- [`51_agent_hooks`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/01_standalone_sdk/51_agent_hooks)\n- [`52_dynamic_workflow.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/52_dynamic_workflow.py)\n- [`53_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/53_client_defined_tools.py)\n- [`54_goal_completion_loop.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/54_goal_completion_loop.py)\n\n### [`02_remote_agent_server/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server)\n\n- [`01_convo_with_local_agent_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/01_convo_with_local_agent_server.py)\n- [`02_convo_with_docker_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/02_convo_with_docker_sandboxed_server.py)\n- [`03_browser_use_with_docker_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/03_browser_use_with_docker_sandboxed_server.py)\n- [`04_convo_with_api_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/04_convo_with_api_sandboxed_server.py)\n- [`05_vscode_with_docker_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/05_vscode_with_docker_sandboxed_server.py)\n- [`06_custom_tool`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/06_custom_tool)\n- [`07_convo_with_cloud_workspace.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/07_convo_with_cloud_workspace.py)\n- [`08_convo_with_apptainer_sandboxed_server.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/08_convo_with_apptainer_sandboxed_server.py)\n- [`09_acp_agent_with_remote_runtime.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/09_acp_agent_with_remote_runtime.py)\n- [`10_cloud_workspace_share_credentials.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py)\n- [`11_conversation_fork.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/11_conversation_fork.py)\n- [`12_settings_and_secrets_api.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/12_settings_and_secrets_api.py)\n- [`13_workspace_get_llm.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/13_workspace_get_llm.py)\n- [`14_client_defined_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/14_client_defined_tools.py)\n- [`15_openai_compatible_gateway.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/15_openai_compatible_gateway.py)\n- [`16_deferred_init.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/16_deferred_init.py)\n- [`hook_scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/hook_scripts)\n- [`scripts`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/02_remote_agent_server/scripts)\n\n### [`03_github_workflows/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows)\n\n- [`01_basic_action`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/01_basic_action)\n- [`02_pr_review`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/02_pr_review)\n- [`03_todo_management`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/03_todo_management)\n- [`04_datadog_debugging`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/04_datadog_debugging)\n- [`05_posthog_debugging`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/03_github_workflows/05_posthog_debugging)\n\n### [`04_llm_specific_tools/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/04_llm_specific_tools)\n\n- [`01_gpt5_apply_patch_preset.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py)\n- [`02_gemini_file_tools.py`](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/04_llm_specific_tools/02_gemini_file_tools.py)\n\n### [`05_skills_and_plugins/`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins)\n\n- [`01_loading_agentskills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/01_loading_agentskills)\n- [`02_loading_plugins`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/02_loading_plugins)\n- [`03_managing_installed_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/03_managing_installed_skills)\n- [`04_mixed_marketplace_skills`](https://github.com/OpenHands/software-agent-sdk/tree/main/examples/05_skills_and_plugins/04_mixed_marketplace_skills)" }, { "name": "pdflatex", diff --git a/skills/openhands-sdk/SKILL.md b/skills/openhands-sdk/SKILL.md index 83123a14..e92ac93b 100644 --- a/skills/openhands-sdk/SKILL.md +++ b/skills/openhands-sdk/SKILL.md @@ -86,18 +86,21 @@ print("All done!") - [Browser Session Recording](https://docs.openhands.dev/sdk/guides/browser-session-recording.md): Record and replay your agent's browser sessions using rrweb. - [Browser Use](https://docs.openhands.dev/sdk/guides/agent-browser-use.md): Enable web browsing and interaction capabilities for your agent. - [Context Condenser](https://docs.openhands.dev/sdk/guides/context-condenser.md): Manage agent memory by condensing conversation history to save tokens. +- [Conversation Goals](https://docs.openhands.dev/sdk/guides/agent-server/conversation-goals.md): Add a resumable goal strategy to a normal agent-server conversation. - [Conversation with Async](https://docs.openhands.dev/sdk/guides/convo-async.md): Use async/await for concurrent agent operations and non-blocking execution. - [Creating Custom Agent](https://docs.openhands.dev/sdk/guides/agent-custom.md): Learn how to design specialized agents with custom tool sets - [Critic (Experimental)](https://docs.openhands.dev/sdk/guides/critic.md): Real-time evaluation of agent actions using an LLM-based critic model, with built-in iterative refinement. - [Custom Tools](https://docs.openhands.dev/sdk/guides/custom-tools.md): Tools define what agents can do. The SDK includes built-in tools for common operations and supports creating custom tools for specialized needs. - [Custom Tools with Remote Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/custom-tools.md): Learn how to use custom tools with a remote agent server by building a custom base image that includes your tool implementations. - [Custom Visualizer](https://docs.openhands.dev/sdk/guides/convo-custom-visualizer.md): Customize conversation visualization by creating custom visualizers or configuring the default visualizer. +- [Deferred Init (Warm-Pool)](https://docs.openhands.dev/sdk/guides/agent-server/deferred-init.md): Pre-warm agent-server pods before a user is matched, then activate them at runtime with POST /api/init. - [Docker Sandbox](https://docs.openhands.dev/sdk/guides/agent-server/docker-sandbox.md): Run agent server in isolated Docker containers for security and reproducibility. - [Exception Handling](https://docs.openhands.dev/sdk/guides/llm-error-handling.md): Provider‑agnostic exceptions raised by the SDK and recommended patterns for handling them. - [FAQ](https://docs.openhands.dev/sdk/faq.md): Frequently asked questions about the OpenHands SDK - [File-Based Agents](https://docs.openhands.dev/sdk/guides/agent-file-based.md): Define specialized sub-agents as simple Markdown files with YAML frontmatter — no Python code required. - [Fork a Conversation](https://docs.openhands.dev/sdk/guides/convo-fork.md): Branch off an existing conversation for follow-up exploration without contaminating the original. - [Getting Started](https://docs.openhands.dev/sdk/getting-started.md): Install the OpenHands SDK and build AI agents that write software. +- [Goal Completion Loop](https://docs.openhands.dev/sdk/guides/convo-goal.md): Drive a conversation toward a verifiable objective with a judge-driven, self-continuing completion loop. - [GPT-5 Preset (ApplyPatchTool)](https://docs.openhands.dev/sdk/guides/llm-gpt5-preset.md): Use the GPT-5 preset to build an agent that swaps the standard FileEditorTool for ApplyPatchTool. - [Hello World](https://docs.openhands.dev/sdk/guides/hello-world.md): The simplest possible OpenHands agent - configure an LLM, create an agent, and complete a task. - [Hooks](https://docs.openhands.dev/sdk/guides/hooks.md): Use lifecycle hooks to observe, log, and customize agent execution. @@ -109,11 +112,12 @@ print("All done!") - [LLM Registry](https://docs.openhands.dev/sdk/guides/llm-registry.md): Dynamically select and configure language models using the LLM registry. - [LLM Streaming](https://docs.openhands.dev/sdk/guides/llm-streaming.md): Stream LLM responses token-by-token for real-time display and interactive user experiences. - [LLM Subscriptions](https://docs.openhands.dev/sdk/guides/llm-subscriptions.md): Use your ChatGPT Plus/Pro subscription to access Codex models without consuming API credits. -- [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Run agents through a local HTTP server with RemoteConversation for client-server architecture. +- [Local Agent Server](https://docs.openhands.dev/sdk/guides/agent-server/local-server.md): Install and run an OpenHands Agent Server on your machine, then connect to it from the SDK. - [Metrics Tracking](https://docs.openhands.dev/sdk/guides/metrics.md): Track token usage, costs, and latency metrics for your agents. - [Model Context Protocol](https://docs.openhands.dev/sdk/guides/mcp.md): Model Context Protocol (MCP) enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically. - [Model Routing](https://docs.openhands.dev/sdk/guides/llm-routing.md): Route agent's LLM requests to different models. - [Observability & Tracing](https://docs.openhands.dev/sdk/guides/observability.md): Enable OpenTelemetry tracing to monitor and debug your agent's execution with tools like Laminar, MLflow, Honeycomb, or any OTLP-compatible backend. +- [OpenAI-Compatible Endpoint](https://docs.openhands.dev/sdk/guides/agent-server/openai-gateway.md): Call an OpenHands agent-server through the OpenAI Chat Completions protocol. - [OpenHands Cloud Workspace](https://docs.openhands.dev/sdk/guides/agent-server/cloud-workspace.md): Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance. - [Overview](https://docs.openhands.dev/sdk/guides/agent-server/overview.md): Run agents on remote servers with isolated workspaces for production deployments. - [Parallel Tool Execution](https://docs.openhands.dev/sdk/guides/parallel-tool-execution.md): Execute multiple tools concurrently within a single LLM response to improve throughput for independent operations. diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index b65847e8..05e70c91 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -40,10 +40,10 @@ def test_catalog_entries_have_required_fields(): assert entry["id"] assert entry["name"] assert entry["description"] - assert entry["kind"] in {"mcp", "http"} - assert entry["iconBg"] + # iconBg/iconColor are optional UI styling hints (OAuth-only entries may + # ship without a bespoke icon background). + assert "iconBg" not in entry or entry["iconBg"] assert entry["connectionOptions"] - assert entry["defaultConnectionOptionId"] for option in entry["connectionOptions"]: assert option["id"] assert option["provider"] in {"mcp", "http"} @@ -72,6 +72,24 @@ def test_catalog_entries_have_required_fields(): assert isinstance(entry["estimatedSetupMinutes"], int) +def test_remote_no_auth_mcp_entries_are_intentionally_public(): + public_remote_mcp_ids = {"cloudflare-docs", "deepwiki", "huggingface"} + + actual = set() + for entry in load_catalog_entries("integrations/catalog"): + for option in entry["connectionOptions"]: + transport = option.get("transport", {}) + if ( + option["provider"] == "mcp" + and option["auth"]["strategy"] == "none" + and transport.get("url", "").startswith("https://") + ): + actual.add(entry["id"]) + assert transport["kind"] == "shttp" + + assert actual == public_remote_mcp_ids + + def test_credential_fields_have_helper_text_and_link(): """All password fields must have helperText plus a link (either a helperLink field or a markdown link embedded in helperText) so users know how to get credentials.""" diff --git a/tests/test_integration_catalog_in_sync.py b/tests/test_integration_catalog_in_sync.py new file mode 100644 index 00000000..b6c12521 --- /dev/null +++ b/tests/test_integration_catalog_in_sync.py @@ -0,0 +1,204 @@ +"""Assert integration catalog files drive the generated JS/Python assets.""" + +from __future__ import annotations + +import json +import subprocess +from pathlib import Path + +import openhands_extensions + +ROOT = Path(__file__).resolve().parents[1] +CATALOG_DIR = ROOT / "integrations" / "catalog" +CATALOG_INDEX = ROOT / "integrations" / "catalog-index.js" +AGGREGATE_ASSETS = [ + ROOT / "integrations" / "integration-catalog.json", + ROOT / "python" / "openhands_extensions" / "integration-catalog.json", +] + + +def _catalog_files() -> dict[str, dict]: + return {p.stem: json.loads(p.read_text()) for p in CATALOG_DIR.glob("*.json")} + + +def _catalog_entries() -> list[dict]: + entries = list(_catalog_files().values()) + return sorted(entries, key=lambda entry: (-(entry.get("popularityRank") if entry.get("popularityRank") is not None else -1), entry["id"])) + + +def _supports_mcp(entry: dict) -> bool: + return any(option.get("provider") == "mcp" for option in entry["connectionOptions"]) + + +def _supports_oauth(entry: dict) -> bool: + return any(option.get("auth", {}).get("strategy") == "oauth2" for option in entry["connectionOptions"]) + + + +def test_catalog_directory_is_hand_authored_source_of_truth() -> None: + for integration_id, entry in _catalog_files().items(): + assert entry["id"] == integration_id + assert "supportsMcp" not in entry + assert "supportsOauth" not in entry + assert "oauthProvider" not in entry + assert "registrationDefaults" not in entry + assert "managedConnectorSlug" not in entry + assert "authStrategy" not in entry + assert "defaultConnectionOptionId" not in entry + assert "kind" not in entry + assert "catalogStatus" not in entry + assert "availability" not in entry + assert "runtimeAvailability" not in entry + + +def test_no_aggregate_catalog_json_exists() -> None: + for asset in AGGREGATE_ASSETS: + assert not asset.exists() + + +def test_generated_js_index_references_catalog_directory() -> None: + body = CATALOG_INDEX.read_text() + for file in sorted(path.name for path in CATALOG_DIR.glob("*.json")): + assert f"./catalog/{file}" in body + assert "integration-catalog.json" not in body + + +def test_python_snapshot_is_built_from_catalog_directory() -> None: + assert openhands_extensions.INTEGRATION_CATALOG_SNAPSHOT == { + "integrations": _catalog_entries() + } + + +def test_python_list_integration_catalog_returns_raw_entries() -> None: + entries = openhands_extensions.list_integration_catalog() + assert entries == _catalog_entries() + for entry in entries: + assert "supportsMcp" not in entry + assert "supportsOauth" not in entry + assert "registrationDefaults" not in entry + assert "managedConnectorSlug" not in entry + assert "authStrategy" not in entry + assert "defaultConnectionOptionId" not in entry + assert "catalogStatus" not in entry + assert "availability" not in entry + assert "runtimeAvailability" not in entry + + +def test_logo_metadata_is_serializable_and_language_agnostic() -> None: + entries = openhands_extensions.list_integration_catalog() + with_logo = [entry for entry in entries if entry.get("logoUrl")] + assert with_logo + assert any(entry["id"].startswith("cloudflare-") for entry in with_logo) + for entry in with_logo: + assert isinstance(entry["logoUrl"], str) + assert entry["logoUrl"].startswith("https://") + assert "react" not in entry["logoUrl"].lower() + + +def test_get_integration_catalog_entry_round_trip() -> None: + github = openhands_extensions.get_integration_catalog_entry("github") + assert github is not None + assert github == next( + entry for entry in openhands_extensions.list_integration_catalog() if entry["id"] == "github" + ) + assert openhands_extensions.get_integration_catalog_entry("nope") is None + + +def _js_call(expr: str) -> str: + result = subprocess.run( + ["node", "--input-type=module", "-e", expr], + capture_output=True, + text=True, + check=True, + cwd=ROOT, + ) + return result.stdout + + +def test_js_reads_the_same_catalog_as_python() -> None: + integrations = _js_call( + "import { listIntegrationCatalog } from './integrations/index.js';\n" + "process.stdout.write(JSON.stringify(listIntegrationCatalog()));" + ) + assert json.loads(integrations) == openhands_extensions.list_integration_catalog() + + github = _js_call( + "import { getIntegrationCatalogEntry } from './integrations/index.js';\n" + "process.stdout.write(JSON.stringify(getIntegrationCatalogEntry('github')));" + ) + assert json.loads(github) == openhands_extensions.get_integration_catalog_entry("github") + + +def _js_filter(mcp, oauth) -> list[str]: + expr = ( + "import { listIntegrationCatalog } from './integrations/index.js';\n" + f"const f = listIntegrationCatalog({{ mcp: {mcp}, oauth: {oauth} }});\n" + "process.stdout.write(JSON.stringify(f.map((e) => e.id)));" + ) + return json.loads(_js_call(expr)) + + +def test_filter_mcp_only() -> None: + py_ids = {e["id"] for e in openhands_extensions.list_integration_catalog(mcp=True)} + js_ids = set(_js_filter("true", "undefined")) + all_mcp = {e["id"] for e in openhands_extensions.list_integration_catalog() if _supports_mcp(e)} + assert py_ids == js_ids == all_mcp + assert "filesystem" in py_ids + + +def test_filter_oauth_only() -> None: + py_ids = {e["id"] for e in openhands_extensions.list_integration_catalog(oauth=True)} + js_ids = set(_js_filter("undefined", "true")) + all_oauth = {e["id"] for e in openhands_extensions.list_integration_catalog() if _supports_oauth(e)} + assert py_ids == js_ids == all_oauth + assert "github" in py_ids + + +def test_filter_oauth_not_mcp() -> None: + py_ids = { + e["id"] for e in openhands_extensions.list_integration_catalog(oauth=True, mcp=False) + } + js_ids = set(_js_filter("false", "true")) + expected = { + e["id"] + for e in openhands_extensions.list_integration_catalog() + if _supports_oauth(e) and not _supports_mcp(e) + } + assert py_ids == js_ids == expected + + +def test_filter_none_returns_all() -> None: + assert len(openhands_extensions.list_integration_catalog()) == len( + openhands_extensions.list_integration_catalog(mcp=None, oauth=None) + ) + + +def test_accessors_return_independent_copies() -> None: + catalog_a = openhands_extensions.list_integration_catalog() + catalog_b = openhands_extensions.list_integration_catalog() + assert catalog_a == catalog_b + assert catalog_a is not catalog_b + catalog_a[0]["__mutated"] = True + assert "__mutated" not in openhands_extensions.list_integration_catalog()[0] + + github = openhands_extensions.get_integration_catalog_entry("github") + assert github is not None + github["__mutated"] = True + assert "__mutated" not in openhands_extensions.get_integration_catalog_entry("github") + + snapshot = openhands_extensions.INTEGRATION_CATALOG_SNAPSHOT + snapshot["integrations"][0]["__mutated"] = True + assert "__mutated" not in openhands_extensions.list_integration_catalog()[0] + + +def test_hubspot_oauth_config_lives_on_connection_option() -> None: + hubspot = openhands_extensions.get_integration_catalog_entry("hubspot") + assert hubspot is not None + assert "registrationDefaults" not in hubspot + option = hubspot["connectionOptions"][0] + assert option["provider"] == "mcp" + assert option["transport"]["url"] == "https://mcp.hubspot.com" + oauth = option["auth"]["oauth"] + assert oauth["authorizationUrl"] == "https://mcp.hubspot.com/oauth/authorize/user" + assert oauth["tokenUrl"] == "https://mcp.hubspot.com/oauth/v3/token" + assert oauth["pkce"] is True diff --git a/tests/test_version_alignment.py b/tests/test_version_alignment.py new file mode 100644 index 00000000..29a9c7c4 --- /dev/null +++ b/tests/test_version_alignment.py @@ -0,0 +1,64 @@ +"""Assert the JS (``package.json``) and Python (``pyproject.toml`` + +``_version.py``) package versions never drift apart. + +``release-please`` is configured to bump both ``package.json`` and +``pyproject.toml`` together (see ``release-please-config.json`` -> +``extra-files``). This test is the guard that catches a manual edit that +bumps only one. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def _package_json_version() -> str: + text = (ROOT / "package.json").read_text() + match = re.search(r'"version"\s*:\s*"([^"]+)"', text) + assert match, "package.json has no version field" + return match.group(1) + + +def _pyproject_version() -> str: + text = (ROOT / "pyproject.toml").read_text() + match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE) + assert match, "pyproject.toml has no version field" + return match.group(1) + + +def _version_module_version() -> str: + import openhands_extensions._version as v + + text = (ROOT / "python" / "openhands_extensions" / "_version.py").read_text() + match = re.search(r'_FALLBACK_VERSION\s*=\s*"([^"]+)"', text) + assert match, "_version.py has no _FALLBACK_VERSION" + fallback = match.group(1) + # Runtime __version__ is metadata-derived when installed, fallback otherwise; + # both must agree with the declared versions. + assert v.__version__ in (fallback, _package_json_version()), ( + f"runtime __version__ {v.__version__!r} disagrees with fallback/package.json" + ) + return fallback + + +def test_package_json_and_pyproject_versions_match() -> None: + assert _package_json_version() == _pyproject_version(), ( + "package.json and pyproject.toml versions differ. release-please " + "should bump both together; check release-please-config.json." + ) + + +def test_version_module_matches_package_json() -> None: + assert _version_module_version() == _package_json_version(), ( + "python/openhands_extensions/_version.py is out of sync with " + "package.json." + ) + + +def test_installed_package_version_matches_source() -> None: + import openhands_extensions + + assert openhands_extensions.__version__ == _package_json_version()