mcp-server-with-mcp-events

Durable personal messages, direct-storage file references and MCP Events wake delivery

Documentation

0.7.1 corrections; live acceptance pending. This source corrects subscription iteration, callback policy and file-lifecycle handling. The final corrected-source build passed 306/306 compiled tests; verification scopes this result separately from exact-archive and live-host acceptance. No package publication, workflow activation, database migration or live deployment is performed by these source changes. See implementation status and the deployment guide.

Durable personal messages and MCP Events for self-hosted n8n — 0.7.1

MCP Events notifications are wake hints. Authoritative work lives in durable PostgreSQL state and is completed only through an explicit acknowledgement after successful processing. The base public MCP tools are claim_pending_work and acknowledge_work. Optional Personal Messages adds send_message; optional Direct Storage Files adds prepare_file_upload, import_file, complete_file_upload and get_file_download. Both features default off, and files require messages. The native storage tool is never exposed in the public MCP catalogue.

Every database operation runs through the existing built-in n8n Postgres node. Message recovery uses an explicitly configured native Schedule Trigger workflow. No package-owned background scheduler, container, database driver, task-ID inference or protocol compatibility layer is added.

Upgrade and consent

Verify the installed application schema and select exactly one separately reviewed upgrade:

  • Canonical 0.6.1 → 0.7.1: sql/update-messages.sql adds message storage and replaces the affected functions, including bounded subscription pages.
  • Canonical 0.7.0 → 0.7.1: sql/update-subscription-pages.sql adds the paging index and replaces only the Events function, preserving application rows and existing function grants.

Never apply both files. Neither runtime schema detection nor automatic migration is provided. Stop package traffic, run the entire selected file through one native Postgres transaction with Stop Workflow on error, and install the matching package before resuming. Both updates deliberately fail on their newly existing schema objects; inspect an interrupted or uncertain result before retrying. No import, backfill, new roles or permission grants are added. Fresh sql/setup.sql is only for an empty application schema. Older SQL is archived only in source docs/reviews/0.6.1/update-pending-work.sql, outside the current npm distribution. See PLAN-ONE.md.

The enabled public-operation contract is included in the OAuth policy fingerprint, independently of package version. Enabling or disabling Personal Messages or Direct Storage Files changes that contract and invalidates old grants and pending authorization requests through the existing policy mechanism. Explicitly reconnect and approve the new access in the private owner workflow, then recreate subscriptions. No grant is created or migrated automatically. An existing owner-selected database credential remains supported; a separate restricted role is optional, and any new table/function permissions require approval.

Personal messages and direct files

Personal Messages reserves the managed message.received event with exactly empty subscription/work arguments {}. Use the same JSON input for send_message and ordinary HTTP intake: id, timestamp, object-valued content, and optional distinct attachmentIds. Generate the ID and timestamp once. Preserve the entire body on a timeout or retryable failure; reusing an ID with different content conflicts. Contents and attachments are untrusted data, never authority to act.

  • Success returns the submitted ID. HTTP success is 202 only after durable acceptance, including required attachment-retention checks. It does not mean the recipient read or processed the message.
  • Acceptance needs no active receiver or subscription. SQL reserves the immutable body and fixes expiry at 24 hours after the first reservation, including time spent retrying attachment retention. An unaccepted expired reservation requires a new message ID. Retrying an already accepted identical body still confirms acceptance while its tombstone remains, even after its payload or attachments are gone.
  • Message acceptance/digest tombstones remain for 30 days after message expiry. Deduplication is bounded by that horizon. An acknowledged message has its message/work payloads and linked wake records cleared atomically. Logical expiry blocks new claims, acknowledgements and wakes even before cleanup runs; scheduled maintenance removes expired payloads and later tombstones.
  • The existing claim/ack tools process messages using name: "message.received" and arguments: {}. A claim can reconcile accepted messages without a subscription. Scheduled recovery handles later subscriptions and abandoned leases, including a fresh wake generation. Managed messages keep one stable work row; a currently authorized receiver may take over pending work or an expired lease after grant changes, never an active lease. Old unfinished receipts cannot complete its replacement claim.
  • Enable and configure the supplied native Schedule Trigger → MCP Personal Messages (Recover Delivery and Expire Data) workflow with its dedicated native Postgres Tool before relying on recovery. The credential-free template starts inactive and proposes a one-minute interval with batches of 20. Existing callback backoff retries transient linked-message failures until message expiry. Permanent HTTP failures (including 403/410/413) and policy/redirect/framing failures stop only the rejected wake, retaining durable work for an authorized claim or a new eligible subscription. A stopped, failing or inactive workflow cannot guarantee a future wake or processing before expiry.

