← Back to Blog

ODR: Internals of Microsoft's New Native MCP Registration

2026-03-17 · Research Team

Introduction

As computer use agents become increasingly prevalent, operating system vendors are seeking to provide native infrastructure for managing how these agents interact with the system. Model Context Protocol or MCP, has emerged as the dominant standard for connecting agents to external tooling and data sources. Recognizing this, Microsoft has released a public preview of native MCP support in Windows through the Dev and Beta Insider Preview Channels, introducing what they call the On-Device Registry (ODR).

But what actually powers these new features? While Microsoft has published documentation on how to register and use MCP server through ODR, the internal mechanisms: how clients are validated, how consent is managed, and how the system enforces access control, remain largely undocumented. Through reverse engineering of Odr.exe and its associated libraries, the blog post examines the internals of ODR's MCP infrastructure. We document the new, undocumented COM interfaces (IMcpAccessManagerStatics and IMcpConsentManagerStatics) that govern client and server access, this SQL-backed consent database that persists user decisions, and the ETW telemetry that audits every interaction between MCP clients and servers.

MCP on Windows Overview

As stated by Microsoft, the "On-Device registry", or ODR, is what powers the new MCP functionality on Windows. At its core, an MCP server is really nothing more than an app server communicating with the agent over a well-defined protocol. ODR allows Microsoft to manage registered MCP servers and to contain them to only a specific set of system resources, users, etc. with the ability to prompt users for access to additional system resources when necessary.

ODR simply acts as an "aggregator" of all MCP servers which are registered with Windows through this new functionality. At this current time, however, MCP servers do not have to be registered in this manner. From the Microsoft documentation:

Directly installed MCP bundles can't run in the securely contained agent process and will not be accessible from the Windows on-device agent registry...

ODR provides a clean interface to register an MCP server with Windows, while allowing developers to gain the security benefits:

  1. Having the OS manage all the MCP infrastructure - with the added benefit of the ability to easily enumerate a list of all registered MCP servers on the machine
  2. Granular permissions for MCP servers
  3. The ability to use native Windows telemetry generation sources, like Event Tracing for Windows (ETW), to log interactions between clients and servers

To gain the full benefits of using ODR, the target MCP infrastructure needs to be packaged as an MSIX, which allows all the MCP servers to be run in a contained environment in context of a separate agent user account.

Odr.exe

Odr.exe ships with the latest, at the time of this blog post, Dev and Beta Insider Preview channels. However, all the functionality which ODR leverages must be enabled through the Experimental agentic features setting in System > AI components.

Experimental AI feature settings

This toggle enables the various features, gated by numerous feature flags, on the operating system. Odr.exe is accessible in the %PATH% environmental variable on Windows, and is accessible as a command line tool. The full path to Odr.exe is C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy. However, C:\Windows\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\ShellMcpServers\Assets contains all the various default/built-in MCP server JSON configuration files (MCPB) demonstrated by the Microsoft documentation. This does not include the MCP servers explicitly registered with ODR.

MCP server manifests

Using Odr.exe mcp list, ODR is capable of enumerating all the registered MCP servers, along with their associated manifests.

C:\Users\ANON\Desktop>odr mcp list
{
  "servers": [
    {
      "description": "https://test.com/",
      "name": "com.test/server",
      "remotes": [
        {
          "headers": [],
          "type": "http",
          "url": "https://test.com/"
        }
      ],
      "version": ""
    },
    {
      "_meta": {
        "io.modelcontextprotocol.registry/publisher-provided": {
          "com.microsoft.windows": {
            "__dirname": "C:\\Windows\\SystemApps\\MicrosoftWindows.Client.Core_cw5n1h2txyewy",
            "manifest": {
              "manifest_version": "0.3",
              "name": "file-mcp-server",
              "display_name": "File Explorer",
              "version": "1.1.0",
              "description": "This agent connector helps manage and organize your files",
              "author": {
                "name": "Microsoft Windows",
                "url": "https://aka.ms/MicrosoftWindows"
              },
              "icon": "Icons/MCPBIcons/FilesConnector.png",
              "server": {
                "type": "binary",
                "entry_point": "C:\\windows\\system32\\shellhost.exe",
                "mcp_config": {
                  "command": "odr.exe",
                  "args": [
                    "mcp",
                    "--proxy",
                    "MicrosoftWindows.Client.Core_cw5n1h2txyewy_com.microsoft.windows.ai.mcpServer_file-mcp-server"
                  ]
                }
              },
              "tools_generated": false,
              "license": "MIT",
              "_meta": {
                "com.microsoft.windows": {
                  "package_family_name": "MicrosoftWindows.Client.Core_cw5n1h2txyewy",
                  "static_responses": {
                    "tools/list": {
                      "tools": [
                        {
                          "name": "get_file_details",
                          "description": "Get file details such as name, extension, size, creation time.",
                          "inputSchema": {
                            "type": "object",
                            "properties": {
                              "path": {
                                "description": "Path to the file",
                                "type": "string"
                              }
                            },
                            "required": [
                              "path"
                            ]
                          },

    <--- TRUNCATED FOR SPACE --->

                        }
                      ]
                    },
                  }
                }
              },
              "localization": {
                "resources": "ShellMcpServers/Resources/${locale}/FileMcpServer.json",
                "default_locale": "en-US"
              }
            }
          }
        }
      },
      "description": "This agent connector helps manage and organize your files",
      "name": "MicrosoftWindows.Client.Core_cw5n1h2txyewy/file-mcp-server",
      "packages": [
        {
          "identifier": "MicrosoftWindows.Client.Core_cw5n1h2txyewy_com.microsoft.windows.ai.mcpServer_file-mcp-server",
          "registryType": "on_device",
          "runtimeHint": "odr.exe",
          "transport": {
            "type": "stdio"
          }
        }
      ],
      "version": "1.1.0"
    }
  ]
}

Since we know the location of these manifests, it is trivial for us to aggregate them. However, because ODR relies on package identities for MCP server registration, we can leverage WinRT (an abstraction over COM) to do this for us. WinRT is the implementation Microsoft has for most AI-related code infrastructure, the Insider Preview SDK has many (currently) unimplemented WinRT namespaces, as the DLL in which this code is packaged, Windows.AI.Agents.dll, has not yet shipped on Windows. However, reverse engineering ODR and its associated infrastructure shows that the current COM components, which we will see later in this blog post, are instrumented via WinRT and are not present in the latest Insider Preview SDK (as these interfaces are for internal usage).

To programmatically enumerate a list of registered MCP servers, one simply needs to reference the documentation for how to register a packaged MCP server via ODR. This documentation states the MCP server must add an application extension in the application's manifest. An application extension, or AppExtension, is used to identify a group of applications that share resources. In this case, the app extension is named com.microsoft.windows.ai.mcpServer. All the registered MCP servers will therefore specify this app extension in the application's manifest, allowing ODR a very convenient way to enumerate a list of MCP servers. We can easily achieve the same thing programmatically by using WinRT to display all current applications which have this app extension as part of their packaged app's manifest.

static
IAsyncAction
EnumerateMcpServersAsync ()
{
    winrt::hresult hr;
    AppExtensionCatalog catalog;
    IVectorView<AppExtension> mcp_vector;

    hr = S_OK;
    catalog = nullptr;
    mcp_vector = nullptr;

    //
    // Open the app extension for MCP servers
    //
    try
    {
        catalog = AppExtensionCatalog::Open(L"com.microsoft.windows.ai.mcpServer");
    }
    catch (winrt::hresult_error const& ex)
    {
        winrt::hresult hr = ex.code();
        goto Exit;
    }

    //
    // Enumerate the MCP servers
    //
    try
    {
        mcp_vector = co_await catalog.FindAllAsync();
    }
    catch (winrt::hresult_error const& ex)
    {
        winrt::hresult hr = ex.code();
        goto Exit;
    }

    if (mcp_vector.Size() == 0)
    {
		wprintf(L"[-] No MCP servers found!\n");
        goto Exit;
	}

	wprintf(L"[+] Enumerating MCP servers...\n");
    wprintf(L"[+] Found %d MCP servers installed!\n", mcp_vector.Size());

    for (const auto& i : mcp_vector)
    {
		wprintf(L"  [*] MCP server: %s\n", i.DisplayName().c_str());
    }

    wprintf(L"\n");

Exit:
    if (FAILED(hr))
    {
        wprintf(L"[-] Error! (HRESULT: 0x%lx)\n", hr);
    }

    co_return;
}

Running the above WinRT code results in a listing of all MSIX packaged MCP servers registered with ODR - without requiring any undocumented COM interfaces. At the time of this blog post 11 servers are shipped with Windows, with the 12th being Origin's test server used for this post.

C:\Users\ANON\Desktop> .\McpServerEnumeration.exe
[+] Enumerating MCP servers...
[+] Found 12 MCP servers installed!
  [*] MCP server: AppLaunchMcpServer
  [*] MCP server: FilesAgentMcpServer
  [*] MCP server: SystemInfoMcpServer
  [*] MCP server: WindowingMcpServer
  [*] MCP server: SettingsMcpServer
  [*] MCP server: AppInfoMcpServer
  [*] MCP server: FilesConnector
  [*] MCP server: FileSearchMcpServer
  [*] MCP server: WindowUnderstandingMcpServer
  [*] MCP server: TroubleshootingMcpServer
  [*] MCP server: MagnifierMcpServer
  [*] MCP server: Sample MCP server

Enumerating MCP servers, however, is only a portion of the functionality we are interested in. In addition, we are also interested in how ODR actually works plus how ODR manages MCP clients, servers, permissions, system resources, and how ODR can be interacted with programmatically without Odr.exe.

ODR Basic Architecture

With the addition of ODR, MCP clients and servers no longer "directly" talk to each other. Given that MCP servers which are registered with ODR are now in an isolated user session, clients must go through what Microsoft refers to as an MCP proxy. This proxy provides the opportunity for Windows to enforce the aforementioned features we have talked about, including auditing and logging. The MCP proxy in this case is ODR itself! This is why MCP servers need to register with ODR.

https://blogs.windows.com/windowsdeveloper/2025/11/18/ignite-2025-furthering-windows-as-the-premier-platform-for-developers-governed-by-security/

