tabby-mcp-server 1.0.3 → 1.0.4

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.
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  **A Comprehensive MCP Server Plugin for Tabby Terminal**
14
14
 
15
- *Connect AI assistants to your terminal with full control capabilities — 18 MCP tools included*
15
+ *Connect AI assistants to your terminal with full control capabilities — 21 MCP tools included*
16
16
 
17
17
  [English](README.md) | [中文](README_CN.md)
18
18
 
@@ -28,9 +28,9 @@
28
28
 
29
29
  ### 🖥️ Terminal Control
30
30
  - Execute commands with output capture
31
+ - Send interactive input (vim, less, top)
31
32
  - Read terminal buffer content
32
- - Abort running commands
33
- - List all terminal sessions
33
+ - Abort/monitor running commands
34
34
 
35
35
  </td>
36
36
  <td width="50%">
@@ -211,14 +211,16 @@ For clients that don't support SSE, use the STDIO bridge:
211
211
 
212
212
  ## 🛠️ Available Tools
213
213
 
214
- ### Terminal Control (4)
214
+ ### Terminal Control (6)
215
215
 
216
216
  | Tool | Description |
217
217
  |------|-------------|
218
218
  | `get_session_list` | List all terminal sessions |
219
219
  | `exec_command` | Execute command with output |
220
+ | `send_input` | Send interactive input (Ctrl+C, etc) |
220
221
  | `get_terminal_buffer` | Read terminal buffer |
221
222
  | `abort_command` | Abort running command |
223
+ | `get_command_status` | Monitor active commands |
222
224
 
223
225
  ### Tab Management (10)
224
226
 
package/dist/index.js CHANGED
@@ -17453,6 +17453,8 @@ const core_1 = __webpack_require__(9430);
17453
17453
  const tabby_core_1 = __webpack_require__(1368);
17454
17454
  const mcpService_1 = __webpack_require__(7554);
17455
17455
  const mcpLogger_service_1 = __webpack_require__(9924);
17456
+ // Version from package.json - update on each release
17457
+ const PLUGIN_VERSION = '1.0.4';
17456
17458
  /**
17457
17459
  * MCP Settings Tab Component
17458
17460
  */
@@ -17461,6 +17463,19 @@ let McpSettingsTabComponent = class McpSettingsTabComponent {
17461
17463
  this.config = config;
17462
17464
  this.mcpService = mcpService;
17463
17465
  this.logger = logger;
17466
+ this.version = PLUGIN_VERSION;
17467
+ this.saveMessage = '';
17468
+ }
17469
+ ngOnInit() {
17470
+ // Ensure timing config exists
17471
+ if (!this.config.store.mcp.timing) {
17472
+ this.config.store.mcp.timing = {
17473
+ pollInterval: 100,
17474
+ initialDelay: 0,
17475
+ sessionStableChecks: 5,
17476
+ sessionPollInterval: 200
17477
+ };
17478
+ }
17464
17479
  }
