mcp-ide 0.1.0 → 0.1.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.
@@ -8,43 +8,63 @@ allowed-tools: mcp__plugin_ide_ide__*
8
8
 
9
9
  Start and manage your development environment with terminals, processes, interactive Ink components, and dashboards.
10
10
 
11
+ ## Tools (8 total)
12
+
13
+ ### Process Management (require mide.yaml)
14
+
15
+ | Tool | Description |
16
+ |------|-------------|
17
+ | `list_processes` | List all processes with status, port, URL, health |
18
+ | `manage_process(name, op)` | Start, stop, or restart a process |
19
+ | `get_logs(name, tail?)` | Get process logs |
20
+
21
+ ### Pane Management
22
+
23
+ | Tool | Description |
24
+ |------|-------------|
25
+ | `create_pane(name, command)` | Create a terminal pane |
26
+ | `create_interaction(schema?, ink_file?)` | Show interactive Ink form/component |
27
+ | `remove_pane(name)` | Remove a pane |
28
+ | `capture_pane(name, lines?)` | Capture terminal output |
29
+
30
+ ### Status
31
+
32
+ | Tool | Description |
33
+ |------|-------------|
34
+ | `set_status(status, message?)` | Update window title/status |
35
+
11
36
  ## Starting the Environment
12
37
 
13
- When the user wants to start their dev environment, use `list_processes` to initialize and show all processes from `mide.yaml`. This will:
14
- 1. Create the tmux session
15
- 2. Start all auto-start processes
16
- 3. Open the terminal window (if configured)
38
+ ```
39
+ list_processes() // Initializes tmux session, shows all processes
40
+ ```
41
+
42
+ ## Managing Processes
17
43
 
18
44
  ```
19
- list_processes() // Initializes environment and shows status
45
+ manage_process(name: "api", op: "start")
46
+ manage_process(name: "api", op: "stop")
47
+ manage_process(name: "api", op: "restart")
20
48
  ```
21
49
 
22
- ## Terminal & Pane Tools
50
+ ## Creating Terminal Panes
23
51
 
24
- ### `create_pane`
25
- Create a terminal pane running any command. Use for dev servers, build commands, or any shell process.
26
52
  ```
27
53
  create_pane(name: "dev-server", command: "npm run dev")
28
- create_pane(name: "tests", command: "npm test --watch", group: "tools")
54
+ create_pane(name: "tests", command: "npm test --watch")
29
55
  ```
30
56
 
31
- ### `remove_pane`
32
- Remove a terminal pane by name.
33
-
34
57
  ## Interactive Ink Components
35
58
 
36
- ### `show_interaction`
37
- Show interactive Ink components for user input, TUI dashboards, or any terminal UI.
38
-
39
- **Schema mode** - Define forms inline (no file needed):
59
+ **Schema mode** - Define forms inline:
40
60
  ```
41
- show_interaction(
61
+ create_interaction(
42
62
  schema: {
43
63
  questions: [
44
64
  { question: "What's your name?", header: "Name", inputType: "text" },
45
65
  { question: "Select role", header: "Role", options: [
46
- { label: "Developer", description: "Write code" },
47
- { label: "Designer", description: "Create designs" }
66
+ { label: "Developer" },
67
+ { label: "Designer" }
48
68
  ]}
49
69
  ]
50
70
  },
@@ -52,103 +72,56 @@ show_interaction(
52
72
  )
53
73
  ```
54
74
 
55
- **File mode** - Run custom Ink components (.tsx/.jsx files):
75
+ **File mode** - Run custom Ink components:
56
76
  ```
57
- show_interaction(ink_file: "color-picker.tsx", title: "Pick a Color")
58
- show_interaction(ink_file: "dashboard.tsx")
77
+ create_interaction(ink_file: "color-picker.tsx", title: "Pick a Color")
59
78
  ```
60
79
 
61
- File resolution order:
62
- 1. Absolute paths used as-is
63
- 2. Project `.mide/interactive/` directory
64
- 3. Global `~/.mide/interactive/` directory
65
-
66
- ### `get_interaction_result`
67
- Get result from a non-blocking interaction (when `block: false`).
68
-
69
- ### `cancel_interaction`
70
- Cancel a pending interaction.
80
+ File resolution: `.mide/interactive/` → `~/.mide/interactive/`
71
81
 
72
- ## Writing Custom Ink Components
82
+ ## Writing Ink Components
73
83
 
74
- Create `.tsx` files in `.mide/interactive/` (project) or `~/.mide/interactive/` (global):
84
+ Create `.tsx` files in `.mide/interactive/`:
75
85
 