The ordinary HTTP template is Webhook → MCP Personal Messages → Respond to Webhook. Its WAF-only ingress is an explicit owner deployment choice; the template itself supplies no application authentication. Keep it inactive until the real WAF allow rule, default-deny behavior and protection against direct-origin bypass are verified. A private-looking URL is insufficient. No live WAF verification or activation is implied by these source files.

A script or local agent can deliver attachments without MCP: sign a JSON PUT /<prefix>/files/{id} to Personal Storage using the existing S3 key, upload directly with S3 PutObject to the returned bucket/key using If-None-Match: * and the exact declared Content-Type, then sign POST /<prefix>/files/{id}/complete. Only after completion succeeds, POST the message JSON with that durable ID in attachmentIds to the authorized native n8n intake URL. Preserve stable file/message IDs and immutable inputs on retries. This uses the existing storage API and WAF deployment boundary; no additional endpoint, signing framework or credential is introduced. The companion Personal Storage repository's docs/file-delivery.md documents those signed control routes; PLAN-ONE.md connects them to the message workflow.

For files, select the existing native S3 credential on each endpoint/message node that uses them, with the existing Personal Storage access key, HTTPS endpoint ending in /s3, explicit Force Path Style = true, and SSL/TLS verification enabled. The adapter uses official Smithy signing. It sends only bounded signed JSON metadata to the sibling /files control routes; it never receives upload/download file bytes. Metadata requests/responses are capped at 64 KiB, ordinary calls have a 30-second timeout and hosted-import control calls have a 150-second timeout. Errors returned by the adapter exclude credentials and private URLs.

Upload bytes directly to Personal Storage using the returned PUT URL and headers, then complete the upload before referencing its durable ID. Hosted import forwards the provided file reference so Personal Storage fetches the bytes itself. Download returns an ETag-bound GET URL and headers, without fetching the file through n8n. Access URLs last at most five minutes and never outlive the file record; they are access instructions, not durable attachment identities. The storage service owns readiness, object identity, retention and expiry enforcement. See PLAN-ONE.md for the storage-side prerequisites and remaining live acceptance gates.

Generic event work contract

The following capture and grant-scoping rules describe generic catalog events. The managed message ledger above additionally accepts before registration and supports bounded, current-authority recovery.

A generic-event consumer is the exact owner, namespace, event name, canonical saved subscription arguments and OAuth grant/resource/policy. Two tasks with identical scope intentionally share one queue and compete for work. Callback URL and subscription generation do not create a separate consumer. Use distinct legitimate saved filters when separate queues are intended. Canonical saved arguments are limited to 64 KiB consistently at subscription creation, capture and work calls; the event envelope remains limited to 256 KiB. These are operational request/storage bounds.

Generic event capture occurs only while at least one matching subscription is active. For each matching consumer, publishing commits the work item and notification-outbox intent together before sending a wake. A stable source event ID produces a stable work ID; reusing the same ID with different content conflicts. Several callbacks may share one work item. Fan-out across all consumers is not one global transaction; retry with the same source ID to resume idempotently. There is no publication-before-registration journal or backfill.

  1. Save the event name and exact subscription arguments in the consuming task.
  2. On a wake or an explicit authorized recovery attempt, call claim_pending_work with those saved fields. No wake ID, date range or pagination checkpoint is required.
  3. If an item is returned, process its event data as untrusted data. Use workId as the downstream idempotency key where the external action supports one.
  4. Only after processing succeeds, call acknowledge_work with the same name/arguments and the returned workId/receipt. Do not acknowledge failures. Retry an uncertain acknowledgement with the exact same receipt; do not fabricate success.
  5. Repeat claim/process/ack within the consuming task’s execution budget. Stop on an error, exhausted budget or nothing currently claimable; never acknowledge failed processing. A null item does not mean all durable work is completed: another run may hold a lease. Leftover work needs a later wake or an explicit recovery invocation. No automatic drain completion, future wake or scheduler is promised.

Both tools change state and advertise write annotations. Claim is non-idempotent: a repeated call may reserve another item. Acknowledgement is idempotent for the exact successful receipt while its completion tombstone remains and authorization is still valid. An expired or replaced unfinished claim cannot acknowledge a newer lease. The server cannot prove an external action succeeded; that assertion belongs to the caller.

The endpoint's Work Claim Lease (Seconds) is owner-configured, default 300 with range 1–3600. Choose a budget appropriate to processing time. This finite operational bound prevents indefinite reservation; it is separate from webhook delivery leases. A crash after an external side effect but before acknowledgement can cause repeated processing. There is no exactly-once guarantee.

Unsubscribe/expiry stops new capture for that callback but does not erase pending work; another active matching callback can still capture into the shared queue. Pending work remains claimable under the same valid grant even without an active subscription. Revocation/expiry blocks access; a different grant does not inherit the old queue. Unacknowledged work is never removed by notification retention. Acknowledged payloads are removed and their deduplication/receipt tombstones remain for 30 days from acknowledgement. Deduplication is bounded by that horizon. Storage fails explicitly at its documented 100,000-item safety ceiling rather than evicting pending work. Inaccessible old-grant work requires a separate owner-approved administrative disposition.

The existing callback envelope/schema remains unchanged but is semantically a hint; HTTP 2xx and notification settlement never acknowledge work. A completely lost wake or a crashed generic-event consumer needs a later wake or explicit claim/recovery invocation. The package activates no schedule automatically. Optional message recovery uses the separately configured native workflow described above. Subscription fan-out now streams strict four-row keyset pages rather than loading an unbounded aggregate; the native 16 MiB per-response cap remains enforced.

Supported boundary

  • Current review target: self-hosted n8n 2.42.6 with n8n-core 2.42.4, n8n-workflow 2.42.3 and built-in nodes 2.42.4. Isolated native loader/graph smoke is distinct from live-host/editor/Postgres acceptance, which remains outstanding. Earlier database tests used n8n 2.42.5 with n8n-core 2.42.3, n8n-workflow 2.42.2 and built-in nodes 2.42.3; those are historical evidence.
  • Node.js 24+; see docs/VERIFICATION.md for runtimes actually tested.
  • One regular n8n process. Queue and multi-main remain unsupported.
  • Public canonical HTTPS production webhook endpoint, outbound HTTPS callbacks, and a reachable PostgreSQL server configured through the native Postgres credential.
  • Native Postgres Tool, node typeVersion 2.7, connected to each custom node's Postgres Storage input. This uses n8n's tool invocation mechanism deterministically; no model is involved.
  • Trusted administrators who can edit nodes, database roles or credentials. Database writers can alter security-critical state; encrypted payloads do not prevent rollback, deletion or manipulation of unencrypted metadata.

This private self-hosted package retains n8n's Sustainable Use License. It is not an official or verified n8n Cloud node. The final 0.7.1 npm audit --omit=dev reported zero known vulnerabilities for 23 production dependencies. This excludes development dependencies and the installed host; it is not a security certification. The preceding 0.7.0 report remains historical evidence at repository docs/reviews/0.7.0/PRODUCTION-DEPENDENCY-AUDIT.json. The historical 0.6.0 full-tree audit in docs/DEPENDENCY-AUDIT.json reported two high-severity findings (axios and the pinned n8n-workflow dependency chain); those historical findings are not resolved merely by the current omit-development scan.

Callback and storage correction boundaries

Callback transport integrates the public native helpers.getSecureEgressFilter() policy with strict public-address validation and pinned DNS answers. It does not relax native deny policy or replace pinning with an unrestricted HTTP request; three actual-native-helper egress cases passed through an offline request seam, while final aggregate and live-instance verification remain deployment gates. Permanent HTTP and policy/redirect/framing failures terminate only the rejected wake, never acknowledge its durable work; 410/413 are explicit no-retry cases.

Personal Storage cleanup retires an expired managed record when its object revision changed, preserving the replacement object and allowing later expired files to be swept. Browser/S3 multipart paths reject managed destinations. No file bytes move through n8n, and cleanup does not adopt or delete a replacement object.

Shared table naming

All 13 application tables use the global_mcp_events_ prefix because they are shared across workflows. The schema remains mcp_events; the message acceptance ledger joins the existing OAuth, Events and work-inbox storage. Generic Events enqueue atomically writes work and notification intent; message acceptance and later work/wake reconciliation are separate durable operations. No automatic renaming or import of existing database objects is performed. See docs/DATABASE-SETUP.md for the table map and administrator handoff.

Set up fresh database storage

  1. Select a dedicated application database or isolated application schema, separate from n8n's internal tables. An administrator reviews the complete sql/setup.sql (generated from the five canonical sources: OAuth, Events, work inbox, messages and dispatcher).
  2. Using a regular built-in Postgres → Execute Query node with a setup credential, paste and execute the entire sql/setup.sql as one query invocation, with Transaction batching and Stop Workflow on error. Its schema, tables, functions and removal of PUBLIC privileges must commit together; never run the constituent files separately. Existing tables cause the transaction to fail rather than being silently adapted. Setup performs no automatic structural upgrade.
  3. A separate restricted runtime role is recommended for least privilege, not enforced or required by the package. It needs USAGE on mcp_events, SELECT/INSERT/UPDATE/DELETE on its application tables, and EXECUTE on its functions. An existing non-superuser object-owner account can also execute the operations when the owner explicitly chooses it; its broader create/alter/drop permissions increase the impact of misuse. The functions are SECURITY INVOKER. A database or role name alone does not establish whether n8n uses it internally. Avoid unnecessary privileges and access to unrelated tables.
  4. The administrator sets a bounded runtime-role statement_timeout, for example 10 seconds, and an appropriate lock timeout. The dispatcher additionally sets transaction-local lock_timeout to 5 seconds. Native Connection Timeout controls connecting, not SQL execution. No JavaScript race is used to pretend a running SQL operation was cancelled.
  5. Keep n8n and database clocks synchronized. Database expiry/lease decisions use fresh PostgreSQL time; application-created occurrence/expiration timestamps must remain coherent.

Never paste database passwords or private keys into SQL or workflow items. Set them privately in the native Postgres credential. Configure TLS/SSH according to the native node's supported behavior; this package does not reimplement or change those settings.

Configure the native Postgres Tool

Use one dedicated storage subnode for each custom node. Templates are provided under examples/; credential references are intentionally absent, so select your own credentials after import.

Native node settings:

  • Type: Postgres Tool, typeVersion 2.7
  • Resource: Database; Operation: Execute Query
  • Query Batching: Transaction
  • Retry On Fail: off; On Error: Stop Workflow
  • Keep empty-string replacement disabled
  • Select the runtime Postgres credential

Query must be exactly:

SELECT mcp_events.storage_operation($1::text, $2::text, $3::text, $4::jsonb) AS result

Query Parameters must be exactly this expression:

={{ [$fromAI('authority', '', 'string'), $fromAI('family', '', 'string'), $fromAI('operation', '', 'string'), $fromAI('payload', '', 'string')] }}

The expression declares native tool arguments; the custom node supplies them directly. It does not ask an LLM to generate SQL. The SQL is fixed; the operation is allowlisted. Values include scoped identifiers, keyed hashes, encrypted records and bounded metadata. Raw OAuth tokens, client secrets, encryption keys, browser handles and callback signing secrets do not enter these database arguments.

The package checks the connected node type, version, query, parameter expression and transaction setting through public graph APIs. Those APIs do not expose every execution flag or all tool consumers: you must keep retries off, stop on error, and avoid sharing/exporting the storage subnode. There is no unsupported private-API workaround. A lost response after a database commit is an uncertain outcome, not permission to retry blindly.

Each invocation completes one atomic operation. Separate invocations never share a transaction. Callbacks occur outside database transactions while durable fenced leases protect subscription/delivery work.

Create the Events credential

Create one MCP Events Server credential shared by endpoint, management, publisher and message workflows:

  • Server Key: stable alphanumeric namespace, with ., _ and - allowed after its first character; this isolates database authority state.
  • Owner ID: stable private owner identifier, independent of n8n accounts.
  • Issuer URL: the canonical production endpoint base, for example https://n8n.example.com/prefix/webhook/events-auth, without a trailing slash. Preserve your deployment's real prefix. Node Path is only events-auth.
  • Registered Client ID and Registered Client Secret: configure the same static OAuth client in ChatGPT. Generate a strong URL-safe secret of 32–256 characters privately.
  • Allowed Redirect URLs (JSON): exact HTTPS callbacks for your ChatGPT connection; no wildcards. This server advertises issuer identification and includes matching iss in authorization redirects. Confirm ChatGPT's actual selected callback.
  • Storage Encryption Key: canonical base64 for exactly 32 random bytes, generated privately. Back it up securely with the database. No key-rotation migration is supplied.
  • Event Catalog (JSON): configured generic event names plus object input/payload schemas. When Personal Messages is enabled, message.received is managed by the package; do not add a conflicting manual definition.
  • Enable Personal Messages / Enable Direct Storage Files: both default off. Files require messages; changing either feature changes the public-operation consent policy.
  • Allowed Browser Origins (JSON): normally [], which rejects browser-origin MCP requests while permitting server-to-server requests without Origin.