Once the MCP server is registered with ODR (for example, when the packaged MSIX is installed), ODR becomes the "bridge" between clients and servers - brokering and managing all requests. The way this works is by ODR's transforming of an MCP's JSON MCPB file. Upon registration, ODR will manipulate the JSON file's mcp_config and update it with a specific ODR command. For example, we registered a sample MCP server using ODR. The mcp_config was updated, by ODR, to use a command line of odr mcp --proxy, which specifies the package name of our sample MCP server. This allows ODR to proxy interaction with the registered MCP server.

"server": {
    "type": "binary",
    "entry_point": "SampleMCPServer.McpServer.exe",
    "mcp_config": {
        "command": "odr.exe",
        "args": [
        "mcp",
        "--proxy",
        "9f3a67cd-72e7-4e02-8769-3015e4c43f3f_czkckqegssqse_com.microsoft.windows.ai.mcpServer_MCP-Server-Sample-App"
        ]
    }
},
"tools": [
    {
        "name": "get_random_fact",
        "description": "Gets a random interesting fact from an online API."
    },
    {
        "name": "get_random_quote",
        "description": "Gets a random inspirational quote from an online API."
    },
    {
        "name": "get_weather",
        "description": "Gets current weather information for a specified city."
    }
],

In this case, since the MCP server registered is not built-in to Windows, the binaries themselves are located in C:\Program Files\WindowsApps\ - specifically C:\Program Files\WindowsApps\9f3a67cd-72e7-4e02-8769-3015e4c43f3f_1.0.11.0_x64__czkckqegssqse in our case. When ODR runs the MCP server via the command line arguments above, Odr.exe runs under the user which invoked Odr.exe, but the MCP server itself runs under a new user's context.

ODR new user context

