Repository navigation
Conversation
b65c25b to
72669c9
Compare
Why would we want to have this supported as a config? I also wonder how this might interact with the max destination limit, I would imagine this could create a lot of destinations. Have we considered this? |
I think allow the retry schedule to be configurable is helpful in general. Outpost OSS itself is less opinionated. We can avoid exposing that for cloud unless explicitly requested for simplicity sake?
Yes, this is the main thing I tried to highlight in our internal Notion page. There's a decision tree we need to decide regarding how we perceive |
72669c9 to
0b7d1a2
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seems odd to me that MCP events would have a different retry schedule unless it's spec-mandated, in which case the different schedule should probably be hardcoded |
0b7d1a2 to
72cd77a
Compare
Add an
mcpdestination type that delivers events to MCP Events (draft SEP-3415) webhook subscriptions. Experimental and off by default (EXPERIMENTAL_MCP_ENABLED=false); its config keys carry theEXPERIMENTAL_prefix (experimental.mcpin YAML).Stacked on #1173. Until it merges, the diff here includes its changes; this PR is the MCP commits on top of it.
In MCP Events, an agent subscribes through an MCP server to one event type and gives a callback URL; the server then POSTs signed events there until the subscription expires. Here each subscription is one Outpost destination of type
mcp, created and refreshed by the operator's MCP server with the Admin API key. Outpost covers the webhook mode only: no replay,cursoris alwaysnull.Changes
eventId,name,timestamp,data,cursor, at most 256 KiB) and signed with Standard Webhooks headers plusX-MCP-Subscription-Id.webhook-idismsg_plus 22 base64url characters (128 random bits), minted when the event is matched to the subscription and kept across automatic retries; it is never derived fromeventId, as the draft requires. A manual retry gets a new one. Timestamp and signature are computed again on every attempt. Rotatingcredentials.secretkeeps the old secret signing alongside the new one for 24 hours. Receivers should dedupe retries onwebhook-id; a receiver that dedupes byeventIdmust scope it to the subscription (X-MCP-Subscription-Id), because one event is delivered once per matching subscription.EXPERIMENTAL_MCP_VERIFICATION_TTL_SECONDS) and cleared when the tenant is deleted. Challenges are rate limited per tenant and principal, and failed challenges per tenant and callback host.httpsin normalized form, as the draft requires. Connections go through the address guard from feat: block private addresses for webhook destinations #1176 and no redirect is followed. For development only,EXPERIMENTAL_MCP_ALLOW_INSECURE_CALLBACKSalso acceptshttpwhen every address the host resolves to is inDESTINATIONS_PRIVATE_ADDRESS_ALLOWLIST.mcpretries onEXPERIMENTAL_MCP_RETRY_SCHEDULE(default 30 s, 2 min, 10 min) through the retry hook from feat: let destination types classify failures and set their own retry policy #1174. The delivery task carries the message ID, so retries reuse it.mcpdestinations are created and updated through the destinations API with the Admin API key only; feat: add MCP subscribe and unsubscribe endpoints #1183 later limits creating and updating them to the subscribe route. One topic, no*.config.url,config.principal,config.argumentsandtopicsare immutable.expires_atis accepted and returned formcpdestinations, using the expiry from feat: add destination expiry #1173. Each subscription counts towardMAX_DESTINATIONS_PER_TENANT; at the limit, creating one first removes the tenant's expired ones. Create checks for a duplicate ID and a full tenant before sending the challenge; the store still refuses both atomically (fix: make concurrent tenant and destination writes safe #1172). Themcptype is left out of destination-type metadata for tenant JWT callers, so the portal showsmcpdestinations as a generic entry.422onconfig.urlwith a category (connection_refused,timeout,tls_error,challenge_failed,https_required,address_not_allowed), never the endpoint's response. A verification rate limit returns429(rate_limited) withRetry-After. An unavailable verification store returns503(verification_unavailable). All three apply to create and to updates that re-verify.mcpis in the OpenAPI spec as an experimental destination type, so the SDKs model it; they pick it up in the usual SDK release PRs.EXPERIMENTAL_MCP_ENABLEDandEXPERIMENTAL_MCP_RETRY_SCHEDULEare listed in theManagedConfigschema.Deviations from the draft
The one list for this chain; #1182, #1183 and the later PRs point here.
EXPERIMENTAL_MCP_ALLOW_INSECURE_CALLBACKSis anhttpexception the draft doesn't have (it requireshttps). Development only, documented as outside the draft.dataisn't checked against apayloadSchema; the draft saysdataconforms to the schema. Event definitions come in feat: add MCP event definitions in the events/list format and a catalog endpoint #1182; validating at publish is a follow-up.EXPERIMENTAL_MCP_SUBSCRIPTION_TTL_MAX_SECONDS);terminatedandgapenvelopes; delivery status reporting.cursor,maxAgeMsandtruncatedare accepted and ignored.Behavior changes
For destination types other than
mcp:expires_atreturns422(expires_at is not supported for this destination type);nullis accepted.For
mcpdestinations:mcpalways blocks private addresses, whateverDESTINATIONS_BLOCK_PRIVATE_ADDRESSESis set to;DESTINATIONS_PRIVATE_ADDRESS_ALLOWLISTapplies to it too.EXPERIMENTAL_MCP_RETRY_SCHEDULEis separate fromRETRY_SCHEDULE,RETRY_INTERVAL_SECONDSandMAX_RETRY_LIMIT, which don't apply tomcp.410or413response, and an event over 256 KiB (attempt codepayload_too_large, nothing sent), is not retried and does not count toward auto-disable. Webhooks still retry and count them.EXPERIMENTAL_MCP_ENABLED=false,mcpis rejected like any unknown type. Existingmcpdestinations stay listed (credentials hidden), match no events and receive no deliveries or attempts; they lapse at their expiry as usual.POST /retryfor a storedmcpdestination answers 400destination_type_disabled, and a publish with thatdestination_idmatches nothing (202, emptydestination_ids), the same as for a disabled destination.Smaller: a destination type can opt out of the publisher cache (
PublisherCacheBypasser), sincemcppublishers hold no resources and one cache entry per subscription would evict costly ones.Notes
mcpdestinations should be returned by the destinations API at all, or stay internal to the subscribe endpoints. This affects the SDKDestinationunion and whether subscriptions count towardMAX_DESTINATIONS_PER_TENANT.webhook-idto equaleventId; with this PR they don't match.DESTINATIONS_PROXY_URL, when set, carriesmcpdeliveries and verification challenges (through the whole proxy chain, as for webhooks); connections to the proxy aren't address-checked, so the proxy must refuse private addresses. Unset, connections are direct and the guard checks each dialed address.HTTP_PROXY/HTTPS_PROXYare never used.Not in this PR
argumentsto filter mapping: feat: add MCP event definitions in the events/list format and a catalog endpoint #1182.webhook-idto theeventIdfor receivers written against an earlier draft. It would be a stated deviation; the default stays random.🤖 Generated with Claude Code