Skip to main content
The @centure/node-sdk provides a transport wrapper for the Model Context Protocol (MCP) that automatically scans messages for prompt injection attacks before they reach your MCP server.

What is MCP?

The Model Context Protocol is a standard for connecting AI models to external tools and data sources. The Centure SDK intercepts MCP messages to detect and block malicious prompts.

Installation

Install both the Centure SDK and the MCP SDK:
npm install @centure/node-sdk @modelcontextprotocol/sdk

Basic Setup

Wrap any MCP transport with CentureMCPClientTransport to add automatic scanning:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { CentureClient } from "@centure/node-sdk";
import { CentureMCPClientTransport } from "@centure/node-sdk/mcp";

// Create your underlying MCP transport
const stdioTransport = new StdioClientTransport({
  command: "your-mcp-server",
  args: ["--arg1", "--arg2"],
});

// Wrap it with Centure security scanning
const secureTransport = new CentureMCPClientTransport({
  client: new CentureClient({
    apiKey: process.env.CENTURE_API_KEY,
  }),
  transport: stdioTransport,
});

// Use with MCP client
const client = new Client({
  name: "your-client",
  version: "1.0.0",
});

await client.connect(secureTransport);
The transport intercepts all messages sent through the MCP client and scans them before forwarding to the server.

Configuration Hooks

The transport provides hooks to customize scanning behavior:

shouldScanMessage

Determines whether a specific message should be scanned. Return false to skip scanning.
const secureTransport = new CentureMCPClientTransport({
  client: centureClient,
  transport: stdioTransport,

  shouldScanMessage: ({ message, extra }) => {
    // Skip scanning for tool list requests
    if (message.method === "tools/list") {
      return false;
    }

    // Skip scanning for specific notification types
    if (extra?.notification && extra.notification === "progress") {
      return false;
    }

    return true;
  },
});
Parameters:
  • message (JSONRPCMessage) - The MCP message to potentially scan
  • extra (MessageExtraInfo) - Additional context about the message
Returns: boolean or { scan: boolean }

onAfterScan

Called after a message is scanned, regardless of the result. Use this for logging or metrics.
const secureTransport = new CentureMCPClientTransport({
  client: centureClient,
  transport: stdioTransport,

  onAfterScan: ({ message, scanResult }) => {
    // Log all scan results
    console.log(`Scanned ${message.method}:`, {
      safe: scanResult.is_safe,
      categories: scanResult.categories,
      requestId: scanResult.request_id,
    });

    // Optionally allow unsafe messages to pass through
    if (scanResult.categories.every((c) => c.confidence === "low")) {
      return { passthrough: true };
    }
  },
});
Parameters:
  • message (JSONRPCMessage) - The scanned message
  • extra (MessageExtraInfo) - Additional context
  • scanResult (ScanResponse) - The scan result from Centure API
Returns: void or { passthrough: boolean }

onUnsafeMessage

Called when an unsafe message is detected. Controls whether to block or allow the message.
const secureTransport = new CentureMCPClientTransport({
  client: centureClient,
  transport: stdioTransport,

  onUnsafeMessage: ({ message, scanResult }) => {
    // Allow medium-confidence threats
    const isMedium = scanResult.categories.every(
      (c) => c.confidence === "medium"
    );

    if (isMedium) {
      console.warn("Allowing medium-confidence threat:", scanResult.categories);
      return { passthrough: true };
    }

    // Block high-confidence threats with custom error
    console.error("Blocking high-confidence threat:", scanResult.categories);
    return {
      passthrough: false,
      replace: {
        jsonrpc: "2.0",
        id: message.id,
        error: {
          code: -32001,
          message: "Security violation: prompt injection detected",
          data: {
            categories: scanResult.categories,
            requestId: scanResult.request_id,
          },
        },
      },
    };
  },
});
Parameters:
  • message (JSONRPCMessage) - The unsafe message
  • extra (MessageExtraInfo) - Additional context
  • scanResult (ScanResponse) - The scan result from Centure API
Returns: { passthrough: boolean, replace?: JSONRPCMessage }
Always return an object from onUnsafeMessage. Set passthrough: false to block the message and optionally provide a replace message to send instead.

onBeforeSend

Called before any message is sent through the transport, before scanning occurs.
const secureTransport = new CentureMCPClientTransport({
  client: centureClient,
  transport: stdioTransport,

  onBeforeSend: ({ message, extra }) => {
    console.log(`Sending ${message.method} to MCP server`);
  },
});
Parameters:
  • message (JSONRPCMessage) - The message about to be sent
  • extra (MessageExtraInfo) - Additional context
Returns: void

Complete Example