This user is not a "special" user in the sense that it is any different from any other user account. The benefit, however, of using a dedicated user is that all the code which runs in the MCP server is in a separate user session (which is how all of the users files are isolated, and explicit permission is required to access the "real" user's files).

This is the basic architecture of ODR: MCP servers are registered with Windows and Windows brokers the communication while also isolating the MCP server in its own dedicated user-context. More interestingly, however, is how Windows manages MCP server access through a new, undocumented, WinRT interface.

ODR Internals

Odr.exe is a simple application that, by itself, really does "nothing". Odr.exe simply loads a new DLL shipped with Windows, OSClient.NET.dll (this appears to be a .NET binary compiled as a Native AOT project), resolves the exported function OdrMain and forwards the command line arguments passed in to Odr.exe.

OdrMain

OSClient.NET provides the main functionality of ODR, centred around the concept of client identities. Because ODR brokers the communication between MCP clients and servers, the client identity is the same as the MCP client. Clients now invoke ODR to establish the proxy, instead of the MCP server directly. Thus, ODR begins any MCP server communication process by validating the client. WinRT is being heavily leveraged here, and a client is represented by a ClientIdentity C++ class that is part of the ClientIdentityUtils namespace. The constructor for this class is called per-client and is responsible for getting information such as:

  • The client's PID
  • The client's parent process
  • A hardcoded list of invalid clients (CMD, PowerShell, Explorer, and WinLogon)
  • A hardcoded list of test processes (not applicable for our purposes)

Further on the constructor performs more "intensive" work - via WMSSImpl_ClientIdentityUtils_ClientIdentity_EvaluateCandidateClientProcess. This performs additional checks to see if this is an explicitly-disallowed client (invalid clients list). Other actions include extracting information such as:

  • The client's certificate information
  • The display name of the client (product name, file description, company name, version info, etc.)
  • The app package information (if applicable, via GetPackageFamilyName, GetPackageFullName, and GetApplicationUserModelId)

This provides the object which represents the client process with all of the necessary information needed for future access checks.

0:007> dx @$curprocess.Name
@$curprocess.Name : Odr.exe
    Length           : 0x7

0:007> k
 # Child-SP          RetAddr               Call Site
00 00000099`3abff568 00007ff8`ac9bcec9     OSClient_NET!WMSSImpl_ClientIdentityUtils_ClientIdentity__EvaluateCandidateClientProcess
01 00000099`3abff570 00007ff8`acab3ee1     OSClient_NET!WMSSImpl_ClientIdentityUtils_ClientIdentity__EnumerateParentProcesses+0x49
02 00000099`3abff5c0 00007ff8`ad07b040     OSClient_NET!WMSSImpl_ClientIdentityUtils_ClientIdentity___c____cctor_b__42_0+0x41
03 00000099`3abff600 00007ff8`ad07b11b     OSClient_NET!S_P_CoreLib_System_Lazy_1<System___Canon>__ViaFactory+0x30
04 00000099`3abff640 00007ff8`ad07b25e     OSClient_NET!S_P_CoreLib_System_Lazy_1<System___Canon>__ExecutionAndPublication+0x4b
05 00000099`3abff6a0 00007ff8`ac4d32de     OSClient_NET!S_P_CoreLib_System_Lazy_1<System___Canon>__CreateValue+0x4e
06 00000099`3abff6f0 00007ff8`ad1fcb64     OSClient_NET!Odr_WMSS_Program__TryRegisterClientFromIdentityAsync_d__66__MoveNext+0xce
07 00000099`3abff740 00007ff8`ac4ced53     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncMethodBuilderCore__Start<Odr_WMSS_Program__TryRegisterClientFromIdentityAsync_d__66>+0x64
08 00000099`3abff7a0 00007ff8`ad07d303     OSClient_NET!Odr_WMSS_Program__TryRegisterClientFromIdentityAsync+0x33
09 00000099`3abff7f0 00007ff8`ac779c04     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task_1<System___Canon>__InnerInvoke+0x73
0a 00000099`3abff830 00007ff8`ac785b8a     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunFromThreadPoolDispatchLoop+0x44
0b 00000099`3abff880 00007ff8`ac77f8b7     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__ExecuteWithThreadLocal+0x6a
0c 00000099`3abff8d0 00007ff8`ac783777     OSClient_NET!S_P_CoreLib_System_Threading_ThreadPoolWorkQueue__Dispatch+0x237
0d 00000099`3abff970 00007ff9`3dcb5210     OSClient_NET!S_P_CoreLib_System_Threading_WindowsThreadPool__DispatchCallback+0x67
0e 00000099`3abff9c0 00007ff9`3dcb6261     ntdll!TppWorkpExecuteCallback+0x4d0
0f 00000099`3abffb20 00007ff9`3bc5e8d7     ntdll!TppWorkerThread+0x801
10 00000099`3abffe80 00007ff9`3dccc3dc     KERNEL32!BaseThreadInitThunk+0x17
11 00000099`3abffeb0 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

Once ODR has established a client context, but before actually doing anything related to the MCP server, two policy checks happen. The first is related to if MCP servers are allowed to execute in the manner they are being requested and, then if the user which is requesting access to the MCP server is allowed access. These policies are evaluated using an ODR-internal GUID which represents a given MCP server. For example, the test MCP registered for the purposes of this blog post has a GUID of 7aa01d1d-6eae-4b6b-a509-08c8cfe759f8.

ODR maintains internal state for each MCP server, with the GUID ID being one of the items. GetExecutionEnvironmentForServerEntry, in the McpExecutionEnvironmentPolicy class, is responsible for getting the execution environment for the server, which includes evaluating the execution policy associated with the target MCP server. MCP servers in ODR are represented by the OSClient_NET!WMSSImpl_OnDeviceMcpRegistry_Models_McpServerEntry object and have class items including the server ID, source, last accessed time, definition information. Once the correct server is located by GUID server ID, it then undergoes the policy evaluation.

0:006> dx @$curprocess.Name
@$curprocess.Name : Odr.exe
    Length           : 0x7
0:006> k
 # Child-SP          RetAddr               Call Site
00 00000099`3aafef08 00007ff8`acad5210     OSClient_NET!WMSSImpl_WMSS_Utils_McpExecutionEnvironmentPolicy__GetExecutionEnvironmentForServerEntry
01 00000099`3aafef10 00007ff8`ac779aa5     OSClient_NET!WMSSImpl_WMSS_Services_ProxyService__RunProxyAsync_d__19__MoveNext+0x240
02 00000099`3aafefa0 00007ff8`acdf8740     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
03 00000099`3aaff010 00007ff8`ac789459     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<S_P_CoreLib_System_Threading_Tasks_VoidTaskResult__WMSSImpl_WMSS_Services_ProxyService__RunProxyAsync_d__19>__MoveNext_0+0x40
04 00000099`3aaff050 00007ff8`ac786b69     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
05 00000099`3aaff0b0 00007ff8`acc87deb     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
06 00000099`3aaff120 00007ff8`acabf924     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task_1<ModelContextProtocol_Core_ModelContextProtocol_SynchronizationExtensions_Releaser>__TrySetResult+0x8b
07 00000099`3aaff170 00007ff8`ac779aa5     OSClient_NET!WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServerAsync_d__9__MoveNext+0x1a4
08 00000099`3aaff1d0 00007ff8`ad11c813     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
09 00000099`3aaff240 00007ff8`ac789459     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServerAsync_d__9>__MoveNext_0+0x53
0a 00000099`3aaff290 00007ff8`ac786b69     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
0b 00000099`3aaff2f0 00007ff8`acc87deb     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
0c 00000099`3aaff360 00007ff8`acabfe87     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task_1<ModelContextProtocol_Core_ModelContextProtocol_SynchronizationExtensions_Releaser>__TrySetResult+0x8b
0d 00000099`3aaff3b0 00007ff8`ac779aa5     OSClient_NET!WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServersAsync_d__7__MoveNext+0x487
0e 00000099`3aaff470 00007ff8`ad11c953     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
0f 00000099`3aaff4e0 00007ff8`ac789459     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServersAsync_d__7>__MoveNext_0+0x53
10 00000099`3aaff530 00007ff8`ac786b69     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
11 00000099`3aaff590 00007ff8`acc87deb     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
12 00000099`3aaff600 00007ff8`acad013b     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task_1<ModelContextProtocol_Core_ModelContextProtocol_SynchronizationExtensions_Releaser>__TrySetResult+0x8b
13 00000099`3aaff650 00007ff8`ac779aa5     OSClient_NET!WMSSImpl_WMSS_Catalogs_MsixCatalogBase__GetServersAsync_d__24__MoveNext+0x1db
14 00000099`3aaff6e0 00007ff8`ad11dcf3     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
15 00000099`3aaff750 00007ff8`ac789459     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_WMSS_Catalogs_MsixCatalogBase__GetServersAsync_d__24>__MoveNext_0+0x53
16 00000099`3aaff7a0 00007ff8`ac786b69     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
17 00000099`3aaff800 00007ff8`ac785bf4     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
18 00000099`3aaff870 00007ff8`ac77f8b7     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__ExecuteWithThreadLocal+0xd4
19 00000099`3aaff8c0 00007ff8`ac783777     OSClient_NET!S_P_CoreLib_System_Threading_ThreadPoolWorkQueue__Dispatch+0x237
1a 00000099`3aaff960 00007ff9`3dcb5210     OSClient_NET!S_P_CoreLib_System_Threading_WindowsThreadPool__DispatchCallback+0x67
1b 00000099`3aaff9b0 00007ff9`3dcb6261     ntdll!TppWorkpExecuteCallback+0x4d0
1c 00000099`3aaffb10 00007ff9`3bc5e8d7     ntdll!TppWorkerThread+0x801
1d 00000099`3aaffe70 00007ff9`3dccc3dc     KERNEL32!BaseThreadInitThunk+0x17
1e 00000099`3aaffea0 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

0:006> dx (wchar_t*)0x00000235b4428d64
(wchar_t*)0x00000235b4428d64                 : 0x235b4428d64 : "7aa01d1d-6eae-4b6b-a509-08c8cfe759f8" [Type: wchar_t *]
    55 '7' [Type: wchar_t]

The first policy check occurs only for remote MCP servers (not local to the system), the MDM named policy "WindowsAI.DisableRemoteAgentConnectors" is checked with a fallback to the registry value Software\Microsoft\Windows\WindowsAI\DisableRemoteAgentConnectors (note that both are allow by default). Secondly, if the calling client identity is SYSTEM, the rest of the checks are bypassed and execution is allowed. The next policy check is for BypassMode, again using either MDM named policy "WindowsAI.AgentConnectorMinimumPolicy" or a registry fallback, as seen in OSClient_NET!WMSSImpl_Odr_Utilities_McpPolicy__IsBypassModeEnabled.

ODR MCP server policy bypass mode

The default ConnectorEnvironmentPolicy value on the evaluation machine is 0, disabling bypass mode. If bypass mode is enabled, no additional checks occur. Note, the bypass mode policy is only applied if the MCP server does not contain an MSIX AppExtension, thus all MSIX registered MCP servers will ignore the BypassMode policy. The final server check is for the package URI and MCPB manifest, represented by an OSClient_NET!WMSSImpl_McpbHelpers_McpbManifest object. The manifest is extracted and parsed to ensure that the static_responses field is present and valid.

The next part of the policy evaluation involves the client. The client identity we previously talked about is used to get the name of the client. All of the client's applicable information is packaged up (most importantly, the user) and passed to OSClient_NET!WMSSImpl_WMSS_AccessManager_AccessManagerInvoker__CheckAccessForUser. The next section will dive into the functionality encapsulated here, but if the check fails (e.g., the user does not have access) the name of the client process identity, and the MCP Server name extracted from the name field of the MCPB, are used to prompt the user to allow or deny access. In this case the client is WinDbg, because this is where ODR was originally launched from.

ODR prompting the user

CheckAccessForUser, and subsequent functions, are interesting to us because they are instrumented through a brand new COM server, abstracted over WinRT.

IMcpAccessManagerStatics and IMcpConsentManagerStatics

ODR now sees two new COM interfaces, IMcpAccessManagerStatics and IMcpConsentManagerStatics. This is because the new MCP functionality on works off of a "user/consent" model - meaning that access checks are performed against users accessing resources and, if the access has not been granted, a user is prompted for their consent to allow software to access said resources.

  • IMcpAccessManagerStatics contains:

    • CheckAccessForUser
    • RequestAccessForUserAsync
  • IMcpConsentManagerStatics contains:

    • ClearConsentsForClient
    • ClearConsentsForServer
    • GetConsentsForClient
    • GetConsentsForServer
    • GetConsents
    • SetConsent

This is identifiable by first locating the code infrastructure associated with this COM interface. Reverse engineering shows that the WinRT activatable class ID is present in the Windows Runtime Registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId - specifically the WindowsUdk.AI.Agents.Mcp namespace. This reveals this WinRT interface is implemented in windowsudk.shellcommon.dll

MCP WinRT activatable classes

However, we know that WinRT is simply built on top of COM, which eventually bubbles down to RPC, the transport protocol for COM. To get a better understanding of the lower-level functionality that is implemented by our two WinRT classes, we should investigate where the COM client and COM server are implemented.

Taking execution to its logical end, which is a call to RPCRT4!NdrpClientCall3 - responsible for dispatching the target method in the RPC server process, we can gain better insight into where the actual code for this COM interface is implemented.

0:006> u @rax L1
RPCRT4!NdrpClientCall3:
00007ffe`d2157660 4c8bdc          mov     r11,rsp
0:006> k
 # Child-SP          RetAddr               Call Site
00 000000ca`640fea20 00007ffe`d2853262     combase!ObjectStublessClient+0x141 [onecore\com\combase\ndr\ndrole\amd64\stblsclt.cxx @ 289] 
01 000000ca`640fedb0 00007ffe`ba38858f     combase!ObjectStubless+0x42 [onecore\com\combase\ndr\ndrole\amd64\stubless.asm @ 176] 
02 000000ca`640fee00 00007ffe`ba387da6     windowsudk_shellcommon!winrt::impl::consume_Windows_Internal_AI_Agents_Mcp_IMcpAccessManagerStatics<winrt::Windows::Internal::AI::Agents::Mcp::IMcpAccessManagerStatics>::CheckAccessForUser+0x6f
03 000000ca`640fee80 00007ffe`ba38875e     windowsudk_shellcommon!winrt::impl::factory_cache_entry<winrt::Windows::Internal::AI::Agents::Mcp::McpAccessManager,winrt::Windows::Internal::AI::Agents::Mcp::IMcpAccessManagerStatics>::call<<lambda_c3d2e88f80efa4a35097348e14ba099d> & __ptr64>+0x11e
04 000000ca`640fef00 00007ffe`ba388616     windowsudk_shellcommon!winrt::WindowsUdk::AI::Agents::Mcp::implementation::McpAccessManager::CheckAccessForUser+0x12a
05 000000ca`640ff010 00007ffe`7251bd1c     windowsudk_shellcommon!winrt::impl::produce<winrt::WindowsUdk::AI::Agents::Mcp::factory_implementation::McpAccessManager,winrt::WindowsUdk::AI::Agents::Mcp::IMcpAccessManagerStatics>::CheckAccessForUser+0x56
06 000000ca`640ff050 00007ffe`7245cffc     OSClient_NET!windowsudk_interop_ABI_WindowsUdk_AI_Agents_Mcp_IMcpAccessManagerStaticsMethods__CheckAccessForUser+0x1fc
07 000000ca`640ff1f0 00007ffe`723e56cd     OSClient_NET!WMSSImpl_WMSS_AccessManager_AccessManagerInvoker__CheckAccessForUser+0x3c
08 000000ca`640ff240 00007ffe`724587f1     OSClient_NET!WMSSImpl_WMSS_Utils_McpExecutionEnvironmentPolicy__IsAllowedByPolicy+0x49d
09 000000ca`640ff370 00007ffe`7246fd67     OSClient_NET!WMSSImpl_WMSS_Filters_CamServerFilter__FilterServers+0x1b1
0a 000000ca`640ff400 00007ffe`72129aa5     OSClient_NET!WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServersAsync_d__7__MoveNext+0x367
0b 000000ca`640ff4c0 00007ffe`72acc953     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
0c 000000ca`640ff530 00007ffe`72139459     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServersAsync_d__7>__MoveNext_0+0x53
0d 000000ca`640ff580 00007ffe`72136b69     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
0e 000000ca`640ff5e0 00007ffe`72637deb     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
0f 000000ca`640ff650 00007ffe`7248013b     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task_1<ModelContextProtocol_Core_ModelContextProtocol_SynchronizationExtensions_Releaser>__TrySetResult+0x8b
10 000000ca`640ff6a0 00007ffe`72129aa5     OSClient_NET!WMSSImpl_WMSS_Catalogs_MsixCatalogBase__GetServersAsync_d__24__MoveNext+0x1db
11 000000ca`640ff730 00007ffe`72acdcf3     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
12 000000ca`640ff7a0 00007ffe`72139459     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_WMSS_Catalogs_MsixCatalogBase__GetServersAsync_d__24>__MoveNext_0+0x53
13 000000ca`640ff7f0 00007ffe`72136b69     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
14 000000ca`640ff850 00007ffe`72135bf4     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
15 000000ca`640ff8c0 00007ffe`7212f8b7     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__ExecuteWithThreadLocal+0xd4
16 000000ca`640ff910 00007ffe`72133777     OSClient_NET!S_P_CoreLib_System_Threading_ThreadPoolWorkQueue__Dispatch+0x237
17 000000ca`640ff9b0 00007ffe`d3695210     OSClient_NET!S_P_CoreLib_System_Threading_WindowsThreadPool__DispatchCallback+0x67
18 000000ca`640ffa00 00007ffe`d3696261     ntdll!TppWorkpExecuteCallback+0x4d0
19 000000ca`640ffb60 00007ffe`d29be8d7     ntdll!TppWorkerThread+0x801
1a 000000ca`640ffec0 00007ffe`d36ac3dc     KERNEL32!BaseThreadInitThunk+0x17
1b 000000ca`640ffef0 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

0:006> dx (combase!_MIDL_STUBLESS_PROXY_INFO*)@rdx
(combase!_MIDL_STUBLESS_PROXY_INFO*)@rdx                 : 0x7ffeb477df60 [Type: _MIDL_STUBLESS_PROXY_INFO *]
    [+0x000] pStubDesc        : 0x7ffeb4772fd0 [Type: _MIDL_STUB_DESC *]
    [+0x008] ProcFormatString : 0x7ffeb4789ed2 : 0x33 [Type: unsigned char *]
    [+0x010] FormatStringOffset : 0x7ffeb4789e62 : 0xb96c [Type: unsigned short *]
    [+0x018] pTransferSyntax  : 0x7ffeb4786f20 [Type: _RPC_SYNTAX_IDENTIFIER *]
    [+0x020] nCount           : 0x2 [Type: unsigned __int64]
    [+0x028] pSyntaxInfo      : 0x7ffeb477e120 [Type: _MIDL_SYNTAX_INFO *]

0:006> dx ((combase!_MIDL_STUBLESS_PROXY_INFO*)@rdx)->pStubDesc->RpcInterfaceInformation
((combase!_MIDL_STUBLESS_PROXY_INFO*)@rdx)->pStubDesc->RpcInterfaceInformation : 0x0 [Type: void *]

At this point in the execution, we can see that the COM stub's RpcInterfaceInformation member, which contains the necessary information to dispatch the call's context to the target RPC server, is empty. This is because the RPC_MESSAGE, which encapsulates the necessary information for the RPC method call, is only populated in combase!Ndr64ExtProxyInitialize (by way of RPCRT4!Ndr64ClientInitialize) for the WinRT call demonstrated here. RpcInterfaceInformation reveals that the interface ID is FBC59BB7-5D0E-530B-BA65-773313F2FDFA.

`Ndr64ExtProxyInitialize`

`Ndr64ClientInitialize`

0:006> dx (combase!_RPC_MESSAGE*)0x000000f3e1dfe330
(combase!_RPC_MESSAGE*)0x000000f3e1dfe330                 : 0xf3e1dfe330 [Type: _RPC_MESSAGE *]
    [+0x000] Handle           : 0x22102b6d230 [Type: void *]
    [+0x008] DataRepresentation : 0x0 [Type: unsigned long]
    [+0x010] Buffer           : 0x0 [Type: void *]
    [+0x018] BufferLength     : 0x0 [Type: unsigned int]
    [+0x01c] ProcNum          : 0x8006 [Type: unsigned int]
    [+0x020] TransferSyntax   : 0x22102b5bcf8 [Type: _RPC_SYNTAX_IDENTIFIER *]
    [+0x028] RpcInterfaceInformation : 0xf3e1dfe540 [Type: void *]
    [+0x030] ReservedForRuntime : 0x0 [Type: void *]
    [+0x038] ManagerEpv       : 0x0 [Type: void *]
    [+0x040] ImportContext    : 0x0 [Type: void *]
    [+0x048] RpcFlags         : 0x0 [Type: unsigned long]

0:006> dx (combase!_RPC_CLIENT_INTERFACE*)((combase!_RPC_MESSAGE*)0x000000f3e1dfe330)->RpcInterfaceInformation
(combase!_RPC_CLIENT_INTERFACE*)((combase!_RPC_MESSAGE*)0x000000f3e1dfe330)->RpcInterfaceInformation                 : 0xf3e1dfe540 [Type: _RPC_CLIENT_INTERFACE *]
    [+0x000] Length           : 0x60 [Type: unsigned int]
    [+0x004] InterfaceId      [Type: _RPC_SYNTAX_IDENTIFIER]
    [+0x018] TransferSyntax   [Type: _RPC_SYNTAX_IDENTIFIER]
    [+0x030] DispatchTable    : 0x0 [Type: RPC_DISPATCH_TABLE *]
    [+0x038] RpcProtseqEndpointCount : 0x0 [Type: unsigned int]
    [+0x040] RpcProtseqEndpoint : 0x0 [Type: _RPC_PROTSEQ_ENDPOINT *]
    [+0x048] DefaultManagerEpv : 0x0 [Type: void *]
    [+0x050] InterpreterInfo  : 0x7ffeb477df60 [Type: void *]
    [+0x058] Flags            : 0x2000000 [Type: unsigned int]

0:006> dx ((combase!_RPC_CLIENT_INTERFACE*)((combase!_RPC_MESSAGE*)0x000000f3e1dfe330)->RpcInterfaceInformation)->InterfaceId
((combase!_RPC_CLIENT_INTERFACE*)((combase!_RPC_MESSAGE*)0x000000f3e1dfe330)->RpcInterfaceInformation)->InterfaceId                 [Type: _RPC_SYNTAX_IDENTIFIER]
    [+0x000] SyntaxGUID       : {FBC59BB7-5D0E-530B-BA65-773313F2FDFA} [Type: _GUID]
    [+0x010] SyntaxVersion    [Type: _RPC_VERSION]

In this case the interface ID is associated with Windows.Internal.AI.Agents.Mcp.IMcpAccessManagerStatics.

Interface ID

Activatable Class

We can see that camsvc is the name of the Server in the WinRT Registry key. Using System Informer we can then determine what process is associated with the string camsvc. In this case it is a svchost.exe process which, based on the -s command line argument, is hosting the camsvc service.

`camsvc`

Cross-referencing this with the camsvc service itself, we can see that the server is CapabilityAccessManager.dll. This is the image which hosts all the WinRT functionality! In addition, the IMcpConsentManagerStatics COM server is hosted in the same DLL.

`camsvc` Service DLL

We then can simply set a breakpoint on capabilityaccessmanager!McpAccessManagerStatics::CheckAccessForUser in the target svchost.exe process!

0:009> g
Breakpoint 0 hit
capabilityaccessmanager!McpAccessManagerStatics::CheckAccessForUser:
00007ffe`bdad1a60 4c8bdc          mov     r11,rsp
0:004> k
 # Child-SP          RetAddr               Call Site
00 000000fa`c44fd3f8 00007ffe`d2153663     capabilityaccessmanager!McpAccessManagerStatics::CheckAccessForUser
01 000000fa`c44fd400 00007ffe`d215570e     RPCRT4!Invoke+0x73
02 000000fa`c44fd480 00007ffe`d2096c50     RPCRT4!Ndr64StubWorker+0x6ee
03 000000fa`c44fda90 00007ffe`d262c9a3     RPCRT4!NdrStubCall3+0xc0
04 000000fa`c44fdb00 00007ffe`d262c8bb     combase!CStdStubBuffer_Invoke+0xa3 [onecore\com\combase\ndr\ndrole\stub.cxx @ 1388] 
05 (Inline Function) --------`--------     combase!InvokeStubWithExceptionPolicyAndTracing::__l6::<lambda_c9f3956a20c9da92a64affc24fdd69ec>::operator()+0x26 [onecore\com\combase\dcomrem\channelb.cxx @ 1163] 
06 000000fa`c44fdb40 00007ffe`d262be36     combase!ObjectMethodExceptionHandlingAction<<lambda_c9f3956a20c9da92a64affc24fdd69ec> >+0x47 [onecore\com\combase\dcomrem\excepn.hxx @ 94] 
07 (Inline Function) --------`--------     combase!InvokeStubWithExceptionPolicyAndTracing+0x182 [onecore\com\combase\dcomrem\channelb.cxx @ 1161] 
08 000000fa`c44fdba0 00007ffe`d262b109     combase!DefaultStubInvoke+0x376 [onecore\com\combase\dcomrem\channelb.cxx @ 1230] 
09 (Inline Function) --------`--------     combase!SyncStubCall::Invoke+0x7 [onecore\com\combase\dcomrem\channelb.cxx @ 1287] 
0a (Inline Function) --------`--------     combase!SyncServerCall::StubInvoke+0x33 [onecore\com\combase\dcomrem\ServerCall.hpp @ 790] 
0b 000000fa`c44fdd60 00007ffe`d26e3a8f     combase!StubInvoke+0x149 [onecore\com\combase\dcomrem\channelb.cxx @ 1496] 
0c 000000fa`c44fde10 00007ffe`d263230a     combase!ServerCall::ContextInvoke+0x28f [onecore\com\combase\dcomrem\ctxchnl.cxx @ 1436] 
0d 000000fa`c44fe0a0 00007ffe`d266f72e     combase!DefaultInvokeInApartment+0x8a [onecore\com\combase\dcomrem\callctrl.cxx @ 3256] 
0e 000000fa`c44fe0d0 00007ffe`d262cd91     combase!ComInvokeWithLockAndIPID+0xcce [onecore\com\combase\dcomrem\channelb.cxx @ 2163] 
0f (Inline Function) --------`--------     combase!ThreadInvokeReturnHresult+0x14a [onecore\com\combase\dcomrem\channelb.cxx @ 7003] 
10 000000fa`c44fe3d0 00007ffe`d21080f7     combase!ThreadInvoke+0x161 [onecore\com\combase\dcomrem\channelb.cxx @ 7103] 
11 000000fa`c44fe470 00007ffe`d20d1ab4     RPCRT4!DispatchToStubInCNoAvrf+0x17
12 000000fa`c44fe4c0 00007ffe`d20d287a     RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x194
13 000000fa`c44fe590 00007ffe`d20bfc54     RPCRT4!LRPC_SCALL::DispatchRequest+0x85a
14 000000fa`c44fea00 00007ffe`d20d001c     RPCRT4!LRPC_SCALL::QueueOrDispatchCall+0xe4
15 000000fa`c44febc0 00007ffe`d20d601c     RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
16 000000fa`c44fed40 00007ffe`d20d51a3     RPCRT4!LRPC_ADDRESS::HandleRequest+0x3ac
17 000000fa`c44fee20 00007ffe`d20d4148     RPCRT4!LRPC_ADDRESS::ProcessIO+0x2f3
18 000000fa`c44ff190 00007ffe`d3697e9e     RPCRT4!LrpcIoComplete+0xc8
19 000000fa`c44ff2b0 00007ffe`d3695fc3     ntdll!TppAlpcpExecuteCallback+0x44e
1a 000000fa`c44ff420 00007ffe`d29be8d7     ntdll!TppWorkerThread+0x563
1b 000000fa`c44ff780 00007ffe`d36ac3dc     KERNEL32!BaseThreadInitThunk+0x17
1c 000000fa`c44ff7b0 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

The CheckAccessForUser function has a prototype that accepts an interface to the Windows::System::IUser class, which is an abstraction representing a particular user context - in this case, this represents the user of the ODR client requesting to interact with a target MCP server. Additionally, multiple strings are passed (represented by winrt::hstring) representing both the ODR client (in this case the package family name and application ID) and MCP servers (the package name).

0:004> dx (wchar_t*)(@r8+0x1c)
(wchar_t*)(@r8+0x1c)                 : 0x29c0890474c : "Microsoft.WinDbg_8wekyb3d8bbwe!Microsoft.WinDbg" [Type: wchar_t *]
    77 'M' [Type: wchar_t]
0:004> dx (wchar_t*)(@r9+0x1c)
(wchar_t*)(@r9+0x1c)                 : 0x29c0888b26c : "9f3a67cd-72e7-4e02-8769-3015e4c43f3f_czkckqegssqse_com.microsoft.windows.ai.mcpServer_MCP-Server-Sample-App" [Type: wchar_t *]
    57 '9' [Type: wchar_t]

In addition, the primary functionality surrounding CheckAccessForUser is an output parameter, of type Windows::Internal::AI::Agents::Mcp::McpAccessStatus, which denotes the status of the access operation such as if access was denied, and additional context around the access denied operation. The actual return value of this function is to indicate failure or success, not to also encapsulate if access was denied.

Internally, CheckAccessForUser begins by performing an access check of the RPC caller, by impersonating the caller and inspecting the security descriptors present in the caller's token. If the token is SYSTEM or if it contains the various application package IDs associated with the Windows AI infrastructure (e.g. The ODR package ID, MicrosoftWindows.Client.CBS_cw5n1h2txyewy), then the client is valid and further checks occur.

RPC caller check

Using the client's user SID from the token, a call to Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck is made, which begins the calls to the IMcpConsentManager interface (although the interface does not need to be invoked since we are already executing code in the server process, we can simply call the functionality directly).

0:006> pc
capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck+0x69:
00007ffb`f299d375 e856eeffff      call    capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpConsentManager::GetClientGlobalConsent (00007ffb`f299c1d0)
0:006> k
 # Child-SP          RetAddr               Call Site
00 000000c9`13a7d090 00007ffb`f2981d8a     capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck+0x69
01 000000c9`13a7d1d0 00007ffb`f2981b3e     capabilityaccessmanager!McpAccessManagerStatics::CheckAccessForUserInternal+0x1ee
02 000000c9`13a7d330 00007ffc`06713663     capabilityaccessmanager!McpAccessManagerStatics::CheckAccessForUser+0xde
03 000000c9`13a7d420 00007ffc`0671570e     RPCRT4!Invoke+0x73
04 000000c9`13a7d4a0 00007ffc`06656c50     RPCRT4!Ndr64StubWorker+0x6ee
05 000000c9`13a7dab0 00007ffc`0461c9a3     RPCRT4!NdrStubCall3+0xc0
06 000000c9`13a7db20 00007ffc`0461c8bb     combase!CStdStubBuffer_Invoke+0xa3 [onecore\com\combase\ndr\ndrole\stub.cxx @ 1388] 
07 (Inline Function) --------`--------     combase!InvokeStubWithExceptionPolicyAndTracing::__l6::<lambda_c9f3956a20c9da92a64affc24fdd69ec>::operator()+0x26 [onecore\com\combase\dcomrem\channelb.cxx @ 1163] 
08 000000c9`13a7db60 00007ffc`0461be36     combase!ObjectMethodExceptionHandlingAction<<lambda_c9f3956a20c9da92a64affc24fdd69ec> >+0x47 [onecore\com\combase\dcomrem\excepn.hxx @ 94] 
09 (Inline Function) --------`--------     combase!InvokeStubWithExceptionPolicyAndTracing+0x182 [onecore\com\combase\dcomrem\channelb.cxx @ 1161] 
0a 000000c9`13a7dbc0 00007ffc`0461b109     combase!DefaultStubInvoke+0x376 [onecore\com\combase\dcomrem\channelb.cxx @ 1230] 
0b (Inline Function) --------`--------     combase!SyncStubCall::Invoke+0x7 [onecore\com\combase\dcomrem\channelb.cxx @ 1287] 
0c (Inline Function) --------`--------     combase!SyncServerCall::StubInvoke+0x33 [onecore\com\combase\dcomrem\ServerCall.hpp @ 790] 
0d 000000c9`13a7dd80 00007ffc`046d3a8f     combase!StubInvoke+0x149 [onecore\com\combase\dcomrem\channelb.cxx @ 1496] 
0e 000000c9`13a7de30 00007ffc`0462230a     combase!ServerCall::ContextInvoke+0x28f [onecore\com\combase\dcomrem\ctxchnl.cxx @ 1436] 
0f 000000c9`13a7e0c0 00007ffc`0465f72e     combase!DefaultInvokeInApartment+0x8a [onecore\com\combase\dcomrem\callctrl.cxx @ 3256] 
10 000000c9`13a7e0f0 00007ffc`0461cd91     combase!ComInvokeWithLockAndIPID+0xcce [onecore\com\combase\dcomrem\channelb.cxx @ 2163] 
11 (Inline Function) --------`--------     combase!ThreadInvokeReturnHresult+0x14a [onecore\com\combase\dcomrem\channelb.cxx @ 7003] 
12 000000c9`13a7e3f0 00007ffc`066c80f7     combase!ThreadInvoke+0x161 [onecore\com\combase\dcomrem\channelb.cxx @ 7103] 
13 000000c9`13a7e490 00007ffc`06691ab4     RPCRT4!DispatchToStubInCNoAvrf+0x17
14 000000c9`13a7e4e0 00007ffc`0669287a     RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x194
15 000000c9`13a7e5b0 00007ffc`0667fc54     RPCRT4!LRPC_SCALL::DispatchRequest+0x85a
16 000000c9`13a7ea20 00007ffc`0669001c     RPCRT4!LRPC_SCALL::QueueOrDispatchCall+0xe4
17 000000c9`13a7ebe0 00007ffc`0669601c     RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
18 000000c9`13a7ed60 00007ffc`066951a3     RPCRT4!LRPC_ADDRESS::HandleRequest+0x3ac
19 000000c9`13a7ee40 00007ffc`06694148     RPCRT4!LRPC_ADDRESS::ProcessIO+0x2f3
1a 000000c9`13a7f1b0 00007ffc`06817e9e     RPCRT4!LrpcIoComplete+0xc8
1b 000000c9`13a7f2d0 00007ffc`06815fc3     ntdll!TppAlpcpExecuteCallback+0x44e
1c 000000c9`13a7f440 00007ffc`05bbe8d7     ntdll!TppWorkerThread+0x563
1d 000000c9`13a7f7a0 00007ffc`0682c3dc     KERNEL32!BaseThreadInitThunk+0x17
1e 000000c9`13a7f7d0 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

Windows::Internal::AI::Agents::Mcp::Implementation::McpConsentManager::GetClientGlobalConsent is the first of the MCP consent manager code to be called, from the AccessCheck function. This function accepts two parameters - which are strings that represent both the client identity and the requesting user's SID.

0:003> u @rip L1
capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck+0x69:
00007ff8`2871f965 e856eeffff      call    capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpConsentManager::GetClientGlobalConsent (00007ff8`2871e7c0)

0:003> dx (wchar_t*)(__int64)(*(__int64*)@rcx)
(wchar_t*)(__int64)(*(__int64*)@rcx)                 : 0x19f718a6580 : "Microsoft.WinDbg_8wekyb3d8bbwe!Microsoft.WinDbg" [Type: wchar_t *]
    77 'M' [Type: wchar_t]

0:003> dx (wchar_t*)(__int64)(*(__int64*)@rdx)
(wchar_t*)(__int64)(*(__int64*)@rdx)                 : 0x19f718a6500 : "S-1-5-21-3708322579-3252695597-3467443573-1001" [Type: wchar_t *]
    83 'S' [Type: wchar_t]

Execution then redirects to capabilityaccessmanager_desktop_storage!GetUInt64MCPSetting - which is packaged in CapabilityAccessManager.Desktop.Storage.dll.

`GetUInt64McpSetting`

GetUInt64McpSetting eventually results in the function Windows::Internal::CapabilityAccess::Desktop::Storage::Database::MCP::GetSetting being executed - which now infers that there is a database component to the new ODR and MCP functionality on Windows.

ODR Consent Database

To begin understanding the consent database, further reverse engineering identified that the client identity and the user SID string are used to query the database functionality of ODR.

MCP "key" extraction

Now, this brings up the question - "what database are you talking about"? There are a few ways in which we could determine the target database of the operation, including inspecting the call to obtain the databaseInterface previously shown. However, even easier, we can simply look to see what files the current svchost.exe process has open via System Informer.

ODR database files

Examining these database files shows they are only accessibly by SYSTEM (along with the C:\ProgramData\Microsoft\Windows\CapabilityAccessManager folder itself). This makes sense, as access should only be delegated to the actual implementation of the MCP COM interface(s) - which are hosted in svchost.exe which also runs as SYSTEM.

ODR database file permissions

Using a simple GUI tool to examine the database, which is just a standard SQL database, we can see that the target operation here is to the CapabilityConsentStorage.db database, specifically the McpConsent table.

CapabilityConsentStorage layout

Before moving on, it is probably worth talking a bit more about the layout of the database. The Clients table contains the string of the client name itself. In the case of the debugger in which Odr.exe was originally launched, the client is Microsoft.WinDbg_8wekyb3d8bbwe!Microsoft.WinDbg. Notice each client (in this case there is only one) is associated with an ID, which in this case is 1.

Clients table

If we then go back to the McpConsent table we can take a look at each time a user was prompted for consent. For the purposes of this blog post, many "consent" requests were executed - with ID 37 being "the most recent". User 1 and Client 1 were attempting to access Server 12. User 1 and Client 1 represent the current user and client identities in the analysis we have been performing, with server 12 representing the test MCP server we have created.

McpConsent table - populated

Users table

Servers table

If we remember the previous query operation, and the way the McpConsent database table was laid out, consent can also occur for a particular resource. In the database we performed this analysis on, the only supported resource at the time is Files, which has an ID of 1 in the Resources table. Additionally, the only supported option for the target operation, in the Operations table, is KnownFolders - an ID of 1. In our current query, the target resource and operation are both set to 0, which means they are not applicable. If the target configuration is not found, a call to Windows::Internal::CapabilityAccess::Extensions::Storage::APIWrappers::MCP::PutSetting is made to add the target configuration (e.g., user X and client Y attempting to access server Z) to the database.

Adding to the McpConsent table

As a point of contention, the presence of the ODR database is only available if a specific feature flag, with name which is not useful for the purposes of identification, is available. If the ODR database feature is not enabled, the MCP consent infrastructure is managed via the registry through the Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\MCPConsent key.

Moving on, once the client has been evaluated this the same code path is taken for the server itself (via Windows::Internal::AI::Agents::Mcp::Implementation::McpConsentManager::GetServerGlobalConsent). In this case the two parameters are the server name and, still, the user SID as a string.

0:007> u @rip L1
capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck+0x76:
00007ff8`2871f972 e8e5efffff      call    capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpConsentManager::GetServerGlobalConsent (00007ff8`2871e95c)

0:007> dx (wchar_t*)(__int64)(*(__int64*)@rcx)
(wchar_t*)(__int64)(*(__int64*)@rcx)                 : 0x19f711ec200 : "9f3a67cd-72e7-4e02-8769-3015e4c43f3f_czkckqegssqse_com.microsoft.windows.ai.mcpServer_MCP-Server-Sample-App" [Type: wchar_t *]
    57 '9' [Type: wchar_t]

0:007> dx (wchar_t*)(__int64)(*(__int64*)@rdx)
(wchar_t*)(__int64)(*(__int64*)@rdx)                 : 0x19f71905780 : "S-1-5-21-3708322579-3252695597-3467443573-1001" [Type: wchar_t *]
    83 'S' [Type: wchar_t]

After the database is read from/prepared with the requesting user and client identity information, a call to Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::RequestConsentResponse occurs. This is the main functionality which prompts the user for consent, achieved through functionality in another class - Windows.Internal.AI.Agents.Mcp.McpConsentExperience. This is reached via WinRT (eventually by RPC) at the {C53275B4-E8EA-5549-9AF1-7355E5DA6D97} interface ID.

`IMcpConsentExperienceStatics` interface

Using the same methodology from earlier, we can clearly see that this WinRT interface resides in the ConsentUxUserSvc server.

`ConsentUxUserSvc`

Based on the Svc suffix, we can query this service via the SCM.

C:\Windows\System32>sc query ConsentUxUserSvc

SERVICE_NAME: ConsentUxUserSvc
        TYPE               : 60  USER_SHARE_PROCESS TEMPLATE
        STATE              : 1  STOPPED
        WIN32_EXIT_CODE    : 1077  (0x435)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

Using System Informer, once again, we can see that the service DLL is ConsentUxClient.dll, which contains all the MCP consent experience infrastructure. We know that the target function in this case is RequestConsentResponse, but all the consent experience functionality is encapsulated by the following functions:

  • RequestClientFileAccessConsent
  • RequestConsentResponse
  • RequestServerForClientConsent
  • ShowConsentDialog
  • ShowCustomXamlConsentDialog
0:007> g
Breakpoint 0 hit
consentuxclient!McpConsentExperienceStatics::RequestConsentResponse:
00007ff8`2ce20940 48895c2408      mov     qword ptr [rsp+8],rbx ss:00000093`a77fdbd0=000002027d114ab8
0:005> k
 # Child-SP          RetAddr               Call Site
00 00000093`a77fdbc8 00007ff8`3d223663     consentuxclient!McpConsentExperienceStatics::RequestConsentResponse
01 00000093`a77fdbd0 00007ff8`3d22570e     RPCRT4!Invoke+0x73
02 00000093`a77fdc50 00007ff8`3d166c50     RPCRT4!Ndr64StubWorker+0x6ee
03 00000093`a77fe260 00007ff8`3c79c9a3     RPCRT4!NdrStubCall3+0xc0
04 00000093`a77fe2d0 00007ff8`3c79c8bb     combase!CStdStubBuffer_Invoke+0xa3 [onecore\com\combase\ndr\ndrole\stub.cxx @ 1388] 
05 (Inline Function) --------`--------     combase!InvokeStubWithExceptionPolicyAndTracing::__l6::<lambda_c9f3956a20c9da92a64affc24fdd69ec>::operator()+0x26 [onecore\com\combase\dcomrem\channelb.cxx @ 1163] 
06 00000093`a77fe310 00007ff8`3c79be36     combase!ObjectMethodExceptionHandlingAction<<lambda_c9f3956a20c9da92a64affc24fdd69ec> >+0x47 [onecore\com\combase\dcomrem\excepn.hxx @ 94] 
07 (Inline Function) --------`--------     combase!InvokeStubWithExceptionPolicyAndTracing+0x182 [onecore\com\combase\dcomrem\channelb.cxx @ 1161] 
08 00000093`a77fe370 00007ff8`3c79b109     combase!DefaultStubInvoke+0x376 [onecore\com\combase\dcomrem\channelb.cxx @ 1230] 
09 (Inline Function) --------`--------     combase!SyncStubCall::Invoke+0x7 [onecore\com\combase\dcomrem\channelb.cxx @ 1287] 
0a (Inline Function) --------`--------     combase!SyncServerCall::StubInvoke+0x33 [onecore\com\combase\dcomrem\ServerCall.hpp @ 790] 
0b 00000093`a77fe530 00007ff8`3c853b8f     combase!StubInvoke+0x149 [onecore\com\combase\dcomrem\channelb.cxx @ 1496] 
0c 00000093`a77fe5e0 00007ff8`3c7a230a     combase!ServerCall::ContextInvoke+0x28f [onecore\com\combase\dcomrem\ctxchnl.cxx @ 1436] 
0d 00000093`a77fe870 00007ff8`3c7df72e     combase!DefaultInvokeInApartment+0x8a [onecore\com\combase\dcomrem\callctrl.cxx @ 3256] 
0e 00000093`a77fe8a0 00007ff8`3c79cd91     combase!ComInvokeWithLockAndIPID+0xcce [onecore\com\combase\dcomrem\channelb.cxx @ 2163] 
0f (Inline Function) --------`--------     combase!ThreadInvokeReturnHresult+0x14a [onecore\com\combase\dcomrem\channelb.cxx @ 7003] 
10 00000093`a77feba0 00007ff8`3d1d80f7     combase!ThreadInvoke+0x161 [onecore\com\combase\dcomrem\channelb.cxx @ 7103] 
11 00000093`a77fec40 00007ff8`3d1a1ab4     RPCRT4!DispatchToStubInCNoAvrf+0x17
12 00000093`a77fec90 00007ff8`3d1a287a     RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x194
13 00000093`a77fed60 00007ff8`3d18fc54     RPCRT4!LRPC_SCALL::DispatchRequest+0x85a
14 00000093`a77ff1d0 00007ff8`3d1a001c     RPCRT4!LRPC_SCALL::QueueOrDispatchCall+0xe4
15 00000093`a77ff390 00007ff8`3d1a601c     RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
16 00000093`a77ff510 00007ff8`3d1a51a3     RPCRT4!LRPC_ADDRESS::HandleRequest+0x3ac
17 00000093`a77ff5f0 00007ff8`3d1a4148     RPCRT4!LRPC_ADDRESS::ProcessIO+0x2f3
18 00000093`a77ff960 00007ff8`3d6b7ece     RPCRT4!LrpcIoComplete+0xc8
19 00000093`a77ffa80 00007ff8`3d6b5ff3     ntdll!TppAlpcpExecuteCallback+0x44e
1a 00000093`a77ffbf0 00007ff8`3ccce8d7     ntdll!TppWorkerThread+0x563
1b 00000093`a77fff50 00007ff8`3d6cc40c     KERNEL32!BaseThreadInitThunk+0x17
1c 00000093`a77fff80 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

0:005> dx @$curprocess.Name
@$curprocess.Name : svchost.exe
    Length           : 0xb

0:005> dx @$curprocess.Environment.EnvironmentBlock->ProcessParameters->CommandLine
@$curprocess.Environment.EnvironmentBlock->ProcessParameters->CommandLine                 [Type: _UNICODE_STRING]
    [+0x000] Length           : 0x84 [Type: unsigned short]
    [+0x002] MaximumLength    : 0x86 [Type: unsigned short]
    [+0x008] Buffer           : 0x2027d105300 : "C:\WINDOWS\system32\svchost.exe -k DevicesFlow -s ConsentUxUserSvc" [Type: wchar_t *]

RequestConsentResponse begins with another call to AccessCheckForRPCCaller, just as we saw with CapabilityAccessManager.dll. From here, one of two functions is called: RequestServerForClientConsent or RequestClientFileAccessConsent. The names here are pretty self-explanatory, requests for consent either allow access to an MCP server or, if access has already been granted, specific file(s) needed for the MCP server can be requested on behalf of the MCP (ODR) client. In this case the call to RequestServerForClientConsent is called - which simply prompts the user, captures the result from the user, and then returns the decision (as a string) to the caller.

`RequestServerForClientConsent`

`RequestServerForClientConsent` dialog

0:005> dx (wchar_t*)0x000002027d15620c
(wchar_t*)0x000002027d15620c                 : 0x2027d15620c : "AlwaysAllow" [Type: wchar_t *]
    65 'A' [Type: wchar_t]

Coming back to CapabilityAccessManager.dll, the return value for RequestConsentResponse is the result of the user's decision, as a string.

0:004> u @rip-0x5 L1
capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck+0x298:
00007ff8`2871fb94 e8cf010000      call    capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::RequestConsentResponse (00007ff8`2871fd68)

0:004> u @rip L1
capabilityaccessmanager!Windows::Internal::AI::Agents::Mcp::Implementation::McpAccessManager::AccessCheck+0x29d:
00007ff8`2871fb99 488bd0          mov     rdx,rax

0:004> dx (wchar_t*)@rax
(wchar_t*)@rax                 : 0x9fd87fb40 : "AlwaysAllow" [Type: wchar_t *]
    65 'A' [Type: wchar_t]

If the user has consented, the SetClientGlobalConsent, SetServerGlobalConsent, and SetConsent functions (in CapabilityAccessManager.dll) are called to update the consent result. We can see that the McpConsent table value has changed Value from 2 to 0. This is because the consent has been changed to "always allow" - which has a value of 0 (a value of 2 seems to indicate "Allow" as in "Allow Once"). This can be more clearly seen in the AccessCheck function. The return values of GetClientGlobalConsent and GetServerGlobalConsent are compared against a range of values of 1 - 3. A value of 2 indicates that CapabilityAccessManager.dll is required to re-ask the user for consent. A value of 0, from the database, results in the AccessCheck function returning without any further checks, as the user has, at this point, allowed full access.

`McpConsent` database update

`AccessCheck` consent logic

MCP Auditing - Windows Telemetry

In the previous section of the blog, we outlined how ODR manages MCP server and system resources access. If one continues to trace the call from OSClient.NET.dll, for the CheckAccessForUser function, eventually an ETW event is written:

0:007> k
 # Child-SP          RetAddr               Call Site
00 000000fc`e35fe968 00007fff`b94bb9b3     ntdll!EtwEventWriteTransfer
01 000000fc`e35fe970 00007fff`b94bcadb     OSClient_NET!Microsoft_Telemetry_Windows_Win32_PInvoke__EventWriteTransfer+0xc3
02 000000fc`e35fea60 00007fff`b9bf8656     OSClient_NET!Microsoft_Telemetry_Microsoft_Telemetry_TraceEventBuilder__EventWriteTransfer+0xdb
03 000000fc`e35feac0 00007fff`b9cf73da     OSClient_NET!WMSS_Logging_WMSS_Logging_EtwLog__Write_15+0x6e6
04 000000fc`e35fec20 00007fff`b9c80a23     OSClient_NET!WMSSImpl_WMSS_Logging_AsimovEventsLogger__LogConsentOutcome+0xea
05 000000fc`e35fec80 00007fff`b9cf7b21     OSClient_NET!WMSSImpl_WMSS_Utils_McpExecutionEnvironmentPolicy__IsAllowedByPolicy+0x4d3
06 000000fc`e35fedb0 00007fff`ba2f82a2     OSClient_NET!WMSSImpl_WMSS_Filters_CamServerFilter___FilterServers_g__IsAllowed_3_0+0x121
07 000000fc`e35fee20 00007fff`ba2f8759     OSClient_NET!System_Linq_System_Linq_Enumerable_ArrayWhereIterator_1<System___Canon>__ToList_0+0xc2
08 000000fc`e35fefe0 00007fff`b9d0feb7     OSClient_NET!System_Linq_System_Linq_Enumerable_ListWhereIterator_1<System___Canon>__ToList+0x69
09 000000fc`e35ff030 00007fff`b99f21a5     OSClient_NET!WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServersAsync_d__7__MoveNext+0x457
0a 000000fc`e35ff100 00007fff`ba3a0193     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
0b 000000fc`e35ff170 00007fff`b9a01b59     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_OnDeviceMcpRegistry_McpRegistry__GetServersAsync_d__7>__MoveNext_0+0x53
0c 000000fc`e35ff1c0 00007fff`b99ff269     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
0d 000000fc`e35ff220 00007fff`b9eddd8b     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
0e 000000fc`e35ff290 00007fff`b9d20a1b     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task_1<ModelContextProtocol_Core_ModelContextProtocol_SynchronizationExtensions_Releaser>__TrySetResult+0x8b
0f 000000fc`e35ff2e0 00007fff`b99f21a5     OSClient_NET!WMSSImpl_WMSS_Catalogs_MsixCatalogBase__GetServersAsync_d__24__MoveNext+0x1db
10 000000fc`e35ff370 00007fff`ba3a1533     OSClient_NET!S_P_CoreLib_System_Threading_ExecutionContext__RunInternal+0xb5
11 000000fc`e35ff3e0 00007fff`b9a01b59     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1<System___Canon__WMSSImpl_WMSS_Catalogs_MsixCatalogBase__GetServersAsync_d__24>__MoveNext_0+0x53
12 000000fc`e35ff430 00007fff`b99ff269     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_AwaitTaskContinuation__RunOrScheduleAction_0+0xc9
13 000000fc`e35ff490 00007fff`b99fe2f4     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__RunContinuations+0x49
14 000000fc`e35ff500 00007fff`b99f7fb7     OSClient_NET!S_P_CoreLib_System_Threading_Tasks_Task__ExecuteWithThreadLocal+0xd4
15 000000fc`e35ff550 00007fff`b99fbe77     OSClient_NET!S_P_CoreLib_System_Threading_ThreadPoolWorkQueue__Dispatch+0x237
16 000000fc`e35ff5f0 00007ff8`3d6b5240     OSClient_NET!S_P_CoreLib_System_Threading_WindowsThreadPool__DispatchCallback+0x67
17 000000fc`e35ff640 00007ff8`3d6b6291     ntdll!TppWorkpExecuteCallback+0x4d0
18 000000fc`e35ff7a0 00007ff8`3ccce8d7     ntdll!TppWorkerThread+0x801
19 000000fc`e35ffb00 00007ff8`3d6cc40c     KERNEL32!BaseThreadInitThunk+0x17
1a 000000fc`e35ffb30 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

Investigating the call to EtwEventWriteTransfer further, we can see that the event data is as follows: this is event ID 12, and there are 8 "fields" in the ETW event.

0:002> dx *(unsigned int*)((__int64)@rsp+0x28)
*(unsigned int*)((__int64)@rsp+0x28) : 0x8 [Type: unsigned int]

0:002> dx -r2 (ntdll!_EVENT_DATA_DESCRIPTOR(*)[8])(*(__int64*)((__int64)@rsp+0x30))
(ntdll!_EVENT_DATA_DESCRIPTOR(*)[8])(*(__int64*)((__int64)@rsp+0x30))                 : 0x1ec73520f90 [Type: _EVENT_DATA_DESCRIPTOR (*)[8]]
    [0]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x1ec734289d0 [Type: unsigned __int64]
        [+0x008] Size             : 0x24 [Type: unsigned long]
        [+0x00c] Reserved         : 0x2 [Type: unsigned long]
        [+0x00c] Type             : 0x2 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [1]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x1ec734f8cb0 [Type: unsigned __int64]
        [+0x008] Size             : 0x57 [Type: unsigned long]
        [+0x00c] Reserved         : 0x1 [Type: unsigned long]
        [+0x00c] Type             : 0x1 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [2]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x7e3d8fec58 [Type: unsigned __int64]
        [+0x008] Size             : 0x8 [Type: unsigned long]
        [+0x00c] Reserved         : 0x0 [Type: unsigned long]
        [+0x00c] Type             : 0x0 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [3]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x7fff9e2cd034 [Type: unsigned __int64]
        [+0x008] Size             : 0x2 [Type: unsigned long]
        [+0x00c] Reserved         : 0x0 [Type: unsigned long]
        [+0x00c] Type             : 0x0 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [4]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x1ec734e59fc [Type: unsigned __int64]
        [+0x008] Size             : 0x60 [Type: unsigned long]
        [+0x00c] Reserved         : 0x0 [Type: unsigned long]
        [+0x00c] Type             : 0x0 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [5]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x1ec735908cc [Type: unsigned __int64]
        [+0x008] Size             : 0xd8 [Type: unsigned long]
        [+0x00c] Reserved         : 0x0 [Type: unsigned long]
        [+0x00c] Type             : 0x0 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [6]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x1ec7386dfbc [Type: unsigned __int64]
        [+0x008] Size             : 0x10 [Type: unsigned long]
        [+0x00c] Reserved         : 0x0 [Type: unsigned long]
        [+0x00c] Type             : 0x0 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
    [7]              [Type: _EVENT_DATA_DESCRIPTOR]
        [+0x000] Ptr              : 0x7fff9e2cd034 [Type: unsigned __int64]
        [+0x008] Size             : 0x2 [Type: unsigned long]
        [+0x00c] Reserved         : 0x0 [Type: unsigned long]
        [+0x00c] Type             : 0x0 [Type: unsigned char]
        [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
        [+0x00e] Reserved2        : 0x0 [Type: unsigned short]

The second data descriptor (element 1 in the array) provides information on how to decode the trace logging event (EVENT_DATA_DESCRIPTOR_TYPE_EVENT_METADATA).

0:002> dx -r1 (*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fa0))
(*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fa0))                 [Type: _EVENT_DATA_DESCRIPTOR]
    [+0x000] Ptr              : 0x1ec734f8cb0 [Type: unsigned __int64]
    [+0x008] Size             : 0x57 [Type: unsigned long]
    [+0x00c] Reserved         : 0x1 [Type: unsigned long]
    [+0x00c] Type             : 0x1 [Type: unsigned char]
    [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
    [+0x00e] Reserved2        : 0x0 [Type: unsigned short]
0:002> db 0x1ec734f8cb0
000001ec`734f8cb0  57 00 00 4d 43 50 43 6f-6e 73 65 6e 74 4f 75 74  W..MCPConsentOut
000001ec`734f8cc0  63 6f 6d 65 00 50 61 72-74 41 5f 50 72 69 76 54  come.PartA_PrivT
000001ec`734f8cd0  61 67 73 00 0a 41 63 74-69 76 69 74 79 49 64 00  ags..ActivityId.
000001ec`734f8ce0  01 43 6c 69 65 6e 74 49-64 00 01 53 65 72 76 65  .ClientId..Serve
000001ec`734f8cf0  72 49 64 00 01 4f 75 74-63 6f 6d 65 00 01 4d 65  rId..Outcome..Me
000001ec`734f8d00  73 73 61 67 65 00 01 00-02 00 00 00 00 00 00 00  ssage...........
000001ec`734f8d10  34 d0 2c 9e ff 7f 00 00-02 00 00 00 00 00 00 00  4.,.............
000001ec`734f8d20  04 00 00 00 00 00 00 50-e2 00 00 00 06 00 00 00  .......P........

In this case MCPConsentOutcome is the name of the event itself, it is not part of the metadata. The following fields are the actual event data descriptors (third and subsequent descriptors):

  • ActivityId
  • ClientId
  • ServerId
  • Outcome
  • Message

Note: PartA_PrivTags is a privacy tag field and hardcoded to a value of 0x1000000 in the log consent logger function in OSClient.NET.dll. This represents PDT_ProductAndServicePerformance.

ActivityId, in this case, is just a value of 0.

0:002> dx -r1 (*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fc0))
(*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fc0))                 [Type: _EVENT_DATA_DESCRIPTOR]
    [+0x000] Ptr              : 0x7fff9e2cd034 [Type: unsigned __int64]
    [+0x008] Size             : 0x2 [Type: unsigned long]
    [+0x00c] Reserved         : 0x0 [Type: unsigned long]
    [+0x00c] Type             : 0x0 [Type: unsigned char]
    [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
    [+0x00e] Reserved2        : 0x0 [Type: unsigned short]

0:002> db 0x7fff9e2cd034 L1
00007fff`9e2cd034  00   

ClientId, as you would expect, is WinDbg.

0:002> dx -r1 (*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fd0))
(*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fd0))                 [Type: _EVENT_DATA_DESCRIPTOR]
    [+0x000] Ptr              : 0x1ec734e59fc [Type: unsigned __int64]
    [+0x008] Size             : 0x60 [Type: unsigned long]
    [+0x00c] Reserved         : 0x0 [Type: unsigned long]
    [+0x00c] Type             : 0x0 [Type: unsigned char]
    [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
    [+0x00e] Reserved2        : 0x0 [Type: unsigned short]

0:002> dx (wchar_t*)0x1ec734e59fc
(wchar_t*)0x1ec734e59fc                 : 0x1ec734e59fc : "Microsoft.WinDbg_8wekyb3d8bbwe!Microsoft.WinDbg" [Type: wchar_t *]
    77 'M' [Type: wchar_t]

ServerId, as you would expect, is the target MCP server.

0:002> dx -r1 (*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fe0))
(*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520fe0))                 [Type: _EVENT_DATA_DESCRIPTOR]
    [+0x000] Ptr              : 0x1ec735908cc [Type: unsigned __int64]
    [+0x008] Size             : 0xd8 [Type: unsigned long]
    [+0x00c] Reserved         : 0x0 [Type: unsigned long]
    [+0x00c] Type             : 0x0 [Type: unsigned char]
    [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
    [+0x00e] Reserved2        : 0x0 [Type: unsigned short]

0:002> dx (wchar_t*)0x1ec735908cc
(wchar_t*)0x1ec735908cc                 : 0x1ec735908cc : "9f3a67cd-72e7-4e02-8769-3015e4c43f3f_czkckqegssqse_com.microsoft.windows.ai.mcpServer_MCP-Server-Sample-App" [Type: wchar_t *]
    57 '9' [Type: wchar_t]

Outcome is the string of the consent action which was taken. In our case, this was Allowed.

0:002> dx -r1 (*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520ff0))
(*((ntdll!_EVENT_DATA_DESCRIPTOR *)0x1ec73520ff0))                 [Type: _EVENT_DATA_DESCRIPTOR]
    [+0x000] Ptr              : 0x1ec7386dfbc [Type: unsigned __int64]
    [+0x008] Size             : 0x10 [Type: unsigned long]
    [+0x00c] Reserved         : 0x0 [Type: unsigned long]
    [+0x00c] Type             : 0x0 [Type: unsigned char]
    [+0x00d] Reserved1        : 0x0 [Type: unsigned char]
    [+0x00e] Reserved2        : 0x0 [Type: unsigned short]

0:002> dx (wchar_t*)0x1ec7386dfbc
(wchar_t*)0x1ec7386dfbc                 : 0x1ec7386dfbc : "Allowed" [Type: wchar_t *]
    65 'A' [Type: wchar_t]

Lastly, Message, which is a context structure to pass message data for conditional events, was empty.

There are also two dozen or so other TraceLogging events, some of these events occur when a tool call is made, when a tool is registered with the MCP server, when the MCP agents are listed/enumerated, when ODR is launched, etc.

Other MCP-related ETW events.

Reverse engineering of this provider's registration reveals that it's using the GUID {39C84785-FE74-4601-9E68-408A7EFAB941} and is named Windows-AI-MCP. There are two additional providers: Windows-AI-On-Device-Registry and Windows-AI-Agents, with GUIDs {86BA0849-C1D1-4F2F-9234-503B58EEE0FD} and {8C84D7D1-F1A1-46BD-A753-E865B9B7DA45} respectively.

0:000> k
 # Child-SP          RetAddr               Call Site
00 00000077`1c0ffac8 00007ffe`36159b4c     ntdll!EtwEventRegister
01 00000077`1c0ffad0 00007ffe`3615b079     OSClient_NET!Microsoft_Telemetry_Microsoft_Telemetry_EventProvider___ctor+0x9c
02 00000077`1c0ffb90 00007ffe`368ff3d1     OSClient_NET!Microsoft_Telemetry_Microsoft_Telemetry_TraceEventEtwProvider___ctor+0x49
03 00000077`1c0ffc50 00007ffe`368ff56c     OSClient_NET!WMSS_Logging_WMSS_Logging_EtwLog___ctor+0xc1
04 00000077`1c0ffcb0 00007ffe`36731d38     OSClient_NET!WMSS_Logging_WMSS_Logging_EtwLog___cctor+0x1c
05 00000077`1c0ffce0 00007ffe`36731bcd     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__EnsureClassConstructorRun+0xc8
06 00000077`1c0ffd60 00007ffe`368ff6d4     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__CheckStaticClassConstructionReturnGCStaticBase+0xd
07 00000077`1c0ffd90 00007ffe`3732c867     OSClient_NET!WMSS_Logging_WMSS_Logging_BaseEtwEvent__LogStart+0xd4
08 00000077`1c0ffdd0 00007ffe`363e6828     OSClient_NET!WMSS_Logging_WMSS_Logging_BaseEtwEvent__LogFunction<System___Canon>+0x17
09 00000077`1c0ffe10 00007ffe`363e3ad1     OSClient_NET!Odr_WMSS_OdrProcess__Main+0x118
0a 00000077`1c0ffe60 00007ff6`5dd61268     OSClient_NET!OSClient_NET_MegaAOT_Program__OdrMain+0x71
0b 00000077`1c0ffea0 00007ffe`9c8ce8d7     Odr!__scrt_common_main_seh+0x10c
0c 00000077`1c0ffee0 00007ffe`9e0ec48c     KERNEL32!BaseThreadInitThunk+0x17
0d 00000077`1c0fff10 00000000`00000000     ntdll!RtlUserThreadStart+0x2c
0:000> dx -r0 *(_GUID*)@rcx
*(_GUID*)@rcx                 : {39C84785-FE74-4601-9E68-408A7EFAB941} [Type: _GUID]

...

0:000> k
 # Child-SP          RetAddr               Call Site
00 00000077`1c0ffb90 00007ffe`368ff3d1     OSClient_NET!Microsoft_Telemetry_Microsoft_Telemetry_TraceEventEtwProvider___ctor+0x6e
01 00000077`1c0ffc50 00007ffe`368ff56c     OSClient_NET!WMSS_Logging_WMSS_Logging_EtwLog___ctor+0xc1
02 00000077`1c0ffcb0 00007ffe`36731d38     OSClient_NET!WMSS_Logging_WMSS_Logging_EtwLog___cctor+0x1c
03 00000077`1c0ffce0 00007ffe`36731bcd     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__EnsureClassConstructorRun+0xc8
04 00000077`1c0ffd60 00007ffe`368ff6d4     OSClient_NET!S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__CheckStaticClassConstructionReturnGCStaticBase+0xd
05 00000077`1c0ffd90 00007ffe`3732c867     OSClient_NET!WMSS_Logging_WMSS_Logging_BaseEtwEvent__LogStart+0xd4
06 00000077`1c0ffdd0 00007ffe`363e6828     OSClient_NET!WMSS_Logging_WMSS_Logging_BaseEtwEvent__LogFunction<System___Canon>+0x17
07 00000077`1c0ffe10 00007ffe`363e3ad1     OSClient_NET!Odr_WMSS_OdrProcess__Main+0x118
08 00000077`1c0ffe60 00007ff6`5dd61268     OSClient_NET!OSClient_NET_MegaAOT_Program__OdrMain+0x71
09 00000077`1c0ffea0 00007ffe`9c8ce8d7     Odr!__scrt_common_main_seh+0x10c
0a 00000077`1c0ffee0 00007ffe`9e0ec48c     KERNEL32!BaseThreadInitThunk+0x17
0b 00000077`1c0fff10 00000000`00000000     ntdll!RtlUserThreadStart+0x2c
0:000> dx -r0 (wchar_t*)(@rcx+0xc)
(wchar_t*)(@rcx+0xc)                 : 0x7ffe39535884 : "Windows-AI-MCP" [Type: wchar_t *]

In addition to the undocumented ETW providers just mentioned, Microsoft-Windows-Privacy-Auditing is also leveraged to log additional items of interest in CapabilityAccessManager.dll. The current events are:

  • DatabaseRecoverySuccessEvent
  • DatabaseRecoveryFailureEvent
  • DatabaseMigrationSuccessEvent
  • DatabaseMigrationFailureEvent
  • DatabaseCreationFailureEvent
  • CapabilityProvisionedSuccessEvent
  • CapabilityProvisionedFailureEvent
  • AccessCheckEvent
  • SystemGlobalDefaultCreatedSuccess
  • SystemGlobalDefaultCreatedFailureEvent
  • SystemGlobalChangedSuccessEvent
  • SystemGlobalChangedFailureEvent
  • SQLiteTraceEvent
  • SQLiteProfileEvent
  • ProvosionHandlerCalledSuccessEvent
  • ProvisionAnsweredFailureEvent
  • PromptAnsweredSuccessEvent
  • PromptAnsweredFailureEvent
  • PackageDeprovisionedSuccessEvent
  • UserGlobalDefaultCreatedSuccessEvent
  • UserGlobalDefaultCreatedFailureEvent
  • UserGlobalChangedFailureEvent
  • UserAppDefaultCreatedSuccessEvent
  • UserAppDefaultCreatedFAilureEvent
  • UserAppChangedSuccessEvent
  • UserAppChangedFailureEvent

Lastly, Windows Notification Facility, or WNF provides additional telemetry. WNF telemetry mostly surrounds the actual Capability Access Management service and fires if the SCM requests that the service updates its current state.

WNF ETW data

Additionally, each of the Capabilities in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\Capabilities Registry key contains the AccessChangeWnf value for which a notification can be published. The AccessChangeWnf contains the WNF_STATE_NAME_INTERNAL state name information to publish the notification to the correct location.

AccessChangeWnf

Conclusion

ODR and MCP on Windows is still very much in its infancy, and we can already see a rich feature set which includes WinRT interfaces and more than sufficient logging. Additionally, ODR goes much deeper than what has been documented in this blog post, including around how ODR both registers MCP servers and gates access to system resources by an unauthorized user. It will be very interesting to see how ODR and other AI infrastructure on Windows evolve as these features are ported to general availability.

Thank you for reading, and continue to follow along as we investigate the quickly growing world of computer use agents and surrounding infrastructure!