Compare commits

...

1 Commits

Author SHA1 Message Date
nasir@endelospay.com
82d922e8bf fix 2025-07-22 22:11:18 +05:00
11 changed files with 2061 additions and 11 deletions

View File

@@ -11,11 +11,12 @@ MCP_SERVER_PORT=3000
MCP_SERVER_HOST=localhost
# MCP Transport Configuration
# Options: "stdio", "http", or "both"
# Options: "stdio", "http", "both", or "all"
# stdio: Uses stdin/stdout for communication (for MCP clients like Claude Desktop)
# http: Uses HTTP server with JSON-RPC 2.0 over HTTP POST (for web clients)
# both: Runs both stdio and HTTP transports simultaneously (recommended)
MCP_TRANSPORT=both
# both: Runs both stdio and HTTP transports simultaneously
# all: Runs all transports (stdio + HTTP + SSE) simultaneously (recommended)
MCP_TRANSPORT=all
# Authentication Configuration - Admin
ADMIN_USERNAME=admin@healthcare.com

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
logs
.env

245
N8N-SETUP-GUIDE.md Normal file
View File

@@ -0,0 +1,245 @@
# 🔧 n8n MCP Client Setup Guide
This guide shows you how to connect n8n to the Laravel Healthcare MCP Server via HTTP.
## 📋 Prerequisites
1. **MCP Server Running**: Ensure your Laravel Healthcare MCP Server is running
2. **n8n Installed**: Have n8n installed and accessible
3. **Network Access**: n8n can reach the MCP server (usually `http://localhost:3000`)
## 🚀 Quick Start
### 1. Start the MCP Server
```bash
cd laravel-healthcare-mcp-server
npm start
```
The server will start in dual mode (stdio + HTTP) and be available at:
- **HTTP Endpoint**: `http://localhost:3000/mcp`
- **n8n Info**: `http://localhost:3000/n8n/info`
### 2. Test Server Connectivity
```bash
# Test server health
curl http://localhost:3000/health
# Get n8n-specific information
curl http://localhost:3000/n8n/info
```
## 🔌 n8n Integration Methods
### Method 1: HTTP Request Node (Recommended)
**Step 1: Add HTTP Request Node**
- Add an "HTTP Request" node to your n8n workflow
- Set the following parameters:
**Basic Configuration:**
- **Method**: `POST`
- **URL**: `http://localhost:3000/mcp`
- **Headers**:
```json
{
"Content-Type": "application/json",
"Accept": "application/json"
}
```
**Step 2: Initialize MCP Connection**
```json
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "n8n",
"version": "1.0.0"
}
},
"id": 1
}
```
**Step 3: List Available Tools**
```json
{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 2
}
```
**Step 4: Execute Healthcare Tools**
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "public_manage_login",
"arguments": {
"email": "provider@example.com",
"password": "your_password"
}
},
"id": 3
}
```
### Method 2: Custom n8n Node (Advanced)
For advanced users, you can create a custom n8n node specifically for MCP:
1. **Create Node Structure**:
```
nodes/
├── McpClient/
│ ├── McpClient.node.ts
│ └── mcp-client.svg
```
2. **Node Configuration**:
- Server URL: `http://localhost:3000/mcp`
- Protocol: JSON-RPC 2.0
- Methods: initialize, tools/list, tools/call, ping
## 📊 Available Healthcare Tools
The MCP server provides 470+ healthcare API tools organized by category:
### Authentication Tools
- `public_manage_login` - Provider login
- `patient_login` - Patient portal login
- `partner_login` - Partner authentication
### Clinical Tools (Provider Auth Required)
- `provider_get_patients` - Get patient list
- `provider_create_patient` - Create new patient
- `provider_update_patient` - Update patient data
- `provider_get_appointments` - Get appointments
### Patient Portal Tools
- `patient_get_profile` - Get patient profile
- `patient_get_appointments` - Get patient appointments
- `patient_book_appointment` - Book new appointment
### Business Tools
- `partner_get_analytics` - Get business analytics
- `affiliate_get_commissions` - Get affiliate data
- `network_get_providers` - Get network providers
## 🔐 Authentication Flow
### 1. Login First
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "public_manage_login",
"arguments": {
"email": "your-email@example.com",
"password": "your-password"
}
},
"id": 1
}
```
### 2. Token Auto-Storage
The server automatically extracts and stores authentication tokens from login responses.
### 3. Use Protected Tools
After login, you can use provider-authenticated tools without additional auth.
## 🧪 Testing Examples
### Example 1: Get Patient List
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "provider_get_patients",
"arguments": {
"page": 1,
"limit": 10
}
},
"id": 4
}
```
### Example 2: Create Patient
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "provider_create_patient",
"arguments": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"dateOfBirth": "1990-01-01",
"phone": "+1234567890"
}
},
"id": 5
}
```
## 🔧 Troubleshooting
### Common Issues
1. **Connection Refused**
- Ensure MCP server is running: `npm start`
- Check server URL: `http://localhost:3000/mcp`
- Verify port 3000 is not blocked
2. **Authentication Errors**
- Login first using `public_manage_login`
- Check credentials are correct
- Verify token storage in server logs
3. **Tool Not Found**
- List available tools: `tools/list`
- Check tool name spelling
- Verify tool exists in current server version
### Debug Commands
```bash
# Check server status
curl http://localhost:3000/health
# Get server info for n8n
curl http://localhost:3000/n8n/info
# List all tools
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
```
## 📚 Additional Resources
- **Server Documentation**: `README.md`
- **API Reference**: `MCP-TOOLS-REFERENCE.md`
- **Example Workflow**: `n8n-workflow-template.json`
- **Configuration**: `n8n-mcp-config.json`
## 🆘 Support
If you encounter issues:
1. Check server logs for errors
2. Verify n8n can reach the server URL
3. Test with curl commands first
4. Check authentication flow

