fix
This commit is contained in:
347
sse-client-example.html
Normal file
347
sse-client-example.html
Normal file
@@ -0,0 +1,347 @@
|
||||
<!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>
|
Reference in New Issue
Block a user