Skip to content
RAGRAF

Setup guide

Connecting RAGRAF to DeepSeek Harness

--agent-kind external MCP · stdio · JSON-RPC mcp__ragraf__ragraf_* verified by measurements · dsh 0.2.0-rc.2

The DeepSeek Harness 0.2.0-rc.2 Electron app with a built-in MCP client (the @deepseek-ai/dsh-mcp-client plugin). RAGRAF connects to it as an MCP server over stdio; its tools surface as mcp__ragraf__ragraf_*. The pairing was debugged end to end and confirmed by live-session measurements: everything below is reproducible commands, paths and measured outputs.

  • dsh 0.2.0-rc.2 · Electron
  • 24 ragraf_* tools
  • 3 RAGRAF fixes
  • 4 verification levels

Notation in commands: $repo — path to the local RAGRAF clone (the snippets set it on the first line), $dsh — the CLI bundled with the app. Commands and outputs are quoted as measured — including the Russian strings the product prints.

Source: the integration-session document "RAGRAF × DeepSeek Harness" — reproducible commands, paths and measured outputs of a live session (dsh 0.2.0-rc.2).

How it works

Why stdio, not streamable-http

The HARNESS MCP client declares capabilities: {} — the server never receives roots. Serve RAGRAF over HTTP as a single service and there is no way to tell "which project is this": the client's current directory never reaches the server. A stdio server knows its process working directory, and the corpus is resolved through the development registry.

Why one server for all projects

The HARNESS client does not let RAGRAF tools restart it, and keeping one server per project would mean keeping a copy of the index in memory for each. Instead, the corpus is switched with the mcp__ragraf__use_project operation, and the default corpus is resolved from the working directory via the registry. If none is found, the server still starts, answers catalog and use_project honestly, and refuses the remaining operations with a reason.

WhatWhere
Server entry pointtools/ragraf-mcp.py · репозиторий RAGRAF
24 built-in toolssrc/ragraf/mcp_server.py
Operations facade (22)src/ragraf/facade.py
Corpus registrycatalog/catalog.json
Profile patch layer%USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml
Skill%USERPROFILE%\.dsh\skills\ragraf-core\SKILL.md
Global rules%USERPROFILE%\.dsh\AGENTS.md · раздел 6

What was fixed in RAGRAF

The defect was not "inconvenient" but "does not work at all": the very first response containing Russian text killed the server. Measured by running it — three causes, each confirmed by a measurement.

The defect as measured
UnicodeEncodeError: 'charmap' codec can't encode characters in position 97-105
  File "src/ragraf/mcp_server.py", line 551, in send
    sys.stdout.write(json.dumps(message, ensure_ascii=False) + "\n")
  1. Stream encodingThe MCP SDK passes only 12 variables to the child process (APPDATA, PATH, TEMP, USERPROFILE and friends — DEFAULT_INHERITED_ENV_VARS), and HARNESS additionally scrubs names containing PASSWORD|SECRET|TOKEN|KEY plus every DSH_*. Neither PYTHONUTF8 nor PYTHONIOENCODING is in that set — the process locale stays cp1252. Fix: force_utf8_stdio() reconfigures stdin, stdout and stderr to UTF-8 without relying on the environment.
  2. Dependenciesfastembed, PyYAML, sqlite_vec, tree_sitter and ijson were reachable to the PATH interpreter only through PYTHONUSERBASE — HARNESS scrubs it, and the server reports "PyYAML is not installed". Solution: the server is launched by the project's own .venv interpreter — measured to import every package even with all PYTHON* variables removed.
  3. Mandatory --projectA client that starts the server without arguments could not start it at all. Fix: --project is no longer required — the corpus is resolved from the working directory through the registry (_resolve_default_project()), the ragraf_use_project tool was added for manual switching, and --agent is now also read from the RAGRAF_AGENT variable.
