@achieveai/hitl-mcp-server 1.1.0 → 2.0.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 (55) 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/{index.js → mcp-server.js} +124 -113
  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 +38 -0
  22. package/dist/setup.d.ts.map +1 -0
  23. package/dist/setup.js +130 -0
  24. package/dist/setup.js.map +1 -0
  25. package/package.json +58 -62
  26. package/LICENSE +0 -20
  27. package/README.md +0 -422
  28. package/config/claude-desktop.json +0 -11
  29. package/config/cursor-mcp.json +0 -17
  30. package/config/vscode-mcp.json +0 -17
  31. package/dist/__tests__/dialog-manager.test.d.ts +0 -2
  32. package/dist/__tests__/dialog-manager.test.d.ts.map +0 -1
  33. package/dist/__tests__/dialog-manager.test.js +0 -140
  34. package/dist/__tests__/dialog-manager.test.js.map +0 -1
  35. package/dist/dialog-manager.d.ts +0 -36
  36. package/dist/dialog-manager.d.ts.map +0 -1
  37. package/dist/dialog-manager.js +0 -529
  38. package/dist/dialog-manager.js.map +0 -1
  39. package/dist/dialog-manager.test.d.ts +0 -2
  40. package/dist/dialog-manager.test.d.ts.map +0 -1
  41. package/dist/dialog-manager.test.js +0 -156
  42. package/dist/dialog-manager.test.js.map +0 -1
  43. package/dist/index.d.ts +0 -3
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/index.js.map +0 -1
  46. package/dist/test-client.d.ts +0 -3
  47. package/dist/test-client.d.ts.map +0 -1
  48. package/dist/test-client.js +0 -111
  49. package/dist/test-client.js.map +0 -1
  50. package/dist/test-dialog-manager.d.ts +0 -2
  51. package/dist/test-dialog-manager.d.ts.map +0 -1
  52. package/dist/test-dialog-manager.js +0 -156
  53. package/dist/test-dialog-manager.js.map +0 -1
  54. package/example-usage.md +0 -223
  55. package/mcp.json +0 -152
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":""}
@@ -1,25 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
5
- import { DialogManager } from './dialog-manager.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
6
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 } from './setup.js';
7
12
  const TOOL_NAME = 'ask_human';
13
+ const SETUP_TOOL_NAME = 'setup';
8
14
  const SERVER_NAME = 'hitl-mcp-server';
