On this page

MCP servers provide additional tools, resources, and prompts. Haskell Agent can connect to local stdio processes and remote HTTP servers.

For a complete first connection, follow Connect and verify an MCP server. It uses Sentry's hosted endpoint and checks a read-only result before requesting any mutation.

Add a server interactively

Open:

/mcp

Use a to add a remote URL or local command. For an HTTP server, the manager starts OAuth when required. You can also ask Meta Console to add an endpoint:

/meta add the MCP server at https://example.com/mcp

Replace that example URL with your server's actual endpoint and review the configuration preview.

Manage connections

In the MCP manager:

KeyAction
Arrow keys or j / kSelect a server
EnterInspect its tools
iRe-authorize an HTTP server
SpaceEnable or disable
x, then yRemove
rRestart the MCP runtime

Saved interactive changes restart the runtime without discarding the session. Environment values are not displayed.

Configure from the command line

agent-cli mcp list
agent-cli mcp list --json
agent-cli mcp add sentry --transport http https://mcp.sentry.dev/mcp
agent-cli mcp disable sentry
agent-cli mcp enable sentry

These commands manage ~/.haskell-agent/config.json. A running session picks up external catalog changes when you restart its MCP runtime with /mcp, then r.

Read the catalog as JSON

agent-cli mcp list --json writes a JSON array, including disabled entries. Each object has name (string), enabled (boolean), transport (string), url (string or null), command (string), args (string array), cwd (string or null), and envKeys (string array). Environment values are intentionally omitted. This is saved configuration, not a live connectivity or authentication check; an empty array means no configured entries.

agent-cli mcp list --json | jq -r '.[] | select(.enabled) | .name'

The example needs jq. In scripts, check the command's exit status before parsing stdout; a configuration-read error is not an empty catalog. Do not execute command or URL strings from the output as shell code.

A local server can be managed through Nix:

agent-cli mcp add project-tools -- nix run /absolute/path/to/server

The referenced flake must run a stdio MCP server. The agent does not supply an implementation for this example.

Configuration examples

Merge entries into mcpServers in ~/.haskell-agent/config.json. This example illustrates both transports. Replace the local flake path and remote URL with servers you operate or trust; neither placeholder is a bundled server.

{
  "version": 1,
  "mcpInitStrategy": "auto",
  "mcpServers": {
    "project-tools": {
      "command": "nix",
      "args": ["run", "/absolute/path/to/server"],
      "startupTimeoutSeconds": 120,
      "requestTimeoutSeconds": 60,
      "protocol": "auto",
      "roots": false,
      "sampling": false,
      "logLevel": "warning"
    },
    "remote-tools": {
      "url": "https://example.com/mcp"
    }
  }
}

command is an executable, and args contains separate arguments. Shell operators are not interpreted automatically. A stdio server must reserve stdout for protocol messages and write diagnostic output to stderr. Run the local command in your Nix environment first to check that the executable exists; a protocol server waiting on stdin is not itself a failure.

Server field reference

FieldType / defaultBehavior
enabledBoolean / trueDisabled entries remain configured but are not started
urlOptional stringRemote Streamable HTTP endpoint; mutually exclusive with command
commandString / emptyLocal executable; required when url is absent
argsString array / emptyArguments for the local executable
cwdOptional stringWorking directory for a local process
envString-to-string object / emptyServer environment; values are hidden in manager/list diagnostics, not encrypted by putting them in JSON
startupTimeoutSecondsPositive integer / 30Startup timeout; allow additional time for a first Nix build
requestTimeoutSecondsPositive integer / 60Idle request timeout; progress can extend the wait
protocolString / autoauto, modern, or legacy; see negotiation below
rootsBoolean / falsePermit workspace-root requests when supported by the host
samplingBoolean / falsePermit isolated model-generation requests when supported by the host
logLevelOptional stringdebug, info, notice, warning, error, critical, alert, emergency; absent leaves server logging unchanged
oauthOptional objectRemote-only client registration and scopes; see authentication below
displayNameOptional nonblank stringHuman-readable connection name

Managed remote entries can also contain connectionId, connectionCredentials, and connectionGeneration. These identify the protected credential selection and lifecycle. Preserve them when editing an existing entry; do not duplicate connection identities or manufacture them for a new server.

