mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-30 21:49:13 -05:00
Compare commits
3 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18cacca2d0 | ||
|
|
1f19ac392f | ||
|
|
840b1b34f1 |
@@ -126,10 +126,14 @@ export const updatePromptDescription = async (
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
// URL-encode server and prompt names to handle slashes (e.g., "com.atlassian/atlassian-mcp-server")
|
||||
// Auth header is automatically added by the interceptor
|
||||
const response = await apiPut<any>(
|
||||
`/servers/${encodeURIComponent(serverName)}/prompts/${encodeURIComponent(promptName)}/description`,
|
||||
{ description },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -30,8 +30,11 @@ export const callTool = async (
|
||||
? `/tools/${encodeURIComponent(server)}/${encodeURIComponent(request.toolName)}`
|
||||
: '/tools/call';
|
||||
|
||||
// Auth header is automatically added by the interceptor
|
||||
const response = await apiPost<any>(url, request.arguments);
|
||||
const response = await apiPost<any>(url, request.arguments, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`, // Add bearer auth for MCP routing
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success === false) {
|
||||
return {
|
||||
@@ -63,10 +66,14 @@ export const toggleTool = async (
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
// URL-encode server and tool names to handle slashes (e.g., "com.atlassian/atlassian-mcp-server")
|
||||
// Auth header is automatically added by the interceptor
|
||||
const response = await apiPost<any>(
|
||||
`/servers/${encodeURIComponent(serverName)}/tools/${encodeURIComponent(toolName)}/toggle`,
|
||||
{ enabled },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -92,10 +99,14 @@ export const updateToolDescription = async (
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
// URL-encode server and tool names to handle slashes (e.g., "com.atlassian/atlassian-mcp-server")
|
||||
// Auth header is automatically added by the interceptor
|
||||
const response = await apiPut<any>(
|
||||
`/servers/${encodeURIComponent(serverName)}/tools/${encodeURIComponent(toolName)}/description`,
|
||||
{ description },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('mcphub_token')}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -8,12 +8,6 @@
|
||||
],
|
||||
"env": {
|
||||
"AMAP_MAPS_API_KEY": "your-api-key"
|
||||
},
|
||||
"tools": {
|
||||
"amap-maps_regeocode": {
|
||||
"enabled": true,
|
||||
"description": "Updated via UI test"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
|
||||
@@ -283,6 +283,12 @@ export const searchToolsByVector = async (
|
||||
}>
|
||||
> => {
|
||||
try {
|
||||
// If serverNames is an empty array (not undefined), return empty results
|
||||
// This happens when using $smart/{group} with an empty or non-existent group
|
||||
if (serverNames !== undefined && serverNames.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const vectorRepository = getRepositoryFactory(
|
||||
'vectorEmbeddings',
|
||||
)() as VectorEmbeddingRepository;
|
||||
|
||||
133
tests/services/vectorSearchService.test.ts
Normal file
133
tests/services/vectorSearchService.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
||||
|
||||
// Mock dependencies before importing vectorSearchService
|
||||
jest.mock('../../src/db/index.js', () => ({
|
||||
getRepositoryFactory: jest.fn(() => () => ({
|
||||
searchByText: jest.fn(),
|
||||
saveEmbedding: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../src/db/connection.js', () => ({
|
||||
getAppDataSource: jest.fn(() => ({
|
||||
isInitialized: true,
|
||||
query: jest.fn(),
|
||||
})),
|
||||
initializeDatabase: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../src/utils/smartRouting.js', () => ({
|
||||
getSmartRoutingConfig: jest.fn(() => ({
|
||||
enabled: true,
|
||||
openaiApiKey: 'test-key',
|
||||
openaiApiBaseUrl: 'https://api.openai.com/v1',
|
||||
openaiApiEmbeddingModel: 'text-embedding-3-small',
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('openai', () => {
|
||||
return {
|
||||
default: jest.fn().mockImplementation(() => ({
|
||||
apiKey: 'test-key',
|
||||
embeddings: {
|
||||
create: jest.fn().mockResolvedValue({
|
||||
data: [{ embedding: new Array(1536).fill(0.1) }],
|
||||
}),
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Import after mocks are set up
|
||||
import { searchToolsByVector } from '../../src/services/vectorSearchService.js';
|
||||
import { getRepositoryFactory } from '../../src/db/index.js';
|
||||
|
||||
describe('vectorSearchService', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('searchToolsByVector', () => {
|
||||
it('should return empty array when serverNames is an empty array', async () => {
|
||||
// This test verifies the fix for the $smart/group routing issue
|
||||
// When serverNames is an empty array (empty group), no results should be returned
|
||||
const result = await searchToolsByVector('test query', 10, 0.3, []);
|
||||
|
||||
// Result should be empty when an empty server list is passed
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should search all servers when serverNames is undefined', async () => {
|
||||
const mockSearchResults = [
|
||||
{
|
||||
similarity: 0.9,
|
||||
embedding: {
|
||||
text_content: 'test tool description',
|
||||
metadata: JSON.stringify({
|
||||
serverName: 'server1',
|
||||
toolName: 'tool1',
|
||||
description: 'Test tool 1',
|
||||
inputSchema: {},
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockRepository = {
|
||||
searchByText: jest.fn().mockResolvedValue(mockSearchResults),
|
||||
saveEmbedding: jest.fn(),
|
||||
};
|
||||
|
||||
(getRepositoryFactory as jest.Mock).mockReturnValue(() => mockRepository);
|
||||
|
||||
const result = await searchToolsByVector('test query', 10, 0.3, undefined);
|
||||
|
||||
// searchByText should be called since serverNames is undefined
|
||||
expect(mockRepository.searchByText).toHaveBeenCalled();
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
expect(result[0].serverName).toBe('server1');
|
||||
});
|
||||
|
||||
it('should filter results by serverNames when provided', async () => {
|
||||
const mockSearchResults = [
|
||||
{
|
||||
similarity: 0.9,
|
||||
embedding: {
|
||||
text_content: 'test tool 1',
|
||||
metadata: JSON.stringify({
|
||||
serverName: 'server1',
|
||||
toolName: 'tool1',
|
||||
description: 'Test tool 1',
|
||||
inputSchema: {},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
similarity: 0.85,
|
||||
embedding: {
|
||||
text_content: 'test tool 2',
|
||||
metadata: JSON.stringify({
|
||||
serverName: 'server2',
|
||||
toolName: 'tool2',
|
||||
description: 'Test tool 2',
|
||||
inputSchema: {},
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockRepository = {
|
||||
searchByText: jest.fn().mockResolvedValue(mockSearchResults),
|
||||
saveEmbedding: jest.fn(),
|
||||
};
|
||||
|
||||
(getRepositoryFactory as jest.Mock).mockReturnValue(() => mockRepository);
|
||||
|
||||
// Filter to only server1
|
||||
const result = await searchToolsByVector('test query', 10, 0.3, ['server1']);
|
||||
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].serverName).toBe('server1');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user