How @ References Resolve Local Files and MCP Resources in DeepSeek-Reasonix Chat

DeepSeek-Reasonix resolves @ references through a three-stage pipeline in internal/control/refs.go that extracts tokens, classifies them as MCP resources or local files, and injects sanitized content into chat context before the turn reaches the model.

In the esengine/DeepSeek-Reasonix repository, any token prefixed with @ triggers a resolution process that transforms bare filenames or server identifiers into concrete context. The implementation lives primarily in internal/control/refs.go and handles everything from MCP resources (@server:uri) to local files, directories, and image attachments, enforcing workspace sandboxing and size limits throughout.

The Three-Stage Resolution Pipeline

The resolution logic follows a strict pipeline: token extraction, classification, and content injection. Each stage is designed to safely expand user input into model-ready markup without exposing the underlying filesystem beyond the workspace root.

Stage 1: Token Extraction with parseRefTokens

The process begins with parseRefTokens, which scans the input line using the regular expression @([^\s]+). This captures any non-whitespace string following an @ symbol. The function returns a deduplicated list of raw tokens, automatically stripping trailing punctuation to ensure @README.md and @README.md: are treated as the same reference.

Stage 2: Classification via classifyRef and detectRefsMode

Once extracted, each token is classified by classifyRef (or the inline logic of detectRefsMode). The classifier checks three distinct categories:

  • MCP Resource (refResource): Tokens matching the pattern @<server>:<uri> where <server> exists in c.host.ServerNames(). These reference external resources provided by connected Model Context Protocol servers.
  • Local File or Directory (refFile): Tokens that resolve to an existing path inside the workspace via workspaceRefPath and os.Stat. This includes directories, which receive special handling.
  • Image Attachment (refImage): Image files recognized by extension or MIME type that should be attached to the turn for vision-capable models.

Tokens that do not match these patterns (such as email addresses or @-mentions) are ignored and passed through unchanged.

Stage 3: Content Injection with resolveRefs

The final stage orchestrated by resolveRefs (and resolveScopedRefs) iterates over classified references and generates XML-like tags that the model consumes:

  • MCP resources: Calls c.host.ReadResource(ctx, r.server, r.uri) and wraps the result in <resource ref="@server:uri">…</resource> tags.
  • Files and directories: Invokes readFileRef(r.path, c.cpRoot). If the path is a directory, it produces a truncated listing (maximum 100 entries) inside <dir> tags. Files are wrapped in <file> tags.
  • Images: Emits <image path="…"> tags indicating an attachment available for multimodal models.

Security and Workspace Sandboxing

Before any filesystem access occurs, resolveAbsRef sanitizes user-supplied paths against the controller’s workspace root (c.cpRoot). This prevents directory traversal attacks (e.g., ../../../etc/passwd) by resolving the absolute path and ensuring it remains within the allowed workspace boundary. The detectRefsMode function accepts a boolean flag to toggle between scoped (workspace-only) and unscoped resolution, with the chat interface typically enforcing scoped mode for safety.

Handling Different Resource Types

MCP Resources

When a user references an MCP resource using the @<server>:<uri> syntax, the system validates the server name against active connections. The ReadResource call returns the content, which is injected directly into the prompt context. This allows the model to consume live data from external tools without the user manually copying content.

Files and Directories

For local files, readFileRef applies several safeguards:

  • Size limits: Text files larger than maxFileRefBytes (64 KB) are truncated with a clear marker indicating the omission.
  • Directory listings: Recursive directory scans are capped at 100 entries to prevent context window overflow.
  • Binary files: Non-text files are replaced with a placeholder indicating the file type and size rather than raw bytes.

Images and PDFs

Image attachments receive special treatment: instead of injecting base64 data into the text context, the system emits <image path="docs/arch.png"> tags that signal the frontend to attach the actual image file to the API request for vision models.

PDF files are handled by readPDFRef, which attempts to extract text via the pdftotext command-line tool, falling back to a Python-based extractor if necessary. This allows users to reference documentation or specifications directly without manual conversion.

Resolution Flow Implementation

The following simplified Go code illustrates how ResolveRefs orchestrates the complete pipeline:

func (c *Controller) ResolveRefs(ctx context.Context, line string) (block string, errs []string) {
    // 1️⃣ Detect and classify all @‑tokens.
    refs := c.detectRefsMode(line, false)      // false → allow un‑scoped refs
    // 2️⃣ Resolve bare filenames that exist somewhere under the workspace.
    refs = resolveBareNames(refs, c.cpRoot)

    var b strings.Builder
    for _, r := range refs {
        switch r.kind {
        case refResource: // MCP resource
            txt, err := c.host.ReadResource(ctx, r.server, r.uri)
            if err != nil { errs = append(errs, "@"+r.raw+" — "+err.Error()); continue }
            appendRefBlock(&b, "resource", `ref="`+r.raw+`"`, txt)

        case refFile: // local file / directory
            txt, isDir, err := readFileRef(r.path, c.cpRoot)
            if err != nil { errs = append(errs, "@"+r.raw+" — "+err.Error()); continue }
            tag := "file"
            if isDir { tag = "dir" }
            appendRefBlock(&b, tag, `path="`+r.path+`"`, txt)

        case refImage: // image attachment
            appendRefBlock(&b, "image", `path="`+r.path+`"`,
                "[image attachment available at @"+r.path+" …]")
        }
    }
    return b.String(), errs
}

Summary

  • Token extraction in internal/control/refs.go uses regex @([^\s]+) to find references, stripping trailing punctuation and deduplicating results.
  • Classification distinguishes MCP resources (@server:uri) from local files and images using classifyRef and filesystem checks against the workspace root.
  • Security is enforced by resolveAbsRef, which prevents path traversal by validating all paths against c.cpRoot.
  • Content injection converts resources into XML-like tags (<resource>, <file>, <dir>, <image>) with size limits (64 KB for text, 100 entries for directories) and special handling for PDFs via readPDFRef.

Frequently Asked Questions

How does DeepSeek-Reasonix prevent path traversal attacks when resolving @ references?

The resolveAbsRef function sanitizes every user-supplied path by resolving it against the controller’s workspace root (c.cpRoot). It validates that the final absolute path remains within the workspace boundary, rejecting any attempts to use .. sequences to escape the sandbox. This ensures that @../../../etc/passwd resolves safely or fails outright rather than exposing sensitive system files.

What is the difference between @ references for files versus MCP resources?

Local file references (e.g., @README.md) are resolved against the workspace filesystem using os.Stat and workspaceRefPath, then injected as <file> or <dir> tags. MCP resources (e.g., @modelcontextprotocol:specs/api.md) use the @<server>:<uri> syntax, trigger a lookup against connected server names via c.host.ServerNames(), and call ReadResource to fetch content from external tools, wrapping results in <resource> tags.

Are there size limits when including files via @ references?

Yes. Text files are truncated to maxFileRefBytes (64 KB) with a truncation marker indicating omitted content. Directories are limited to 100 entries in their recursive listing. Binary files are not inlined; instead, a placeholder description is inserted. These limits prevent individual references from consuming excessive context window space.

How are PDF files handled differently from plain text files?

When a user references a PDF, the system invokes readPDFRef, which attempts text extraction via the pdftotext command-line utility, falling back to a Python-based extractor if necessary. The extracted text is then treated like a standard text file (subject to the 64 KB limit), whereas plain text files are read directly without conversion. This allows PDF content to be searchable and consumable by the model without requiring manual pre-processing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →