@agfpd/totp-presence-mcp 0.2.0 → 0.2.1
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 -2
- package/package.json +2 -2
- package/src/server.mjs +0 -19
- package/src/totp.mjs +5 -59
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ owner-presence gate against prompt injection.
|
|
|
11
11
|
## Modes
|
|
12
12
|
|
|
13
13
|
- **SOFT (default, zero-sudo, one command):** MCP tools
|
|
14
|
-
(`totp_verify` / `totp_check_session`
|
|
14
|
+
(`totp_verify` / `totp_check_session`) the agent calls itself,
|
|
15
15
|
plus a tiny conditional SessionStart directive. Advisory — cannot DENY a tool
|
|
16
16
|
call; `tamper_resistant: false`.
|
|
17
17
|
- **HARD (opt-in, two honest actions — one sudo, one phone pairing):** a
|
|
@@ -31,7 +31,10 @@ codex plugin add totp-presence@agfpd
|
|
|
31
31
|
|
|
32
32
|
The source repo is **private** (`agfpd` org), so installing requires read access
|
|
33
33
|
to the org (git/gh authenticated) — the same access model as every other agfpd
|
|
34
|
-
plugin.
|
|
34
|
+
plugin. The MCP server is published separately on npm as
|
|
35
|
+
[`@agfpd/totp-presence-mcp`](https://www.npmjs.com/package/@agfpd/totp-presence-mcp)
|
|
36
|
+
and `.mcp.json` launches it via `npx`, so **Node ≥18 must be on `PATH`** (npx
|
|
37
|
+
fetches and runs it on first session start).
|
|
35
38
|
|
|
36
39
|
Restart the session afterward — `hooks.json` / `.mcp.json` are not hot-reload.
|
|
37
40
|
SOFT (advisory MCP) is live immediately. For HARD: `/totp-presence:setup` (one
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agfpd/totp-presence-mcp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "MCP server for the totp-presence identity-gate plugin (Claude Code + Codex CLI).
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "MCP server for the totp-presence identity-gate plugin (Claude Code + Codex CLI). Two tools — totp_verify / totp_check_session — wrap the root-owned /etc/totp-presence/verify so an agent can prove the physical owner is present before risky actions. The server runs as the user, never reads the seed; all verification happens under sudo in the audited root verifier.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "agfpd",
|
package/src/server.mjs
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* code and inspect presence sessions without a hook:
|
|
7
7
|
* totp_verify(code, integration) open/renew a presence session
|
|
8
8
|
* totp_check_session(integration) inspect an integration's session state
|
|
9
|
-
* totp_status() core install state + honest capability matrix
|
|
10
9
|
*
|
|
11
10
|
* The previous build hand-rolled the JSON-RPC stdio transport on the Python
|
|
12
11
|
* stdlib. This rewrite delegates the wire protocol (initialize handshake,
|
|
@@ -38,7 +37,6 @@ import {
|
|
|
38
37
|
ToolError,
|
|
39
38
|
totpVerify,
|
|
40
39
|
totpCheckSession,
|
|
41
|
-
totpStatus,
|
|
42
40
|
} from './totp.mjs';
|
|
43
41
|
|
|
44
42
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -95,21 +93,6 @@ export const TOOLS = [
|
|
|
95
93
|
additionalProperties: false,
|
|
96
94
|
},
|
|
97
95
|
},
|
|
98
|
-
{
|
|
99
|
-
name: 'totp_status',
|
|
100
|
-
description:
|
|
101
|
-
'Report core install state and the honest capability matrix.\n\n' +
|
|
102
|
-
'Returns core_installed, mode (soft|hard), tamper_resistant (false|true), ' +
|
|
103
|
-
"and each integration's session state for the invoking user. " +
|
|
104
|
-
"mode/tamper_resistant never over-assure: 'soft' means advisory-only " +
|
|
105
|
-
"(cannot DENY a tool call); 'hard' requires the root-owned guard to be " +
|
|
106
|
-
"installed. Note: 'hard' here means the guard is present — the PreToolUse " +
|
|
107
|
-
'hook must also be registered and the session restarted for enforcement to ' +
|
|
108
|
-
'be live. On Codex, PreToolUse covers Bash + apply_patch + mcp__ only (not ' +
|
|
109
|
-
'every tool — openai/codex#20204), and headless `codex exec` does not fire ' +
|
|
110
|
-
'the hook at all (Phase 0).',
|
|
111
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
112
|
-
},
|
|
113
96
|
];
|
|
114
97
|
|
|
115
98
|
/**
|
|
@@ -133,8 +116,6 @@ export function createServer({ version, runVerify } = {}) {
|
|
|
133
116
|
value = totpVerify(args.code, args.integration, { runVerify });
|
|
134
117
|
} else if (name === 'totp_check_session') {
|
|
135
118
|
value = totpCheckSession(args.integration);
|
|
136
|
-
} else if (name === 'totp_status') {
|
|
137
|
-
value = totpStatus();
|
|
138
119
|
} else {
|
|
139
120
|
// Tool-not-found is an exceptional condition, not a tool execution error
|
|
140
121
|
// → protocol error (-32602), matching the audited Python build.
|
package/src/totp.mjs
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { spawnSync } from 'node:child_process';
|
|
21
|
-
import { statSync, readFileSync
|
|
21
|
+
import { statSync, readFileSync } from 'node:fs';
|
|
22
22
|
import { userInfo } from 'node:os';
|
|
23
23
|
|
|
24
24
|
// --------------------------------------------------------------------------
|
|
@@ -32,7 +32,7 @@ export const RUNTIME_BASE = '/var/run/totp-presence';
|
|
|
32
32
|
|
|
33
33
|
// Root-owned PreToolUse guard scripts. Their presence is the server's best proxy
|
|
34
34
|
// for "HARD enforcement is installed" (the hook must also be registered and the
|
|
35
|
-
// session restarted
|
|
35
|
+
// session restarted before HARD actually fires — see detectCapability).
|
|
36
36
|
export const ROOT_GUARDS = [`${INSTALL_DIR}/claude-code-guard.sh`, `${INSTALL_DIR}/guard.sh`];
|
|
37
37
|
|
|
38
38
|
export const DEFAULT_WINDOW_SECONDS = 1500;
|
|
@@ -69,14 +69,6 @@ function isFile(p) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function isDir(p) {
|
|
73
|
-
try {
|
|
74
|
-
return statSync(p).isDirectory();
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
72
|
/**
|
|
81
73
|
* Resolve the human user this server should scope sessions to.
|
|
82
74
|
*
|
|
@@ -175,8 +167,7 @@ function requireCore() {
|
|
|
175
167
|
if (!coreInstalled()) {
|
|
176
168
|
throw new ToolError(
|
|
177
169
|
'totp-presence core is not installed on this host. Run: ' +
|
|
178
|
-
'/totp-presence:setup (one guided sudo).
|
|
179
|
-
'installation state.',
|
|
170
|
+
'/totp-presence:setup (one guided sudo).',
|
|
180
171
|
);
|
|
181
172
|
}
|
|
182
173
|
}
|
|
@@ -195,7 +186,7 @@ function resolveIntegration(name) {
|
|
|
195
186
|
if (!isFile(config)) {
|
|
196
187
|
throw new ToolError(
|
|
197
188
|
`Integration ${JSON.stringify(name)} is not installed — ${config} does not exist. ` +
|
|
198
|
-
'
|
|
189
|
+
'Run /totp-presence:setup to enroll an integration.',
|
|
199
190
|
);
|
|
200
191
|
}
|
|
201
192
|
return { name, user, sessionFile: sessionFileFor(name, user), config };
|
|
@@ -287,7 +278,7 @@ export function totpVerify(code, integration, { runVerify = defaultRunVerify } =
|
|
|
287
278
|
throw new ToolError(
|
|
288
279
|
(r.stderr || '').trim() ||
|
|
289
280
|
(r.stdout || '').trim() ||
|
|
290
|
-
`Verify exited with code ${rc}.
|
|
281
|
+
`Verify exited with code ${rc}. Check the totp-presence install (e.g. core/setup.sh status).`,
|
|
291
282
|
);
|
|
292
283
|
}
|
|
293
284
|
|
|
@@ -331,48 +322,3 @@ export function totpCheckSession(integration) {
|
|
|
331
322
|
expires_in_seconds: ts + window - now,
|
|
332
323
|
};
|
|
333
324
|
}
|
|
334
|
-
|
|
335
|
-
/** Report core install state and the honest capability matrix. */
|
|
336
|
-
export function totpStatus() {
|
|
337
|
-
const result = {
|
|
338
|
-
core_installed: coreInstalled(),
|
|
339
|
-
install_dir: INSTALL_DIR,
|
|
340
|
-
runtime_base: RUNTIME_BASE,
|
|
341
|
-
user: null,
|
|
342
|
-
integrations: [],
|
|
343
|
-
...detectCapability(),
|
|
344
|
-
};
|
|
345
|
-
try {
|
|
346
|
-
result.user = invokingUser();
|
|
347
|
-
} catch (exc) {
|
|
348
|
-
result.user_error = exc instanceof Error ? exc.message : String(exc);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (!isDir(INSTALL_DIR)) {
|
|
352
|
-
return result;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
let entries;
|
|
356
|
-
try {
|
|
357
|
-
entries = readdirSync(INSTALL_DIR).filter(f => f.endsWith('-config')).sort();
|
|
358
|
-
} catch {
|
|
359
|
-
entries = [];
|
|
360
|
-
}
|
|
361
|
-
for (const fname of entries) {
|
|
362
|
-
const name = fname.slice(0, -'-config'.length);
|
|
363
|
-
if (!INTEGRATION_NAME_RE.test(name)) {
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
try {
|
|
367
|
-
result.integrations.push(totpCheckSession(name));
|
|
368
|
-
} catch (exc) {
|
|
369
|
-
// never let one integration break status
|
|
370
|
-
result.integrations.push({
|
|
371
|
-
integration: name,
|
|
372
|
-
open: false,
|
|
373
|
-
error: exc instanceof Error ? exc.message : String(exc),
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return result;
|
|
378
|
-
}
|