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
- 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.
- 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.
- 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
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.
Part of the October Challenge #846.
Why
Today you can only back up all connectors at once (
Exporton 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
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.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 toreq.user.organizationId.What to build
GET /api/connectors/:id/export?format=json|yaml(defaultjson).{ version: '1.0', exportedAt, secretsIncluded: false, connectors: [ … ] }) with one connector. That way the existingPOST /api/connectors/import-allrestores it unchanged.toPublicConnector()(env vars and headers emptied and listed inmaskedEnvVars/maskedHeaders;authConfigdropped).user:password@frombaseUrlwhenisSecretValue(baseUrl)(database connectors use connection strings);endpointMapping.headersvalue whose name passesisSecretName()or whose value passesisSecretValue()(a cURL import stores literal headers such asX-Api-Key).format=yaml: serialize withjs-yaml(already a backend dependency) usingdump(data, { noRefs: true }). Respond withContent-Type: application/yamlandContent-Disposition: attachment; filename="<slug>.anythingmcp.yaml"(or.json).formatquery onGET export-all.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 ashandleExportAll.connectors/page.tsx): accept.json,.yaml,.yml. If the text does not start with{or[, parse it as YAML withjs-yamlload(text, { schema: JSON_SCHEMA })(addjs-yamlto 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()andImportAllDto(line 493). It already accepts the envelope and themasked*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/importAllclients.Acceptance criteria
postgres://u:p@hostbase URL and a tool with a literalX-Api-Keyheader produces a file without any of those values, also for an ADMIN (unit test).import-allinto an identical connector (minus secrets); round-trip test next toconnectors.import-all.spec.ts..yamlfile works from the dialog. A YAML file with!!js/functionis rejected. Playwright test inpackages/frontend/tests/e2e/(runnpm run test:e2e -w packages/frontend).cd packages/backend && npx jest src/connectors.export-all/import-all, which are missing) added to the connectors table indocs/api-reference.md(around line 85); a short "Share a single connector" note indocs/connectors/rest.md.Out of scope
Size
M (1–2 days)
How to claim
Comment "I'd like to work on this" and we'll assign you. Rules in #846.