141
http-client-example.js Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env node
/**
* @fileoverview Example HTTP MCP Client for Laravel Healthcare MCP Server
* Demonstrates how to connect to MCP server via HTTP transport
*/
import axios from 'axios';
class HttpMcpClient {
constructor(serverUrl = 'http://localhost:3000/mcp') {
this.serverUrl = serverUrl;
this.requestId = 1;
}
/**
* Send MCP request via HTTP
* @param {string} method - MCP method name
* @param {Object} params - Method parameters
* @returns {Promise<Object>} Response
*/
async request(method, params = {}) {
const request = {
jsonrpc: "2.0",
method: method,
params: params,
id: this.requestId++
};
try {
console.log(`📤 Sending MCP request: ${method}`);
console.log(`📝 Request:`, JSON.stringify(request, null, 2));
const response = await axios.post(this.serverUrl, request, {
headers: {
'Content-Type': 'application/json'
},
timeout: 30000
});
console.log(`📥 Response:`, JSON.stringify(response.data, null, 2));
return response.data;
} catch (error) {
console.error(`❌ Request failed:`, error.message);
if (error.response) {
console.error(`📋 Response data:`, error.response.data);
}
throw error;
}
}
/**
* Initialize MCP connection
*/
async initialize() {
return await this.request('initialize', {
protocolVersion: "2024-11-05",
clientInfo: {
name: "http-mcp-client",
version: "1.0.0"
}
});
}
/**
* List available tools
*/
async listTools() {
return await this.request('tools/list');
}
/**
* Execute a tool
* @param {string} toolName - Tool name
* @param {Object} args - Tool arguments
*/
async callTool(toolName, args = {}) {
return await this.request('tools/call', {
name: toolName,
arguments: args
});
}
/**
* Ping server
*/
async ping() {
return await this.request('ping');
}
}
// Example usage
async function main() {
const client = new HttpMcpClient();
try {
console.log("🚀 Starting HTTP MCP Client...");
console.log("=".repeat(50));
// Test connection
console.log("1⃣ Testing connection...");
await client.initialize();
// List tools
console.log("\n2⃣ Listing available tools...");
const toolsResponse = await client.listTools();
const tools = toolsResponse.result?.tools || [];
console.log(`📊 Found ${tools.length} tools`);
// Show first 5 tools
console.log("\n📋 First 5 tools:");
tools.slice(0, 5).forEach((tool, index) => {
console.log(` ${index + 1}. ${tool.name}: ${tool.description}`);
});
// Test tool execution (login)
console.log("\n3⃣ Testing tool execution (login)...");
await client.callTool('public_manage_login', {
email: 'test@example.com',
password: 'password'
});
// Test ping
console.log("\n4⃣ Testing ping...");
await client.ping();
console.log("\n✅ All tests completed successfully!");
} catch (error) {
console.error("\n❌ Client test failed:", error.message);
process.exit(1);
}
}
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export { HttpMcpClient };

