<!DOCTYPE html>
<html>
<head>
<title>Okta MCP SSE Test</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#output {
background: #f5f5f5;
padding: 15px;
border-radius: 8px;
white-space: pre-wrap;
margin: 10px 0;
max-height: 500px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
border: 1px solid #ddd;
}
button { margin: 5px; padding: 8px; }
.container { max-width: 900px; margin: 0 auto; }
.error { color: red; }
.success { color: green; }
.arg-row { margin-bottom: 8px; }
label { display: inline-block; min-width: 160px; }
select, input, textarea { font-size: 14px; }
.tool-group { font-weight: bold; background: #eee; }
</style>
</head>
<body>
<div class="container">
<h1>Okta MCP SSE Test</h1>
<div>
<h3>Connection Status: <span id="status">Disconnected</span></h3>
<button onclick="connect()">Connect SSE</button>
<button onclick="disconnect()">Disconnect</button>
<button onclick="clearOutput()">Clear Output</button>
</div>
<div>
<h3>Test Any Tool</h3>
<form id="toolForm" onsubmit="event.preventDefault(); sendToolRequest();">
<label for="toolSelect">Select Tool:</label>
<select id="toolSelect" onchange="renderArgFields()"></select>
<div id="argFields"></div>
<button type="submit">Send Request</button>
</form>
</div>
<h3>Output:</h3>
<div id="output"></div>
</div>
<script>
// --- Tool Schemas (autogenerated from your codebase) ---
const toolSchemas = {
// Application Manager
'create_application': { group: 'Application Manager', args: { label: {type:'string',required:true}, applicationType: {type:'string',required:true}, signOnMode: {type:'string',required:true}, redirectUris: {type:'string',required:false}, postLogoutRedirectUris: {type:'string',required:false}, grantTypes: {type:'string',required:false}, responseTypes: {type:'string',required:false}, tokenEndpointAuthMethod: {type:'string',required:false}, pkceRequired: {type:'boolean',required:false} } },
'list_all_applications': { group: 'Application Manager', args: { q: {type:'string'}, after: {type:'string'}, useOptimization: {type:'boolean'}, limit: {type:'integer'}, filter: {type:'string'}, expand: {type:'string'}, includeNonDeleted: {type:'boolean'} } },
'assign_app_to_group': { group: 'Application Manager', args: { appId: {type:'string',required:true}, groupId: {type:'string',required:true}, priority: {type:'integer'} } },
'list_app_group_assignments': { group: 'Application Manager', args: { appId: {type:'string',required:true} } },
'remove_app_group_assignment': { group: 'Application Manager', args: { appId: {type:'string',required:true}, groupId: {type:'string',required:true} } },
'list_app_user_assignments': { group: 'Application Manager', args: { appId: {type:'string',required:true} } },
'assign_user_to_app': { group: 'Application Manager', args: { appId: {type:'string',required:true}, userId: {type:'string',required:true}, scope: {type:'string'} } },
'remove_app_user_assignment': { group: 'Application Manager', args: { appId: {type:'string',required:true}, userId: {type:'string',required:true} } },
'get_application': { group: 'Application Manager', args: { appId: {type:'string',required:true} } },
'update_application': { group: 'Application Manager', args: { appId: {type:'string',required:true}, label: {type:'string',required:true}, settings: {type:'string'} } },
// Group Manager
'list_groups': { group: 'Group Manager', args: { q: {type:'string'}, filter: {type:'string'}, after: {type:'string'}, limit: {type:'integer'}, expand: {type:'string'}, search: {type:'string'} } },
'create_group': { group: 'Group Manager', args: { name: {type:'string',required:true}, description: {type:'string'} } },
'get_group': { group: 'Group Manager', args: { groupId: {type:'string',required:true} } },
'update_group': { group: 'Group Manager', args: { groupId: {type:'string',required:true}, name: {type:'string',required:true}, description: {type:'string'} } },
'add_user_to_group': { group: 'Group Manager', args: { groupId: {type:'string',required:true}, userId: {type:'string',required:true} } },
'remove_user_from_group': { group: 'Group Manager', args: { groupId: {type:'string',required:true}, userId: {type:'string',required:true} } },
'list_group_users': { group: 'Group Manager', args: { groupId: {type:'string',required:true} } },
'list_group_apps': { group: 'Group Manager', args: { groupId: {type:'string',required:true}, after: {type:'string'}, limit: {type:'integer'} } },
// User Manager
'list_users': { group: 'User Manager', args: { q: {type:'string'}, after: {type:'string'}, limit: {type:'integer'}, filter: {type:'string'}, search: {type:'string'} } },
'create_user': { group: 'User Manager', args: { profile: {type:'string',required:true}, credentials: {type:'string'}, activate: {type:'boolean'}, nextLogin: {type:'string'} } },
'get_user': { group: 'User Manager', args: { userId: {type:'string',required:true} } },
'update_user': { group: 'User Manager', args: { userId: {type:'string',required:true}, profile: {type:'string'}, credentials: {type:'string'} } },
'activate_user': { group: 'User Manager', args: { userId: {type:'string',required:true}, sendEmail: {type:'boolean'} } },
'deactivate_user': { group: 'User Manager', args: { userId: {type:'string',required:true}, sendEmail: {type:'boolean'} } },
// Policy Manager
'list_policies': { group: 'Policy Manager', args: { type: {type:'string'}, status: {type:'string'}, q: {type:'string'} } },
'list_policy_mappings': { group: 'Policy Manager', args: { policyId: {type:'string',required:true} } },
'assign_policy_to_app': { group: 'Policy Manager', args: { policyId: {type:'string',required:true}, appId: {type:'string',required:true} } }
};
// --- UI Logic ---
let eventSource;
let sessionId;
const MCP_SERVER = 'http://localhost:3001';
function updateStatus(text, isError = false) {
const status = document.getElementById('status');
status.textContent = text;
status.className = isError ? 'error' : 'success';
}
function disconnect() {
if (eventSource) {
eventSource.close();
eventSource = null;
sessionId = null;
updateStatus('Disconnected', true);
appendOutput('Disconnected from SSE server');
}
}
function connect() {
disconnect();
updateStatus('Connecting...');
eventSource = new EventSource(`${MCP_SERVER}/sse`);
eventSource.onopen = () => {
appendOutput('SSE Connection opened');
updateStatus('Connected');
};
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'connection') {
sessionId = data.sessionId;
updateStatus(`Connected (Session: ${sessionId})`);
appendOutput(`Connected with session ID: ${sessionId}`);
} else {
appendOutput(JSON.stringify(data, null, 2));
}
} catch (error) {
appendOutput('Error parsing message: ' + event.data);
}
};
eventSource.onerror = (error) => {
updateStatus('Connection Error', true);
appendOutput('SSE Error: ' + (error.message || 'Unknown error'));
};
}
function clearOutput() {
document.getElementById('output').textContent = '';
}
function appendOutput(text) {
const output = document.getElementById('output');
output.textContent = '';
let formatted = text;
try {
const obj = JSON.parse(text);
// If JSON-RPC tool response, extract and pretty-print the tool result
if (
obj &&
obj.result &&
obj.result.content &&
Array.isArray(obj.result.content) &&
obj.result.content[0] &&
typeof obj.result.content[0].text === 'string'
) {
try {
const inner = JSON.parse(obj.result.content[0].text);
formatted = JSON.stringify(inner, null, 2);
} catch {
formatted = obj.result.content[0].text;
}
} else if (obj && obj.type === 'connection') {
// Don't show connection events
formatted = '';
} else if (obj && obj.result && obj.result.content && Array.isArray(obj.result.content) && obj.result.content[0] && obj.result.content[0].type === 'text') {
// If it's a text type but not JSON, just show the text
formatted = obj.result.content[0].text;
} else {
formatted = JSON.stringify(obj, null, 2);
}
} catch (e) {
// Not JSON, leave as is
}
output.textContent = formatted;
output.scrollTop = output.scrollHeight;
}
function renderArgFields() {
const tool = document.getElementById('toolSelect').value;
const schema = toolSchemas[tool];
const argFields = document.getElementById('argFields');
argFields.innerHTML = '';
if (!schema) return;
for (const [arg, meta] of Object.entries(schema.args)) {
const row = document.createElement('div');
row.className = 'arg-row';
const label = document.createElement('label');
label.textContent = arg + (meta.required ? ' *' : '') + ': ';
label.title = meta.description || '';
row.appendChild(label);
let input;
if (meta.type === 'boolean') {
input = document.createElement('select');
input.innerHTML = '<option value="">(unset)</option><option value="true">true</option><option value="false">false</option>';
} else if (meta.type === 'integer') {
input = document.createElement('input');
input.type = 'number';
} else if (meta.type === 'string' && arg.toLowerCase().includes('json')) {
input = document.createElement('textarea');
input.rows = 2;
input.cols = 40;
} else {
input = document.createElement('input');
input.type = 'text';
}
input.id = 'arg_' + arg;
input.name = arg;
row.appendChild(input);
if (meta.description) {
const desc = document.createElement('span');
desc.style.fontSize = '12px';
desc.style.color = '#666';
desc.textContent = ' ' + meta.description;
row.appendChild(desc);
}
argFields.appendChild(row);
}
}
function sendToolRequest() {
const tool = document.getElementById('toolSelect').value;
const schema = toolSchemas[tool];
if (!tool || !schema) {
appendOutput('Please select a tool.');
return;
}
const args = {};
for (const [arg, meta] of Object.entries(schema.args)) {
const input = document.getElementById('arg_' + arg);
let value = input.value;
if (meta.type === 'boolean') {
if (value === 'true') value = true;
else if (value === 'false') value = false;
else value = undefined;
} else if (meta.type === 'integer') {
value = value ? parseInt(value, 10) : undefined;
} else if (meta.type === 'string' && arg.toLowerCase().includes('json')) {
try { value = value ? JSON.parse(value) : undefined; } catch (e) { appendOutput('Invalid JSON for ' + arg); return; }
}
if (value !== undefined && value !== '') args[arg] = value;
}
if (!sessionId) {
appendOutput('Not connected. Click Connect SSE first.');
return;
}
fetch(`${MCP_SERVER}/messages?sessionId=${sessionId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
params: { name: tool, arguments: args },
id: Date.now()
})
})
.then(r => r.json())
.then(data => appendOutput('Response: ' + JSON.stringify(data, null, 2)))
.catch(e => appendOutput('Error: ' + e.message));
}
// Populate tool dropdown
window.onload = function() {
const select = document.getElementById('toolSelect');
const groups = {};
for (const [tool, meta] of Object.entries(toolSchemas)) {
if (!groups[meta.group]) groups[meta.group] = [];
groups[meta.group].push(tool);
}
for (const group of Object.keys(groups)) {
const optgroup = document.createElement('optgroup');
optgroup.label = group;
for (const tool of groups[group]) {
const option = document.createElement('option');
option.value = tool;
option.textContent = tool;
optgroup.appendChild(option);
}
select.appendChild(optgroup);
}
renderArgFields();
};
</script>
</body>
</html>