Skip to main content
This tutorial connects your own code to the gateway. You’ll write a tiny webhook that redacts email addresses from tool results, expose it over HTTPS, register it under Rule Engines, prove it works with the built-in Test action, then attach it to a gateway rule and watch its modify verdict land in your logs. By the end you have the full loop — your service deciding, in-line, what passes the gateway. It assumes you’ve seen a rule fire before; if not, do Add your first gateway rule first. The full webhook contract — every field, every verdict — lives in Building a Custom Rule Engine; this lesson walks one path end to end.
Registering engines uses the Manage integrations capability; if you don’t see Rule Engines in the left-hand navigation, your role lacks it. See the capabilities reference. Your engine must be reachable on public HTTPS — MCP Manager rejects http:// and private, loopback, or link-local addresses.

What you’ll need

  • Node.js 18+ and a terminal.
  • A way to expose a local port over public HTTPS — a tunnel such as ngrok, or a quick deploy to any host.
  • A gateway with a server whose tool returns text. The Quickstart gateway from Your first governed tool call works.
  • About 25 minutes.

How a custom engine fits

A custom rule engine is a webhook the gateway POSTs to when a rule using it fires. The gateway sends the message wrapped in a metadata envelope; your webhook returns one of four verdicts — pass, block, modify, or error — and the gateway acts on it before forwarding. This tutorial uses modify: your webhook rewrites the tool result and hands back a complete replacement.

Step 1: Write the webhook

Create a project and install Express:
terminal
mkdir email-redactor && cd email-redactor
npm init -y && npm pkg set type=module
npm install express
Save this as server.js. It authenticates with a shared secret, pulls the text out of the tool result, and returns a modify verdict that redacts any email — echoing the inbound request id so the response stays valid JSON-RPC:
server.js
import express from 'express';

const app = express();
app.use(express.json({ limit: '16mb' })); // matches the gateway's body cap

const SECRET = process.env.RULE_ENGINE_SECRET ?? 'change-me';

app.post('/inspect', (req, res) => {
  // Authenticate before doing any work.
  if (req.headers['x-api-key'] !== SECRET) {
    return res.status(401).json({ error: 'unauthorized' });
  }

  const { metadata, body } = req.body;
  const result = body?.result;
  const email = /\b[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\b/g; // defined per-call: no shared lastIndex

  // Pull text out of the result — a content envelope or a plain string.
  const text =
    typeof result === 'string'
      ? result
      : Array.isArray(result?.content)
        ? result.content.filter((c) => c?.type === 'text').map((c) => c.text).join('\n')
        : null;

  if (!text || !email.test(text)) {
    return res.json({ type: 'pass', comment: 'no email found' });
  }

  // Rebuild the result with emails redacted, keeping the SAME id.
  const redacted = text.replace(email, '[REDACTED EMAIL]');
  const newResult =
    typeof result === 'string'
      ? redacted
      : { ...result, content: result.content.map((c) => (c?.type === 'text' ? { ...c, text: redacted } : c)) };

  res.json({
    type: 'modify',
    comment: 'redacted email address(es)',
    modifiedPayload: { body: { jsonrpc: '2.0', id: metadata.requestId, result: newResult } },
  });
});

app.listen(3000, () => console.log('rule engine on :3000'));
Run it: RULE_ENGINE_SECRET=my-secret node server.js.
Two things make this contract-correct, and your real engine needs both: echo metadata.requestId as the id of modifiedPayload.body (a mismatch makes the client hang), and return modifiedPayload.body as a complete JSON-RPC response with only jsonrpc, id, and result (or error). Extra fields get the response rejected as malformed. See the contract.

Step 2: Expose it over public HTTPS

The gateway calls your engine over the public internet, so a localhost URL won’t register. Put a public HTTPS address in front of port 3000 — the fastest way is a tunnel:
terminal
ngrok http 3000
Copy the https://… forwarding URL ngrok prints. Your webhook is now reachable at https://<your-tunnel>/inspect. (Any host works — deploy the same file to a platform that gives you an HTTPS URL if you prefer.)

Step 3: Register the engine

1

Add a Custom engine

Open Rule Engines in the left-hand navigation and click Add. Choose the Custom provider.
2

Set the endpoint and method

Set Name to Email redactor, the Endpoint URL to https://<your-tunnel>/inspect, and leave the HTTP method at POST.
3

Add the shared secret as a header

Under Headers, add X-Api-Key with the value you used for RULE_ENGINE_SECRET (for example my-secret). MCP Manager stores it encrypted and sends it on every call, so your webhook can authenticate the caller. Save.

Step 4: Test the engine before wiring it up

Every engine has a Test action — use it to confirm your webhook answers correctly without touching a gateway.
1

Open Test

On the Email redactor engine, click Test. A modal opens with a sample-text box (up to 500 characters).
2

Enter text with an email

Type something like Contact alice@example.com for details and run it. The engine returns a modify verdict with the comment redacted email address(es), and you see how long the call took. A pass verdict on text with no email confirms the other branch.
The Test runs outside a live gateway session, so it can’t forward runtime identity headers. This engine doesn’t use them, so Test reflects production behavior exactly. An engine that depends on forwarded headers would behave differently under Test.

Step 5: Attach it to a gateway rule

1

Open the Rules tab

Open your gateway from Gateways, go to its Rules tab, and click Add new rule.
2

Select your engine as the detection method

Name the rule Email redactor (custom) and pick Email redactor from the Detection method dropdown — your registered engines appear below the two built-in methods. There’s no action picker: the engine’s verdict decides what happens.
3

Set the hook and failure mode

Set the Detection hook to Response, so the rule scans tool results. Leave the Failure mode at Block (the default for custom engines) so an unreachable engine fails closed. Turn Alerts on, confirm it’s Enabled, and save.

Step 6: Trigger it and read the verdict

Make a tool call whose result contains an email. With the docs server connected:
Search the MCP Manager docs for an example email address and quote the matching text exactly.
The result comes back through the gateway, your webhook redacts the address, and the client receives [REDACTED EMAIL] in place of it. Then open Logs, find the tools/call, and open its details:
  • The entry is a policy_enforced_mutation.
  • rule_engine_type reads modify, and rule_engine_comment shows redacted email address(es) — the comment your webhook returned.
  • The Body shows the redacted text.
You wrote a webhook, exposed it over HTTPS, registered it as a custom engine, verified it with Test, attached it to a response-leg rule, and saw its modify verdict and comment in the audit trail. Your own code is now an in-line policy on the gateway — swap the email regex for any classifier, model call, or field-level logic you need.

Where to take it next

  • Return block or error. Block a result outright, or signal that you couldn’t decide and let the rule’s failure mode take over.
  • Scope to the caller. Turn on header forwarding and filter results by the requesting user’s identity.
  • Borrow a recipe. The custom rule engine examples cover slimming verbose responses, summarizing long text, and identity-scoped filtering.
  • Or skip the code. Amazon Bedrock and Lakera Guard plug in as managed engines with no webhook to host.

Further reading

Building a Custom Rule Engine

The full webhook contract — envelope, the four verdicts, types, and operational limits.

Custom Rule Engines

Registering, configuring, testing, and deleting engines in the UI.

Custom Rule Engine Examples

Five runnable recipes for the modify and block verdicts.

Trace a call in your logs

Read the rule-engine columns your verdict populated.