Here’s a production-ready configuration with all hooks:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { CentureClient } from "@centure/node-sdk";
import { CentureMCPClientTransport } from "@centure/node-sdk/mcp";

// Initialize Centure client
const centureClient = new CentureClient({
  apiKey: process.env.CENTURE_API_KEY,
});

// Create base MCP transport
const stdioTransport = new StdioClientTransport({
  command: "npx",
  args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
});

// Wrap with security scanning
const secureTransport = new CentureMCPClientTransport({
  client: centureClient,
  transport: stdioTransport,

  // Skip scanning for safe methods
  shouldScanMessage: ({ message }) => {
    const safeMethods = [
      "initialize",
      "tools/list",
      "resources/list",
      "prompts/list",
    ];

    if (safeMethods.includes(message.method)) {
      return false;
    }

    return true;
  },

  // Log all scan results
  onAfterScan: ({ message, scanResult }) => {
    if (!scanResult.is_safe) {
      console.warn(`Unsafe message detected in ${message.method}:`, {
        categories: scanResult.categories,
        requestId: scanResult.request_id,
      });
    }
  },

  // Handle unsafe messages
  onUnsafeMessage: ({ message, scanResult }) => {
    // Allow low and medium confidence detections with logging
    const maxConfidence = scanResult.categories.reduce(
      (max, cat) => {
        const levels = { low: 1, medium: 2, high: 3 };
        return Math.max(max, levels[cat.confidence]);
      },
      0
    );

    if (maxConfidence < 3) {
      console.warn("Allowing non-high confidence threat");
      return { passthrough: true };
    }

    // Block high-confidence threats
    console.error("BLOCKED: High-confidence prompt injection");
    return {
      passthrough: false,
      replace: {
        jsonrpc: "2.0",
        id: message.id,
        error: {
          code: -32001,
          message: "Request blocked: security violation detected",
          data: {
            requestId: scanResult.request_id,
            categories: scanResult.categories.map((c) => c.code),
          },
        },
      },
    };
  },

  // Log all outgoing messages
  onBeforeSend: ({ message }) => {
    console.log(`→ ${message.method}`);
  },
});

// Connect MCP client
const client = new Client({
  name: "secure-mcp-client",
  version: "1.0.0",
});

await client.connect(secureTransport);

// Use the client normally
const tools = await client.listTools();
console.log("Available tools:", tools);

Transport Types

CentureMCPClientTransportOptions

Configuration options for the transport wrapper:
interface CentureMCPClientTransportOptions {
  client: CentureClient;
  transport: Transport;
  shouldScanMessage?: (context: ShouldScanMessageContext) => boolean | ShouldScanMessageResult;
  onAfterScan?: (context: OnAfterScanContext) => void | OnAfterScanResult;
  onUnsafeMessage?: (context: OnUnsafeMessageContext) => OnUnsafeMessageResult;
  onBeforeSend?: (context: { message: JSONRPCMessage; extra?: MessageExtraInfo }) => void;
}

Hook Context Types

type ShouldScanMessageContext = {
  message: JSONRPCMessage;
  extra?: MessageExtraInfo;
};

type OnAfterScanContext = {
  message: JSONRPCMessage;
  extra?: MessageExtraInfo;
  scanResult: ScanResponse;
};

type OnUnsafeMessageContext = {
  message: JSONRPCMessage;
  extra?: MessageExtraInfo;
  scanResult: ScanResponse;
};

type OnUnsafeMessageResult = {
  passthrough: boolean;
  replace?: JSONRPCMessage;
};

Best Practices

Block high-confidence threats: Always block messages with high confidence detections in production environments.
Log all detections: Use onAfterScan to log scanning results for security monitoring and incident response.
Skip safe methods: Use shouldScanMessage to skip scanning for methods that cannot carry threats (e.g., tools/list, initialize).
Provide clear error messages: When blocking messages, include the request_id in error responses for debugging and support.
Do not allow high confidence detections to pass through without careful review. These indicate strong evidence of prompt injection attacks.

Supported Transports

The CentureMCPClientTransport works with any MCP transport implementation:
  • StdioClientTransport - Communicate with servers via stdin/stdout
  • SSEClientTransport - Communicate with servers via Server-Sent Events
  • Custom transports - Any transport implementing the MCP Transport interface

Error Handling

The transport handles scanning errors gracefully:
const secureTransport = new CentureMCPClientTransport({
  client: centureClient,
  transport: stdioTransport,

  onAfterScan: ({ message, scanResult }) => {
    // This hook runs even if scanning fails
    // Check if scanResult indicates an error
  },
});
If the Centure API is unreachable or returns an error, the message is blocked by default. Use onAfterScan to implement custom fallback behavior.

Next Steps