---
title: "MCP Security"
description: "Execution-layer security for MCP tool calls — deterministic attack detection, policy enforcement, and audit trails."
doc_version: "1.0"
last_updated: "2026-05-29"
canonical: "https://www.agentsh.org/docs/mcp-security/"
---

# MCP Security

## MCP Security

agentsh secures **Model Context Protocol (MCP)** tool calls. When AI agents use MCP servers, agentsh intercepts every tool call at the LLM proxy layer, evaluates it against configurable policies, detects cross-server attack patterns, and blocks or permits execution with full audit trails.

        Why MCP Security matters

MCP servers extend AI agent capabilities with external tools — databases, APIs, file systems. Without enforcement, a compromised or malicious MCP server can exfiltrate data, shadow legitimate tools, or orchestrate cross-server attacks. agentsh sits between the LLM and the agent runtime, ensuring every MCP tool call is evaluated before the agent can execute it.

### Architecture

MCP security operates at three layers: the **protocol inspection layer** scans tool definitions and results for poisoning patterns, the **LLM proxy layer** intercepts tool calls in LLM responses and evaluates cross-server attack patterns, and the **network monitor layer** enforces network-level policy on actual TCP connections made by MCP server processes.

_(Mermaid diagram omitted)_

1. Agent sends request to LLM via proxy

2. LLM responds with tool_use blocks

3. Proxy extracts tool calls (Anthropic/OpenAI format)

4. Each tool call checked: registry → cross-server → rate limit → version pin → policy

5. Blocked tools rewritten to text blocks; agent sees “[agentsh] Tool blocked”

6. Allowed tools pass through; protocol inspector scans definitions and results for poisoning patterns

7. Network monitor enforces TCP/HTTP policy on all outbound connections from MCP server processes

## Attack Detection

agentsh detects seven categories of attacks using a **session analyzer** that maintains a sliding window of tool call history, and a **pattern detector** that scans tool definitions and results. Rules are evaluated atomically to prevent time-of-check/time-of-use (TOCTOU) races.

### Tool Shadowing CRITICAL

Detects when a malicious MCP server registers a tool with the **same name** as a tool from a legitimate server. The attacker's tool “shadows” the original, intercepting calls meant for the real tool.

_(Mermaid diagram omitted)_

- **Detection:** Registry tracks tool name collisions across servers. Any duplicate name triggers an immediate block.

- **Severity:** Critical — all uses of the shadowed tool name are blocked.

- **Default:** Always enabled (cannot be disabled).

### Read-Then-Send CRITICAL

Detects when one MCP server reads sensitive data and a **different** server attempts to send data externally. This is the classic data exfiltration pattern.

_(Mermaid diagram omitted)_

- **Detection:** Tracks tool categories (read, write, send, compute) per server. When a “send”-category tool fires after a “read” from a different server, the send is blocked.

- **Window:** Configurable (default: 30 seconds).

- **Categories:** Tools are classified by name prefix — `read_`, `get_`, `fetch_` are “read”; `send_`, `post_`, `email_` are “send”.

### Cross-Server Flow HIGH

A broader version of read-then-send that detects suspicious data flows between servers. Catches write, compute, and unknown-category tools following reads from a different server.

- **Detection:** Blocks when write/send/compute/unknown tools follow reads from a different server within the window.

- **Window:** Configurable (default: 30 seconds).

- **Same-turn mode:** Optionally restrict to tool calls in the same LLM request turn (default: enabled).

### Burst Detection HIGH

Detects rapid-fire tool calls from a single server, suggesting automated data exfiltration or denial-of-service behavior.

- **Detection:** Per-server timestamp tracking. When a server exceeds `max_calls` within `window`, further calls are blocked.

- **Default:** 10 calls per 5-second window.

### Tool Poisoning CRITICAL

Detects hidden malicious instructions embedded in MCP tool **descriptions** and **input schemas**. A compromised or malicious server can inject prompt-injection payloads into tool metadata that the LLM reads and blindly follows.

_(Mermaid diagram omitted)_

The detector scans tool definitions using built-in regex patterns across five categories. Patterns are applied to tool definitions (description and input schema), tool call arguments, sampling request text content, and tool call results (when output inspection is enabled). Custom detection-pattern YAML is not a supported configuration surface in this build.

