Skip to main content
[AVAILABLE] as a library. packages/connector-sdk.

Connector responsibilities — and what a Connector must never do

A Connector validates a request, executes using an already-resolved credential, and returns a deterministic response. A Connector never evaluates policy, authorizes execution, interprets AI output, makes a business decision, or resolves a credential itself — credential resolution happens exclusively inside the Execution Gateway, before a Connector is ever called.
import {
  connectorCapabilities,
  type Connector,
  type ConnectorExecutionContext,
  type ConnectorRequest,
  type ConnectorResponse,
} from "@parmana/connector-sdk";

class ExampleConnector implements Connector {
  readonly connectorId = "example";
  readonly capabilities = connectorCapabilities(["crm:read"]);

  async execute(request: ConnectorRequest, context: ConnectorExecutionContext): Promise<ConnectorResponse> {
    if (!this.capabilities.includes(request.capability)) {
      throw new Error(`does not declare capability "${request.capability}"`);
    }
    // context.credential is an opaque CredentialHandle — read it, never log it.
    return { success: true, metadata: { recordId: "example-1" } };
  }
}

Capabilities are namespaced verbs

ConnectorCapability is a plain string, validated eagerly by connectorCapabilities() against namespace:verb (e.g. http:get, crm:read, payments:refund) — a malformed capability throws at connector construction, not at execution time. By convention, ExecutableContent.action is the capability string; this is what execution-control’s DefaultConnectorPolicy already checks unchanged.

Registering a connector

import { ConnectorSdkRegistry, StaticCredentialProvider, healthyNow } from "@parmana/connector-sdk";

const registry = new ConnectorSdkRegistry();

registry.register({
  connector: new ExampleConnector(),
  metadata: {
    connectorId: "example",
    displayName: "Example CRM",
    version: { major: 1, minor: 0, patch: 0 },
    health: healthyNow(),
  },
  connectorIdentity: { connectorId: "example", publicIdentity: "spiffe://parmana/connectors/example", authenticationMetadata: {} },
  credentialProvider: new StaticCredentialProvider({ example: { token: "..." } }),
  policy, // a ConnectorPolicy — e.g. CapabilityConnectorPolicy(new DefaultConnectorPolicy(...))
  gatewayAuthentication,
  crypto, // @parmana/crypto CryptoProvider — reused for evidence hashing, not reimplemented
});
registry implements execution-control’s ConnectorRegistry interface (get()), so it plugs directly into ExecutionControlService exactly like InMemoryConnectorRegistry.

Testing your connector hermetically

Use MockConnector to test anything upstream of your connector (policy wiring, routing) without a real target system:
import { MockConnector, connectorCapabilities } from "@parmana/connector-sdk";

const connector = new MockConnector({
  connectorId: "example",
  capabilities: connectorCapabilities(["crm:read"]),
  script: { respond: () => ({ success: true, metadata: { recordId: "example-1" } }) },
});
To test failure handling, use script: { failWith: new Error("upstream unavailable") }. connector.invocations records every ConnectorRequest the connector received, so tests can assert exactly what was executed.

Version and health checks fail closed

SdkConnectorExecutor rejects execution — before invoking your connector — if:
  • an expectedVersion was configured at registration and the connector’s own metadata.version doesn’t match (guards a rolling deployment that swapped connector builds underneath a pinned expectation), or
  • metadata.health.status === "unavailable".

What every connector’s evidence looks like

Every execution produces a ConnectorEvidence object — connector ID, version, capability, a sanitized endpoint (credentials and query parameters stripped), the credential provider’s ID, redacted request/response summaries, timestamps, and a hash computed by the existing TrustRecordHasher — attached at ExecutionResult.metadata.connector. See Execution Trust Record for how this reaches the Trust Record without any change to its schema or hashing pipeline.