FileWhat was done
src/ragraf/mcp_server.pyforce_utf8_stdio(); --project no longer required; _resolve_default_project(); the ragraf_use_project tool; --agent also taken from RAGRAF_AGENT; refusal without a selected corpus — a readable message instead of ConfigError
src/ragraf/cli.pythe reindex command now records the run in the registry, like index does
tests/test_mcp_compat.pynew file: checks for the scrubbed environment, corpus autodetection and switching
tools/Invoke-ProjectDoctrine.ps1the Test-McpClientCompatibility and Test-ExternalOpsAreRecorded checks; the registry-coverage rule now inspects every function that launches the indexer, not the whole file (16 checks instead of 14)
README.md · INTEGRATION.md · AGENTS.mdnumbers aligned with the code: 24 tools; the "three traps" documented with measurements

Compatibility with previous clients is preserved: AutoClaw and SagaAI pass --project explicitly and set PYTHONIOENCODING — nothing changed for them.

Installation — three steps

All three steps place files into the HARNESS home directory (%USERPROFILE%\.dsh or $DSH_HOME); the ready-made blocks ship in the harness/ directory of the RAGRAF repository. In the commands below, $repo is the path to the local RAGRAF clone and $dsh is the app's bundled CLI.

  1. Connect the MCP serverThe ready block harness/ragraf-mcp.patch.yml is appended to the end of the profile patch layer cordis.patch.yml. The application stores patches as an array, so the block goes in as a separate "- insert:" line. Back it up first — the file belongs to the user.
    Appending the patch block (PowerShell)
    $repo = 'C:\AutoClaw\RAGRAF'   # путь к локальному клону RAGRAF
    $src  = Join-Path $repo 'harness\ragraf-mcp.patch.yml'
    $dst  = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.dsh\profiles\desktop\cordis.patch.yml'
    
    # 1. бэкап (обязательно: файл принадлежит пользователю)
    Copy-Item $dst "$dst.bak-$(Get-Date -Format yyyyMMdd-HHmmss)"
    
    # 2. проверить, что блока ещё нет
    Select-String -LiteralPath $dst -Pattern 'mcp-ragraf'
    
    # 3. дописать блок (слой патчей — массив; блок вставляется отдельной строкой "- insert:")
    Add-Content -LiteralPath $dst -Value (Get-Content -LiteralPath $src -Raw) -Encoding utf8NoBOM

    Check that the patch applies — without launching the app:

    Verification via dsh --dump-config
    $dsh = Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd'
    & $dsh --profile headless --patch $src --dump-config | Select-String 'serverName: ragraf' -Context 1,3

    The desktop profile is the app's working profile; headless is for one-off command-line tasks. For a profile other than desktop, place the block into its cordis.patch.yml the same way.

  2. Install the skillThe skill root %USERPROFILE%\.dsh\skills is read by the @deepseek-ai/dsh-skill-filesystem provider: it looks for <name>/SKILL.md at the top level. A skill in this directory is discovered without a restart — the provider watches the root.
    Installing the ragraf-core skill
    $skills = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.dsh\skills\ragraf-core'
    New-Item -ItemType Directory -Path $skills -Force | Out-Null
    Copy-Item (Join-Path $repo 'harness\skills\ragraf-core\SKILL.md') "$skills\SKILL.md" -Force
  3. Add the rule to the global instructionsHARNESS reads %USERPROFILE%\.dsh\AGENTS.md (the dsh-agent-instructions module; dshHome = $DSH_HOME or ~/.dsh). The section to insert is harness/agents-section.md; place it before the "Changelog" section, otherwise the rule ends up after the history (in the verified installation it is section 6). Expected behavior: asked "where does a code task start", the agent answers mcp__ragraf__ensure and refers to that section.

Verification — four levels

From narrow to broad: the server alone → a live run inside the app → the skill → the project checker. Every level is a command and an expected output measured in the live session.

A check of that very defect: PYTHONUTF8 and PYTHONIOENCODING are removed before the run — exactly what HARNESS does. The test is tests/test_mcp_compat.py: scrubbed environment, corpus autodetection, switching.

Compatibility test (PowerShell)
cd $repo
Remove-Item Env:\PYTHONUTF8, Env:\PYTHONIOENCODING -ErrorAction SilentlyContinue
python tests\test_mcp_compat.py

Expected output

Text
ИТОГ: пройдено 13, провалено 0

Three limitations to know

Restarting the app is mandatory

