348 lines
9.9 KiB
HTML
348 lines
9.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Laravel Healthcare MCP SSE Client</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 20px;
|
|
}
|
|
.container {
|
|
max-width: 1000px;
|
|
margin: 0 auto;
|
|
}
|
|
.section {
|
|
margin: 20px 0;
|
|
padding: 15px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 5px;
|
|
}
|
|
.response {
|
|
background: #f5f5f5;
|
|
padding: 10px;
|
|
margin: 10px 0;
|
|
border-radius: 5px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
.log {
|
|
background: #e8f4f8;
|
|
padding: 10px;
|
|
margin: 10px 0;
|
|
border-radius: 5px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
}
|
|
button {
|
|
padding: 10px 20px;
|
|
margin: 5px;
|
|
background: #007cba;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
}
|
|
button:hover {
|
|
background: #005a87;
|
|
}
|
|
button:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
input,
|
|
textarea {
|
|
width: 100%;
|
|
padding: 8px;
|
|
margin: 5px 0;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
.status {
|
|
padding: 10px;
|
|
margin: 10px 0;
|
|
border-radius: 5px;
|
|
}
|
|
.connected {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
.disconnected {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
.message {
|
|
margin: 5px 0;
|
|
padding: 5px;
|
|
background: #fff;
|
|
border-left: 3px solid #007cba;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🏥 Laravel Healthcare MCP SSE Client</h1>
|
|
|
|
<div class="section">
|
|
<h3>SSE Connection</h3>
|
|
<input
|
|
type="text"
|
|
id="sseUrl"
|
|
value="http://localhost:3000/sse"
|
|
placeholder="SSE Stream URL"
|
|
/>
|
|
<input
|
|
type="text"
|
|
id="messageUrl"
|
|
value="http://localhost:3000/sse/message"
|
|
placeholder="SSE Message URL"
|
|
/>
|
|
<button id="connectBtn" onclick="connectSSE()">Connect SSE</button>
|
|
<button id="disconnectBtn" onclick="disconnectSSE()" disabled>
|
|
Disconnect
|
|
</button>
|
|
<div id="connectionStatus" class="status disconnected">
|
|
Disconnected
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>MCP Commands</h3>
|
|
<button onclick="sendInitialize()">Initialize</button>
|
|
<button onclick="sendListTools()">List Tools</button>
|
|
<button onclick="sendPing()">Ping</button>
|
|
<br /><br />
|
|
<input
|
|
type="text"
|
|
id="toolName"
|
|
placeholder="Tool Name (e.g., public_manage_login)"
|
|
value="public_manage_login"
|
|
/>
|
|
<textarea id="toolArgs" placeholder="Tool Arguments (JSON)" rows="3">
|
|
{"email": "test@example.com", "password": "password"}</textarea
|
|
>
|
|
<button onclick="sendToolCall()">Execute Tool</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>SSE Events Log</h3>
|
|
<button onclick="clearLog()">Clear Log</button>
|
|
<div id="sseLog" class="log">SSE events will appear here...</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>MCP Response</h3>
|
|
<div id="mcpResponse" class="response">
|
|
MCP responses will appear here...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let eventSource = null;
|
|
let requestId = 1;
|
|
|
|
function connectSSE() {
|
|
const sseUrl = document.getElementById("sseUrl").value;
|
|
|
|
if (eventSource) {
|
|
eventSource.close();
|
|
}
|
|
|
|
logSSEEvent("Connecting", `Attempting to connect to ${sseUrl}`);
|
|
eventSource = new EventSource(sseUrl);
|
|
|
|
eventSource.onopen = function (event) {
|
|
updateConnectionStatus(true);
|
|
logSSEEvent("Connection opened", {
|
|
readyState: eventSource.readyState,
|
|
url: sseUrl,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
};
|
|
|
|
eventSource.onmessage = function (event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
logSSEEvent("Default message", data);
|
|
} catch (e) {
|
|
logSSEEvent("Raw message", event.data);
|
|
}
|
|
};
|
|
|
|
// Handle specific MCP events
|
|
eventSource.addEventListener("mcp-initialized", function (event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
logSSEEvent("MCP Initialized", data);
|
|
updateConnectionStatus(true, "MCP Protocol Ready");
|
|
} catch (e) {
|
|
logSSEEvent("MCP Init (raw)", event.data);
|
|
}
|
|
});
|
|
|
|
eventSource.addEventListener("ping", function (event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
logSSEEvent("Heartbeat", data);
|
|
} catch (e) {
|
|
logSSEEvent("Ping (raw)", event.data);
|
|
}
|
|
});
|
|
|
|
eventSource.addEventListener("method-executed", function (event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
logSSEEvent("Method Executed", data);
|
|
} catch (e) {
|
|
logSSEEvent("Method (raw)", event.data);
|
|
}
|
|
});
|
|
|
|
eventSource.onerror = function (event) {
|
|
logSSEEvent("Connection error", {
|
|
readyState: eventSource.readyState,
|
|
error: event,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
// Only update status to disconnected if connection actually failed
|
|
if (eventSource.readyState === EventSource.CLOSED) {
|
|
updateConnectionStatus(false, "Connection closed");
|
|
} else if (eventSource.readyState === EventSource.CONNECTING) {
|
|
updateConnectionStatus(false, "Reconnecting...");
|
|
}
|
|
};
|
|
}
|
|
|
|
function disconnectSSE() {
|
|
if (eventSource) {
|
|
eventSource.close();
|
|
eventSource = null;
|
|
updateConnectionStatus(false);
|
|
logSSEEvent("Connection closed", "Manual disconnect");
|
|
}
|
|
}
|
|
|
|
function updateConnectionStatus(connected, customMessage = null) {
|
|
const statusDiv = document.getElementById("connectionStatus");
|
|
const connectBtn = document.getElementById("connectBtn");
|
|
const disconnectBtn = document.getElementById("disconnectBtn");
|
|
|
|
if (connected) {
|
|
statusDiv.textContent = customMessage || "Connected to SSE stream";
|
|
statusDiv.className = "status connected";
|
|
connectBtn.disabled = true;
|
|
disconnectBtn.disabled = false;
|
|
} else {
|
|
statusDiv.textContent = customMessage || "Disconnected from SSE stream";
|
|
statusDiv.className = "status disconnected";
|
|
connectBtn.disabled = false;
|
|
disconnectBtn.disabled = true;
|
|
}
|
|
}
|
|
|
|
function logSSEEvent(type, data) {
|
|
const logDiv = document.getElementById("sseLog");
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const message = document.createElement("div");
|
|
message.className = "message";
|
|
message.innerHTML = `<strong>[${timestamp}] ${type}:</strong> ${JSON.stringify(
|
|
data,
|
|
null,
|
|
2
|
|
)}`;
|
|
logDiv.appendChild(message);
|
|
logDiv.scrollTop = logDiv.scrollHeight;
|
|
}
|
|
|
|
function clearLog() {
|
|
document.getElementById("sseLog").innerHTML =
|
|
"SSE events will appear here...";
|
|
}
|
|
|
|
async function sendMCPMessage(method, params = {}) {
|
|
const messageUrl = document.getElementById("messageUrl").value;
|
|
const request = {
|
|
jsonrpc: "2.0",
|
|
method: method,
|
|
params: params,
|
|
id: requestId++,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(messageUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
const result = await response.json();
|
|
document.getElementById(
|
|
"mcpResponse"
|
|
).innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
|
|
logSSEEvent("MCP Response", result);
|
|
return result;
|
|
} catch (error) {
|
|
const errorMsg = `Error: ${error.message}`;
|
|
document.getElementById(
|
|
"mcpResponse"
|
|
).innerHTML = `<div style="color: red;">${errorMsg}</div>`;
|
|
logSSEEvent("MCP Error", error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function sendInitialize() {
|
|
await sendMCPMessage("initialize", {
|
|
protocolVersion: "2024-11-05",
|
|
clientInfo: {
|
|
name: "sse-web-client",
|
|
version: "1.0.0",
|
|
},
|
|
});
|
|
}
|
|
|
|
async function sendListTools() {
|
|
await sendMCPMessage("tools/list");
|
|
}
|
|
|
|
async function sendPing() {
|
|
await sendMCPMessage("ping");
|
|
}
|
|
|
|
async function sendToolCall() {
|
|
const toolName = document.getElementById("toolName").value;
|
|
const toolArgs = document.getElementById("toolArgs").value;
|
|
|
|
try {
|
|
const args = JSON.parse(toolArgs);
|
|
await sendMCPMessage("tools/call", {
|
|
name: toolName,
|
|
arguments: args,
|
|
});
|
|
} catch (error) {
|
|
document.getElementById(
|
|
"mcpResponse"
|
|
).innerHTML = `<div style="color: red;">Invalid JSON arguments: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// Auto-connect on page load
|
|
window.onload = function () {
|
|
// Don't auto-connect, let user choose when to connect
|
|
};
|
|
|
|
// Clean up on page unload
|
|
window.onbeforeunload = function () {
|
|
if (eventSource) {
|
|
eventSource.close();
|
|
}
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|