Skip to content

🎃 Export a single connector as JSON or YAML, and accept YAML on import #855

Description

@keysersoft

Part of the October Challenge #846.

Why

Today you can only back up all connectors at once (Export on the Connectors page). That works for backups, but not for sharing one hand-built connector with a colleague, attaching it to a bug report, or keeping it in Git. For Git, YAML diffs better than JSON. Users ask for "export this connector" from the connector page, and for a format they can review in a pull request.

Self-hosted vs Cloud

  • Self-hosted: available in Community and Business, no licence gate.
  • Cloud (DEPLOYMENT_MODE=cloud): same behaviour. Import still goes through the existing licence/trial check per connector (licenseGuard.checkCanCreateConnector), so Cloud plan limits apply unchanged. Do not touch them.
  • Who can use it: export is available to any member who can open the connector (same check as GET /api/connectors/:id, i.e. assertOrgMatch; VIEWER included). This is safe only because the single-connector export never contains secrets, whatever the role (see below). Import keeps today's rule: VIEWER is refused (assertCanCreate). All reads and writes are scoped to req.user.organizationId.

What to build

  1. Backend: GET /api/connectors/:id/export?format=json|yaml (default json).
    • Return the same envelope as export-all ({ version: '1.0', exportedAt, secretsIncluded: false, connectors: [ … ] }) with one connector. That way the existing POST /api/connectors/import-all restores it unchanged.
    • Secrets are always redacted, also for ADMIN: build the connector with toPublicConnector() (env vars and headers emptied and listed in maskedEnvVars / maskedHeaders; authConfig dropped).
    • Defence in depth, because a shared file travels further than a backup:
      • strip user:password@ from baseUrl when isSecretValue(baseUrl) (database connectors use connection strings);
      • empty any tool endpointMapping.headers value whose name passes isSecretName() or whose value passes isSecretValue() (a cURL import stores literal headers such as X-Api-Key).
    • format=yaml: serialize with js-yaml (already a backend dependency) using dump(data, { noRefs: true }). Respond with Content-Type: application/yaml and Content-Disposition: attachment; filename="<slug>.anythingmcp.yaml" (or .json).
    • Optional, cheap: accept the same format query on GET export-all.
  2. Frontend: connector page (connectors/[id]/page.tsx): an Export button in the header actions with a small JSON/YAML choice. It downloads the file using the same Blob pattern as handleExportAll.
  3. Frontend: import dialog (connectors/page.tsx): accept .json,.yaml,.yml. If the text does not start with { or [, parse it as YAML with js-yaml load(text, { schema: JSON_SCHEMA }) (add js-yaml to the frontend). JSON_SCHEMA means no custom tags and no !!js/*. Then send the same JSON body as today. Show a clear error for invalid YAML. Update the dialog text ("JSON or YAML").

Where to look

  • packages/backend/src/connectors/connectors.controller.ts:827: exportAll(). Copy the field list; note that export-all can include secrets for an ADMIN backup (secretsIncluded), which the single export must never do.
  • packages/backend/src/connectors/connectors.controller.ts:879: findOne(), the access check to reuse. Declare the new route so it does not clash with :id.
  • packages/backend/src/connectors/connectors.controller.ts:1337: importAll() and ImportAllDto (line 493). It already accepts the envelope and the masked* fields.
  • packages/backend/src/connectors/connector-secrets.util.ts:77, :85, :267: isSecretName, isSecretValue, toPublicConnector.
  • packages/backend/src/connectors/connectors.import-all.spec.ts:116: the export → import round-trip tests. Copy this pattern.
  • packages/frontend/src/app/connectors/page.tsx:140: handleExportAll / handleImportAll (line 162) / import dialog (line 270ff, accept=".json").
  • packages/frontend/src/app/connectors/[id]/page.tsx:753: header actions (Test / Edit / Delete).
  • packages/frontend/src/lib/api.ts:422: connectors.exportAll / importAll clients.

Acceptance criteria

  • Exporting a connector with secret env vars, a secret header, a postgres://u:p@host base URL and a tool with a literal X-Api-Key header produces a file without any of those values, also for an ADMIN (unit test).
  • The JSON and YAML exports both re-import through import-all into an identical connector (minus secrets); round-trip test next to connectors.import-all.spec.ts.
  • Another organization's connector id returns 403/404, never data.
  • Importing a .yaml file works from the dialog. A YAML file with !!js/function is rejected. Playwright test in packages/frontend/tests/e2e/ (run npm run test:e2e -w packages/frontend).
  • Backend tests pass: cd packages/backend && npx jest src/connectors.
  • Docs: the new endpoint (and the existing export-all / import-all, which are missing) added to the connectors table in docs/api-reference.md (around line 85); a short "Share a single connector" note in docs/connectors/rest.md.

Out of scope

  • Including secrets in single exports (even as an opt-in).
  • Overwriting or merging into an existing connector on import (duplicates are still skipped by name).
  • Changing the export-all secret behaviour for admins.

Size

M (1–2 days)

How to claim

Comment "I'd like to work on this" and we'll assign you. Rules in #846.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthacktoberfestGood for Hacktoberfest contributorshelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions