Connector reference¶
Lookup tables for connector authors and host applications: the connectors.yaml schema,
the factory and registry APIs, how to call a connector in-process, and the catalog of what ships today.
For walkthroughs, see Use a connector and Build a connector.
config/connectors.yaml¶
connectors:
<connector_id>:
enabled: true # false → connector not instantiated
exposed_via: # controls which bindings surface this connector
- rest
- grpc
- mcp
# connector-specific keys passed via SecretProvider or connector __init__
ConnectorFactory API¶
| Method | Description |
|---|---|
load() |
Reads connectors.yaml and bootstraps the runtime config store. Does not instantiate connectors — instantiation is lazy. |
await get(connector_id, *, tenant_id=None, config_name=None) |
Resolves the tenant's config from the store (the default, or config_name), then returns the cached tenant-pinned instance or builds one. Raises ConfigNotFoundError when the tenant has no config — existence is entitlement. Omitting tenant_id means __default__. |
store |
The runtime ConnectorConfigStore (create / update / delete / set_default / list). Writes drop the affected cached instances immediately. |
get_for_protocol(id, protocol, action=None) |
Sync, __default__ tenant only. Returns the default-tenant instance, or None if the connector isn't enabled or isn't exposed for that protocol in YAML. For playground and enumeration — use get() for tenant-scoped calls. |
is_exposed(connector_id, protocol) |
True if the YAML entry lists protocol in exposed_via. Connectors with no YAML entry (pushed only through the store) are exposed on every protocol. Does not check enabled. |
list_for_protocol(protocol) |
Default-tenant instances of every enabled, registered connector exposed for protocol. |
Connector registry API¶
get_connector_registry() is defined in base_connector.py and exported from the top-level node_wire_runtime package — it is not in node_wire_runtime.connector_registry. Use it to read the connector-id → class map after auto_register() has imported your logic module:
from node_wire_runtime import get_connector_registry
from node_wire_runtime.connector_registry import auto_register
auto_register() # requires NW_ALLOWED_CONNECTORS
registry = get_connector_registry() # Dict[str, Type[BaseConnector]]
connector_cls = registry["google_drive"]
For the full run pipeline (YAML config, instantiation, protocol routing), use ConnectorFactory (see Calling a connector directly).
Calling a connector directly (in-process)¶
Use connector.run(dict) for the full pipeline (validation, policy, retries, error mapping).
Set NW_ALLOWED_CONNECTORS to a comma-separated list of entry-point names (e.g. google_drive) before calling auto_register() — without it, auto_register() loads nothing (fail-closed).
Scope policy applies here too. The factory attaches the same policy hook to every run(), however the instance was obtained. With the default NW_MCP_SCOPE_POLICY_DEFAULT=deny, a call with no principal or scopes fails with POLICY_DENIED. How to grant scopes, or switch to allow for local scripts: Build a connector — Security.
For a complete, runnable walkthrough — two tenants, the config store, TENANT_MISMATCH, and revoking access — see Use a connector.
Multi-tenant / named configs: resolve the tenant in your host, then await factory.get("google_drive", tenant_id=tenant_id, config_name=name). The instance is pinned to that tenant. The contract is in Tenancy — the pin contract.
For composing actions within a connector, use self.call_action. It routes through connector.run so policy hooks, resilience, and the ConnectorResponse error path apply (including MCP scope policy). It returns the nested action’s output model on success (validated from run()’s data). On policy denial it raises PolicyDenied, which the outer run() maps like any other action failure.
Optional keyword args principal, tenant_id, and scopes override the caller identity for the nested call. When omitted, call_action inherits identity from the outer run() (MCP/REST with JWT or scoped API key), so nested actions receive the same authorization as a direct tool call. On a factory-pinned instance, an explicit nested tenant_id must agree with the instance pin.
from node_wire_runtime import BaseConnector, nw_action
@nw_action("upload_then_describe")
async def upload_then_describe(
self, params: MyInput, *, trace_id: str
) -> GoogleDriveOperationOutput:
created = await self.call_action(
"files.create",
{"action": "files.create", "name": params.name, "mime_type": params.mime_type},
)
file_id = created.raw["id"]
return await self.call_action(
"files.get",
{"action": "files.get", "file_id": file_id},
)
Connector catalog¶
Every shipped connector, with the same fields for each. Request and response schemas are not
repeated here. They are generated from each connector's Pydantic models and served live at
/docs (Swagger UI) and /openapi.json, and the same models drive the MCP tool schemas. Tool names
are <connector_id>_<action> with dots replaced by underscores (google_drive_files_list).
Environment variables are also listed in Configuration.
Per-tenant secrets follow the tenant secret naming.
| Connector | Actions | auth.provider |
Setup guide |
|---|---|---|---|
http_generic |
request |
none |
— |
smtp |
send_email |
static_credentials |
— |
stripe |
charge, create_payment_intent, create_subscription, cancel_subscription, issue_refund |
static_token |
— |
salesforce |
create_lead, read_lead, update_lead, delete_lead, create_contact, read_contact, update_contact, delete_contact |
oauth2 (refresh_token) |
Salesforce |
google_drive |
files.list, files.get, files.create, files.upload, files.update, files.delete, permissions.create |
service_account (or upstream_bearer) |
Google Drive |
slack |
post_message, send_direct_message, upload_file |
static_token |
Slack |
fhir_epic |
read_patient, search_patients, search_encounter, create_document_reference, search_document_reference |
oauth2 (private_key_jwt) |
— |
fhir_cerner |
same actions as fhir_epic |
oauth2 (private_key_jwt) |
— |
http_generic¶
- Package:
node-wire-http,src/node_wire_http_generic/. No standalone MCP image. - Secrets / settings: none. The whole request (method, URL, params, body) is the payload.
- Egress: only
GET,POST,PUT,PATCHandDELETE. Internal destinations (localhost, loopback, private and link-local ranges, metadata endpoints) are rejected. SetNW_HTTP_GENERIC_ALLOWED_HOSTSto restrict hosts further. Logs drop query strings and fragments.
smtp¶
- Package:
node-wire-smtp,src/node_wire_smtp/. MCP imagenw-smtp. - Secrets / settings:
SMTP_HOST,SMTP_PORT,SMTP_USE_TLS,SMTP_USERNAME,SMTP_PASSWORD,FROM_EMAIL. - Egress: the payload carries only
to,subject,bodyand an optionalfrom_email. Relay settings are server-side only, so credentials never go to a caller-chosen host. SetNW_SMTP_ALLOWED_HOSTSin production.subjectmay not contain newlines or control characters.
stripe¶
- Package:
node-wire-stripe,src/node_wire_stripe/. MCP imagenw-stripe. - Secrets / settings:
STRIPE_API_KEY, sent raw inAuthorization(prefix: ""). - Errors:
src/node_wire_stripe/logic.pyis the workederror_mapexample (Build a connector).
salesforce¶
- Package:
node-wire-salesforce,src/node_wire_salesforce/. MCP imagenw-salesforce. - Secrets / settings:
SALESFORCE_INSTANCE_URL,SALESFORCE_TOKEN_URL,SALESFORCE_CLIENT_ID,SALESFORCE_CLIENT_SECRET,SALESFORCE_REFRESH_TOKEN. - Guide: Salesforce covers configuration and examples.
google_drive¶
- Package:
node-wire-google-drive,src/node_wire_google_drive/. MCP imagenw-google-drive. - Secrets / settings:
GOOGLE_DRIVE_SA_JSON(JSON contents, or an absolute path locally),GOOGLE_DRIVE_FOLDER_ID. - Per-user access:
upstream_bearerrelays each caller's own Google token (Google Drive — upstream bearer). - Guide: Google Drive covers service-account setup and the error codes.
slack¶
- Package:
node-wire-slack,src/node_wire_slack/. MCP imagenw-slack. - Secrets / settings:
SLACK_BOT_TOKEN. File uploads from disk must sit underNW_SLACK_ATTACHMENTS_DIR(default/slack_attachments). - Guide: Slack covers bot setup, channel resolution and the error codes.
fhir_epic and fhir_cerner¶
- Packages:
node-wire-fhir-epic(src/node_wire_fhir_epic/, MCP imagenw-smartonfhir-epic) andnode-wire-fhir-cerner(src/node_wire_fhir_cerner/, MCP imagenw-smartonfhir-cerner). Both are SMART on FHIR R4 backend services with Epic- and Cerner-specific schemas. - Secrets / settings: Epic:
EPIC_FHIR_BASE_URL,EPIC_TOKEN_URL,EPIC_CLIENT_ID,EPIC_KID,EPIC_PRIVATE_KEY. Cerner: the same with aCERNER_prefix, plusCERNER_SCOPES. - Privacy: logs record operation mode, HTTP status and counts, never request parameters or FHIR bodies (Privacy).
- Arguments: normalizers accept common LLM aliases (
patientId→resource_id; root-levelpatient/patientId→patient_idandsort→_sortonsearch_encounter). Encounter search requires a patient filter before any outbound call.
| Action | When to use | Example arguments |
|---|---|---|
read_patient |
You have a Patient ID | {"resource_id": "12724066"} |
search_patients |
No ID, or name-based search | {"given_name": "Nancy", "family_name": "Smart"} |
search_encounter |
Find medical visits | {"patient_id": "12724066"} |
Related documentation¶
| Doc | When to read it |
|---|---|
| Use a connector | Calling a connector in-process or over REST, per tenant |
| Build a connector | Writing a connector by hand |
| connector-bindings.md | How actions surface on REST / MCP / gRPC |
| configuration.md | NW_* environment variables |
| public-api.md | What is covered by SemVer |