123
http-client-example.py Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Example HTTP MCP Client for Laravel Healthcare MCP Server
Demonstrates how to connect to MCP server via HTTP transport
"""
import json
import requests
from typing import Dict, Any, Optional
class HttpMcpClient:
def __init__(self, server_url: str = "http://localhost:3000/mcp"):
self.server_url = server_url
self.request_id = 1
self.session = requests.Session()
self.session.headers.update({
'Content-Type': 'application/json'
})
def request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Send MCP request via HTTP"""
if params is None:
params = {}
request_data = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": self.request_id
}
self.request_id += 1
print(f"📤 Sending MCP request: {method}")
print(f"📝 Request: {json.dumps(request_data, indent=2)}")
try:
response = self.session.post(
self.server_url,
json=request_data,
timeout=30
)
response.raise_for_status()
result = response.json()
print(f"📥 Response: {json.dumps(result, indent=2)}")
return result
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
raise
def initialize(self) -> Dict[str, Any]:
"""Initialize MCP connection"""
return self.request('initialize', {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "python-http-mcp-client",
"version": "1.0.0"
}
})
def list_tools(self) -> Dict[str, Any]:
"""List available tools"""
return self.request('tools/list')
def call_tool(self, tool_name: str, args: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Execute a tool"""
if args is None:
args = {}
return self.request('tools/call', {
"name": tool_name,
"arguments": args
})
def ping(self) -> Dict[str, Any]:
"""Ping server"""
return self.request('ping')
def main():
"""Example usage"""
client = HttpMcpClient()
try:
print("🚀 Starting HTTP MCP Client...")
print("=" * 50)
# Test connection
print("1⃣ Testing connection...")
client.initialize()
# List tools
print("\n2⃣ Listing available tools...")
tools_response = client.list_tools()
tools = tools_response.get('result', {}).get('tools', [])
print(f"📊 Found {len(tools)} tools")
# Show first 5 tools
print("\n📋 First 5 tools:")
for i, tool in enumerate(tools[:5], 1):
print(f" {i}. {tool['name']}: {tool['description']}")
# Test tool execution (login)
print("\n3⃣ Testing tool execution (login)...")
client.call_tool('public_manage_login', {
'email': 'test@example.com',
'password': 'password'
})
# Test ping
print("\n4⃣ Testing ping...")
client.ping()
print("\n✅ All tests completed successfully!")
except Exception as e:
print(f"\n❌ Client test failed: {e}")
return 1
return 0
if __name__ == "__main__":
exit(main())

View File

@@ -35,7 +35,7 @@ console.log("");
process.env.LARAVEL_API_BASE_URL =
process.env.LARAVEL_API_BASE_URL || "https://example.com";
const transportMode = process.env.MCP_TRANSPORT || "both";
const transportMode = process.env.MCP_TRANSPORT || "all";
console.log(
`🚀 Starting Laravel Healthcare MCP Server (${transportMode} mode)...`
);
@@ -474,6 +474,384 @@ app.post("/mcp", async (req, res) => {
}
});
// n8n MCP Server Information endpoint
app.get("/n8n/info", (req, res) => {
try {
if (!toolGenerator) {
return res.status(500).json({ error: "MCP not initialized" });
}
const tools = toolGenerator.generateAllTools();
const toolsByCategory = {};
tools.forEach((tool) => {
const category = getToolCategory(tool.name);
if (!toolsByCategory[category]) {
toolsByCategory[category] = [];
}
toolsByCategory[category].push({
name: tool.name,
description: tool.description,
authType: toolGenerator.getTool(tool.name)?.authType || "public",
});
});
res.json({
server: {
name: "Laravel Healthcare MCP Server",
version: "1.0.0",
description:
"MCP server providing access to 470+ Laravel Healthcare API endpoints",
protocol: "2024-11-05",
transport: "http",
},
endpoints: {
mcp: "/mcp",
health: "/health",
tools: "/tools",
stats: "/stats",
},
capabilities: {
tools: true,
logging: true,
authentication: true,
},
tools: {
total: tools.length,
categories: toolsByCategory,
},
examples: {
initialize: {
url: "POST /mcp",
body: {
jsonrpc: "2.0",
method: "initialize",
params: {
protocolVersion: "2024-11-05",
clientInfo: { name: "n8n", version: "1.0.0" },
},
id: 1,
},
},
listTools: {
url: "POST /mcp",
body: {
jsonrpc: "2.0",
method: "tools/list",
params: {},
id: 2,
},
},
callTool: {
url: "POST /mcp",
body: {
jsonrpc: "2.0",
method: "tools/call",
params: {
name: "public_manage_login",
arguments: { email: "user@example.com", password: "password" },
},
id: 3,
},
},
},
});
} catch (error) {
console.error("Failed to get n8n info:", error);
res.status(500).json({ error: error.message });
}
});
// Store active SSE connections globally
const sseConnections = new Set();
// SSE endpoint for MCP protocol
app.get("/sse", (req, res) => {
console.log("🔌 New SSE client connecting...");
// Set SSE headers
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Cache-Control, Content-Type",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
});
// Add to active connections
sseConnections.add(res);
// Send initial MCP connection event
const connectionEvent = {
jsonrpc: "2.0",
method: "notification/initialized",
params: {
protocolVersion: "2024-11-05",
serverInfo: {
name: "laravel-healthcare-mcp-server",
version: "1.0.0",
},
capabilities: {
tools: { listChanged: true },
logging: {},
},
message: "MCP SSE transport connected successfully",
timestamp: new Date().toISOString(),
},
};
res.write(`event: mcp-initialized\n`);
res.write(`data: ${JSON.stringify(connectionEvent)}\n\n`);
// Send periodic heartbeat
const heartbeat = setInterval(() => {
if (res.writableEnded) {
clearInterval(heartbeat);
sseConnections.delete(res);
return;
}
try {
const pingEvent = {
jsonrpc: "2.0",
method: "notification/ping",
params: {
timestamp: new Date().toISOString(),
connections: sseConnections.size,
},
};
res.write(`event: ping\n`);
res.write(`data: ${JSON.stringify(pingEvent)}\n\n`);
} catch (error) {
console.log("❌ SSE heartbeat failed, removing connection");
clearInterval(heartbeat);
sseConnections.delete(res);
}
}, 15000); // Every 15 seconds
// Handle client disconnect
req.on("close", () => {
console.log("🔌 SSE client disconnected");
clearInterval(heartbeat);
sseConnections.delete(res);
});
req.on("error", (error) => {
console.log("❌ SSE connection error:", error.message);
clearInterval(heartbeat);
sseConnections.delete(res);
});
console.log(
`✅ SSE client connected (${sseConnections.size} total connections)`
);
});
// SSE MCP message endpoint with proper protocol handling
app.post("/sse/message", async (req, res) => {
try {
const request = req.body;
const startTime = Date.now();
// Validate JSON-RPC format
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32600,
message: "Invalid Request - missing or invalid jsonrpc field",
},
id: request.id || null,
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32600,
message: "Invalid Request - missing method field",
},
id: request.id || null,
});
}
console.log(`📨 SSE MCP request: ${request.method} (ID: ${request.id})`);
// Handle MCP methods with proper error handling
let result;
try {
switch (request.method) {
case "initialize":
const { clientInfo } = request.params || {};
console.log(
`🤝 SSE MCP client connected: ${clientInfo?.name || "Unknown"} v${
clientInfo?.version || "Unknown"
}`
);
result = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {
listChanged: true,
},
logging: {},
},
serverInfo: {
name: "laravel-healthcare-mcp-server",
version: "1.0.0",
},
};
break;
case "tools/list":
const allTools = toolGenerator.generateAllTools();
const tools = allTools.map((tool) => {
const toolDef = toolGenerator.getTool(tool.name);
return {
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
// Add metadata for better client understanding
metadata: {
authType: toolDef?.authType || "public",
category: getToolCategory(tool.name),
endpoint: {
method: toolDef?.endpoint?.method,
path: toolDef?.endpoint?.path,
},
},
};
});
result = { tools };
console.log(`📋 Listed ${tools.length} tools for SSE MCP client`);
break;
case "tools/call":
const { name: toolName, arguments: toolArgs } = request.params;
console.log(`🔧 SSE MCP tool execution: ${toolName}`);
console.log(`📝 Parameters:`, JSON.stringify(toolArgs, null, 2));
const tool = toolGenerator.getTool(toolName);
if (!tool) {
throw new Error(`Tool not found: ${toolName}`);
}
const toolResult = await tool.execute(toolArgs || {});
// Handle login token extraction for provider authentication
if (toolName === "public_manage_login" && toolResult && authManager) {
await handleLoginTokenExtraction(toolResult);
}
result = {
content: [
{
type: "text",
text: JSON.stringify(toolResult, null, 2),
},
],
};
const duration = Date.now() - startTime;
console.log(
`✅ SSE Tool ${toolName} executed successfully (${duration}ms)`
);
break;
case "ping":
result = {
timestamp: new Date().toISOString(),
server: "laravel-healthcare-mcp-server",
transport: "sse",
};
break;
default:
return res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32601,
message: `Method not found: ${request.method}`,
},
id: request.id || null,
});
}
// Return successful JSON-RPC response
const response = {
jsonrpc: "2.0",
result: result,
id: request.id || null,
};
res.json(response);
// Broadcast to SSE connections if needed
if (sseConnections.size > 0) {
const notification = {
jsonrpc: "2.0",
method: "notification/method_executed",
params: {
method: request.method,
success: true,
timestamp: new Date().toISOString(),
duration: Date.now() - startTime,
},
};
sseConnections.forEach((connection) => {
try {
if (!connection.writableEnded) {
connection.write(`event: method-executed\n`);
connection.write(`data: ${JSON.stringify(notification)}\n\n`);
}
} catch (error) {
console.log(
"Failed to broadcast to SSE connection:",
error.message
);
}
});
}
} catch (methodError) {
const duration = Date.now() - startTime;
console.error(
`❌ SSE Tool execution failed (${duration}ms):`,
methodError.message
);
// Return MCP-compliant error response
return res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32603,
message: methodError.message || "Internal error",
data: {
method: request.method,
timestamp: new Date().toISOString(),
duration: duration,
},
},
id: request.id || null,
});
}
} catch (error) {
console.error(`❌ SSE MCP request error:`, error);
res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32700,
message: "Parse error",
},
id: null,
});
}
});
// List all tools
app.get("/tools", (req, res) => {
try {
@@ -746,6 +1124,10 @@ app.use("*", (req, res) => {
error: "Endpoint not found",
availableEndpoints: [
"GET /health",
"POST /mcp",
"GET /sse",
"POST /sse/message",
"GET /n8n/info",
"GET /tools",
"GET /tools/:toolName",
"POST /tools/:toolName/execute",
@@ -872,7 +1254,9 @@ async function startHttpMode() {
*/
async function startBothModes() {
try {
console.log("🔄 Starting MCP server in dual transport mode (stdio + HTTP)...");
console.log(
"🔄 Starting MCP server in dual transport mode (stdio + HTTP)..."
);
const mcpReady = await initializeMCP();
if (!mcpReady) {
@@ -895,14 +1279,18 @@ async function startBothModes() {
// Start HTTP server
const httpPromise = new Promise((resolve, reject) => {
const server = app.listen(port, host, () => {
const serverUrl = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
const serverUrl = `http://${
host === "0.0.0.0" ? "localhost" : host
}:${port}`;
const toolCount = toolGenerator.generateAllTools().length;
console.log("\n" + "=".repeat(70));
console.log("🚀 LARAVEL HEALTHCARE MCP SERVER - DUAL MODE");
console.log("=".repeat(70));
console.log("📡 Transports:");
console.log(` • Stdio: Ready for MCP clients (Claude Desktop, VS Code)`);
console.log(
` • Stdio: Ready for MCP clients (Claude Desktop, VS Code)`
);
console.log(` • HTTP: ${serverUrl}`);
console.log("=".repeat(70));
console.log(`🌐 Host: ${host}`);
@@ -937,7 +1325,7 @@ async function startBothModes() {
resolve(server);
});
server.on('error', reject);
server.on("error", reject);
});
// Wait for both transports to initialize
@@ -955,13 +1343,429 @@ async function startBothModes() {
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
} catch (error) {
console.error("❌ Failed to start dual transport server:", error);
process.exit(1);
}
}
/**
* Start MCP server with all transports (stdio + HTTP + SSE)
*/
async function startAllTransports() {
try {
console.log(
"🔄 Starting MCP server with all transports (stdio + HTTP + SSE)..."
);
const mcpReady = await initializeMCP();
if (!mcpReady) {
console.error("❌ Cannot start server without MCP initialization");
process.exit(1);
}
// Start stdio transport in background
const stdioPromise = (async () => {
try {
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
console.log("✅ Stdio transport connected and ready");
} catch (error) {
console.error("❌ Failed to start stdio transport:", error);
// Don't exit - other transports can still work
}
})();
// Start SSE transport
const ssePromise = (async () => {
try {
// Store active SSE connections
const sseConnections = new Set();
// Add SSE endpoint for MCP
app.get("/sse", (req, res) => {
console.log("🔌 New SSE client connecting...");
// Set SSE headers
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Cache-Control, Content-Type",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
});
// Add to active connections
sseConnections.add(res);
// Send initial MCP connection event
const connectionEvent = {
jsonrpc: "2.0",
method: "notification/initialized",
params: {
protocolVersion: "2024-11-05",
serverInfo: {
name: "laravel-healthcare-mcp-server",
version: "1.0.0",
},
capabilities: {
tools: { listChanged: true },
logging: {},
},
message: "MCP SSE transport connected successfully",
timestamp: new Date().toISOString(),
},
};
res.write(`event: mcp-initialized\n`);
res.write(`data: ${JSON.stringify(connectionEvent)}\n\n`);
// Send periodic heartbeat
const heartbeat = setInterval(() => {
if (res.writableEnded) {
clearInterval(heartbeat);
sseConnections.delete(res);
return;
}
try {
const pingEvent = {
jsonrpc: "2.0",
method: "notification/ping",
params: {
timestamp: new Date().toISOString(),
connections: sseConnections.size,
},
};
res.write(`event: ping\n`);
res.write(`data: ${JSON.stringify(pingEvent)}\n\n`);
} catch (error) {
console.log("❌ SSE heartbeat failed, removing connection");
clearInterval(heartbeat);
sseConnections.delete(res);
}
}, 15000); // Every 15 seconds
// Handle client disconnect
req.on("close", () => {
console.log("🔌 SSE client disconnected");
clearInterval(heartbeat);
sseConnections.delete(res);
});
req.on("error", (error) => {
console.log("❌ SSE connection error:", error.message);
clearInterval(heartbeat);
sseConnections.delete(res);
});
console.log(
`✅ SSE client connected (${sseConnections.size} total connections)`
);
});
// SSE MCP message endpoint with proper protocol handling
app.post("/sse/message", async (req, res) => {
try {
const request = req.body;
const startTime = Date.now();
// Validate JSON-RPC format
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32600,
message: "Invalid Request - missing or invalid jsonrpc field",
},
id: request.id || null,
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32600,
message: "Invalid Request - missing method field",
},
id: request.id || null,
});
}
console.log(
`📨 SSE MCP request: ${request.method} (ID: ${request.id})`
);
// Handle MCP methods with proper error handling
let result;
try {
switch (request.method) {
case "initialize":
const { clientInfo } = request.params || {};
console.log(
`🤝 SSE MCP client connected: ${
clientInfo?.name || "Unknown"
} v${clientInfo?.version || "Unknown"}`
);
result = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {
listChanged: true,
},
logging: {},
},
serverInfo: {
name: "laravel-healthcare-mcp-server",
version: "1.0.0",
},
};
break;
case "tools/list":
const allTools = toolGenerator.generateAllTools();
const tools = allTools.map((tool) => {
const toolDef = toolGenerator.getTool(tool.name);
return {
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
// Add metadata for better client understanding
metadata: {
authType: toolDef?.authType || "public",
category: getToolCategory(tool.name),
endpoint: {
method: toolDef?.endpoint?.method,
path: toolDef?.endpoint?.path,
},
},
};
});
result = { tools };
console.log(
`📋 Listed ${tools.length} tools for SSE MCP client`
);
break;
case "tools/call":
const { name: toolName, arguments: toolArgs } =
request.params;
console.log(`🔧 SSE MCP tool execution: ${toolName}`);
console.log(
`📝 Parameters:`,
JSON.stringify(toolArgs, null, 2)
);
const tool = toolGenerator.getTool(toolName);
if (!tool) {
throw new Error(`Tool not found: ${toolName}`);
}
const toolResult = await tool.execute(toolArgs || {});
// Handle login token extraction for provider authentication
if (
toolName === "public_manage_login" &&
toolResult &&
authManager
) {
await handleLoginTokenExtraction(toolResult);
}
result = {
content: [
{
type: "text",
text: JSON.stringify(toolResult, null, 2),
},
],
};
const duration = Date.now() - startTime;
console.log(
`✅ SSE Tool ${toolName} executed successfully (${duration}ms)`
);
break;
case "ping":
result = {
timestamp: new Date().toISOString(),
server: "laravel-healthcare-mcp-server",
transport: "sse",
};
break;
default:
return res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32601,
message: `Method not found: ${request.method}`,
},
id: request.id || null,
});
}
// Return successful JSON-RPC response
const response = {
jsonrpc: "2.0",
result: result,
id: request.id || null,
};
res.json(response);
// Broadcast to SSE connections if needed
if (sseConnections.size > 0) {
const notification = {
jsonrpc: "2.0",
method: "notification/method_executed",
params: {
method: request.method,
success: true,
timestamp: new Date().toISOString(),
duration: Date.now() - startTime,
},
};
sseConnections.forEach((connection) => {
try {
if (!connection.writableEnded) {
connection.write(`event: method-executed\n`);
connection.write(
`data: ${JSON.stringify(notification)}\n\n`
);
}
} catch (error) {
console.log(
"Failed to broadcast to SSE connection:",
error.message
);
}
});
}
} catch (methodError) {
const duration = Date.now() - startTime;
console.error(
`❌ SSE Tool execution failed (${duration}ms):`,
methodError.message
);
// Return MCP-compliant error response
return res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32603,
message: methodError.message || "Internal error",
data: {
method: request.method,
timestamp: new Date().toISOString(),
duration: duration,
},
},
id: request.id || null,
});
}
} catch (error) {
console.error(`❌ SSE MCP request error:`, error);
res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32700,
message: "Parse error",
},
id: null,
});
}
});
console.log("✅ SSE transport configured and ready");
} catch (error) {
console.error("❌ Failed to configure SSE transport:", error);
}
})();
// Start HTTP server
const httpPromise = new Promise((resolve, reject) => {
const server = app.listen(port, host, () => {
const serverUrl = `http://${
host === "0.0.0.0" ? "localhost" : host
}:${port}`;
const toolCount = toolGenerator.generateAllTools().length;
console.log("\n" + "=".repeat(80));
console.log("🚀 LARAVEL HEALTHCARE MCP SERVER - ALL TRANSPORTS");
console.log("=".repeat(80));
console.log("📡 Available Transports:");
console.log(
` • Stdio: Ready for MCP clients (Claude Desktop, VS Code)`
);
console.log(` • HTTP: ${serverUrl}/mcp (JSON-RPC 2.0)`);
console.log(` • SSE: ${serverUrl}/sse (Server-Sent Events)`);
console.log("=".repeat(80));
console.log(`🌐 Host: ${host}`);
console.log(`🔌 Port: ${port}`);
console.log(`🔗 API URL: ${process.env.LARAVEL_API_BASE_URL}`);
console.log(`📊 Available Tools: ${toolCount}`);
console.log(`🔄 Protocol Version: 2024-11-05`);
console.log("=".repeat(80));
console.log("📋 Connection URLs:");
console.log(` • HTTP MCP: POST ${serverUrl}/mcp`);
console.log(` • SSE Stream: GET ${serverUrl}/sse`);
console.log(` • SSE Messages: POST ${serverUrl}/sse/message`);
console.log(` • Health: GET ${serverUrl}/health`);
console.log(` • n8n Info: GET ${serverUrl}/n8n/info`);
console.log("=".repeat(80));
console.log("🧪 Test Commands:");
console.log(" # Test SSE connection");
console.log(` curl -N ${serverUrl}/sse`);
console.log("");
console.log(" # Test SSE MCP message");
console.log(` curl -X POST ${serverUrl}/sse/message \\`);
console.log(` -H "Content-Type: application/json" \\`);
console.log(` -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'`);
console.log("=".repeat(80));
console.log("📊 Server Status: READY (All Transports)");
console.log(`⏰ Started at: ${new Date().toLocaleString()}`);
console.log("=".repeat(80));
console.log("💡 Press Ctrl+C to stop the server");
console.log("");
resolve(server);
});
server.on("error", reject);
});
// Wait for all transports to initialize
const [, , server] = await Promise.all([
stdioPromise,
ssePromise,
httpPromise,
]);
// Graceful shutdown for all transports
const shutdown = (signal) => {
console.log(`\n🛑 Received ${signal}, shutting down all transports...`);
server.close(() => {
console.log("✅ HTTP server stopped");
console.log("✅ SSE transport stopped");
console.log("✅ Stdio transport stopped");
process.exit(0);
});
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
} catch (error) {
console.error("❌ Failed to start all transports server:", error);
process.exit(1);
}
}
/**
* Main entry point - start server based on transport mode
*/
@@ -975,9 +1779,11 @@ async function main() {
await startHttpMode();
} else if (transportMode === "both") {
await startBothModes();
} else if (transportMode === "all") {
await startAllTransports();
} else {
console.error(`❌ Invalid transport mode: ${transportMode}`);
console.log("💡 Valid modes: 'stdio', 'http', or 'both'");
console.log("💡 Valid modes: 'stdio', 'http', 'both', or 'all'");
console.log("💡 Set MCP_TRANSPORT environment variable");
process.exit(1);
}

96
n8n-mcp-config.json Normal file
View File

@@ -0,0 +1,96 @@
{
"name": "Laravel Healthcare MCP Server",
"description": "MCP server providing access to 470+ Laravel Healthcare API endpoints",
"version": "1.0.0",
"server": {
"url": "http://localhost:3000/mcp",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Accept": "application/json"
}
},
"protocol": {
"version": "2024-11-05",
"transport": "http",
"format": "json-rpc-2.0"
},
"capabilities": {
"tools": true,
"logging": true,
"authentication": true
},
"endpoints": {
"initialize": {
"method": "initialize",
"description": "Initialize MCP connection"
},
"tools/list": {
"method": "tools/list",
"description": "List all available healthcare API tools"
},
"tools/call": {
"method": "tools/call",
"description": "Execute a healthcare API tool"
},
"ping": {
"method": "ping",
"description": "Test server connectivity"
}
},
"authentication": {
"types": [
"public",
"provider",
"patient",
"partner",
"affiliate",
"network"
],
"login_tools": {
"provider": "public_manage_login",
"patient": "patient_login",
"partner": "partner_login"
}
},
"categories": {
"authentication": "Login and authentication tools",
"clinical": "Provider/clinical data management",
"patient-portal": "Patient portal and records",
"business": "Partner and business operations",
"affiliate": "Affiliate management",
"network": "Network operations"
},
"examples": {
"initialize": {
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "n8n",
"version": "1.0.0"
}
},
"id": 1
},
"list_tools": {
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 2
},
"login": {
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "public_manage_login",
"arguments": {
"email": "provider@example.com",
"password": "your_password"
}
},
"id": 3
}
}
}

174
n8n-workflow-template.json Normal file
View File

@@ -0,0 +1,174 @@
{
"name": "Laravel Healthcare MCP Integration",
"nodes": [
{
"parameters": {},
"id": "start",
"name": "Start",
"type": "n8n-nodes-base.start",
"typeVersion": 1,
"position": [240, 300]
},
{
"parameters": {
"url": "http://localhost:3000/mcp",
"options": {
"headers": {
"Content-Type": "application/json"
}
},
"bodyParametersUi": {
"parameter": [
{
"name": "jsonrpc",
"value": "2.0"
},
{
"name": "method",
"value": "initialize"
},
{
"name": "params",
"value": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "n8n",
"version": "1.0.0"
}
}
},
{
"name": "id",
"value": 1
}
]
}
},
"id": "initialize",
"name": "Initialize MCP",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [460, 300]
},
{
"parameters": {
"url": "http://localhost:3000/mcp",
"options": {
"headers": {
"Content-Type": "application/json"
}
},
"bodyParametersUi": {
"parameter": [
{
"name": "jsonrpc",
"value": "2.0"
},
{
"name": "method",
"value": "tools/list"
},
{
"name": "params",
"value": {}
},
{
"name": "id",
"value": 2
}
]
}
},
"id": "list_tools",
"name": "List Tools",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [680, 300]
},
{
"parameters": {
"url": "http://localhost:3000/mcp",
"options": {
"headers": {
"Content-Type": "application/json"
}
},
"bodyParametersUi": {
"parameter": [
{
"name": "jsonrpc",
"value": "2.0"
},
{
"name": "method",
"value": "tools/call"
},
{
"name": "params",
"value": {
"name": "public_manage_login",
"arguments": {
"email": "{{ $json.email }}",
"password": "{{ $json.password }}"
}
}
},
{
"name": "id",
"value": 3
}
]
}
},
"id": "login",
"name": "Login Tool",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [900, 300]
}
],
"connections": {
"Start": {
"main": [
[
{
"node": "Initialize MCP",
"type": "main",
"index": 0
}
]
]
},
"Initialize MCP": {
"main": [
[
{
"node": "List Tools",
"type": "main",
"index": 0
}
]
]
},
"List Tools": {
"main": [
[
{
"node": "Login Tool",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": ["mcp", "healthcare", "laravel"],
"triggerCount": 0,
"updatedAt": "2025-01-22T00:00:00.000Z",
"versionId": "1"
}

View File

@@ -9,10 +9,14 @@
"start:stdio": "node index.js",
"start:http": "node http-tools-server.js",
"start:both": "node http-tools-server.js",
"start:all": "node http-tools-server.js",
"start:n8n": "node http-tools-server.js",
"dev": "node --watch http-tools-server.js",
"dev:stdio": "node --watch index.js",
"dev:http": "node --watch http-tools-server.js",
"dev:both": "node --watch http-tools-server.js"
"dev:both": "node --watch http-tools-server.js",
"dev:all": "node --watch http-tools-server.js",
"dev:n8n": "node --watch http-tools-server.js"
},
"keywords": [
"mcp",

347
sse-client-example.html Normal file
View 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>

112
web-client-example.html Normal file
View File

@@ -0,0 +1,112 @@
<!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 Web Client</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 800px; margin: 0 auto; }
.response { background: #f5f5f5; padding: 10px; margin: 10px 0; border-radius: 5px; }
button { padding: 10px 20px; margin: 5px; background: #007cba; color: white; border: none; border-radius: 5px; cursor: pointer; }
button:hover { background: #005a87; }
input, textarea { width: 100%; padding: 8px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; }
.tool-list { max-height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; }
</style>
</head>
<body>
<div class="container">
<h1>🏥 Laravel Healthcare MCP Web Client</h1>
<div>
<h3>Server Connection</h3>
<input type="text" id="serverUrl" value="http://localhost:3000/mcp" placeholder="MCP Server URL">
<button onclick="testConnection()">Test Connection</button>
<button onclick="listTools()">List Tools</button>
</div>
<div>
<h3>Tool Execution</h3>
<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="4">{"email": "test@example.com", "password": "password"}</textarea>
<button onclick="executeTool()">Execute Tool</button>
</div>
<div>
<h3>Response</h3>
<div id="response" class="response">Ready to connect...</div>
</div>
<div>
<h3>Available Tools</h3>
<div id="toolList" class="tool-list">Click "List Tools" to load...</div>
</div>
</div>
<script>
let requestId = 1;
async function mcpRequest(method, params = {}) {
const serverUrl = document.getElementById('serverUrl').value;
const request = {
jsonrpc: "2.0",
method: method,
params: params,
id: requestId++
};
try {
const response = await fetch(serverUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request)
});
const result = await response.json();
document.getElementById('response').innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
return result;
} catch (error) {
document.getElementById('response').innerHTML = `<div style="color: red;">Error: ${error.message}</div>`;
throw error;
}
}
async function testConnection() {
await mcpRequest('initialize', {
protocolVersion: "2024-11-05",
clientInfo: {
name: "web-client",
version: "1.0.0"
}
});
}
async function listTools() {
const result = await mcpRequest('tools/list');
if (result.result && result.result.tools) {
const toolsHtml = result.result.tools.map(tool =>
`<div><strong>${tool.name}</strong>: ${tool.description}</div>`
).join('');
document.getElementById('toolList').innerHTML = toolsHtml;
}
}
async function executeTool() {
const toolName = document.getElementById('toolName').value;
const toolArgs = document.getElementById('toolArgs').value;
try {
const args = JSON.parse(toolArgs);
await mcpRequest('tools/call', {
name: toolName,
arguments: args
});
} catch (error) {
document.getElementById('response').innerHTML = `<div style="color: red;">Invalid JSON arguments: ${error.message}</div>`;
}
}
</script>
</body>
</html>