Harness Compiler API¤
This page renders the harness and compiler source APIs directly from code. It is the closest analogue to tinygrad's source-backed developer pages.
Capture¤
capture
¤
Capture coordination and the lineage report (design §28.1 control ops, §51).
This is the control-plane logic behind both the CLI and the MCP server: start / finish a capture, mark an episode outcome, summarize, and produce the deterministic exact-lineage report that §51 names as the first concrete milestone::
frontier host -> jdsl capture -> canonical trace -> exact lineage report
The coordinator never depends on MCP; the MCP server is a thin shell over it.
Classes:
-
CaptureCoordinator–Owns capture lifecycle and analysis over a
HarnessStore.
CaptureCoordinator
dataclass
¤
CaptureCoordinator(store: HarnessStore)
Owns capture lifecycle and analysis over a HarnessStore.
Methods:
-
lineage_report–The §51 exact-lineage report over a capture's episodes.
lineage_report
¤
lineage_report(capture_id: str) -> dict[str, Any]
The §51 exact-lineage report over a capture's episodes.
Source code in jdsl_harness/capture.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
store
¤
Harness storage (design §30, §35 PR6).
Boring storage first: SQLite for metadata, JSONL for the append-only event stream, a content-addressed blob store on the filesystem. The source of truth for raw execution is the append-only events + blobs; the SQLite tables are a rebuildable index over them (§30). No vector DB, no server dependency.
Layout under root/::
harness.db SQLite metadata (captures, episodes, event_index, packages)
captures/<capture_id>.jsonl append-only canonical event spool
blobs/sha256/<digest> content-addressed blobs
Classes:
-
HarnessStore–Metadata + event spool + blob store for one harness instance.
HarnessStore
¤
HarnessStore(root: str | Path)
Metadata + event spool + blob store for one harness instance.
Methods:
-
ingest–Record a single already-formed event (used by adapters/ingest server).
-
sink–A trace sink that appends to this capture's spool and indexes each event.
Source code in jdsl_harness/store.py
70 71 72 73 74 75 76 77 78 79 | |
ingest
¤
ingest(event: TraceEvent) -> TraceEvent
Record a single already-formed event (used by adapters/ingest server).
Source code in jdsl_harness/store.py
126 127 128 | |
sink
¤
sink(capture_id: str) -> JsonlTraceSink
A trace sink that appends to this capture's spool and indexes each event. Reuses one hash-chainer per capture so sequences/hashes stay consistent.
Source code in jdsl_harness/store.py
119 120 121 122 123 124 | |
gateway
¤
The jdsl tool gateway — Tier A capture (design §8.1, §35 PR5).
The preferred capture mode: the model sees task tools through jdsl, so the gateway records tool identity, schema, arguments, result, error, timing, and state around mutations (§8.1). Wrapping a tool with the gateway records a canonical tool.call.started/completed/failed triple on the sink, with the environment's state snapshot attached around state-changing calls when an adapter is present.
Classes:
-
ToolGateway–Wraps callables/Tools so their calls are recorded to a trace sink. Preserves
ToolGateway
¤
ToolGateway(sink: TraceSink, *, capture_id: str, episode_id: str, env: EnvironmentAdapter | None = None, source: EventSource | None = None)
Wraps callables/Tools so their calls are recorded to a trace sink. Preserves the original tool's schema and return value; capture is transparent to the caller (fail-open, §7.2 — a broken sink never breaks the tool call).
Methods:
-
record_outcome–Emit the environment's task outcome, if the adapter reports one (§8.1).
-
wrap–Return a callable that records each invocation.
logical_idis the
Source code in jdsl_harness/gateway.py
25 26 27 28 29 30 31 32 33 | |
record_outcome
¤
record_outcome() -> TraceEvent | None
Emit the environment's task outcome, if the adapter reports one (§8.1).
Source code in jdsl_harness/gateway.py
71 72 73 74 75 76 77 78 79 | |
wrap
¤
wrap(fn: Any, *, logical_id: str | None = None, host_name: str | None = None, destructive: bool = False) -> Any
Return a callable that records each invocation. logical_id is the
portable capability id; destructive triggers state snapshots (§8.1).
Source code in jdsl_harness/gateway.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
server
¤
Harness daemon: local ingest data plane + MCP control plane (design §7, §28, §35 PR6).
The architectural split of §7 lives here. The data plane is a fast local HTTP
loopback endpoint that host hooks POST events to and returns immediately (§7.2) —
never a slow remote request on every tool event. The control plane (§7.1, §28)
is a small, stable set of operations (jdsl.capture.*, jdsl.compile, …) exposed
as an MCP server when the MCP SDK is available, and always available in-process via
CaptureCoordinator.
MCP is optional: importing this module never requires the mcp package. If it is
absent, build_mcp_server raises a clear error and the HTTP ingest server still
runs.
Classes:
-
IngestServer–Loopback HTTP ingest for host hooks and adapters (§7.2 telemetry plane).
Functions:
-
build_mcp_server–Build an MCP server exposing the control tools (§28.1). Requires the
mcp
IngestServer
¤
IngestServer(store: HarnessStore, *, host: str = '127.0.0.1', port: int = 8848)
Loopback HTTP ingest for host hooks and adapters (§7.2 telemetry plane).
Endpoints
POST /ingest body: a canonical TraceEvent dict
POST /hook/claude?cap=… body: a Claude Code hook payload
POST /hook/gemini?cap=… body: a Gemini CLI hook payload
POST /hook/opencode?cap=… body: a jdsl OpenCode hook envelope
GET /captures list captures
GET /capture/
The hook itself fails open for observation (§7.2): a bad request never 500s the agent — it returns a 200 with an error note so the host loop keeps moving.
Source code in jdsl_harness/server.py
43 44 45 46 47 48 49 50 51 52 | |
build_mcp_server
¤
build_mcp_server(store: HarnessStore, name: str = 'jdsl-harness') -> Any
Build an MCP server exposing the control tools (§28.1). Requires the mcp
package; raises a clear error if it is not installed. Kept import-optional so
the core harness never depends on the MCP SDK (§36).
Source code in jdsl_harness/server.py
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
Compiler Passes¤
normalize
¤
Deterministic trajectory normalization (design §13, §35 PR8).
Raw traces carry surface variation; the compiler needs symbolic structure. This
stage turns a canonical Episode into a NormEpisode: an ordered list of action
steps whose arguments are annotated with exact dataflow lineage (§16.1) and whose
instance-specific values are replaced with symbolic references (§13.2). No model
is used — every decision here is structural and reproducible.
Classes:
-
NormStep–One normalized action step (a tool call) with lineage-annotated arguments.
-
ModelDecision–A residual semantic decision observed in the trace (react/predict leaf).
-
NormEpisode–
Functions:
-
normalize_episode–Normalize one episode.
canonicalmaps host tool names to logical ids -
normalize_all– -
synth_store–The store name assigned to a tool result that had no explicit store. Shared
-
synth_node_id–
NormStep
dataclass
¤
NormStep(index: int, logical_tool: str, host_tool: str | None, arguments: dict[str, Any], arg_lineage: dict[str, str | None], result: Any = None, ok: bool = True, error: Any = None, store: str | None = None, node_id: str | None = None, state_before: dict[str, Any] = dict())
One normalized action step (a tool call) with lineage-annotated arguments.
Methods:
-
symbolic_args–Arguments with lineaged values replaced by
$ref(path)markers (§13.2).
symbolic_args
¤
symbolic_args() -> dict[str, Any]
Arguments with lineaged values replaced by $ref(path) markers (§13.2).
Source code in jdsl_harness/compiler/normalize.py
51 52 53 54 55 56 57 | |
ModelDecision
dataclass
¤
ModelDecision(index: int, node_id: str | None, inputs: list[str], outputs: list[str], kind: str = 'predict')
A residual semantic decision observed in the trace (react/predict leaf).
NormEpisode
dataclass
¤
NormEpisode(episode_id: str, steps: list[NormStep] = list(), decisions: list[ModelDecision] = list(), success: bool | None = None, outcome: dict[str, Any] | None = None, source_digest: str | None = None)
normalize_episode
¤
normalize_episode(episode: Episode, *, canonical: dict[str, str] | None = None) -> NormEpisode
Normalize one episode. canonical maps host tool names to logical ids
(§13.1 tool canonicalization); unmapped tools keep their host name.
Source code in jdsl_harness/compiler/normalize.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | |
normalize_all
¤
normalize_all(episodes: list[Episode], *, canonical: dict[str, str] | None = None) -> list[NormEpisode]
Source code in jdsl_harness/compiler/normalize.py
153 154 | |
synth_store
¤
synth_store(tool: str, index: int) -> str
The store name assigned to a tool result that had no explicit store. Shared by the normalizer, staticizer, and verifier so refs stay consistent (§13.2).
Source code in jdsl_harness/compiler/normalize.py
26 27 28 29 | |
synth_node_id
¤
synth_node_id(tool: str, index: int) -> str
Source code in jdsl_harness/compiler/normalize.py
32 33 | |
lineage
¤
Exact dataflow lineage (design §16.1, §13.2).
The single most valuable deterministic signal: when a tool argument value is exactly a value that already exists in trusted state, the model should not regenerate it — it should reference it (§16.1 "If the identifier already exists in trusted state, the model should not regenerate the identifier"). This module finds, for a given value, the JSON path in a prior state that produced it. No model involved; this is pure structural comparison.
Functions:
-
is_meaningful–Only mine lineage for identifier-like values: non-trivial scalars. Strings
-
find_source–Return the best JSON path in
statewhose value equalsvalueexactly, or -
find_all_sources–Every path in
stateequal tovalue. Ordered best-first: symbolic-index
is_meaningful
¤
is_meaningful(value: Any) -> bool
Only mine lineage for identifier-like values: non-trivial scalars. Strings must be at least 2 chars; small ints/bools are too common to attribute.
Source code in jdsl_harness/compiler/lineage.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
find_source
¤
find_source(value: Any, state: dict[str, Any], *, max_depth: int = 6) -> str | None
Return the best JSON path in state whose value equals value exactly, or
None. Paths use dotted keys and [i] indices (the ref syntax of §21.1). A list
index equal to a state variable is emitted symbolically (orders[$selected_index],
§49) so it generalizes to the model's decision rather than hardcoding the row.
Source code in jdsl_harness/compiler/lineage.py
35 36 37 38 39 40 41 42 43 | |
find_all_sources
¤
find_all_sources(value: Any, state: dict[str, Any], *, max_depth: int = 6) -> list[str]
Every path in state equal to value. Ordered best-first: symbolic-index
paths (which generalize) before literal-index paths, then shortest.
Source code in jdsl_harness/compiler/lineage.py
46 47 48 49 50 51 52 53 54 55 56 | |
candidates
¤
Behavior candidate mining — the six atom types (design §4.2, §14.1, §16, §35 PR9).
Stage A of the compiler (§14.1): analyze each normalized episode independently and
extract local behavior facts — no generalization yet. Facts carry a grouping key
so consolidate.py (Stage B) can measure support and counterexamples across many
episodes and assign an evidence grade (§15).
Classes:
-
Fact–One local behavior observation from a single episode.
Functions:
-
extract_facts–All local facts from one normalized episode (§14.1).
Fact
dataclass
¤
Fact(type: str, claim: dict[str, Any], episode_id: str, outcome_ok: bool | None = None, state_before: dict[str, Any] = dict())
One local behavior observation from a single episode.
Methods:
-
key–Stable identity for grouping equivalent facts across episodes.
key
¤
key() -> tuple
Stable identity for grouping equivalent facts across episodes.
Source code in jdsl_harness/compiler/candidates.py
34 35 36 | |
extract_facts
¤
extract_facts(ep: NormEpisode) -> list[Fact]
All local facts from one normalized episode (§14.1).
Source code in jdsl_harness/compiler/candidates.py
39 40 41 42 43 44 45 46 47 | |
consolidate
¤
Cross-trace consolidation and evidence grading (design §14.2, §15, §35 PR9).
Stage B of the compiler: group equivalent local facts, measure support and counterexamples against the episodes where each claim was applicable, and assign a conservative evidence grade (§15). Frequency alone is never enough — a claim contradicted in an applicable episode is contested and cannot become a hard rule (§15, §44.2). Grades E4/E5 are reserved for the verifier and held-out evaluation.
Classes:
Functions:
-
consolidate–Consolidate local facts from all episodes into graded candidates.
contracts
Evidence
dataclass
¤
Evidence(applicable: int = 0, support: int = 0, counterexamples: int = 0, episodes: list[str] = list(), success_support: int = 0, fail_support: int = 0)
Candidate
dataclass
¤
Candidate(candidate_id: str, type: str, claim: dict[str, Any], evidence: Evidence, grade: str = E0, status: str = 'proposed', contract_sources: list[str] = list())
consolidate
¤
consolidate(episodes: list[NormEpisode], *, contracts: dict[tuple, list[str]] | None = None) -> list[Candidate]
Consolidate local facts from all episodes into graded candidates. contracts
optionally maps a fact key to contract source ids, lifting it to E3 (§15 E3).
Source code in jdsl_harness/compiler/consolidate.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
staticize
¤
Staticization: build the Behavior IR from evidence (design §17, §25, §35 PR11).
The compiler objective is lowest model burden while preserving verified behavior (§17, §2.3). We follow the staticization ordering (§17): constant? exact dataflow? deterministic predicate? fixed action? bounded recovery? classification? — and only what survives as genuine judgment stays a residual model leaf.
The structural method is §25: build a control skeleton from the modal successful trajectory, replace instance values with refs, insert verified guards, wrap known recovery, and residualize the rest.
Classes:
-
CompiledBehavior–The staticizer's output: an IR, its provenance, and burden metrics (§33).
Functions:
CompiledBehavior
dataclass
¤
CompiledBehavior(ir: BehaviorIR, provenance: list[NodeProvenance] = list(), stats: dict[str, Any] = dict(), required_capabilities: list[str] = list())
The staticizer's output: an IR, its provenance, and burden metrics (§33).
staticize
¤
staticize(episodes: list[NormEpisode], candidates: list[Candidate], *, name: str = 'behavior', model: CompilerModel | None = None, compiler_model_id: str | None = None) -> CompiledBehavior
Source code in jdsl_harness/compiler/staticize.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
residualize
¤
Residualize the semantic decisions the compiler could not remove (design §18, §26, §35 PR10).
Everything the staticizer can prove deterministic has been lowered to control,
dataflow, guards, and fixed actions. What remains — genuine language judgment — is
turned into typed residual signatures (§18): the small interface between one tree
leaf and the frozen model. A residual leaf prefers predict + deterministic act
over a wide-open react (§18.3).
Functions:
-
residualize_decision–Turn one observed model decision into a typed Signature + its IR leaf.
residualize_decision
¤
residualize_decision(decision: ModelDecision, *, model: CompilerModel | None = None) -> tuple[Signature, IRPredict | IRReact]
Turn one observed model decision into a typed Signature + its IR leaf.
Source code in jdsl_harness/compiler/residualize.py
18 19 20 21 22 23 24 25 26 27 28 | |
verify
¤
Verification: deterministic layers that decide what survives (design §32, §35 PR12).
Compilation without verification produces fragile policies (§32). None of this trusts the compiler model — every check is deterministic (§24.1): structural validity (§32.1), then replay of the compiled deterministic behavior against the historical episodes (§32.2). Dataflow refs that reproduce the recorded values, and guards that pick the recorded branch, are promoted to replay-verified (E4).
Classes:
Functions:
-
verify– -
promote_replay_verified–When replay is fully clean, lift each compiled node's provenance to E4
VerificationReport
dataclass
¤
VerificationReport(structural_ok: bool = True, structural_problems: list[str] = list(), replay_checks: int = 0, replay_passed: int = 0, problems: list[str] = list())
verify
¤
verify(compiled: CompiledBehavior, episodes: list[NormEpisode], *, required_capabilities: set[str] | None = None) -> VerificationReport
Source code in jdsl_harness/compiler/verify.py
51 52 53 54 55 56 57 58 59 60 | |
promote_replay_verified
¤
promote_replay_verified(compiled: CompiledBehavior, report: VerificationReport) -> None
When replay is fully clean, lift each compiled node's provenance to E4 (replay-verified, §15). Safety-sensitive guards still needed E3 to be compiled.
Source code in jdsl_harness/compiler/verify.py
113 114 115 116 117 118 119 | |
package
¤
Assemble a portable BehaviorPackage from compiled + verified behavior (design §22, §23, §35 PR13) and the end-to-end compile pipeline (§24).
compile_behavior is the whole spine (§24): normalize → consolidate → staticize →
verify → package. It is deterministic given the traces and the (optional) compiler
model; the model only proposes wording/signatures, never the structure or the
evidence counts (§24.1).
Classes:
-
CompileResult–The full output of one compile run — package plus every intermediate.
Functions:
-
compile_behavior–Run the whole compiler pipeline over canonical trace episodes (§24).
-
build_package–
CompileResult
¤
CompileResult(*, package: BehaviorPackage, compiled: CompiledBehavior, candidates: list[Candidate], verification: VerificationReport, normalized: list[NormEpisode])
The full output of one compile run — package plus every intermediate.
Source code in jdsl_harness/compiler/package.py
46 47 48 49 50 51 52 53 | |
compile_behavior
¤
compile_behavior(episodes: list[Episode], *, name: str = 'behavior', task_family: str = '', canonical: dict[str, str] | None = None, contracts: dict[tuple, list[str]] | None = None, effects: dict[str, ToolEffects] | None = None, model: CompilerModel | None = None, compiler_model_id: str | None = None, capture_fidelity: str = 'F3', version: str = '0.1.0') -> CompileResult
Run the whole compiler pipeline over canonical trace episodes (§24).
Source code in jdsl_harness/compiler/package.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | |
build_package
¤
build_package(compiled: CompiledBehavior, report: VerificationReport, candidates: list[Candidate], norm: list[NormEpisode], *, name: str, task_family: str = '', effects: dict[str, ToolEffects] | None = None, compiler_model_id: str | None = None, capture_fidelity: str = 'F3', version: str = '0.1.0') -> BehaviorPackage
Source code in jdsl_harness/compiler/package.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | |
Host Adapters¤
correlation
¤
Tool-call correlation for host hooks.
Host integrations often deliver "before tool" and "after tool" as separate HTTP
posts. This small state object remembers started calls by host-provided call id
and annotates completion events with the correct parent_event_id so replay and
normalization do not have to guess from sequence order.
Classes:
-
ToolCallCorrelator–Short-lived in-memory correlation state scoped to one ingest process.
Functions:
-
host_call_id–Common field names used by host hook payloads for a tool-call id.
ToolCallCorrelator
dataclass
¤
ToolCallCorrelator(_by_id: dict[tuple[str, str, str], str] = dict(), _open: dict[tuple[str, str], list[tuple[str, str, str | None]]] = dict(), _counter: Any = (lambda: itertools.count(1))())
Short-lived in-memory correlation state scoped to one ingest process.
host_call_id
¤
host_call_id(payload: dict[str, Any]) -> str | None
Common field names used by host hook payloads for a tool-call id.
Source code in jdsl_harness/adapters/correlation.py
79 80 81 82 83 84 85 | |
claude_code
¤
Claude Code host adapter (design §8.2, §29.1).
Translates Claude Code's structured hook payloads into canonical jdsl events. The adapter uses only the structured JSON hook payload — never scraped terminal text (§8.2). It is a pure function of the payload so it can be unit-tested against recorded fixtures without running the host (§46 hook fixture tests).
Claude Code hook events used (§29.1): SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, SessionEnd. The tool call is correlated across Pre/Post by (session, tool_name, tool_input).
Functions:
-
to_events–Map one Claude Code hook payload to zero or more canonical events. The
to_events
¤
to_events(payload: dict[str, Any], *, capture_id: str, model: str | None = None, correlator: ToolCallCorrelator | None = None) -> list[TraceEvent]
Map one Claude Code hook payload to zero or more canonical events. The session id becomes the episode id; unknown hook names are dropped (capture fidelity is recorded elsewhere, §8.2).
Source code in jdsl_harness/adapters/claude_code.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | |
gemini_cli
¤
Gemini CLI host adapter (design §8.2, §29.2).
Maps Gemini CLI's hook payloads to canonical jdsl events. Gemini exposes a broader surface than Claude Code, including model and tool-selection events (§29.2); for the first release these are used for capture only (not enforcement). Full model requests are not stored by default (§29.2).
Hooks used: SessionStart, BeforeAgent, BeforeToolSelection, BeforeTool, AfterTool, AfterAgent, SessionEnd.
Functions:
to_events
¤
to_events(payload: dict[str, Any], *, capture_id: str, model: str | None = None, correlator: ToolCallCorrelator | None = None) -> list[TraceEvent]
Source code in jdsl_harness/adapters/gemini_cli.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
opencode
¤
OpenCode host adapter.
The TypeScript plugin translates OpenCode hook inputs into the stable
jdsl.opencode-hook.v1 envelope. This module maps that envelope into canonical
jdsl trace events without depending on OpenCode's TypeScript types.
Classes:
-
OpenCodeEnvelopeError–An OpenCode hook payload does not match the jdsl stable envelope.
Functions:
-
to_events–Map one stable OpenCode envelope to canonical trace events.
OpenCodeEnvelopeError
¤
Bases: ValueError
An OpenCode hook payload does not match the jdsl stable envelope.
to_events
¤
to_events(payload: dict[str, Any], *, capture_id: str, model: str | None = None, correlator: ToolCallCorrelator | None = None) -> list[TraceEvent]
Map one stable OpenCode envelope to canonical trace events.
Source code in jdsl_harness/adapters/opencode.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
mcp_proxy
¤
Transparent MCP proxy — Tier-A capture for MCP-native tools (design §8.1.1, §35 PR5).
Many modern tools already arrive through MCP. Rather than writing a wrapper per tool, jdsl proxies an upstream MCP server: it discovers the upstream tools, preserves their input/output schemas, exposes them namespaced to the host, forwards calls, and records the full call + result to the trace store (§8.1.1).
The MCP SDK is an optional dependency (§36): this module defines the recording
logic in a transport-neutral way (ProxiedTool, record_proxied_call) that works
without mcp, and a serve_proxy entry point that lazily imports the SDK.
Classes:
-
ProxiedTool–An upstream MCP tool the proxy exposes, with its preserved schema (§8.1.1).
-
MCPProxy–Holds proxy configuration and the discovered upstream tool table. The actual
-
StdioUpstream–One upstream MCP server reached over stdio.
Functions:
-
record_proxied_call–Record one forwarded MCP call as canonical events (§8.1.1 items 4-6).
-
build_stdio_proxy_server–Build a low-level MCP stdio proxy server for one upstream.
-
discover_stdio_tools–Discover tools from one stdio upstream using the installed MCP SDK.
-
call_stdio_tool–Forward one tool call to a stdio upstream.
-
serve_proxy–Serve the stdio MCP proxy on this process's stdin/stdout.
ProxiedTool
dataclass
¤
ProxiedTool(server: str, name: str, input_schema: dict[str, Any] = dict(), output_schema: dict[str, Any] = dict(), description: str = '')
An upstream MCP tool the proxy exposes, with its preserved schema (§8.1.1).
Attributes:
-
namespaced(str) –Host-visible name, namespaced by server to keep logical ids collision-free.
namespaced
property
¤
namespaced: str
Host-visible name, namespaced by server to keep logical ids collision-free.
MCPProxy
dataclass
¤
MCPProxy(sink: TraceSink, capture_id: str, episode_id: str = 'ep_proxy', tools: dict[str, ProxiedTool] = dict())
Holds proxy configuration and the discovered upstream tool table. The actual
stdio/HTTP transport is provided by serve_proxy (needs the mcp SDK).
Methods:
-
toolset_event–Emit the exposed toolset for the compiler's tool-visibility mining (§42).
toolset_event
¤
toolset_event() -> TraceEvent
Emit the exposed toolset for the compiler's tool-visibility mining (§42).
Source code in jdsl_harness/mcp_proxy.py
90 91 92 93 94 95 96 97 | |
StdioUpstream
dataclass
¤
StdioUpstream(server: str, command: str, args: list[str] = list(), env: dict[str, str] | None = None, cwd: str | Path | None = None)
One upstream MCP server reached over stdio.
record_proxied_call
¤
record_proxied_call(sink: TraceSink, tool: ProxiedTool, arguments: dict[str, Any], *, capture_id: str, episode_id: str, result: Any = None, error: Any = None, host_call_id: str | None = None) -> list[TraceEvent]
Record one forwarded MCP call as canonical events (§8.1.1 items 4-6).
Source code in jdsl_harness/mcp_proxy.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
build_stdio_proxy_server
¤
build_stdio_proxy_server(upstream: StdioUpstream | dict[str, Any], sink: TraceSink, *, capture_id: str, episode_id: str = 'ep_proxy', name: str = 'jdsl-mcp-proxy') -> Any
Build a low-level MCP stdio proxy server for one upstream.
The server discovers upstream tools, exposes namespaced copies with preserved JSON schemas, forwards calls to the upstream tool name, and records canonical tool-call events.
Source code in jdsl_harness/mcp_proxy.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
discover_stdio_tools
async
¤
discover_stdio_tools(upstream: StdioUpstream | dict[str, Any]) -> list[Any]
Discover tools from one stdio upstream using the installed MCP SDK.
Source code in jdsl_harness/mcp_proxy.py
173 174 175 176 177 178 | |
call_stdio_tool
async
¤
call_stdio_tool(upstream: StdioUpstream | dict[str, Any], name: str, arguments: dict[str, Any]) -> Any
Forward one tool call to a stdio upstream.
Source code in jdsl_harness/mcp_proxy.py
181 182 183 184 185 186 | |
serve_proxy
¤
serve_proxy(*, upstream: StdioUpstream | dict[str, Any], sink: TraceSink, capture_id: str, episode_id: str = 'ep_proxy', name: str = 'jdsl-mcp-proxy') -> None
Serve the stdio MCP proxy on this process's stdin/stdout.
Source code in jdsl_harness/mcp_proxy.py
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | |