client

package
v0.30.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package client provides MCP protocol client implementation for communicating with backend servers.

This package implements the BackendClient interface defined in the vmcp package, using the mark3labs/mcp-go SDK for protocol communication.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewHTTPBackendClient

func NewHTTPBackendClient(registry vmcpauth.OutgoingAuthRegistry, opts ...Option) (vmcp.BackendClient, error)

NewHTTPBackendClient creates a new HTTP-based backend client. This client supports streamable-HTTP and SSE transports.

The registry parameter manages authentication strategies for outgoing requests to backend MCP servers. It must not be nil. To disable authentication, use a registry configured with the "unauthenticated" strategy.

Options are additive: nil or absent options reproduce the default behavior exactly. See WithDialControl to install a per-connection dial hook for SSRF / DNS-rebinding defense.

Returns an error if registry is nil.

Types

type Option added in v0.30.1

type Option func(*httpBackendClient)

Option configures an httpBackendClient.

func WithDialControl added in v0.30.1

func WithDialControl(control func(network, address string, c syscall.RawConn) error) Option

WithDialControl installs a per-connection Control hook on the dialer used to reach every backend. The hook fires after DNS resolution and before the TCP handshake, receiving the resolved peer IP in address — which is why it defeats DNS-rebinding attacks that a host-name–based allow/deny check cannot: a hostname can legitimately resolve to a blocked IP after the name-based check passes.

The hook composes with per-backend CA-bundle handling: it augments the internally built *http.Transport rather than replacing it. Supplying it implies the standard dial timeouts (30 s Timeout, 30 s KeepAlive) used throughout this package.

The signature matches net.Dialer.Control exactly.

Security limitations embedders must understand:

  • Per-TCP-dial, not per-request: the hook fires once per TCP connection. A pooled connection is reused without re-invoking the hook until it is recycled. Because each backend gets its own isolated transport and connection pool, a reused connection is always one this hook already approved on its first dial — reuse cannot reach an unclassified peer. This client does not offer per-request re-classification.

  • Proxy transparency: when http.ProxyFromEnvironment selects a proxy (HTTP_PROXY/HTTPS_PROXY set), the dial target is the proxy server, so the hook receives the proxy's IP, not the backend's. Embedders relying on this hook for SSRF or IP allow-listing must either unset the proxy env vars or additionally validate the request URL's host before dialing.

  • Both IP families: the address argument may be an IPv4 or IPv6 literal (host:port form); embedders must handle both families — including IPv4-mapped IPv6 such as ::ffff:127.0.0.1 — in their check. See the OWASP SSRF Prevention Cheat Sheet for the full set of ranges to deny (loopback, RFC 1918, link-local 169.254/16, CGNAT 100.64/10, IPv6 ULA).

Example

ExampleWithDialControl shows how an embedder installs a dial-control hook that rejects connections to non-public IP ranges — the building block of an SSRF / DNS-rebinding defense. The hook receives the resolved peer IP, so it classifies the address the kernel is about to connect to rather than the (re-resolvable) hostname. A production check should additionally cover the ranges listed in the WithDialControl doc comment (CGNAT, IPv6 ULA, etc.).

denyNonPublic := func(_, address string, _ syscall.RawConn) error {
	host, _, err := net.SplitHostPort(address)
	if err != nil {
		return err
	}
	ip := net.ParseIP(host)
	if ip == nil {
		return fmt.Errorf("unresolved dial address %q", address)
	}
	if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
		return fmt.Errorf("blocked connection to non-public IP %s", ip)
	}
	return nil
}

registry := auth.NewDefaultOutgoingAuthRegistry()
if err := registry.RegisterStrategy(
	authtypes.StrategyTypeUnauthenticated, &strategies.UnauthenticatedStrategy{},
); err != nil {
	panic(err)
}

backendClient, err := NewHTTPBackendClient(registry, WithDialControl(denyNonPublic))
if err != nil {
	panic(err)
}
_ = backendClient

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL