carpool-mcp 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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/cli.js +1214 -0
- package/dist/consent.js +49 -0
- package/hooks/pre-publish.mjs +74 -0
- package/package.json +42 -0
package/dist/consent.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// apps/mcp/src/consent.ts
|
|
2
|
+
var CONSENT_MODES = ["ask", "off"];
|
|
3
|
+
var CONSENT_MODE_ENV = "CARPOOL_PUBLISH_MODE";
|
|
4
|
+
var NON_PROMPTING_MODES = /* @__PURE__ */ new Set(["bypassPermissions", "acceptEdits", "dontAsk", "auto"]);
|
|
5
|
+
var ASK_REASON = "Publishing puts this research on sale. It cannot be recalled once bought \u2014 delisting stops new sales but copies already paid for stay with their buyers. Review the question, sources and stripped items before approving.";
|
|
6
|
+
function decideConsent(input, cfg = {}) {
|
|
7
|
+
if (input.agent_id) {
|
|
8
|
+
return {
|
|
9
|
+
decision: "deny",
|
|
10
|
+
reason: "carpool_publish is not available to subagents. Publishing is irreversible and needs a human in an interactive session; a subagent cannot be one."
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
if (input.permission_mode && NON_PROMPTING_MODES.has(input.permission_mode)) {
|
|
14
|
+
return {
|
|
15
|
+
decision: "deny",
|
|
16
|
+
reason: `carpool_publish is not available in permission mode "${input.permission_mode}", which does not prompt. Publishing cannot be auto-approved.`
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const raw = cfg.mode?.trim();
|
|
20
|
+
if (raw === "off") {
|
|
21
|
+
return {
|
|
22
|
+
decision: "deny",
|
|
23
|
+
reason: `Publishing is turned off here (${CONSENT_MODE_ENV}=off).`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (raw !== void 0 && raw !== "" && raw !== "ask") {
|
|
27
|
+
return {
|
|
28
|
+
decision: "deny",
|
|
29
|
+
reason: `${CONSENT_MODE_ENV} is set to "${raw}", which is not one of ${CONSENT_MODES.join(" | ")}. Refusing rather than guessing at an irreversible action.`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { decision: "ask", reason: ASK_REASON };
|
|
33
|
+
}
|
|
34
|
+
function hookOutput(d) {
|
|
35
|
+
return {
|
|
36
|
+
hookSpecificOutput: {
|
|
37
|
+
hookEventName: "PreToolUse",
|
|
38
|
+
permissionDecision: d.decision,
|
|
39
|
+
permissionDecisionReason: d.reason
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
ASK_REASON,
|
|
45
|
+
CONSENT_MODES,
|
|
46
|
+
CONSENT_MODE_ENV,
|
|
47
|
+
decideConsent,
|
|
48
|
+
hookOutput
|
|
49
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreToolUse hook for carpool_publish.
|
|
4
|
+
*
|
|
5
|
+
* This exists because an MCP server cannot tell that it is running inside a
|
|
6
|
+
* subagent. Only the client knows, so only the client can enforce it.
|
|
7
|
+
*
|
|
8
|
+
* **It decides nothing itself.** Every rule lives in `src/consent.ts` and is
|
|
9
|
+
* imported from the compiled `dist/consent.js` below. That is the whole point of
|
|
10
|
+
* this file's current shape: it used to carry a hand-written subset of those
|
|
11
|
+
* rules — different reason strings, a different mode vocabulary, no awareness of
|
|
12
|
+
* the `auto` path — while `decideConsent`'s five tests covered the copy nobody
|
|
13
|
+
* executed. The tested decision could `allow`; this one always asked. Two
|
|
14
|
+
* implementations of one security decision, and the tests were pointed at the
|
|
15
|
+
* wrong one.
|
|
16
|
+
*
|
|
17
|
+
* What the decision can actually see, and therefore what it decides on:
|
|
18
|
+
* - `agent_id` — present for a subagent. Deniable, and denied.
|
|
19
|
+
* - `permission_mode` — a non-prompting mode must never auto-approve a
|
|
20
|
+
* publish, which is irreversible.
|
|
21
|
+
* - `CARPOOL_PUBLISH_MODE` — `ask` (default) or `off`. Nothing else.
|
|
22
|
+
*
|
|
23
|
+
* What it cannot see: whether the session is `-p`, headless, or backgrounded.
|
|
24
|
+
* There is no documented field for that. So the README says what this does —
|
|
25
|
+
* denies subagents, non-prompting modes and `off`, otherwise asks — and does not
|
|
26
|
+
* claim "blocked outside interactive sessions", which would be a lie.
|
|
27
|
+
*
|
|
28
|
+
* There is no `allow` outcome. Auto-listing always asks first.
|
|
29
|
+
*
|
|
30
|
+
* Install:
|
|
31
|
+
* "hooks": { "PreToolUse": [{ "matcher": "mcp__carpool__carpool_publish",
|
|
32
|
+
* "hooks": [{ "type": "command", "command": "node <path>/pre-publish.mjs" }] }] }
|
|
33
|
+
* Run `pnpm --filter @carpool/mcp build` first — this loads dist/consent.js.
|
|
34
|
+
*/
|
|
35
|
+
import { readFileSync } from "node:fs";
|
|
36
|
+
|
|
37
|
+
/** Emit and exit. Every exit from this file goes through here. */
|
|
38
|
+
function emit(decision, reason) {
|
|
39
|
+
process.stdout.write(
|
|
40
|
+
JSON.stringify({
|
|
41
|
+
hookSpecificOutput: {
|
|
42
|
+
hookEventName: "PreToolUse",
|
|
43
|
+
permissionDecision: decision,
|
|
44
|
+
permissionDecisionReason: reason,
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let input;
|
|
52
|
+
try {
|
|
53
|
+
input = JSON.parse(readFileSync(0, "utf8"));
|
|
54
|
+
} catch {
|
|
55
|
+
// Unparseable input is not a reason to let an irreversible action through.
|
|
56
|
+
emit("deny", "carpool publish hook could not read its input; refusing rather than guessing.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fail closed on a missing build rather than falling back to a second copy of
|
|
60
|
+
// the rules — a fallback is how the two implementations diverged in the first
|
|
61
|
+
// place.
|
|
62
|
+
let decideConsent, hookOutput;
|
|
63
|
+
try {
|
|
64
|
+
({ decideConsent, hookOutput } = await import(new URL("../dist/consent.js", import.meta.url).href));
|
|
65
|
+
} catch (e) {
|
|
66
|
+
emit(
|
|
67
|
+
"deny",
|
|
68
|
+
"carpool publish hook could not load its decision module (dist/consent.js): " +
|
|
69
|
+
`${e.message}. Run \`pnpm --filter @carpool/mcp build\`. Refusing rather than guessing.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const out = hookOutput(decideConsent(input, { mode: process.env.CARPOOL_PUBLISH_MODE }));
|
|
74
|
+
process.stdout.write(JSON.stringify(out));
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "carpool-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Carpool: search, buy and sell prior agent research over x402 on Hedera.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"carpool-mcp": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"hooks",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20.19"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/RomarioKavin1/carpool.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://carpool-dashboard-plum.vercel.app",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/RomarioKavin1/carpool/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp",
|
|
29
|
+
"claude",
|
|
30
|
+
"x402",
|
|
31
|
+
"hedera",
|
|
32
|
+
"research",
|
|
33
|
+
"agents"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@hiero-ledger/sdk": "2.88.0",
|
|
37
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
38
|
+
"@x402/fetch": "2.25.0",
|
|
39
|
+
"@x402/hedera": "2.25.0",
|
|
40
|
+
"zod": "3.25.76"
|
|
41
|
+
}
|
|
42
|
+
}
|