Skip to main content
Best for: teams implementing MCP servers or MCP tool handlers directly in TypeScript and wanting tool-level boundary protection without changing their server design.

Install

npm install @verifiedx-core/sdk
This page is for standalone MCP tool handlers you own directly. If MCP is being surfaced through OpenAI Agents or another higher-level SDK, use that native adapter page instead.

Net-new VerifiedX code

This is the actual VerifiedX delta in an existing MCP server.
import { initVerifiedX } from "@verifiedx-core/sdk";
import { wrapMcpTool } from "@verifiedx-core/sdk/mcp";

const verifiedx = await initVerifiedX();

const searchMemories = wrapMcpTool(
  "search_memories",
  async (params) => ({ ok: true, hits: [] }),
  { verifiedx },
);

const addMemory = wrapMcpTool(
  "add_memory",
  async (params) => ({ ok: true, saved: params }),
  { verifiedx, policyScope: "memory_write" },
);

// In your existing callTool path:
const handlers = {
  search_memories: searchMemories,
  add_memory: addMemory,
};

return handlers[toolName](params);
That is the important part. The rest of your MCP server stays the same: your listTools() output, transport, server wiring, and request routing do not need to be redesigned.
If a tool is definitely a durable memory write, pass policyScope: "memory_write" instead of relying only on name heuristics.
wrapMcpTool(...) is a thin adapter over VerifiedX core boundary wrappers. It uses the same preflight, decision-receipt, execution-report, and runtime-loopback path as the rest of the product.

Full example

import { initVerifiedX } from "@verifiedx-core/sdk";
import { wrapMcpTool } from "@verifiedx-core/sdk/mcp";

const toolDefinitions = [
  {
    name: "search_memories",
    description: "Search customer memory entries.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string" },
      },
      required: ["query"],
    },
  },
  {
    name: "add_memory",
    description: "Persist a customer memory entry.",
    inputSchema: {
      type: "object",
      properties: {
        namespace: { type: "string" },
        key: { type: "string" },
        value: {},
      },
      required: ["namespace", "key", "value"],
    },
  },
  {
    name: "set_workflow_status",
    description: "Update internal workflow status.",
    inputSchema: {
      type: "object",
      properties: {
        workflow_id: { type: "string" },
        status: { type: "string" },
        reason: { type: "string" },
      },
      required: ["workflow_id", "status", "reason"],
    },
  },
];

const verifiedx = await initVerifiedX();

const handlers = {
  search_memories: wrapMcpTool(
    "search_memories",
    async ({ query }) => ({
      ok: true,
      hits: [{ key: "cust_123.preference", query }],
    }),
    { verifiedx },
  ),

  add_memory: wrapMcpTool(
    "add_memory",
    async ({ namespace, key, value }) => ({
      ok: true,
      saved: { namespace, key, value },
    }),
    { verifiedx, policyScope: "memory_write" },
  ),

  set_workflow_status: wrapMcpTool(
    "set_workflow_status",
    async ({ workflow_id, status, reason }) => ({
      ok: true,
      workflow_updated: { workflow_id, status, reason },
    }),
    { verifiedx },
  ),
};

export async function listTools() {
  return toolDefinitions;
}

export async function callTool(toolName, params) {
  const handler = handlers[toolName];
  if (!handler) {
    return { ok: false, error: `Unknown tool: ${toolName}` };
  }
  return handler(params);
}
Do not use raw bindHarness(...) for this path. The TypeScript MCP surface is wrapMcpTool(...).

Composed systems

If this MCP tool call is part of a larger multi-agent or agent+human workflow, pass upstream context into VerifiedX so the current tool invocation has better system and situational awareness before it takes a high-impact action. This is useful when a supervisor agent, parent workflow, or human reviewer already has context that the current MCP tool should use before taking action. VerifiedX does not require a fixed schema for this. Pass the upstream context you already have in any JSON-serializable shape.
const upstream = {
  source: "workflow_supervisor",
  workflow_id: "WF-2203",
  approval_status: "approved_with_follow_up",
  human_review: {
    reviewer: "ops_lead",
    result: "approved",
  },
  prior_agent_output: {
    summary: "Billing verification is complete.",
  },
};

const result = await verifiedx.withUpstreamContext(upstream, async () => {
  return await handlers.set_workflow_status({
    workflow_id: "WF-2203",
    status: "awaiting_human",
    reason: "billing verification is missing",
  });
});
Upstream context is supporting workflow context from outside the current tool invocation. It is not proof that this tool already executed any local action.

What the wrapper already does

Once wrapped, VerifiedX handles the MCP tool boundary directly. That includes:
  • Injecting _meta.verifiedx into request params and result payloads
  • Recording retrieval-like tools into run history as support inputs
  • Preflighting high-impact tools before the handler runs
  • Observing tool-result ingress with sourceUri: "mcp://<toolName>"
  • Emitting MCP tool-result events with sourceLineage: ["mcp_tool"]
There is no separate TypeScript tool-definition wrapper. Keep your existing MCP tool definitions as they are and wrap the handlers you register behind callTool(...).

What gets preflighted

VerifiedX infers the protected boundary from the MCP tool name and params. That includes:
  • memory_write
  • external_message_send
  • record_mutation
  • system_change
Retrieval-like tools stay in run history as support inputs instead of being treated as mutations. Examples from the actual MCP wrapper logic:
  • search_memories records retrieval history
  • add_memory with namespace, key, and value preflights as memory_write
  • message-like tools such as send_email infer external_message_send
  • record-like tools such as update_customer_record infer record_mutation
  • internal update tools such as set_workflow_status infer system_change
The explicit policyScope override is mainly for durable memory writes today. Other action classes are inferred from the MCP tool name and params shape.

What to expect at runtime

Protected MCP boundaries can return:
  • allow
  • allow_with_warning
  • replan_required
  • goal_fail_terminal
Every outcome includes a structured decision receipt. If a tool is replanned, the side effect does not execute. The wrapped handler returns the normal VerifiedX blocked result shape, including:
  • ok: false
  • blocked: true
  • boundary_outcome
  • safe_next_steps
  • decision_receipt
In a standalone MCP server, you will usually consume that blocked result locally and let the caller replan. When the same tool is composed inside a larger orchestration layer, the same receipt can signal either local replan or upstream replan.

Validation coverage

The standalone TypeScript MCP wrapper is directly covered in this repo. That includes:
  • A dedicated MCP wrapper test where search_memories is recorded as retrieval history and add_memory is preflighted as memory_write
  • A fixture app that wraps an MCP tool with wrapMcpTool(...)
  • Additional MCP listTools() and callTool(...) coverage inside the OpenAI Agents TypeScript integration, including hosted MCP approval requests

Pricing note

One protected action check equals one real boundary preflight. Taint, event ingest, execution reports, and decision reads are all included at that price. The Free Sandbox includes every language, provider, framework, and adapter. VerifiedX does not replace your MCP server or orchestration. It returns receipts your system can keep local, route downstream, or pass upstream.
For the full raw runtime reference, see the TypeScript SDK. If your MCP tools are being used through OpenAI Agents, see the OpenAI Agents SDK page.