Create a connector
Package a user-owned external client and namespaced tools without routing credentials through Noir.
A connector is a thin composition unit around an external system. It contains tools, an optional client for application code, a health check, and cleanup.
import { connector, jsonSchema, tool } from '@noir-agent/agent'
export function linear(options: { token: string }) {
const client = new LinearClient({ apiKey: options.token })
return connector({
client,
tools: {
getIssue: tool({
description: 'Read one Linear issue by identifier.',
input: jsonSchema<{ id: string }>({
type: 'object',
properties: { id: { type: 'string' } },
required: ['id'],
additionalProperties: false,
}),
effect: 'read',
retry: 'safe',
execute: ({ id }, { signal }) => client.issue(id, { signal }),
project: (issue) => ({ id: issue.id, title: issue.title, state: issue.state?.name }),
}),
},
health: async () => ({ ok: await client.ping() }),
close: () => client.close(),
})
}Register it by name:
connectors: {
linear: linear({ token: process.env.LINEAR_API_KEY! }),
}The tool becomes linear.getIssue. Connector names are part of the model-facing API, so keep them stable.
Design guidance
Create the provider client in application code. Accept explicit credentials or use the provider SDK's established environment defaults. Do not make users configure an OpenRouter, Stripe, or database client “through Noir” when the native library already has a clear API.
Use close when the client owns sockets or polling. Use health for a bounded permission check, not an expensive full account scan. Never expose the raw client to model output; client is for host code and services.
Connector versus plugin
Choose a connector for a named system and its operations. Choose a plugin when the bundle also needs hooks, routes, policy defaults, prompts, services, or startup lifecycle. A plugin can still wrap a provider toolkit, while the toolkit itself remains created by the provider's package.