@1mcp/agent 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -9
- package/build/capabilities/capabilityManager.js +7 -6
- package/build/clients/clientManager.js +39 -8
- package/build/config/configManager.js +1 -1
- package/build/config/configManager.test.js +106 -0
- package/build/config/transportConfig.js +17 -38
- package/build/constants.js +2 -1
- package/build/handlers/notificationHandlers.js +10 -10
- package/build/handlers/requestHandlers.js +79 -50
- package/build/index.js +30 -2
- package/build/logger/logger.js +0 -17
- package/build/logger/mcpTransport.js +1 -1
- package/build/server/expressServer.js +21 -6
- package/build/serverManager.js +21 -14
- package/build/serverManager.test.js +158 -0
- package/build/services/configReloadService.js +10 -8
- package/build/types.js +31 -0
- package/build/utils/clientFiltering.js +29 -0
- package/build/utils/errorHandling.js +66 -43
- package/build/utils/errorTypes.js +47 -0
- package/package.json +22 -3
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npx -y @1mcp/agent --config ~/Library/Application\ Support/Claude/claude_desktop
|
|
|
28
28
|
"mcpServers": {
|
|
29
29
|
"1mcp": {
|
|
30
30
|
"type": "http",
|
|
31
|
-
"url": "http://localhost:3050"
|
|
31
|
+
"url": "http://localhost:3050/sse"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -41,24 +41,81 @@ npx -y @1mcp/agent --config ~/Library/Application\ Support/Claude/claude_desktop
|
|
|
41
41
|
You can run the server directly using `npx`:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
#
|
|
44
|
+
# Basic usage (starts server with SSE transport)
|
|
45
45
|
npx -y @1mcp/agent
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
npx -y @1mcp/agent --
|
|
47
|
+
# Use existing Claude Desktop config
|
|
48
|
+
npx -y @1mcp/agent --config ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
49
49
|
|
|
50
|
-
#
|
|
51
|
-
npx -y @1mcp/agent --
|
|
50
|
+
# Use stdio transport instead of SSE
|
|
51
|
+
npx -y @1mcp/agent --transport stdio
|
|
52
52
|
|
|
53
|
-
# Show
|
|
53
|
+
# Show all available options
|
|
54
54
|
npx -y @1mcp/agent --help
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Available options:
|
|
58
|
-
- `--transport, -t`:
|
|
59
|
-
- `--config, -c`:
|
|
58
|
+
- `--transport, -t`: Choose transport type ("stdio" or "sse", default: "sse")
|
|
59
|
+
- `--config, -c`: Use a specific config file
|
|
60
|
+
- `--port, -P`: Change SSE port (default: 3050)
|
|
61
|
+
- `--host, -H`: Change SSE host (default: localhost)
|
|
62
|
+
- `--tags, -g`: Filter servers by tags (see Tags section below)
|
|
60
63
|
- `--help, -h`: Show help
|
|
61
64
|
|
|
65
|
+
### Understanding Tags
|
|
66
|
+
|
|
67
|
+
Tags help you control which MCP servers are available to different clients. Think of tags as labels that describe what each server can do.
|
|
68
|
+
|
|
69
|
+
#### How to Use Tags
|
|
70
|
+
|
|
71
|
+
1. **In your server config**: Add tags to each server to describe its capabilities
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"web-server": {
|
|
76
|
+
"command": "uvx",
|
|
77
|
+
"args": ["mcp-server-fetch"],
|
|
78
|
+
"tags": ["network", "web"],
|
|
79
|
+
"disabled": false
|
|
80
|
+
},
|
|
81
|
+
"file-server": {
|
|
82
|
+
"command": "npx",
|
|
83
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "~/Downloads"],
|
|
84
|
+
"tags": ["filesystem"],
|
|
85
|
+
"disabled": false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
2. **When starting 1MCP in stdio mode**: You can filter servers by tags
|
|
92
|
+
```bash
|
|
93
|
+
# Only start servers with the "network" tag
|
|
94
|
+
npx -y @1mcp/agent --transport stdio --tags "network"
|
|
95
|
+
|
|
96
|
+
# Start servers with either "network" or "filesystem" tags
|
|
97
|
+
npx -y @1mcp/agent --transport stdio --tags "network,filesystem"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
3. **When using SSE transport**: Clients can request servers with specific tags
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"1mcp": {
|
|
105
|
+
"type": "http",
|
|
106
|
+
"url": "http://localhost:3050/sse?tags=network" // Only connect to network-capable servers
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Example tags:
|
|
113
|
+
- `network`: For servers that make web requests
|
|
114
|
+
- `filesystem`: For servers that handle file operations
|
|
115
|
+
- `memory`: For servers that provide memory/storage
|
|
116
|
+
- `shell`: For servers that run shell commands
|
|
117
|
+
- `db`: For servers that handle database operations
|
|
118
|
+
|
|
62
119
|
## Configuration
|
|
63
120
|
|
|
64
121
|
### Global Configuration
|
|
@@ -5,16 +5,17 @@ import { registerRequestHandlers } from '../handlers/requestHandlers.js';
|
|
|
5
5
|
* Collects capabilities from all clients and registers them with the server
|
|
6
6
|
* @param clients Record of client instances
|
|
7
7
|
* @param server The MCP server instance
|
|
8
|
+
* @param tags Array of tags to filter clients by
|
|
8
9
|
* @returns The combined server capabilities
|
|
9
10
|
*/
|
|
10
|
-
export async function setupCapabilities(clients,
|
|
11
|
+
export async function setupCapabilities(clients, serverInfo) {
|
|
11
12
|
// Collect capabilities from all clients
|
|
12
13
|
const capabilities = collectCapabilities(clients);
|
|
13
14
|
// Set up notification handlers
|
|
14
|
-
setupClientToServerNotifications(clients,
|
|
15
|
-
setupServerToClientNotifications(clients,
|
|
15
|
+
setupClientToServerNotifications(clients, serverInfo);
|
|
16
|
+
setupServerToClientNotifications(clients, serverInfo);
|
|
16
17
|
// Register request handlers based on capabilities
|
|
17
|
-
registerRequestHandlers(clients,
|
|
18
|
+
registerRequestHandlers(clients, serverInfo, capabilities);
|
|
18
19
|
return capabilities;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -24,9 +25,9 @@ export async function setupCapabilities(clients, server) {
|
|
|
24
25
|
*/
|
|
25
26
|
function collectCapabilities(clients) {
|
|
26
27
|
let capabilities = {};
|
|
27
|
-
for (const [name,
|
|
28
|
+
for (const [name, clientInfo] of Object.entries(clients)) {
|
|
28
29
|
try {
|
|
29
|
-
const clientCapabilities = client.getServerCapabilities() || {};
|
|
30
|
+
const clientCapabilities = clientInfo.client.getServerCapabilities() || {};
|
|
30
31
|
logger.debug(`Capabilities from ${name}: ${JSON.stringify(clientCapabilities)}`);
|
|
31
32
|
capabilities = { ...capabilities, ...clientCapabilities };
|
|
32
33
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import createClient from '../client.js';
|
|
2
2
|
import logger from '../logger/logger.js';
|
|
3
3
|
import { CONNECTION_RETRY, MCP_SERVER_NAME } from '../constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { withErrorHandling } from '../utils/errorHandling.js';
|
|
5
|
+
import { ClientConnectionError, ClientNotFoundError } from '../utils/errorTypes.js';
|
|
6
|
+
import { ClientStatus } from '../types.js';
|
|
5
7
|
/**
|
|
6
8
|
* Creates client instances for all transports with retry logic
|
|
7
9
|
* @param transports Record of transport instances
|
|
@@ -15,15 +17,27 @@ export async function createClients(transports) {
|
|
|
15
17
|
const client = await createClient();
|
|
16
18
|
// Connect with retry logic
|
|
17
19
|
await connectWithRetry(client, transport, name);
|
|
18
|
-
clients[name] =
|
|
20
|
+
clients[name] = {
|
|
21
|
+
name,
|
|
22
|
+
transport,
|
|
23
|
+
client,
|
|
24
|
+
status: ClientStatus.Connected,
|
|
25
|
+
lastConnected: new Date(),
|
|
26
|
+
};
|
|
19
27
|
logger.info(`Client created for ${name}`);
|
|
20
28
|
}
|
|
21
29
|
catch (error) {
|
|
22
30
|
logger.error(`Failed to create client for ${name}: ${error}`);
|
|
23
|
-
|
|
31
|
+
clients[name] = {
|
|
32
|
+
name,
|
|
33
|
+
transport,
|
|
34
|
+
client: await createClient(),
|
|
35
|
+
status: ClientStatus.Error,
|
|
36
|
+
lastError: error instanceof Error ? error : new Error(String(error)),
|
|
37
|
+
};
|
|
24
38
|
}
|
|
25
39
|
}
|
|
26
|
-
return clients;
|
|
40
|
+
return Object.freeze(clients);
|
|
27
41
|
}
|
|
28
42
|
/**
|
|
29
43
|
* Connects a client to its transport with retry logic
|
|
@@ -71,13 +85,30 @@ export function getClient(clients, clientName) {
|
|
|
71
85
|
return client;
|
|
72
86
|
}
|
|
73
87
|
/**
|
|
74
|
-
* Executes a client operation with error handling
|
|
88
|
+
* Executes a client operation with error handling and timeout
|
|
75
89
|
* @param clients Record of client instances
|
|
76
90
|
* @param clientName The name of the client to use
|
|
77
91
|
* @param operation The operation to execute
|
|
92
|
+
* @param options Operation options including timeout and retry settings
|
|
78
93
|
* @returns The result of the operation
|
|
79
94
|
*/
|
|
80
|
-
export async function executeClientOperation(clients, clientName, operation) {
|
|
81
|
-
const
|
|
82
|
-
|
|
95
|
+
export async function executeClientOperation(clients, clientName, operation, options = {}) {
|
|
96
|
+
const clientInfo = getClient(clients, clientName);
|
|
97
|
+
const { retryCount = 0, retryDelay = 1000 } = options;
|
|
98
|
+
return withErrorHandling(async () => {
|
|
99
|
+
let lastError;
|
|
100
|
+
for (let i = 0; i <= retryCount; i++) {
|
|
101
|
+
try {
|
|
102
|
+
return await operation(clientInfo);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
106
|
+
if (i < retryCount) {
|
|
107
|
+
logger.info(`Retrying operation ${operation.name} on client ${clientName} after ${retryDelay}ms`);
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw lastError;
|
|
113
|
+
}, `Error executing operation on client ${clientName}`)();
|
|
83
114
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// Mock modules before importing ConfigManager
|
|
4
|
+
jest.mock('fs', () => ({
|
|
5
|
+
existsSync: jest.fn(),
|
|
6
|
+
mkdirSync: jest.fn(),
|
|
7
|
+
writeFileSync: jest.fn(),
|
|
8
|
+
readFileSync: jest.fn(),
|
|
9
|
+
watch: jest.fn(() => ({ close: jest.fn() })),
|
|
10
|
+
}));
|
|
11
|
+
const mockLogger = {
|
|
12
|
+
error: jest.fn(),
|
|
13
|
+
warn: jest.fn(),
|
|
14
|
+
info: jest.fn(),
|
|
15
|
+
debug: jest.fn(),
|
|
16
|
+
level: 'info',
|
|
17
|
+
transports: [],
|
|
18
|
+
};
|
|
19
|
+
jest.doMock('../logger/logger.js', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: mockLogger,
|
|
22
|
+
}));
|
|
23
|
+
// Mock constants
|
|
24
|
+
jest.doMock('../constants.js', () => ({
|
|
25
|
+
__esModule: true,
|
|
26
|
+
DEFAULT_CONFIG: { mcpServers: {} },
|
|
27
|
+
getGlobalConfigPath: jest.fn(),
|
|
28
|
+
getGlobalConfigDir: jest.fn().mockReturnValue('/test'),
|
|
29
|
+
}));
|
|
30
|
+
// Import after mocks are set up
|
|
31
|
+
import { ConfigManager, ConfigChangeEvent } from './configManager.js';
|
|
32
|
+
import { DEFAULT_CONFIG } from '../constants.js';
|
|
33
|
+
describe('ConfigManager', () => {
|
|
34
|
+
const testConfigPath = '/test/config.json';
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
// Reset all mocks
|
|
37
|
+
jest.clearAllMocks();
|
|
38
|
+
// Reset singleton instance
|
|
39
|
+
ConfigManager.instance = undefined;
|
|
40
|
+
// Default mock implementations
|
|
41
|
+
fs.existsSync.mockReturnValue(true);
|
|
42
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({ mcpServers: {} }));
|
|
43
|
+
});
|
|
44
|
+
describe('getInstance', () => {
|
|
45
|
+
it('should create singleton instance', () => {
|
|
46
|
+
const instance1 = ConfigManager.getInstance(testConfigPath);
|
|
47
|
+
const instance2 = ConfigManager.getInstance(testConfigPath);
|
|
48
|
+
expect(instance1).toBe(instance2);
|
|
49
|
+
});
|
|
50
|
+
it('should use provided config path', () => {
|
|
51
|
+
const instance = ConfigManager.getInstance(testConfigPath);
|
|
52
|
+
expect(instance.configFilePath).toBe(testConfigPath);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('ensureConfigExists', () => {
|
|
56
|
+
it('should create config directory and file if they do not exist', () => {
|
|
57
|
+
fs.existsSync
|
|
58
|
+
.mockReturnValueOnce(false) // Directory doesn't exist
|
|
59
|
+
.mockReturnValueOnce(false); // File doesn't exist
|
|
60
|
+
ConfigManager.getInstance(testConfigPath);
|
|
61
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(path.dirname(testConfigPath), { recursive: true });
|
|
62
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(testConfigPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('config watching', () => {
|
|
66
|
+
it('should start and stop watching config file', () => {
|
|
67
|
+
const instance = ConfigManager.getInstance(testConfigPath);
|
|
68
|
+
instance.startWatching();
|
|
69
|
+
expect(fs.watch).toHaveBeenCalledWith(testConfigPath, expect.any(Function));
|
|
70
|
+
instance.stopWatching();
|
|
71
|
+
expect(instance['configWatcher']).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
it('should reload config on file change', () => {
|
|
74
|
+
const instance = ConfigManager.getInstance(testConfigPath);
|
|
75
|
+
const mockWatcher = { close: jest.fn() };
|
|
76
|
+
let watchCallback = () => { };
|
|
77
|
+
fs.watch.mockImplementation((path, callback) => {
|
|
78
|
+
watchCallback = callback;
|
|
79
|
+
return mockWatcher;
|
|
80
|
+
});
|
|
81
|
+
instance.startWatching();
|
|
82
|
+
// Setup spy for event emission
|
|
83
|
+
const emitSpy = jest.spyOn(instance, 'emit');
|
|
84
|
+
// Mock new config data
|
|
85
|
+
const newConfig = { mcpServers: { test: { url: 'test-url' } } };
|
|
86
|
+
fs.readFileSync.mockReturnValueOnce(JSON.stringify(newConfig));
|
|
87
|
+
// Simulate file change
|
|
88
|
+
watchCallback('change', path.basename(testConfigPath));
|
|
89
|
+
expect(emitSpy).toHaveBeenCalledWith(ConfigChangeEvent.TRANSPORT_CONFIG_CHANGED, newConfig.mcpServers);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('getTransportConfig', () => {
|
|
93
|
+
it('should return copy of transport config', () => {
|
|
94
|
+
const testConfig = {
|
|
95
|
+
mcpServers: {
|
|
96
|
+
test: { url: 'test-url' },
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
fs.readFileSync.mockReturnValueOnce(JSON.stringify(testConfig));
|
|
100
|
+
const instance = ConfigManager.getInstance(testConfigPath);
|
|
101
|
+
const config = instance.getTransportConfig();
|
|
102
|
+
expect(config).toEqual(testConfig.mcpServers);
|
|
103
|
+
expect(config).not.toBe(instance['transportConfig']); // Should be a copy
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -1,74 +1,53 @@
|
|
|
1
1
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
2
2
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ZodError } from 'zod';
|
|
4
4
|
import logger from '../logger/logger.js';
|
|
5
|
-
|
|
6
|
-
* Zod schema for transport configuration
|
|
7
|
-
*/
|
|
8
|
-
export const transportConfigSchema = z.object({
|
|
9
|
-
type: z.enum(['stdio', 'sse', 'http']).optional(),
|
|
10
|
-
disabled: z.boolean().optional(),
|
|
11
|
-
// SSEServerParameters fields
|
|
12
|
-
url: z.string().url().optional(),
|
|
13
|
-
headers: z.record(z.string()).optional(),
|
|
14
|
-
// StdioServerParameters fields
|
|
15
|
-
command: z.string().optional(),
|
|
16
|
-
args: z.array(z.string()).optional(),
|
|
17
|
-
stderr: z.union([z.string(), z.number()]).optional(),
|
|
18
|
-
cwd: z.string().optional(),
|
|
19
|
-
env: z.record(z.string()).optional(),
|
|
20
|
-
});
|
|
5
|
+
import { transportConfigSchema } from '../types.js';
|
|
21
6
|
/**
|
|
22
7
|
* Creates transport instances from configuration
|
|
23
8
|
* @returns Record of transport instances
|
|
24
9
|
*/
|
|
25
|
-
export function createTransports(
|
|
10
|
+
export function createTransports(config) {
|
|
26
11
|
const transports = {};
|
|
27
|
-
for (const [name,
|
|
28
|
-
if (
|
|
12
|
+
for (const [name, params] of Object.entries(config)) {
|
|
13
|
+
if (params.disabled) {
|
|
29
14
|
logger.debug(`Skipping disabled transport: ${name}`);
|
|
30
15
|
continue;
|
|
31
16
|
}
|
|
32
17
|
try {
|
|
33
|
-
|
|
34
|
-
const validatedTransport = transportConfigSchema.parse(transport);
|
|
35
|
-
// Merge environment variables
|
|
36
|
-
validatedTransport.env = {
|
|
37
|
-
...Object.fromEntries(Object.entries(process.env)
|
|
38
|
-
.filter(([_, v]) => v !== undefined)
|
|
39
|
-
.map(([k, v]) => [k, String(v)])),
|
|
40
|
-
...validatedTransport.env,
|
|
41
|
-
};
|
|
42
|
-
// Create transport based on type
|
|
18
|
+
const validatedTransport = transportConfigSchema.parse(params);
|
|
43
19
|
if (validatedTransport.type === 'sse' || validatedTransport.type === 'http') {
|
|
44
20
|
if (!validatedTransport.url) {
|
|
45
21
|
throw new Error(`URL is required for SSE transport: ${name}`);
|
|
46
22
|
}
|
|
47
|
-
|
|
23
|
+
const transport = new SSEClientTransport(new URL(validatedTransport.url), {
|
|
48
24
|
requestInit: {
|
|
49
25
|
headers: validatedTransport.headers,
|
|
50
26
|
},
|
|
51
27
|
});
|
|
28
|
+
transport.timeout = validatedTransport.timeout;
|
|
29
|
+
transport.tags = validatedTransport.tags;
|
|
30
|
+
transports[name] = transport;
|
|
52
31
|
}
|
|
53
32
|
else {
|
|
54
|
-
// For stdio transport, ensure required fields are present
|
|
55
33
|
if (!validatedTransport.command) {
|
|
56
34
|
throw new Error(`Command is required for stdio transport: ${name}`);
|
|
57
35
|
}
|
|
58
|
-
|
|
36
|
+
const transport = new StdioClientTransport(validatedTransport);
|
|
37
|
+
transport.timeout = validatedTransport.timeout;
|
|
38
|
+
transport.tags = validatedTransport.tags;
|
|
39
|
+
transports[name] = transport;
|
|
59
40
|
}
|
|
60
|
-
logger.debug(`Created
|
|
41
|
+
logger.debug(`Created transport: ${name}`);
|
|
61
42
|
}
|
|
62
43
|
catch (error) {
|
|
63
44
|
if (error instanceof ZodError) {
|
|
64
45
|
logger.error(`Invalid transport configuration for ${name}:`, error.errors);
|
|
65
46
|
}
|
|
66
|
-
else if (error instanceof Error) {
|
|
67
|
-
logger.error(`Failed to create transport for ${name}: ${error.message}`);
|
|
68
|
-
}
|
|
69
47
|
else {
|
|
70
|
-
logger.error(`
|
|
48
|
+
logger.error(`Error creating transport ${name}:`, error);
|
|
71
49
|
}
|
|
50
|
+
throw error;
|
|
72
51
|
}
|
|
73
52
|
}
|
|
74
53
|
return transports;
|
package/build/constants.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// Server configuration
|
|
5
5
|
export const PORT = 3050;
|
|
6
|
+
export const HOST = '127.0.0.1';
|
|
6
7
|
// API endpoints
|
|
7
8
|
export const SSE_ENDPOINT = '/sse';
|
|
8
9
|
export const MESSAGES_ENDPOINT = '/messages';
|
|
@@ -22,7 +23,7 @@ export const ERROR_CODES = {
|
|
|
22
23
|
// MCP constants
|
|
23
24
|
export const MCP_CONFIG_FILE = 'mcp.json';
|
|
24
25
|
export const MCP_SERVER_NAME = '1mcp';
|
|
25
|
-
export const MCP_SERVER_VERSION = '0.
|
|
26
|
+
export const MCP_SERVER_VERSION = '0.4.0';
|
|
26
27
|
export const MCP_URI_SEPARATOR = '_1mcp_';
|
|
27
28
|
// Global config paths
|
|
28
29
|
export const CONFIG_DIR_NAME = '1mcp';
|
|
@@ -4,9 +4,9 @@ import { withErrorHandling } from '../utils/errorHandling.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Sets up client-to-server notification handlers
|
|
6
6
|
* @param clients Record of client instances
|
|
7
|
-
* @param
|
|
7
|
+
* @param serverInfo The MCP server instance
|
|
8
8
|
*/
|
|
9
|
-
export function setupClientToServerNotifications(clients,
|
|
9
|
+
export function setupClientToServerNotifications(clients, serverInfo) {
|
|
10
10
|
const clientNotificationSchemas = [
|
|
11
11
|
CancelledNotificationSchema,
|
|
12
12
|
ProgressNotificationSchema,
|
|
@@ -16,11 +16,11 @@ export function setupClientToServerNotifications(clients, server) {
|
|
|
16
16
|
ToolListChangedNotificationSchema,
|
|
17
17
|
PromptListChangedNotificationSchema,
|
|
18
18
|
];
|
|
19
|
-
for (const [name,
|
|
19
|
+
for (const [name, clientInfo] of Object.entries(clients)) {
|
|
20
20
|
clientNotificationSchemas.forEach((schema) => {
|
|
21
|
-
client.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
21
|
+
clientInfo.client.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
22
22
|
logger.info(`Received notification in client: ${name} ${JSON.stringify(notification)}`);
|
|
23
|
-
server.notification({
|
|
23
|
+
serverInfo.server.notification({
|
|
24
24
|
...notification,
|
|
25
25
|
params: {
|
|
26
26
|
...notification.params,
|
|
@@ -34,20 +34,20 @@ export function setupClientToServerNotifications(clients, server) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Sets up server-to-client notification handlers
|
|
36
36
|
* @param clients Record of client instances
|
|
37
|
-
* @param
|
|
37
|
+
* @param serverInfo The MCP server instance
|
|
38
38
|
*/
|
|
39
|
-
export function setupServerToClientNotifications(clients,
|
|
39
|
+
export function setupServerToClientNotifications(clients, serverInfo) {
|
|
40
40
|
const serverNotificationSchemas = [
|
|
41
41
|
CancelledNotificationSchema,
|
|
42
42
|
ProgressNotificationSchema,
|
|
43
43
|
InitializedNotificationSchema,
|
|
44
44
|
RootsListChangedNotificationSchema,
|
|
45
45
|
];
|
|
46
|
-
for (const [name,
|
|
46
|
+
for (const [name, clientInfo] of Object.entries(clients)) {
|
|
47
47
|
serverNotificationSchemas.forEach((schema) => {
|
|
48
|
-
server.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
48
|
+
serverInfo.server.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
49
49
|
logger.info(`Received notification in server: ${name} ${JSON.stringify(notification)}`);
|
|
50
|
-
client.notification({
|
|
50
|
+
clientInfo.client.notification({
|
|
51
51
|
...notification,
|
|
52
52
|
params: {
|
|
53
53
|
...notification.params,
|