Profile settings are read at startup. The inserted mcp-ragraf block takes effect after restarting DeepSeek Harness; until then, mcp__ragraf__* tools will not appear in the interface. This is normal behavior, not a failure.

A lingering Joern server during indexing

Full indexing starts Joern (JVM) on port 9500. Measured: after the first reindexing, a 3 GB java.exe process stayed behind holding the port; it was removed together with its parent cmd.exe (taskkill /PID <cmd> /T /F). Before and after long runs, check processes and the port:

Checking for a lingering Joern (PowerShell)
Get-CimInstance Win32_Process -Filter "Name='java.exe'" |
  Select-Object ProcessId, @{n='MB';e={[int]($_.WorkingSetSize/1MB)}}
Get-NetTCPConnection -LocalPort 9500 -ErrorAction SilentlyContinue

# висящий сервер снимается вместе с родительским процессом:
taskkill /PID <PID родительского cmd.exe> /T /F

reindex used to lose the run record in the registry

Found and fixed while working on this same task: index recorded the result in the registry, reindex did not. Measured on RAGRAF's own corpus: rev-0046 in the index, rev-0045 in the registry — because of that, check reindexed the corpus on every verification, which looked like "RAGRAF is slow". The symptom: catalog shows a revision different from index_status. The checker rule now inspects every function that launches the indexer.

Rollback

HARNESS: restore the patch layer and AGENTS.md from the backups (the installation printed their names), remove the ragraf-core skill directory.

Rollback — HARNESS settings (PowerShell)
$home2 = [Environment]::GetFolderPath('UserProfile')
# 1. вернуть слой патчей из бэкапа (имя печатала установка)
Copy-Item "$home2\.dsh\profiles\desktop\cordis.patch.yml.bak-<метка>" `
          "$home2\.dsh\profiles\desktop\cordis.patch.yml" -Force
# 2. вернуть глобальные правила
Copy-Item "$home2\.dsh\AGENTS.md.bak-<метка>" "$home2\.dsh\AGENTS.md" -Force
# 3. убрать навык
Remove-Item "$home2\.dsh\skills\ragraf-core" -Recurse -Force

RAGRAF: the edits are additive — they do not touch the index, the storage schema, revision numbering or the registry, so rollback needs no reindexing. To restore the previous behavior, three changes suffice: remove the force_utf8_stdio() call, return required=True to --project, and delete the ragraf_use_project tool from TOOLS and _handlers.

HARNESS behavior: where things live

WhatWhere
App installation%LOCALAPPDATA%\Programs\DeepSeek Harness
HARNESS pluginsresources\app.asar · дерево dsh/node_modules
HARNESS home directory%USERPROFILE%\.dsh
Profile settings%USERPROFILE%\.dsh\profiles\<имя>
Electron data%APPDATA%\@deepseek-ai\dsh-desktop
Plugin registryhttps://registry.npmjs.org/
CLIresources\runtime\cli\bin\dsh.cmd

The home directory is controlled by DSH_HOME (an explicit path wins) and DSH_AGENTS_HOME (the shared agent-skills directory, default ~/.agents). HARNESS scrubs both from child-process environments — this does not affect the MCP server launch: it reads only --project and the working directory.

Skill root resolution order (rank): <project>/.dsh/skills (100), <project>/.agents/skills (200), user customSkillDirs (300), ~/.dsh/skills (400), ~/.agents/skills (500). The project root is the nearest directory containing .git.

HARNESS is developed in a private repository (github.com/deepseek-ai/deepseek-harness); no public API verification was done. The contract above is described from the facts of the current installation — lib/index.js and package READMEs inside app.asar — not from documentation.

Agent regimen

The regimen ships in two layers: the ragraf-core skill (the provider loads it without a restart) and a section of the global rules %USERPROFILE%\.dsh\AGENTS.md — "a code task starts with mcp__ragraf__ensure". The universal regimen fragment (Integrations page) is the skill foundation; it also works on its own.

Telemetry

Telemetry is identical to other external agents: kind=external, argument hashes instead of full query texts, token estimate (chars/4) in usage_call — enough to compare the pairing's economics against the grep approach. The agent name comes from --agent in the patch block or the RAGRAF_AGENT variable: the server has read it since the HARNESS fixes.