AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Harness2go

mcp-thiagojesus-harness2go · by thiagojesus

Swiss-army knife CLI for migrating coding-agent sessions (and their MCP/agent config) between OpenCode, Claude Code, and VS Code

— No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add mcp-thiagojesus-harness2go

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-thiagojesus-harness2go)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 1mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Harness2go? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

harness2go

[](https://github.com/thiagojesus/harness2go/actions/workflows/tests.yml) [](https://pypi.org/project/harness2go/)

A swiss-army knife for migrating coding-agent harness sessions — and their MCP server / subagent config — between OpenCode, Claude Code, and VS Code (GitHub Copilot Chat).

h2go opencode2claude  ...
h2go claude2opencode  ...
h2go vscode2opencode  ...
h2go vscode2claude    ...
h2go opencode2vscode  ...
h2go claude2vscode    ...

h2go is a thin dispatcher over the six directions, each of which is also installed as its own standalone command (opencode2claude, claude2opencode, vscode2opencode, vscode2claude, opencode2vscode, claude2vscode) for anyone who only ever migrates one way. They share small harness_common.py / vscode_common.py modules (JSONC parsing, frontmatter parsing, secret masking, interactive prompts, VS Code session decoding/encoding) so behavior stays consistent across all of them.

Installation

Zero runtime dependencies (plain standard library).

uv tool install harness2go
# or
pip install harness2go
# or, without installing anything persistently:
uvx --from harness2go h2go opencode2claude list

This puts h2go and the six standalone commands (opencode2claude, claude2opencode, vscode2opencode, vscode2claude, opencode2vscode, claude2vscode) on your PATH. Then from anywhere:

h2go opencode2claude list
h2go claude2vscode convert 

opencode2claude — OpenCode → Claude Code

Converts an OpenCode session into a Claude Code session transcript (.jsonl) that Claude Code can list and --resume.

OpenCode's real conversation data lives in a SQLite DB at ~/.local/share/opencode/opencode.db (the session, message, and part tables) — not in the small per-session status stub files some OpenCode wrappers leave lying around. This script reads directly from that DB (read-only — it never writes to opencode.db).

# List every OpenCode session available to convert
opencode2claude list

# Convert one session (writes into ~/.claude/projects//.jsonl)
opencode2claude convert ses_XXXXXXXXXXXX

# Convert everything
opencode2claude convert --all

# Preview without writing anything
opencode2claude convert ses_XXXXXXXXXXXX --dry-run

After converting, resume the session from the project directory it belongs to:

cd /path/to/project && claude --resume 

After converting a single session (interactively, not --all), a wizard offers to also import:

  • MCP servers configured for that OpenCode project (global +

project-level opencode.json(c)) via the real claude mcp add CLI, at local/project/user scope, one server at a time.

  • Agents referenced in the session (assistant turns + task tool

subagent_type calls) as Claude Code subagent stubs (.claude/agents/*.md, local or global scope). These are honest placeholders — OpenCode's real system-prompt text for built-in/plugin agents (Sisyphus, Oracle, Librarian, ...) is compiled into a third-party plugin bundle and isn't recoverable from the session, so the stub only carries what's genuinely observable (name, usage count, task descriptions, best-effort model from oh-my-openagent.json) and says so.

Control it with convert ... --wizard {auto,always,never} (default auto: runs only for a single, non-dry-run conversion in an interactive terminal).

opencode2claude import-global

Same MCP/agent import wizard, but independent of any session or opencode.db: MCP servers come from OpenCode's global config only (~/.config/opencode/opencode.jsonc); agents come from ~/.config/opencode/agent/*.md — real files the user actually authored, copied verbatim — plus any additional agent names known only via oh-my-openagent.json's model-routing config (stub fidelity; a real file always takes precedence over a stub for the same agent name).

claude2opencode — Claude Code → OpenCode

The mirror image. Converts a Claude Code session (.jsonl transcript) into a real OpenCode session — inserted into opencode.db — and imports Claude Code's MCP servers / subagent definitions into OpenCode's own config.

# List every Claude Code session available to convert
claude2opencode list

# Convert one session (by UUID, searched across ~/.claude/projects/*,
# or pass a direct .jsonl path)
claude2opencode convert 

# Preview without writing anything
claude2opencode convert  --dry-run

This one writes to a live application database, which is a materially riskier operation than writing a plain session file — so convert always takes a timestamped backup of opencode.db (opencode.db.bak-) before inserting anything, and does the whole insert in a single transaction.

Like the other direction, converting a single session offers a wizard to import the MCP servers configured for that Claude Code project (Claude's local + project scope, from ~/.claude.json and .mcp.json) and any project-level Claude agents (.claude/agents/*.md) into OpenCode's project-level config. import-global does the session-independent version: Claude Code's user-scope MCP servers (~/.claude.json top-level mcpServers) and global agents (~/.claude/agents/*.md, copied verbatim — these are real files the user wrote, so unlike the Claude-Code-agent-stub direction there's no lesser-fidelity fallback needed here).

Writing into OpenCode's opencode.jsonc also takes a backup first, and warns if the existing file has ////* */ comments — comments aren't preserved across a rewrite (no dependency-free JSONC writer exists to keep them), so the backup is the safety net.

Design notes

  • Claude Code's transcript is per-content-block JSONL lines chained by

parentUuid; OpenCode is structured SQLite rows. claude2opencode regroups consecutive assistant lines sharing the same message.id back into one OpenCode message (one step, bounded by synthesized step-start/step-finish parts), and reunites each tool_use block with its later tool_result line into a single OpenCode tool part — the exact inverse of how opencode2claude split things apart.

  • thinking blocks become OpenCode reasoning parts (the cryptographic

signature is dropped — it isn't meaningful outside Anthropic's API and OpenCode has no equivalent concept).

  • Tool name mapping is the literal inverse of opencode2claude's

(Bash→bash, Read→read, Edit→edit, Task→task, AskUserQuestion→question, TodoWrite→todowrite, TaskOutput→background_output); anything else passes through lowercased by name.

  • The session row reuses OpenCode's existing project row (OpenCode

appears to key everything off a single project per install rather than one per directory — a fresh project row is only created if none exists at all).

vscode2opencode / vscode2claude — VS Code (Copilot Chat) → OpenCode/Claude Code

VS Code (via the GitHub Copilot Chat extension) stores chat sessions as an append-only "operation log" — not a simple flat file. The first line is a full JSON snapshot (kind: 0); later lines are Set/Push/Delete patches against explicit object paths (verified against VS Code's own source, chatSessionStore.ts/objectMutationLog.ts in microsoft/vscode). Sessions live in two places:

  • No-folder ("empty window") chats: ~/Library/Application Support/Code/User/globalStorage/emptyWindowChatSessions/.json[l]
  • Per-project chats: ~/Library/Application Support/Code/User/workspaceStorage//chatSessions/.json[l] (the hash's workspace.json maps it to a folder)

vscode_common.py replays that log and normalizes it into the same canonical "turns" shape claude2opencode.py's transcript parser produces, so both converters build on one shared reader.

vscode2opencode list
vscode2opencode convert  [--directory DIR] [--dry-run]

vscode2claude list
vscode2claude convert  [--directory DIR] [--dry-run]

--directory is required (or falls back to the current working directory with a warning) for no-folder sessions, which have no natural project association.

Scope — core content only. A single VS Code response can contain any of ~35 distinct content-block kinds (progress messages, mcpServersStarting notices, confirmations, todoList widgets, terminal/ notebook/file-edit blocks, pull requests, ...). Only the substantive ones are mapped — text/markdown, thinking, and toolInvocationSerialized — the same content OpenCode/Claude Code already model; everything else is UI chrome, dropped the same way opencode2claude.py drops OpenCode's step-start/step-finish structural markers.

Two things worth knowing:

  • VS Code's own tool names (run_in_terminal, copilot_readFile, ...)

don't match either target's tool vocabulary, so they pass through verbatim (lowercased on the OpenCode side, unchanged on the Claude Code side) rather than being misleadingly relabeled — an accepted limitation of "core content only," not a bug. Likewise, a tool's persisted "input" is often just VS Code's human-readable invocation message (e.g. "Reading file.py"), not the tool's real structured arguments — that's what's actually available in the serialized log.

  • thinking blocks become plain text (Claude Code target) or

reasoning (OpenCode target) — never a real Anthropic thinking block — since these turns frequently aren't even from an Anthropic model (VS Code routes through GPT/Gemini/etc. too), so a fabricated signature would be actively wrong, not just unnecessary.

  • On the Claude Code side specifically: message ids are always freshly

synthesized as msg_, never VS Code's own request_ — Claude Code sends the last assistant message's id back to the API as a previous_message_id diagnostic on resume, and the API rejects anything not shaped like a real response id. (Discovered by testing an actual resume against a converted session — the API returned a 400 until this was fixed.)

opencode2vscode / claude2vscode — OpenCode/Claude Code → VS Code (Copilot Chat)

The reverse of vscode2opencode.py/vscode2claude.py: writes a real, openable VS Code chat session.

opencode2vscode convert ses_XXXXXXXXXXXX [--dry-run]
claude2vscode convert  [--dry-run]

This one needs more than a session file. VS Code's Chat view discovers sessions purely through an index kept in state.vscdb (its general-purpose settings store) — confirmed by reading chatSessionStore.ts's actual load path, which has no fallback that scans the session folder. A session file dropped in without a matching index entry is invisible to the UI. So convert:

  • Writes the session file (.json) in the right location.
  • Merges a matching entry into state.vscdb's chat.ChatSessionStore.index

key (an ItemTable row, standard VS Code storage-service SQLite), always taking a timestamped backup of state.vscdb first.

  • Refuses to run if VS Code is currently open. That index is cached in

memory by a running window and only read fresh on cold start — writing it underneath a live VS Code process risks the change being silently discarded the next time that window saves any chat state at all. Close VS Code first.

  • Only targets an existing VS Code workspace, matched by directory (the

same workspace.json lookup vscode2opencode.py/vscode2claude.py use for reading). If the target project has never been opened in VS Code, the session is written as a no-folder ("empty window") chat instead of inventing a new workspace — the same "never fabricate what isn't already there" discipline as the MCP/agent import directions.

Content mapping mirrors the read direction in reverse: text/reasoning (OpenCode) or text/thinking (Claude Code) become VS Code markdownContent/ thinking blocks, and tool calls become toolInvocationSerialized blocks — with the tool's original input rendered as a human-readable summary (VS Code's serialized format has no slot for raw structured arguments) and its output carried in resultDetails when there is one.

Validated with a real three-way round trip: a real VS Code session was converted to Claude Code (previous PR), then converted back to VS Code with this code (against a throwaway copy of state.vscdb, never the real one) — replaying the written file reproduced the original conversation content, and the index entry showed up correctly alongside the pre-existing real entries, untouched.

MCP servers and agents, without a session — including VS Code

Every direction supports import-global, so you can bring over MCP servers and custom agents without converting an actual session:

opencode2claude import-global   # opencode2vscode, claude2vscode, vscode2opencode, vscode2claude too

Before building the VS Code side of this, it's worth knowing that some pairings already work with zero conversion, because VS Code natively reads certain other-harness config directly (confirmed by reading VS Code's own source):

  • Claude Code agents ↔ VS Code: VS Code discovers .claude/agents/*.md

(project) and ~/.claude/agents/*.md (global) directly — it's hardcoded into VS Code's own agent-discovery list. If a Claude Code agent already exists, VS Code already sees it. claude2vscode import-global reports this instead of pretending there's something to convert.

  • Claude Code project-scope MCP ↔ VS Code: both read .mcp.json at the

project root using the identical {"mcpServers": {...}} shape — VS Code's own source comment literally says "Uses the Claude-style format".

What's genuinely new, because no such overlap exists:

  • OpenCode ↔ VS Code, MCP and agents, both scopes. Different formats

and locations entirely (OpenCode's opencode.jsonc "mcp" key / ~/.config/opencode/agent/*.md vs. VS Code's own schemas below).

  • **VS Code's own global MCP config**, /mcp.json

(JSONC, {"servers": {...}}) ↔ OpenCode's global config and Claude Code's global config (~/.claude.json "mcpServers"). Claude's shape is field-identical to VS Code's own (type/command/args/env, or type/url/headers), so that pairing is close to a straight copy; OpenCode's shape (command+args merged into one list, environment instead of env) needs real translation.

  • VS Code's own custom agents, .github/agents/*.md (project) /

~/.copilot/agents/*.md (global), .agent.md frontmatter (name/description/model/tools) — relevant whenever the source or target doesn't already overlap with Claude's .claude/agents convention.

vscode2claude import-global reuses the real claude mcp add --scope user CLI (like opencode2claude already does) rather than hand-writing ~/.claude.json.

Requirements

Python 3.9+, standard library only at runtime (sqlite3, argparse) — pyproject.toml declares zero dependencies. Optionally uses git/claude --version/opencode --version if present on PATH, all with safe fallbacks.

Project layout

harness2go/
├── pyproject.toml          # build config + console_scripts entry points
├── src/harness2go/         # the actual package
│   ├── cli.py              # h2go dispatcher
│   ├── harness_common.py   # shared JSONC/frontmatter/prompt helpers
│   ├── vscode_common.py    # shared VS Code session decode/encode
│   ├── opencode2claude.py
│   ├── claude2opencode.py
│   ├── vscode2opencode.py
│   ├── vscode2claude.py
│   ├── opencode2vscode.py
│   └── claude2vscode.py
└── tests/                  # unittest suite, run against the installed package

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.