ArunKr commited on
Commit
098336a
·
verified ·
1 Parent(s): d25be2e

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. mcp.json +21 -0
  2. static/dashboard.html +164 -22
mcp.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": 1,
3
+ "servers": [
4
+ {
5
+ "id": "example-local",
6
+ "name": "Example MCP Server",
7
+ "url": "http://localhost:3000/mcp",
8
+ "auth": {
9
+ "apiKey": "",
10
+ "token": ""
11
+ },
12
+ "headers": {},
13
+ "tools": [
14
+ "search",
15
+ "read_file",
16
+ "write_file"
17
+ ]
18
+ }
19
+ ]
20
+ }
21
+
static/dashboard.html CHANGED
@@ -479,6 +479,31 @@
479
  </div>
480
  </div>
481
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  </div>
483
  </div>
484
 
@@ -786,10 +811,22 @@
786
  }
787
  const id = preferScope === 'agent' ? activeAgentTerminalId : activeTerminalId;
788
  const t = terminals[id];
789
- if (autoShow) switchMode(preferScope === 'agent' ? 'agent' : 'terminal');
 
 
 
 
 
 
 
790
  if (t && t.ws && t.ws.readyState === WebSocket.OPEN) t.ws.send(data);
791
  }
792
 
 
 
 
 
 
793
  function openSettings() {
794
  const overlay = document.getElementById('settings-overlay');
795
  const drawer = document.getElementById('settings-drawer');
@@ -909,6 +946,22 @@
909
  });
910
  }
911
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
912
  // Apply defaults if fields are empty
913
  if (config.default_base_url && !document.getElementById('chat-base-url').value) {
914
  document.getElementById('chat-base-url').value = config.default_base_url;
@@ -1200,6 +1253,7 @@
1200
  function openAutonomousMode() {
1201
  switchMode('chat');
1202
  setChatMode('autonomous');
 
1203
  }
1204
 
1205
  window.addEventListener('resize', () => {
@@ -1667,11 +1721,71 @@
1667
  url,
1668
  apiKey: '',
1669
  token: '',
1670
- headersJson: '{}'
 
1671
  });
1672
  saveMcpServers(servers);
1673
  }
1674
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1675
  function deleteMcpServer(id) {
1676
  if (!confirm('Delete this MCP server?')) return;
1677
  const servers = loadMcpServers().filter(s => s.id !== id);
@@ -1683,8 +1797,8 @@
1683
  saveMcpServers(servers);
1684
  }
1685
 
1686
- async function testMcpServer(id) {
1687
- const statusEl = document.getElementById(`mcp-status-${id}`);
1688
  const server = loadMcpServers().find(s => s.id === id);
1689
  if (!server) return;
1690
  statusEl.textContent = 'Testing...';
@@ -1712,7 +1826,12 @@
1712
  }
1713
 
1714
  function renderMcpServers() {
1715
- const list = document.getElementById('mcp-server-list');
 
 
 
 
 
1716
  if (!list) return;
1717
  const servers = loadMcpServers();
1718
  list.innerHTML = '';
@@ -1723,7 +1842,20 @@
1723
 
1724
  servers.forEach((s) => {
1725
  const card = document.createElement('div');
1726
- card.className = 'bg-gray-800 border border-gray-700 rounded-lg p-4 space-y-3';
 
 
 
 
 
 
 
 
 
 
 
 
 
1727
  card.innerHTML = `
1728
  <div class="flex items-start justify-between gap-3">
1729
  <div class="min-w-0">
@@ -1731,40 +1863,50 @@
1731
  <div class="text-xs text-gray-400 truncate">${sanitizeHtml(s.url)}</div>
1732
  </div>
1733
  <div class="flex gap-2 shrink-0">
1734
- <button class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs" type="button" id="mcp-test-${s.id}">Test</button>
1735
- <button class="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-xs" type="button" id="mcp-del-${s.id}">Delete</button>
1736
  </div>
1737
  </div>
 
 
1738
  <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
1739
  <div>
1740
  <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">API Key</label>
1741
- <input type="password" value="${sanitizeHtml(s.apiKey || '')}" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" id="mcp-apikey-${s.id}">
1742
  </div>
1743
  <div>
1744
  <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Bearer Token</label>
1745
- <input type="password" value="${sanitizeHtml(s.token || '')}" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" id="mcp-token-${s.id}">
1746
  </div>
1747
  </div>
1748
  <div>
1749
  <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Extra Headers (JSON)</label>
1750
- <textarea rows="2" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono" id="mcp-headers-${s.id}">${sanitizeHtml(s.headersJson || '{}')}</textarea>
 
 
 
 
1751
  </div>
1752
  <div class="flex items-center justify-between">
1753
- <div class="text-xs text-gray-300">Status: <span class="text-gray-100" id="mcp-status-${s.id}">—</span></div>
1754
- <button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-xs" type="button" id="mcp-save-${s.id}">Save</button>
1755
  </div>
 
1756
  `;
1757
  list.appendChild(card);
1758
 
1759
- document.getElementById(`mcp-del-${s.id}`).onclick = () => deleteMcpServer(s.id);
1760
- document.getElementById(`mcp-test-${s.id}`).onclick = () => testMcpServer(s.id);
1761
- document.getElementById(`mcp-save-${s.id}`).onclick = () => {
1762
- updateMcpServer(s.id, {
1763
- apiKey: document.getElementById(`mcp-apikey-${s.id}`).value || '',
1764
- token: document.getElementById(`mcp-token-${s.id}`).value || '',
1765
- headersJson: document.getElementById(`mcp-headers-${s.id}`).value || '{}'
1766
- });
1767
- };
 
 
 
1768
  });
1769
  }
