Connector manifest
A connector has no separate manifest file: the ConnectorDef you pass to defineConnector() is the manifest. The platform reads it to register capabilities, publish the marketplace listing, wire event streams, and decide which tools route through the deterministic executor. This page documents every field of that schema, the capability declarations it implies, delivery over MCP, and the versioning rules the registry enforces.
The manifest is the definition
The whole schema, exactly as exported from @fibric/connector-sdk:
export interface ConnectorDef {
id: string;
version: string;
category: ConnectorCategory;
publisher?: 'first-party' | 'partner' | 'private';
auth: AuthSchema;
tools: Record<string, ToolDef>;
events?: Record<string, { kind: 'webhook' | 'poll'; topic?: string }>;
probe?: (ctx: ConnectorCtx) => { status: string; metric?: { label: string; value: unknown } };
}
export function defineConnector(def: ConnectorDef): ConnectorDef;
defineConnector() returns the def unchanged. There is no hidden registration side effect and no build step that transforms it; what you export is what the platform reads. That flatness is deliberate: the def can be reviewed in a pull request, diffed between versions, and validated by fibric connectors test without executing a handler.
Field reference
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Stable identifier, unique across the registry. Convention: cn- prefix for connectors, op- for operator packs. |
version | string | yes | Semver. Published versions are immutable; see versioning. |
category | ConnectorCategory | yes | One of eight values; drives marketplace placement and capability namespacing. |
publisher | 'first-party' | 'partner' | 'private' | no | Who stands behind the listing. Omitted means private: visible only inside your tenant. |
auth | AuthSchema | yes | How credentials are obtained, as a declaration of kind. Never carries material. See auth patterns. |
tools | Record<string, ToolDef> | yes | The capabilities this connector provides, keyed by capability name. See exposing actions. |
events | Record<string, {...}> | no | The event streams this connector emits into the envelope bus. See emitting events. |
probe | (ctx) => {...} | no | Health check the platform calls to render connection status. |
id
The id is permanent. It appears in receipts (action.connector), in capability bindings, and in marketplace URLs; changing it is publishing a different connector. Use a short, lowercase, hyphenated name with the type prefix: cn-kustomer, cn-magento, cn-amazon-connect are the live first-party examples. The registry rejects an id already claimed by another publisher.
version
Strict semver, no prerelease tags in published listings. The version in the def is the version the registry records at fibric publish time; there is no separate package version to drift from it.
category
export type ConnectorCategory =
| 'crm'
| 'commerce'
| 'voice'
| 'shipping'
| 'comms'
| 'data'
| 'hardware'
| 'ai-operator';
| Category | Covers | Live example |
|---|---|---|
crm | Helpdesks, CRM, support platforms | cn-kustomer |
commerce | Order and catalog systems | cn-magento |
voice | Telephony and contact-center platforms | cn-amazon-connect |
shipping | Carriers and fulfillment | — |
comms | Messaging, email, notifications | — |
data | Warehouses, feeds, generic data sources | — |
hardware | Building systems, sensors, industrial gateways | — |
ai-operator | Operator packs; operators are connectors too | op-order-sentinel, op-radar-analyst |
The category is coarse on purpose. Fine-grained matching happens through capability names, not categories; the category is for humans browsing the marketplace and for review routing.
publisher
first-party is reserved for Fibric-maintained connectors. partner listings carry the publisher's name and go through marketplace review. private connectors skip review entirely and are installable only within the publishing tenant, which is the right setting for internal systems that will never be listed.
auth
An AuthSchema is a kind plus optional OAuth scopes; it never carries credential material, and no field of the def accepts any. The helpers oauth2(), apiKey(), and none() cover the common kinds; basic, aws_iam, and mtls are declared as literals. The full treatment, including rotation and secret storage, is on connector auth patterns.
tools
The tools record is the connector's capability surface. Keys are the capability names operators will request ('conversation.read', 'order.hold'); values are ToolDefs built with tool(). The one field the registry cares deeply about is sideEffecting: it decides, at registration time and immutably per version, whether a tool runs inline as a read or is reachable only through a validated ExecutionPlan. The handler contract, input validation, and idempotency rules are on exposing actions.
events
Each entry declares one stream the connector turns into EventEnvelopes, keyed by the event_type it will emit. kind: 'webhook' means the source pushes and the platform provisions an ingest URL per connection; kind: 'poll' means the platform schedules pulls, which is the shape for hardware and for APIs without webhooks. The optional topic names the upstream subscription (a webhook topic, an MQTT topic, a queue). Field-level rules for the envelopes you emit are on emitting events.
probe
The probe is a cheap, read-only health check the platform calls periodically and on the connection settings screen. Return status: 'ok' when a lightweight authenticated call succeeds, anything else to surface degradation, and optionally one metric worth showing next to the status light.
probe: (ctx) => ({
status: 'ok',
metric: { label: 'open conversations', value: 214 },
}),
The probe runs outside the executor, so it must be strictly read-only. A probe that mutates the target system is a governance bypass and fails marketplace review.
Capability declarations
Tool keys are not private names; they are the public vocabulary of the capability layer. When your def declares 'order.hold', the registry records that this connector provides the order.hold capability, and any operator requiring it can be bound to your connector without either side naming the other. Two rules follow:
- Use the shared vocabulary where it exists. If the capability index already defines
conversation.read, provide it under that exact name rather than inventingticket.fetch. Matching is by string equality; a synonym is an incompatibility. - Name new capabilities as
noun.verb, lowercase, dot-separated, with the noun in the singular:shipment.reroute, notreroute_shipments. New names enter the shared vocabulary when the listing is reviewed.
// from this def...
tools: {
'conversation.read': tool({ handler: readConversation }),
'note.write': tool({ sideEffecting: true, input: NoteArgs, handler: writeNote }),
},
events: {
'conversation.created': { kind: 'webhook', topic: 'conversations' },
},
// ...the registry records:
// provides capability conversation.read (read)
// provides capability note.write (governed write)
// emits event_type conversation.created
Delivery over MCP
A registered connector is served as an MCP server. Each entry in tools becomes an MCP tool with the same name; the input validator's shape is surfaced as the tool's input schema; events feed the envelope bus rather than the MCP session. You do not write any MCP plumbing, and you cannot opt out of it: the protocol surface is generated from the def, which is what keeps a helpdesk, a BACnet gateway, and an operator pack speaking one protocol.
Two consequences worth knowing. First, tool names must be valid MCP tool identifiers, which the noun.verb convention already satisfies. Second, the governance boundary is preserved across the protocol: a side-effecting MCP tool call from any client still lands in the executor and is disposed under the tenant's trust policy. MCP is a transport, not a back door.
Versioning
The registry treats the def as an interface and holds versions to semver discipline:
| Change | Bump | Notes |
|---|---|---|
| Bug fix inside a handler; probe changes; log changes | patch | No visible contract change. Auto-applied within the installed minor line. |
| New tool or event; new optional input field; widened enum | minor | Additive. Existing bindings are unaffected; new capabilities become bindable. |
Removed or renamed tool or event; narrowed input; changed sideEffecting; changed auth.kind | major | Breaking. Installed connections stay on their version until an operator of the tenant re-binds explicitly. |
Published versions are immutable, and receipts record the connector version that executed every action, so an audit can always answer which contract a side effect ran under. Flipping sideEffecting in either direction is always major: it moves a tool across the governance boundary, which is the most consequential change a def can make.
A complete manifest
Every field in use, in the shape review expects:
import { defineConnector, tool, oauth2 } from '@fibric/connector-sdk';
import { z } from 'zod';
const NoteArgs = z.object({
conversation_id: z.string().min(1),
body: z.string().min(1).max(4000),
});
export default defineConnector({
id: 'cn-brightdesk',
version: '1.2.0',
category: 'comms',
publisher: 'partner',
auth: oauth2({ scopes: ['conversations.read', 'notes.write'] }),
tools: {
'conversation.read': tool({
input: (a) => ({ conversation_id: z.string().parse((a as any).conversation_id) }),
handler: async (ctx, args) => {
ctx.log('read', { id: args.conversation_id });
// return await ctx.http.get(`/v1/conversations/${args.conversation_id}`);
return { id: args.conversation_id, status: 'open' };
},
}),
'note.write': tool({
sideEffecting: true,
input: (a) => NoteArgs.parse(a),
handler: async (ctx, args) => {
// return await ctx.http.post(`/v1/conversations/${args.conversation_id}/notes`,
// { body: args.body });
return { ok: true };
},
}),
},
events: {
'conversation.created': { kind: 'webhook', topic: 'conversations' },
'conversation.updated': { kind: 'webhook', topic: 'conversations' },
},
probe: (ctx) => ({ status: 'ok', metric: { label: 'open conversations', value: 214 } }),
});
Keep going
- Connector auth patterns: the
authfield in working depth. - Emitting events: field-level rules for the envelopes your streams produce.
- Exposing actions: the
tool()contract, preconditions, and idempotency. - Publishing to the marketplace: listing metadata and the review process.