Documentation
¶
Overview ¶
Package mcp adapts tools served by external Model Context Protocol (MCP) servers into the harness's tool.Tool interface, so the agent loop can call remote MCP tools exactly as it calls built-in ones.
Transport ¶
This adapter speaks ONLY the MCP Streamable HTTP client transport (HTTP POST plus SSE), per the project's hard constraint. The stdio transport is deliberately never imported, constructed, or exposed; no MCP server process is ever spawned. If the underlying SDK ships a stdio/command transport, this package intentionally does not use it.
SDK ¶
It wraps github.com/modelcontextprotocol/go-sdk/mcp (the official Go SDK), using mcp.StreamableClientTransport for the transport and mcp.Client / mcp.ClientSession for the handshake, tool listing, and tool invocation.
Trust ¶
Remote MCP servers are an untrusted supply-chain surface (see docs/harnesses/08). Tool names are namespaced as mcp__<server>__<tool> so a remote server can never shadow a built-in tool, and the conservative ReadOnly default keeps remote tools serialized unless they explicitly advertise a read-only hint.
Index ¶
- Variables
- func AllowOAuthLoopbackForTest(t interface{ ... }, opts *OAuthOptions)
- func HasCredentialHeaders(headers map[string]string) bool
- func OAuthCredentialRecordKey(resource string, opts OAuthOptions) ([]byte, error)
- func Register(cat *tool.Catalog, tools []tool.Tool) (skipped []string, err error)
- func RegisterCallWithQuery(cat *tool.Catalog, m *Manager) (bool, error)
- func RegisterResourceTools(cat *tool.Catalog, p Provider) (bool, error)
- func ValidateClientURL(raw string) error
- type CallResult
- type Manager
- func (m *Manager) CallTool(ctx context.Context, server, toolName string, args json.RawMessage) (CallResult, error)
- func (m *Manager) Close() error
- func (m *Manager) GetPrompt(ctx context.Context, server, name string, args map[string]string) (PromptResult, error)
- func (m *Manager) ListPrompts(_ context.Context, server string) ([]Prompt, error)
- func (m *Manager) ListResources(_ context.Context, server string) ([]Resource, error)
- func (m *Manager) ReadResource(ctx context.Context, server, uri string) (ResourceContents, error)
- func (m *Manager) Servers() []*Server
- func (m *Manager) Tools() []tool.Tool
- type OAuthClientConfig
- type OAuthController
- type OAuthError
- type OAuthNetworkPolicy
- type OAuthOptions
- type OAuthPresenter
- type OAuthPresenterFunc
- type OAuthSubject
- type Prompt
- type PromptArgument
- type PromptExpander
- type PromptMessage
- type PromptResult
- type Provider
- type Resource
- type ResourceContents
- type Server
- type ServerConfig
Constants ¶
This section is empty.
Variables ¶
var ( // ErrOAuthLoginRequired reports that no usable durable credential remains. ErrOAuthLoginRequired = errors.New("mcp oauth: login required") ErrOAuthUnavailable = errors.New("mcp oauth: unavailable") )
var ErrUnknownServer = errors.New("mcp: unknown server")
ErrUnknownServer is returned (wrapped) by the Provider routing methods when a caller names a server that is not connected. It is a CLIENT error (the name is wrong), distinct from a transport/protocol fault on a known server — callers at the network surface map it to InvalidArgument, while other faults map to Internal/Unavailable.
Functions ¶
func AllowOAuthLoopbackForTest ¶
func AllowOAuthLoopbackForTest(t interface{ Helper() }, opts *OAuthOptions)
AllowOAuthLoopbackForTest enables loopback only for in-process test servers. It is deliberately absent from OAuthNetworkPolicy and every production config projection, so operator input can never relax the loopback denial.
func HasCredentialHeaders ¶
HasCredentialHeaders reports whether headers contain a credential-bearing header that cannot be combined with OAuth. Matching is case-insensitive.
func OAuthCredentialRecordKey ¶
func OAuthCredentialRecordKey(resource string, opts OAuthOptions) ([]byte, error)
OAuthCredentialRecordKey derives the opaque persistence key used by the OAuth controller for resource and options. Hosts constructing a single-record Reader must use this helper rather than duplicating the identity framing protocol.
func Register ¶
Register adds each wrapped tool to the catalog. It uses Catalog.Register (not MustRegister) so a name collision surfaces as an error rather than a panic; a collision should be impossible given the mcp__ namespacing, but two servers configured with the same Name (or a server advertising duplicate tools) would trip it.
SKIP-AND-CONTINUE on collision: a duplicate-tool error is NON-fatal — the already-registered tool wins (FIRST-wins, so a caller registering the authoritative set first — e.g. the server-global MCP tools before client tools — keeps it), the colliding tool is skipped, and Register KEEPS registering the rest. This is what makes "global wins, the other non-colliding client tools are preserved" actually true (a return-on-first-dup would silently drop every tool ordered after the collider). Non-duplicate registration errors are also accumulated rather than aborting.
It returns the NAMES of the skipped/failed tools (in encounter order) alongside the joined error, so a caller can emit ONE provenance-bearing WARN listing exactly which tools were dropped (e.g. "client MCP: shadowed by a server-global tool: <names>") rather than a generic line. The skipped slice is non-nil iff the error is non-nil; the error still satisfies errors.Is(_, tool.ErrDuplicateTool) when any collision occurred. Both are nil/empty when every tool registered cleanly.
func RegisterCallWithQuery ¶
RegisterCallWithQuery registers the CallMcpWithQuery meta-tool into cat, gated on the manager exposing at least one tool (the escape hatch is meaningless without tools to call). It returns whether the tool was registered and the first registration error (if any). It takes the *Manager (not just Provider) because the gate reads the tool list — Provider has CallTool but no Tools(), while *Manager exposes both (it is the production Provider implementation AND the tool-list owner). This mirrors RegisterResourceTools's "register only when non-empty" gating, but over TOOLS rather than resources: the meta-tool is about calling a remote tool, so it registers whenever any MCP tools are present, independent of the resource meta-tools' MCPResourceTools gate.
func RegisterResourceTools ¶
RegisterResourceTools registers the ListMcpResources and ReadMcpResource meta-tools into cat, built over the given Provider — but ONLY when at least one connected server exposes at least one resource. This mirrors the skills "register only when non-empty" gating: there is no value in advertising resource tools when no resources exist. It returns whether the tools were registered and the first registration error (if any).
func ValidateClientURL ¶
ValidateClientURL validates a CLIENT-PROVIDED Streamable HTTP MCP endpoint before it is mounted per-session (e.g. an editor's session/new mcpServers entry). It is a deliberate SSRF backstop applied ONLY to the untrusted client path — the operator-configured Connect/NewManager path is intentionally NOT gated this way, since an operator may legitimately point a server at an internal host.
The contract: the URL must be absolute and carry a host, and the scheme must be "https" — OR "http" only when the host is an explicit loopback address ("127.0.0.1", "::1", "localhost"). Everything else (file/ftp/gopher/etc., a relative URL, a hostless URL, or plaintext http to a non-loopback host) is rejected. Note this is a SCHEME/host-shape allowlist, not metadata-IP filtering: the editor is a local-trusted process, so we filter the obviously dangerous shapes rather than resolving and blocking link-local/metadata IPs.
Types ¶
type CallResult ¶
type CallResult struct {
// Server is the configured name of the server the call was routed to.
Server string
// Tool is the remote tool name the call was made against (the server-side
// name, NOT the mcp__<server>__<tool> namespaced name the model sees).
Tool string
// Content is the per-block content (text/blob + MIME + URI), extracted from
// the remote CallToolResult.Content. The model-facing flattened string half
// is NOT here — CallMcpWithQuery builds its own (filtered) string, or the
// caller renders the blocks itself.
Content []ResourceContents
// StructuredContent is the remote result's structuredContent, marshaled to
// json.RawMessage. nil when the remote result carried none. CallMcpWithQuery
// prefers StructuredContent (it is the typed, schema-validated view) and
// falls back to a JSON-parsing Text block otherwise.
StructuredContent json.RawMessage
// IsError reports whether the remote tool reported an error (the MCP
// IsError flag). A remote error is still a successful CallTool at the
// transport level — the caller decides how to surface it.
IsError bool
}
CallResult is the adapter value-object view of a remote MCP tool call's raw result, BEFORE any model-facing truncation. CallMcpWithQuery filters it through jq. It carries NO mcpsdk types (so internal/app can consume it without the SDK dependency): the per-block content is the existing ResourceContents adapter VO, and StructuredContent is a json.RawMessage.
It is deliberately separate from remoteTool.Execute's session.ToolResult: Execute truncates and fail-closes on over-cap structured output (a truncated JSON blob is unparseable, so the model gets an actionable error pointing at CallMcpWithQuery instead). CallTool returns the UNTRUNCATED raw result so the jq filter in CallMcpWithQuery can narrow it BEFORE it enters model context.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager holds a set of connected MCP servers and presents their tools as a single aggregate. It is the convenient entry point when wiring several servers at once.
func NewManager ¶
func NewManager(ctx context.Context, configs []ServerConfig, onError func(cfg ServerConfig, err error), diag port.Diagnostics) (*Manager, error)
NewManager connects to each ServerConfig over Streamable HTTP. By default a server that fails to connect is logged-and-skipped (via onError) so one bad server does not take down the harness; the surviving servers are returned in the Manager. If onError is nil, connection errors are silently skipped.
Connections run CONCURRENTLY with a bounded fan-out (maxConnectConcurrency), so N independent servers connect in ~max(handshake) instead of N×(handshake). This is the dominant cost of embedded-server startup when ToolHive discovers multiple workloads (issue #218): the connects were serial, each bounded by defaultConnectTimeout. Order of m.servers is NOT guaranteed — callers must not assume insertion order (none do; Tools/Servers are name-routed, not positional).
NewManager returns an error only if no servers could be connected AND at least one was configured, so the caller can distinguish "nothing usable" from "all good".
func (*Manager) CallTool ¶
func (m *Manager) CallTool(ctx context.Context, server, toolName string, args json.RawMessage) (CallResult, error)
CallTool implements Provider by routing the raw tool call to the named server. Unlike remoteTool.Execute it returns the UNTRUNCATED raw result so a caller (CallMcpWithQuery) can filter it through jq before it enters model context.
func (*Manager) Close ¶
Close closes every connected server, returning the first error encountered (after attempting to close them all).
func (*Manager) GetPrompt ¶
func (m *Manager) GetPrompt(ctx context.Context, server, name string, args map[string]string) (PromptResult, error)
GetPrompt implements Provider by routing the expansion to the named server.
func (*Manager) ListPrompts ¶
ListPrompts implements Provider. With an empty server name it returns the union of every connected server's prompt snapshot; otherwise the named one.
func (*Manager) ListResources ¶
ListResources implements Provider. With an empty server name it returns the union of every connected server's resource snapshot; otherwise it returns the named server's snapshot (error if the name is unknown).
func (*Manager) ReadResource ¶
ReadResource implements Provider by routing the read to the named server.
type OAuthClientConfig ¶
type OAuthClientConfig struct {
Preregistered *oauthex.ClientCredentials
ClientIDMetadataDocumentURL string
}
OAuthClientConfig selects one durable client-registration profile.
type OAuthController ¶
type OAuthController struct {
// contains filtered or unexported fields
}
OAuthController owns one official SDK authorization handler and the durable credential state for one MCP resource.
func NewOAuthController ¶
func NewOAuthController(ctx context.Context, resource string, opts OAuthOptions) (*OAuthController, error)
NewOAuthController constructs the OAuth handler once. The caller context bounds credential restoration; the returned controller has its own lifetime and is safe for concurrent use until Close.
func (*OAuthController) Authorize ¶
func (c *OAuthController) Authorize(ctx context.Context, req *http.Request, resp *http.Response) error
Authorize coalesces concurrent and late-arriving equivalent challenges for this credential identity while leaving cancellation bounded by each caller's context. A completed outcome remains attached to its challenge key until a different credential/challenge arrives or ResetCredential invalidates it.
func (*OAuthController) Close ¶
func (c *OAuthController) Close() error
Close cancels the controller lifetime, joins authorization leaders and waiters, and releases the shared OAuth/resource HTTP idle pool. It is idempotent and does not close the borrowed credential store. Presenter implementations must honor cancellation: Close does not return while host code can still access controller-owned state.
func (*OAuthController) ResetCredential ¶
func (c *OAuthController) ResetCredential(ctx context.Context) error
ResetCredential conditionally deletes the current record and clears the live token source. A concurrent CAS winner is preserved and adopted.
func (*OAuthController) TokenSource ¶
func (c *OAuthController) TokenSource(ctx context.Context) (oauth2.TokenSource, error)
TokenSource returns the controller's current durable token source. A nil source means the resource has not required authorization yet.
type OAuthError ¶
type OAuthError struct {
// contains filtered or unexported fields
}
OAuthError exposes only a stable safe category and never retains its cause.
func (*OAuthError) Error ¶
func (e *OAuthError) Error() string
func (*OAuthError) Is ¶
func (e *OAuthError) Is(target error) bool
Is reports whether the error has the requested safe OAuth category.
type OAuthNetworkPolicy ¶
type OAuthNetworkPolicy struct {
AdditionalOrigins []string
PrivateOrigins []string
MaxRedirects int
}
OAuthNetworkPolicy declares endpoint origins. DNS and transport enforcement is added by the controller's hardened HTTP client; persistence uses the origins to reject records for endpoints outside the configured identity.
type OAuthOptions ¶
type OAuthOptions struct {
Subject OAuthSubject
Issuer string
Client OAuthClientConfig
RedirectURL string
Presenter OAuthPresenter
// CredentialStore enables mutable restore, authorization, refresh rotation, and reset.
// It is mutually exclusive with CredentialReader so reads and writes cannot cross CAS domains.
CredentialStore credentialstore.Store
// CredentialReader restores an existing opaque credential without mutation.
// It is mutually exclusive with CredentialStore.
CredentialReader credentialstore.Reader
// AllowInMemoryRefresh permits a refreshed token from a read-only source to be
// used only for this controller lifetime. It does not provide restart durability.
AllowInMemoryRefresh bool
Network OAuthNetworkPolicy
RequestRefreshToken bool
AllowedScopes []string
Timeout time.Duration
// contains filtered or unexported fields
}
OAuthOptions configures the persistence-capable SDK handler core.
type OAuthPresenter ¶
type OAuthPresenter interface {
PresentAuthorization(context.Context, string) (*auth.AuthorizationResult, error)
}
OAuthPresenter hands an authorization URL to a host-owned interactive flow.
func OAuthLoginPresenter ¶
func OAuthLoginPresenter(present func(context.Context, string) (oauthlogin.Result, error)) OAuthPresenter
OAuthLoginPresenter adapts the host loopback callback to the official SDK authorization result. Validation remains owned by the loopback runtime and the SDK/controller; this bridge only converts their value types.
type OAuthPresenterFunc ¶
OAuthPresenterFunc adapts a function into an OAuthPresenter.
func (OAuthPresenterFunc) PresentAuthorization ¶
func (f OAuthPresenterFunc) PresentAuthorization(ctx context.Context, authorizationURL string) (*auth.AuthorizationResult, error)
PresentAuthorization calls f with the authorization URL.
type OAuthSubject ¶
OAuthSubject identifies the host profile and principal that own a credential.
type Prompt ¶
type Prompt struct {
Server string
Name string
Title string
Description string
Arguments []PromptArgument
}
Prompt is the adapter's value-object view of a prompt template advertised by a remote MCP server. Like Resource it carries no SDK types so non-adapter layers (and a later gRPC stage) can consume it freely. Server records the owning connected server.
type PromptArgument ¶
PromptArgument describes a single templating argument a prompt accepts.
type PromptExpander ¶
type PromptExpander struct {
// contains filtered or unexported fields
}
PromptExpander implements prompt.CommandExpander by recognizing an input of the form
/mcp__<server>__<prompt> [key=value ...]
expanding it via Provider.GetPrompt, and returning the flattened, role-tagged expansion. The "mcp__<server>__<prompt>" shape mirrors the namespacing of proxied remote tools, so a model that knows the tool namespace can address a prompt the same way. It carries no SDK types and only depends on the domain CommandExpander interface plus this package's Provider.
ARGUMENT SYNTAX: arguments are whitespace-separated "key=value" pairs (the value may not contain spaces in v1; this is a deliberately simple, documented grammar consistent with the positional feel of DirCommandExpander). A token without an "=" is ignored. The MCP prompt's declared arguments determine what the server actually uses.
PRECEDENCE: this expander only matches the "/mcp__..." shape. Any other input — including a "/name"-style slash command handled by DirCommandExpander — returns ("", false, nil) so the next expander in a prompt.MultiExpander runs. Composed AFTER DirCommandExpander, a file-backed command named "mcp__x__y" would shadow this; in practice command files are not named with the mcp__ prefix, so the two do not overlap.
func NewPromptExpander ¶
func NewPromptExpander(p Provider) *PromptExpander
NewPromptExpander builds a PromptExpander over the given Provider.
type PromptMessage ¶
PromptMessage is one role-tagged message of an expanded prompt. Role is the MCP role string ("user"/"assistant"); Text is the flattened textual content.
type PromptResult ¶
type PromptResult struct {
Description string
Messages []PromptMessage
}
PromptResult is the adapter's view of an expanded prompt: its optional description plus the rendered messages.
type Provider ¶
type Provider interface {
// ListResources returns the resource snapshots. As of ADR 0057 these are
// lazily refreshed on a notifications/resources/list_changed (the first call
// after a notification pays a bounded synchronous re-list). server==""
// returns the union across all servers; a specific name returns just that
// server's (or an error if the name is unknown).
ListResources(ctx context.Context, server string) ([]Resource, error)
// ReadResource reads a single resource by URI from the named server.
ReadResource(ctx context.Context, server, uri string) (ResourceContents, error)
// ListPrompts returns the prompt snapshots. As of ADR 0057 these are lazily
// refreshed on a notifications/prompts/list_changed. server=="" returns the
// union across all servers.
ListPrompts(ctx context.Context, server string) ([]Prompt, error)
// GetPrompt expands a named prompt with args on the named server.
GetPrompt(ctx context.Context, server, name string, args map[string]string) (PromptResult, error)
// CallTool invokes a remote tool by name and returns the raw typed result
// (content blocks + structured content), BEFORE any model-facing truncation.
// Used by CallMcpWithQuery to filter the full result through jq before it
// enters model context (remoteTool.Execute truncates/fail-closes, so it
// cannot serve that path). Returns ErrUnknownServer (wrapped) for an
// unknown server name; transport faults surface verbatim. args (a
// json.RawMessage of the remote tool's input) are passed verbatim to the
// remote tool.
CallTool(ctx context.Context, server, toolName string, args json.RawMessage) (CallResult, error)
}
Provider is the read-side seam over the connected MCP servers' resources and prompts. It is what the resource/prompt meta-tools and the prompt expander are built against, and is the surface a later (gRPC) stage consumes to expose resources/prompts to clients. *Manager is the production implementation; routing is by server name, with the empty server name meaning "all servers".
LAYERING: this seam lives in the adapter package (not the domain) for the same reason as skills.Source — resources/prompts are packaged at composition time; no domain port consumes them. ReadResource/GetPrompt return Go errors only for genuine faults (unknown server, transport failure); the tools/expander built over a Provider translate those into model-facing tool errors, never aborting a turn.
type Resource ¶
type Resource struct {
Server string
URI string
Name string
Title string
Description string
MIMEType string
Size int64
ReadOnly bool
}
Resource is the adapter's value-object view of a single resource advertised by a remote MCP server. It is deliberately a plain struct (no SDK types) so the rest of the harness — and a later gRPC stage — can consume it without taking a dependency on the MCP SDK. Server records which connected server owns it.
ReadOnly is always true: a resource is a read; exposing one never mutates the server. The field exists so the resource-reading tool can advertise ReadOnly() uniformly and so callers do not special-case the concept.
type ResourceContents ¶
ResourceContents is the adapter's view of one chunk of a read resource's body. Text holds UTF-8 textual content; Blob holds raw binary bytes. Exactly one is normally populated per chunk by a well-behaved server. The harness never base64-dumps Blob into the model context — readResource summarizes binary chunks instead (see flattenResourceContents).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a live connection to one remote MCP server. It owns the SDK client session and the tool.Tool wrappers derived from the server's tool list, plus the snapshots of the server's resources and prompts captured at connect.
As of ADR 0057 the adapter holds the standalone SSE GET stream open per connected server and subscribes to server-initiated notifications/{tools,prompts,resources}/list_changed: a notification sets the matching *Dirty flag, and the next read of Tools()/Resources()/Prompts() lazily re-lists under a bounded context.Background() and swaps in the fresh snapshot. The dirty flag is cleared BEFORE the fetch (not after) so a notification arriving during the re-list re-arms it — the safe direction (at worst one redundant refresh, never a lost update).
Catalog mutation (live tool.Catalog refresh) is deliberately Phase 2 — it gets its own ADR. In Phase 1, Tools() DOES re-list on dirty (so a per-session catalog assembly that calls mgr.Tools() after a list_changed picks up the fresh set), but the already-registered remoteTool specs in an existing session are NOT updated — a tool the server dropped surfaces a tool-call error on use. This means two sessions created around the same notification may see different tool surfaces (a timing-dependent split); this is the accepted Phase 1 trade-off, documented in ADR 0057.
A dropped session (the SDK's ErrConnectionClosed / errSessionMissing, surfacing as "session not found" / "connection closed") is re-established transparently by withSession: a single bounded reconnect attempt per call, serialized under mu so N concurrent failing calls produce ONE dial. See reconnect.go and ADR 0056.
func Connect ¶
func Connect(ctx context.Context, cfg ServerConfig, diag port.Diagnostics) (*Server, error)
Connect establishes a Streamable HTTP session to the configured MCP server, performs the initialize handshake, lists the server's tools, and returns a Server whose Tools() are ready to register into a catalog.
The connect handshake and tool listing are bounded by cfg.Timeout (or defaultConnectTimeout). A non-nil error means the server should be treated as unavailable; callers (the composition root) are expected to log-and-skip such a server rather than aborting the whole harness.
func (*Server) Close ¶
Close terminates the MCP session. It is safe to call once; subsequent calls return the SDK's session-close result. It takes s.mu and sets closed (the terminal flag) and dropped so a post-close call path never attempts a reconnect dial: reconnect's top-of-function `if s.closed` check returns errServerClosed before any dial.
func (*Server) HasOAuthCredential ¶
HasOAuthCredential reports whether this connected OAuth server restored or durably stored a credential. It exposes readiness only and never reads or returns token data.
func (*Server) Prompts ¶
Prompts returns the server's prompt snapshot. Lazily re-listed on a notifications/prompts/list_changed (ADR 0057). See Tools() for the lock discipline.
func (*Server) Resources ¶
Resources returns the server's resource snapshot. Lazily re-listed on a notifications/resources/list_changed (ADR 0057). See Tools() for the lock discipline.
func (*Server) Tools ¶
Tools returns the wrapped remote tools exposed by this server. If a notifications/tools/list_changed has fired since the last read (ADR 0057), the snapshot is lazily re-listed under a bounded context.Background() before returning, so a post-notification caller sees the server's current tool set. The re-list runs WITHOUT s.mu held (it may reconnect, which re-acquires s.mu); only the dirty-check and the final swap hold the lock.
type ServerConfig ¶
type ServerConfig struct {
// Name is a short, stable identifier for the server. It becomes the
// <server> segment of every wrapped tool's namespaced name, so it should be
// unique across the configured servers and contain no "__" sequence.
Name string
// URL is the server's Streamable HTTP endpoint (e.g. https://host/mcp).
URL string
// Headers are extra HTTP headers sent on every request to the server, such
// as "Authorization: Bearer ...". Optional.
Headers map[string]string
// OAuth enables the adapter-local authorization-code controller. It is
// mutually exclusive with a static Authorization header.
OAuth *OAuthOptions
// Timeout bounds the connect handshake and tool listing. If zero,
// defaultConnectTimeout is used. It does not bound later tool calls, which
// are governed by the per-call context.
Timeout time.Duration
}
ServerConfig describes a single remote MCP server to connect to over the Streamable HTTP transport.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package jq is a sandboxed wrapper around github.com/itchyny/gojq that evaluates a jq filter against a JSON input and returns the JSON-stringified result.
|
Package jq is a sandboxed wrapper around github.com/itchyny/gojq that evaluates a jq filter against a JSON input and returns the JSON-stringified result. |
|
Package source is the pluggable EXTENSIBILITY POINT for WHERE the harness's MCP server configs come from.
|
Package source is the pluggable EXTENSIBILITY POINT for WHERE the harness's MCP server configs come from. |