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 aiThe 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:
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:
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.