Skip to content

Commit fac881b

Browse files
committed
Add an MCP start_httptoolkit command
1 parent 4a8ddd1 commit fac881b

File tree

3 files changed

+71
-15
lines changed

3 files changed

+71
-15
lines changed

src/api/api-model.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@ const INTERCEPTOR_TIMEOUT = 1000;
2525
/**
2626
* Returns the command + args needed to invoke the ctl and mcp tools.
2727
*
28-
* If HTK_TOOLS_PATH is set (by the desktop app), wrapper scripts in that
29-
* directory are used. Otherwise the server's own binary is used with ctl/mcp
30-
* subcommands, stabilized via the oclif 'current' symlink when available.
28+
* If HTK_DESKTOP_RESOURCES is set (by the desktop app or wrapper scripts),
29+
* wrapper scripts in that directory are used. Otherwise the server's own
30+
* binary is used with ctl/mcp subcommands, stabilized via the oclif
31+
* 'current' symlink when available.
3132
*/
3233
function getToolPaths(): { ctl: string[]; mcp: string[] } {
33-
// If we're running via a modern desktop app, we can use the path provided directly:
34-
const toolsPath = process.env.HTK_TOOLS_PATH;
35-
if (toolsPath) {
34+
const resourcesPath = process.env.HTK_DESKTOP_RESOURCES;
35+
if (resourcesPath) {
3636
const ext = process.platform === 'win32' ? '.cmd' : '';
3737
return {
38-
ctl: [path.join(toolsPath, `httptoolkit-ctl${ext}`)],
39-
mcp: [path.join(toolsPath, `httptoolkit-mcp${ext}`)]
38+
ctl: [path.join(resourcesPath, `httptoolkit-ctl${ext}`)],
39+
mcp: [path.join(resourcesPath, `httptoolkit-mcp${ext}`)]
4040
};
4141
}
4242

src/commands/mcp.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as readline from 'readline';
2+
import { execFile } from 'child_process';
23

34
import { Command, flags } from '@oclif/command';
5+
import { delay } from '@httptoolkit/util';
46

57
import { SERVER_VERSION } from '../constants';
68
import { apiRequest } from '../api/bridge-client';
@@ -51,6 +53,61 @@ function operationsToMcpTools(operations: HtkOperation[]): any[] {
5153
}
5254

5355
const POLL_INTERVAL_MS = 5_000;
56+
const LAUNCH_TIMEOUT_MS = 30_000;
57+
const LAUNCH_POLL_MS = 500;
58+
59+
function launchDesktopApp(): boolean {
60+
const exePath = process.env.HTK_DESKTOP_EXE;
61+
if (!exePath) return false;
62+
63+
execFile(exePath, [], () => {});
64+
return true;
65+
}
66+
67+
async function startHttpToolkit(
68+
log: (msg: string) => void,
69+
refreshOperations: () => Promise<void>
70+
): Promise<{ content: any[]; isError?: boolean }> {
71+
// Check if it's already running (maybe it just connected since the last poll)
72+
await refreshOperations();
73+
if ((await apiRequest('GET', '/api/status').catch(() => null))?.ready) {
74+
await refreshOperations();
75+
return {
76+
content: [{ type: 'text', text: 'HTTP Toolkit is already running and ready.' }]
77+
};
78+
}
79+
80+
log('Launching HTTP Toolkit desktop app...');
81+
if (!launchDesktopApp()) {
82+
return {
83+
content: [{ type: 'text', text: 'Cannot launch HTTP Toolkit: desktop app path not detected.' }],
84+
isError: true
85+
};
86+
}
87+
88+
// Wait for the UI to connect and send operations
89+
const deadline = Date.now() + LAUNCH_TIMEOUT_MS;
90+
while (Date.now() < deadline) {
91+
await delay(LAUNCH_POLL_MS);
92+
await refreshOperations();
93+
try {
94+
const status = await apiRequest('GET', '/api/status');
95+
if (status?.ready) {
96+
log('HTTP Toolkit is ready');
97+
return {
98+
content: [{ type: 'text', text: 'HTTP Toolkit has been launched and is ready.' }]
99+
};
100+
}
101+
} catch {
102+
// Server not yet available, keep waiting
103+
}
104+
}
105+
106+
return {
107+
content: [{ type: 'text', text: 'HTTP Toolkit was launched but is not yet ready. It may still be starting up — try again in a moment.' }],
108+
isError: true
109+
};
110+
}
54111

55112
async function runMcpServer(): Promise<void> {
56113
const log = (msg: string) => process.stderr.write(`[MCP] ${msg}\n`);
@@ -68,20 +125,18 @@ async function runMcpServer(): Promise<void> {
68125

69126
function getToolsList(): any[] {
70127
if (cachedOperations.length > 0) return operationsToMcpTools(cachedOperations);
71-
// No running instance — offer a placeholder tool
128+
// No running instance — the only available action is to launch it.
129+
// This tool disappears once HTTP Toolkit is running and real tools become available.
72130
return [{
73131
name: 'start_httptoolkit',
74-
description: 'HTTP Toolkit is not running or the UI is not connected. Please start HTTP Toolkit and try again.',
132+
description: 'HTTP Toolkit is not currently running. Call this to launch it — once started, more tools will become available.',
75133
inputSchema: { type: 'object', properties: {} }
76134
}];
77135
}
78136

79137
async function handleToolCall(name: string, args: Record<string, unknown>): Promise<{ content: any[]; isError?: boolean }> {
80138
if (name === 'start_httptoolkit') {
81-
return {
82-
content: [{ type: 'text', text: 'HTTP Toolkit is not running or the UI is not connected. Please start HTTP Toolkit and try again.' }],
83-
isError: true
84-
};
139+
return startHttpToolkit(log, refreshOperations);
85140
}
86141

87142
// Map MCP tool name back to operation name

src/interceptors/terminal/terminal-env-overrides.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ export function getInheritableCurrentEnv() {
195195
// Set by Oclif:
196196
'HTTPTOOLKIT_SERVER_BINPATH',
197197
// Set by the desktop app:
198-
'HTK_TOOLS_PATH',
198+
'HTK_DESKTOP_EXE',
199+
'HTK_DESKTOP_RESOURCES',
199200
// Set by Electron:
200201
'NO_AT_BRIDGE',
201202
'ORIGINAL_XDG_CURRENT_DESKTOP',

0 commit comments

Comments
 (0)