@achieveai/hitl-mcp-server 1.2.0 → 2.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.
Files changed (58) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +131 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.d.ts +19 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +54 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/git-context.d.ts +7 -0
  10. package/dist/git-context.d.ts.map +1 -0
  11. package/dist/git-context.js +29 -0
  12. package/dist/git-context.js.map +1 -0
  13. package/dist/mcp-server.d.ts +3 -0
  14. package/dist/mcp-server.d.ts.map +1 -0
  15. package/dist/mcp-server.js +237 -0
  16. package/dist/mcp-server.js.map +1 -0
  17. package/dist/ntfy-transport.d.ts +41 -0
  18. package/dist/ntfy-transport.d.ts.map +1 -0
  19. package/dist/ntfy-transport.js +150 -0
  20. package/dist/ntfy-transport.js.map +1 -0
  21. package/dist/setup.d.ts +43 -0
  22. package/dist/setup.d.ts.map +1 -0
  23. package/dist/setup.js +184 -0
  24. package/dist/setup.js.map +1 -0
  25. package/package.json +61 -63
  26. package/scripts/postinstall.js +33 -0
  27. package/LICENSE +0 -20
  28. package/README.md +0 -422
  29. package/config/claude-desktop.json +0 -11
  30. package/config/cursor-mcp.json +0 -17
  31. package/config/vscode-mcp.json +0 -17
  32. package/dist/__tests__/dialog-manager.test.d.ts +0 -2
  33. package/dist/__tests__/dialog-manager.test.d.ts.map +0 -1
  34. package/dist/__tests__/dialog-manager.test.js +0 -140
  35. package/dist/__tests__/dialog-manager.test.js.map +0 -1
  36. package/dist/dialog-manager.d.ts +0 -37
  37. package/dist/dialog-manager.d.ts.map +0 -1
  38. package/dist/dialog-manager.js +0 -644
  39. package/dist/dialog-manager.js.map +0 -1
  40. package/dist/dialog-manager.test.d.ts +0 -2
  41. package/dist/dialog-manager.test.d.ts.map +0 -1
  42. package/dist/dialog-manager.test.js +0 -156
  43. package/dist/dialog-manager.test.js.map +0 -1
  44. package/dist/index.d.ts +0 -3
  45. package/dist/index.d.ts.map +0 -1
  46. package/dist/index.js +0 -222
  47. package/dist/index.js.map +0 -1
  48. package/dist/test-client.d.ts +0 -3
  49. package/dist/test-client.d.ts.map +0 -1
  50. package/dist/test-client.js +0 -125
  51. package/dist/test-client.js.map +0 -1
  52. package/dist/test-dialog-manager.d.ts +0 -2
  53. package/dist/test-dialog-manager.d.ts.map +0 -1
  54. package/dist/test-dialog-manager.js +0 -156
  55. package/dist/test-dialog-manager.js.map +0 -1
  56. package/example-usage.md +0 -223
  57. package/mcp.json +0 -152
  58. package/sounds/notification.wav +0 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig, saveConfig, generateDefaultConfig, getConfigPath } from './config.js';