9
- const SERVER_VERSION = '1.0.0';
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));
10
18
  class HumanInTheLoopServer {
11
19
  server;
12
- dialogManager;
20
+ transport;
13
21
  constructor() {
14
- this.server = new Server({
15
- name: SERVER_NAME,
16
- version: SERVER_VERSION,
17
- }, {
18
- capabilities: {
19
- tools: {},
20
- },
21
- });
22
- this.dialogManager = new DialogManager();
22
+ const config = loadConfig();
23
+ this.server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
24
+ this.transport = new NtfyTransport(config);
23
25
  this.setupHandlers();
24
26
  }
25
27
  setupHandlers() {
@@ -27,34 +29,35 @@ class HumanInTheLoopServer {
27
29
  tools: [
28
30
  {
29
31
  name: TOOL_NAME,
30
- description: `CRITICAL: Use this tool whenever you have ANY doubt or need human decision-making. This tool is ESSENTIAL for:
31
-
32
- WHEN TO USE (err on the side of asking):
33
- • You have even slight uncertainty about what the user wants
34
- • You need clarification on ambiguous requirements or instructions
35
- The user explicitly told you to ask for their input on decisions
36
- Multiple valid approaches exist and you're unsure which to choose
37
- A decision could have significant consequences
38
- • You're about to make assumptions that might be wrong
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
- Think carefully about which option you would recommend and mark it with "(RECOMMENDED)" in the label
45
- Example: { label: "PostgreSQL (RECOMMENDED)", value: "postgres", description: "Best for complex queries" }
46
- The human can select one or more options AND provide additional context
47
- • The "(RECOMMENDED)" marker helps guide the user but won't appear in the returned value
48
-
49
- This tool opens an interactive browser dialog with a pleasant notification sound. The human can select options and optionally provide additional context to guide your next steps.
50
-
51
- IMPORTANT: When in doubt, ASK. Using this tool is far better than making incorrect assumptions. Getting human input ensures accuracy and alignment with user expectations.`,
32
+ description: `CRITICAL: Use this tool whenever you have ANY doubt or need human decision-making.
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.`,
52
51
  inputSchema: {
53
52
  type: 'object',
54
53
  properties: {
55
54
  question: {
56
55
  type: 'string',
57
- description: 'The question or decision you need help with. Be clear and specific.'
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.',
58
61
  },
59
62
  options: {
60
63
  type: 'array',
@@ -64,95 +67,117 @@ IMPORTANT: When in doubt, ASK. Using this tool is far better than making incorre
64
67
  properties: {
65
68
  label: {
66
69
  type: 'string',
67
- description: 'Display label for this option'
70
+ description: 'Display label for this option',
68
71
  },
69
72
  value: {
70
73
  type: 'string',
71
- description: 'Value to return if this option is selected'
74
+ description: 'Value to return if this option is selected',
72
75
  },
73
76
  description: {
74
77
  type: 'string',
75
- description: 'Optional detailed description of what this option means'
76
- }
78
+ description: 'Optional detailed description of what this option means',
79
+ },
77
80
  },
78
- required: ['label', 'value']
81
+ required: ['label', 'value'],
79
82
  },
80
- minItems: 1
83
+ minItems: 1,
81
84
  },
82
85
  allowMultiple: {
83
86
  type: 'boolean',
84
87
  description: 'Whether to allow selecting multiple options (checkbox vs radio)',
85
- default: true
88
+ default: true,
86
89
  },
87
90
  allowOther: {
88
91
  type: 'boolean',
89
- description: 'Whether to show an "Additional Context" text field where the user can provide supplementary information alongside their selection(s)',
90
- default: true
91
- },
92
- context: {
93
- type: 'string',
94
- description: 'Additional context to help the human understand the situation'
92
+ description: 'Whether to show an "Additional Context" text field for supplementary information',
93
+ default: true,
95
94
  },
96
95
  timeout: {
97
96
  type: 'number',
98
- description: 'Timeout in milliseconds (default: no timeout)',
97
+ description: 'Timeout in milliseconds (default: 300000 = 5 minutes)',
99
98
  minimum: 1000,
100
- maximum: 3600000
101
- }
99
+ maximum: 3600000,
100
+ },
102
101
  },
103
- required: ['question', 'options']
104
- }
105
- }
106
- ]
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
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {},
113
+ },
114
+ },
115
+ ],
107
116
  }));
