mcpmem-webhook 1.0.0

Durable webhook delivery worker for the mcpmem MCP server.
Documentation

mcpmem-webhook

The durable webhook delivery worker of mcpmem, an MCP server that gives LLM agents persistent memory.

This crate is a library. It delivers graph change events to an HTTPS endpoint, at least once, from a SQLite outbox.

cargo install mcpmem --features webhooks

Read this before you plan a deployment. The shipped mcpmem binary starts the webhooks role with no worker, and the role then fails with webhook role selected without configured worker ports. The failure is deliberate and visible, instead of a silent no-op. Delivery works for a program that embeds this crate and constructs WebhookWorker::new with a connector, a secret provider, a hostname allowlist and a resolver. No MCP tool manages a subscription in 1.0.0; a user writes the webhook_subscription table, or calls SubscriptionRepository.

Subscriptions

One row of webhook_subscription is one subscription:

Column Meaning
endpoint The HTTPS URL, 2048 characters maximum
event_operations A JSON array of create, update, delete, rename. An empty array matches every kind
entity_types A JSON array of entity types. An empty array matches every type
consumer_origin The origin name of this consumer, 256 characters maximum
ignored_origins A JSON array of origins to drop
secret_ref The lookup name of the signing key, 512 characters maximum
enabled 0 or 1

A subscription never receives its own writes: the filter drops an event whose origin equals consumer_origin, or is in ignored_origins. This stops a delivery loop between two instances.

Delivery

A mutation writes one event_outbox row per matching enabled subscription, in the same transaction as the graph write, so a crash loses no delivery.

One delivery per subscription is in flight, in one process only. claim_due takes a 30-second lease with a token and an epoch, and a write-back succeeds only while the token, the epoch and the deadline still match. A slow worker whose lease expired cannot overwrite a newer attempt.

The worker retries a 408, a 429, and a status of 500 or more. Any other non-2xx status, a policy error and a secret error kill the delivery at once, because a repeat cannot succeed. The delay is the Retry-After header, clamped from one second to one hour, and one second otherwise. After eight attempts the row moves to dead and keeps last_error. A dead row is never claimed again, and the server raises no alert.

The request

Header Value
X-Memory-Signature The lowercase hexadecimal HMAC-SHA256 of <timestamp_us>.<body>
X-Memory-Timestamp The signed timestamp, in microseconds
Idempotency-Key The event id. Duplicate suppression is the receiver's job

A receiver must recompute the MAC over both parts to reject a replayed body.

The body is a version 2 JSON envelope, bounded to 64 KiB. It carries eventId, transactionId, entityId, entityRevision, operation, occurredAtUs, origin, correlationId, causationId, hopCount, and, for a rename, oldName and newName. It carries no observation content: the receiver reads the graph for the entity state.

The endpoint policy

The URL must use https on port 443, with a hostname from the allowlist, no user name, no password and no fragment. The worker resolves the hostname, refuses a private, loopback, link-local, multicast or unspecified address, pins the connection to the resolved address, and disables redirects.

Secrets

secret_ref is a lookup name, not key material. The embedder supplies a SecretProvider; the crate ships StaticSecretProvider, a map from name to key. An unknown name dead-letters the delivery with secret reference is not configured.

Documentation

The full server documentation is in the workspace README. The release notes are in CHANGES.md.

License

Apache-2.0. See LICENSE and NOTICE, which records the derivation from corporatepiyush/mcp-memory 5.2.1.