# MCP API

Perception's MCP server exposes the proc-API surface as JSON-RPC tools that any [Model Context Protocol](https://modelcontextprotocol.io/) client (Claude Code, Cline, Continue, ...) can call.

Writing an Enma script? Use [Proc](/perception/enma/proc-api.md) / [CPU](/perception/enma/cpu-api.md) / [Zydis](/perception/enma/zydis-api.md) directly. Driving perception from an AI agent? Enable MCP.

## Enable

In perception, **Settings → Perception MCP**:

1. Type a **Bind port** (1024..65535), or leave blank for OS-pick.
2. Toggle **Enable MCP server** on.
3. Copy the **Bound URL** that appears.

The server is loopback-only.

### Other toggles in the same panel

* **Auto-start on perception load** — persisted; on next launch the server starts automatically using the saved port (config loads before the autostart fires, so the saved port is reused rather than regenerated).
* **Heap-only scans by default** — controls the default of the `heap_only` flag on `scan_value` / `scan_string` / `scan_pointer_to` / `find_string_refs` when an MCP caller omits it. **On by default.** Flipping it off makes those tools walk the entire user-space when callers don't supply `heap_only`, which can OOM or hang on targets with multi-GiB heaps (Forza-class).

## Connect

**Claude Code:**

```
claude mcp add --transport http perception http://127.0.0.1:<port>/mcp
```

Add `--scope user` for global registration. Other clients (Cline, Continue, ...) accept the same URL via their Streamable HTTP transport.

## Transport

The server auto-detects two framings on the same port:

| First bytes                           | Framing                 | Used by                              |
| ------------------------------------- | ----------------------- | ------------------------------------ |
| `POST` / `GET` / `OPTIONS` / `DELETE` | HTTP/1.1 streamable     | MCP clients                          |
| anything else                         | Line-delimited JSON-RPC | The cpp example below, raw debugging |

Both carry JSON-RPC 2.0. Real MCP clients use the 5 protocol methods (`initialize` / `notifications/initialized` / `tools/list` / `tools/call` / `ping`); raw clients can call tool methods directly (`"method": "process/list"`).

## Handles

Most tools need a `handle` from `process/reference_by_pid` / `_by_name`. Handles are **per-connection**:

* Other connections can't use yours.
* Disconnecting releases everything automatically.
* Manual release: `process/dereference` (one) or `process/cleanup_references` (all).

## Permissions