108
117
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
118
+ // Handle setup tool
119
+ if (request.params.name === SETUP_TOOL_NAME) {
120
+ try {
121
+ const result = await performSetup(SERVER_DIR);
122
+ return {
123
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
124
+ };
125
+ }
126
+ catch (error) {
127
+ throw new McpError(ErrorCode.InternalError, `Setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
128
+ }
129
+ }
109
130
  if (request.params.name !== TOOL_NAME) {
110
131
  throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${request.params.name}`);
111
132
  }
112
133
  const args = request.params.arguments;
113
- if (!args.question || !Array.isArray(args.options) || args.options.length === 0) {
114
- throw new McpError(ErrorCode.InvalidParams, 'Missing required parameters: question and options array');
134
+ if (!args.question || !args.context || !Array.isArray(args.options) || args.options.length === 0) {
135
+ throw new McpError(ErrorCode.InvalidParams, 'Missing required parameters: question, context, and options array');
115
136
  }
116
137
  try {
117
- const dialogOptions = args.options.map((opt) => ({
118
- label: opt.label || opt.value,
119
- value: opt.value,
120
- description: opt.description
121
- }));
122
- const response = await this.dialogManager.showDialog({
123
- id: uuidv4(),
138
+ const repo = detectRepoContext();
139
+ const questionMsg = {
140
+ type: 'question',
141
+ messageId: uuidv4(),
142
+ timestamp: Date.now(),
143
+ repo,
144
+ context: args.context,
124
145
  question: args.question,
125
- options: dialogOptions,
146
+ options: args.options.map((opt) => ({
147
+ label: opt.label || opt.value,
148
+ value: opt.value,
149
+ description: opt.description,
150
+ })),
126
151
  allowMultiple: args.allowMultiple !== false,
127
152
  allowOther: args.allowOther !== false,
128
- context: args.context,
129
- timeout: args.timeout
130
- });
131
- // Helper function to strip (RECOMMENDED) markers from values
132
- const stripRecommended = (value) => {
133
- return value.replace(/\s*\(RECOMMENDED\)\s*/gi, '').trim();
153
+ timeout: args.timeout || 300000,
134
154
  };
135
- let result = {
155
+ console.error(`Publishing question ${questionMsg.messageId} to ntfy...`);
156
+ await this.transport.publishQuestion(questionMsg);
157
+ console.error('Question published. Waiting for answer...');
158
+ const answer = await this.transport.waitForAnswer(questionMsg.messageId, questionMsg.timeout);
159
+ console.error(`Answer received from ${answer.respondedFrom}`);
160
+ // Strip (RECOMMENDED) markers from values
161
+ const stripRecommended = (v) => v.replace(/\s*\(RECOMMENDED\)\s*/gi, '').trim();
162
+ const result = {
136
163
  success: true,
137
- timestamp: response.timestamp
164
+ timestamp: answer.timestamp,
165
+ respondedFrom: answer.respondedFrom,
166
+ responseType: 'none',
138
167
  };
139
- if (response.otherText === 'SKIPPED') {
168
+ if (answer.skipped) {
140
169
  result.skipped = true;
141
170
  result.response = 'User skipped this question';
142
171
  result.responseType = 'skipped';
143
172
  }
144
173
  else {
145
- // Clean the selected values
146
- const cleanedValues = response.selectedValues.map(stripRecommended);
147
- // Include selected values if any
174
+ const cleanedValues = answer.selectedValues.map(stripRecommended);
148
175
  if (cleanedValues.length > 0) {
149
- result.selectedValues = args.allowMultiple ? cleanedValues : cleanedValues[0];
176
+ result.selectedValues = questionMsg.allowMultiple ? cleanedValues : cleanedValues[0];
150
177
  }
151
- // Include context if provided
152
- if (response.otherText && response.otherText !== '') {
153
- result.context = response.otherText;
178
+ if (answer.otherText && answer.otherText !== '') {
179
+ result.context = answer.otherText;
154
180
  }
155
- // Determine response type
156
181
  if (cleanedValues.length > 0 && result.context) {
157
182
  result.responseType = 'selection_with_context';
158
183
  }
@@ -162,17 +187,9 @@ IMPORTANT: When in doubt, ASK. Using this tool is far better than making incorre
162
187
  else if (result.context) {
163
188
  result.responseType = 'context_only';
164
189
  }
165
- else {
166
- result.responseType = 'none';
167
- }
168
190
  }
169
191
  return {
170
- content: [
171
- {
172
- type: 'text',
173
- text: JSON.stringify(result, null, 2)
174
- }
175
- ]
192
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
176
193
  };
177
194
  }
178
195
  catch (error) {
@@ -185,33 +202,27 @@ IMPORTANT: When in doubt, ASK. Using this tool is far better than making incorre
185
202
  text: JSON.stringify({
186
203
  success: false,
187
204
  error: 'timeout',
188
- message: 'The user did not respond within the timeout period'
189
- }, null, 2)
190
- }
191
- ]
205
+ message: 'The user did not respond within the timeout period',
206
+ }, null, 2),
207
+ },
208
+ ],
192
209
  };
193
210
  }
194
- throw new McpError(ErrorCode.InternalError, `Failed to show dialog: ${error instanceof Error ? error.message : 'Unknown error'}`);
211
+ throw new McpError(ErrorCode.InternalError, `Failed to get human response: ${error instanceof Error ? error.message : 'Unknown error'}`);
195
212
  }
196
213
  });
197
214
  }
198
215
  async run() {
199
- const transport = new StdioServerTransport();
200
- await this.dialogManager.initialize();
201
- await this.server.connect(transport);
202
- console.error(`${SERVER_NAME} v${SERVER_VERSION} running on stdio`);
203
- process.on('SIGINT', async () => {
204
- await this.cleanup();
205
- process.exit(0);
206
- });
207
- process.on('SIGTERM', async () => {
208
- await this.cleanup();
216
+ const stdioTransport = new StdioServerTransport();
217
+ await this.server.connect(stdioTransport);
218
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} running on stdio (ntfy-backed)`);
219
+ const shutdown = () => {
220
+ console.error('Shutting down...');
221
+ this.transport.close();
209
222
  process.exit(0);
210
- });
211
- }
212
- async cleanup() {
213
- console.error('Shutting down...');
214
- await this.dialogManager.close();
223
+ };
224
+ process.on('SIGINT', shutdown);
225
+ process.on('SIGTERM', shutdown);
215
226
  }
216
227
  }
217
228
  const server = new HumanInTheLoopServer();
@@ -219,4 +230,4 @@ server.run().catch((error) => {
219
230
  console.error('Server error:', error);
220
231
  process.exit(1);
221
232
  });
222
- //# sourceMappingURL=index.js.map
233
+ //# 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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"}