LMCP organization policy: format reference (3.0.421 or later)
This page is for the IT administrator who deploys LMCP on company computers. It says what a policy can do, where it lives on Windows and on Mac, how to place it with your own tooling, and how to check on a machine that it is in force.
1. What a policy does, in plain terms
A policy only restricts. No key turns a service on, installs software, sets a URL or runs a command.
What IT turns off, the person does not see. A service turned off by policy has no row in LMCP, no tools for any AI app, and cannot be connected. The person cannot turn it on or off.
IT policy prevails over the person's settings. The effective policy is the most restrictive combination of your MDM policy, your console policy and the person's own choices. No source lifts what another restricts.
A complete block is by service. A service block does not hold the screen tools, the browser tools or the terminal: those tools work on any window, page or command, and no table maps an app or a site to a service. To block them, disable them by name with
disabled_tools(section 5).OneDrive, Google Drive and Obsidian off turns off only that service's own tools. The file, Office and PDF tools keep reading the locally synced folder, because on most company computers Desktop and Documents live inside it (section 5).
Removing the connected account is opt-in, and it is decided source by source (section 6).
The activity history stays. The audit log keeps showing the calls made to a service before it was blocked: tool names and times, no content.
2. The policy keys
The policy is one JSON object. Key names are the same in every carrier; in the Windows registry they are written in PascalCase.
| Key | Windows registry value (type) | Type | Meaning | Across sources |
|---|---|---|---|---|
read_only (alias read_only_mode) | ReadOnly, ReadOnlyMode (REG_DWORD, 0 or 1) | bool | Only read tools are allowed | Logical OR |
disabled_tools | DisabledTools (REG_MULTI_SZ) | list of exact tool names | Tools that do not exist for any AI app | Union |
disabled_services | DisabledServices (REG_MULTI_SZ) | list of service ids | Services turned off and invisible | Union |
allowed_services | AllowedServices (REG_MULTI_SZ) | list of service ids | Every service that has a switch and is not listed is off, including services a future version adds. Empty list: none | Intersection |
account_permissions | AccountPermissions (REG_SZ holding JSON) | object | Per-account denials (read, send, write) | Union of denials |
account_permissions_strict | AccountPermissionsStrict (REG_DWORD, 0 or 1) | bool | Strict mode for per-account permissions | Logical OR |
disconnect_disabled_services | DisconnectDisabledServices (REG_DWORD, 0 or 1) | bool | true: the account of a service this source turns off is removed (section 6). Absent or false: nothing is removed | Per source (section 6) |
onedrive_scopes (OneDriveScopes) and audit_log (AuditLog) are reserved names. A 3.0.421 client does not enforce them: it ignores them and reports them as unsupported keys.
Shape of account_permissions:
{"<service>": {"<account_key>": {"denied": ["read", "send", "write"], "name": "<display name>"}}}Rules
An absent key does not restrict. A present key locks that setting for the person, whatever its value.
A wrong type is not ignored. A supported key whose value has another type makes the whole policy of that source unreadable (section 4 says what an unreadable source does). Mac exception in 3.0.421: in the system file a policy key of the wrong type is not read, and the rest of the file applies; the fail-closed read of that file arrives in a later release.
Tool names are exact.
disabled_toolsis never normalized.Service ids are per platform, and one policy serves both platforms by listing the ids of both. An id the platform does not know is ignored.
Platform Service ids frozen by the contract Windows outlook,m365,teams,slack,onedrive,gdrive,office,explorer,tasksMac mail,calendar,contacts,reminders,notes,messages,finder,omnifocus,teams,onedrive,whatsapp,slack,nordvpn,servicenow,m365,stocks,todoThe clients also govern these ids by policy:
disabled_servicesturns them off, andallowed_servicesturns them off when they are not listed. They are not part of the frozen list, so they do not make an allow-list "recognized" (rule 6): Windowswhatsapp,telegram,servicenow,evernote,zoom,obsidian,thunderbird,proton,todoist; Macsignal,telegram,zalo,gdrive,notion,zoom,obsidian,music,thunderbird,proton,weather,office,todoist,evernote.allowed_servicesentries are compared byte for byte: no trim, no case folding. A mistyped id is not that service."Whatsapp"is notwhatsappand"M365 "(trailing space) is notm365, so the real service is not on the list and is turned off; with account removal on, its account is removed.An allow-list that names no service the client knows gives the minimum protection. Every service is hidden and refused, as the list says, and that source removes no account at all.
/status.policyreports it asallowed_services_unrecognized: true(section 8).teams,slack,onedriveandm365are ids of both platforms: a list written for one platform that contains one of them is a recognized list on the other.allowed_servicesalso turns off every service a future version adds until you list it.
3. Where the policy lives
| Windows | Mac | |
|---|---|---|
| MDM source | Registry key HKLM\Software\Policies\LocalMCP | File /Library/Application Support/Local MCP/config.json (JSON, keys in snake_case) |
| Who can write it | An administrator, GPO, Intune | An administrator, an MDM script (root) |
| Read as policy | The key opens and every value has its type | 3.0.421: the policy keys are read with no owner check. The enrollment anchor and account removal additionally require the root rule |
| Never read as policy | HKCU | The person's own configuration |
Root rule (Mac). The file is a regular file owned by root (uid 0), not writable by group or others, on the boot volume. For account removal, and for the absence of the anchor to count, the directory that holds the file must pass the same rule.
The second carrier is the console source: a policy signed by the LMCP server and delivered to enrolled machines (section 7). The console server is not available yet in 3.0.421.
4. Sources, states and what a machine does when it cannot read a policy
Each source is in one state:
| State | Condition | Effect on the machine |
|---|---|---|
absent | The source does not exist | Does not restrict |
in_force | Positively read right now | Applied. The only state in which that source removes an account |
kept_last_good (console only) | The source exists and cannot be read; an earlier good value is known | The earlier value is applied. That source removes no account |
closed | The source exists, cannot be read, and there is no earlier value | Read-only, strict mode, every service refused. That source removes no account |
pending (console only) | The machine carries an enrollment anchor and has no verifiable console policy | No console restriction; the MDM source still applies; the client retries |
When an MDM policy and a console policy are both present there is no winner: both apply.
The MDM source reports
absent,in_forceorclosed, neverkept_last_good: on Windows an unreadable registry key isclosed; on Mac a system file that cannot be read or is not a JSON object, or a governingLMCP_CONFIGprofile that cannot be read, isclosed. A key of the wrong type is the exception of section 2, rule 2. The console source uses all its states on both platforms.
5. Blocking a service, and what a service block does not hold
Turn-off is immediate and unconditional. When policy turns off a whole service (disabled_services, or allowed_services that does not list it), its tools and its row disappear at once and every attempt to connect it is refused, with the source in_force, kept_last_good or closed.
A service that policy leaves with no available tool (through disabled_tools, read_only or per-account denials) is not drawn and cannot be connected. Its account is never removed.
Not held by a service block: screen tools, browser tools and the terminal. To block them, list them in disabled_tools by exact name:
| Group | Windows | Mac |
|---|---|---|
| Screen and windows | ui_find_element, ui_read_tree, ui_get_element, ui_wait_for_element, ui_click, ui_type, ui_keystroke, ui_menu_bar_click, list_windows, list_displays, window_focus, window_set_frame | the same twelve, plus screenshot_capture, screen_record_start, screen_record_stop, screen_record_status |
| Browser | web_session_list, web_session_close, web_login, web_navigate, web_read, web_show, web_screenshot, web_click, web_type, web_extract, web_find, web_eval, web_wait_for | the same thirteen, plus web_session_delete |
| Terminal | run_terminal_command | run_terminal_command |
read_only removes the tools of these groups that act (click, type, navigate, run a command) and keeps the ones that only read. The list above is the control.
OneDrive, Google Drive and Obsidian. Turning onedrive, gdrive or obsidian off removes only the tools of that service (onedrive_*, gdrive_*, obsidian_*). The file tools (file_*, and explorer_* on Windows, finder_* on Mac), the Office tools (word_*, excel_*, ppt_*) and pdf_read keep reading and writing the locally synced folder. To close that content as well, deny the account with account_permissions, or disable those tools by name.
Activity history. The audit log keeps the entries of a service that is blocked later, and the person can still see them: tool names and times, never content.
6. Removing the connected account
Nothing is removed unless you ask for it.
Opt-in:
disconnect_disabled_services: true(registry:DisconnectDisabledServices= 1). Absent or false: never remove. A policy written before 3.0.421 removes no account after the update.Judged per source. Each source (MDM, console) removes only the accounts of the services it turns off itself, and only when that same source carries the opt-in. The opt-in of your MDM policy never applies to a service that only the console turns off, and the reverse. The outcome never depends on the state of the other source; in doubt the account is kept.
Three conditions, in one and the same read: that source carries the opt-in; that source is
in_force; that source has been at rest for 60 seconds (MDM: the registry key or the file was last written at least 60 seconds ago; console: 60 seconds since the applied policy becamein_force). A source inkept_last_goodorclosedremoves nothing.Hiding is immediate; removal waits. The service disappears at once. The account is removed only after the 60 seconds, and one notice is shown to the person first. The notice says whether the account will be removed.
Notifications turned off. The notice is then a line in the LMCP tray menu, and the account is not removed until the person opens the tray.
WhatsApp. Removing a WhatsApp account by policy also deletes the cached chats of every WhatsApp account on the machine, including the main one. This is deleted content, not only a sign-out. The notice says so.
Mac MDM source: it removes accounts only if, in that same read, the system file and its directory pass the root rule.
A typo in
allowed_serviceswith removal on turns the real service off and removes its account (section 2, rule 5). An allow-list that names no known service removes nothing (rule 6).Services with an account to remove: Windows
m365,servicenow,todoist,thunderbird,proton,telegram,whatsapp; Mac the same andzalo. The other services hold no stored account: they are hidden and refused only.Audit. Each removed account writes one
policy_disconnectline to the audit log:origin: "policy",policy_source(mdmorconsole) andservice; never an account.
7. The enrollment anchor
A machine joins your organization's console only when an administrator places the anchor in the administrator-only store. The person cannot type a code.
| Windows registry value / Mac key | Type | Effect |
|---|---|---|
EnrollmentCode / enrollment_code | REG_SZ / string | The enrollment code. Sent exactly as written: a trailing space or line break is part of the code |
EnrollmentRequired / enrollment_required | REG_DWORD / bool | Absent or 0: with no verifiable console policy the console source is pending. 1: it is closed (the machine is read-only and every service is refused until it has a verifiable console policy) |
DeviceLabel / device_label | REG_SZ / string | Absent: the machine reports its computer name. Non-empty: reports that string. Empty: reports no label |
The code is readable by every local user and process of the anchored machine. Treat it as a deployment code, not as a secret; revoke it in the console when it leaks.
In 3.0.421 the console server is not available. An anchored machine stays
pending(reasonunreachable), orclosedwhenEnrollmentRequiredis 1. The examples below therefore show0.Mac: three read cases. Present: the file passes the root rule, is valid JSON and has
enrollment_code. Unreadable: the file fails the root rule or is not valid JSON; an existing enrollment is kept, and a machine with none reportsenrollment.reason: anchor_unreadable. Absent: no file, or a valid root-owned file without the key, and the directory is owned by root and not writable by group or others; otherwise it is unreadable.What the person sees: one notification, "This computer is managed by <org_name>", and the "managed" notice in LMCP. Enrollment asks for no consent.
Re-enrollment, code change, removal. If local state disappears while the anchor is present, the machine enrolls again with the same code. If the code changes, it leaves and enrolls with the new one. If the anchor is removed (a positive read of its absence), the machine leaves the organization at once.
What an enrolled machine reports to the console: machine id and device public key; the device label; platform, OS version, client version; version and state of the console policy; the MDM source state (one word); unsupported keys; whether the console allow-list names no known service (one boolean).
What this feature does not send: mail, file, message or calendar content; the audit log and the names of invoked tools; connected accounts, their names or addresses; the person's own settings; the content of the MDM policy; the OS user name or the person's email; an IP address in the clear.
8. Commands
Run them in an elevated prompt (Windows) or as an administrator with sudo (Mac).
Windows
Place policy values:
reg add "HKLM\Software\Policies\LocalMCP" /v ReadOnly /t REG_DWORD /d 1 /f
reg add "HKLM\Software\Policies\LocalMCP" /v DisabledServices /t REG_MULTI_SZ /d "slack\0teams" /f
reg add "HKLM\Software\Policies\LocalMCP" /v DisabledTools /t REG_MULTI_SZ /d "run_terminal_command\0web_eval" /f
reg add "HKLM\Software\Policies\LocalMCP" /v AllowedServices /t REG_MULTI_SZ /d "outlook\0m365" /f
reg add "HKLM\Software\Policies\LocalMCP" /v DisconnectDisabledServices /t REG_DWORD /d 1 /fAccountPermissions holds JSON; PowerShell avoids the quoting of reg add:
New-ItemProperty -Path 'HKLM:\Software\Policies\LocalMCP' -Name AccountPermissions -PropertyType String -Force `
-Value '{"m365":{"<account_key>":{"denied":["send"]}}}'Place the anchor:
reg add "HKLM\Software\Policies\LocalMCP" /v EnrollmentCode /t REG_SZ /d "EXAMPLE-CODE-0000" /f
reg add "HKLM\Software\Policies\LocalMCP" /v EnrollmentRequired /t REG_DWORD /d 0 /f
reg add "HKLM\Software\Policies\LocalMCP" /v DeviceLabel /t REG_SZ /d "FRONT-DESK-01" /fRemove one value, the anchor, or everything:
reg delete "HKLM\Software\Policies\LocalMCP" /v DisabledServices /f
reg delete "HKLM\Software\Policies\LocalMCP" /v EnrollmentCode /f
reg delete "HKLM\Software\Policies\LocalMCP" /fCheck what is written, and what LMCP applies:
reg query "HKLM\Software\Policies\LocalMCP"
curl.exe -s http://127.0.0.1:8765/status(Invoke-RestMethod http://127.0.0.1:8765/status).policy | ConvertTo-Json -Depth 5Mac
Write the policy to a file of your own, for example policy.json:
{
"disabled_services": ["slack", "teams"],
"disabled_tools": ["run_terminal_command", "web_eval"],
"disconnect_disabled_services": true,
"enrollment_code": "EXAMPLE-CODE-0000",
"enrollment_required": false,
"device_label": "FRONT-DESK-01"
}Place it with the ownership and permissions the root rule needs, for the directory and the file:
sudo mkdir -p "/Library/Application Support/Local MCP"
sudo chown root:wheel "/Library/Application Support/Local MCP"
sudo chmod 755 "/Library/Application Support/Local MCP"
sudo cp policy.json "/Library/Application Support/Local MCP/config.json"
sudo chown root:wheel "/Library/Application Support/Local MCP/config.json"
sudo chmod 644 "/Library/Application Support/Local MCP/config.json"Remove the anchor: place the file again without enrollment_code. Remove everything (the directory stays, owned by root, so the absence is read positively):
sudo rm "/Library/Application Support/Local MCP/config.json"Check what is written, and what LMCP applies:
stat -f '%Su:%Sg %Sp %N' "/Library/Application Support/Local MCP" "/Library/Application Support/Local MCP/config.json"
curl -s http://127.0.0.1:8765/status | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin)["policy"], indent=2))'stat must show root:wheel drwxr-xr-x for the directory and root:wheel -rw-r--r-- for the file. /status answers on the machine itself only, to any local process, without a token.
Reading /status.policy
The answer carries words, dates, counts and two booleans: never a tool name, a service id or an account.
| Field | Values |
|---|---|
state | absent, in_force, kept_last_good, closed: the more severe of the two sources (a pending console counts as absent) |
sources.mdm.state | absent, in_force, closed |
sources.console.state | absent, pending, in_force, kept_last_good, closed |
sources.mdm.allowed_services_unrecognized, sources.console.allowed_services_unrecognized | true when that source's allow-list names no service the client knows |
policy_version | Version of the applied console policy; 0 when none |
device_label | What the machine reports, or empty |
enrollment.state | none, pending, enrolled, refused |
enrollment.reason | Empty, or one of unreachable, invalid_response, code_rejected, seat_limit, machine_removed, org_suspended, anchor_unreadable (Mac only) |
enrollment.org_name, enrollment.enrolled_at, enrollment.last_sync_at | From the signed enrollment; empty when not enrolled |
The answer also carries, per key, whether the setting comes from policy, the person, both or none, and counts.
MDM policy in force, no anchor (the healthy answer after the policy commands above):
{
"state": "in_force",
"sources": {
"mdm": {"state": "in_force", "allowed_services_unrecognized": false},
"console": {"state": "absent", "allowed_services_unrecognized": false}
},
"policy_version": 0,
"device_label": "",
"enrollment": {"state": "none", "org_name": "", "enrolled_at": "", "last_sync_at": "", "reason": ""}
}Anchor placed with EnrollmentRequired 0 (3.0.421: there is no console server yet):
{
"state": "in_force",
"sources": {
"mdm": {"state": "in_force", "allowed_services_unrecognized": false},
"console": {"state": "pending", "allowed_services_unrecognized": false}
},
"policy_version": 0,
"device_label": "FRONT-DESK-01",
"enrollment": {"state": "pending", "org_name": "", "enrolled_at": "", "last_sync_at": "", "reason": "unreachable"}
}Anchor placed with EnrollmentRequired 1: the same answer with "state": "closed" and "console": {"state": "closed", …}. The machine is read-only and every service is refused.
9. Things to know about updates and the cloud relay
Updating from 3.0.420 or earlier: Microsoft 365 asks for consent again. 3.0.421 added the permission
Mail.ReadWriteto the Microsoft 365 connection. Every connected account consents again after the update; in a tenant where an administrator grants consent, IT is asked again.Declared limit of the cloud relay. When a web AI app reaches a computer through the LMCP cloud relay, the browser tools open public pages only. A page opened that way can still, by itself, open connections (a request, a WebSocket) to local or private addresses; that is not closed. To remove the exposure, disable the browser tools by name (section 5). On Windows and on Mac the screen tools and the terminal can also be reached through the relay: to keep a tool away from web AI apps, disable it by name with
disabled_tools.A machine reports its effective
disabled_toolsto the relay, which hides those tools and refuses their calls before they reach the machine, for the first 2,000 names of the list.