1770
 
 
479
  </div>
480
  </div>
481
  </div>
482
+
483
+ <div class="pt-4 border-t border-gray-700">
484
+ <div class="flex items-center justify-between gap-2 mb-2">
485
+ <div class="font-semibold text-gray-200">MCP</div>
486
+ <button onclick="addMcpServerPrompt()"
487
+ class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">+ Add</button>
488
+ </div>
489
+ <div class="text-xs text-gray-400 mb-2">
490
+ Configure MCP servers (URLs + auth). Stored in <code>localStorage</code>. Import/export via <code>mcp.json</code>.
491
+ </div>
492
+ <div class="flex gap-2 mb-3">
493
+ <button onclick="exportMcpConfig()"
494
+ class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Export</button>
495
+ <input id="mcp-import-input" type="file" accept="application/json" class="hidden" />
496
+ <button onclick="document.getElementById('mcp-import-input').click()"
497
+ class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">Import</button>
498
+ </div>
499
+ <div class="flex gap-2 mb-3">
500
+ <input id="mcp-direct-url" type="text" placeholder="Direct MCP URL (https://...)"
501
+ class="flex-grow bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500">
502
+ <button onclick="addMcpServerByUrl()"
503
+ class="bg-gray-700 hover:bg-gray-600 text-white px-2 rounded text-xs">Add</button>
504
+ </div>
505
+ <div id="settings-mcp-server-list" class="space-y-3"></div>
506
+ </div>
507
  </div>
508
  </div>
509
 
 
811
  }
812
  const id = preferScope === 'agent' ? activeAgentTerminalId : activeTerminalId;
813
  const t = terminals[id];
814
+ if (autoShow) {
815
+ if (preferScope === 'agent') {
816
+ openAutonomousMode();
817
+ ensureAgentTerminalVisible();
818
+ } else {
819
+ switchMode('terminal');
820
+ }
821
+ }
822
  if (t && t.ws && t.ws.readyState === WebSocket.OPEN) t.ws.send(data);
823
  }
824
 
825
+ function ensureAgentTerminalVisible() {
826
+ const paneTerminal = document.getElementById('agent-pane-terminal');
827
+ if (paneTerminal && paneTerminal.classList.contains('hidden')) toggleAgentTerminalCollapsed();
828
+ }
829
+
830
  function openSettings() {
831
  const overlay = document.getElementById('settings-overlay');
832
  const drawer = document.getElementById('settings-drawer');
 
946
  });
947
  }
948
 
949
+ // MCP import
950
+ const mcpImport = document.getElementById('mcp-import-input');
951
+ if (mcpImport) {
952
+ mcpImport.addEventListener('change', async (e) => {
953
+ const file = e.target.files?.[0];
954
+ if (!file) return;
955
+ try {
956
+ await importMcpConfigFromFile(file);
957
+ } catch (err) {
958
+ alert(`Failed to import mcp.json: ${err?.message || err}`);
959
+ } finally {
960
+ mcpImport.value = '';
961
+ }
962
+ });
963
+ }
964
+
965
  // Apply defaults if fields are empty
