@autonomous-ai/harness-mcp 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -2
- package/bin.mjs +6 -0
- package/index.mjs +97 -0
- package/package.json +25 -4
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @autonomous-ai/harness-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Harness](https://harness.autonomous.ai/agents) as an MCP server, for clients that install servers from npm.
|
|
4
|
+
|
|
5
|
+
Harness runs coding agents (Claude Code, Codex) in sessions of their own, on this computer or the person's other
|
|
6
|
+
machines, and the person can watch and join every one in the Harness app. With this server an agent can start
|
|
7
|
+
harnesses, wait for them, read their answers and close them:
|
|
8
|
+
|
|
9
|
+
| Tool | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `harness_list` | the harnesses here: state, who started each, what each is about |
|
|
12
|
+
| `harness_start` | start an agent on a task in a folder or a new project, by name |
|
|
13
|
+
| `harness_wait` | wait until harnesses stop working |
|
|
14
|
+
| `harness_read` | a harness's last ask and its answer |
|
|
15
|
+
| `harness_close` | close a harness you started |
|
|
16
|
+
|
|
17
|
+
An agent may give work to, and close, only the harnesses it started.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Harness itself (macOS or Linux; no account needed for harnesses on this computer):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
curl -fsSL https://harness.autonomous.ai/cli/install.sh | bash
|
|
25
|
+
harness start
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
On Linux the installer installs tmux and curl with apt-get, which asks for your password (or run
|
|
29
|
+
`sudo apt-get install -y tmux curl` first).
|
|
30
|
+
|
|
31
|
+
Then add the server to your client, for example:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
claude mcp add harness -- npx -y @autonomous-ai/harness-mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{ "mcpServers": { "harness": { "command": "npx", "args": ["-y", "@autonomous-ai/harness-mcp"] } } }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This package runs `harness mcp` from the installed CLI (or `HARNESS_BIN`). Where Harness is not installed, the
|
|
42
|
+
server offers one tool, `harness_setup`, that says how to install it.
|
|
43
|
+
|
|
44
|
+
Claude Code and Codex can also install the Harness plugin, which brings a skill with it: see
|
|
45
|
+
[harness.autonomous.ai/agents](https://harness.autonomous.ai/agents).
|
|
46
|
+
|
|
47
|
+
MIT licensed. Source: [autonomous-ai/openharness](https://github.com/autonomous-ai/openharness/tree/main/cli/packages/harness-mcp).
|
package/bin.mjs
ADDED
package/index.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npx -y @autonomous-ai/harness-mcp`: Harness's MCP server for clients that install servers from npm. Harness is
|
|
3
|
+
* not an npm package (its installer brings its own Node and tmux, cli/scripts/install.sh), so this finds the
|
|
4
|
+
* `harness` command and runs `harness mcp` in its place. Where Harness is not installed it still answers, with one
|
|
5
|
+
* tool that says how to install it: an agent that found Harness in a directory can then ask its person to.
|
|
6
|
+
*
|
|
7
|
+
* No dependencies: it runs before anything is installed.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'node:child_process'
|
|
10
|
+
import { accessSync, constants } from 'node:fs'
|
|
11
|
+
import { homedir } from 'node:os'
|
|
12
|
+
import { delimiter, join } from 'node:path'
|
|
13
|
+
import { createInterface } from 'node:readline'
|
|
14
|
+
|
|
15
|
+
export const INSTALL = 'curl -fsSL https://harness.autonomous.ai/cli/install.sh | bash'
|
|
16
|
+
|
|
17
|
+
/** Where `harness` is: HARNESS_BIN, else the PATH, else where the installer links it (a client may start this with
|
|
18
|
+
* a PATH that leaves out ~/.local/bin). */
|
|
19
|
+
export function findHarness(env = process.env, home = homedir(), executable = isExecutable) {
|
|
20
|
+
const candidates = [
|
|
21
|
+
env.HARNESS_BIN,
|
|
22
|
+
...(env.PATH ?? '').split(delimiter).filter(Boolean).map((dir) => join(dir, 'harness')),
|
|
23
|
+
join(home, '.local', 'bin', 'harness'),
|
|
24
|
+
].filter(Boolean)
|
|
25
|
+
return candidates.find((path) => executable(path)) ?? null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isExecutable(path) {
|
|
29
|
+
try { accessSync(path, constants.X_OK); return true } catch { return false }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SETUP_TEXT = (platform) => platform === 'win32'
|
|
33
|
+
? 'Harness runs on macOS and Linux. On Windows, install it inside WSL, and run this server from there.'
|
|
34
|
+
: [
|
|
35
|
+
'Harness is not installed on this computer. It runs coding agents (Claude Code, Codex) in sessions of their own that the person can watch, and gives you tools to start, wait for, read and close them.',
|
|
36
|
+
'',
|
|
37
|
+
'To install it, ask the person, then run:',
|
|
38
|
+
` ${INSTALL}`,
|
|
39
|
+
' harness start',
|
|
40
|
+
'',
|
|
41
|
+
'No account is needed for harnesses on this computer. Then restart this MCP server (or your client) to get the Harness tools.',
|
|
42
|
+
'More: https://harness.autonomous.ai/agents',
|
|
43
|
+
].join('\n')
|
|
44
|
+
|
|
45
|
+
/** The answer to one JSON-RPC message while Harness is not installed, or null for a notification. */
|
|
46
|
+
export function setupAnswer(message, platform = process.platform) {
|
|
47
|
+
const id = message?.id
|
|
48
|
+
const isRequest = id !== undefined && id !== null
|
|
49
|
+
const reply = (result) => ({ jsonrpc: '2.0', id, result })
|
|
50
|
+
switch (message?.method) {
|
|
51
|
+
case 'initialize':
|
|
52
|
+
return reply({
|
|
53
|
+
protocolVersion: typeof message.params?.protocolVersion === 'string' ? message.params.protocolVersion : '2025-06-18',
|
|
54
|
+
capabilities: { tools: {} },
|
|
55
|
+
serverInfo: { name: 'harness', title: 'Harness (not installed)', version: '0.1.0' },
|
|
56
|
+
instructions: SETUP_TEXT(platform),
|
|
57
|
+
})
|
|
58
|
+
case 'ping': return reply({})
|
|
59
|
+
case 'tools/list':
|
|
60
|
+
return reply({ tools: [{
|
|
61
|
+
name: 'harness_setup',
|
|
62
|
+
description: 'Harness is not installed here. Call this for how to install it, which gives you tools to start, wait for, read and close other coding agents.',
|
|
63
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
64
|
+
annotations: { readOnlyHint: true },
|
|
65
|
+
}] })
|
|
66
|
+
case 'tools/call':
|
|
67
|
+
if (message.params?.name !== 'harness_setup') return { jsonrpc: '2.0', id, error: { code: -32602, message: `Unknown tool: ${String(message.params?.name)}` } }
|
|
68
|
+
return reply({ content: [{ type: 'text', text: SETUP_TEXT(platform) }] })
|
|
69
|
+
default:
|
|
70
|
+
return isRequest ? { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${String(message?.method)}` } } : null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Serve [setupAnswer] on newline-delimited JSON-RPC until the input ends. */
|
|
75
|
+
export async function serveSetup(input = process.stdin, output = process.stdout, platform = process.platform) {
|
|
76
|
+
for await (const line of createInterface({ input, crlfDelay: Infinity })) {
|
|
77
|
+
if (!line.trim()) continue
|
|
78
|
+
let message
|
|
79
|
+
try { message = JSON.parse(line) } catch {
|
|
80
|
+
output.write(`${JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } })}\n`)
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
const reply = setupAnswer(message, platform)
|
|
84
|
+
if (reply) output.write(`${JSON.stringify(reply)}\n`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Run `harness mcp` in this process's place: its stdio, its exit, and the signals a client stops it with. */
|
|
89
|
+
export function runHarness(path, run = spawn) {
|
|
90
|
+
return new Promise((done) => {
|
|
91
|
+
const child = run(path, ['mcp'], { stdio: 'inherit' })
|
|
92
|
+
const forward = (signal) => () => child.kill(signal)
|
|
93
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, forward(signal))
|
|
94
|
+
child.on('error', () => done(1))
|
|
95
|
+
child.on('exit', (code, signal) => done(code ?? (signal ? 1 : 0)))
|
|
96
|
+
})
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autonomous-ai/harness-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Harness as an MCP server: start, wait for, read and close Claude Code and Codex agents the person can watch.",
|
|
5
|
+
"mcpName": "io.github.autonomous-ai/harness",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"harness-mcp": "bin.mjs"
|
|
9
|
+
},
|
|
10
|
+
"exports": "./index.mjs",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin.mjs",
|
|
13
|
+
"index.mjs",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["mcp", "mcp-server", "agents", "claude-code", "codex", "parallel", "harness"],
|
|
20
|
+
"homepage": "https://harness.autonomous.ai/agents",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/autonomous-ai/openharness.git",
|
|
24
|
+
"directory": "cli/packages/harness-mcp"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT"
|
|
27
|
+
}
|