Remote authentication and OAuth

  1. Add the HTTP endpoint through /mcp, then follow its authorization flow.
  2. Verify the browser's service and requested scopes before granting access.
  3. Return to the manager and inspect the server's tools. Test a read-only operation against a resource you can independently identify.
  4. Use i on the selected HTTP server to re-authorize after revoking access or changing the required account.

For a pre-registered OAuth application, the optional oauth object accepts clientId, clientSecret, clientIdMetadataUrl, and scopes. The first three are optional strings; scopes is a string array, empty by default. A client secret requires a client ID. A metadata URL must use HTTPS and include a path; scope entries must not be blank. Use the values issued by the server operator, not arbitrary identifiers.

For example, merge this named server into your existing configuration rather than replacing unrelated settings:

{
  "version": 1,
  "mcpServers": {
    "remote-tools": {
      "url": "https://example.com/mcp",
      "oauth": {
        "clientId": "your-registered-client-id",
        "scopes": ["read"]
      }
    }
  }
}

For services using a supplied bearer token, env.MCP_ACCESS_TOKEN supplies the token. Legacy token-file integrations can use env.MCP_OAUTH_TOKEN_FILE pointing to a private JSON record with client_id, token_endpoint, access_token, refresh_token, and expires_at. Keep these files private and outside version control. Prefer the interactive managed flow instead of copying credentials between files.

On a token-file request returning HTTP 401, the client locks and re-reads the record, refreshes the token, atomically saves rotated credentials with private permissions, and retries once. Failed refresh is an error, not an unbounded authorization loop. HTTP 401/403 challenges can indicate missing scopes as well as an invalid token.

Startup and tool discovery

By default, interactive sessions start servers progressively so you can use the prompt while connections initialize. One-shot commands wait for initialization. A connecting or unavailable server cannot yet provide usable tools.

Some providers discover MCP tools on demand rather than placing every tool in every model request. Ask for the operation you need; the model can search the connected tool catalog. Enabled server connections are shared with subagents, but discovery state can be session-local.

Discovery and invocation payloads

DialectDiscoveryInvocation
Codextool_search: required query, optional limit (default 8)Matching declarations become available on the next model request; call the discovered tool directly
Genericmcp_search: optional query, server, limit (1–50)mcp_call: required name in qualified server__tool form; optional object arguments
Groksearch_tool: required query, optional limit (default 5, range 1–255)use_tool: required tool_name and object tool_input
{"query":"list projects","server":"YOUR_CONNECTED_SERVER","limit":5}

This example is for generic discovery, not invocation. Read the returned schema and use its real qualified name; do not assume a tool called list_projects exists. A partial catalog means some servers may still be connecting. Recheck status before searching again; restarting a runtime can change its catalog.

Resource listing is separate: mcp_list_resources accepts optional server and otherwise queries all connected resource-capable servers. mcp_read_resource requires server and uri returned by that listing or a resource_link. A template must be instantiated with valid values before reading; a URI is not necessarily an HTTP URL.

{"server":"YOUR_CONNECTED_SERVER","uri":"REPLACE_WITH_RETURNED_RESOURCE_URI"}

Resource content may be text or encoded binary data; treat it as untrusted source material. For an uncertain mutation result, inspect the affected resource before retrying: a transport error does not prove that a send, creation or update did not happen.

Protocol negotiation

auto first probes server/discover for the modern 2026-07-28 protocol. A failed probe or no answer within five seconds falls back to legacy initialize, requesting 2025-11-25. The selected protocol era is remembered across reconnects. Use legacy to skip the probe for a known older server, or modern to require discovery to succeed.

Do not change protocol selection to fix an expired token. Authentication failures and protocol incompatibility need different remedies.

CLI OAuth scopes and logout

agent-cli mcp login https://example.com/mcp --scope read --scope write
agent-cli mcp logout https://example.com/mcp

Replace the example endpoint and scopes with values required by your service. Repeat --scope for additional scopes. Login performs authorization; requested scopes combine the first nonempty source (server challenge, resource metadata, then configured scopes), previously granted scopes for a matching issuer/resource, and your additional scopes, without duplicates. offline_access is included only when the authorization server advertises it. Requesting a scope does not guarantee that the account is authorized for it; inspect the consent screen and verify a read-only operation.

