feat: local MCP harness (tools/resources/prompts) + Harness UI page

Streamable-HTTP MCP server on POST /mcp: device.state, attack.deploy/stop/
status/deauth/capture/export_hc22000, loot.handshakes/enterprise_creds,
recon.aps/isearch/devices, pineap.kick_client/set_filter tools; recon DB +
bundled opencode skills resources; attack playbook prompts. Cookie or Bearer
auth. Harness page shows endpoint, token, curl snippet, capability explorer
and a copy-paste pi.dev prompt. scripts/harness_stdio.py for stdio-only
agents.
This commit is contained in:
2026-08-18 20:00:13 -05:00
parent f31b38d1fa
commit e871e49466
8 changed files with 917 additions and 2 deletions
@@ -34,6 +34,7 @@ const App = (() => {
{ key: 'recon', label: 'Recon', hash: '#/recon', icon: 'recon' },
{ key: 'logging', label: 'Logging', hash: '#/logging', icon: 'logging' },
{ key: 'modules', label: 'Payloads', hash: '#/modules', icon: 'modules' },
{ key: 'harness', label: 'Harness', hash: '#/harness', icon: 'extension' },
{ key: 'settings', label: 'Settings', hash: '#/settings', icon: 'settings' }
];
const railDividers = new Set(['logging']);
@@ -418,7 +419,8 @@ const App = (() => {
'#/settings/wifi': 'settings_wifi',
'#/settings/led': 'settings_led',
'#/settings/advanced': 'settings_advanced',
'#/settings/help': 'settings_help'
'#/settings/help': 'settings_help',
'#/harness': 'harness'
};
return { init, route, toast, showLogin, checkInternet, wsUrl: (p) => WS_BASE + p,
@@ -1388,6 +1388,90 @@ function attackLauncher(kind, opts) {
};
}
views.harness = (root) => {
root.appendChild(h('h1', { class: 'page-title', text: 'Harness' }));
const box = h('div', {});
root.appendChild(box);
const info = h('div', { class: 'pineap-title-card' });
info.appendChild(h('div', { class: 'pineap-card-title' }, 'Local Harness (MCP)'));
const infoBody = h('div', { style: 'font-size:13px;line-height:1.9' });
info.appendChild(infoBody);
box.appendChild(info);
const tok = h('code', { style: 'font-size:12px', text: '…' });
const endpoint = h('code', { style: 'font-size:12px', text: location.origin + '/mcp' });
infoBody.appendChild(h('div', { class: 'row' }, h('div', { style: 'min-width:130px', text: 'Endpoint' }), endpoint));
infoBody.appendChild(h('div', { class: 'row' }, h('div', { style: 'min-width:130px', text: 'Bearer token' }), tok));
infoBody.appendChild(h('div', { class: 'muted', style: 'font-size:12px', text: 'Agents call POST /mcp with JSON-RPC 2.0 (MCP Streamable HTTP). The token is the current session token.' }));
const snippet = h('pre', { style: 'font-size:12px;overflow:auto;background:rgba(127,127,127,.12);padding:10px;border-radius:4px;white-space:pre-wrap' });
const capBox = h('div', { class: 'pineap-title-card' });
capBox.appendChild(h('div', { class: 'pineap-card-title' }, 'Capabilities'));
const capBody = h('div', { style: 'font-size:13px' });
capBox.appendChild(capBody);
box.appendChild(capBox);
const promptBox = h('div', { class: 'pineap-title-card' });
promptBox.appendChild(h('div', { class: 'pineap-card-title' }, 'Prompt for pi.dev'));
const promptArea = h('textarea', { rows: 14, style: 'width:100%;font-family:monospace;font-size:12px;box-sizing:border-box' });
promptBox.appendChild(promptArea);
promptBox.appendChild(h('div', { class: 'row', style: 'margin-top:8px' },
h('div', {}, btn('Copy Prompt', () => {
promptArea.select();
document.execCommand('copy');
App.toast('Copied');
})),
h('div', {}, btn('Copy Token', () => {
navigator.clipboard.writeText(tok.textContent).then(() => App.toast('Token copied'))
.catch(() => App.toast('Copy failed', 'error'));
}))));
box.appendChild(promptBox);
function buildPrompt(token) {
return 'You are driving a WiFi Pineapple Pager (FENRIS firmware) through its local MCP harness.\n' +
'Endpoint: ' + location.origin + '/mcp (Streamable HTTP, POST JSON-RPC 2.0).\n' +
'Authorization: Bearer ' + token + '\n\n' +
'Before acting, read these resources (MCP resources/read) — they are the field-verified operating manual:\n' +
' skills://pineapple-control (device access, radios, UCI truth, pineapd crash-loop fix)\n' +
' skills://wifi-deauth (deauth + handshake methodology, PMKSA failure modes)\n' +
' skills://aircrack-suite (hashcat handoff)\n\n' +
'Rules:\n' +
'1. The DEVICE is the source of truth: read device.state / UCI before and after every change; never assume.\n' +
'2. Only attack the network the operator explicitly authorized (currently <authorized-test-ssid>). No deauth blasts — short targeted bursts.\n' +
'3. After attack.deploy, verify with attack.status (live flag) before proceeding.\n' +
'4. Use the playbook prompts (prompts/get): evil-wpa-attack, evil-enterprise-attack, recon-survey.\n' +
'5. Report verified outcomes only; say what you changed on the device.';
}
function load() {
PagerAPI.get('/api/harness/capabilities').then((r) => {
const d = r.data || {};
capBody.innerHTML = '';
const tools = d.tools || [];
const prompts = d.prompts || [];
capBody.appendChild(h('div', { class: 'muted', style: 'font-size:12px',
text: tools.length + ' tools, ' + prompts.length + ' playbooks, ' +
((d.resources || []).length) + ' resources' }));
const list = h('ul', { style: 'font-size:12px;padding-left:18px' });
tools.forEach((t) => list.appendChild(h('li', { text: t.name + ' — ' + t.description })));
capBody.appendChild(list);
}).catch(() => {});
PagerAPI.get('/api/harness/token').then((r) => {
const t = (r.data || {}).token || '';
tok.textContent = t ? t.slice(0, 12) + '…' : '(none)';
snippet.textContent = 'curl -s -X POST ' + location.origin + '/mcp \\\n' +
' -H "Content-Type: application/json" \\\n' +
' -H "Authorization: Bearer ' + t + '" \\\n' +
' -d \'{"jsonrpc":"2.0","id":1,"method":"tools/list"}\'';
promptArea.value = buildPrompt(t);
infoBody.appendChild(snippet);
}).catch(() => {});
}
load();
return { destroy: () => {} };
};
function deauthPanel() {
const wrap = h('div', { class: 'pineap-title-card' });
wrap.appendChild(h('div', { class: 'pineap-card-title' }, 'Deauth Targeting'));