There is no database-path field. Select PostgreSQL connectivity on the native Postgres node, not in this credential. Use the same Events credential, Server Key, database and schema consistently across all configured custom nodes. A different policy for the same authority invalidates older grants; a different encryption key fails closed.

Synthetic catalog for testing:

[{"name":"staging.ping","description":"Synthetic test only","inputSchema":{"type":"object","properties":{"runId":{"type":"string"}},"required":["runId"],"additionalProperties":false},"payloadSchema":{"type":"object","properties":{"runId":{"type":"string"},"message":{"type":"string"}},"required":["runId","message"],"additionalProperties":false}}]

Workflows and private approval

Endpoint: MCP Server with MCP Events plus its native Postgres Storage subnode. Set Path to match the issuer suffix and publish. MCP URL is issuer plus /mcp. No downstream main execution is emitted on normal handled requests, and the native database tool is not included in the public MCP catalogue.

Owner approval: Manual Trigger → MCP Events Manage, plus its dedicated Postgres Storage subnode. Save it unpublished/inactive, set This workflow can be called by: No other workflows, and disable Available in MCP. Disable successful, failed, manual and progress execution saving. The graph guard permits only the expected main component and the explicitly configured native storage connection. n8n's manual runtime flag does not prove the saved publication state; keeping it unpublished is an operator requirement.

Prefer retaining an already verified private approval workflow and adding its dedicated native storage subnode. After importing a new template, verify the saved settings rather than trusting the imported JSON. Some n8n deployments hide the caller-policy control when Sharing is unavailable; use the supported workflow API with explicit authorization to set callerPolicy: none, then verify it. Do not relax the private guard.

Start OAuth in ChatGPT. Its browser page displays a pending request and fingerprint. Manually run List Pending Requests; compare the exact request ID, fingerprint, client, redirect, resource and scope. Select Approve Request and paste the exact ID/fingerprint as literal parameters. Approve only the connection you initiated. Return to the browser Continue link. Pending requests expire after five minutes; access tokens last one hour; the grant family has a thirty-day absolute limit. Refresh rotates tokens, and detected reuse retires the family.

Publish/drain: MCP Events Publish plus its dedicated storage subnode. Supply a stable event ID, timestamp with timezone, catalog name and JSON data. A publish enqueues durably and attempts delivery. Schedule Drain Pending Deliveries explicitly for retries/restart backlog. Delivery is at least once; consumers must be idempotent. Revocation prevents later attempts but cannot recall bytes already in flight.

Execution-data and logging boundary

Native tool invocation uses n8n's execution machinery and records controlled tool inputs/results in memory. They contain ciphertext/hashes/metadata, not raw token/key material. For webhook invocation, this package supplies an empty explicit parent input object rather than inheriting the HTTP body. Normal handled public routes return without launching a downstream main workflow; this is not a guarantee that host/proxy parsing or native errors can never be logged.

Known historical n8n 2.42.5 limitation: native tool output bookkeeping can emit a nonfatal metadata warning from this custom webhook context after returning the query result. The historical 0.6.0 25-scenario native test produced 466 such warnings, so log noise can be substantial. This is not a new 0.7.1 host test. There is no private host patch to suppress it. Full-host logging/persistence acceptance remains outstanding.

Native message HTTP/recovery workflows also carry plaintext message inputs and may carry short-lived hosted-file URLs in memory. Keep successful, failed, manual and progress execution saving disabled, and verify host/proxy retention separately.

The stock Postgres node can include database details or failed SQL in errors. Keep SQL fixed and secrets out of parameters; package errors are sanitized before returning to clients. Review host retention/logging and disable execution saving for the private administrative/publisher workflows. Do not export unfiltered logs or execution data as a diagnostic shortcut.