```yaml
# Supported MCP detection controls
sandbox:
  mcp:
    output_inspection:
      enabled: true
      on_detection: "alert"      # allow | alert | block
    sampling:
      policy: "block"           # allow | alert | block
```

#### Detection Pattern Categories

agentsh scans tool definitions, tool call arguments, and sampling request content for dangerous patterns across five severity categories:

| Category | Severity | Example Patterns |
| --- | --- | --- |
| Credential Theft | CRITICAL | `.ssh/id_`, `.env`, `/etc/passwd`, `/etc/shadow`, `api_key`, `secret_key`, `access_token` |
| Exfiltration | HIGH | `curl ... https://`, `wget`, `netcat`, `base64 \| curl`, pipe to `curl` |
| Hidden Instructions | HIGH | `IMPORTANT: before/always/must`, `IGNORE PREVIOUS`, `SYSTEM OVERRIDE`, `DO NOT SHOW` |
| Shell Injection | MEDIUM | `; cmd`, `&& cmd`, `$(cmd)`, backtick execution |
| Path Traversal | MEDIUM | `../../`, `/etc/`, `/root/`, `/home/user/.` (hidden files) |

- **Scan scope:** Tool `description` field and all string values within `inputSchema` (recursively walked with JSON path tracking).

- **Match context:** Each detection includes 50 characters of surrounding context for human review.

- **Custom patterns:** Add your own regex patterns via configuration (see below).

### Tool Output Poisoning CRITICAL

Scans every tool call **result** returned by MCP servers for prompt injection payloads. A compromised server can embed hidden instructions in its output that the LLM interprets as commands.

_(Mermaid diagram omitted)_

- **Detection:** Scans all text content blocks from `tools/call` responses using the same pattern engine as Tool Poisoning.

- **Patterns:** Hidden instructions (`SYSTEM OVERRIDE`, `IGNORE PREVIOUS`), exfiltration URLs (`curl`, `wget`, `https://`), credential references.

- **Action:** Configurable — `alert` (emit event, allow result) or `block` (return JSON-RPC error to client instead of the poisoned result).

```yaml
sandbox:
  mcp:
    output_inspection:
      enabled: true
      on_detection: "block"      # allow | alert | block
```

### Server Name Typosquatting HIGH

Detects when a new MCP server registers with a name that is **suspiciously similar** to an existing server. An attacker can register `notes-servar` to impersonate `notes-server`, hoping the LLM routes tool calls to the impostor.

_(Mermaid diagram omitted)_

- **Algorithm:** Levenshtein edit distance normalized to a similarity score between 0.0 (completely different) and 1.0 (identical).

- **Threshold:** Configurable (default: 0.85). Names exceeding the threshold trigger an `mcp_server_name_similarity` event.

- **Scope:** Compares against all known server IDs from the session’s sliding window, burst tracking, and shadow entries.

- **Exact matches:** Return score 1.0 immediately (handled as duplicates, not typosquatting).

```yaml
sandbox:
  mcp:
    cross_server:
      shadow_tool:
        similarity_check: true        # Enable typosquatting detection
        similarity_threshold: 0.85  # Score range: 0.0–1.0
```

### Attack Summary

| Attack | Rule | Severity | What It Catches |
| --- | --- | --- | --- |
| Tool Shadowing | `shadow_tool` | CRITICAL | Attacker server overwrites a legitimate tool name |
| Data Exfiltration | `read_then_send` | CRITICAL | Server A reads secrets, Server B sends them out |
| Suspicious Data Flow | `cross_server_flow` | HIGH | Write/compute follows cross-server read |
| Rapid Exfiltration | `burst` | HIGH | Rapid-fire tool calls from one server |
| Tool Poisoning | `tool_poisoning` | CRITICAL | Hidden instructions or credential theft in tool descriptions |
| Output Poisoning | `output_poisoning` | CRITICAL | Prompt injection in tool call results |
| Tool Rug Pull | `version_pin` | CRITICAL | Tool definition or binary silently changed after initial registration |
| Server Typosquatting | `name_similarity` | HIGH | Server name impersonates a legitimate server via similar spelling |

## Tool Call Interception