76
86
  ```tsx
77
87
  import { Box, Text, useInput, useApp } from 'ink';
78
88
  import { useState } from 'react';
79
89
 
80
- // onComplete is injected globally - call it to return data
81
90
  declare const onComplete: (result: unknown) => void;
82
91
 
83
92
  function MyComponent() {
84
93
  const { exit } = useApp();
85
- const [value, setValue] = useState('');
86
94
 
87
95
  useInput((input, key) => {
88
96
  if (key.return) {
89
- onComplete({ selected: value }); // Return data to Claude
90
- exit(); // Close the component
91
- }
92
- if (key.escape) {
93
- onComplete({ cancelled: true });
97
+ onComplete({ value: "done" });
94
98
  exit();
95
99
  }
96
100
  });
97
101
 
98
- return (
99
- <Box flexDirection="column">
100
- <Text bold>My Interactive Component</Text>
101
- <Text>Press Enter to confirm, Escape to cancel</Text>
102
- </Box>
103
- );
102
+ return <Text>Press Enter to confirm</Text>;
104
103
  }
105
104
 
106
- export default MyComponent; // Must have default export
105
+ export default MyComponent;
107
106
  ```
108
107
 
109
- **Available imports:**
110
- - `ink` - Box, Text, useInput, useApp, useFocus, Newline, Spacer, Static, Transform
111
- - `ink-text-input` - TextInput component
112
- - `ink-select-input` - SelectInput component
113
- - `react` - useState, useEffect, useMemo, useCallback, etc.
114
-
115
- **Key patterns:**
116
- - `useInput((input, key) => {...})` - Handle keyboard input
117
- - `useApp().exit()` - Close the component
118
- - `onComplete(data)` - Return result to Claude (global function)
119
- - `useState` - Manage component state
108
+ **Available imports:** `ink`, `ink-text-input`, `ink-select-input`, `react`
120
109
 
121
- ### `set_status`
122
- Update the terminal window title/status indicator.
110
+ ## Capturing Terminal Output
123
111
 
124
- ## Process Management (requires `mide.yaml`)
125
-
126
- - `list_processes` - Overview of all processes (also initializes environment)
127
- - `get_status` - Detailed status of a single process
128
- - `get_logs` - Get stdout/stderr logs
129
- - `get_url` - Get the preview URL for a process
130
- - `start_process` - Start a stopped process
131
- - `stop_process` - Stop a running process
132
- - `restart_process` - Restart a process
112
+ ```
113
+ capture_pane(name: "dev-server", lines: 50)
114
+ // Returns last 50 lines of terminal output
115
+ ```
133
116
 
134
117
  ## When to Use
135
118
 
136
119
  | User Intent | Tool |
137
120
  |-------------|------|
138
121
  | "start dev environment" | `list_processes` |
139
- | "run a command in terminal" | `create_pane` |
140
- | "ask user a question" | `show_interaction` with schema |
141
- | "show a color picker" | `show_interaction` with ink_file |
142
- | "create an ink component" | Write .tsx file, then `show_interaction` |
143
- | "check if server is running" | `get_status` |
122
+ | "run a command" | `create_pane` |
123
+ | "ask user a question" | `create_interaction` with schema |
124
+ | "show a picker" | `create_interaction` with ink_file |
125
+ | "what's in the terminal" | `capture_pane` |
126
+ | "restart the API" | `manage_process(op: "restart")` |
144
127
  | "show me the logs" | `get_logs` |
145
- | "restart the API" | `restart_process` |
146
-
147
- ## Best Practices
148
-
149
- 1. Use `list_processes` first to initialize the environment
150
- 2. Use `show_interaction` for structured user input instead of asking in chat
151
- 3. For simple questions, use schema mode (no file needed)
152
- 4. For complex UIs, create Ink components in `.mide/interactive/`
153
- 5. Use `create_pane` for long-running processes you want visible
154
- 6. Check process status before suggesting restarts
package/README.md CHANGED
@@ -50,39 +50,36 @@ processes:
50
50
  > "restart the frontend"
51
51
  ```
52
52
 
53
- ## MCP Tools
53
+ ## MCP Tools (8 total)
54
54
 
55
55
  ### Process Management
56
56
 
57
57
  | Tool | Description |
58
58
  |------|-------------|
59
- | `list_processes` | List all processes with status |
60
- | `start_process(name)` | Start a process |
61
- | `stop_process(name)` | Stop a process |
62
- | `restart_process(name)` | Restart a process |
63
- | `get_status(name)` | Get detailed status |
59
+ | `list_processes` | List all processes with status, port, URL, health |
60
+ | `manage_process(name, op)` | Start, stop, or restart a process |
64
61
  | `get_logs(name, tail?)` | Get process logs |
65
- | `get_url(name)` | Get process URL |
66
62
 
67
- ### Terminal Panes
63
+ ### Panes
68
64
 
69
65
  | Tool | Description |
70
66
  |------|-------------|
71
- | `create_pane(name, command, group?)` | Create a terminal pane |
72
- | `remove_pane(name)` | Remove a terminal pane |
73
- | `set_status(message)` | Update window title/status |
67
+ | `create_pane(name, command)` | Create a terminal pane |
68
+ | `create_interaction(schema?, ink_file?)` | Show interactive Ink form/component |
69
+ | `remove_pane(name)` | Remove a pane |
70
+ | `capture_pane(name, lines?)` | Capture terminal output |
74
71
 
75
- ### Interactive Ink Components
72
+ ### Status
76
73
 
77
74
  | Tool | Description |
78
75
  |------|-------------|
79
- | `show_interaction(schema?, ink_file?, title?, block?)` | Show interactive form or Ink component |
80
- | `get_interaction_result(id, block?)` | Get result from non-blocking interaction |
81
- | `cancel_interaction(id)` | Cancel an active interaction |
76
+ | `set_status(status, message?)` | Update window title/status |
77
+
78
+ ## Interactive Ink Components
82
79
 
83
80
  **Schema mode** - Define forms inline:
84
81
  ```typescript
