@silver886/mcp-proxy 0.2.4 → 0.2.6

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/static/setup.js CHANGED
@@ -92,22 +92,37 @@
92
92
  const row = document.createElement('div');
93
93
  row.className = 'host-row';
94
94
  row.dataset.uid = host.uid;
95
- row.innerHTML = `
96
- <div class="host-head">
97
- <div class="host-id" data-role="title">Host ${esc(host.id || '(unnamed)')}</div>
98
- <button type="button" class="host-remove" data-action="remove">Remove</button>
99
- </div>
100
- <label for="id">Host ID</label>
101
- <input id="id" type="text" data-field="id" value="${esc(host.id)}" placeholder="dev-laptop" required pattern="(?!.*__)[A-Za-z0-9._\\-]+" title="Letters, digits, '.', '_', '-'. Must not contain '__' and must be unique across hosts." />
102
-
103
- <label for="tunnelUrl">Tunnel URL</label>
104
- <input id="tunnelUrl" type="url" data-field="tunnelUrl" value="${esc(host.tunnelUrl)}" placeholder="https://abc-xyz.trycloudflare.com" required />
105
-
106
- <label for="authToken">Auth Token</label>
107
- <input id="authToken" type="text" data-field="authToken" value="${esc(host.authToken)}" placeholder="Paste token from host agent" required />
108
-
109
- <div class="host-status ${host.status.startsWith('Error') ? 'error' : host.status.startsWith('Partial') ? 'partial' : host.status ? 'ok' : ''}">${esc(host.status)}</div>
110
- `;
95
+
96
+ const head = document.createElement('div');
97
+ head.className = 'host-head';
98
+ const title = document.createElement('div');
99
+ title.className = 'host-id';
100
+ title.dataset.role = 'title';
101
+ title.textContent = `Host ${host.id || '(unnamed)'}`;
102
+ head.appendChild(title);
103
+ const removeBtn = document.createElement('button');
104
+ removeBtn.type = 'button';
105
+ removeBtn.className = 'host-remove';
106
+ removeBtn.dataset.action = 'remove';
107
+ removeBtn.textContent = 'Remove';
108
+ head.appendChild(removeBtn);
109
+ row.appendChild(head);
110
+
111
+ appendHostField(row, host.uid, 'id', 'text', host.id, 'Host ID', 'dev-laptop', {
112
+ pattern: '(?!.*__)[A-Za-z0-9._\\-]+',
113
+ title: "Letters, digits, '.', '_', '-'. Must not contain '__' and must be unique across hosts.",
114
+ });
115
+ appendHostField(row, host.uid, 'tunnelUrl', 'url', host.tunnelUrl, 'Tunnel URL', 'https://abc-xyz.trycloudflare.com');
116
+ appendHostField(row, host.uid, 'authToken', 'text', host.authToken, 'Auth Token', 'Paste token from host agent');
117
+
118
+ const status = document.createElement('div');
119
+ const statusClass = host.status.startsWith('Error') ? 'error'
120
+ : host.status.startsWith('Partial') ? 'partial'
121
+ : host.status ? 'ok' : '';
122
+ status.className = statusClass ? `host-status ${statusClass}` : 'host-status';
123
+ status.textContent = host.status;
124
+ row.appendChild(status);
125
+
111
126
  container.appendChild(row);
112
127
  }
113
128
  // Hide remove button when there's only one row.
@@ -119,6 +134,28 @@
119
134
  validateHostIdUniqueness();
120
135
  }
121
136
 
137
+ // Build a label + input pair via DOM properties so user-supplied values
138
+ // can't escape attribute context. Template-literal interpolation with
139
+ // esc() escapes <, >, & only — quotes and apostrophes pass through and
140
+ // would let an upstream-supplied token break out of value="…".
141
+ function appendHostField(row, uid, field, type, value, labelText, placeholder, extras) {
142
+ const id = `${field}-${uid}`;
143
+ const label = document.createElement('label');
144
+ label.htmlFor = id;
145
+ label.textContent = labelText;
146
+ row.appendChild(label);
147
+ const input = document.createElement('input');
148
+ input.id = id;
149
+ input.type = type;
150
+ input.dataset.field = field;
151
+ input.value = value;
152
+ input.placeholder = placeholder;
153
+ input.required = true;
154
+ if (extras?.pattern) input.pattern = extras.pattern;
155
+ if (extras?.title) input.title = extras.title;
156
+ row.appendChild(input);
157
+ }
158
+
122
159
  document.getElementById('hosts-container').addEventListener('input', (e) => {
123
160
  const row = e.target.closest('.host-row');
124
161
  if (!row) return;
@@ -583,15 +620,39 @@
583
620
  const checked = honorPriors && priorSelections.selectedTools
584
621
  ? priorSelections.selectedTools.has(toolKey)
585
622
  : true;
586
- item.innerHTML = `
587
- <div class="tool-check">
588
- <input type="checkbox" id="${esc(cbId)}" data-role="tool" data-host="${esc(hostId)}" data-server="${esc(serverName)}" data-tool="${esc(tool.name)}"${checked ? ' checked' : ''}>
589
- </div>
590
- <label class="tool-label" for="${esc(cbId)}">
591
- <span class="tool-name">${esc(tool.name)}</span>
592
- ${tool.description ? `<span class="tool-desc">${esc(tool.description)}</span>` : ''}
593
- </label>
594
- `;
623
+
624
+ // Build via DOM properties so tool.name (sourced from an upstream
625
+ // MCP server, untrusted) can't escape attribute context. esc() only
626
+ // covers text-node escaping; a quote in tool.name would break out
627
+ // of data-tool="" / id="…" / for="" if interpolated as HTML.
628
+ const checkWrap = document.createElement('div');
629
+ checkWrap.className = 'tool-check';
630
+ const cb = document.createElement('input');
631
+ cb.type = 'checkbox';
632
+ cb.id = cbId;
633
+ cb.dataset.role = 'tool';
634
+ cb.dataset.host = hostId;
635
+ cb.dataset.server = serverName;
636
+ cb.dataset.tool = tool.name;
637
+ cb.checked = checked;
638
+ checkWrap.appendChild(cb);
639
+ item.appendChild(checkWrap);
640
+
641
+ const label = document.createElement('label');
642
+ label.className = 'tool-label';
643
+ label.htmlFor = cbId;
644
+ const nameSpan = document.createElement('span');
645
+ nameSpan.className = 'tool-name';
646
+ nameSpan.textContent = tool.name;
647
+ label.appendChild(nameSpan);
648
+ if (tool.description) {
649
+ const descSpan = document.createElement('span');
650
+ descSpan.className = 'tool-desc';
651
+ descSpan.textContent = tool.description;
652
+ label.appendChild(descSpan);
653
+ }
654
+ item.appendChild(label);
655
+
595
656
  list.appendChild(item);
596
657
  }
597
658
  wrapper.appendChild(list);