966
  if (config.default_base_url && !document.getElementById('chat-base-url').value) {
967
  document.getElementById('chat-base-url').value = config.default_base_url;
 
1253
  function openAutonomousMode() {
1254
  switchMode('chat');
1255
  setChatMode('autonomous');
1256
+ ensureAgentTerminalVisible();
1257
  }
1258
 
1259
  window.addEventListener('resize', () => {
 
1721
  url,
1722
  apiKey: '',
1723
  token: '',
1724
+ headersJson: '{}',
1725
+ toolsJson: '[]'
1726
  });
1727
  saveMcpServers(servers);
1728
  }
1729
 
1730
+ function addMcpServerByUrl() {
1731
+ const urlEl = document.getElementById('mcp-direct-url');
1732
+ const url = (urlEl?.value || '').trim();
1733
+ if (!url) return;
1734
+ const name = url.replace(/^https?:\/\//, '').replace(/\/+$/, '');
1735
+ const servers = loadMcpServers();
1736
+ servers.push({
1737
+ id: crypto.randomUUID ? crypto.randomUUID() : String(Date.now()),
1738
+ name,
1739
+ url,
1740
+ apiKey: '',
1741
+ token: '',
1742
+ headersJson: '{}',
1743
+ toolsJson: '[]'
1744
+ });
1745
+ saveMcpServers(servers);
1746
+ if (urlEl) urlEl.value = '';
1747
+ }
1748
+
1749
+ function exportMcpConfig() {
1750
+ const servers = loadMcpServers().map((s) => {
1751
+ let headers = {};
1752
+ let tools = [];
1753
+ try { headers = s.headersJson ? JSON.parse(s.headersJson) : {}; } catch { headers = {}; }
1754
+ try { tools = s.toolsJson ? JSON.parse(s.toolsJson) : []; } catch { tools = []; }
1755
+ return {
1756
+ id: s.id,
1757
+ name: s.name,
1758
+ url: s.url,
1759
+ auth: { apiKey: s.apiKey || '', token: s.token || '' },
1760
+ headers,
1761
+ tools
1762
+ };
1763
+ });
1764
+ const payload = { version: 1, servers };
1765
+ const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
1766
+ const a = document.createElement('a');
1767
+ a.href = URL.createObjectURL(blob);
1768
+ a.download = 'mcp.json';
1769
+ a.click();
1770
+ setTimeout(() => URL.revokeObjectURL(a.href), 500);
1771
+ }
1772
+
1773
+ async function importMcpConfigFromFile(file) {
1774
+ const text = await file.text();
1775
+ const parsed = JSON.parse(text);
1776
+ const servers = Array.isArray(parsed?.servers) ? parsed.servers : [];
1777
+ const normalized = servers.map((s) => ({
1778
+ id: s.id || (crypto.randomUUID ? crypto.randomUUID() : String(Date.now())),
1779
+ name: s.name || s.id || 'mcp-server',
1780
+ url: s.url || '',
1781
+ apiKey: s?.auth?.apiKey || '',
1782
+ token: s?.auth?.token || '',
1783
+ headersJson: JSON.stringify(s.headers || {}, null, 2),
1784
+ toolsJson: JSON.stringify(s.tools || [], null, 2)
1785
+ })).filter((s) => s.url);
1786
+ saveMcpServers(normalized);
1787
+ }
1788
+
1789
  function deleteMcpServer(id) {
1790
  if (!confirm('Delete this MCP server?')) return;
1791
  const servers = loadMcpServers().filter(s => s.id !== id);
 
1797
  saveMcpServers(servers);
1798
  }
1799
 
1800
+ async function testMcpServer(id, statusElementId = null) {
1801
+ const statusEl = document.getElementById(statusElementId || `mcp-status-${id}`);
1802
  const server = loadMcpServers().find(s => s.id === id);
1803
  if (!server) return;
1804
  statusEl.textContent = 'Testing...';
 
1826
  }
1827
 
1828
  function renderMcpServers() {
1829
+ renderMcpServersInto('mcp-server-list', { compact: false });
1830
+ renderMcpServersInto('settings-mcp-server-list', { compact: true });
1831
+ }
1832
+
1833
+ function renderMcpServersInto(containerId, { compact } = {}) {
1834
+ const list = document.getElementById(containerId);
1835
  if (!list) return;
1836
  const servers = loadMcpServers();
1837
  list.innerHTML = '';
 
1842
 
1843
  servers.forEach((s) => {
1844
  const card = document.createElement('div');
1845
+ card.className = compact
1846
+ ? 'bg-gray-900/40 border border-gray-700 rounded-lg p-3 space-y-2'
1847
+ : 'bg-gray-800 border border-gray-700 rounded-lg p-4 space-y-3';
1848
+
1849
+ const toolsPreview = (() => {
1850
+ try {
1851
+ const tools = s.toolsJson ? JSON.parse(s.toolsJson) : [];
1852
+ if (!Array.isArray(tools) || tools.length === 0) return '—';
1853
+ return tools.slice(0, 8).join(', ') + (tools.length > 8 ? '…' : '');
1854
+ } catch {
1855
+ return 'Invalid tools JSON';
1856
+ }
1857
+ })();
1858
+
1859
  card.innerHTML = `
1860
  <div class="flex items-start justify-between gap-3">
1861
  <div class="min-w-0">
 
1863
  <div class="text-xs text-gray-400 truncate">${sanitizeHtml(s.url)}</div>
1864
  </div>
1865
  <div class="flex gap-2 shrink-0">
1866
+ <button class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs" type="button" id="${containerId}-mcp-test-${s.id}">Test</button>
1867
+ <button class="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-xs" type="button" id="${containerId}-mcp-del-${s.id}">Delete</button>
1868
  </div>
1869
  </div>
1870
+ <div class="text-xs text-gray-300">Tools: <span class="text-gray-100">${sanitizeHtml(toolsPreview)}</span></div>
1871
+ ${compact ? '' : `
1872
  <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
1873
  <div>
1874
  <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">API Key</label>
1875
+ <input type="password" value="${sanitizeHtml(s.apiKey || '')}" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" id="${containerId}-mcp-apikey-${s.id}">
1876
  </div>
1877
  <div>
1878
  <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Bearer Token</label>
1879
+ <input type="password" value="${sanitizeHtml(s.token || '')}" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" id="${containerId}-mcp-token-${s.id}">
1880
  </div>
1881
  </div>
1882
  <div>
1883
  <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Extra Headers (JSON)</label>
1884
+ <textarea rows="2" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono" id="${containerId}-mcp-headers-${s.id}">${sanitizeHtml(s.headersJson || '{}')}</textarea>
1885
+ </div>
1886
+ <div>
1887
+ <label class="block text-xs font-semibold text-gray-400 mb-1 uppercase">Tools (JSON array)</label>
1888
+ <textarea rows="2" class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono" id="${containerId}-mcp-tools-${s.id}">${sanitizeHtml(s.toolsJson || '[]')}</textarea>
1889
  </div>
1890
  <div class="flex items-center justify-between">
1891
+ <div class="text-xs text-gray-300">Status: <span class="text-gray-100" id="${containerId}-mcp-status-${s.id}">—</span></div>
1892
+ <button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-xs" type="button" id="${containerId}-mcp-save-${s.id}">Save</button>
1893
  </div>
1894
+ `}
1895
  `;
1896
  list.appendChild(card);
1897
 
1898
+ document.getElementById(`${containerId}-mcp-del-${s.id}`).onclick = () => deleteMcpServer(s.id);
1899
+ document.getElementById(`${containerId}-mcp-test-${s.id}`).onclick = () => testMcpServer(s.id, `${containerId}-mcp-status-${s.id}`);
1900
+ if (!compact) {
1901
+ document.getElementById(`${containerId}-mcp-save-${s.id}`).onclick = () => {
1902
+ updateMcpServer(s.id, {
1903
+ apiKey: document.getElementById(`${containerId}-mcp-apikey-${s.id}`).value || '',
1904
+ token: document.getElementById(`${containerId}-mcp-token-${s.id}`).value || '',
1905
+ headersJson: document.getElementById(`${containerId}-mcp-headers-${s.id}`).value || '{}',
1906
+ toolsJson: document.getElementById(`${containerId}-mcp-tools-${s.id}`).value || '[]'
1907
+ });
1908
+ };
1909
+ }
1910
  });
1911
  }
1912