85
- show_interaction({
82
+ create_interaction({
86
83
  schema: {
87
84
  questions: [
88
85
  { question: "What's your name?", header: "Name", inputType: "text" },
@@ -94,14 +91,14 @@ show_interaction({
94
91
  },
95
92
  title: "User Setup"
96
93
  })
94
+ // Returns: { action: "accept", answers: { Name: "John", Role: "Developer" } }
97
95
  ```
98
96
 
99
97
  **File mode** - Run custom Ink components:
100
98
  ```typescript
101
- show_interaction({
99
+ create_interaction({
102
100
  ink_file: "color-picker.tsx", // Relative to .mide/interactive/
103
- title: "Pick a Color",
104
- block: true
101
+ title: "Pick a Color"
105
102
  })
106
103
  // Returns: { action: "accept", result: { color: "blue" } }
107
104
  ```
package/dist/index.js CHANGED
@@ -174,29 +174,18 @@ function formatAge(date) {
174
174
  return `${days}d`;
175
175
  }
176
176
  // Process tool schemas
177
- const StartProcessSchema = z.object({
177
+ const ManageProcessSchema = z.object({
178
178
  name: z.string().describe("Process name from mide.yaml"),
179
- args: z.string().optional().describe("Additional arguments to pass to the command"),
180
- force: z.boolean().optional().describe("Kill any process using the port before starting"),
181
- });
182
- const StopProcessSchema = z.object({
183
- name: z.string().describe("Process name to stop"),
184
- });
185
- const RestartProcessSchema = z.object({
186
- name: z.string().describe("Process name to restart"),
187
- });
188
- const GetStatusSchema = z.object({
189
- name: z.string().describe("Process name"),
179
+ op: z.enum(["start", "stop", "restart"]).describe("Operation to perform"),
180
+ args: z.string().optional().describe("Additional arguments (for start)"),
181
+ force: z.boolean().optional().describe("Kill any process using the port (for start)"),
190
182
  });
191
183
  const GetLogsSchema = z.object({
192
184
  name: z.string().describe("Process name"),
193
185
  stream: z.enum(["stdout", "stderr", "combined"]).optional().describe("Log stream (default: combined)"),
194
186
  tail: z.number().optional().describe("Number of lines to return (default: 100)"),
195
187
  });
196
- const GetUrlSchema = z.object({
197
- name: z.string().describe("Process name"),
198
- });
199
- // Dynamic terminal schemas
188
+ // Pane tool schemas
200
189
  const CreatePaneSchema = z.object({
201
190
  name: z.string().describe("Unique name for the pane"),
202
191
  command: z.string().describe("Command to run in the pane"),
@@ -206,10 +195,9 @@ const CreatePaneSchema = z.object({
206
195
  const RemovePaneSchema = z.object({
207
196
  name: z.string().describe("Name of the pane to remove"),
208
197
  });
209
- // Test blocking tool schema (for validating progress heartbeats)
210
- const TestBlockingSchema = z.object({
211
- duration_seconds: z.number().describe("How long to block in seconds"),
212
- heartbeat_interval_ms: z.number().optional().describe("Progress heartbeat interval in ms (default: 25000)"),
198
+ const CapturePaneSchema = z.object({
199
+ name: z.string().describe("Name of the pane to capture"),
200
+ lines: z.number().optional().describe("Number of lines to capture (default: 100)"),
213
201
  });
214
202
  // Interaction tool schemas
215
203
  const FormQuestionSchema = z.object({
@@ -227,30 +215,28 @@ const FormQuestionSchema = z.object({
227
215
  const FormSchemaSchema = z.object({
228
216
  questions: z.array(FormQuestionSchema).min(1).describe("Questions to ask"),
229
217
  });
230
- const ShowInteractionSchema = z.object({
218
+ const CreateInteractionSchema = z.object({
231
219
  schema: FormSchemaSchema.optional().describe("Form schema (AskUserQuestion-compatible)"),
232
220
  ink_file: z.string().optional().describe("Path to custom Ink component file (.tsx/.jsx)"),
233
221
  title: z.string().optional().describe("Form title"),
234
222
  group: z.string().optional().describe("tmux layout group"),
235
- timeout_ms: z.number().optional().describe("Auto-cancel after N ms"),
236
- block: z.boolean().optional().describe("Block until done (default: true)"),
237
- });
238
- const GetInteractionResultSchema = z.object({
239
- interaction_id: z.string().describe("Interaction ID from non-blocking show_interaction"),
240
- block: z.boolean().optional().describe("Wait for result (default: false)"),
241
- });
242
- const CancelInteractionSchema = z.object({
243
- interaction_id: z.string().describe("Interaction ID to cancel"),
223
+ timeout_ms: z.number().optional().describe("Auto-cancel after N ms (default: blocks indefinitely)"),
244
224
  });
245
225
  const SetStatusSchema = z.object({
246
226
  status: z.enum(["pending", "running", "completed", "failed"]),
247
227
  message: z.string().optional(),
248
228
  });
249
- // Process tools
250
- const PROCESS_TOOLS = [
229
+ // Test blocking tool schema (for internal testing)
230
+ const TestBlockingSchema = z.object({
231
+ duration_seconds: z.number().describe("How long to block in seconds"),
232
+ heartbeat_interval_ms: z.number().optional().describe("Progress heartbeat interval in ms (default: 25000)"),
233
+ });
234
+ // MCP Tools - Simplified API (8 tools)
235
+ const MCP_TOOLS = [
236
+ // Process tools (require mide.yaml)
251
237
  {
252
238
  name: "list_processes",
253
- description: "List all processes defined in mide.yaml with their status",
239
+ description: "List all processes from mide.yaml with full status, port, URL, and health info",
254
240
  inputSchema: {
255
241
  type: "object",
256
242
  properties: {},
@@ -258,49 +244,17 @@ const PROCESS_TOOLS = [
258
244
  },
259
245
  },
260
246
  {
261
- name: "start_process",
262
- description: "Start a process defined in mide.yaml",
247
+ name: "manage_process",
248
+ description: "Start, stop, or restart a process defined in mide.yaml",
263
249
  inputSchema: {
264
250
  type: "object",
265
251
  properties: {
266
252
  name: { type: "string", description: "Process name from mide.yaml" },
267
- args: { type: "string", description: "Additional arguments to pass to the command" },
268
- force: { type: "boolean", description: "Kill any process using the port before starting" },
253
+ op: { type: "string", enum: ["start", "stop", "restart"], description: "Operation to perform" },
254
+ args: { type: "string", description: "Additional arguments (for start)" },
255
+ force: { type: "boolean", description: "Kill any process using the port (for start)" },
269
256
  },
270
- required: ["name"],
271
- },
272
- },
273
- {
274
- name: "stop_process",
275
- description: "Stop a running process",
276
- inputSchema: {
277
- type: "object",
278
- properties: {
279
- name: { type: "string", description: "Process name to stop" },
280
- },
281
- required: ["name"],
282
- },
283
- },
284
- {
285
- name: "restart_process",
286
- description: "Restart a process",
287
- inputSchema: {
288
- type: "object",
289
- properties: {
290
- name: { type: "string", description: "Process name to restart" },
291
- },
292
- required: ["name"],
293
- },
294
- },
295
- {
296
- name: "get_status",
297
- description: "Get detailed status of a process",
298
- inputSchema: {
299
- type: "object",
300
- properties: {
301
- name: { type: "string", description: "Process name" },
302
- },
303
- required: ["name"],
257
+ required: ["name", "op"],
304
258
  },
305
259
  },
306
260
  {
@@ -310,137 +264,91 @@ const PROCESS_TOOLS = [
310
264
  type: "object",
311
265
  properties: {
312
266
  name: { type: "string", description: "Process name" },
313
- stream: { type: "string", enum: ["stdout", "stderr", "combined"], description: "Log stream (default: combined)" },
314
267
  tail: { type: "number", description: "Number of lines to return (default: 100)" },
315
268
  },
316
269
  required: ["name"],
317
270
  },
318
271
  },
319
- {
320
- name: "get_url",
321
- description: "Get the URL for a process (if it has a port)",
322
- inputSchema: {
323
- type: "object",
324
- properties: {
325
- name: { type: "string", description: "Process name" },
326
- },
327
- required: ["name"],
328
- },
329
- },
272
+ // Pane tools
330
273
  {
331
274
  name: "create_pane",
332
- description: "Create a terminal/process pane running a command. Use for dev servers, build commands, or any shell process. In embedded mode (default when inside tmux), creates pane in current session. In standalone mode, creates in separate IDE session.",
275
+ description: "Create a terminal pane running a command",
333
276
  inputSchema: {
334
277
  type: "object",
335
278
  properties: {
336
279
  name: { type: "string", description: "Unique name for the pane" },
337
- command: { type: "string", description: "Command to run (e.g., 'npm run dev', 'python server.py')" },
338
- group: { type: "string", description: "Group to place the pane in (default: 'dynamic', standalone mode only)" },
339
- mode: { type: "string", enum: ["embedded", "standalone"], description: "Tmux mode: embedded (current session) or standalone (separate IDE session)" },
280
+ command: { type: "string", description: "Command to run (e.g., 'npm run dev')" },
281
+ group: { type: "string", description: "Layout group (standalone mode only)" },
340
282
  },
341
283
  required: ["name", "command"],
342
284
  },
343
285
  },
344
286
  {
345
- name: "remove_pane",
346
- description: "Remove a terminal/process pane by name",
347
- inputSchema: {
348
- type: "object",
349
- properties: {
350
- name: { type: "string", description: "Name of the pane to remove" },
351
- },
352
- required: ["name"],
353
- },
354
- },
355
- {
356
- name: "set_status",
357
- description: "Update the IDE terminal window title to show current status",
358
- inputSchema: {
359
- type: "object",
360
- properties: {
361
- status: {
362
- type: "string",
363
- enum: ["pending", "running", "completed", "failed"],
364
- description: "Current status indicator (⏳ pending, 🔄 running, ✅ completed, ❌ failed)",
365
- },
366
- message: {
367
- type: "string",
368
- description: "Custom message (e.g., 'Building project...', '3 tests failed')",
369
- },
370
- },
371
- required: ["status"],
372
- },
373
- },
374
- {
375
- name: "test_blocking",
376
- description: "Test tool that blocks for a specified duration while sending progress heartbeats. Used to validate timeout handling.",
377
- inputSchema: {
378
- type: "object",
379
- properties: {
380
- duration_seconds: { type: "number", description: "How long to block in seconds" },
381
- heartbeat_interval_ms: { type: "number", description: "Progress heartbeat interval in ms (default: 25000)" },
382
- },
383
- required: ["duration_seconds"],
384
- },
385
- },
386
- {
387
- name: "show_interaction",
388
- description: "Show an interactive form or custom Ink component in a tmux pane and collect user input. By default blocks until user completes the form.",
287
+ name: "create_interaction",
288
+ description: "Create an interactive Ink component or form in a pane. Blocks until user completes.",
389
289
  inputSchema: {
390
290
  type: "object",
391
291
  properties: {
392
292
  schema: {
393
293
  type: "object",
394
- description: "Form schema with questions (AskUserQuestion-compatible)",
294
+ description: "Form schema with questions",
395
295
  properties: {
396
296
  questions: {
397
297
  type: "array",
398
298
  items: {
399
299
  type: "object",
400
300
  properties: {
401
- question: { type: "string", description: "The question to ask" },
402
- header: { type: "string", description: "Short label (max 12 chars)" },
403
- options: { type: "array", description: "Selection options (omit for text input)" },
404
- multiSelect: { type: "boolean", description: "Allow multiple selections" },
301
+ question: { type: "string" },
302
+ header: { type: "string" },
303
+ options: { type: "array" },
304
+ multiSelect: { type: "boolean" },
405
305
  inputType: { type: "string", enum: ["text", "textarea", "password"] },
406
- placeholder: { type: "string" },
407
- validation: { type: "string", description: "Regex pattern" },
408
306
  },
409
307
  required: ["question", "header"],
410
308
  },
411
309
  },
412
310
  },
413
- required: ["questions"],
414
311
  },
415
- ink_file: { type: "string", description: "Path to custom Ink component file (.tsx/.jsx) - saves tokens!" },
416
- title: { type: "string", description: "Form title" },
417
- group: { type: "string", description: "tmux layout group" },
312
+ ink_file: { type: "string", description: "Path to Ink component (.tsx) - resolves from .mide/interactive/" },
313
+ title: { type: "string", description: "Title" },
418
314
  timeout_ms: { type: "number", description: "Auto-cancel timeout in ms" },
419
- block: { type: "boolean", description: "Block until done (default: true)" },
420
315
  },
421
316
  },
422
317
  },
423
318
  {
424
- name: "get_interaction_result",
425
- description: "Get the result of a non-blocking interaction",
319
+ name: "remove_pane",
320
+ description: "Remove a terminal or interaction pane by name",
321
+ inputSchema: {
322
+ type: "object",
323
+ properties: {
324
+ name: { type: "string", description: "Name of the pane to remove" },
325
+ },
326
+ required: ["name"],
327
+ },
328
+ },
329
+ {
330
+ name: "capture_pane",
331
+ description: "Capture terminal output from a pane",
426
332
  inputSchema: {
427
333
  type: "object",
428
334
  properties: {
429
- interaction_id: { type: "string", description: "Interaction ID from show_interaction" },
430
- block: { type: "boolean", description: "Wait for result (default: false)" },
335
+ name: { type: "string", description: "Name of the pane" },
336
+ lines: { type: "number", description: "Number of lines to capture (default: 100)" },
431
337
  },
432
- required: ["interaction_id"],
338
+ required: ["name"],
433
339
  },
434
340
  },
341
+ // Status tool
435
342
  {
436
- name: "cancel_interaction",
437
- description: "Cancel an active interaction",
343
+ name: "set_status",
344
+ description: "Update the terminal window title/status indicator",
438
345
  inputSchema: {
439
346
  type: "object",
440
347
  properties: {
441
- interaction_id: { type: "string", description: "Interaction ID to cancel" },
348
+ status: { type: "string", enum: ["pending", "running", "completed", "failed"] },
349
+ message: { type: "string", description: "Custom message" },
442
350
  },
443
- required: ["interaction_id"],
351
+ required: ["status"],
444
352
  },
445
353
  },
446
354
  ];
@@ -555,12 +463,19 @@ async function main() {
555
463
  content: [{ type: "text", text: "No processes defined in mide.yaml" }],
556
464
  };
557
465
  }
466
+ // Return full status including URL
558
467
  const formatted = processes.map((p) => {
468
+ const proc = processManager.getProcess(p.name);
469
+ const state = proc?.getState();
559
470
  const parts = [`${p.name}: ${p.status}`];
560
471
  if (p.port)
561
472
  parts.push(`port=${p.port}`);
473
+ if (state?.url)
474
+ parts.push(`url=${state.url}`);
562
475
  if (p.healthy !== undefined)
563
476
  parts.push(`healthy=${p.healthy}`);
477
+ if (state?.pid)
478
+ parts.push(`pid=${state.pid}`);
564
479
  if (p.error)
565
480
  parts.push(`error=${p.error}`);
566
481
  return parts.join(" | ");
@@ -569,68 +484,32 @@ async function main() {
569
484
  content: [{ type: "text", text: formatted.join("\n") }],
570
485
  };
571
486
  }
572
- case "start_process": {
573
- if (!processManager) {
574
- return formatToolError("No mide.yaml found - process management not available");
575
- }
576
- const parsed = StartProcessSchema.parse(args);
577
- await processManager.startProcess(parsed.name, {
578
- args: parsed.args,
579
- force: parsed.force,
580
- });
581
- return {
582
- content: [{ type: "text", text: `Process "${parsed.name}" started` }],
583
- };
584
- }
585
- case "stop_process": {
586
- if (!processManager) {
587
- return formatToolError("No mide.yaml found - process management not available");
588
- }
589
- const parsed = StopProcessSchema.parse(args);
590
- await processManager.stopProcess(parsed.name);
591
- return {
592
- content: [{ type: "text", text: `Process "${parsed.name}" stopped` }],
593
- };
594
- }
595
- case "restart_process": {
596
- if (!processManager) {
597
- return formatToolError("No mide.yaml found - process management not available");
598
- }
599
- const parsed = RestartProcessSchema.parse(args);
600
- await processManager.restartProcess(parsed.name);
601
- return {
602
- content: [{ type: "text", text: `Process "${parsed.name}" restarted` }],
603
- };
604
- }
605
- case "get_status": {
487
+ case "manage_process": {
606
488
  if (!processManager) {
607
489
  return formatToolError("No mide.yaml found - process management not available");
608
490
  }
609
- const parsed = GetStatusSchema.parse(args);
610
- const process = processManager.getProcess(parsed.name);
611
- if (!process) {
612
- return formatToolError(`Process "${parsed.name}" not found`);
491
+ const parsed = ManageProcessSchema.parse(args);
492
+ switch (parsed.op) {
493
+ case "start":
494
+ await processManager.startProcess(parsed.name, {
495
+ args: parsed.args,
496
+ force: parsed.force,
497
+ });
498
+ return {
499
+ content: [{ type: "text", text: `Process "${parsed.name}" started` }],
500
+ };
501
+ case "stop":
502
+ await processManager.stopProcess(parsed.name);
503
+ return {
504
+ content: [{ type: "text", text: `Process "${parsed.name}" stopped` }],
505
+ };
506
+ case "restart":
507
+ await processManager.restartProcess(parsed.name);
508
+ return {
509
+ content: [{ type: "text", text: `Process "${parsed.name}" restarted` }],
510
+ };
613
511
  }
614
- const state = process.getState();
615
- const lines = [
616
- `Name: ${state.name}`,
617
- `Status: ${state.status}`,
618
- ];
619
- if (state.pid)
620
- lines.push(`PID: ${state.pid}`);
621
- if (state.port)
622
- lines.push(`Port: ${state.port}`);
623
- if (state.url)
624
- lines.push(`URL: ${state.url}`);
625
- if (state.healthy !== undefined)
626
- lines.push(`Healthy: ${state.healthy}`);
627
- if (state.restartCount > 0)
628
- lines.push(`Restart Count: ${state.restartCount}`);
629
- if (state.error)
630
- lines.push(`Error: ${state.error}`);
631
- return {
632
- content: [{ type: "text", text: lines.join("\n") }],
633
- };
512
+ break;
634
513
  }
635
514
  case "get_logs": {
636
515
  if (!processManager) {
@@ -648,21 +527,6 @@ async function main() {
648
527
  content: [{ type: "text", text: content || "(no logs yet)" }],
649
528
  };
650
529
  }
651
- case "get_url": {
652
- if (!processManager) {
653
- return formatToolError("No mide.yaml found - process management not available");
654
- }
655
- const parsed = GetUrlSchema.parse(args);
656
- const url = processManager.getUrl(parsed.name);
657
- if (!url) {
658
- return {
659
- content: [{ type: "text", text: `Process "${parsed.name}" has no URL (no port configured or detected)` }],
660
- };
661
- }
662
- return {
663
- content: [{ type: "text", text: url }],
664
- };
665
- }
666
530
  case "create_pane": {
667
531
  const parsed = CreatePaneSchema.parse(args);
668
532
  // Determine effective mode: tool param > config > auto-detect
@@ -723,6 +587,25 @@ async function main() {
723
587
  content: [{ type: "text", text: `Removed pane "${parsed.name}"` }],
724
588
  };
725
589
  }
590
+ case "capture_pane": {
591
+ const parsed = CapturePaneSchema.parse(args);
592
+ const lines = parsed.lines ?? 100;
593
+ // Try embedded manager first
594
+ if (embeddedTmuxManager?.hasPane(parsed.name)) {
595
+ const content = await embeddedTmuxManager.capturePane(parsed.name, lines);
596
+ return {
597
+ content: [{ type: "text", text: content || "(no output)" }],
598
+ };
599
+ }
600
+ // Try standalone tmux manager
601
+ if (tmuxManager) {
602
+ const content = await tmuxManager.capturePane(parsed.name, lines);
603
+ return {
604
+ content: [{ type: "text", text: content || "(no output)" }],
605
+ };
606
+ }
607
+ return formatToolError("Pane not found or no tmux session active");
608
+ }
726
609
  case "set_status": {
727
610
  const parsed = SetStatusSchema.parse(args);
728
611
  // Use embedded manager if available, otherwise standalone
@@ -739,7 +622,6 @@ async function main() {
739
622
  content: [{ type: "text", text: `Status: ${parsed.status}${parsed.message ? ` - ${parsed.message}` : ""}` }],
740
623
  };
741
624
  }
742
- // test_blocking is handled separately in the request handler (needs server access)
743
625
  default:
744
626
  return formatToolError(`Unknown tool: ${name}`);
745
627
  }
@@ -756,134 +638,15 @@ async function main() {
756
638
  // Handle tool listing
757
639
  server.setRequestHandler(ListToolsRequestSchema, async () => {
758
640
  let tools = [];
759
- // Process tools (always available if config exists)
641
+ // Full mode: all tools available (with mide.yaml)
760
642
  if (processManager) {
761
- tools = [...tools, ...PROCESS_TOOLS];
643
+ tools = [...MCP_TOOLS];
762
644
  }
763
645
  else if (embeddedTmuxManager) {
764
- // Embedded mode: offer terminal tools without full config
765
- tools.push({
766
- name: "create_pane",
767
- description: "Create a terminal/process pane in the current tmux session. Use for dev servers, build commands, or any shell process.",
768
- inputSchema: {
769
- type: "object",
770
- properties: {
771
- name: { type: "string", description: "Unique name for the pane" },
772
- command: { type: "string", description: "Command to run (e.g., 'npm run dev', 'python server.py')" },
773
- },
774
- required: ["name", "command"],
775
- },
776
- }, {
777
- name: "remove_pane",
778
- description: "Remove a terminal/process pane by name",
779
- inputSchema: {
780
- type: "object",
781
- properties: {
782
- name: { type: "string", description: "Name of the pane to remove" },
783
- },
784
- required: ["name"],
785
- },
786
- }, {
787
- name: "set_status",
788
- description: "Update the terminal window title to show current status",
789
- inputSchema: {
790
- type: "object",
791
- properties: {
792
- status: {
793
- type: "string",
794
- enum: ["pending", "running", "completed", "failed"],
795
- description: "Current status indicator",
796
- },
797
- message: { type: "string", description: "Custom message" },
798
- },
799
- required: ["status"],
800
- },
801
- }, {
802
- name: "test_blocking",
803
- description: "Test tool that blocks for a specified duration while sending progress heartbeats.",
804
- inputSchema: {
805
- type: "object",
806
- properties: {
807
- duration_seconds: { type: "number", description: "How long to block in seconds" },
808
- heartbeat_interval_ms: { type: "number", description: "Progress heartbeat interval in ms (default: 25000)" },
809
- },
810
- required: ["duration_seconds"],
811
- },
812
- },
813
- // Interaction tools (ink-runner based)
814
- {
815
- name: "show_interaction",
816
- description: "Show an interactive form or custom Ink component in a tmux pane and collect user input. By default blocks until user completes the form.",
817
- inputSchema: {
818
- type: "object",
819
- properties: {
820
- schema: {
821
- type: "object",
822
- description: "Form schema with questions (AskUserQuestion-compatible)",
823
- properties: {
824
- questions: {
825
- type: "array",
826
- items: {
827
- type: "object",
828
- properties: {
829
- question: { type: "string", description: "The question to ask" },
830
- header: { type: "string", description: "Short label (max 12 chars)" },
831
- options: { type: "array", description: "Selection options (omit for text input)" },
832
- multiSelect: { type: "boolean", description: "Allow multiple selections" },
833
- inputType: { type: "string", enum: ["text", "textarea", "password"] },
834
- placeholder: { type: "string" },
835
- validation: { type: "string", description: "Regex pattern" },
836
- },
837
- required: ["question", "header"],
838
- },
839
- },
840
- },
841
- required: ["questions"],
842
- },
843
- ink_file: { type: "string", description: "Path to custom Ink component file (.tsx/.jsx) - resolves from ~/.mide/interactive/ or .mide/interactive/" },
844
- title: { type: "string", description: "Form title" },
845
- timeout_ms: { type: "number", description: "Auto-cancel timeout in ms" },
846
- block: { type: "boolean", description: "Block until done (default: true)" },
847
- },
848
- },
849
- }, {
850
- name: "get_interaction_result",
851
- description: "Get the result of a non-blocking interaction",
852
- inputSchema: {
853
- type: "object",
854
- properties: {
855
- interaction_id: { type: "string", description: "Interaction ID from show_interaction" },
856
- block: { type: "boolean", description: "Wait for result (default: false)" },
857
- },
858
- required: ["interaction_id"],
859
- },
860
- }, {
861
- name: "cancel_interaction",
862
- description: "Cancel an active interaction",
863
- inputSchema: {
864
- type: "object",
865
- properties: {
866
- interaction_id: { type: "string", description: "Interaction ID to cancel" },
867
- },
868
- required: ["interaction_id"],
869
- },
870
- });
871
- }
872
- else {
873
- // Minimal mode: just test_blocking
874
- tools.push({
875
- name: "test_blocking",
876
- description: "Test tool that blocks for a specified duration while sending progress heartbeats. Used to validate timeout handling.",
877
- inputSchema: {
878
- type: "object",
879
- properties: {
880
- duration_seconds: { type: "number", description: "How long to block in seconds" },
881
- heartbeat_interval_ms: { type: "number", description: "Progress heartbeat interval in ms (default: 25000)" },
882
- },
883
- required: ["duration_seconds"],
884
- },
885
- });
646
+ // Embedded mode: pane tools only (no process management)
647
+ tools = MCP_TOOLS.filter(t => ["create_pane", "create_interaction", "remove_pane", "capture_pane", "set_status"].includes(t.name));
886
648
  }
649
+ // Minimal mode: no tools (tmux not available)
887
650
  return { tools };
888
651
  });
889
652
  // Handle tool calls
@@ -936,12 +699,12 @@ async function main() {
936
699
  }],
937
700
  };
938
701
  }
939
- // Handle show_interaction (needs server access for progress notifications)
940
- if (name === "show_interaction") {
702
+ // Handle create_interaction (needs server access for progress notifications)
703
+ if (name === "create_interaction") {
941
704
  if (!interactionManager) {
942
705
  return formatToolError("Interaction tools not available - tmux required");
943
706
  }
944
- const parsed = ShowInteractionSchema.parse(args);
707
+ const parsed = CreateInteractionSchema.parse(args);
945
708
  if (!parsed.schema && !parsed.ink_file) {
946
709
  return formatToolError("Either schema or ink_file is required");
947
710
  }
@@ -956,21 +719,12 @@ async function main() {
956
719
  if (tmuxManager && config?.settings?.autoAttachTerminal !== false && !isInsideTmux()) {
957
720
  await tmuxManager.openTerminal(config?.settings?.terminalApp, configDir);
958
721
  }
959
- // Non-blocking mode
960
- if (parsed.block === false) {
961
- return {
962
- content: [{
963
- type: "text",
964
- text: JSON.stringify({ interaction_id: interactionId, status: "pending" })
965
- }],
966
- };
967
- }
968
- // Blocking mode with progress heartbeats
722
+ // Blocking mode with progress heartbeats (always blocks now)
969
723
  const progressToken = request.params._meta?.progressToken;
970
724
  const heartbeatIntervalMs = 25000;
971
725
  const startTime = Date.now();
972
726
  let heartbeatCount = 0;
973
- console.error(`[mide] show_interaction blocking: id=${interactionId}, progressToken=${progressToken}`);
727
+ console.error(`[mide] create_interaction: id=${interactionId}, progressToken=${progressToken}`);
974
728
  while (true) {
975
729
  // Wait for result with short timeout
976
730
  const result = await interactionManager.waitForResult(interactionId, heartbeatIntervalMs);
@@ -1025,52 +779,6 @@ async function main() {
1025
779
  }
1026
780
  }
1027
781
  }
1028
- // Handle get_interaction_result
1029
- if (name === "get_interaction_result") {
1030
- if (!interactionManager) {
1031
- return formatToolError("Interaction tools not available - tmux required");
1032
- }
1033
- const parsed = GetInteractionResultSchema.parse(args);
1034
- const state = interactionManager.getState(parsed.interaction_id);
1035
- if (!state) {
1036
- return formatToolError(`Interaction "${parsed.interaction_id}" not found`);
1037
- }
1038
- // If blocking requested and still pending, wait
1039
- if (parsed.block && state.status === "pending") {
1040
- const result = await interactionManager.waitForResult(parsed.interaction_id, 30000);
1041
- if (result) {
1042
- return {
1043
- content: [{
1044
- type: "text",
1045
- text: JSON.stringify({ status: "completed", result })
1046
- }],
1047
- };
1048
- }
1049
- }
1050
- return {
1051
- content: [{
1052
- type: "text",
1053
- text: JSON.stringify({
1054
- status: state.status,
1055
- result: state.result
1056
- })
1057
- }],
1058
- };
1059
- }
1060
- // Handle cancel_interaction
1061
- if (name === "cancel_interaction") {
1062
- if (!interactionManager) {
1063
- return formatToolError("Interaction tools not available - tmux required");
1064
- }
1065
- const parsed = CancelInteractionSchema.parse(args);
1066
- const success = await interactionManager.cancel(parsed.interaction_id);
1067
- return {
1068
- content: [{
1069
- type: "text",
1070
- text: JSON.stringify({ success })
1071
- }],
1072
- };
1073
- }
1074
782
  return await handleToolCall(name, args);
1075
783
  }
1076
784
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-ide",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Interactive Development Environment for Claude Code - manage processes, terminals, and interactive UIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",