ghl-mcp-scoped
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ghl-mcp-scopedShow me which GoHighLevel tools are allowed under my content-only policy."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ghl-mcp-scoped
A least-privilege policy wrapper that sits in front of any GoHighLevel MCP server: you declare which tools an AI agent may call and under what conditions, and the wrapper enforces it, logs every call, and refuses the rest.
$ ghl-mcp-scoped run --policy policies/content-only.yaml -- <your ghl mcp server>
agent --> tools/list
wrapper <-- 4 tools visible: blogs_create-blog-post, blogs_get-blogs, social-media-posting_create-post, locations_get-location
agent --> tools/call {"name": "blogs_create-blog-post", "arguments": {"locationId": "loc_EXAMPLE_CLIENT_A", "title": "5 signs your furnace is done"}}
wrapper <-- OK fake-ghl executed blogs_create-blog-post
agent --> tools/call {"name": "contacts_upsert-contact", "arguments": {"locationId": "loc_EXAMPLE_CLIENT_A", "email": "lead@example.test"}}
wrapper <-- REFUSED ghl-mcp-scoped: 'contacts_upsert-contact' was blocked by policy rule 'no-crm'. a content agent has no business reading or writing the CRM
rule: no-crm
agent --> tools/call {"name": "social-media-posting_create-post", "arguments": {"locationId": "loc_SOMEONE_ELSES", "summary": "..."}}
wrapper <-- REFUSED ghl-mcp-scoped: 'social-media-posting_create-post' was blocked by policy rule 'social-create-one-location'. rule 'social-create-one-location' allows this tool only when its argument constraints hold; 'locationId'='loc_SOMEONE_ELSES' is not in the permitted set
rule: social-create-one-locationThat is real output. Reproduce it with python examples/demo_session.py, which runs the wrapper in front of the fake MCP server bundled in tests/.
Quick start
Install it next to whatever MCP client you use:
pip install ghl-mcp-scoped # or: pip install -e . from a cloneCopy a policy and edit the location ids in it:
cp policies/read-only.yaml my-policy.yamlCheck it, and see exactly what your agent will be able to do:
ghl-mcp-scoped validate my-policy.yamlPut the wrapper in front of your existing server in
.mcp.json. Before:{ "mcpServers": { "ghl": { "command": "npx", "args": ["-y", "some-ghl-mcp-server"], "env": { "GHL_API_KEY": "..." } } } }After:
{ "mcpServers": { "ghl": { "command": "ghl-mcp-scoped", "args": [ "run", "--policy", "my-policy.yaml", "--", "npx", "-y", "some-ghl-mcp-server" ], "env": { "GHL_API_KEY": "..." } } } }The wrapped server still gets the same environment, the same stdio, the same everything. The only change is that
tools/callnow has to get past your policy first.Restart the client and read the audit log at the path your policy names.
Related MCP server: SentinelGate
The problem
MCP servers for GoHighLevel exist in numbers, and the working assumption across them is all-or-nothing: you hand the server an API key or a private integration token for a location, and every tool it implements becomes callable by whatever model is driving. Several state plainly that they give the agent full access to the location. That is a reasonable default for a developer poking at their own sandbox. It is not a reasonable default for an agency running an agent against a client's CRM, where the same credential reaches contacts, conversations, opportunities, calendars and payments.
The gap is not authentication - that part works. The gap is that there is no way to say "this agent may draft blog posts for this one location and nothing else" without forking the server. So the choice on offer is: give the agent everything, or don't use it. This wrapper is the third option, and it works with a server you did not write and cannot modify.
Note the ordinary failure modes, not just the dramatic ones. An agent that is merely confused deletes tags, moves an opportunity to Won, or messages a real contact at 3am. A scope is cheaper than an incident review.
Policy reference
A policy is one YAML file. Everything below is the whole language; there is no expression syntax and nothing in a policy is ever executed.
Top level
Key | Type | Default | Meaning |
| int |
| Policy schema version. Only |
| string | - | Free text, shown by |
| string | - | Free text, shown by |
|
|
| What happens to a tool no rule matches. |
| bool |
| A hard cap: a tool whose name carries no read verb is denied before any rule is consulted, even a rule that allows it. |
| list of strings |
| The verbs that make a tool name count as a read. Matched against name tokens, so |
| list of rules |
| The rules, evaluated in file order. |
| mapping | see below | Audit log settings. |
| mapping | see below | What |
A rule
Key | Type | Default | Meaning |
| string | required | Tool name, or a glob: |
|
|
| What to do when the name matches. |
| list of constraints |
| Argument-level conditions. Only valid on an |
|
|
| What happens when |
| string | - | Text shown to the agent in the refusal and written to the audit log. Write these; they are what the agent reads. |
| string | auto | Short name for the rule, used in refusals and audit lines. |
The first rule whose match fits the tool name decides. A later rule for the same name never fires, and validate rejects the policy if an earlier rule already handles that exact pattern unconditionally.
confirm refuses the call and tells the caller a human has to run it. It does not prompt: stdio is occupied by the protocol, and there is nowhere to ask.
- id: human-sends-messages
match: "conversations_send-a-new-message"
action: confirm
reason: an outbound SMS reaches a real person and cannot be recalledArgument constraints (when)
Each entry names an argument path and exactly one matcher.
Matcher | Example | Passes when |
|
| The value equals the literal (types included: |
|
| The value is one of the listed items. |
|
|
|
|
| The path exists and is not null. |
|
| The path is absent or null. |
Extra key: optional: true makes an absent path pass for equals / in / matches (it means "if it is there it must look like this"). Without it, an absent path fails those three.
Paths are dotted and walk both objects and arrays: locationId, contact.locationId, tags.0. An unresolvable path is treated as absent, never as an error.
- id: upsert-scoped
match: "contacts_upsert-contact"
action: allow
when:
- arg: locationId
in: ["loc_EXAMPLE_CLIENT_A", "loc_EXAMPLE_CLIENT_B"]
- arg: dndSettings
forbidden: true
otherwise: deny
reason: upserts are fine inside this engagement's own locationsvisibility
Key | Default | Meaning |
|
| Denied tools are stripped from |
|
| So are |
A tool that is allowed conditionally stays listed: the arguments do not exist yet at list time, so the wrapper cannot know, and hiding it would be a lie in the other direction. It is judged at call time.
audit
Key | Default | Meaning |
|
| Turn logging off. |
|
| Log file. A relative path resolves against the policy file's directory. Directories are created as needed. |
|
| When false, arguments are replaced wholesale by the placeholder. |
|
| Regex, case-insensitive, matched against key names. |
|
| What a redacted value becomes. |
|
| Longer strings are truncated with |
CLI
ghl-mcp-scoped run --policy POLICY [--audit-log PATH] [--verbose] -- <server command...>
ghl-mcp-scoped validate POLICY [--tools tools.json|tools.txt]validate prints the rule table, the mode default, any warnings, and - with --tools, given a tools/list dump or a plain list of names - the effective decision for every tool your server actually exposes. It exits 2 on an invalid policy, so it belongs in CI.
The three shipped policies
Policy | Use it when | Shape |
| The agent answers questions and writes nothing: reporting, lookups, drafting copy from real data. The safest thing to hand a new agent. |
|
| A content agent publishes blogs and schedules social posts for one location and must not touch the CRM. |
|
| An operator is driving the agent live and wants the full toolset with a paper trail and a few hard stops. Not for an unattended loop. |
|
Every id in them (loc_EXAMPLE_CLIENT_A, and so on) is a placeholder. Replace them before use.
Audit log
One JSON object per line, appended, flushed on every write. Real lines from the transcript above:
{"ts": "2026-09-01T10:07:05.464Z", "event": "tools/list", "pid": 10644, "decision": "filtered", "rule": "visibility", "reason": "4 tool(s) visible, 8 hidden", "visible": ["blogs_create-blog-post", "blogs_get-blogs", "social-media-posting_create-post", "locations_get-location"], "hidden": ["contacts_get-contacts", "contacts_get-contact", "contacts_upsert-contact", "contacts_add-tags", "conversations_send-a-new-message", "conversations_search-conversation", "opportunities_update-opportunity", "payments_list-transactions"]}
{"ts": "2026-09-01T10:07:05.464Z", "event": "tools/call", "pid": 10644, "tool": "contacts_upsert-contact", "decision": "deny", "rule": "no-crm", "reason": "a content agent has no business reading or writing the CRM", "arguments": {"locationId": "loc_EXAMPLE_CLIENT_A", "email": "[REDACTED]"}, "forwarded": false}Fields: ts (UTC, ISO 8601), event (session_start, tools/call, tools/list, session_end), pid, tool, decision (allow / deny / confirm / filtered), rule, reason, arguments, forwarded.
Redaction is structural, by key name. Any key matching the redaction regex has its whole value replaced, at any depth, whatever its type; values are never inspected or guessed at. That is why email is redacted above while locationId is not. Credentials passed as arguments are removed by the same mechanism, and the wrapper never reads or logs the environment variables your GHL server authenticates with.
The log is still sensitive: it holds the arguments your agent used. It is gitignored here, and it should be treated like any other client record. Turn log_arguments: false off if you only need the decision trail.
Limitations
Read these before you rely on it.
It is a policy layer, not authentication. It does not verify who is calling, and it holds no credentials of its own.
Anyone who can edit the policy file can rewrite the rules. Put it under the same access control as your other production config, and check it into version control so changes are reviewable.
Anyone who can reach the wrapped server directly bypasses the wrapper entirely. It only constrains traffic that goes through it. If the same API key is also in another
.mcp.jsonentry, or in a shell script, or in an n8n node, none of that is scoped.Argument matching is structural, not semantic.
locationId in [...]proves a string matched a list. It does not know whether the body of a message is abusive, whether a blog post is defamatory, or whether a "test" contact is a real person. A policy constrains reach, not judgment.A wildcard
allowindenylistmode inherits future tools. If the wrapped server adds a tool in its next release,denylistmode allows it silently.validatewarns about this;allowlistdoes not have the problem.Newline-delimited JSON-RPC over stdio only. That is what MCP stdio servers speak. HTTP and SSE transports are not wrapped.
Tool names are the unit of control. If a server hides several operations behind one generic tool, the policy can only allow or deny the whole thing, plus whatever the arguments let you pin down.
No rate limiting, no spend caps, no time windows. Out of scope for v1.
Contributing
Issues and pull requests welcome, particularly: policies for real-world agency setups, matcher gaps found in practice, and compatibility reports against specific GHL MCP servers (name the server and the tool names, never a key or a location id).
Run the suite before you open a PR:
python -m pytest tests -qThe tests spawn a real wrapper process in front of a fake MCP server (tests/fake_ghl_server.py) and speak JSON-RPC to it over a pipe. There is no network access and no GoHighLevel credential anywhere in this repo, and there must not be one in a contribution either.
Built by SkynetLabs (Waseem Nasir) - we wire GoHighLevel and AI agents for agencies. https://skynetjoe.com ยท https://calendly.com/skynetlabs/schedule-a-free-consultation
GoHighLevel is a trademark of its respective owner. This is an independent, unaffiliated tool and is not endorsed by, sponsored by, or connected with GoHighLevel.
License
MIT. See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Security & DLP proxy for MCP: tool-poisoning scans, PII redaction on tool args/results. Beta.
AgentGuard โ 20-tool AI safety MCP: policy preflight, risk scoring, audit logging, rate limits.
MCP enforcement layer that intercepts AI agent actions and blocks rule violations before execution.
Related MCP Servers
- AlicenseBqualityCmaintenanceSecurity gateway that wraps any MCP server with per-tool policies, approval gates, and optional Ed25519-signed decision receipts. Shadow mode logs every tool call without blocking; enforce mode applies block, rate-limit, and minimum-tier rules. Receipts are independently verifiable offline with no accounts needed.569310MIT

SentinelGateofficial
AlicenseNot gradedqualityAmaintenanceOpen-source MCP proxy that enforces security policies, content scanning, and audit logging between AI agents and tool servers25AGPL 3.0- AlicenseNot gradedqualityBmaintenanceMCP server for AI agent security guardrails. Provides input validation, prompt injection detection, PII redaction, output filtering, policy enforcement, rate limiting, and comprehensive audit logging.761MIT
- AlicenseNot gradedqualityCmaintenanceRuntime proxy that intercepts and blocks MCP tool calls based on YAML-defined policies, enforcing security rules for AI agents like Claude Code or Cursor.711Apache 2.0
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/waseemnasir2k26/ghl-mcp-scoped'
If you have feedback or need assistance with the MCP directory API, please join our Discord server