The LLM proxy intercepts every tool call in LLM responses before the agent can execute them. It supports both **Anthropic** and **OpenAI** response formats, for both buffered and streaming (SSE) responses.

### Evaluation Order

Each tool call is evaluated through a chain of checks. The first check that blocks wins:

_(Mermaid diagram omitted)_

### Response Rewriting

When tool calls are blocked, the proxy rewrites the LLM response so the agent sees a blocked message instead of the tool call.

| Scenario | Anthropic Behavior | OpenAI Behavior |
| --- | --- | --- |
| All tools blocked | `tool_use` blocks → `text` blocks; `stop_reason` → `"end_turn"` | `tool_calls` removed; `content` set to blocked msg; `finish_reason` → `"stop"` |
| Some tools blocked | Blocked `tool_use` → `text`; `stop_reason` stays `"tool_use"` | Blocked calls removed from array; `finish_reason` stays `"tool_calls"` |

The agent sees: `[agentsh] Tool 'send_email' blocked by policy`

### SSE Streaming Interception

For streaming responses (Server-Sent Events), agentsh evaluates tool calls in real-time as SSE chunks arrive, without buffering the entire response. Blocked tool events are **suppressed mid-stream** and replaced with text blocks.

- **Anthropic SSE:** Intercepts at `content_block_start` events. Blocked tool_use blocks are suppressed and replaced with text content blocks.

- **OpenAI SSE:** Intercepts at the first delta chunk containing a tool call ID and function name. Subsequent argument chunks for blocked tools are filtered out.

- **Zero buffering:** Line-by-line SSE parsing with a 256KB buffer. No waiting for the full response.

### Sampling Request Control

MCP servers can request LLM access via `sampling/createMessage`. agentsh intercepts these requests and applies policy before they reach the model.

| Policy | Behavior |
| --- | --- |
| `block` | Reject the sampling request (default) |
| `alert` | Allow but emit an audit event |
| `allow` | Allow the sampling request |

```yaml
sandbox:
  mcp:
    sampling:
      policy: "block"          # Default: block all sampling requests
      per_server:
        trusted-server: "allow"  # Override for specific servers
        analytics: "alert"       # Allow with audit logging
```

Message content is scanned for hidden instructions before the sampling request is processed. Rate limits apply to sampling calls — they count against the server's rate limit budget.

## Policy Configuration

MCP security is configured in the `sandbox.mcp` section of your agentsh configuration, or in the `mcp_rules` section of a policy file.

```yaml
# agentsh.yaml
sandbox:
  mcp:
    enforce_policy: true       # Master switch for MCP enforcement
    fail_closed: false         # Block unknown tools (not in any rule)?
```

### Server Policies

Control which MCP servers are allowed to provide tools. Evaluated **before** tool-level policies.

| Mode | Behavior |
| --- | --- |
| `allowlist` | Only listed servers allowed; all others denied |
| `denylist` | Listed servers denied; all others allowed |
| `none` | No server-level filtering (default) |

```yaml
sandbox:
  mcp:
    server_policy: "allowlist"
    allowed_servers:
      - id: "internal-*"         # Glob patterns supported
      - id: "llm-tools"
    denied_servers:              # Used when server_policy: "denylist"
      - id: "untrusted_*"
```

### Tool Policies

Fine-grained control over individual tools. Each rule matches on server pattern, tool pattern, and optional content hash.

| Mode | Behavior |
| --- | --- |
| `allowlist` | Only listed tools allowed; others denied |
| `denylist` | Listed tools denied; others allowed |
| `none` | No tool-level filtering (default) |

```yaml
sandbox:
  mcp:
    tool_policy: "allowlist"
    allowed_tools:
      - server: "database"        # Server glob pattern
        tool: "query_*"           # Tool glob pattern
        content_hash: "sha256:1a2b3c..."  # Optional hash pin
      - server: "filesystem"
        tool: "read_file"
      - server: "notes"
        tool: "write_note"
    denied_tools:                # Used when tool_policy: "denylist"
      - server: "*"
        tool: "exec_*"            # Block all exec tools from any server
```

        Rule matching

All three fields (server, tool, content_hash) must match for a rule to fire. Server and tool fields use **glob patterns** (`*`, `?`). Content hash is an exact match when specified, or ignored when empty.

## Version Pinning

