mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-31 20:00:00 -05:00
feat: introduce cloud server market (#260)
This commit is contained in:
273
src/services/cloudService.ts
Normal file
273
src/services/cloudService.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import {
|
||||
CloudServer,
|
||||
CloudTool,
|
||||
MCPRouterResponse,
|
||||
MCPRouterListServersResponse,
|
||||
MCPRouterListToolsResponse,
|
||||
MCPRouterCallToolResponse,
|
||||
} from '../types/index.js';
|
||||
import { loadOriginalSettings } from '../config/index.js';
|
||||
|
||||
// MCPRouter API default base URL
|
||||
const DEFAULT_MCPROUTER_API_BASE = 'https://api.mcprouter.to/v1';
|
||||
|
||||
// Get MCPRouter API config from system configuration
|
||||
const getMCPRouterConfig = () => {
|
||||
const settings = loadOriginalSettings();
|
||||
const mcpRouterConfig = settings.systemConfig?.mcpRouter;
|
||||
|
||||
return {
|
||||
apiKey: mcpRouterConfig?.apiKey || process.env.MCPROUTER_API_KEY || '',
|
||||
referer: mcpRouterConfig?.referer || process.env.MCPROUTER_REFERER || 'https://mcphub.app',
|
||||
title: mcpRouterConfig?.title || process.env.MCPROUTER_TITLE || 'MCPHub',
|
||||
baseUrl:
|
||||
mcpRouterConfig?.baseUrl || process.env.MCPROUTER_API_BASE || DEFAULT_MCPROUTER_API_BASE,
|
||||
};
|
||||
};
|
||||
|
||||
// Get axios config with MCPRouter headers
|
||||
const getAxiosConfig = (): AxiosRequestConfig => {
|
||||
const mcpRouterConfig = getMCPRouterConfig();
|
||||
|
||||
return {
|
||||
headers: {
|
||||
Authorization: mcpRouterConfig.apiKey ? `Bearer ${mcpRouterConfig.apiKey}` : '',
|
||||
'HTTP-Referer': mcpRouterConfig.referer || 'https://mcphub.app',
|
||||
'X-Title': mcpRouterConfig.title || 'MCPHub',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// List all available cloud servers
|
||||
export const getCloudServers = async (): Promise<CloudServer[]> => {
|
||||
try {
|
||||
const axiosConfig = getAxiosConfig();
|
||||
const mcpRouterConfig = getMCPRouterConfig();
|
||||
|
||||
const response = await axios.post<MCPRouterResponse<MCPRouterListServersResponse>>(
|
||||
`${mcpRouterConfig.baseUrl}/list-servers`,
|
||||
{},
|
||||
axiosConfig,
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
|
||||
if (data.code !== 0) {
|
||||
throw new Error(data.message || 'Failed to fetch servers');
|
||||
}
|
||||
|
||||
return data.data.servers || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching cloud market servers:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get a specific cloud server by name
|
||||
export const getCloudServerByName = async (name: string): Promise<CloudServer | null> => {
|
||||
try {
|
||||
const servers = await getCloudServers();
|
||||
return servers.find((server) => server.name === name || server.config_name === name) || null;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching cloud server ${name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// List tools for a specific cloud server
|
||||
export const getCloudServerTools = async (serverKey: string): Promise<CloudTool[]> => {
|
||||
try {
|
||||
const axiosConfig = getAxiosConfig();
|
||||
const mcpRouterConfig = getMCPRouterConfig();
|
||||
|
||||
if (
|
||||
!axiosConfig.headers?.['Authorization'] ||
|
||||
axiosConfig.headers['Authorization'] === 'Bearer '
|
||||
) {
|
||||
throw new Error('MCPROUTER_API_KEY_NOT_CONFIGURED');
|
||||
}
|
||||
|
||||
const response = await axios.post<MCPRouterResponse<MCPRouterListToolsResponse>>(
|
||||
`${mcpRouterConfig.baseUrl}/list-tools`,
|
||||
{
|
||||
server: serverKey,
|
||||
},
|
||||
axiosConfig,
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
if (data.code !== 0) {
|
||||
throw new Error(data.message || 'Failed to fetch tools');
|
||||
}
|
||||
|
||||
return data.data.tools || [];
|
||||
} catch (error) {
|
||||
console.error(`Error fetching tools for server ${serverKey}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Call a tool on a cloud server
|
||||
export const callCloudServerTool = async (
|
||||
serverName: string,
|
||||
toolName: string,
|
||||
args: Record<string, any>,
|
||||
): Promise<MCPRouterCallToolResponse> => {
|
||||
try {
|
||||
const axiosConfig = getAxiosConfig();
|
||||
const mcpRouterConfig = getMCPRouterConfig();
|
||||
|
||||
if (
|
||||
!axiosConfig.headers?.['Authorization'] ||
|
||||
axiosConfig.headers['Authorization'] === 'Bearer '
|
||||
) {
|
||||
throw new Error('MCPROUTER_API_KEY_NOT_CONFIGURED');
|
||||
}
|
||||
|
||||
const response = await axios.post<MCPRouterResponse<MCPRouterCallToolResponse>>(
|
||||
`${mcpRouterConfig.baseUrl}/call-tool`,
|
||||
{
|
||||
server: serverName,
|
||||
name: toolName,
|
||||
arguments: args,
|
||||
},
|
||||
axiosConfig,
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
|
||||
if (data.code !== 0) {
|
||||
throw new Error(data.message || 'Failed to call tool');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.error(`Error calling tool ${toolName} on server ${serverName}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get all categories from cloud servers
|
||||
export const getCloudCategories = async (): Promise<string[]> => {
|
||||
try {
|
||||
const servers = await getCloudServers();
|
||||
const categories = new Set<string>();
|
||||
|
||||
servers.forEach((server) => {
|
||||
// Extract categories from content or description
|
||||
// This is a simple implementation, you might want to parse the content more sophisticatedly
|
||||
if (server.content) {
|
||||
const categoryMatches = server.content.match(/category[:\s]*([^,\n]+)/gi);
|
||||
if (categoryMatches) {
|
||||
categoryMatches.forEach((match) => {
|
||||
const category = match.replace(/category[:\s]*/i, '').trim();
|
||||
if (category) categories.add(category);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(categories).sort();
|
||||
} catch (error) {
|
||||
console.error('Error fetching cloud market categories:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get all tags from cloud servers
|
||||
export const getCloudTags = async (): Promise<string[]> => {
|
||||
try {
|
||||
const servers = await getCloudServers();
|
||||
const tags = new Set<string>();
|
||||
|
||||
servers.forEach((server) => {
|
||||
// Extract tags from content or description
|
||||
if (server.content) {
|
||||
const tagMatches = server.content.match(/tag[s]?[:\s]*([^,\n]+)/gi);
|
||||
if (tagMatches) {
|
||||
tagMatches.forEach((match) => {
|
||||
const tag = match.replace(/tag[s]?[:\s]*/i, '').trim();
|
||||
if (tag) tags.add(tag);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(tags).sort();
|
||||
} catch (error) {
|
||||
console.error('Error fetching cloud market tags:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Search cloud servers by query
|
||||
export const searchCloudServers = async (query: string): Promise<CloudServer[]> => {
|
||||
try {
|
||||
const servers = await getCloudServers();
|
||||
const searchTerms = query
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.filter((term) => term.length > 0);
|
||||
|
||||
if (searchTerms.length === 0) {
|
||||
return servers;
|
||||
}
|
||||
|
||||
return servers.filter((server) => {
|
||||
const searchText = [
|
||||
server.name,
|
||||
server.title,
|
||||
server.description,
|
||||
server.content,
|
||||
server.author_name,
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
return searchTerms.some((term) => searchText.includes(term));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error searching cloud market servers:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Filter cloud servers by category
|
||||
export const filterCloudServersByCategory = async (category: string): Promise<CloudServer[]> => {
|
||||
try {
|
||||
const servers = await getCloudServers();
|
||||
|
||||
if (!category) {
|
||||
return servers;
|
||||
}
|
||||
|
||||
return servers.filter((server) => {
|
||||
const content = (server.content || '').toLowerCase();
|
||||
return content.includes(category.toLowerCase());
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error filtering cloud market servers by category:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Filter cloud servers by tag
|
||||
export const filterCloudServersByTag = async (tag: string): Promise<CloudServer[]> => {
|
||||
try {
|
||||
const servers = await getCloudServers();
|
||||
|
||||
if (!tag) {
|
||||
return servers;
|
||||
}
|
||||
|
||||
return servers.filter((server) => {
|
||||
const content = (server.content || '').toLowerCase();
|
||||
return content.includes(tag.toLowerCase());
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error filtering cloud market servers by tag:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user