Skip to content

Commit dc20a2e

Browse files
New1Directionclaude
andcommitted
feat(adapters/introspect-mcp-ts): TS port of the bridge for npx distribution
The TypeScript port of korg-introspect-mcp. Completes the npm distribution story alongside @korg/recall-mcp — after both publish, the entire korg ecosystem becomes npx-installable in Claude Code with zero Python + pipx + venv friction. Same architecture as the Python sibling, same wire contract, same safety gating, same convention for command_id → CLI argv mapping. Both ports can coexist; the Python version stays as the reference implementation. src/: - discovery.ts: resolveBinary (PATH lookup), runIntrospect (spawn + parse with 15s timeout), validateDocument (schema check, required- field check, duplicate command_id check). Mirrors Python module. - args.ts: convention-based argv builder. snake_case → kebab-case long flags, bool true → flag-only, arrays repeat the flag, command_id dot-split → subcommand path. Same behavior across ecosystem. - safety.ts: Policy with allowed side_effects set. Defaults to none + fs_read only; KORG_INTROSPECT_MCP_ALLOW opts into broader access. all/* aliases supported. - invoker.ts: spawn the binary, format stdout per output_mode (envelope JSON pretty-printed, stream raw, session refused, none → "ok"). Non-zero exit codes append stderr + exit_code to the response so agents can debug. - server.ts: builds MCP server via SDK's low-level Server class. Tool names use command_id directly so cross-tool agents see the same identifier as recall returns. - cli.ts: <binary> [--allow] [--list-tools] [--version] [--help] with the connect-then-await-close fix. tests: 56 covering args (15), safety (12), discovery (10), invoker (6), server (6) including end-to-end SDK-client → compiled CLI → fixture binary roundtrip, sanity (1). End-to-end smoke-tested against real Rust thump binary: discovered 9 callables, default policy correctly flagged fs_write/network ones as denied. Pairs cleanly with @korg/recall-mcp for the full loop. Also adds adapters/PUBLISHING.md documenting the npm publish flow: one-time @korg org creation, npm login, `npm publish --access public` per package, versioning, deprecate-vs-unpublish policy. npm pack dry-run preview verified: - @korg/recall-mcp v0.1.0: 12.8 kB packed, 39.1 kB unpacked - @korg/introspect-mcp v0.1.0: 10.8 kB packed, 33.9 kB unpacked Each ships only dist/ + README + LICENSE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 94a6ed6 commit dc20a2e

19 files changed

Lines changed: 1921 additions & 0 deletions

‎adapters/PUBLISHING.md‎

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Publishing the npm packages
2+
3+
This doc covers the publish flow for `@korg/recall-mcp` and
4+
`@korg/introspect-mcp`. Both packages are TypeScript ports of their
5+
Python siblings (`adapters/recall-mcp/` and `adapters/introspect-mcp/`)
6+
intended for `npx`-style distribution to Claude Code users.
7+
8+
## One-time setup
9+
10+
### 1. Create the `@korg` org on npm
11+
12+
Scoped packages (`@korg/...`) require an npm org. **Public packages
13+
under a scope are free.**
14+
15+
1. Go to https://www.npmjs.com/org/create
16+
2. Org name: `korg`
17+
3. Visibility: public
18+
4. After creation, add yourself (and any co-maintainers) as
19+
members under https://www.npmjs.com/settings/korg/members
20+
21+
If the `@korg` scope is taken, fall back to your personal scope
22+
(e.g. `@new1direction`) by editing each `package.json`:
23+
24+
```json
25+
"name": "@new1direction/recall-mcp"
26+
```
27+
28+
### 2. Log in to npm
29+
30+
```bash
31+
npm login
32+
# follow the OTP / 2FA prompts
33+
npm whoami # confirm
34+
```
35+
36+
## Publish flow
37+
38+
For each package (`recall-mcp-ts`, `introspect-mcp-ts`):
39+
40+
```bash
41+
cd adapters/recall-mcp-ts
42+
43+
# Sanity check what would ship
44+
npm publish --dry-run
45+
46+
# Run the test suite one more time
47+
npm test
48+
49+
# Publish (scoped packages default to private, override with --access)
50+
npm publish --access public
51+
```
52+
53+
Same flow for `introspect-mcp-ts`.
54+
55+
After publishing, both packages become available via:
56+
57+
```bash
58+
npx -y @korg/recall-mcp --help
59+
npx -y @korg/introspect-mcp thump --list-tools
60+
```
61+
62+
## What gets published
63+
64+
The `files` field in each `package.json` ships only:
65+
66+
- `dist/` — compiled JavaScript + `.d.ts` declarations
67+
- `README.md`
68+
- `LICENSE`
69+
70+
The `tests/`, `src/`, `tsconfig.json`, and `node_modules/` are NOT
71+
included. The published tarball weighs ~11–13 kB.
72+
73+
## Versioning
74+
75+
Both packages use Semantic Versioning. Bump via npm:
76+
77+
```bash
78+
npm version patch # 0.1.0 → 0.1.1
79+
npm version minor # 0.1.0 → 0.2.0
80+
npm version major # 0.1.0 → 1.0.0
81+
```
82+
83+
`npm version` creates a git tag automatically. Commit + push the
84+
version bump + tag, then `npm publish`.
85+
86+
## Coordinating with the Python siblings
87+
88+
The TS port shares the `korg:introspect@v1` schema with the Python
89+
reference. If the schema bumps (e.g. `@v2`), both implementations must
90+
bump in lockstep. There's a contract test in both that asserts the
91+
current schema ID — CI on either side will fail if drift sneaks in.
92+
93+
Tool name (`korg-recall-mcp.recall`), exit-code table, and Capabilities
94+
field set are all part of the cross-language contract. The Python
95+
reference is authoritative for any new field.
96+
97+
## What the npm org name unlocks
98+
99+
When users see `npx -y @korg/recall-mcp` in a doc or blog post, the
100+
`@korg` prefix immediately signals "this is part of the korg
101+
ecosystem" — discoverable on npm via `npm search @korg`. The scope
102+
also reserves the namespace; nobody else can publish `@korg/whatever`.
103+
104+
## Withdraw / unpublish
105+
106+
npm's 24-hour withdrawal window: `npm unpublish @korg/recall-mcp@0.1.0`
107+
within 24 hours of publishing. After 24 hours, npm only allows
108+
`deprecate`, not `unpublish` (security policy). Use `deprecate` for
109+
any breaking issue:
110+
111+
```bash
112+
npm deprecate @korg/recall-mcp@0.1.0 "Critical bug — use 0.1.1+"
113+
```

‎adapters/introspect-mcp-ts/LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Korg ecosystem maintainers
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# @korg/introspect-mcp
2+
3+
> **Bridge any `--introspect`-aware CLI to Claude Code via MCP. TypeScript edition.**
4+
5+
One MCP server, any binary in the ecosystem. After installing once,
6+
adding a new tool surface to Claude Code's agent is **just one line of
7+
config per binary**. The TS port of `korg-introspect-mcp` — same wire
8+
contract, same safety gating, same `command_id` conventions as the
9+
Python reference, distributed via npm for `npx` install.
10+
11+
```bash
12+
npx -y @korg/introspect-mcp thump --list-tools
13+
# [korg-introspect-mcp] thump v0.2.0 — 9 tool(s):
14+
# thump.tui side_effects=fs_read ...
15+
# thump.generate side_effects=fs_write ... (would be denied)
16+
# ...
17+
```
18+
19+
## Why a TypeScript port
20+
21+
The Python `korg-introspect-mcp` works perfectly — but it requires
22+
`pipx install` + a Python venv. For Claude Code (a TypeScript app)
23+
users on Mac/Linux, the canonical "install a new MCP server" idiom is
24+
`npx -y @some/package`. This TS port closes that distribution gap.
25+
26+
The Python version stays as the reference implementation. Both ports
27+
emit the same MCP wire format, accept the same args, and produce the
28+
same tool results.
29+
30+
## Register with Claude Code
31+
32+
Add one entry per binary you want to expose:
33+
34+
```json
35+
{
36+
"mcpServers": {
37+
"korg-thump": {
38+
"command": "npx",
39+
"args": ["-y", "@korg/introspect-mcp", "thump"],
40+
"env": { "KORG_INTROSPECT_MCP_ALLOW": "fs_write" }
41+
},
42+
"korg": {
43+
"command": "npx",
44+
"args": ["-y", "@korg/introspect-mcp", "/usr/local/bin/korg"],
45+
"env": { "KORG_INTROSPECT_MCP_ALLOW": "all" }
46+
},
47+
"korg-korgex": {
48+
"command": "npx",
49+
"args": ["-y", "@korg/introspect-mcp", "korgex"]
50+
}
51+
}
52+
}
53+
```
54+
55+
Restart Claude Code. **30+ tools across the entire korg ecosystem**
56+
land in the agent's toolset. Tool names match each binary's
57+
`command_id` from its `--introspect` document, so the
58+
recall→re-invoke loop (when paired with `@korg/recall-mcp`) is
59+
deterministic.
60+
61+
## What it does
62+
63+
1. At startup, runs `<binary> --introspect` and parses the
64+
`korg:introspect@v1` document.
65+
2. Registers one MCP tool per `Callable`, using the introspect
66+
`input_schema` directly as the MCP `inputSchema` (no re-encoding).
67+
3. When the agent calls a tool, the bridge:
68+
- maps MCP arguments to CLI argv (kebab-case long flags,
69+
bool flag-on-true, arrays repeat the flag, command_id segments
70+
become subcommand path),
71+
- execs the binary as a subprocess,
72+
- formats stdout per the declared `output_mode` (envelope JSON
73+
parsed + pretty-printed, stream returned raw, session refused
74+
as unsupported in v1, none → "ok"),
75+
- honors `capabilities.side_effects` — refuses
76+
`fs_write` / `network` / `ledger_write` invocations unless
77+
explicitly allowed.
78+
79+
## Safety gate
80+
81+
By default only `none` and `fs_read` callables are invocable.
82+
Everything else requires explicit opt-in via the
83+
`KORG_INTROSPECT_MCP_ALLOW` env var (set in the MCP server's
84+
`env` block):
85+
86+
| Value | Allows |
87+
|---|---|
88+
| (unset) | `none`, `fs_read` (safe default) |
89+
| `fs_write` | + file writes |
90+
| `network` | + network access |
91+
| `ledger_write` | + writing to the korg ledger |
92+
| `fs_write,network,ledger_write` | full access (combine) |
93+
| `all` or `*` | everything |
94+
95+
When the agent calls a denied tool, the response explains exactly
96+
which env var to set. **The default is stricter than running the
97+
binary directly via Bash** — defense in depth for MCP-driven
98+
invocations.
99+
100+
## Argv mapping convention
101+
102+
| MCP argument | CLI argv |
103+
|---|---|
104+
| `"query": "x"` | `--query x` |
105+
| `"top_n": 5` | `--top-n 5` |
106+
| `"quiet": true` | `--quiet` |
107+
| `"quiet": false` | *(omitted)* |
108+
| `"tags": ["a", "b"]` | `--tags a --tags b` |
109+
| `command_id: "thump.bun.script.run"` | `bun script run` as subcommand path |
110+
111+
This convention matches clap + argparse with kebab-case long flags,
112+
which the entire korg ecosystem uses. If a binary deviates, the right
113+
fix is on the binary side — keep this mapper boring.
114+
115+
## CLI usage
116+
117+
```bash
118+
# Sanity-check what an agent will see (no MCP server launched):
119+
npx -y @korg/introspect-mcp thump --list-tools
120+
121+
# Run as MCP server on stdio (default — what Claude Code calls):
122+
npx -y @korg/introspect-mcp thump --allow fs_write
123+
124+
# Pass an absolute path or PATH name:
125+
npx -y @korg/introspect-mcp /usr/local/bin/korgex --allow all
126+
```
127+
128+
## What's deliberately NOT supported in v1
129+
130+
- **`output_mode: session`** — long-lived stateful sessions over stdio
131+
MCP need persistent bidirectional I/O. Agents that try to call a
132+
session-mode callable get a clear error explaining why.
133+
- **Streaming progress mid-call.** Stdout is buffered and returned at
134+
completion. A v2 could stream chunks back via MCP notifications.
135+
- **Auto-rerun on tool drift.** If `<binary> --introspect` changes
136+
between bridge invocations, restart the MCP server to pick up
137+
the new schema.
138+
139+
## Tests
140+
141+
56 tests covering:
142+
- **args.test.ts** (15) — kebab conversion, bool/array/null handling,
143+
command_id subcommand-path splitting, naked binary, nested
144+
subcommand paths, dashed names not split, fallback for
145+
missing-prefix command_ids.
146+
- **safety.test.ts** (12) — default deny, env-var parsing
147+
(single/comma/`all`/`*`/whitespace), case-insensitivity, factories,
148+
denial-message contents.
149+
- **discovery.test.ts** (10) — binary lookup (absolute/PATH),
150+
`--introspect` invocation, malformed JSON, schema mismatch,
151+
duplicate IDs, missing required fields, valid minimal document,
152+
end-to-end with a fixture binary.
153+
- **invoker.test.ts** (6) — envelope JSON pretty-print, kebab-case
154+
flag flow-through, array flag repetition, session-mode refused,
155+
non-zero exit reports stderr+code, timeout.
156+
- **server.test.ts** (6) — `buildToolsList` produces correct MCP
157+
shape, tags capabilities in description, passes input_schema
158+
verbatim, end-to-end SDK client → compiled CLI → fixture binary
159+
roundtrip (init + list + call), default policy refuses fs_write
160+
with a helpful env-var hint.
161+
- **_sanity.test.ts** (1) — fixture binary spawns correctly.
162+
163+
Run them:
164+
```bash
165+
npm test
166+
```
167+
168+
## Architecture
169+
170+
```
171+
src/
172+
├── discovery.ts # exec <binary> --introspect, validate doc
173+
├── args.ts # MCP arguments → CLI argv (uniform convention)
174+
├── safety.ts # Policy: side_effects allow-list
175+
├── invoker.ts # exec the binary, format by output_mode
176+
├── server.ts # MCP wiring via @modelcontextprotocol/sdk
177+
├── cli.ts # CLI entry: <binary> [--allow] [--list-tools]
178+
└── index.ts # public API for programmatic use
179+
```
180+
181+
Zero required runtime dependencies beyond `@modelcontextprotocol/sdk`.
182+
Pure stdlib for everything else.
183+
184+
## License
185+
186+
MIT.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "@korg/introspect-mcp",
3+
"version": "0.1.0",
4+
"description": "Generic --introspect → MCP server bridge. Wrap any korg:introspect@v1 binary as an MCP server with one line of Claude Code config.",
5+
"type": "module",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"bin": {
9+
"korg-introspect-mcp": "dist/cli.js"
10+
},
11+
"files": [
12+
"dist",
13+
"README.md",
14+
"LICENSE"
15+
],
16+
"engines": {
17+
"node": ">=18"
18+
},
19+
"scripts": {
20+
"build": "tsc",
21+
"clean": "rm -rf dist",
22+
"test": "node --test --import tsx tests/*.test.ts",
23+
"prepublishOnly": "npm run clean && npm run build"
24+
},
25+
"keywords": [
26+
"mcp",
27+
"claude-code",
28+
"introspect",
29+
"cli-bridge",
30+
"korg"
31+
],
32+
"author": "Korg ecosystem maintainers",
33+
"license": "MIT",
34+
"repository": {
35+
"type": "git",
36+
"url": "https://github.com/New1Direction/korg.git",
37+
"directory": "adapters/introspect-mcp-ts"
38+
},
39+
"dependencies": {
40+
"@modelcontextprotocol/sdk": "^1.0.0"
41+
},
42+
"devDependencies": {
43+
"@types/node": "^22.0.0",
44+
"tsx": "^4.7.0",
45+
"typescript": "^5.4.0"
46+
}
47+
}

0 commit comments

Comments
 (0)