Detect when MCP tool definitions change after initial registration. This catches supply-chain attacks where a tool's behavior is silently modified.

```yaml
sandbox:
  mcp:
    version_pinning:
      enabled: true
      on_change: "block"         # block | alert | allow
      auto_trust_first: true    # Pin hash on first use
      pin_binary: true          # Also pin the server executable
```

| Option | Description |
| --- | --- |
| `on_change: "block"` | Reject tool calls if the definition hash differs from the pinned hash |
| `on_change: "alert"` | Allow the call but emit a version-pin alert event |
| `on_change: "allow"` | Ignore hash changes entirely |
| `auto_trust_first` | Automatically pin the hash when a tool is first seen (no manual pinning required) |
| `pin_binary` | Also compute and pin a SHA-256 hash of the MCP server executable (see Binary Pinning) |

You can also manually manage pins via the CLI:

```bash
# Pin a tool at its current hash
agentsh mcp pins trust --server my-server --tool read_file --hash abc123

# Show difference between pinned and current version
agentsh mcp pins diff --server my-server --tool read_file

# List all pinned tool versions
agentsh mcp pins list --json

# Remove a tool's version pin
agentsh mcp pins reset --server my-server --tool read_file
```

### Binary Pinning (TOCTOU Protection)

When `pin_binary: true` is set, agentsh also computes a **SHA-256 hash of the MCP server executable** itself. This prevents an attacker from replacing the server binary between sessions or between verification and execution (TOCTOU attack).

        How TOCTOU prevention works

When agentsh hashes a server binary, it resolves the command to an **absolute path** and stores both the path and hash. At execution time, the stored absolute path is used directly — bypassing PATH lookup entirely. This ensures the exact binary that was verified is the binary that runs, even if an attacker manipulates PATH between verification and execution.

| Pin Status | auto_trust_first: true | auto_trust_first: false |
| --- | --- | --- |
| `match` | Proceed normally | Proceed normally |
| `not_pinned` | Pin hash and proceed | Apply `on_change` policy |
| `mismatch` | Apply `on_change` policy | Apply `on_change` policy |

When `on_change` is `"block"`, a binary hash mismatch prevents the MCP server from starting entirely. When `"alert"`, the server starts but an `mcp_server_binary_mismatch` event is emitted.

### TLS Fingerprint Pinning

Pin HTTP and SSE MCP servers to a specific TLS public key using SPKI (Subject Public Key Info) fingerprints. Unlike certificate pinning, SPKI pinning survives certificate renewals as long as the key pair remains the same.

```yaml
sandbox:
  mcp:
    servers:
      - id: "secure-api"
        type: "http"
        url: "https://mcp.example.com"
        tls_fingerprint: "sha256:a1b2c3d4e5f6..."  # SPKI SHA-256 hash (64 hex chars)
```

The fingerprint is the SHA-256 hash of the server certificate's Subject Public Key Info in DER format. To extract it from a running server:

```text
# Extract SPKI fingerprint from a server
openssl s_client -connect mcp.example.com:443 < /dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -hex
```

If the fingerprint field is empty or omitted, no TLS pinning is applied. On mismatch, the connection is rejected and an audit event is emitted.

## Rate Limiting

Enforce per-server call rate limits to prevent abuse and contain runaway tool usage.

```yaml
sandbox:
  mcp:
    rate_limits:
      enabled: true
      default_rpm: 100          # Default calls per minute
      default_burst: 10        # Default burst allowance
      per_server:               # Per-server overrides
        external-api:
          calls_per_minute: 10
          burst: 2
        internal-tools:
          calls_per_minute: 500
          burst: 50
```

### Cross-Server Detection Configuration

Fine-tune the cross-server attack detection rules:

```yaml
sandbox:
  mcp:
    cross_server:
      enabled: true             # Master switch for cross-server detection
      shadow_tool:
        enabled: true           # Tool name collision detection (always recommended)
      read_then_send:
        enabled: true
        window: "30s"           # Time window for read→send correlation
      burst:
        enabled: true
        max_calls: 10          # Maximum calls before triggering
        window: "5s"           # Burst detection window
      cross_server_flow:
        enabled: true
        same_turn_only: true   # Only flag flows within the same LLM request
        window: "30s"           # Cross-server flow detection window
```