Shared with [enma](/perception/enma/proc-api.md#permissions). Toggle in **Scripting → API permissions**:

| Flag                        | Gates                                                                                                                                                                                                                |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kernel_rw_access`          | Kernel-mode addresses in any read / write / disasm / `query_memory_region` / `find_pattern*` call; the `eprocess` field in `process/list` + `info_by_*`; the `ethread` field in `get_threads`; `system/list_drivers` |
| `write_memory`              | Every tool that writes target memory: `write_virtual_memory`, `write_typed_value`, `write_string`, `copy_memory`, `fill_memory`                                                                                      |
| `virtual_memory_operations` | `allocate_memory`, `free_memory`                                                                                                                                                                                     |

Blocked calls return `-32001` with the missing permission named.

## Error codes

| Code     | Meaning                         |
| -------- | ------------------------------- |
| `-32700` | Parse error                     |
| `-32600` | Invalid request                 |
| `-32601` | Method not found                |
| `-32602` | Invalid params                  |
| `-32603` | Internal                        |
| `-32001` | Permission denied               |
| `-32002` | Stale / cross-connection handle |
| `-32003` | Target not found                |
| `-32004` | Operation failed                |

## Tools

59 tools. Addresses + handles are **hex strings** (`"0x7ff7..."`) — JSON numbers lose precision past 2^53. Required params are listed plain, optional params get `?`.

Every "take a handle" tool below has `handle` as its first param — omitted from the params column to keep things readable. The tools that don't take a handle are called out per-section.

### Discovery + reference lifecycle

Params shown literally (no implicit `handle` — `process/dereference` explicitly takes the handle it's about to release).

| Tool                         | Params   |                                                   |
| ---------------------------- | -------- | ------------------------------------------------- |
| `process/list`               | —        | Snapshot of every active process.                 |
| `process/info_by_pid`        | `pid`    | One process by PID.                               |
| `process/info_by_name`       | `name`   | One process by image name.                        |
| `process/reference_by_pid`   | `pid`    | Take a per-connection handle. Returns hex string. |
| `process/reference_by_name`  | `name`   | Same, by image name.                              |
| `process/dereference`        | `handle` | Release one handle.                               |
| `process/cleanup_references` | —        | Release every handle this connection holds.       |
| `process/list_references`    | —        | What this connection currently holds.             |

### Memory I/O

| Tool                           | Params                                            |                                                                                   |
| ------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------- |
| `process/read_virtual_memory`  | `address`, `size`                                 | Raw bytes as hex. Max 16 MiB.                                                     |
| `process/write_virtual_memory` | `address`, `data`                                 | Gated `write_memory`. `data` is hex.                                              |
| `process/is_valid_address`     | `address`                                         | Does the address resolve?                                                         |
| `process/read_typed_value`     | `address`, `type`                                 | `type` ∈ `u8..u64 / i8..i64 / f32 / f64 / ptr / bool`.                            |
| `process/write_typed_value`    | `address`, `type`, `value`                        | Gated `write_memory`. Use hex string for `value` when type is u64/i64/ptr.        |
| `process/read_string`          | `address`, `max_len?`, `encoding?`                | `max_len` 1024 default. `encoding` ∈ `auto / ascii / utf16` (default auto-sniff). |
| `process/write_string`         | `address`, `text`, `encoding?`, `null_terminate?` | Gated `write_memory`. `encoding` ∈ `ascii / utf16`.                               |
| `process/copy_memory`          | `src_address`, `dst_address`, `size`              | In-target memcpy. Gated `write_memory`. Max 64 MiB, 1 MiB chunks.                 |
| `process/fill_memory`          | `address`, `size`, `byte`                         | Memset. `byte` 0..255 (0x90 = NOP, 0xCC = int3). Gated `write_memory`.            |
| `process/read_pointer_chain`   | `base_address`, `offsets`                         | `offsets` is an int array, max 64.                                                |
| `process/disassemble`          | `address`, `max_bytes?`, `max_instructions?`      | Zydis. Defaults 256 / 32.                                                         |

### Modules / threads / PE

| Tool                          | Params                                    |                                                                                                                                                                                                                            |
| ----------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `process/get_modules`         | —                                         | All loaded modules.                                                                                                                                                                                                        |
| `process/get_threads`         | —                                         | All threads.                                                                                                                                                                                                               |
| `process/get_module_by_name`  | `name`                                    | One module by name.                                                                                                                                                                                                        |
| `process/get_export_address`  | `module_base`, `export_name`              | Single resolve.                                                                                                                                                                                                            |
| `process/get_import_address`  | `module_base`, `import_name`              | Resolve IAT slot VA.                                                                                                                                                                                                       |
| `process/get_module_imports`  | `module_base`                             | Full IAT walk.                                                                                                                                                                                                             |
| `process/list_module_exports` | `module_base`                             | Full EAT walk.                                                                                                                                                                                                             |
| `process/get_module_sections` | `module_base`                             | PE sections.                                                                                                                                                                                                               |
| `process/get_pe_header`       | `module_base`                             | NT/optional header summary.                                                                                                                                                                                                |
| `process/get_module_strings`  | `module_base`, `min_length?`, `encoding?` | `min_length` default 4. `encoding` ∈ `ascii / utf16 / both` (default both).                                                                                                                                                |
| `process/get_exception_table` | `module_base`, `max_entries?`             | x64 RUNTIME\_FUNCTION entries from `.pdata`. Precise function bounds.                                                                                                                                                      |
| `process/get_data_directory`  | `module_base`, `directory`                | One PE data-dir entry. `directory` ∈ `export / import / resource / exception / security / basereloc / debug / architecture / globalptr / tls / load_config / bound_import / iat / delay_import / com_descriptor` or 0..15. |

### Memory regions + allocation

| Tool                               | Params       |                                                                                                                                                                                                   |
| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `process/query_memory_region`      | `address`    | VirtualQuery-style.                                                                                                                                                                               |
| `process/enumerate_memory_regions` | `heap_only?` | All committed regions. `heap_only` default false.                                                                                                                                                 |
| `process/allocate_memory`          | `size`       | Gated `virtual_memory_operations`. Max 256 MiB. Allocation itself is safe. To execute code from the returned VA the target must have Control Flow Guard (CFG) off; reads + writes are unaffected. |
| `process/free_memory`              | `address`    | Same gate.                                                                                                                                                                                        |

### Pattern + scanner + xrefs + signature

| Tool                         | Params                                                             |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ---------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `process/find_pattern`       | `start`, `size`, `signature`                                       | IDA-style `"AB CD ?? EF"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `process/find_all_patterns`  | `start`, `size`, `signature`                                       | Same, all hits (cap 1024).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `process/scan_value`         | `type`, `value`, `aligned?`, `heap_only?`                          | `type` ∈ `u8..u64 / i8..i64 / f32 / f64`. Use hex string for `value` when u64/i64. Defaults: aligned true. `heap_only` defaults to the MCP UI's "Heap-only by default" toggle (on by default — skips code/module regions); pass `heap_only=false` to walk full user-space.                                                                                                                                                                                                                                                                                                             |
| `process/scan_next`          | `compare`, `value?`, `min?`, `max?`                                | `compare` ∈ `exact / range / unchanged / changed / increased / decreased`. `value` for `exact`, `min`+`max` for `range`.                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `process/scan_string`        | `text`, `encoding?`, `heap_only?`                                  | `encoding` ∈ `ascii / utf16`, default ascii. `heap_only` default = UI toggle (see `scan_value`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `process/scan_pointer_to`    | `target_address`, `heap_only?`                                     | Aligned QWORDs pointing at `target_address`. `heap_only` default = UI toggle.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `process/find_xrefs`         | `module_base`, `target_address`                                    | Decode `.text`, return refs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `process/find_string_refs`   | `module_base`, `text`, `encoding?`, `heap_only?`, `string_module?` | Combo: scan for the string, then decode `module_base`'s `.text` for code refs to each hit. Phase 1 (string search) defaults to a heap-only VAD walk (`heap_only` follows the UI toggle) and is **pre-capped at 1 GiB** so the listener never crashes on huge targets — if the cap fires the call returns an error asking you to pass `heap_only=true` or set `string_module` (hex VA of the module that owns the string, usually the same as `module_base`) for a fast bounded scan of just that module's image. Phase 2 caps code hits at 4096; response includes a `truncated` flag. |
| `process/generate_signature` | `address`, `max_length?`                                           | Default 32. `is_unique=false` if length exhausted.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `process/diff_memory`        | `addr_a`, `addr_b`, `size`                                         | Cap 1 MiB.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

### Code analysis

| Tool                                 | Params                                   |                                                                             |
| ------------------------------------ | ---------------------------------------- | --------------------------------------------------------------------------- |
| `process/find_function_bounds`       | `address`, `scan_back?`, `scan_forward?` | Defaults 4096 / 65536. Heuristic — use `get_exception_table` for precision. |
| `process/find_function_by_signature` | `module_base`, `signature`               | AOB-scan a module's `.text` + run bounds walk on each hit.                  |
| `process/analyze_vtable`             | `vtable_address`, `max_entries?`         | Default 64. Classifies entries as code/data per loaded modules.             |
| `process/read_rtti`                  | `vtable_address`                         | Win64 RTTI: class name + base classes.                                      |

### Symbol / function lookup

| Tool                            | Params                                       |                                                                                          |
| ------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `process/lookup_symbol`         | `address`                                    | VA → `{module_base, module_name, module_offset, section, nearest_export}`.               |
| `process/find_function_by_name` | `pattern`, `case_sensitive?`, `max_results?` | Substring match across all modules' export tables. Default case-insensitive, 64 results. |

### Handles

| Tool                   | Params         |                                                                            |
| ---------------------- | -------------- | -------------------------------------------------------------------------- |
| `process/enum_handles` | `max_entries?` | Default 8192. `NtQuerySystemInformation(SystemExtendedHandleInformation)`. |

### System / environment

| Tool                       | Params         |                                                                                                              |
| -------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------ |
| `system/info`              | —              | Build number, page size, processor count + arch. `is_24h2_or_later` flag for build-keyed offsets. No handle. |
| `system/list_drivers`      | `max_entries?` | Kernel modules via `NtQuerySystemInformation(SystemModuleInformation)`. Gated `kernel_rw_access`. No handle. |
| `process/get_command_line` | —              | Reads `PEB.ProcessParameters.CommandLine`. x64 only.                                                         |
| `process/list_environment` | `max_bytes?`   | Reads `PEB.ProcessParameters.Environment`. Returns `[{key, value}]`.                                         |

### Enma scripting bridge

None of these takes a `handle` — they run a script (or return reference text) with its own permissions, independent of any referenced process.

| Tool                 | Params   |                                                                                                                                                                                                                                                                                                                                    |
| -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `script/get_context` | —        | Returns the full enma language + Perception API reference as a single `context` string. **Call this once per session before generating any script** — enma is proprietary and its addon surface can't be inferred from training data. Covers language grammar, all 17 pre-shipped enma addons, and all 12 Perception API surfaces. |
| `script/validate`    | `source` | Compile-only. **All** addons registered (render / proc / cpu / zydis / sound / win / unicorn / net / input / **gui** / **thread** / filesystem). Returns `{ ok, errors:[] }`.                                                                                                                                                      |
| `script/execute`     | `source` | Compile + run `main()` once. **GUI and thread addons are NOT registered** — those resources would outlive a one-shot script and leak. For long-lived scripts use the in-app script editor. Returns `{ ok, logs:[] }`.                                                                                                              |

## Example — minimal C++ client

Build with the VS Developer Command Prompt:

```
cl /EHsc /std:c++17 minimal_mcp.cpp /link Ws2_32.lib
```

```cpp
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")

int main(int argc, char** argv) {
    if (argc < 2) { printf("usage: %s <port>\n", argv[0]); return 1; }
    WSADATA wd; WSAStartup(MAKEWORD(2, 2), &wd);

    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in a{};
    a.sin_family = AF_INET;
    a.sin_port   = htons((u_short)atoi(argv[1]));
    inet_pton(AF_INET, "127.0.0.1", &a.sin_addr);
    if (connect(s, (sockaddr*)&a, sizeof(a)) != 0) { printf("connect failed\n"); return 1; }

    auto call = [&](const char* line) {
        send(s, line, (int)strlen(line), 0);
        char buf[8192];
        int n = recv(s, buf, sizeof(buf) - 1, 0);
        if (n > 0) { buf[n] = 0; printf("%s", buf); }
    };

    // 1. List processes.
    call("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"process/list\",\"params\":{}}\n");

    // 2. Reference notepad.exe.
    call("{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"process/reference_by_name\","
         "\"params\":{\"name\":\"notepad.exe\"}}\n");

    // 3. Run a one-shot enma script.
    call("{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"script/execute\","
         "\"params\":{\"source\":\"fn main() { println(\\\"hello from mcp\\\"); }\"}}\n");

    closesocket(s);
    WSACleanup();
    return 0;
}
```

Plain line-delimited JSON-RPC — the server's auto-detect routes us to that framing because we don't open with `POST` / `GET`. For the HTTP path, wrap each request in `POST /mcp HTTP/1.1\r\nContent-Length: N\r\n\r\n<body>` — that's what Claude Code does for you.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.perception.cx/perception/enma/mcp-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
