@achieveai/hitl-mcp-server 1.2.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 (56) 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 -63
  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 -37
  36. package/dist/dialog-manager.d.ts.map +0 -1
  37. package/dist/dialog-manager.js +0 -644
  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 -125
  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
  56. package/sounds/notification.wav +0 -0
@@ -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"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Transport layer for communicating with ntfy.sh.
3
+ *
4
+ * - Publishes question messages (HTTP POST)
5
+ * - Subscribes for answer messages (SSE stream)
6
+ */
7
+ export class NtfyTransport {
8
+ config;
9
+ abortController = null;
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+ /** Full URL for the ntfy topic. */
14
+ get topicUrl() {
15
+ const base = this.config.ntfyUrl.replace(/\/+$/, '');
16
+ return `${base}/${this.config.topicId}`;
17
+ }
18
+ /**
19
+ * Publish a question message to the ntfy topic.
20
+ */
21
+ async publishQuestion(msg) {
22
+ const response = await fetch(this.topicUrl, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify(msg),
26
+ });
27
+ if (!response.ok) {
28
+ throw new Error(`Failed to publish question: ${response.status} ${response.statusText}`);
29
+ }
30
+ }
31
+ /**
32
+ * Subscribe and wait for an answer to a specific question.
33
+ * Opens an SSE connection and filters for answer messages matching questionId.
34
+ *
35
+ * @param questionId - The messageId of the question to wait for
36
+ * @param timeout - Timeout in ms (0 = no timeout)
37
+ * @returns The answer message
38
+ */
39
+ async waitForAnswer(questionId, timeout) {
40
+ return new Promise((resolve, reject) => {
41
+ this.abortController = new AbortController();
42
+ const { signal } = this.abortController;
43
+ let timer;
44
+ if (timeout && timeout > 0) {
45
+ timer = setTimeout(() => {
46
+ this.abortController?.abort();
47
+ reject(new Error('Dialog timeout'));
48
+ }, timeout);
49
+ }
50
+ const cleanup = () => {
51
+ if (timer)
52
+ clearTimeout(timer);
53
+ };
54
+ // Use ntfy's JSON stream endpoint — since=<now_unix> to only get future messages
55
+ const sinceTs = Math.floor(Date.now() / 1000);
56
+ const sseUrl = `${this.topicUrl}/json?since=${sinceTs}`;
57
+ this.startSSEListener(sseUrl, signal, (msg) => {
58
+ if (msg.type === 'answer' && msg.questionId === questionId) {
59
+ cleanup();
60
+ resolve(msg);
61
+ }
62
+ }).catch((err) => {
63
+ cleanup();
64
+ if (!signal.aborted) {
65
+ reject(err);
66
+ }
67
+ });
68
+ signal.addEventListener('abort', () => {
69
+ cleanup();
70
+ });
71
+ });
72
+ }
73
+ /**
74
+ * Publish an answer message to the ntfy topic (used by client app, exposed here for testing).
75
+ */
76
+ async publishAnswer(msg) {
77
+ const response = await fetch(this.topicUrl, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify(msg),
81
+ });
82
+ if (!response.ok) {
83
+ throw new Error(`Failed to publish answer: ${response.status} ${response.statusText}`);
84
+ }
85
+ }
86
+ /**
87
+ * Open a streaming connection to ntfy and invoke the callback for each parsed HITL message.
88
+ * Uses fetch streaming (works in Node 18+).
89
+ */
90
+ async startSSEListener(url, signal, onMessage) {
91
+ const response = await fetch(url, {
92
+ headers: { Accept: 'application/x-ndjson' },
93
+ signal,
94
+ });
95
+ if (!response.ok) {
96
+ throw new Error(`ntfy subscription failed: ${response.status} ${response.statusText}`);
97
+ }
98
+ const reader = response.body?.getReader();
99
+ if (!reader)
100
+ throw new Error('No response body from ntfy');
101
+ const decoder = new TextDecoder();
102
+ let buffer = '';
103
+ try {
104
+ while (true) {
105
+ const { done, value } = await reader.read();
106
+ if (done)
107
+ break;
108
+ buffer += decoder.decode(value, { stream: true });
109
+ const lines = buffer.split('\n');
110
+ buffer = lines.pop() ?? '';
111
+ for (const line of lines) {
112
+ const trimmed = line.trim();
113
+ if (!trimmed)
114
+ continue;
115
+ try {
116
+ const ntfyEvent = JSON.parse(trimmed);
117
+ // ntfy wraps messages — the actual payload is in the 'message' field
118
+ if (ntfyEvent.message) {
119
+ try {
120
+ const hitlMsg = JSON.parse(ntfyEvent.message);
121
+ if (hitlMsg.type === 'question' || hitlMsg.type === 'answer') {
122
+ onMessage(hitlMsg);
123
+ }
124
+ }
125
+ catch {
126
+ // Not a valid HITL message, ignore
127
+ }
128
+ }
129
+ }
130
+ catch {
131
+ // Not valid JSON, ignore
132
+ }
133
+ }
134
+ }
135
+ }
136
+ catch (err) {
137
+ if (err instanceof Error && err.name === 'AbortError')
138
+ return;
139
+ throw err;
140
+ }
141
+ }
142
+ /**
143
+ * Close any active subscriptions.
144
+ */
145
+ close() {
146
+ this.abortController?.abort();
147
+ this.abortController = null;
148
+ }
149
+ }
150
+ //# sourceMappingURL=ntfy-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ntfy-transport.js","sourceRoot":"","sources":["../src/ntfy-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /** Result of a single setup step. */
2
+ export interface SetupStepResult {
3
+ step: string;
4
+ status: 'ok' | 'created' | 'launched' | 'already_running' | 'not_found' | 'error';
5
+ message: string;
6
+ }
7
+ /** Aggregated result of the full setup process. */
8
+ export interface SetupResult {
9
+ success: boolean;
10
+ steps: SetupStepResult[];
11
+ summary: string;
12
+ }
13
+ /**
14
+ * Check if a process with the given name is currently running.
15
+ * Uses `tasklist` on Windows and `pgrep` on Unix.
16
+ */
17
+ export declare function isProcessRunning(processName: string): boolean;
18
+ /**
19
+ * Search known locations for the HITL client binary.
20
+ * Returns the first path that exists, or null if none found.
21
+ *
22
+ * @param serverDir - The directory where the compiled server JS lives (e.g. server/dist/)
23
+ */
24
+ export declare function findClientBinary(serverDir: string): string | null;
25
+ /**
26
+ * Launch the client binary as a fully detached background process.
27
+ */
28
+ export declare function launchClient(binaryPath: string): void;
29
+ /**
30
+ * Perform the full HITL client setup:
31
+ * 1. Ensure ~/.hitl/config.json exists
32
+ * 2. Check if the client process is running
33
+ * 3. Find and launch the client binary if needed
34
+ *
35
+ * @param serverDir - The directory of the running server JS (used to resolve relative binary paths)
36
+ */
37
+ export declare function performSetup(serverDir: string): Promise<SetupResult>;
38
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAMA,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,iBAAiB,GAAG,WAAW,GAAG,OAAO,CAAC;IAClF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAe7D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAajE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAMrD;AAkBD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmD1E"}
package/dist/setup.js ADDED
@@ -0,0 +1,130 @@
1
+ import { existsSync } from 'fs';
2
+ import { execSync, spawn } from 'child_process';
3
+ import { homedir } from 'os';
4
+ import path from 'path';
5
+ import { saveConfig, generateDefaultConfig, getConfigPath } from './config.js';
6
+ /**
7
+ * Check if a process with the given name is currently running.
8
+ * Uses `tasklist` on Windows and `pgrep` on Unix.
9
+ */
10
+ export function isProcessRunning(processName) {
11
+ try {
12
+ if (process.platform === 'win32') {
13
+ const output = execSync(`tasklist /FI "IMAGENAME eq ${processName}" /NH`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
14
+ return output.toLowerCase().includes(processName.toLowerCase());
15
+ }
16
+ else {
17
+ execSync(`pgrep -x ${processName}`, { stdio: ['pipe', 'pipe', 'pipe'] });
18
+ return true;
19
+ }
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ /**
26
+ * Search known locations for the HITL client binary.
27
+ * Returns the first path that exists, or null if none found.
28
+ *
29
+ * @param serverDir - The directory where the compiled server JS lives (e.g. server/dist/)
30
+ */
31
+ export function findClientBinary(serverDir) {
32
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
33
+ const candidates = [
34
+ // Dev build (relative to server dist → repo root → client)
35
+ path.resolve(serverDir, '..', '..', 'client', 'src-tauri', 'target', 'debug', binaryName),
36
+ // Release build
37
+ path.resolve(serverDir, '..', '..', 'client', 'src-tauri', 'target', 'release', binaryName),
38
+ // Installed location
39
+ path.join(homedir(), '.hitl', binaryName),
40
+ ];
41
+ return candidates.find((p) => existsSync(p)) ?? null;
42
+ }
43
+ /**
44
+ * Launch the client binary as a fully detached background process.
45
+ */
46
+ export function launchClient(binaryPath) {
47
+ const child = spawn(binaryPath, [], {
48
+ detached: true,
49
+ stdio: 'ignore',
50
+ });
51
+ child.unref();
52
+ }
53
+ /**
54
+ * Build a user-friendly "not found" message listing the paths that were searched.
55
+ */
56
+ function buildNotFoundMessage() {
57
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
58
+ return [
59
+ `HITL client binary not found. Searched locations:`,
60
+ ` - <serverDir>/../../client/src-tauri/target/debug/${binaryName}`,
61
+ ` - <serverDir>/../../client/src-tauri/target/release/${binaryName}`,
62
+ ` - ~/.hitl/${binaryName}`,
63
+ ``,
64
+ `To build the client from source, run:`,
65
+ ` cd client && cargo tauri build`,
66
+ ].join('\n');
67
+ }
68
+ /**
69
+ * Perform the full HITL client setup:
70
+ * 1. Ensure ~/.hitl/config.json exists
71
+ * 2. Check if the client process is running
72
+ * 3. Find and launch the client binary if needed
73
+ *
74
+ * @param serverDir - The directory of the running server JS (used to resolve relative binary paths)
75
+ */
76
+ export async function performSetup(serverDir) {
77
+ const steps = [];
78
+ let overallSuccess = true;
79
+ // Step 1: Ensure config exists
80
+ const configPath = getConfigPath();
81
+ if (existsSync(configPath)) {
82
+ steps.push({ step: 'config', status: 'ok', message: `Config already exists at ${configPath}` });
83
+ }
84
+ else {
85
+ try {
86
+ const config = generateDefaultConfig();
87
+ saveConfig(config);
88
+ steps.push({ step: 'config', status: 'created', message: `Created default config at ${configPath}` });
89
+ }
90
+ catch (error) {
91
+ overallSuccess = false;
92
+ steps.push({
93
+ step: 'config',
94
+ status: 'error',
95
+ message: `Failed to create config: ${error instanceof Error ? error.message : String(error)}`,
96
+ });
97
+ }
98
+ }
99
+ // Step 2: Check if client is already running
100
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
101
+ if (isProcessRunning(binaryName)) {
102
+ steps.push({ step: 'client', status: 'already_running', message: 'HITL client is already running' });
103
+ return { success: overallSuccess, steps, summary: formatSummary(steps) };
104
+ }
105
+ // Step 3: Find and launch binary
106
+ const binaryPath = findClientBinary(serverDir);
107
+ if (!binaryPath) {
108
+ overallSuccess = false;
109
+ steps.push({ step: 'client', status: 'not_found', message: buildNotFoundMessage() });
110
+ return { success: overallSuccess, steps, summary: formatSummary(steps) };
111
+ }
112
+ try {
113
+ launchClient(binaryPath);
114
+ steps.push({ step: 'client', status: 'launched', message: `Launched HITL client from ${binaryPath}` });
115
+ }
116
+ catch (error) {
117
+ overallSuccess = false;
118
+ steps.push({
119
+ step: 'client',
120
+ status: 'error',
121
+ message: `Failed to launch client: ${error instanceof Error ? error.message : String(error)}`,
122
+ });
123
+ }
124
+ return { success: overallSuccess, steps, summary: formatSummary(steps) };
125
+ }
126
+ /** Format step results into a human-readable summary. */
127
+ function formatSummary(steps) {
128
+ return steps.map((s) => `[${s.status.toUpperCase()}] ${s.step}: ${s.message}`).join('\n');
129
+ }
130
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,63 +1,58 @@
1
- {
2
- "name": "@achieveai/hitl-mcp-server",
3
- "version": "1.2.0",
4
- "description": "Human In The Loop MCP Server - Enables LLM agents to ask questions and get user feedback through interactive dialogs",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "hitl-mcp-server": "dist/index.js"
8
- },
9
- "type": "module",
10
- "files": [
11
- "dist/",
12
- "config/",
13
- "sounds/",
14
- "mcp.json",
15
- "README.md",
16
- "LICENSE",
17
- "example-usage.md"
18
- ],
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/achieveai/HumanInTheLoop.git"
22
- },
23
- "bugs": {
24
- "url": "https://github.com/achieveai/HumanInTheLoop/issues"
25
- },
26
- "homepage": "https://github.com/achieveai/HumanInTheLoop#readme",
27
- "engines": {
28
- "node": ">=18.0.0"
29
- },
30
- "scripts": {
31
- "build": "tsc",
32
- "dev": "tsx watch src/index.ts",
33
- "start": "node dist/index.js",
34
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
35
- "test:dialog": "tsx src/test-client.ts",
36
- "typecheck": "tsc --noEmit"
37
- },
38
- "keywords": [
39
- "mcp",
40
- "llm",
41
- "human-in-the-loop",
42
- "dialog",
43
- "interactive"
44
- ],
45
- "author": "MCQdb LLC <https://achieve.ai>",
46
- "license": "GPL-3.0",
47
- "dependencies": {
48
- "@modelcontextprotocol/sdk": "^1.0.0",
49
- "express": "^4.21.2",
50
- "open": "^10.1.0",
51
- "uuid": "^11.0.5"
52
- },
53
- "devDependencies": {
54
- "@types/express": "^5.0.0",
55
- "@types/jest": "^29.5.14",
56
- "@types/node": "^22.10.5",
57
- "@types/uuid": "^10.0.0",
58
- "jest": "^29.7.0",
59
- "ts-jest": "^29.2.6",
60
- "tsx": "^4.19.2",
61
- "typescript": "^5.7.3"
62
- }
63
- }
1
+ {
2
+ "name": "@achieveai/hitl-mcp-server",
3
+ "version": "2.0.0",
4
+ "description": "Human-in-the-Loop MCP Server enables LLM agents to ask questions across all your devices via ntfy.sh",
5
+ "type": "module",
6
+ "main": "./dist/mcp-server.js",
7
+ "bin": {
8
+ "hitl": "./dist/cli.js",
9
+ "hitl-mcp-server": "./dist/mcp-server.js"
10
+ },
11
+ "files": [
12
+ "dist/**/*.js",
13
+ "dist/**/*.d.ts",
14
+ "dist/**/*.map",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "mcp",
19
+ "llm",
20
+ "human-in-the-loop",
21
+ "hitl",
22
+ "dialog",
23
+ "interactive",
24
+ "model-context-protocol",
25
+ "ntfy",
26
+ "notifications"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/achieveai/hitl-mcp-server.git"
31
+ },
32
+ "license": "MIT",
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "start": "node dist/mcp-server.js",
36
+ "dev": "tsx src/index.ts",
37
+ "test": "jest --config jest.config.js",
38
+ "clean": "rimraf dist",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "dependencies": {
42
+ "@hitl/shared": "*",
43
+ "@modelcontextprotocol/sdk": "^1.0.0",
44
+ "eventsource": "^2.0.2",
45
+ "uuid": "^9.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/eventsource": "^1.1.15",
49
+ "@types/node": "^20.0.0",
50
+ "@types/uuid": "^9.0.0",
51
+ "typescript": "^5.3.0",
52
+ "tsx": "^4.0.0",
53
+ "rimraf": "^5.0.0",
54
+ "jest": "^29.7.0",
55
+ "@jest/globals": "^29.7.0",
56
+ "ts-jest": "^29.1.0"
57
+ }
58
+ }
package/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 3, 29 June 2007
3
-
4
- Copyright (C) 2025 Human-in-the-Loop MCP Server Contributors
5
-
6
- This program is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- This program is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with this program. If not, see <https://www.gnu.org/licenses/>.
18
-
19
- For the full text of the GNU General Public License Version 3,
20
- please visit: https://www.gnu.org/licenses/gpl-3.0.txt