Skip to content

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

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.

KeyWindows registry value (type)TypeMeaningAcross sources
read_only (alias read_only_mode)ReadOnly, ReadOnlyMode (REG_DWORD, 0 or 1)boolOnly read tools are allowedLogical OR
disabled_toolsDisabledTools (REG_MULTI_SZ)list of exact tool namesTools that do not exist for any AI appUnion
disabled_servicesDisabledServices (REG_MULTI_SZ)list of service idsServices turned off and invisibleUnion
allowed_servicesAllowedServices (REG_MULTI_SZ)list of service idsEvery service that has a switch and is not listed is off, including services a future version adds. Empty list: noneIntersection
account_permissionsAccountPermissions (REG_SZ holding JSON)objectPer-account denials (read, send, write)Union of denials
account_permissions_strictAccountPermissionsStrict (REG_DWORD, 0 or 1)boolStrict mode for per-account permissionsLogical OR
disconnect_disabled_servicesDisconnectDisabledServices (REG_DWORD, 0 or 1)booltrue: the account of a service this source turns off is removed (section 6). Absent or false: nothing is removedPer 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

  1. An absent key does not restrict. A present key locks that setting for the person, whatever its value.

  2. 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.

  3. Tool names are exact. disabled_tools is never normalized.

  4. 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.

    PlatformService ids frozen by the contract
    Windowsoutlook, m365, teams, slack, onedrive, gdrive, office, explorer, tasks
    Macmail, calendar, contacts, reminders, notes, messages, finder, omnifocus, teams, onedrive, whatsapp, slack, nordvpn, servicenow, m365, stocks, todo

    The clients also govern these ids by policy: disabled_services turns them off, and allowed_services turns 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): Windows whatsapp, telegram, servicenow, evernote, zoom, obsidian, thunderbird, proton, todoist; Mac signal, telegram, zalo, gdrive, notion, zoom, obsidian, music, thunderbird, proton, weather, office, todoist, evernote.

  5. allowed_services entries are compared byte for byte: no trim, no case folding. A mistyped id is not that service. "Whatsapp" is not whatsapp and "M365 " (trailing space) is not m365, so the real service is not on the list and is turned off; with account removal on, its account is removed.

  6. 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.policy reports it as allowed_services_unrecognized: true (section 8). teams, slack, onedrive and m365 are ids of both platforms: a list written for one platform that contains one of them is a recognized list on the other.

  7. allowed_services also turns off every service a future version adds until you list it.

3. Where the policy lives

WindowsMac
MDM sourceRegistry key HKLM\Software\Policies\LocalMCPFile /Library/Application Support/Local MCP/config.json (JSON, keys in snake_case)
Who can write itAn administrator, GPO, IntuneAn administrator, an MDM script (root)
Read as policyThe key opens and every value has its type3.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 policyHKCUThe 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:

StateConditionEffect on the machine
absentThe source does not existDoes not restrict
in_forcePositively read right nowApplied. 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 knownThe earlier value is applied. That source removes no account
closedThe source exists, cannot be read, and there is no earlier valueRead-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 policyNo console restriction; the MDM source still applies; the client retries

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:

GroupWindowsMac
Screen and windowsui_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_framethe same twelve, plus screenshot_capture, screen_record_start, screen_record_stop, screen_record_status
Browserweb_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_forthe same thirteen, plus web_session_delete
Terminalrun_terminal_commandrun_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.

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 keyTypeEffect
EnrollmentCode / enrollment_codeREG_SZ / stringThe enrollment code. Sent exactly as written: a trailing space or line break is part of the code
EnrollmentRequired / enrollment_requiredREG_DWORD / boolAbsent 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_labelREG_SZ / stringAbsent: the machine reports its computer name. Non-empty: reports that string. Empty: reports no label

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 /f

AccountPermissions 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" /f

Remove 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" /f

Check 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 5

Mac

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.

FieldValues
stateabsent, in_force, kept_last_good, closed: the more severe of the two sources (a pending console counts as absent)
sources.mdm.stateabsent, in_force, closed
sources.console.stateabsent, pending, in_force, kept_last_good, closed
sources.mdm.allowed_services_unrecognized, sources.console.allowed_services_unrecognizedtrue when that source's allow-list names no service the client knows
policy_versionVersion of the applied console policy; 0 when none
device_labelWhat the machine reports, or empty
enrollment.statenone, pending, enrolled, refused
enrollment.reasonEmpty, 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_atFrom 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