Crux
GuidesTools

Connect an MCP server

Add portable Streamable HTTP or stdio tools with safe credentials, policy, approval, and evaluations.

An MCP server can add tools to any Crux prompt or context without binding that definition to one model provider. Add one inert mcp() source to use[]; the active adapter connects and discovers tools only when the prompt executes.

Install

pnpm add @use-crux/core @use-crux/mcp
pnpm add @use-crux/ai ai

The same source works with @use-crux/ai, @use-crux/openai, @use-crux/anthropic, and @use-crux/google. The AI adapter delegates MCP mechanics to its native client; the other adapters use the official client. Both paths return ordinary Crux tools with the same lifecycle.

Connect to Streamable HTTP

Production credentials usually belong to a tenant or request. Resolve them from runtimeContext for each invocation so they do not become long-lived module state:

prompts/support.ts
import { prompt } from "@use-crux/core";
import { mcp, streamableHttp } from "@use-crux/mcp";

interface RuntimeContext {
  readonly mcpToken: string;
}

const crm = mcp<RuntimeContext>({
  id: "crm",
  transport: ({ runtimeContext, abortSignal }) => {
    if (abortSignal?.aborted) throw abortSignal.reason;
    return streamableHttp({
      url: "https://mcp.example.com/v1",
      headers: { Authorization: `Bearer ${runtimeContext.mcpToken}` },
    });
  },
  tools: { allow: ["search_customer", "update_customer"], prefix: "crm_" },
});

export const support = prompt({
  id: "support",
  use: [crm],
  prompt: "Help the customer with the available CRM tools.",
});

Pass the same typed context to the adapter call:

await generate(support, {
  model,
  runtimeContext: { mcpToken: await secrets.forTenant(tenantId) },
});

Do not place tokens in the URL query, prompt text, source ID, or tool prefix. Crux omits headers, query values, environment values, raw errors, _meta, and binary payloads from its safe observability and Project Index projections.

Redirects use redirect: 'error' by default. If you explicitly choose redirect: 'follow', Crux permits same-origin redirects and removes configured credentials before a cross-origin hop. A stable final endpoint is safer.

Connect a local stdio server

Stdio is useful for development servers and local processes:

prompts/files.ts
import { prompt } from "@use-crux/core";
import { mcp, stdio } from "@use-crux/mcp";

const localFiles = mcp({
  id: "local-files",
  transport: stdio({
    command: process.execPath,
    args: ["./servers/files.mjs"],
    env: { WORKSPACE_ROOT: process.cwd() },
  }),
  tools: { allow: ["read_file"], prefix: "files_" },
});

export const inspectWorkspace = prompt({
  id: "inspect-workspace",
  use: [localFiles],
  prompt: "Read the requested project file.",
});

Pass command and args separately and keep model input out of both. Each invocation spawns a fresh process, initializes, discovers, executes, and closes it. Crux also closes on setup errors, tool errors, cancellation, timeout, stream disposal, and approval suspension.

Select and prefix tools

Use a production allowlist. Selection uses exact remote names and happens before prefixing:

tools: { allow: ['search_customer'], prefix: 'crm_' }

The model sees crm_search_customer; the server still receives search_customer. allow and deny cannot be combined. A configured allowlist entry that is absent during discovery is simply unavailable—Crux never invents its schema or description.

Final exposed names must start with a letter or underscore, contain only letters, digits, _, or -, and be at most 64 characters. Incompatible names fail before model-provider I/O. A prefix can fix a leading digit and can prevent global collisions between servers; it cannot remove a dot or shorten a name.

Apply middleware and Safety

MCP tools join the ordinary tool lifecycle after their final exposed names are known. Match middleware and policy against that final name:

import { toolMiddleware } from "@use-crux/core";
import { toolPolicy } from "@use-crux/core/safety";

const auditCrm = toolMiddleware({
  id: "audit-crm",
  match: [/^crm_/],
  afterExecute: ({ toolName, durationMs }) => {
    audit.record({ toolName, durationMs });
  },
});

const approveWrites = toolPolicy({
  id: "approve-crm-writes-v1",
  match: { tool: "crm_update_customer" },
  action: "requestApproval",
  reason: "Customer changes require operator approval.",
});

export const governedSupport = prompt({
  id: "governed-support",
  use: [crm],
  prompt: "Help the customer.",
  toolMiddleware: [auditCrm, approveWrites],
});

Remote MCP annotations are untrusted hints. They never disable middleware, approval, Safety, guardrails, constraints, or application policy.

Resume an approval

Approval suspends the invocation and closes its MCP session. Persist the returned messages in trusted server-side storage, append the human decision, and call the adapter again:

import {
  appendToolApprovalResponse,
  findToolApprovalRequests,
} from "@use-crux/core/adapter/tool";

const [request] = findToolApprovalRequests(first.messages);

if (request) {
  const messages = appendToolApprovalResponse(first.messages, {
    approvalId: request.approvalId,
    approvalToken: request.approvalToken,
    approved: true,
  });

  await generate(governedSupport, { model, messages, runtimeContext });
}

Resume reconnects and rediscovers. If the tool name, schema, arguments, call identity, or requesting policy changed, Crux returns approval-invalid and does not invoke the remote tool. Changing policy semantics incompatibly should also change the policy ID.

Evaluate without remote side effects

A live MCP task can read or change real systems. For an Eval, bind the same callable managed production task to a deterministic in-process or spawned MCP fixture with no network dependency. Use a stable fixture revision so identity changes when behavior changes. If a dynamic dependency cannot be proven, Crux executes it normally but does not reuse its task output.

Inspect the run and catalog

Start Crux Local and execute the prompt. Run Detail presents connection and discovery before the ordinary tool call, plus policy, approval, errors, and cleanup. Catalog presents the authored mcp.server, discovered tool schemas and fingerprints, relations, last successful discovery, and active/stale/removed availability. A server-reported name or version is labeled untrusted.

Discovery failures remain visible even when provider I/O never started. Old tools become stale after a failure and removed after a successful discovery no longer returns them; they are not shown as falsely current.

Current scope

V1 supports tools over Streamable HTTP and stdio. MCP resources as prompt inputs, prompts, sampling, elicitation, tasks, roots, pools, config auto-discovery, rename maps, and configurable binary limits are deferred.

Provider-hosted MCP is not equivalent to this integration. It does not pass through Crux's portable discovery, policy, approval replay, result mapping, observability, Eval identity, and Project Index contracts.

See the MCP API reference for exact types and limits.

On this page