Skip to main content

Distributed MCP Gateway

Use this guide to build independent MCP providers, automatic Registry discovery, compatible replicas, and one secured Streamable HTTP or stdio gateway.
API details: @hile/mcp. Protocol baseline: MCP 2026-07-28.

Complete Example

This recipe composes independent Hile microservices into one MCP server. Each provider owns its capability files and deployment lifecycle. The gateway discovers compatible replicas, applies public naming and authorization, and exposes the cached catalog over the official Streamable HTTP or stdio adapter.
The gateway is a projection, not the owner of provider business logic. Providers remain deployable and scalable without changing the public MCP endpoint. External MCP uses Streamable HTTP or stdio; the Hile WebSocket connection is an internal Micro transport detail, not an MCP WebSocket transport.

1. Define capabilities under mcps

Use defineMcpResource() for static or RFC 6570 template resources and defineMcpPrompt() for prompts. The package API page contains complete examples for all three capability kinds. Prompt arguments and RFC 6570 template variables may declare completions. Tools and static resources cannot. Official metadata fields such as title, description, icons, _meta, tool/resource annotations, resource size, and cacheHint are preserved across discovery.

2. Attach each provider after Micro listen

Run another process with the same provider ID and capability files to add a replica. Its instance ID and publisher address are unique, while its deterministic fingerprint is identical.

3. Create the unified gateway

Use startup: 'allow-empty' when the gateway may start before providers. Use require-provider when an empty initial catalog is a deployment error.

4. Mount Streamable HTTP on the existing server

The adapter does not create or own a port. Host and Origin validation protect the HTTP boundary, while authenticate supplies the external MCP identity and scopes. The invocation credential independently authenticates the gateway to each provider. responseMode passes through to the official SDK, keepAliveMs controls stream keepalives, and maxSubscriptions bounds HTTP subscription listeners. For a public endpoint, choose { authentication: { mode: 'public' } } explicitly and expose only capabilities designed for anonymous use. An omitted authentication policy is rejected. For OAuth, select mode: 'oauth' with an official OAuthTokenVerifier, required scopes, and metadata containing the public MCP resource URL plus Authorization Server metadata. The middleware serves /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server, and emits an RFC-compliant Bearer challenge. Authorization Server behavior is intentionally outside this package.

5. Inspect discovery and multi-instance state

Expose this data through an authenticated operational endpoint or metrics, not through MCP capability handlers. A provider in conflict is omitted from the public catalog until all live replicas converge on one fingerprint.

6. Use stdio for a process-local MCP host

authInfo is process-level policy. Without it, scoped capabilities are hidden. Do not write application logs to stdout in a stdio server because stdout belongs to the MCP transport.

Capability Behavior

All capability handlers receive signal, verified principal, optional inputResponses, untrusted-or-verified requestState, and awaited emit.progress() / emit.log() methods. This gateway projects server-owned tools, resources, and prompts. Roots and sampling belong to the connected client/server session rather than a discovered provider definition, and durable Tasks belong in an application job system. No MCP-over-WebSocket or custom pagination protocol is added. Completion callbacks receive the partial value, cancellation signal, verified principal, and already supplied string arguments. When mutable resource data changes, call attachment.notifyResourceUpdated(resourceName, variables?); connected clients subscribed to the expanded URI receive notifications/resources/updated. Sessionful stdio connections track subscriptions per connection. Streamable HTTP uses the official endpoint notifier and subscription event bus; stateless HTTP requests cannot retain a subscription across requests.

Discovery And Routing

  1. Each attachment publishes one instance-scoped retained manifest.
  2. The source retrieves matching manifests and Registry-observed publisher addresses in one snapshot call.
  3. The gateway validates schemas and metadata, groups instances by provider ID, and rejects fingerprint or public-name conflicts.
  4. Compatible instances are selected round-robin from the cached catalog.
  5. Invocation carries the expected provider ID, instance ID, fingerprint, capability, input, and credential.
  6. The source streams to the selected publisher address. The provider verifies identity and credential before validation, authorization, and handler execution.
  7. Progress and log frames are forwarded; one terminal result completes the MCP call.
Catalog updates re-project connected MCP servers and emit list-change notifications. Resource update events are instance/fingerprint validated for every compatible replica, deduplicated, and delivered only to matching subscriptions. Discovery polling never runs in the invocation hot path.