The CLI credential file under ~/.haskell-agent/credentials/mcp/ is keyed by the exact URL string, including its query. Use the same URL spelling for login, configured startup and logout. Configuration OAuth lookup can normalize equivalent endpoint spelling, but preserves the query; it is not a reason to assume two spellings share a credential file. A changed issuer or resource prevents reuse of the old registration and granted scopes. Managed hosts can instead bind credentials to an immutable connection identity.

logout removes that URL's local credential file if present; an absent file is a no-op. It does not remove the server configuration, revoke the grant at the provider, remove an explicitly supplied bearer token, or terminate another process's authenticated connection. Disable or restart the running MCP runtime separately, and use the provider's account controls if remote grant revocation is required.

Prompts and resources

MCP is not limited to tools. A server may publish prompt templates and addressable resources. Invoke a template using the server and prompt names from its catalog:

/mcp prompt server-name prompt-name argument=value

/mcps is an alias for /mcp, including prompt invocation. With no arguments either opens the manager. For a server whose catalog declares a review prompt with a target argument, an invocation would be:

/mcp prompt project-tools review target=src

This is a catalog-dependent example, not a bundled prompt. Each argument token is split at its first =; the remainder is a string value, and a token without = supplies an empty value. Use the exact declared names, avoid duplicate keys, and do not pass secrets. The server validates its required arguments. A missing server, unsupported prompt or failed request displays an error without submitting an expanded turn.

On success the returned messages are rendered and submitted as the next model turn, rather than merely previewed in the editor. Review whether you trust the server before invoking a prompt; its text can request actions but does not grant tool authorization.

The resolved messages become the next turn. Resources are available through mcp_list_resources and mcp_read_resource; request a resource's actual URI rather than guessing a filesystem path. Tool results may contain resource links that require a separate read.

Server instructions are supplied to the model. In progressive mode they arrive when the connections settle. Server-published skills can appear alongside local skills and are fetched and integrity-checked when used.

Progress, cancellation, and user input

requestTimeoutSeconds measures inactivity. Progress notifications extend the wait, up to ten times the configured value, and appear in running-tool output. A timed-out stdio request receives a cancellation notification; an HTTP response stream is closed. Cancellation does not prove that a remote mutation was undone.

Servers may request a form or an authorization URL during a call. Interactive forms are validated and reviewed before submission; URL requests show the destination and require explicit consent before opening a browser. Decline and cancel remain available. Non-interactive runs do not advertise elicitation support.

When servers announce changed tools, the manager and discovery catalog refresh. A changed deferred tool schema or approval declaration requires rediscovery. An older exposed handler will reject changed definitions instead of silently using the new contract.

Permissions

Before requesting a mutation, verify the connection with a small inspection:

  1. Open /mcp, select the server, and press Enter to inspect its tools.
  2. Choose an available read-only operation and ask the agent to perform it against a resource you are authorized to access.
  3. Check that the result names the intended service and resource.

Expected result: a tool result from that server, not merely a description of what the agent could do. If the tool is missing, inspect the connection status and available catalog before changing permissions.

Tools explicitly marked read-only by the server avoid generic mutation approval. Other tools are treated as mutations under the session's approval policy. A server's read-only annotation is its claim, not proof that its implementation is harmless; connect only servers you trust.

Roots and sampling are disabled by default. Roots can disclose the workspace location. Sampling lets a server request generation from your configured model. Enable these per server only when needed.

If authorization or startup fails, inspect the server in /mcp, check its endpoint or executable, and re-authorize or restart rather than repeatedly retrying a mutation with an uncertain outcome.

Diagnose a connection

SymptomCheckRecovery
Server absentagent-cli mcp list, spelling, enabled flagAdd or enable the intended entry; restart with /mcp then r
Local startup timeoutExecutable availability, Nix build output, stdout protocol disciplineComplete installation first; increase startup timeout only for a genuinely slow startup
HTTP 401 or 403Account, expired grant, requested scopes, endpointRe-authorize with i; verify read access before retrying a write
Connected but tool unavailableTool catalog, deferred discovery, changed schemaInspect with Enter and request discovery again
Request times outService health and progress outputInspect remote state before repeating a mutation; do not blindly lengthen every timeout
Roots or sampling unavailablePer-server setting and host supportEnable only the capability the trusted server actually needs

Current limitations

MCP image and audio blocks are described to the model, but their bytes are not forwarded. Task identifiers are not persisted across restarts. Icon metadata is retained but is not rendered by the terminal interface.