vouchington-tooling 0.30.0 → 0.31.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 +5 -3
- package/bin/node-guard.mjs +9 -0
- package/bin/vouchington-mcp.mjs +20 -0
- package/bin/vouchington.mjs +18 -0
- package/dist/agent-blackboard/feedback-outbox-counts.d.mts +5 -0
- package/dist/agent-blackboard/feedback-outbox-counts.mjs +11 -0
- package/dist/agent-blackboard/feedback-outbox-directory.d.mts +2 -0
- package/dist/agent-blackboard/feedback-outbox-directory.mjs +16 -0
- package/dist/agent-blackboard/feedback-outbox.mjs +5 -7
- package/dist/agent-blackboard/index.d.mts +1 -0
- package/dist/agent-blackboard/index.mjs +1 -0
- package/dist/cli/commands/agent-blackboard-flags.d.mts +4 -0
- package/dist/cli/commands/agent-blackboard-flags.mjs +34 -0
- package/dist/cli/commands/agent-blackboard.mjs +27 -35
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.mjs +12 -12
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +2 -1
- package/dist/mcp-server/dispatch.d.mts +4 -1
- package/dist/mcp-server/dispatch.mjs +6 -4
- package/dist/mcp-server/handlers-outbox.mjs +2 -7
- package/dist/mcp-server/harness.test-helpers.d.mts +1 -0
- package/dist/mcp-server/harness.test-helpers.mjs +1 -1
- package/dist/mcp-server/main.d.mts +5 -0
- package/dist/mcp-server/main.mjs +8 -0
- package/dist/mcp-server/run.d.mts +2 -0
- package/dist/mcp-server/run.mjs +4 -1
- package/dist/mcp-server/sdk.d.mts +11 -2
- package/dist/mcp-server/sdk.mjs +37 -5
- package/dist/mcp-server/server.mjs +3 -2
- package/dist/mcp-server/tools.mjs +3 -2
- package/dist/mcp-server/worktree.d.mts +17 -10
- package/dist/mcp-server/worktree.mjs +48 -31
- package/docs/mcp-server.md +83 -25
- package/package.json +9 -2
- package/scripts/build.mjs +2 -0
- package/skills/blackboard/SKILL.md +66 -4
- package/skills/manifest.json +19 -19
package/dist/mcp-server/sdk.mjs
CHANGED
|
@@ -6,17 +6,34 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import { dirname, sep } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
9
12
|
import { isMissingModuleError } from '../agent-blackboard/client.mjs';
|
|
10
13
|
const SDK_PACKAGE = '@modelcontextprotocol/sdk';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The directory the server was installed into: the parent of the outermost `node_modules` above
|
|
16
|
+
* `from`, which is where `pnpm add` must run so the peers resolve from this package. Outside any
|
|
17
|
+
* `node_modules` (a source checkout) it is the module's own directory.
|
|
18
|
+
*/
|
|
19
|
+
export function installDirectory(from = import.meta.url) {
|
|
20
|
+
const file = typeof from === 'string' && !from.startsWith('file:') ? from : fileURLToPath(from);
|
|
21
|
+
const marker = `${sep}node_modules${sep}`;
|
|
22
|
+
const index = file.indexOf(marker);
|
|
23
|
+
return index === -1 ? dirname(file) : file.slice(0, index);
|
|
24
|
+
}
|
|
25
|
+
export function missingSdkMessage(directory) {
|
|
26
|
+
return (`vouchington mcp needs ${SDK_PACKAGE} (and its zod peer), which is an optional peer ` +
|
|
27
|
+
`dependency of vouchington-tooling and is not installed. Install it in the directory ` +
|
|
28
|
+
`vouchington-tooling is installed in, not in a project: cd ${directory} && ` +
|
|
29
|
+
`pnpm add ${SDK_PACKAGE} zod`);
|
|
30
|
+
}
|
|
14
31
|
const defaultImporter = (specifier) => import(__rewriteRelativeImportExtension(specifier));
|
|
15
32
|
/**
|
|
16
33
|
* Imports the SDK lazily. A static import would make every `vouchington` command fail on a
|
|
17
34
|
* consumer that never installed the optional peer.
|
|
18
35
|
*/
|
|
19
|
-
export async function loadMcpSdk(importSdk = defaultImporter) {
|
|
36
|
+
export async function loadMcpSdk(importSdk = defaultImporter, directory = installDirectory()) {
|
|
20
37
|
try {
|
|
21
38
|
const [server, stdio, types] = await Promise.all([
|
|
22
39
|
importSdk(`${SDK_PACKAGE}/server/mcp.js`),
|
|
@@ -27,7 +44,22 @@ export async function loadMcpSdk(importSdk = defaultImporter) {
|
|
|
27
44
|
}
|
|
28
45
|
catch (error) {
|
|
29
46
|
if (isMissingModuleError(error))
|
|
30
|
-
throw new Error(
|
|
47
|
+
throw new Error(missingSdkMessage(directory), { cause: error });
|
|
31
48
|
throw error;
|
|
32
49
|
}
|
|
33
50
|
}
|
|
51
|
+
export function missingBlackboardMessage(directory) {
|
|
52
|
+
return ('vouchington mcp needs agent-blackboard, which is an optional peer dependency of ' +
|
|
53
|
+
'vouchington-tooling and is not installed. Install it in the directory vouchington-tooling ' +
|
|
54
|
+
`is installed in, not in a project: cd ${directory} && pnpm add agent-blackboard`);
|
|
55
|
+
}
|
|
56
|
+
/** Fails fast at startup when `agent-blackboard` cannot be resolved from this package's install. */
|
|
57
|
+
export function assertBlackboardInstalled(resolveFrom = import.meta.url) {
|
|
58
|
+
try {
|
|
59
|
+
createRequire(resolveFrom).resolve('agent-blackboard');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// `resolve` only fails when the package cannot be found from `resolveFrom`.
|
|
63
|
+
throw new Error(missingBlackboardMessage(installDirectory(resolveFrom)), { cause: error });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -2,8 +2,9 @@ import { callTool } from './dispatch.mjs';
|
|
|
2
2
|
import { TOOLS } from './tools.mjs';
|
|
3
3
|
export const SERVER_NAME = 'vouchington-tooling';
|
|
4
4
|
const INSTRUCTIONS = 'Journals agent-blackboard feedback for a vouchington consumer. Every tool takes an explicit ' +
|
|
5
|
-
'sessionId and an optional worktree (
|
|
6
|
-
'
|
|
5
|
+
'sessionId and an optional worktree (the absolute path of a git worktree top level; it ' +
|
|
6
|
+
'defaults to the launch directory worktree and is required when the server was launched ' +
|
|
7
|
+
'outside one). Hosts prefix the tool names differently: Claude ' +
|
|
7
8
|
'Code mcp__vouchington-tooling__<tool>, Codex mcp__vouchington_tooling__<tool>, Grok ' +
|
|
8
9
|
'vouchington-tooling__<tool> (through search_tool and use_tool), Cursor the vouchington-tooling ' +
|
|
9
10
|
'namespace (through GetDynamicTools and CallDynamicTool). Search for journal_append before ' +
|
|
@@ -7,8 +7,9 @@ const SESSION_ID = {
|
|
|
7
7
|
};
|
|
8
8
|
const WORKTREE = {
|
|
9
9
|
type: 'string',
|
|
10
|
-
description: 'Absolute path of
|
|
11
|
-
'
|
|
10
|
+
description: 'Absolute path of the top level of a git worktree (`git rev-parse --show-toplevel`), not a ' +
|
|
11
|
+
'subdirectory. Defaults to the launch directory worktree; required when the server was ' +
|
|
12
|
+
'launched outside a git worktree.',
|
|
12
13
|
};
|
|
13
14
|
const PARENT_SESSION_ID = {
|
|
14
15
|
type: ['string', 'null'],
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { type RunTextCommand } from '../gh-cli/exec.mts';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The environment for git: no inherited `GIT_*` variables, so `GIT_DIR` cannot redirect the check,
|
|
4
|
+
* and the C locale, so git's diagnostics are not translated and can be classified reliably.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isolatedGitEnv(): NodeJS.ProcessEnv;
|
|
3
7
|
export declare const runIsolatedGit: RunTextCommand;
|
|
4
|
-
/** The worktree root containing `cwd`, or a message that says how to fix the launch directory. */
|
|
5
|
-
export declare function launchWorktreeRoot(cwd: string, runGit: RunTextCommand): Promise<string>;
|
|
6
|
-
/** Paths of the non-bare entries in `git worktree list --porcelain` output. */
|
|
7
|
-
export declare function parseWorktreeList(porcelain: string): string[];
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
9
|
+
* The worktree root containing `cwd`, or undefined when git reports `cwd` is not inside one (so
|
|
10
|
+
* the server may be launched from anywhere). Any other git failure, such as a missing git binary
|
|
11
|
+
* or an unsafe repository, is thrown with the cause rather than reported as "outside a worktree".
|
|
12
|
+
*/
|
|
13
|
+
export declare function launchWorktreeRoot(cwd: string, runGit: RunTextCommand): Promise<string | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the worktree a tool call may act on. It must be an absolute path that, after
|
|
16
|
+
* resolving symlinks, is exactly the top level of a git worktree (of any repository on the
|
|
17
|
+
* machine). A subdirectory, a relative or nonexistent path, and a non-git directory are rejected.
|
|
18
|
+
* Git is asked on every call, so worktrees created after the server started are accepted and
|
|
19
|
+
* removed ones are not. Without `requested`, the launch worktree is used when there is one.
|
|
13
20
|
*/
|
|
14
21
|
export declare function resolveWorktree(input: {
|
|
15
|
-
launchRoot: string;
|
|
22
|
+
launchRoot: string | undefined;
|
|
16
23
|
requested?: string | undefined;
|
|
17
24
|
runGit: RunTextCommand;
|
|
18
25
|
}): Promise<string>;
|
|
@@ -5,8 +5,15 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { createCommandRunner } from '../gh-cli/exec.mjs';
|
|
6
6
|
import { gitEnv } from '../shared-context/index.mjs';
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The environment for git: no inherited `GIT_*` variables, so `GIT_DIR` cannot redirect the check,
|
|
10
|
+
* and the C locale, so git's diagnostics are not translated and can be classified reliably.
|
|
11
|
+
*/
|
|
12
|
+
export function isolatedGitEnv() {
|
|
13
|
+
const { LANGUAGE: _language, ...env } = gitEnv();
|
|
14
|
+
return { ...env, LC_ALL: 'C' };
|
|
15
|
+
}
|
|
16
|
+
export const runIsolatedGit = createCommandRunner('git', (command, args) => execFileAsync(command, args, { env: isolatedGitEnv() }));
|
|
10
17
|
async function canonical(path) {
|
|
11
18
|
try {
|
|
12
19
|
return await realpath(path);
|
|
@@ -15,44 +22,54 @@ async function canonical(path) {
|
|
|
15
22
|
return undefined;
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* git's two "no worktree here" outcomes: outside any repository, and inside a `.git` directory.
|
|
27
|
+
* The messages are English because `isolatedGitEnv` forces the C locale.
|
|
28
|
+
*/
|
|
29
|
+
function isNotAGitRepository(error) {
|
|
30
|
+
if (typeof error !== 'object' || error === null)
|
|
31
|
+
return false;
|
|
32
|
+
const { stderr, message } = error;
|
|
33
|
+
return [stderr, message].some((text) => typeof text === 'string' && /not a git repository|must be run in a work tree/i.test(text));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The worktree root containing `cwd`, or undefined when git reports `cwd` is not inside one (so
|
|
37
|
+
* the server may be launched from anywhere). Any other git failure, such as a missing git binary
|
|
38
|
+
* or an unsafe repository, is thrown with the cause rather than reported as "outside a worktree".
|
|
39
|
+
*/
|
|
19
40
|
export async function launchWorktreeRoot(cwd, runGit) {
|
|
41
|
+
let output;
|
|
20
42
|
try {
|
|
21
|
-
|
|
43
|
+
output = await runGit(['-C', cwd, 'rev-parse', '--show-toplevel']);
|
|
22
44
|
}
|
|
23
45
|
catch (error) {
|
|
24
|
-
|
|
46
|
+
if (isNotAGitRepository(error))
|
|
47
|
+
return undefined;
|
|
48
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
49
|
+
throw new Error(`git could not determine the worktree of ${cwd}: ${reason}. Check that git is installed and ` +
|
|
50
|
+
'on PATH, that the directory exists, and that the repository is readable by this user ' +
|
|
51
|
+
'(see safe.directory).', { cause: error });
|
|
25
52
|
}
|
|
26
|
-
|
|
27
|
-
/** Paths of the non-bare entries in `git worktree list --porcelain` output. */
|
|
28
|
-
export function parseWorktreeList(porcelain) {
|
|
29
|
-
const paths = [];
|
|
30
|
-
for (const block of porcelain.split(/\r?\n\r?\n/)) {
|
|
31
|
-
const lines = block.split(/\r?\n/);
|
|
32
|
-
const first = lines.find((line) => line.startsWith('worktree '));
|
|
33
|
-
if (first !== undefined && !lines.includes('bare'))
|
|
34
|
-
paths.push(first.slice('worktree '.length));
|
|
35
|
-
}
|
|
36
|
-
return paths;
|
|
53
|
+
return realpath(output.trim());
|
|
37
54
|
}
|
|
38
55
|
/**
|
|
39
|
-
* Resolves the worktree a tool call may act on.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* is
|
|
56
|
+
* Resolves the worktree a tool call may act on. It must be an absolute path that, after
|
|
57
|
+
* resolving symlinks, is exactly the top level of a git worktree (of any repository on the
|
|
58
|
+
* machine). A subdirectory, a relative or nonexistent path, and a non-git directory are rejected.
|
|
59
|
+
* Git is asked on every call, so worktrees created after the server started are accepted and
|
|
60
|
+
* removed ones are not. Without `requested`, the launch worktree is used when there is one.
|
|
43
61
|
*/
|
|
44
62
|
export async function resolveWorktree(input) {
|
|
45
63
|
const { launchRoot, requested, runGit } = input;
|
|
46
|
-
if (requested
|
|
47
|
-
throw new Error('worktree
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
if (requested === undefined && launchRoot === undefined)
|
|
65
|
+
throw new Error('worktree is required: this server was launched outside a git worktree, so pass ' +
|
|
66
|
+
'worktree as the absolute path of the git worktree to act on');
|
|
67
|
+
const path = requested ?? launchRoot;
|
|
68
|
+
if (!isAbsolute(path))
|
|
69
|
+
throw new Error('worktree must be an absolute path to a git worktree');
|
|
70
|
+
const target = await canonical(path);
|
|
71
|
+
if (target !== undefined && (await launchWorktreeRoot(target, runGit)) === target)
|
|
52
72
|
return target;
|
|
53
|
-
throw new Error(`worktree ${
|
|
54
|
-
`
|
|
55
|
-
}
|
|
56
|
-
function errorText(error) {
|
|
57
|
-
return error instanceof Error ? error.message : String(error);
|
|
73
|
+
throw new Error(`worktree ${path} is not the top level of a git worktree: it must exist and be the ` +
|
|
74
|
+
'directory `git rev-parse --show-toplevel` reports, not a subdirectory of it');
|
|
58
75
|
}
|
package/docs/mcp-server.md
CHANGED
|
@@ -11,30 +11,68 @@ are later phases.
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Install the server once per machine, in its own dedicated directory, not in a project or a
|
|
15
|
+
worktree: `pnpm add` installs into the directory it runs in. The server needs these packages
|
|
16
|
+
installed next to `vouchington-tooling`. `agent-blackboard` and `@modelcontextprotocol/sdk` are
|
|
17
|
+
optional peer dependencies, so nothing else in the CLI requires them. Pin exact versions; replace
|
|
18
|
+
the placeholders with the current releases:
|
|
16
19
|
|
|
17
20
|
```bash
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
mkdir -p ~/.local/share/vouchington-mcp
|
|
22
|
+
cd ~/.local/share/vouchington-mcp
|
|
23
|
+
pnpm init
|
|
24
|
+
pnpm add --save-exact vouchington-tooling@<version> agent-blackboard@<version>
|
|
25
|
+
pnpm add --save-exact @modelcontextprotocol/sdk@<version> zod@<version>
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
Launch the server by the absolute path into that directory, for example
|
|
29
|
+
`/abs/node ~/.local/share/vouchington-mcp/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs`
|
|
30
|
+
with `~` expanded to an absolute path (see [Launch](#launch)).
|
|
31
|
+
|
|
32
|
+
`zod` is required by the SDK. Both `agent-blackboard` and the SDK are resolved from the server's own
|
|
33
|
+
install, relative to this package's module, never from the worktree a tool call names, so
|
|
34
|
+
repositories do not need to depend on `agent-blackboard`. When either is missing, `vouchington mcp`
|
|
35
|
+
writes a message naming the package and the install directory to add it in to stderr and exits with
|
|
36
|
+
status 1.
|
|
25
37
|
|
|
26
38
|
The connection comes from `AGENT_BLACKBOARD_URL` and `AGENT_BLACKBOARD_TOKEN` in the server's
|
|
27
39
|
environment. The server never searches for, prints, or mints them.
|
|
28
40
|
|
|
41
|
+
## CLI fallback
|
|
42
|
+
|
|
43
|
+
If MCP is unavailable, use `vouchington agent-blackboard journal append|entries|flush|status` as described
|
|
44
|
+
in the blackboard skill. Record the fallback and delivery status in the journal and tell the human. The CLI resolves its
|
|
45
|
+
client from its own installation, so install `agent-blackboard` alongside that trusted CLI.
|
|
46
|
+
Run the script with a verified absolute `node` and a cleared environment (`env -i`), never through
|
|
47
|
+
its `#!/usr/bin/env node` line, which takes `node` from a possibly repository-controlled `PATH`.
|
|
48
|
+
A missing MCP connection does not waive persistence or readback requirements.
|
|
49
|
+
|
|
29
50
|
## Launch
|
|
30
51
|
|
|
31
52
|
```bash
|
|
32
53
|
vouchington mcp
|
|
33
54
|
```
|
|
34
55
|
|
|
35
|
-
The
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
The stable entry point, for a harness that launches the server without a shell or a `PATH`
|
|
57
|
+
lookup, is this package-relative file:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
node_modules/vouchington-tooling/bin/vouchington-mcp.mjs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Run it with an absolute Node, for example
|
|
64
|
+
`/abs/node /abs/machine/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs`. It is also the
|
|
65
|
+
`vouchington-mcp` bin (`node_modules/.bin/vouchington-mcp`) and the `vouchington-tooling/mcp-server/bin`
|
|
66
|
+
export. It takes no arguments, skips CLI argument parsing, and is a hand-written plain-JavaScript
|
|
67
|
+
file that a CLI refactor does not move; a test pins the path. Both it and `bin/vouchington.mjs` check
|
|
68
|
+
the Node version first: on Node older than 24 they print one line to stderr, such as
|
|
69
|
+
`vouchington requires Node >=24 (found 18.19.0)`, and exit with status 1.
|
|
70
|
+
|
|
71
|
+
The command takes no arguments and may be launched with any working directory, including one outside
|
|
72
|
+
every git repository (for example `~`). When the working directory is inside a git worktree, that
|
|
73
|
+
worktree's top level is the default for tools called without `worktree`. Otherwise those calls fail
|
|
74
|
+
with a tool error asking for an explicit `worktree`. stdout carries only protocol frames, and
|
|
75
|
+
diagnostics go to stderr.
|
|
38
76
|
|
|
39
77
|
## Tools
|
|
40
78
|
|
|
@@ -113,42 +151,62 @@ instructions shows it to the agent.
|
|
|
113
151
|
|
|
114
152
|
## Worktree validation
|
|
115
153
|
|
|
116
|
-
`worktree` must be an absolute path that, after resolving symlinks,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
`worktree` is checked on every call. It must be an absolute path that, after resolving symlinks,
|
|
155
|
+
equals the top level of a git worktree, that is, what `git rev-parse --show-toplevel` prints when
|
|
156
|
+
run in it. Any git worktree on the machine is accepted, including one of a repository unrelated to
|
|
157
|
+
the launch directory, so the tools can act on every checkout the user can reach. Worktrees created
|
|
158
|
+
after launch are accepted and removed ones are refused. Rejected: a subdirectory of a worktree, a
|
|
159
|
+
relative or nonexistent path, and a directory that is not in a git worktree. Git runs without
|
|
160
|
+
`GIT_*` variables, so `GIT_DIR` cannot redirect the check, and with `LC_ALL=C` and no `LANGUAGE`, so
|
|
161
|
+
its "not a git repository" diagnostic is never translated.
|
|
162
|
+
|
|
163
|
+
`worktree` only chooses where the outbox lives. It is not a trust boundary for loading code: the
|
|
164
|
+
server never imports modules from it, and a tool that needs to must first get a repository allowlist
|
|
165
|
+
from the machine registration.
|
|
166
|
+
|
|
167
|
+
When `worktree` is omitted, the launch directory's worktree top level is used if the launch
|
|
168
|
+
directory is inside one, and it is validated like any explicit path. If the server was launched
|
|
169
|
+
outside a git worktree, the call fails with a tool error asking for an explicit `worktree`.
|
|
122
170
|
|
|
123
171
|
## Registering the server
|
|
124
172
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
173
|
+
Register the machine-wide installation from [Install](#install) at user level, with absolute paths.
|
|
174
|
+
Never check a registration into a repository: it names a path and a Node that exist only on your
|
|
175
|
+
machine, and every worktree inherits it from your user config. Replace `/abs/node` with the absolute
|
|
176
|
+
path of a Node >= 24 and `/home/you` with your home directory (neither file expands `~`). Set the
|
|
177
|
+
blackboard credentials in the environment you start the harness from.
|
|
128
178
|
|
|
129
|
-
Claude Code,
|
|
179
|
+
Claude Code, `mcpServers` in `~/.claude.json`:
|
|
130
180
|
|
|
131
181
|
```json
|
|
132
182
|
{
|
|
133
183
|
"mcpServers": {
|
|
134
184
|
"vouchington-tooling": {
|
|
135
|
-
"command": "
|
|
136
|
-
"args": [
|
|
185
|
+
"command": "/abs/node",
|
|
186
|
+
"args": [
|
|
187
|
+
"/home/you/.local/share/vouchington-mcp/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs"
|
|
188
|
+
]
|
|
137
189
|
}
|
|
138
190
|
}
|
|
139
191
|
}
|
|
140
192
|
```
|
|
141
193
|
|
|
142
|
-
Codex,
|
|
194
|
+
Codex, `~/.codex/config.toml`:
|
|
143
195
|
|
|
144
196
|
```toml
|
|
145
197
|
[mcp_servers.vouchington-tooling]
|
|
146
|
-
command = "
|
|
147
|
-
args = [
|
|
198
|
+
command = "/abs/node"
|
|
199
|
+
args = [
|
|
200
|
+
"/home/you/.local/share/vouchington-mcp/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs",
|
|
201
|
+
]
|
|
202
|
+
env_vars = ["AGENT_BLACKBOARD_URL", "AGENT_BLACKBOARD_TOKEN"]
|
|
148
203
|
default_tools_approval_mode = "approve"
|
|
149
204
|
required = false
|
|
150
205
|
```
|
|
151
206
|
|
|
207
|
+
`env_vars` forwards those variables from Codex's own environment. Codex does not expand `${VAR}`
|
|
208
|
+
inside `env`, so do not write the credentials there.
|
|
209
|
+
|
|
152
210
|
Approving every tool of the server at once:
|
|
153
211
|
|
|
154
212
|
- Claude Code permission rule: `mcp__vouchington-tooling__*`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"directory": "packages/vouchington-tooling"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"vouchington": "
|
|
17
|
+
"vouchington": "bin/vouchington.mjs",
|
|
18
|
+
"vouchington-mcp": "bin/vouchington-mcp.mjs"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"ast-grep",
|
|
22
|
+
"bin",
|
|
21
23
|
"dist",
|
|
22
24
|
"docs",
|
|
23
25
|
"skills",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"import": "./dist/agent-blackboard/index.mjs",
|
|
69
71
|
"default": "./dist/agent-blackboard/index.mjs"
|
|
70
72
|
},
|
|
73
|
+
"./mcp-server/bin": "./bin/vouchington-mcp.mjs",
|
|
71
74
|
"./session-friction": {
|
|
72
75
|
"types": "./dist/session-friction/index.d.mts",
|
|
73
76
|
"import": "./dist/session-friction/index.mjs",
|
|
@@ -360,10 +363,14 @@
|
|
|
360
363
|
},
|
|
361
364
|
"peerDependencies": {
|
|
362
365
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
366
|
+
"agent-blackboard": "^0.6.0",
|
|
363
367
|
"typescript": ">=5",
|
|
364
368
|
"vitest": ">=5"
|
|
365
369
|
},
|
|
366
370
|
"peerDependenciesMeta": {
|
|
371
|
+
"agent-blackboard": {
|
|
372
|
+
"optional": true
|
|
373
|
+
},
|
|
367
374
|
"@modelcontextprotocol/sdk": {
|
|
368
375
|
"optional": true
|
|
369
376
|
},
|
package/scripts/build.mjs
CHANGED
|
@@ -34,3 +34,5 @@ await copyFile(
|
|
|
34
34
|
new URL('../skills/manifest.json', import.meta.url),
|
|
35
35
|
)
|
|
36
36
|
await chmod(new URL('../dist/cli/index.mjs', import.meta.url), 0o755)
|
|
37
|
+
await chmod(new URL('../bin/vouchington.mjs', import.meta.url), 0o755)
|
|
38
|
+
await chmod(new URL('../bin/vouchington-mcp.mjs', import.meta.url), 0o755)
|
|
@@ -35,9 +35,71 @@ launch worktree by default). Writing or retrying an entry needs no provider skil
|
|
|
35
35
|
`--file` flag, or replay command.
|
|
36
36
|
Search for `journal_append` (by bare name, so every prefix matches) before concluding the server is
|
|
37
37
|
unavailable; a deferred harness lists the tool only on request. If the search finds no such tool
|
|
38
|
-
because the server is not registered
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
because the server is not registered or not connected, use the supported CLI fallback below.
|
|
39
|
+
Tell the human that MCP is unavailable and which CLI is being used. Record the fallback reason,
|
|
40
|
+
entrypoint, affected operations, and observed delivery status in the caller's journal before
|
|
41
|
+
continuing primary work. Do not treat a permission denial as tool unavailability or bypass a host
|
|
42
|
+
approval requirement through the CLI. If neither path persists the note, stop and report the blocker.
|
|
43
|
+
|
|
44
|
+
## CLI fallback
|
|
45
|
+
|
|
46
|
+
Use a trusted machine-installed `vouchington` executable at a verified absolute path outside the
|
|
47
|
+
consumer worktree. Resolve its symlinks and verify the installation's source and version before
|
|
48
|
+
using credentials or requesting an unsandboxed run; its runtime dependencies must also belong to
|
|
49
|
+
that trusted installation. The CLI resolves its client relative to its own installation; installing
|
|
50
|
+
`agent-blackboard` only in the consumer worktree is insufficient for CLI fallback. Do not resolve the executable through the consumer's `node_modules/.bin`,
|
|
51
|
+
`pnpm exec`, or repository-controlled `PATH`. Set `BLACKBOARD_CLI` below to that verified absolute
|
|
52
|
+
script path, and bind any approval request to the same interpreter, script, and arguments.
|
|
53
|
+
Never execute the script directly: its `#!/usr/bin/env node` line would pick whichever `node` comes
|
|
54
|
+
first on `PATH`, which a consumer worktree can control. Set `BLACKBOARD_NODE` to a verified
|
|
55
|
+
absolute Node 24+ binary outside the consumer worktree, and run it with `env -i` so only `HOME`, a
|
|
56
|
+
fixed system `PATH`, and the two blackboard variables reach it. That also drops `NODE_OPTIONS` and
|
|
57
|
+
other loader variables. If no trusted CLI is
|
|
58
|
+
available, report the blocker; do not escalate a consumer-supplied executable. For local development,
|
|
59
|
+
the reviewed source CLI may run inside the sandbox, subject to the harness's credential policy.
|
|
60
|
+
`journal append` invokes the same validated writer as MCP, ensures the exact session identity,
|
|
61
|
+
patches its repository union, and verifies delivery. Use a temporary `0600` markdown input file,
|
|
62
|
+
remove it after the call, and inspect the JSON result: exit zero can mean retained pending feedback.
|
|
63
|
+
The input file is transport to the supported writer, never a replacement journal. Append returns
|
|
64
|
+
`pendingCount` for the caller and `worktreePendingCount` for the whole outbox. Use `journal status`
|
|
65
|
+
or pass `--session-id` to `journal flush` for the same two counts; an unfiltered legacy flush reports
|
|
66
|
+
only the whole-outbox count.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
blackboard() {
|
|
70
|
+
env -i HOME="$HOME" PATH=/usr/bin:/bin \
|
|
71
|
+
AGENT_BLACKBOARD_URL="$AGENT_BLACKBOARD_URL" \
|
|
72
|
+
AGENT_BLACKBOARD_TOKEN="$AGENT_BLACKBOARD_TOKEN" \
|
|
73
|
+
"$BLACKBOARD_NODE" "$BLACKBOARD_CLI" agent-blackboard "$@"
|
|
74
|
+
}
|
|
75
|
+
blackboard journal append \
|
|
76
|
+
--session-id SESSION --agent AGENT --version VERSION \
|
|
77
|
+
--mode interactive --source-event-id EVENT --work-outcome unknown \
|
|
78
|
+
--repository owner/name --coverage-status not-assessed --dropped-count 0 \
|
|
79
|
+
--outbox-directory "$PWD/.local/blackboard-outbox" --file NOTE
|
|
80
|
+
blackboard journal entries --session-id SESSION
|
|
81
|
+
blackboard journal status \
|
|
82
|
+
--session-id SESSION --outbox-directory "$PWD/.local/blackboard-outbox"
|
|
83
|
+
blackboard journal flush \
|
|
84
|
+
--session-id SESSION --outbox-directory "$PWD/.local/blackboard-outbox"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Pass `--parent-session-id` for a child, repeat `--repository` for additional repositories, and use
|
|
88
|
+
explicit coverage sources when assessed. Omit `--timestamp` so the writer owns time. Autonomous
|
|
89
|
+
mode omits `--outbox-directory` and still requires fresh online admission plus readback.
|
|
90
|
+
For session-only or snapshot operations not exposed by this CLI, use the upstream
|
|
91
|
+
`agent-blackboard` CLI under its skill's operation contract; preserve exact identities and tags.
|
|
92
|
+
A sandboxed fallback may only write the outbox: the machine's harness sandbox can withhold
|
|
93
|
+
`AGENT_BLACKBOARD_TOKEN`, so it cannot deliver or read back. If delivery or readback needs the
|
|
94
|
+
credentials and the sandbox withholds them, run that one command unsandboxed through the harness's
|
|
95
|
+
normal approval path for running one command outside the sandbox, with each such run approved
|
|
96
|
+
individually. Never read, print, copy, or export the token
|
|
97
|
+
to work around the sandbox. If approval is denied, leave the record pending in the outbox, report
|
|
98
|
+
`pendingCount`, and tell the human.
|
|
99
|
+
Return to MCP when available and journal recovery. Never silently downgrade envelope validation,
|
|
100
|
+
retention, or delivery checks because the transport changed.
|
|
101
|
+
|
|
102
|
+
## Journaling policy
|
|
41
103
|
|
|
42
104
|
1. Capture consequential observations before filing an issue: failed or recovered checks, denied or
|
|
43
105
|
approved permissions, repeated fixes, scope changes, first-party tool behavior, and architectural
|
|
@@ -65,7 +127,7 @@ it (the consumer wrapper documents it). Do not fall back to a CLI command.
|
|
|
65
127
|
`journal_append` reports it as pending. Report `pendingCount` (this session's unsent records),
|
|
66
128
|
and `worktreePendingCount` when other sessions' records are also unsent, from `journal_append`
|
|
67
129
|
or `outbox_status`. Deliver with `outbox_flush` once the provider is reachable, and continue
|
|
68
|
-
primary work only after persistence succeeds. A full,
|
|
130
|
+
primary work only after persistence succeeds. A full, corrupt, or unwritable outbox blocks
|
|
69
131
|
capture; never evict or silently discard an unsent record.
|
|
70
132
|
5. Autonomous runners require online `session_ensure`, a fresh admission `journal_append`, and
|
|
71
133
|
readback before launching an attempt. Give each attempt's admission entry a `sourceEventId`
|