Security Boundaries

  • HTTP authentication and Host/Origin allowlists protect the public transport.
  • Gateway catalog visibility checks required scopes before registration and can apply isCapabilityExposed policy.
  • Provider authorization repeats scope checks and optional capability-local authorize() after schema validation.
  • Credential mode proves that the invocation was minted for the exact provider instance, fingerprint, capability, and input. Nonces are replay-protected and expire.
  • Prefer one Ed25519 Gateway authority for all discovered Providers. Keep its private key only in the Gateway and distribute the matching public key to Providers; the public key is not a credential and Providers cannot mint invocations. Rotate without downtime by publishing overlapping Provider publicKeys, switching the Gateway private key, then removing the retired public key.
  • HMAC remains available for compatibility. Configure a separate HMAC secret for each provider or trust domain because a symmetric-key holder can mint credentials within that domain.
  • Required scopes travel in each discovered capability manifest. Grant MCP_SCOPE_ALL (*) to an external principal only as an explicit administrator policy that intentionally includes capabilities registered in the future.
  • trusted-internal sends an unsigned principal. Use it only on a Micro mesh where every reachable peer is trusted to impersonate the gateway.
  • requestState is client-echoed state. Treat it as unknown unless the gateway is configured with the official SDK request-state verifier.

Performance And Failure Semantics

  • Registry discovery is one batched snapshot per poll, not one query per provider or request.
  • Catalog parsing and public projection happen only when the snapshot changes.
  • One shared resource-update publication is reused per provider Application; consumers do not create one subscription per provider instance.
  • Cold concurrent calls to one address share connection establishment; established Micro clients are reused.
  • The execution channel is bounded. emit.progress() and emit.log() await capacity so a slow MCP client cannot create an unbounded provider buffer.
  • Abort signals and the tool timeout cover connect, validation, authorization, handler execution, notification delivery, and stream consumption.
  • Only read-only and idempotent tools may request one alternate-instance failover attempt. Mid-stream failure before a terminal result is eligible; cancellation and completed results are not.
  • Closing the gateway rejects new work, aborts active streams, waits for them to settle, then closes discovery.

User Intent

Use this recipe when the user asks for MCP capability decoupling, unified MCP output, automatic discovery, multiple providers, or multiple replicas in a Hile microservice architecture.

Packages To Use

  • @hile/mcp
  • @hile/micro
  • @hile/http when exposing Streamable HTTP on a Hile server
  • @hile/core for lifecycle composition
  • A Standard Schema implementation such as Zod
  • @hile/redis-idempotency inside side-effecting tools when retries must not duplicate business effects

Implementation Steps

  1. Define each capability as a default export under the owning service’s mcps directory.
  2. Start the provider Application, then attach the directory with explicit invocation security.
  3. Give compatible replicas the same provider ID and immutable definitions.
  4. Start a gateway-side Application, provider source, keyring, and gateway.
  5. Mount Streamable HTTP on the existing public server or serve stdio for a local process integration.
  6. Configure external authentication, scopes, aliases, startup policy, diagnostics, and lifecycle cleanup.
  7. Verify discovery, capability behavior, replica routing, conflict handling, streaming, cancellation, security, and dynamic list changes.

Failure And Cleanup Behavior

  • Invalid local definitions fail before publication; a partial loader registration is rolled back.
  • Invalid or spoofed network manifests are omitted from discovery.
  • Mixed fingerprints, duplicate public names, and duplicate resource identities fail closed at catalog construction.
  • The selected provider verifies the expected instance and fingerprint, so a process replacement at the same address cannot execute against a stale catalog.
  • Provider close withdraws the retained manifest before unregistering the dispatcher and unloading definitions. If withdrawal fails, the callable state remains available for a retry.
  • Gateway close is an active invocation barrier. HTTP and stdio handles still have their own lifecycle and should be closed first.

Verification Checklist

  • The official MCP client can list and invoke tools, read both resource forms, and get prompts.
  • The official MCP client can complete prompt arguments and resource-template variables.
  • A subscribed resource receives an updated notification after the provider calls notifyResourceUpdated().
  • OAuth mode serves discovery metadata and rejects missing, expired, or under-scoped Bearer tokens with official SDK responses.
  • Structured tool output satisfies outputSchema.
  • Progress, logging, and an InputRequiredResult multi-round-trip flow reach the client.
  • An unauthenticated or under-scoped client cannot discover or call protected capabilities.
  • A direct Micro caller cannot forge a privileged principal in credential mode.
  • Two matching replicas route successfully and appear as one public provider.
  • A mismatched replica changes the provider to conflict without affecting unrelated providers.
  • Adding, removing, or changing a provider updates a long-lived client’s catalog notification.
  • Cancelling a call stops connection or handler work and does not trigger failover.
  • Shutdown leaves no provider publication, operation handler, active stream, connection waiter, interval, or owned HTTP listener.