fix
This commit is contained in:
@@ -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
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
logs
|
||||
.env
|
245
N8N-SETUP-GUIDE.md
Normal file
245
N8N-SETUP-GUIDE.md
Normal 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
141
http-client-example.js
Normal 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
123
http-client-example.py
Normal 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())
|
@@ -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
96
n8n-mcp-config.json
Normal 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
174
n8n-workflow-template.json
Normal 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"
|
||||
}
|
@@ -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
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>
|
112
web-client-example.html
Normal file
112
web-client-example.html
Normal 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>
|
Reference in New Issue
Block a user