17465
17480
  get isRunning() {
17466
17481
  return this.mcpService.isServerRunning();
@@ -17484,9 +17499,28 @@ let McpSettingsTabComponent = class McpSettingsTabComponent {
17484
17499
  console.log('MCP Logs:', logs);
17485
17500
  alert('Logs have been printed to the console (Cmd+Option+I)');
17486
17501
  }
17502
+ exportLogsToFile() {
17503
+ const logs = this.logger.exportLogs();
17504
+ const json = JSON.stringify(logs, null, 2);
17505
+ const blob = new Blob([json], { type: 'application/json' });
17506
+ const url = URL.createObjectURL(blob);
17507
+ const a = document.createElement('a');
17508
+ a.href = url;
17509
+ a.download = `mcp-logs-${new Date().toISOString().slice(0, 10)}.json`;
17510
+ document.body.appendChild(a);
17511
+ a.click();
17512
+ document.body.removeChild(a);
17513
+ URL.revokeObjectURL(url);
17514
+ this.logger.info('Logs exported to JSON file');
17515
+ }
17487
17516
  clearLogs() {
17488
17517
  this.logger.clearLogs();
17489
17518
  }
17519
+ saveConfig() {
17520
+ this.config.save();
17521
+ this.saveMessage = '✓ Settings saved';
17522
+ setTimeout(() => { this.saveMessage = ''; }, 2000);
17523
+ }
17490
17524
  getConfigExample() {
17491
17525
  return JSON.stringify({
17492
17526
  mcpServers: {
@@ -17508,7 +17542,10 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17508
17542
  selector: 'mcp-settings-tab',
17509
17543
  template: `
17510
17544
  <div class="mcp-settings">
17511
- <h3>🔌 MCP Server Settings</h3>
17545
+ <div class="header-row">
17546
+ <h3>🔌 MCP Server Settings</h3>
17547
+ <span class="version-badge">v{{ version }}</span>
17548
+ </div>
17512
17549
 
17513
17550
  <div class="form-group">
17514
17551
  <label>Server Status</label>
@@ -17534,14 +17571,14 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17534
17571
  <div class="form-group">
17535
17572
  <label>Port</label>
17536
17573
  <input type="number" class="form-control" [(ngModel)]="config.store.mcp.port"
17537
- placeholder="3001" min="1024" max="65535">
17574
+ placeholder="3001" min="1024" max="65535" (change)="saveConfig()">
17538
17575
  <small class="form-text text-muted">MCP server port (default: 3001)</small>
17539
17576
  </div>
17540
17577
 
17541
17578
  <div class="form-group">
17542
17579
  <div class="checkbox">
17543
17580
  <label>
17544
- <input type="checkbox" [(ngModel)]="config.store.mcp.startOnBoot">
17581
+ <input type="checkbox" [(ngModel)]="config.store.mcp.startOnBoot" (change)="saveConfig()">
17545
17582
  Start server on Tabby launch
17546
17583
  </label>
17547
17584
  </div>
@@ -17554,7 +17591,7 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17554
17591
  <div class="form-group">
17555
17592
  <div class="checkbox">
17556
17593
  <label>
17557
- <input type="checkbox" [(ngModel)]="config.store.mcp.enableLogging">
17594
+ <input type="checkbox" [(ngModel)]="config.store.mcp.enableLogging" (change)="saveConfig()">
17558
17595
  Enable logging
17559
17596
  </label>
17560
17597
  </div>
@@ -17562,7 +17599,7 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17562
17599
 
17563
17600
  <div class="form-group" *ngIf="config.store.mcp.enableLogging">
17564
17601
  <label>Log Level</label>
17565
- <select class="form-control" [(ngModel)]="config.store.mcp.logLevel">
17602
+ <select class="form-control" [(ngModel)]="config.store.mcp.logLevel" (change)="saveConfig()">
17566
17603
  <option value="debug">Debug</option>
17567
17604
  <option value="info">Info</option>
17568
17605
  <option value="warn">Warning</option>
@@ -17572,6 +17609,7 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17572
17609
 
17573
17610
  <div class="form-group" *ngIf="config.store.mcp.enableLogging">
17574
17611
  <button class="btn btn-sm btn-secondary" (click)="viewLogs()">View Logs</button>
17612
+ <button class="btn btn-sm btn-outline-secondary ml-2" (click)="exportLogsToFile()">Export JSON</button>
17575
17613
  <button class="btn btn-sm btn-outline-secondary ml-2" (click)="clearLogs()">Clear Logs</button>
17576
17614
  </div>
17577
17615
 
@@ -17582,7 +17620,7 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17582
17620
  <div class="form-group">
17583
17621
  <div class="checkbox">
17584
17622
  <label>
17585
- <input type="checkbox" [(ngModel)]="config.store.mcp.pairProgrammingMode.enabled">
17623
+ <input type="checkbox" [(ngModel)]="config.store.mcp.pairProgrammingMode.enabled" (change)="saveConfig()">
17586
17624
  Enable Pair Programming Mode
17587
17625
  </label>
17588
17626
  </div>
@@ -17594,7 +17632,7 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17594
17632
  <div class="form-group" *ngIf="config.store.mcp.pairProgrammingMode.enabled">
17595
17633
  <div class="checkbox">
17596
17634
  <label>
17597
- <input type="checkbox" [(ngModel)]="config.store.mcp.pairProgrammingMode.showConfirmationDialog">
17635
+ <input type="checkbox" [(ngModel)]="config.store.mcp.pairProgrammingMode.showConfirmationDialog" (change)="saveConfig()">
17598
17636
  Show confirmation dialog
17599
17637
  </label>
17600
17638
  </div>
@@ -17603,7 +17641,7 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17603
17641
  <div class="form-group" *ngIf="config.store.mcp.pairProgrammingMode.enabled">
17604
17642
  <div class="checkbox">
17605
17643
  <label>
17606
- <input type="checkbox" [(ngModel)]="config.store.mcp.pairProgrammingMode.autoFocusTerminal">
17644
+ <input type="checkbox" [(ngModel)]="config.store.mcp.pairProgrammingMode.autoFocusTerminal" (change)="saveConfig()">
17607
17645
  Auto-focus terminal on command execution
17608
17646
  </label>
17609
17647
  </div>
@@ -17611,6 +17649,41 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17611
17649
 
17612
17650
  <hr />
17613
17651
 
17652
+ <h4>⏱️ Timing Settings</h4>
17653
+ <small class="form-text text-muted mb-2">
17654
+ Advanced timing configuration for command execution and session detection
17655
+ </small>
17656
+
17657
+ <div class="form-group">
17658
+ <label>Poll Interval (ms)</label>
17659
+ <input type="number" class="form-control" [(ngModel)]="config.store.mcp.timing.pollInterval"
17660
+ placeholder="100" min="50" max="1000" (change)="saveConfig()">
17661
+ <small class="form-text text-muted">How often to check for command output (default: 100)</small>
17662
+ </div>
17663
+
17664
+ <div class="form-group">
17665
+ <label>Initial Delay (ms)</label>
17666
+ <input type="number" class="form-control" [(ngModel)]="config.store.mcp.timing.initialDelay"
17667
+ placeholder="0" min="0" max="5000" (change)="saveConfig()">
17668
+ <small class="form-text text-muted">Delay before polling starts (default: 0)</small>
17669
+ </div>
17670
+
17671
+ <div class="form-group">
17672
+ <label>Session Stable Checks</label>
17673
+ <input type="number" class="form-control" [(ngModel)]="config.store.mcp.timing.sessionStableChecks"
17674
+ placeholder="5" min="1" max="20" (change)="saveConfig()">
17675
+ <small class="form-text text-muted">Number of stable checks for session ready detection (default: 5)</small>
17676
+ </div>
17677
+
17678
+ <div class="form-group">
17679
+ <label>Session Poll Interval (ms)</label>
17680
+ <input type="number" class="form-control" [(ngModel)]="config.store.mcp.timing.sessionPollInterval"
17681
+ placeholder="200" min="100" max="2000" (change)="saveConfig()">
17682
+ <small class="form-text text-muted">Interval for session ready polling (default: 200)</small>
17683
+ </div>
17684
+
17685
+ <hr />
17686
+
17614
17687
  <h4>🔗 Connection Info</h4>
17615
17688
  <div class="connection-info">
17616
17689
  <p><strong>SSE Endpoint:</strong> <code>http://localhost:{{ config.store.mcp.port }}/sse</code></p>
@@ -17622,12 +17695,29 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17622
17695
  <button class="btn btn-sm btn-outline-primary" (click)="copyConfig()">Copy Config</button>
17623
17696
  </div>
17624
17697
  </div>
17698
+
17699
+ <div class="save-status mt-3" *ngIf="saveMessage">
17700
+ <span class="text-success">{{ saveMessage }}</span>
17701
+ </div>
17625
17702
  </div>
17626
17703
  `,
17627
17704
  styles: [`
17628
17705
  .mcp-settings {
17629
17706
  padding: 1rem;
17630
17707
  }
17708
+ .header-row {
17709
+ display: flex;
17710
+ align-items: center;
17711
+ justify-content: space-between;
17712
+ }
17713
+ .version-badge {
17714
+ background: rgba(0, 123, 255, 0.2);
17715
+ color: #007bff;
17716
+ padding: 0.25rem 0.5rem;
17717
+ border-radius: 4px;
17718
+ font-size: 0.85em;
17719
+ font-weight: bold;
17720
+ }
17631
17721
  .status-container {
17632
17722
  display: flex;
17633
17723
  align-items: center;
@@ -17662,6 +17752,18 @@ exports.McpSettingsTabComponent = McpSettingsTabComponent = __decorate([
17662
17752
  font-size: 0.85em;
17663
17753
  overflow-x: auto;
17664
17754
  }
17755
+ .ml-2 {
17756
+ margin-left: 0.5rem;
17757
+ }
17758
+ .mb-2 {
17759
+ margin-bottom: 0.5rem;
17760
+ display: block;
17761
+ }
17762
+ .save-status {
17763
+ padding: 0.5rem;
17764
+ background: rgba(40, 167, 69, 0.1);
17765
+ border-radius: 4px;
17766
+ }
17665
17767
  `]
17666
17768
  }),
17667
17769
  __metadata("design:paramtypes", [tabby_core_1.ConfigService,
@@ -24861,45 +24963,73 @@ Special keys: \\x03 (Ctrl+C), \\x04 (Ctrl+D), \\x1b (Escape), \\r (Enter)`,
24861
24963
  }
24862
24964
  /**
24863
24965
  * Wait for command output between markers
24966
+ * Timing is configurable via Settings → MCP → Timing
24864
24967
  */
24865
24968
  async waitForCommandOutput(session, startMarker, endMarker, timeout, isAborted) {
24866
24969
  const startTime = Date.now();
24867
24970
  let lastBufferLength = 0;
24868
- let noChangeCount = 0;
24971
+ let stableCount = 0;
24972
+ // Get timing config (with fallback defaults)
24973
+ const timing = this.config.store.mcp?.timing || {};
24974
+ const pollInterval = timing.pollInterval ?? 100;
24975
+ const initialDelay = timing.initialDelay ?? 0;
24976
+ // Optional initial delay (configurable, default 0)
24977
+ if (initialDelay > 0) {
24978
+ await new Promise(resolve => setTimeout(resolve, initialDelay));
24979
+ }
24869
24980
  while (Date.now() - startTime < timeout) {
24870
24981
  if (isAborted()) {
24871
24982
  return { success: false, output: '', error: 'Command aborted' };
24872
24983
  }
24873
24984
  const buffer = this.getTerminalBufferText(session);
24874
- // Check for markers
24875
- const startIndex = buffer.lastIndexOf(startMarker);
24876
- const endIndex = buffer.lastIndexOf(endMarker);
24877
- if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
24878
- const output = buffer.substring(startIndex + startMarker.length, endIndex).trim();
24879
- // Extract exit code from end marker
24880
- const exitCodeMatch = buffer.substring(endIndex).match(new RegExp(`${endMarker}\\s*(\\d+)`));
24881
- const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : 0;
24882
- return {
24883
- success: exitCode === 0,
24884
- output,
24885
- exitCode
24886
- };
24985
+ // Look for end marker with exit code pattern (complete marker)
24986
+ // End marker format: __MCP_END_<timestamp>__ <exit_code>
24987
+ const endPattern = new RegExp(`${endMarker}\\s+(\\d+)`, 'm');
24988
+ const endMatch = buffer.match(endPattern);
24989
+ if (endMatch) {
24990
+ // Found complete end marker with exit code
24991
+ const endIndex = buffer.indexOf(endMatch[0]);
24992
+ const startIndex = buffer.lastIndexOf(startMarker, endIndex);
24993
+ if (startIndex !== -1 && startIndex < endIndex) {
24994
+ // Extract output between markers
24995
+ let output = buffer.substring(startIndex + startMarker.length, endIndex).trim();
24996
+ // Remove command echo (first line often contains the wrapped command)
24997
+ // Look for the start marker echo line and skip it
24998
+ const lines = output.split('\n');
24999
+ if (lines.length > 0 && lines[0].includes(startMarker.slice(0, 10))) {
25000
+ lines.shift();
25001
+ output = lines.join('\n').trim();
25002
+ }
25003
+ const exitCode = parseInt(endMatch[1], 10);
25004
+ return {
25005
+ success: exitCode === 0,
25006
+ output,
25007
+ exitCode
25008
+ };
25009
+ }
24887
25010
  }
24888
- // Track if buffer is changing (to detect stuck commands)
25011
+ // Track buffer stability (helps detect when output is complete)
24889
25012
  if (buffer.length === lastBufferLength) {
24890
- noChangeCount++;
25013
+ stableCount++;
24891
25014
  }
24892
25015
  else {
24893
- noChangeCount = 0;
25016
+ stableCount = 0;
24894
25017
  lastBufferLength = buffer.length;
24895
25018
  }
24896
- await new Promise(resolve => setTimeout(resolve, 100));
25019
+ // Wait between checks (configurable via Settings → MCP → Timing)
25020
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
24897
25021
  }
24898
25022
  // On timeout, return partial output if start marker found
24899
25023
  const buffer = this.getTerminalBufferText(session);
24900
25024
  const startIndex = buffer.lastIndexOf(startMarker);
24901
25025
  if (startIndex !== -1) {
24902
- const partialOutput = buffer.substring(startIndex + startMarker.length).trim();
25026
+ let partialOutput = buffer.substring(startIndex + startMarker.length).trim();
25027
+ // Clean up command echo
25028
+ const lines = partialOutput.split('\n');
25029
+ if (lines.length > 0 && lines[0].includes('&&')) {
25030
+ lines.shift();
25031
+ partialOutput = lines.join('\n').trim();
25032
+ }
24903
25033
  return {
24904
25034
  success: false,
24905
25035
  output: partialOutput,
@@ -27665,16 +27795,58 @@ Set waitForReady=false (default) for immediate return - use get_session_list to
27665
27795
  if (tab) {
27666
27796
  const tabIndex = this.app.tabs.indexOf(tab);
27667
27797
  if (waitForReady) {
27668
- // Wait for the terminal to be ready
27798
+ // Wait for the terminal session to be fully connected
27799
+ // Timing is configurable via Settings → MCP → Timing
27800
+ const timing = this.config.store.mcp?.timing || {};
27801
+ const sessionPollInterval = timing.sessionPollInterval ?? 200;
27802
+ const sessionStableChecks = timing.sessionStableChecks ?? 5;
27669
27803
  const startTime = Date.now();
27670
27804
  let ready = false;
27805
+ let lastBufferLength = 0;
27806
+ let stableCount = 0;
27671
27807
  while (Date.now() - startTime < timeout) {
27672
- // Check if terminal frontend is available
27673
- if (tab.frontend && tab.sessionReady !== false) {
27808
+ const tabAny = tab;
27809
+ // Check multiple indicators of connection readiness:
27810
+ // 1. frontend exists (terminal rendered)
27811
+ // 2. session exists and is open
27812
+ // 3. sessionReady is explicitly true
27813
+ // 4. buffer has content (indicates activity)
27814
+ const hasSession = tabAny.session !== undefined;
27815
+ const sessionOpen = tabAny.session?.open === true;
27816
+ const frontendReady = tabAny.frontend !== undefined;
27817
+ const sessionReady = tabAny.sessionReady === true;
27818
+ // Also check if terminal buffer has content (indicates connection activity)
27819
+ let bufferLength = 0;
27820
+ try {
27821
+ const xterm = tabAny.frontend?.xterm;
27822
+ if (xterm?.buffer?.active) {
27823
+ bufferLength = xterm.buffer.active.length;
27824
+ }
27825
+ }
27826
+ catch (e) {
27827
+ // Ignore buffer access errors
27828
+ }
27829
+ // Consider ready if:
27830
+ // - Session is open, OR
27831
+ // - Frontend is ready AND sessionReady is true, OR
27832
+ // - Terminal buffer has stabilized with content
27833
+ if (sessionOpen || (frontendReady && sessionReady)) {
27674
27834
  ready = true;
27675
27835
  break;
27676
27836
  }
27677
- await new Promise(resolve => setTimeout(resolve, 200));
27837
+ // Check for buffer stability (activity settled)
27838
+ if (bufferLength > 0 && bufferLength === lastBufferLength) {
27839
+ stableCount++;
27840
+ if (stableCount >= sessionStableChecks) {
27841
+ ready = true;
27842
+ break;
27843
+ }
27844
+ }
27845
+ else {
27846
+ stableCount = 0;
27847
+ lastBufferLength = bufferLength;
27848
+ }
27849
+ await new Promise(resolve => setTimeout(resolve, sessionPollInterval));
27678
27850
  }
27679
27851
  this.logger.info(`Opened profile: ${profile.name} (ready: ${ready})`);
27680
27852
  return {
@@ -59734,6 +59906,13 @@ let McpConfigProvider = class McpConfigProvider extends tabby_core_1.ConfigProvi
59734
59906
  enabled: true,
59735
59907
  showConfirmationDialog: true,
59736
59908
  autoFocusTerminal: true
59909
+ },
59910
+ // Timing configuration (in milliseconds)
59911
+ timing: {
59912
+ pollInterval: 100, // How often to check for command output
59913
+ initialDelay: 0, // Delay before starting to poll (0 = no delay)
59914
+ sessionStableChecks: 5, // Number of stable checks for session ready detection
59915
+ sessionPollInterval: 200 // Interval for session ready polling
59737
59916
  }
59738
59917
  }
59739
59918
  };