npm.io
0.6.0 • Published 1h ago

@use-crux/mcp

Licence
Apache-2.0
Version
0.6.0
Deps
4
Size
117 kB
Vulns
0
Weekly
0

@use-crux/mcp

Portable Model Context Protocol tool sources for Crux.

mcp() describes a remote tool source once and composes it through a prompt or context use[]. Crux connects and discovers tools for each invocation, then runs the selected tools through the same middleware, approval, Safety, guardrail, timeout, observability, and Eval lifecycle as authored tools.

Install

Install this package alongside Core and one execution adapter:

pnpm add @use-crux/core @use-crux/mcp
pnpm add @use-crux/ai ai # or @use-crux/openai, @use-crux/anthropic, @use-crux/google

@use-crux/mcp owns both the official MCP client and the AI SDK-native MCP client. Adapter packages keep MCP as an optional peer, so ordinary Core and provider installs do not pull an MCP client into the application.

@use-crux/mcp is ESM-only. Streamable HTTP remains portable; stdio materialization requires a Node runtime and fails closed elsewhere.

Streamable HTTP

Resolve credentials from invocation runtimeContext; do not put tokens in a module-level source definition, URL query, prompt, or logs.

import { prompt, toolMiddleware } from "@use-crux/core";
import { toolPolicy } from "@use-crux/core/safety";
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}` },
      redirect: "error",
    });
  },
  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.",
  toolMiddleware: [
    toolMiddleware({
      id: "crm-audit",
      match: [/^crm_/],
      afterExecute: ({ toolName, durationMs }) =>
        audit.record({ toolName, durationMs }),
    }),
    toolPolicy({
      id: "approve-crm-writes-v1",
      match: { tool: "crm_update_customer" },
      action: "requestApproval",
      reason: "Customer changes require operator approval.",
    }),
  ],
});

HTTP redirects fail by default. redirect: 'follow' follows only safe same-origin hops and strips configured credentials before any cross-origin request. Prefer a stable final endpoint instead of redirects.

stdio

Use stdio for local servers and development tools. Pass the executable and arguments separately; never build a shell command from model input.

import { mcp, stdio } from "@use-crux/mcp";

export 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_" },
});

One invocation owns one fresh MCP session. Crux initializes, discovers the complete bounded tool list, applies selection before prefixing, executes calls, and closes the session on success, failure, cancellation, timeout, stream disposal, or approval suspension. Approval resume creates a new session and rediscovers before executing the exact committed request.

Selection and names

Prefer an explicit allowlist for production servers. It limits what the model can call and makes reviews intentional, but it does not fabricate metadata for a configured tool the server did not discover.

allow and deny are mutually exclusive. prefix is applied after selection and is useful when multiple servers expose the same name or a remote name starts with a digit. Final exposed names must match ^[A-Za-z_][A-Za-z0-9_-]{0,63}$; Crux fails before provider I/O instead of silently renaming an incompatible tool. The original remote name is preserved for tools/call.

Approval resume

Use the normal Crux approval helpers. Persist the returned messages in trusted server-side storage, append the decision, and invoke the same prompt 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 adapter.generate(support, { model, messages, runtimeContext });
}

The approval token and replay commitment bind the call, input, discovered MCP identity, schema, and requesting policies. Missing or changed provenance returns an ordinary approval-invalid tool result and never reaches remote execution.

Evals

A live MCP source can affect real systems. Point an Eval at the callable managed production task, and use a deterministic in-process or spawned MCP fixture when the run must avoid remote side effects. Give the fixture a stable revision so task identity changes with fixture behavior. Dynamic dependencies whose identity cannot be proven execute normally but are not reusable.

Devtools

With Crux Local running, Run Detail shows mcp.connect, mcp.discover, the ordinary tool call, policy and approval evidence, and bounded cleanup. Catalog shows the authored server, discovered tools, schemas, fingerprints, health, and current/stale/removed availability. Self-reported server name and version are untrusted presentation data. Credentials, raw schemas, _meta, binary payloads, and raw transport errors are not retained.

Reliability and limits

  • Each selected image, audio, or embedded binary resource is strictly decoded and limited to 20 MiB.
  • Discovery is bounded to 64 pages and rejects cursor loops, duplicate exposed names, malformed schemas, and non-portable names before provider I/O.
  • HTTP and stdio sessions are call-scoped; there is no connection pool in v1.
  • Cleanup is bounded and cannot mask the primary generation error.

Scope

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

Provider-hosted MCP options are not equivalent to mcp(): they bypass Crux's portable materialization, policy, approval replay, result normalization, observability, Eval identity, and Project Index contracts. First-party Crux adapters use the portable client paths instead.

See the MCP guide and API reference.

Keywords