Write a connector package¶
A connector package adds a connector to MEHO from a repository of your own. You write the connector against the connector kit: one small Python module with the MEHO names a package may use. MEHO then runs the package's operations like the ones it ships itself.
This page is for people who write a connector. If you run MEHO and want to use connectors, start with Do real work.
Words used on this page
- Wheel: a built Python package file (
.whl). - Connector package: a wheel that adds one or more connector
folders under
meho_backplane/connectors/. - Connector kit: the module
meho_connector_kit, from the packagemeho-connector-kit. It holds the MEHO names a package may import. - Kit version: the version of
meho-connector-kit, for example 1.0. It is not the MEHO release number. - Range: the kit versions your package fits, for example
>=1.0,<2. - Overlay image: a new image built from the public MEHO image, with connector packages added.
1. What you build¶
You build a wheel with one folder under meho_backplane/connectors/, for
example meho_backplane/connectors/example_widget/. The wheel has no
other files. The folder holds:
- the connector class, which talks to the vendor's API;
- the operations: hand-written functions that MEHO runs when an agent or a person calls them.
MEHO runs your operations the same way as its own. Each call goes through MEHO's policy check, waits for a person's approval when it must, is written to the audit log, has secret values removed from its answer, and gets a result handle (a link to the stored result) when the answer is a long list. Your code does none of this.
There are two ways to bring a package into MEHO:
- Tier 1: your own image. You build an overlay image for your own install. evoila does not sign it.
- Tier 2: an image evoila signs. Only for evoila's own packages. A person reviews each package version before evoila signs the image.
2. Start from the template folder¶
The template folder is
templates/connector-package/
in the MEHO repository. It holds a small working sample connector, its
tests and its pyproject.toml.
- Copy the folder into a repository of your own.
- Rename the made-up names: the package name
example-connectors, the folderexample_widget, the productexampleand the implementation idexample-widget. - Replace the made-up widget API with the API of your product.
3. The connector kit¶
Your package imports MEHO only through one module:
import meho_connector_kit as kit
The kit version this page describes: 1.1.
The names below are the promise. Every other MEHO module is private. It can change in any MEHO release, without notice. A module whose path has a part that starts with an underscore is private by its name too.
| Group | Name | What a package uses it for |
|---|---|---|
| Register | register_connector_v2 |
Register the connector class under its product, version and implementation id. |
| Register | register_typed_op_registrar |
Queue the function that registers your operations when MEHO starts. |
| Register | register_typed_operation |
Register one operation: the shape of its inputs and its answer, its risk level, whether it waits for approval, and its texts for agents. |
| Register | register_connector_catalog |
Bring the catalog rows of your package: for a product and version, the vendor API file MEHO can load, the ingest profile, and the safety overlay for the operations made from that file. |
| Register | EmbeddingService |
The type of the embedding_service value that MEHO hands to your registering function. Pass it on unchanged. |
| Connector | Connector |
The base class of every connector. |
| Connector | HttpConnector |
The base class for an HTTP API. It brings MEHO's checks for TLS (secure, encrypted connections), addresses, redirects and answer size. |
| Connector | Target |
The target fields a connector may read. |
| Answers | FingerprintResult |
The answer of fingerprint: the detected product and version. |
| Answers | ProbeResult |
The answer of probe: can MEHO reach the target. |
| Answers | OperationResult |
The answer of execute. |
| Answers | AuthModel |
How a target logs in. |
| Errors | ConnectorUpstreamError |
The vendor answered with an error status. |
| Errors | ConnectorAuthError |
The vendor refused the credential. |
| Errors | CredentialsReadError |
MEHO could not read the target's secret. |
| Errors | VaultCredentialsReadError |
MEHO could not read the target's secret from Vault. |
| Errors | VaultClientError |
MEHO could not log in to Vault for the caller. |
| Caller | Operator |
The person or agent who called. Pass it on. |
| Caller | synthesise_system_operator |
MEHO's own caller, for checks that run without a person. |
| Caller | is_system_operator |
Check if the caller is MEHO's own caller. |
| Secrets | load_vault_secret_data |
Read the target's secret from Vault, as the caller. |
| Secrets | load_param_secret_data |
Read a secret that an operation input names, after MEHO checks that the caller may name it. |
| Secrets | strip_credential_value |
Clean a secret value, for example remove a new line at the end. |
| Secrets | is_acceptable_auth_model |
Check that the target uses the shared service account login. |
| Secrets | target_cache_key |
A cache key for one target that keeps tenants apart. |
| Approvals | PreviewContext |
What a preview gets: the target, the caller, the inputs and the connector. |
| Approvals | PreviewBuilder |
The type of a preview: it shows a person what a change will do, while the change waits for approval. |
| Approvals | register_preview_builder |
Register a preview for one operation. |
| Approvals | PermissionPreflight |
The type of a check that runs while a change waits: may the caller make it? |
| Approvals | register_permission_preflight |
Register such a check for one operation. |
| Audit | compute_audit_digest_hash |
A hash made with a secret key of MEHO, for a value that lands in an audit or approval row. |
Inside three classes, the kit also promises some parts:
HttpConnector:__init__,auth_headers,aclose,trace_response_body,_get_jsonand_post_json. The last two start with an underscore, but your subclass calls them, so they are part of the kit.PreviewContext:target,operator,params,connector_instanceandconnector_id.Target:name,host,extras,auth_model,verify_tlsandsecret_ref. Any other field of a target is private.
For every other class, the kit promises only the name.
4. Write the connector¶
The connector is a subclass of kit.HttpConnector (for an HTTP API) or
of kit.Connector. It sets product, version and impl_id, and has
fingerprint and probe. You do not write execute: MEHO runs the
operations you register, not execute.
- The target. Read only the six fields of
kit.Target. Put settings that only your connector needs inextras. - Secrets. Read the target's secret with
kit.load_vault_secret_data. It reads as the caller, so MEHO's rules for who may read what still hold. Never write a secret into an answer, an error text or a log line. - Errors. Raise
kit.ConnectorUpstreamErrorwhen the vendor answers with an error, andkit.ConnectorAuthErrorwhen it refuses the credential. A failed read must never look like an empty answer. - Caches. Key a cache you keep across calls, for example a login
token, with
kit.target_cache_key(target).
The steps, with code, are in connector-packages.md, "Writing the connector". More to read:
- secrets: connectors-shared-vault-creds.md;
- error texts: error-message-shape.md.
5. Add operations¶
Your folder's __init__.py calls kit.register_typed_op_registrar with
one function. MEHO runs that function when it starts. The function calls
kit.register_typed_operation once for each operation. Do not register
the operations when the folder is imported: the import must stay quick
and must not touch the database.
The inputs of register_typed_operation:
- Where it belongs:
product,version,impl_idandop_id, for exampleexample.widget.list. - What runs:
handler, an async function or an async method of your connector class. It gets the caller, the target and the inputs, and returns plain data (a dict). - Shapes:
parameter_schemaandresponse_schema, as JSON Schema. - Risk level:
safety_levelissafe,caution,dangerousordestructive. Every operation that is notsafemust setrequires_approval=True: MEHO then waits until a person approves the call. - Texts for agents:
summary,description,group_key,when_to_useandllm_instructions. An agent picks your operation from these texts, so write them with care. embedding_service: pass on the value MEHO hands to your function.
A preview for a change. Register a preview with
kit.register_preview_builder. It shows the person who approves what the
change will do. Every destructive operation needs one.
What MEHO does for you, so you do not:
- the policy check and the approval wait (approvals.md);
- the audit row;
- removing secret values from the answer;
- result handles for long lists (jsonflux.md).
Catalog rows. MEHO can also make operations from a vendor's API file
(an OpenAPI file). The catalog says, per product and version, which file
to load, how to log in (the ingest profile), and which of the operations
made from the file need a higher risk level or an approval (the safety
overlay). Your package can bring such rows. Put a catalog.yaml with the
same shape as MEHO's own
catalog.yaml
in your connector folder, next to the API file and the profile it names,
and call kit.register_connector_catalog(__name__) from the folder's
__init__.py. MEHO reads the rows when it starts and checks them like its
own. A row for a product and version that MEHO already has stops the
start. The details are in
connector-packages.md, "Bring catalog rows".
6. Say which MEHO releases fit¶
Your package names the kit versions it fits, in its pyproject.toml:
[project]
name = "example-connectors"
version = "0.1.0"
dependencies = ["meho-connector-kit>=1.0,<2"]
>=1.0,<2 means kit version 1.0 or later, but before 2. Use the kit
version of the oldest MEHO release you test against as the lower end.
The kit version of a MEHO release is in
backend/connector-kit/pyproject.toml at the release tag. You can also
read it from the image:
docker run --rm --entrypoint python ghcr.io/evoila/meho:v<x.y.z> \
-c "from importlib.metadata import version; print(version('meho-connector-kit'))"
When the range does not fit, the overlay build stops. It installs the wheels without any package index, against the kit of the base image, so a package that does not fit the kit cannot be installed.
The kit is never published on a public package index. It ships only with MEHO's own image and release files.
7. Test¶
Test against the MEHO release you build for:
- Check out MEHO at the tag of that release.
- Copy your connector folder into
backend/src/meho_backplane/connectors/of the checkout, and your tests next to it. - In
backend/, runuv sync, then your tests and MEHO's operation contract check:
uv run pytest tests/test_operation_contracts.py
The contract check reads every operation MEHO registers at start and checks its texts, its schemas, its risk level and its approval setting. It calls no vendor system.
Your tests may use MEHO's own test setup inside the checkout. That setup is not part of the kit: it can change in any release.
8. Build an image¶
Tier 1. Build an overlay image from the public MEHO image, pinned by
its digest, with the script deploy/connector-overlay/build.sh. The
build checks every wheel, installs it, and refuses a wheel that changes a
file of the public image. The steps are in
connector-packages.md, "Tier 1: your own image".
Tier 2. A person reviews each package version with a checklist and writes a review record. Only then does evoila build and sign the image. The steps are in connector-packages.md, "Tier 2: an image evoila signs".
9. When the kit changes¶
The kit version has two numbers:
- the first number goes up when a name is removed or its inputs change. Your package may break. Read the notes and change your code;
- the second number goes up when names are only added, or a name is marked as old. Your package keeps working.
When the kit changed, the MEHO release notes
(CHANGELOG) have
a section Connector kit. It names the new kit version. For a change in
the first number, each line says the smallest change a package must make.
A name is never removed at once. First it is marked as old, on this page and in the release notes, with what to use instead. It may go in a later kit version.
The full rules are in connector-packages.md, "The connector kit".