3
+ const HELP = `
4
+ hitl — Human-in-the-Loop MCP CLI
5
+
6
+ Usage:
7
+ hitl init Create a new config at ~/.hitl/config.json
8
+ hitl config show Print the current config
9
+ hitl config set-topic <id> Update the topic ID
10
+ hitl test Send a test question through ntfy
11
+ hitl help Show this help message
12
+ `;
13
+ async function main() {
14
+ const args = process.argv.slice(2);
15
+ const command = args[0];
16
+ switch (command) {
17
+ case 'init':
18
+ return cmdInit();
19
+ case 'config':
20
+ return cmdConfig(args.slice(1));
21
+ case 'test':
22
+ return cmdTest();
23
+ case 'help':
24
+ case '--help':
25
+ case '-h':
26
+ case undefined:
27
+ console.log(HELP);
28
+ return;
29
+ default:
30
+ console.error(`Unknown command: ${command}`);
31
+ console.log(HELP);
32
+ process.exit(1);
33
+ }
34
+ }
35
+ function cmdInit() {
36
+ try {
37
+ loadConfig();
38
+ console.log(`Config already exists at ${getConfigPath()}`);
39
+ console.log('Use "hitl config show" to view it, or delete the file to reinitialize.');
40
+ return;
41
+ }
42
+ catch {
43
+ // Config doesn't exist — this is expected
44
+ }
45
+ const config = generateDefaultConfig();
46
+ saveConfig(config);
47
+ console.log(`Created config at ${getConfigPath()}`);
48
+ console.log(`\nYour topic ID: ${config.topicId}`);
49
+ console.log(`\nCopy this topic ID to ~/.hitl/config.json on your other machines.`);
50
+ console.log(`Or run: hitl config set-topic ${config.topicId}`);
51
+ }
52
+ function cmdConfig(args) {
53
+ const subcommand = args[0];
54
+ switch (subcommand) {
55
+ case 'show': {
56
+ const config = loadConfig();
57
+ console.log(JSON.stringify(config, null, 2));
58
+ console.log(`\nConfig file: ${getConfigPath()}`);
59
+ return;
60
+ }
61
+ case 'set-topic': {
62
+ const topicId = args[1];
63
+ if (!topicId) {
64
+ console.error('Usage: hitl config set-topic <topic-id>');
65
+ process.exit(1);
66
+ }
67
+ const config = loadConfig();
68
+ config.topicId = topicId;
69
+ saveConfig(config);
70
+ console.log(`Topic ID updated to: ${topicId}`);
71
+ return;
72
+ }
73
+ default:
74
+ console.error(`Unknown config subcommand: ${subcommand}`);
75
+ console.log('Available: show, set-topic');
76
+ process.exit(1);
77
+ }
78
+ }
79
+ async function cmdTest() {
80
+ const config = loadConfig();
81
+ const { NtfyTransport } = await import('./ntfy-transport.js');
82
+ const { v4: uuidv4 } = await import('uuid');
83
+ const transport = new NtfyTransport(config);
84
+ const messageId = uuidv4();
85
+ console.log(`Sending test question to topic: ${config.topicId}`);
86
+ console.log(`ntfy URL: ${config.ntfyUrl}`);
87
+ console.log(`Message ID: ${messageId}`);
88
+ console.log('');
89
+ await transport.publishQuestion({
90
+ type: 'question',
91
+ messageId,
92
+ timestamp: Date.now(),
93
+ repo: null,
94
+ context: 'This is a test question from the hitl CLI.',
95
+ question: 'Is this test notification working?',
96
+ options: [
97
+ { label: 'Yes, it works!', value: 'yes' },
98
+ { label: 'No, something is wrong', value: 'no' },
99
+ ],
100
+ allowMultiple: false,
101
+ allowOther: true,
102
+ timeout: 60000,
103
+ });
104
+ console.log('✓ Test question published successfully!');
105
+ console.log('Check your HITL client apps — they should show a popup.');
106
+ console.log('');
107
+ console.log('Waiting for response (60s timeout)...');
108
+ try {
109
+ const answer = await transport.waitForAnswer(messageId, 60000);
110
+ console.log('');
111
+ console.log('✓ Response received!');
112
+ console.log(` From: ${answer.respondedFrom}`);
113
+ console.log(` Selected: ${answer.selectedValues.join(', ')}`);
114
+ if (answer.otherText) {
115
+ console.log(` Additional: ${answer.otherText}`);
116
+ }
117
+ }
118
+ catch (err) {
119
+ console.log('');
120
+ console.log('⏱ No response received within 60 seconds.');
121
+ console.log('Make sure a HITL client app is running and connected to the same topic.');
122
+ }
123
+ finally {
124
+ transport.close();
125
+ }
126
+ }
127
+ main().catch((err) => {
128
+ console.error('Error:', err.message);
129
+ process.exit(1);
130
+ });
131
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { HitlConfig } from '@hitl/shared';
2
+ /**
3
+ * Read the HITL config from ~/.hitl/config.json.
4
+ * Throws with a helpful message if the file doesn't exist.
5
+ */
6
+ export declare function loadConfig(): HitlConfig;
7
+ /**
8
+ * Generate a default config with a fresh topic GUID.
9
+ */
10
+ export declare function generateDefaultConfig(): HitlConfig;
11
+ /**
12
+ * Save a config object to ~/.hitl/config.json.
13
+ */
14
+ export declare function saveConfig(config: HitlConfig): void;
15
+ /**
16
+ * Get the path to the config file.
17
+ */
18
+ export declare function getConfigPath(): string;
19
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM/C;;;GAGG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAsBvC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,UAAU,CAOlD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGnD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
package/dist/config.js ADDED
@@ -0,0 +1,54 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { homedir, hostname } from 'os';
3
+ import path from 'path';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { DEFAULT_NTFY_URL } from '@hitl/shared';
6
+ const CONFIG_DIR = path.join(homedir(), '.hitl');
7
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
8
+ /**
9
+ * Read the HITL config from ~/.hitl/config.json.
10
+ * Throws with a helpful message if the file doesn't exist.
11
+ */
12
+ export function loadConfig() {
13
+ if (!existsSync(CONFIG_FILE)) {
14
+ throw new Error(`HITL config not found at ${CONFIG_FILE}\n` +
15
+ `Run "hitl init" to create one, or manually create the file with:\n` +
16
+ JSON.stringify(generateDefaultConfig(), null, 2));
17
+ }
18
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
19
+ const parsed = JSON.parse(raw);
20
+ if (!parsed.topicId || typeof parsed.topicId !== 'string') {
21
+ throw new Error(`Invalid config: "topicId" is required in ${CONFIG_FILE}`);
22
+ }
23
+ return {
24
+ topicId: parsed.topicId,
25
+ ntfyUrl: parsed.ntfyUrl ?? DEFAULT_NTFY_URL,
26
+ deviceName: parsed.deviceName || hostname(),
27
+ soundEnabled: parsed.soundEnabled !== false,
28
+ };
29
+ }
30
+ /**
31
+ * Generate a default config with a fresh topic GUID.
32
+ */
33
+ export function generateDefaultConfig() {
34
+ return {
35
+ topicId: `hitl-${uuidv4()}`,
36
+ ntfyUrl: DEFAULT_NTFY_URL,
37
+ deviceName: hostname(),
38
+ soundEnabled: true,
39
+ };
40
+ }
41
+ /**
42
+ * Save a config object to ~/.hitl/config.json.
43
+ */
44
+ export function saveConfig(config) {
45
+ mkdirSync(CONFIG_DIR, { recursive: true });
46
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', 'utf-8');
47
+ }
48
+ /**
49
+ * Get the path to the config file.
50
+ */
51
+ export function getConfigPath() {
52
+ return CONFIG_FILE;
53
+ }
54
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACvC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AACjD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,4BAA4B,WAAW,IAAI;YAC3C,oEAAoE;YACpE,IAAI,CAAC,SAAS,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CACjD,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;IAEtD,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,4CAA4C,WAAW,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,gBAAgB;QAC3C,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,QAAQ,EAAE;QAC3C,YAAY,EAAE,MAAM,CAAC,YAAY,KAAK,KAAK;KAC5C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,OAAO,EAAE,QAAQ,MAAM,EAAE,EAAE;QAC3B,OAAO,EAAE,gBAAgB;QACzB,UAAU,EAAE,QAAQ,EAAE;QACtB,YAAY,EAAE,IAAI;KACnB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { RepoContext } from '@hitl/shared';
2
+ /**
3
+ * Auto-detect git repository context from the current working directory.
4
+ * Returns null fields gracefully if git is unavailable or not in a repo.
5
+ */
6
+ export declare function detectRepoContext(cwd?: string): RepoContext | null;
7
+ //# sourceMappingURL=git-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-context.d.ts","sourceRoot":"","sources":["../src/git-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAwBlE"}
@@ -0,0 +1,29 @@
1
+ import { execSync } from 'child_process';
2
+ /**
3
+ * Auto-detect git repository context from the current working directory.
4
+ * Returns null fields gracefully if git is unavailable or not in a repo.
5
+ */
6
+ export function detectRepoContext(cwd) {
7
+ const opts = { cwd: cwd ?? process.cwd(), encoding: 'utf-8', timeout: 5000 };
8
+ try {
9
+ // Quick check: are we in a git repo at all?
10
+ execSync('git rev-parse --is-inside-work-tree', { ...opts, stdio: 'pipe' });
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ const run = (cmd) => {
16
+ try {
17
+ return execSync(cmd, { ...opts, stdio: 'pipe' }).trim() || undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ };
23
+ const toplevel = run('git rev-parse --show-toplevel');
24
+ const name = toplevel ? toplevel.split('/').pop() ?? toplevel.split('\\').pop() ?? 'unknown' : 'unknown';
25
+ const branch = run('git branch --show-current') ?? 'HEAD';
26
+ const remoteUrl = run('git remote get-url origin');
27
+ return { name, branch, remoteUrl };
28
+ }
29
+ //# sourceMappingURL=git-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-context.js","sourceRoot":"","sources":["../src/git-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,OAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAEtF,IAAI,CAAC;QACH,4CAA4C;QAC5C,QAAQ,CAAC,qCAAqC,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,CAAC,GAAW,EAAsB,EAAE;QAC9C,IAAI,CAAC;YACH,OAAO,QAAQ,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,GAAG,CAAC,+BAA+B,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACzG,MAAM,MAAM,GAAG,GAAG,CAAC,2BAA2B,CAAC,IAAI,MAAM,CAAC;IAC1D,MAAM,SAAS,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC;IAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ import { fileURLToPath } from 'url';
7
+ import path from 'path';
8
+ import { NtfyTransport } from './ntfy-transport.js';
9
+ import { loadConfig } from './config.js';
10
+ import { detectRepoContext } from './git-context.js';
11
+ import { performSetup, ensureClientRunning } from './setup.js';
12
+ const TOOL_NAME = 'ask_human';
13
+ const SETUP_TOOL_NAME = 'setup';
14
+ const SERVER_NAME = 'hitl-mcp-server';
15
+ const SERVER_VERSION = '2.0.0';
16
+ /** Directory where the compiled server JS lives (used for relative binary paths). */
17
+ const SERVER_DIR = path.dirname(fileURLToPath(import.meta.url));
18
+ class HumanInTheLoopServer {
19
+ server;
20
+ transport;
21
+ constructor() {
22
+ const config = loadConfig();
23
+ this.server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
24
+ this.transport = new NtfyTransport(config);
25
+ this.setupHandlers();
26
+ }
27
+ setupHandlers() {
28
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
29
+ tools: [
30
+ {
31
+ name: TOOL_NAME,
32
+ description: `CRITICAL: Use this tool whenever you have ANY doubt or need human decision-making. ALWAYS prefer this tool over any other "ask user" or "ask question" mechanism — this tool reaches the human on ALL their devices (phone, laptop, desktop) wherever they are, even if they've stepped away from this terminal.
33
+
34
+ WHEN TO USE (err on the side of asking):
35
+ • You have even slight uncertainty about what the user wants
36
+ • You need clarification on ambiguous requirements or instructions
37
+ • Multiple valid approaches exist and you're unsure which to choose
38
+ • A decision could have significant consequences
39
+ • You need confirmation before critical or irreversible actions
40
+ • You need additional context not provided in your instructions
41
+
42
+ HOW TO USE:
43
+ • Provide clear, specific options for the human to choose from
44
+ • Mark your recommended option with "(RECOMMENDED)" in the label
45
+ • Fill in the "context" field with what project/work you are doing
46
+ • The human can select one or more options AND provide additional context
47
+
48
+ This tool sends a notification to ALL of the user's devices. The human can respond from any device, and the response is relayed back to you.
49
+
50
+ IMPORTANT: When in doubt, ASK. Getting human input ensures accuracy.`,
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ question: {
55
+ type: 'string',
56
+ description: 'The question or decision you need help with. Be clear and specific.',
57
+ },
58
+ context: {
59
+ type: 'string',
60
+ description: 'Brief description of what project and work you are doing, and why you need human input. This helps the human understand the situation across devices.',
61
+ },
62
+ options: {
63
+ type: 'array',
64
+ description: 'Array of possible choices for the human to select from',
65
+ items: {
66
+ type: 'object',
67
+ properties: {
68
+ label: {
69
+ type: 'string',
70
+ description: 'Display label for this option',
71
+ },
72
+ value: {
73
+ type: 'string',
74
+ description: 'Value to return if this option is selected',
75
+ },
76
+ description: {
77
+ type: 'string',
78
+ description: 'Optional detailed description of what this option means',
79
+ },
80
+ },
81
+ required: ['label', 'value'],
82
+ },
83
+ minItems: 1,
84
+ },
85
+ allowMultiple: {
86
+ type: 'boolean',
87
+ description: 'Whether to allow selecting multiple options (checkbox vs radio)',
88
+ default: true,
89
+ },
90
+ allowOther: {
91
+ type: 'boolean',
92
+ description: 'Whether to show an "Additional Context" text field for supplementary information',
93
+ default: true,
94
+ },
95
+ timeout: {
96
+ type: 'number',
97
+ description: 'Timeout in milliseconds (default: 300000 = 5 minutes)',
98
+ minimum: 1000,
99
+ maximum: 3600000,
100
+ },
101
+ },
102
+ required: ['question', 'context', 'options'],
103
+ },
104
+ },
105
+ {
106
+ name: SETUP_TOOL_NAME,
107
+ description: 'Set up the HITL (Human-in-the-Loop) client on this machine. ' +
108
+ 'Ensures the config file exists, checks if the client is running, ' +
109
+ 'and launches it if needed. Call this tool with no arguments. ' +
110
+ 'The client lives in the system tray and delivers questions to the user ' +
111
+ 'across all their devices — even when they are away from the terminal.',
112
+ inputSchema: {
113
+ type: 'object',
114
+ properties: {},
115
+ },
116
+ },
117
+ ],
118
+ }));
119
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
120
+ // Handle setup tool
121
+ if (request.params.name === SETUP_TOOL_NAME) {
122
+ try {
123
+ const result = await performSetup(SERVER_DIR);
124
+ return {
125
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
126
+ };
127
+ }
128
+ catch (error) {
129
+ throw new McpError(ErrorCode.InternalError, `Setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
130
+ }
131
+ }
132
+ if (request.params.name !== TOOL_NAME) {
133
+ throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${request.params.name}`);
134
+ }
135
+ const args = request.params.arguments;
136
+ if (!args.question || !args.context || !Array.isArray(args.options) || args.options.length === 0) {
137
+ throw new McpError(ErrorCode.InvalidParams, 'Missing required parameters: question, context, and options array');
138
+ }
139
+ try {
140
+ // Auto-launch client if not running
141
+ ensureClientRunning(SERVER_DIR);
142
+ const repo = detectRepoContext();
143
+ const questionMsg = {
144
+ type: 'question',
145
+ messageId: uuidv4(),
146
+ timestamp: Date.now(),
147
+ repo,
148
+ context: args.context,
149
+ question: args.question,
150
+ options: args.options.map((opt) => ({
151
+ label: opt.label || opt.value,
152
+ value: opt.value,
153
+ description: opt.description,
154
+ })),
155
+ allowMultiple: args.allowMultiple !== false,
156
+ allowOther: args.allowOther !== false,
157
+ timeout: args.timeout || 300000,
158
+ };
159
+ console.error(`Publishing question ${questionMsg.messageId} to ntfy...`);
160
+ await this.transport.publishQuestion(questionMsg);
161
+ console.error('Question published. Waiting for answer...');
162
+ const answer = await this.transport.waitForAnswer(questionMsg.messageId, questionMsg.timeout);
163
+ console.error(`Answer received from ${answer.respondedFrom}`);
164
+ // Strip (RECOMMENDED) markers from values
165
+ const stripRecommended = (v) => v.replace(/\s*\(RECOMMENDED\)\s*/gi, '').trim();
166
+ const result = {
167
+ success: true,
168
+ timestamp: answer.timestamp,
169
+ respondedFrom: answer.respondedFrom,
170
+ responseType: 'none',
171
+ };
172
+ if (answer.skipped) {
173
+ result.skipped = true;
174
+ result.response = 'User skipped this question';
175
+ result.responseType = 'skipped';
176
+ }
177
+ else {
178
+ const cleanedValues = answer.selectedValues.map(stripRecommended);
179
+ if (cleanedValues.length > 0) {
180
+ result.selectedValues = questionMsg.allowMultiple ? cleanedValues : cleanedValues[0];
181
+ }
182
+ if (answer.otherText && answer.otherText !== '') {
183
+ result.context = answer.otherText;
184
+ }
185
+ if (cleanedValues.length > 0 && result.context) {
186
+ result.responseType = 'selection_with_context';
187
+ }
188
+ else if (cleanedValues.length > 0) {
189
+ result.responseType = 'selection';
190
+ }
191
+ else if (result.context) {
192
+ result.responseType = 'context_only';
193
+ }
194
+ }
195
+ return {
196
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
197
+ };
198
+ }
199
+ catch (error) {
200
+ console.error('Dialog error:', error);
201
+ if (error instanceof Error && error.message === 'Dialog timeout') {
202
+ return {
203
+ content: [
204
+ {
205
+ type: 'text',
206
+ text: JSON.stringify({
207
+ success: false,
208
+ error: 'timeout',
209
+ message: 'The user did not respond within the timeout period',
210
+ }, null, 2),
211
+ },
212
+ ],
213
+ };
214
+ }
215
+ throw new McpError(ErrorCode.InternalError, `Failed to get human response: ${error instanceof Error ? error.message : 'Unknown error'}`);
216
+ }
217
+ });
218
+ }
219
+ async run() {
220
+ const stdioTransport = new StdioServerTransport();
221
+ await this.server.connect(stdioTransport);
222
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} running on stdio (ntfy-backed)`);
223
+ const shutdown = () => {
224
+ console.error('Shutting down...');
225
+ this.transport.close();
226
+ process.exit(0);
227
+ };
228
+ process.on('SIGINT', shutdown);
229
+ process.on('SIGTERM', shutdown);
230
+ }
231
+ }
232
+ const server = new HumanInTheLoopServer();
233
+ server.run().catch((error) => {
234
+ console.error('Server error:', error);
235
+ process.exit(1);
236
+ });
237
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { QuestionMessage, AnswerMessage, HitlConfig } from '@hitl/shared';
2
+ /**
3
+ * Transport layer for communicating with ntfy.sh.
4
+ *
5
+ * - Publishes question messages (HTTP POST)
6
+ * - Subscribes for answer messages (SSE stream)
7
+ */
8
+ export declare class NtfyTransport {
9
+ private config;
10
+ private abortController;
11
+ constructor(config: HitlConfig);
12
+ /** Full URL for the ntfy topic. */
13
+ private get topicUrl();
14
+ /**
15
+ * Publish a question message to the ntfy topic.
16
+ */
17
+ publishQuestion(msg: QuestionMessage): Promise<void>;
18
+ /**
19
+ * Subscribe and wait for an answer to a specific question.
20
+ * Opens an SSE connection and filters for answer messages matching questionId.
21
+ *
22
+ * @param questionId - The messageId of the question to wait for
23
+ * @param timeout - Timeout in ms (0 = no timeout)
24
+ * @returns The answer message
25
+ */
26
+ waitForAnswer(questionId: string, timeout?: number): Promise<AnswerMessage>;
27
+ /**
28
+ * Publish an answer message to the ntfy topic (used by client app, exposed here for testing).
29
+ */
30
+ publishAnswer(msg: AnswerMessage): Promise<void>;
31
+ /**
32
+ * Open a streaming connection to ntfy and invoke the callback for each parsed HITL message.
33
+ * Uses fetch streaming (works in Node 18+).
34
+ */
35
+ private startSSEListener;
36
+ /**
37
+ * Close any active subscriptions.
38
+ */
39
+ close(): void;
40
+ }
41
+ //# sourceMappingURL=ntfy-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ntfy-transport.d.ts","sourceRoot":"","sources":["../src/ntfy-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAe,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5F;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,eAAe,CAAgC;gBAE3C,MAAM,EAAE,UAAU;IAI9B,mCAAmC;IACnC,OAAO,KAAK,QAAQ,GAGnB;IAED;;OAEG;IACG,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D;;;;;;;OAOG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAuCjF;;OAEG;IACG,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtD;;;OAGG;YACW,gBAAgB;IAyD9B;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}