## Environment Variable Filtering

Control which environment variables are passed to MCP server processes. This prevents credential leakage to untrusted servers — an MCP server should not have access to `AWS_SECRET_ACCESS_KEY` or `ANTHROPIC_API_KEY` unless explicitly permitted.

```yaml
sandbox:
  mcp:
    servers:
      - id: "github-server"
        command: "npx"
        args: ["-y", "@modelcontextprotocol/server-github"]
        env_allow: ["GITHUB_TOKEN"]  # Only GITHUB_TOKEN + standard vars

      - id: "data-server"
        command: "python"
        args: ["server.py"]
        env_deny: ["AWS_*", "ANTHROPIC_API_KEY"]  # Strip these, pass all others
```

| Mode | Behavior |
| --- | --- |
| `env_allow` set | Only listed variables (plus standard OS variables) are passed. Sensitive-suffix variables are auto-stripped unless explicitly listed. |
| `env_deny` set | Listed variables are stripped; all others pass through. |
| Both empty | Full passthrough (backward compatible). |
| Both set | Allowlist takes precedence. |

**Standard variables** are always passed through, even in allowlist mode: `PATH`, `HOME`, `USER`, `SHELL`, `TERM`, `LANG`, `TMPDIR`, `XDG_RUNTIME_DIR` (and Windows equivalents like `SYSTEMROOT`, `COMSPEC`, `TEMP`).

**Sensitive suffix auto-stripping** (allowlist mode only): variables ending in `_TOKEN`, `_KEY`, `_SECRET`, `_API_KEY`, `_PASSWORD`, or `_CREDENTIALS` are automatically removed unless explicitly listed in `env_allow`.

        Security

Stripped variable names are logged for debugging, but their **values are never logged**. On Windows, environment variable names are case-insensitive and normalized to uppercase for comparison.

## Transport Policy

Restrict which transport types MCP servers can use. When `allowed_transports` is set, servers using other transports are rejected at config load time.

```yaml
sandbox:
  mcp:
    allowed_transports:        # Empty = all allowed (default)
      - "stdio"                 # Local process communication
      - "sse"                   # Server-sent events over HTTP
```

| Transport | Description |
| --- | --- |
| `stdio` | Local subprocess communication via stdin/stdout |
| `http` | HTTP request/response |
| `sse` | Server-sent events over HTTP (streaming) |

If a declared server's `type` does not match `allowed_transports`, agentsh returns a configuration error and refuses to start.

## MCP Policy Generation

The **profile-then-lock** workflow extends to MCP tools. After running a session, `policy generate` produces a policy that includes an `mcp_rules` section with all observed tools, servers, and content hashes.

```bash
# Generate a policy including MCP rules from observed behavior
agentsh policy generate "$SID" --output=locked.yaml

# The generated policy includes mcp_rules:
# - Allowed tools with content hashes for version pinning
# - Allowed servers
# - Blocked tools as comments for review
# - Version pinning and cross-server rules enabled
```

### Example Generated MCP Policy

```text
mcp_rules:
  enforce_policy: true
  tool_policy: "allowlist"
  allowed_tools:
    # Provenance: 2 events (10:30:17 - 10:30:18)
    - server: "notes"
      tool: "read_note"
      content_hash: "sha256:a1b2c3"
    # Provenance: 1 events (10:30:17)
    - server: "notes"
      tool: "write_note"
      content_hash: "sha256:d4e5f6"
  server_policy: "allowlist"
  allowed_servers:
    - id: "notes"
    - id: "web-search"
  # --- Blocked tools (uncomment to allow) ---
  # denied_tools:
  #   - server: "web-search"
  #     tool: "fetch_url"
  version_pinning:
    enabled: true
    on_change: "block"
    auto_trust_first: true
  cross_server:
    enabled: true
    read_then_send:
      enabled: true
```

## CLI Commands

```bash
# List registered MCP tools
agentsh mcp tools --json

# List known MCP servers
agentsh mcp servers

# Query tool call interception events
agentsh mcp calls --session $SID --action block

# Query MCP-related events
agentsh mcp events --session $SID --type mcp_tool_changed --since 1h

# Show tools with security detections
agentsh mcp detections --severity high

# Pin management
agentsh mcp pins trust --server my-server --tool read_file --hash abc123
agentsh mcp pins diff --server my-server --tool read_file
agentsh mcp pins list --json
agentsh mcp pins reset --server my-server --tool read_file
```

