Where to configure it
Admins edit the policy under Settings → MDM Configuration. Editing requires the Manage org settings capability — the MDM Configuration page is reachable by MDM-config-only admin roles, but the Enforce policy editor only renders for admins who can manage org settings. The editor has four groups — Allowed remote MCP URLs, Allowed local (STDIO) packages, Allowed built-in MCP servers, and Blocked built-in tools — and a single Save Enforce policy action. Saving writes the whole policy at once (a save replaces every dimension), so unchanged groups are preserved exactly as fetched.All source rules are fail-closed: anything Enforce cannot positively match to a Runlayer-managed source, an allowlist entry, or a permitted built-in is blocked. Widening what runs always means adding an allowlist entry, never removing a block.
Allowed remote MCP URLs
Enforce blocks remote (HTTP/S) MCP servers that do not point at your Runlayer instance. Add allowed remote MCP URLs to permit specific external endpoints. You can configure up to 50 entries. Each entry is a base URL. A tool call’s MCP URL is allowed when:- The scheme, host, and port match the entry (default ports
443/80for HTTPS / HTTP are treated the same as omitting the port). - If the entry has no path (or only
/), any path on that origin is allowed. - If the entry includes a path (for example
https://partner.example.com/mcp), the MCP URL’s path must match that prefix with a path boundary —/mcpand/mcp/streammatch;/mcpbackupdoes not.
Allowed local (STDIO) packages
Enforce also blocks local (stdio) MCP servers — those launched by a command such asnpx ... or uvx ... — unless the launched package is on your stdio package allowlist. This lets you permit specific published MCP packages without permitting arbitrary local command execution. You can configure up to 100 entries.
Local commands are trivially spoofable, so Enforce never allowlists a raw command string. Instead it extracts a canonical package identity from the command and matches that against the list. A command that does not resolve to a launcher-backed package identity is denied.
Package identity
Each entry is a canonical package identity: an ecosystem (npm or PyPI), a name, and an optional version pin.
- Ecosystem + name must match. Names are normalized before comparison (see Name normalization).
- Version is optional. An entry with no version matches any version; an entry pinned to a version matches only that version. A versionless launch command can never satisfy a pinned entry.
Recognized launchers
Only ecosystem launchers resolve to a package identity. Every other program — including raw binary paths (/usr/local/bin/server), interpreters (node build/index.js, python -m server), and unknown launchers — has no package identity and is blocked.
Which argument is the package
Enforce reads the launcher’s own flags up to the first positional argument (the command), which is the package it runs. An explicit package flag overrides that positional:- npm:
--package/-pnames the package explicitly; its value is the package spec (npx --package foo barinstallsfoo). When present before the command it wins over the positional. - uv:
--fromis the analogue (uvx --from foo barinstallsfoo, runs itsbarentry point). When present it wins over the positional.
npx <evil> --package <allowlisted> still runs <evil>).
Version pinning
Leave the version blank on an entry to allow any version.
Name normalization
Names are normalized so stored entries match what the parser produces at hook time:- npm — lowercased. The leading
@of a scoped name is part of the name, not a version separator. - PyPI — PEP 503 canonical form: lowercased, with runs of
-,_, and.collapsed to a single-. Somcp_server_fetch,MCP.Server.Fetch, andmcp-server-fetchare all the same package.
Always blocked
These never resolve to a single package identity and are always denied, regardless of the allowlist:- Arbitrary-command launchers —
npx --call/-crun an arbitrary command string with no single package identity. - Unrecognized launcher flags before the command — Enforce models each launcher’s own flags exactly. Any other flag before the command has unknown arity (it might or might not consume the next token), so Enforce cannot tell which token is the package and fails closed. Put npm config in
.npmrc/env rather than inline flags. - Shell injection — commands with hidden shell operators, subshells, or injection patterns.
- Unparseable commands — unbalanced quoting or anything the parser cannot tokenize.
- Empty commands.
Worked examples
Residual risk: name, not content
Name-based matching binds the package name, not its content. A dependency-confusion or malicious-republish attack that keeps the same name would still match an allowlist entry. Content binding (lockfile / artifact hash) is intentionally out of scope. Treat the stdio allowlist as a control over which packages are permitted, not a supply-chain integrity guarantee.Promote a discovered package (one-click)
When Detect discovers a local stdio MCP whose command resolves to a package identity, you can Add to allowlist in one click from the sighting’s detail drawer — Enforce appends the parsed identity to the stdio allowlist. This requires the Manage org settings capability. The same one-click Add to allowlist action is available for discovered remote MCP URLs.Blocked built-in tools
Beyond MCP sources, Enforce can deny built-in client tools by type. Toggle a type on to block every built-in tool of that type across supported clients:
Tool type is derived server-side from the tool name, so a client cannot bypass a block by mislabeling a tool’s type in its request. Types you do not toggle on are allowed by default.
This is part of local tool lifecycle enforcement, so it requires Full session scanning APIs and the target Hook client enabled under Settings → General. Shadow MCP source blocking (the remote-URL, stdio, and built-in dimensions) stays active without full session scanning. See Enforcement paths.
Built-in MCP servers
Some clients ship non-configurable, built-in MCP servers that never appear in any config file — for example GitHub Copilot CLI’sgithub-mcp-server, playwright, fetch, time, and computer-use. The client resolves these internally, so the hook identifies them by (client, built-in server name) rather than by URL or command.
Enforce ships a safe default allowlist of well-known built-ins per client, so these keep working out of the box:
In the Allowed built-in MCP servers group:
- Keep Use Runlayer safe defaults on (the default) to trust the built-ins above.
- Turn it off to define a custom list — seeded from the defaults so you can prune or extend it — that replaces the defaults entirely. Each row is a
(client, built-in server name)pair. - Clear the custom list to block all built-in MCP servers.
builtin_allowlist field on the Enforce policy API (PUT .../ai-watch-enforce): null uses the safe defaults, an explicit list overrides them, and [] blocks all built-in MCP servers.
A missing client or built-in server name never matches, so an unidentifiable built-in is blocked like any other unmatched source.
Related Resources
Enforce
How Enforce intercepts calls, deployment, and troubleshooting
Detect
Discover shadow servers and promote them to the allowlist
Policies
Configure access control policies for managed MCPs
Deploy AI Watch
Install the package and turn on Enforce