> ## Documentation Index
> Fetch the complete documentation index at: https://docs.manthan.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Connector Development Guide

> How to implement a Connector against @parmana/connector-sdk — every example uses MockConnector or HttpConnector, no enterprise-specific example.

<Info>**\[AVAILABLE] as a library.** `packages/connector-sdk`.</Info>

## 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.

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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](/trust-record/overview) for how this reaches the Trust Record
without any change to its schema or hashing pipeline.