Temporary MCP Diagnostics remain default-off, /mcp-only, limited to ten minutes and 100 records per node/process. Enter a literal UTC expiry within ten minutes, save/publish, make one discovery attempt and disable again. Capture includes fixed method/protocol classifications, HTTP status, allowlisted numeric error code and fixed stage, never URLs, headers, bodies or error messages. Native logging level/filtering can suppress these messages.

docker logs --since 10m YOUR_N8N_CONTAINER 2>&1 | grep -F 'MCP Events diagnostic '

Review filtered lines locally before sharing because the host logger can add metadata. Updating the capture expiry does not reset the process count/window; restarting does not extend the explicit expiry.

Repository layout

In the source repository this package lives under packages/n8n-nodes-mcp-server-with-mcp-events/. Canonical database sources are in top-level database/, and credential-free workflow templates in workflows/. Build copies these into this package's generated sql/ and examples/ directories for npm distribution; edit only the canonical sources.

Build and verification

From this package directory inside the repository:

npm ci --ignore-scripts
npm test
npm pack

The final 0.7.1 npm test completed a fresh source build and passed 306/306 compiled tests; the sender suite passed 13/13. The earlier 0.7.0 baseline passed 286 tests. Focused, companion-storage and production-audit results are scoped in VERIFICATION.md. Exact-archive installation and dry-run packaging outcomes are recorded separately with artifact identities, not certified by the text inside the archive. Native n8n 2.42.6 loader/graph smoke does not establish live editor, workflow, callback or database acceptance. Do not carry baseline archive counts or hashes into this release.

See docs/VERIFICATION.md for current commands, evidence counts and limits. Historical release verification JSON files apply only to their recorded versions; no live-host acceptance follows from an npm archive or module-loading check. PostgreSQL storage alone does not establish live OAuth, discovery or event-triggered ChatGPT acceptance. Preserve the documented public-node boundary when extending this package; do not import native transport internals or duplicate database plumbing.

Temporary delivery and storage capture

On the publisher, message/recovery or private Manage node, enable Temporary Delivery Diagnostics and enter Diagnostics Expire At (UTC) as a literal UTC timestamp within the next ten minutes. The endpoint retains Temporary MCP Diagnostics, now including sanitized native storage and Events stages for /mcp requests only. OAuth HTTP routes remain outside this capture. All controls default off. Keep the private approval workflow private; diagnostics do not relax its guards.

Each node/process has a shared maximum of 100 records and ten minutes across request summaries and new stages. An explicit timestamp is always required. Updating it does not reset the process budget; a restart does not extend that timestamp. Once the cap or expiry is reached, later stages may be absent. Absence of a record is not evidence an operation did not occur. Turn the toggle off after the single approved attempt; publish an endpoint toggle change only when you intend to activate it.

Delivery records contain fixed stage labels, a random per-invocation correlation, process-local opaque delivery references, controlled operation names, byte counts, exact serialized-body SHA-256 and HTTP status categories. References are salted per capture and cannot link different captures. Payload contents, URLs, headers, tokens, keys, SQL arguments/results, response bodies and raw error strings are excluded. Body fingerprints remain potentially identifying metadata: share only the filtered capture needed for debugging.

envelope_prepared and envelope_signed summarize the existing serialized string before/after signature generation; they do not independently certify signature validity. envelope_sent at the transport seam means the same body string was passed to the Node request stream, not that a remote application processed it. Matching body hashes/lengths connect those observations without logging payloads.

storage_complete means the native response framing returned; a business {ok:false} result can still follow that stage. settlement.outcome names the requested database state, so outcome: delivered with state: error never proves committed delivery.

callback_result with category: received reports the callback HTTP status, including non-2xx responses. A 2xx response establishes HTTP acceptance only. A separate settlement record with state: returned, settled: true means the native database call returned the current SQL contract's successful fenced update result. A thrown call records state: error without claiming rollback or settlement, because the database response could have been lost after commit. Neither HTTP acceptance nor settlement proves ChatGPT exposed the event payload to a model.

Filter service logs locally, then inspect them before sharing (the host logger may add its own metadata):

docker logs --since 10m YOUR_N8N_CONTAINER 2>&1 | grep -E 'MCP Events (delivery )?diagnostic '

Installing this implementation requires the one applicable explicit SQL upgrade described above. Feature changes separately require policy reapproval. Native Postgres execution and standard webhook signing remain in their existing layers. Four-row subscription pages address the aggregate-result defect; historical webhook bookkeeping warnings remain unverified on the live target. No live debugging attempt is performed by installing this package.

Discussion