### REST API

Query MCP state programmatically via the REST API. All endpoints require authentication unless `dev.disable_auth` is set.

| Endpoint | Description | Query Parameters |
| --- | --- | --- |
| `GET /api/v1/mcp/tools` | List discovered MCP tools | `server` — filter by server ID `detections` — filter tools with detections |
| `GET /api/v1/mcp/servers` | List MCP server summaries | None |

```bash
# List all tools from a specific server
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/v1/mcp/tools?server=my-server

# List tools with detections only
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/v1/mcp/tools?detections=true

# List all MCP servers
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/v1/mcp/servers
```

## MCP Event Reference

All MCP events are emitted to the session event store and can be forwarded via OpenTelemetry.

| Event Type | When Emitted | Key Fields |
| --- | --- | --- |
| `mcp_tool_seen` | Tool registered for first time | serverID, toolName, toolHash, description |
| `mcp_tool_changed` | Tool definition changes | serverID, toolName, previousHash, newHash |
| `mcp_tool_called` | Tool call in MCP request | serverID, toolName, input |
| `mcp_tool_call_intercepted` | Tool call evaluated by proxy | action (allow\|block), reason, serverID, toolHash |
| `mcp_cross_server_blocked` | Cross-server rule fired | rule, severity, relatedCalls |
| `mcp_network_connection` | Network connection to known MCP server | serverID, serverAddr, protocol |
| `mcp_detection` | Security anomaly detected in tool definition | detectionType, severity, description |
| `mcp_server_name_similarity` | Server name suspiciously similar to a known server | serverID, similarTo, score |
| `mcp_output_inspection` | Tool result scanned for prompt injection (output poisoning) | serverID, toolName, action (allow\|block), detections[] |
| `mcp_server_binary_mismatch` | Server executable hash differs from pinned hash | serverID, binaryPath, expectedHash, actualHash |

### Full Configuration Reference

```yaml
sandbox:
  mcp:
    enforce_policy: true           # Enable MCP enforcement (default: false)
    fail_closed: false             # Block unknown tools (default: false)

    # Server-level policy
    server_policy: "allowlist"     # allowlist | denylist | none
    allowed_servers:
      - id: "internal-*"
    denied_servers:
      - id: "untrusted_*"

    # Tool-level policy
    tool_policy: "allowlist"       # allowlist | denylist | none
    allowed_tools:
      - server: "database"
        tool: "query_*"
        content_hash: "sha256:..."
    denied_tools:
      - server: "*"
        tool: "exec_*"

    # Version pinning
    version_pinning:
      enabled: true
      on_change: "block"           # block | alert | allow
      auto_trust_first: true
      pin_binary: true             # Pin server executable hash (TOCTOU protection)

    # Rate limiting
    rate_limits:
      enabled: true
      default_rpm: 100
      default_burst: 10
      per_server:
        external-api:
          calls_per_minute: 10
          burst: 2

    # Cross-server attack detection
    cross_server:
      enabled: true
      shadow_tool:
        enabled: true
        similarity_check: true     # Typosquatting detection
        similarity_threshold: 0.85 # Levenshtein similarity threshold
      read_then_send:
        enabled: true
        window: "30s"
      burst:
        enabled: true
        max_calls: 10
        window: "5s"
      cross_server_flow:
        enabled: true
        same_turn_only: true
        window: "30s"

    # Tool result inspection
    output_inspection:
      enabled: true
      on_detection: "block"        # allow | alert | block

    # Per-server declarations
    servers:
      - id: "my-server"
        command: "npx"
        args: ["-y", "@modelcontextprotocol/server-example"]
        env_allow: ["GITHUB_TOKEN"]  # Allowlist: only these + standard vars
        env_deny: ["AWS_*"]          # Denylist: strip these vars
```

## Sitemap

- [Canonical HTML](https://www.agentsh.org/docs/mcp-security/)
- [Site map](https://www.agentsh.org/sitemap.md)
- [Full documentation](https://www.agentsh.org/llms-full.md)
