/**
* MCP Service - Frontend client for MCP server integration
* Provides typed interfaces for calling MCP tools from React components
*/
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8354'
export interface MCPToolResponse {
success: boolean
data?: any
error?: string
server: string
tool: string
timestamp: string
}
export interface MCPServerStatus {
enabled: boolean
base_url: string
healthy: boolean
}
class MCPService {
private baseUrl: string
constructor(baseUrl: string = API_BASE_URL) {
this.baseUrl = baseUrl
}
/**
* Get status of all MCP servers
*/
async getServers(): Promise<Record<string, MCPServerStatus>> {
try {
const response = await fetch(`${this.baseUrl}/api/mcp/servers`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const data = await response.json()
return data.servers || {}
} catch (error) {
console.error('Failed to get MCP servers:', error)
return {}
}
}
/**
* Check health of a specific MCP server
*/
async checkHealth(serverName: string): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/api/mcp/servers/${serverName}/health`)
if (!response.ok) return false
const data = await response.json()
return data.healthy || false
} catch (error) {
console.error(`Failed to check health for ${serverName}:`, error)
return false
}
}
/**
* List available tools on an MCP server
*/
async listTools(serverName: string): Promise<any[]> {
try {
const response = await fetch(`${this.baseUrl}/api/mcp/servers/${serverName}/tools`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const data = await response.json()
return data.tools || []
} catch (error) {
console.error(`Failed to list tools for ${serverName}:`, error)
return []
}
}
/**
* Call an MCP tool
*/
async callTool(
serverName: string,
toolName: string,
args?: Record<string, any>
): Promise<MCPToolResponse> {
try {
const response = await fetch(
`${this.baseUrl}/api/mcp/servers/${serverName}/tools/${toolName}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(args || {}),
}
)
if (!response.ok) {
const errorText = await response.text()
return {
success: false,
error: `HTTP ${response.status}: ${errorText}`,
server: serverName,
tool: toolName,
timestamp: new Date().toISOString(),
}
}
return await response.json()
} catch (error) {
console.error(`Failed to call tool ${toolName} on ${serverName}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
server: serverName,
tool: toolName,
timestamp: new Date().toISOString(),
}
}
}
// Unity3D specific helpers
async unity3dCallTool(toolName: string, args?: Record<string, any>) {
return this.callTool('unity3d', toolName, args)
}
// VRChat specific helpers
async vrchatCallTool(toolName: string, args?: Record<string, any>) {
return this.callTool('vrchat', toolName, args)
}
// Avatar specific helpers
async avatarCallTool(toolName: string, args?: Record<string, any>) {
return this.callTool('avatar', toolName, args)
}
// OSC specific helpers
async oscCallTool(toolName: string, args?: Record<string, any>) {
return this.callTool('osc', toolName, args)
}
// VRoid specific helpers
async vroidCallTool(toolName: string, args?: Record<string, any>) {
return this.callTool('vroid', toolName, args)
}
// Resonite specific helpers
async resoniteCallTool(toolName: string, args?: Record<string, any>) {
return this.callTool('resonite', toolName, args)
}
// Dreame Robot specific methods
async dreameCommand(robotId: string, action: string, parameters?: Record<string, any>) {
try {
const response = await fetch(`${this.baseUrl}/api/dreame/${robotId}/command`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action, parameters: parameters || {} }),
})
if (!response.ok) {
const errorText = await response.text()
return {
success: false,
error: `HTTP ${response.status}: ${errorText}`,
robot_id: robotId,
timestamp: new Date().toISOString(),
}
}
return await response.json()
} catch (error) {
console.error(`Failed to send Dreame command ${action} to ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
timestamp: new Date().toISOString(),
}
}
}
async dreameStatus(robotId: string) {
try {
const response = await fetch(`${this.baseUrl}/api/dreame/${robotId}/status`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(`Failed to get Dreame status for ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
}
}
}
async dreameMap(robotId: string, includeHistory: boolean = false, highResolution: boolean = false) {
try {
const params = new URLSearchParams()
if (includeHistory) params.append('include_history', 'true')
if (highResolution) params.append('high_resolution', 'true')
const url = `${this.baseUrl}/api/dreame/${robotId}/map${params.toString() ? '?' + params.toString() : ''}`
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(`Failed to get Dreame map for ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
}
}
}
async dreameSettings(robotId: string, settings: Record<string, any>) {
try {
const response = await fetch(`${this.baseUrl}/api/dreame/${robotId}/settings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(settings),
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(`Failed to update Dreame settings for ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
}
}
}
async dreameZoneClean(robotId: string, zones: number[][]) {
try {
const response = await fetch(`${this.baseUrl}/api/dreame/${robotId}/zone`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(zones),
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(`Failed to start Dreame zone cleaning for ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
}
}
}
async dreameRoomClean(robotId: string, roomId: number) {
try {
const response = await fetch(`${this.baseUrl}/api/dreame/${robotId}/room/${roomId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(`Failed to start Dreame room cleaning for ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
}
}
}
async dreameSpotClean(robotId: string, x: number, y: number) {
try {
const response = await fetch(`${this.baseUrl}/api/dreame/${robotId}/spot?x=${x}&y=${y}`, {
method: 'POST',
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(`Failed to start Dreame spot cleaning for ${robotId}:`, error)
return {
success: false,
error: error instanceof Error ? error.message : String(error),
robot_id: robotId,
}
}
}
}
// Export singleton instance
export const mcpService = new MCPService()
export default mcpService