Class McpHttpPolicy

java.lang.Object
com.mcpdbwizard.pub.McpHttpPolicy

public final class McpHttpPolicy extends Object
How a generated MCP server exposes its Streamable HTTP transport: which network interface it binds, and which browser origins it will answer.

Both are required by the MCP specification's transport security rules, and both are read from the environment at run time rather than baked into generated source — the same pattern the bearer token (MCP_HTTP_TOKEN) and the TLS keystore already use.

Binding

The spec says a server running locally SHOULD bind only to loopback rather than every interface. Jetty's default is every interface, so the default here is "127.0.0.1" and exposing the server beyond the machine is a deliberate act: set "MCP_HTTP_HOST". That matters because the other two protections are opt-in — a server generated with neither MCP_HTTP_TOKEN nor MCP_HTTPS answers anyone who can reach the port.

Origins

The spec requires that a server MUST validate the Origin header and answer 403 when it is present and invalid, to defeat DNS rebinding. Rebinding works by making the attacker's page look same-origin to the browser, so the same-origin policy never engages and the browser sends the request without a preflight; checking Origin server-side is what closes it.

An absent Origin is allowed: non-browser MCP clients do not send one, and the requirement is about rejecting a header that is present and wrong. A malformed one, or the literal null that browsers send from sandboxed and file: contexts, is rejected.

With "MCP_ALLOWED_ORIGINS" unset, loopback origins are allowed and nothing else — which pairs with the loopback default above. Setting it replaces that default rather than adding to it, so the variable always states the whole allowlist; include the loopback forms explicitly if a local browser client still needs them. The single value "*" disables the check, which forfeits the protection and should be a last resort. Copyright 2003-2026 ATB Consultancy Services Ltd (formerly Orinda Software Ltd, Dublin, Ireland)

  • Field Details

    • BIND_HOST_VARIABLE

      public static final String BIND_HOST_VARIABLE
      Environment variable naming the interface to bind, e.g. 0.0.0.0 for all of them.
      See Also:
    • ALLOWED_ORIGINS_VARIABLE

      public static final String ALLOWED_ORIGINS_VARIABLE
      Environment variable holding a comma-separated allowlist of origins.
      See Also:
    • DEFAULT_BIND_HOST

      public static final String DEFAULT_BIND_HOST
      Loopback, so a generated server is not on the network until someone says so.
      See Also:
    • ALLOW_ANY_ORIGIN

      public static final String ALLOW_ANY_ORIGIN
      The one "MCP_ALLOWED_ORIGINS" value that turns the origin check off.
      See Also:
    • ALLOW_UNAUTHENTICATED_VARIABLE

      public static final String ALLOW_UNAUTHENTICATED_VARIABLE
      Set this to serve an unauthenticated server off loopback anyway.
      See Also:
    • MAX_REQUEST_BYTES_VARIABLE

      public static final String MAX_REQUEST_BYTES_VARIABLE
      Largest request body accepted on the MCP endpoint. Unset means no cap.
      See Also:
  • Method Details

    • fromEnvironment

      public static McpHttpPolicy fromEnvironment()
      Build the policy from the process environment.
    • bindHost

      public static String bindHost()
      The interface the HTTP transport should bind, defaulting to loopback.
      Returns:
      a host suitable for InetSocketAddress / ServerConnector.setHost
    • isAnyOriginAllowed

      public boolean isAnyOriginAllowed()
      True when this policy has been told to answer every origin.
    • isLoopbackBindHost

      public static boolean isLoopbackBindHost(String theHost)
      Whether a bind address keeps the server on this machine.
    • exposureRefusalReason

      public static String exposureRefusalReason(String theBindHost, boolean theAuthenticationGenerated)
      Why this server must not start, or null if it may.

      Binding off loopback is the single act that puts a generated server on a network, and until now nothing tied it to the two features that protect one. Both of those are opt-in and both fail closed, so a config can quite reasonably ship without them — which is fine on loopback and not fine once the port is reachable. Publishing the container's MCP port needs MCP_HTTP_HOST, so the exposing step is exactly where the question belongs.

      Authentication, specifically — TLS does not count. TLS encrypts the wire and restricts nobody; a server with TLS and no token is an open server that is merely hard to eavesdrop on. The thing that decides who may call is the bearer token.

      Parameters:
      theBindHost - the interface about to be bound
      theAuthenticationGenerated - whether this server was generated with bearer-token auth
    • isUnauthenticatedExposureOverridden

      public static boolean isUnauthenticatedExposureOverridden()
      Whether the exposure guard has been deliberately disabled — worth saying out loud.
    • maxRequestBytes

      public static long maxRequestBytes()
      Largest request body to accept, or 0 for no cap.

      Opt-in rather than defaulted. A cap low enough to be useful against heap exhaustion is also low enough to reject a legitimate large argument — a base64 BLOB being written through a tool, say — and only the deployment knows which of its tools carry bulk. Defaulting this on would trade a rare failure for a routine one.

    • isOriginAllowed

      public boolean isOriginAllowed(String theOrigin)
      Whether a request carrying this Origin may proceed.
      Parameters:
      theOrigin - the raw header value; null or blank when the client sent none
      Returns:
      false only when the header is present and not allowed, which the caller answers 403