@1mcp/agent 0.1.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 +176 -53
- package/build/capabilities/capabilityManager.js +8 -10
- package/build/client.js +4 -3
- package/build/clients/clientManager.js +47 -12
- package/build/config/configManager.js +31 -9
- package/build/config/configManager.test.js +106 -0
- package/build/config/transportConfig.js +38 -23
- package/build/constants.js +32 -1
- package/build/handlers/notificationHandlers.js +11 -11
- package/build/handlers/requestHandlers.js +81 -52
- package/build/index.js +64 -98
- package/build/{logger.js → logger/logger.js} +7 -20
- package/build/{mcpTransport.js → logger/mcpTransport.js} +33 -5
- package/build/middleware/loggingMiddleware.js +114 -0
- package/build/server/expressServer.js +109 -0
- package/build/server.js +9 -42
- package/build/serverManager.js +78 -0
- package/build/serverManager.test.js +158 -0
- package/build/services/configReloadService.js +21 -26
- package/build/types.js +31 -0
- package/build/utils/clientFiltering.js +29 -0
- package/build/utils/errorHandling.js +67 -44
- package/build/utils/errorTypes.js +47 -0
- package/package.json +26 -4
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import logger from '../logger.js';
|
|
2
|
-
import
|
|
3
|
-
import { createTransports } from '../config/transportConfig.js';
|
|
1
|
+
import logger from '../logger/logger.js';
|
|
2
|
+
import { ConfigChangeEvent, ConfigManager } from '../config/configManager.js';
|
|
4
3
|
import { createClients } from '../clients/clientManager.js';
|
|
5
|
-
import {
|
|
4
|
+
import { setupCapabilities } from '../capabilities/capabilityManager.js';
|
|
5
|
+
import { createTransports } from '../config/transportConfig.js';
|
|
6
6
|
/**
|
|
7
7
|
* Service to handle dynamic configuration reloading
|
|
8
8
|
*/
|
|
9
9
|
export class ConfigReloadService {
|
|
10
10
|
static instance;
|
|
11
|
-
|
|
11
|
+
serverInfo = null;
|
|
12
12
|
currentTransports = {};
|
|
13
13
|
isReloading = false;
|
|
14
14
|
/**
|
|
@@ -27,12 +27,13 @@ export class ConfigReloadService {
|
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* Initialize the service with the server instance
|
|
30
|
-
* @param
|
|
30
|
+
* @param serverInfo The MCP server instance
|
|
31
31
|
* @param initialTransports The initial transports
|
|
32
32
|
*/
|
|
33
|
-
initialize(
|
|
34
|
-
this.
|
|
33
|
+
initialize(serverInfo, initialTransports) {
|
|
34
|
+
this.serverInfo = serverInfo;
|
|
35
35
|
this.currentTransports = initialTransports;
|
|
36
|
+
const configManager = ConfigManager.getInstance();
|
|
36
37
|
// Set up configuration change listener
|
|
37
38
|
configManager.on(ConfigChangeEvent.TRANSPORT_CONFIG_CHANGED, this.handleConfigChange.bind(this));
|
|
38
39
|
// Start watching for configuration changes
|
|
@@ -44,37 +45,30 @@ export class ConfigReloadService {
|
|
|
44
45
|
* @param newConfig The new transport configuration
|
|
45
46
|
*/
|
|
46
47
|
async handleConfigChange(newConfig) {
|
|
47
|
-
if (!this.
|
|
48
|
+
if (!this.serverInfo || this.isReloading) {
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
this.isReloading = true;
|
|
51
52
|
logger.info('Handling configuration change...');
|
|
53
|
+
// Store current transports for reconnection
|
|
54
|
+
const currentTransportEntries = Object.entries(this.currentTransports);
|
|
52
55
|
try {
|
|
53
|
-
//
|
|
54
|
-
const
|
|
55
|
-
// Disconnect and clean up old transports that are no longer in the config
|
|
56
|
-
// or have changed configuration
|
|
57
|
-
const transportKeysToRemove = Object.keys(this.currentTransports).filter((key) => !newTransports[key] ||
|
|
58
|
-
JSON.stringify(newConfig[key]) !== JSON.stringify(configManager.getTransportConfig()[key]));
|
|
59
|
-
for (const key of transportKeysToRemove) {
|
|
56
|
+
// Close all current transports
|
|
57
|
+
for (const [key, transport] of currentTransportEntries) {
|
|
60
58
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Note: Transport interface might not have a standard disconnect method,
|
|
64
|
-
// but we can safely try to close it if it's a closeable transport
|
|
65
|
-
if (transport && 'close' in transport && typeof transport.close === 'function') {
|
|
66
|
-
await transport.close();
|
|
67
|
-
logger.info(`Closed transport: ${key}`);
|
|
68
|
-
}
|
|
59
|
+
await transport.close();
|
|
60
|
+
logger.info(`Closed transport: ${key}`);
|
|
69
61
|
}
|
|
70
62
|
catch (error) {
|
|
71
63
|
logger.error(`Error closing transport ${key}: ${error}`);
|
|
72
64
|
}
|
|
73
65
|
}
|
|
66
|
+
// Create new transports from updated configuration
|
|
67
|
+
const newTransports = createTransports(newConfig);
|
|
74
68
|
// Create clients for the new transports
|
|
75
69
|
const newClients = await createClients(newTransports);
|
|
76
|
-
//
|
|
77
|
-
await
|
|
70
|
+
// Register new capabilities with the server
|
|
71
|
+
await setupCapabilities(newClients, this.serverInfo);
|
|
78
72
|
// Update current transports
|
|
79
73
|
this.currentTransports = newTransports;
|
|
80
74
|
logger.info('Configuration reload completed successfully');
|
|
@@ -90,6 +84,7 @@ export class ConfigReloadService {
|
|
|
90
84
|
* Stop the service and clean up resources
|
|
91
85
|
*/
|
|
92
86
|
stop() {
|
|
87
|
+
const configManager = ConfigManager.getInstance();
|
|
93
88
|
configManager.stopWatching();
|
|
94
89
|
configManager.removeAllListeners(ConfigChangeEvent.TRANSPORT_CONFIG_CHANGED);
|
|
95
90
|
logger.info('Config reload service stopped');
|
package/build/types.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for transport configuration
|
|
4
|
+
*/
|
|
5
|
+
export const transportConfigSchema = z.object({
|
|
6
|
+
type: z.enum(['stdio', 'sse', 'http']).optional(),
|
|
7
|
+
disabled: z.boolean().optional(),
|
|
8
|
+
timeout: z.number().optional(),
|
|
9
|
+
tags: z.array(z.string()).optional(),
|
|
10
|
+
// HTTP/SSE Parameters
|
|
11
|
+
url: z.string().url().optional(),
|
|
12
|
+
headers: z.record(z.string()).optional(),
|
|
13
|
+
// StdioServerParameters fields
|
|
14
|
+
command: z.string().optional(),
|
|
15
|
+
args: z.array(z.string()).optional(),
|
|
16
|
+
stderr: z.union([z.string(), z.number()]).optional(),
|
|
17
|
+
cwd: z.string().optional(),
|
|
18
|
+
env: z.record(z.string()).optional(),
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Enum representing possible client connection states
|
|
22
|
+
*/
|
|
23
|
+
export var ClientStatus;
|
|
24
|
+
(function (ClientStatus) {
|
|
25
|
+
/** Client is successfully connected */
|
|
26
|
+
ClientStatus["Connected"] = "connected";
|
|
27
|
+
/** Client is disconnected */
|
|
28
|
+
ClientStatus["Disconnected"] = "disconnected";
|
|
29
|
+
/** Client encountered an error */
|
|
30
|
+
ClientStatus["Error"] = "error";
|
|
31
|
+
})(ClientStatus || (ClientStatus = {}));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logger from '../logger/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Filters clients by tags
|
|
4
|
+
* @param clients Record of client instances
|
|
5
|
+
* @param tags Array of tags to filter by
|
|
6
|
+
* @returns Filtered record of client instances
|
|
7
|
+
*/
|
|
8
|
+
export function filterClientsByTags(clients, tags) {
|
|
9
|
+
if (!tags || tags.length === 0) {
|
|
10
|
+
return clients;
|
|
11
|
+
}
|
|
12
|
+
const filteredClients = {};
|
|
13
|
+
let matchedClients = 0;
|
|
14
|
+
for (const [name, clientInfo] of Object.entries(clients)) {
|
|
15
|
+
const clientTags = clientInfo.transport.tags || [];
|
|
16
|
+
const hasMatchingTags = tags.every((tag) => clientTags.includes(tag));
|
|
17
|
+
if (hasMatchingTags) {
|
|
18
|
+
filteredClients[name] = clientInfo;
|
|
19
|
+
matchedClients++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (matchedClients === 0) {
|
|
23
|
+
logger.warn(`No clients found matching tags: ${tags.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
logger.debug(`Found ${matchedClients} clients matching tags: ${tags.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
return Object.freeze(filteredClients);
|
|
29
|
+
}
|
|
@@ -1,47 +1,11 @@
|
|
|
1
1
|
import { ERROR_CODES } from '../constants.js';
|
|
2
|
-
import logger from '../logger.js';
|
|
2
|
+
import logger from '../logger/logger.js';
|
|
3
|
+
import { MCPError, InvalidRequestError } from './errorTypes.js';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
data;
|
|
9
|
-
constructor(message, code, data) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.name = 'MCPError';
|
|
12
|
-
this.code = code;
|
|
13
|
-
this.data = data;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export class ClientNotFoundError extends MCPError {
|
|
17
|
-
constructor(clientName) {
|
|
18
|
-
super(`Client not found: ${clientName}`, ERROR_CODES.TRANSPORT_NOT_FOUND);
|
|
19
|
-
this.name = 'ClientNotFoundError';
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export class ClientConnectionError extends MCPError {
|
|
23
|
-
constructor(clientName, originalError) {
|
|
24
|
-
super(`Failed to connect to client: ${clientName}${originalError ? ` - ${originalError.message}` : ''}`, ERROR_CODES.TRANSPORT_NOT_FOUND, { originalError });
|
|
25
|
-
this.name = 'ClientConnectionError';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export class InvalidRequestError extends MCPError {
|
|
29
|
-
constructor(message, data) {
|
|
30
|
-
super(message, ERROR_CODES.INVALID_PARAMS, data);
|
|
31
|
-
this.name = 'InvalidRequestError';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export class ProxyError extends MCPError {
|
|
35
|
-
constructor(message, originalError) {
|
|
36
|
-
super(message, ERROR_CODES.INTERNAL_SERVER_ERROR, originalError ? { originalError: originalError.message } : undefined);
|
|
37
|
-
this.name = 'ProxyError';
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Handles errors in async functions by wrapping them in a try/catch block
|
|
42
|
-
* @param fn The async function to wrap
|
|
43
|
-
* @param errorMessage The error message to log
|
|
44
|
-
* @returns A wrapped function that handles errors
|
|
5
|
+
* Wraps a function with error handling
|
|
6
|
+
* @param fn The function to wrap
|
|
7
|
+
* @param errorMessage The error message to use if the function fails
|
|
8
|
+
* @returns The wrapped function
|
|
45
9
|
*/
|
|
46
10
|
export function withErrorHandling(fn, errorMessage) {
|
|
47
11
|
return async (...args) => {
|
|
@@ -54,11 +18,70 @@ export function withErrorHandling(fn, errorMessage) {
|
|
|
54
18
|
if (error instanceof MCPError) {
|
|
55
19
|
throw error;
|
|
56
20
|
}
|
|
57
|
-
// Convert other errors to
|
|
58
|
-
throw new
|
|
21
|
+
// Convert other errors to MCPError
|
|
22
|
+
throw new MCPError(errorMessage, ERROR_CODES.INTERNAL_SERVER_ERROR, {
|
|
23
|
+
originalError: error instanceof Error ? error : new Error(String(error)),
|
|
24
|
+
});
|
|
59
25
|
}
|
|
60
26
|
};
|
|
61
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Normalizes an error to an MCPError type
|
|
30
|
+
* @param error The error to normalize
|
|
31
|
+
* @param errorMessage The error message to use if the error is not an MCPError
|
|
32
|
+
* @returns The normalized error
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeError(error, errorMessage) {
|
|
35
|
+
if (error instanceof MCPError) {
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
return new MCPError(error.message || errorMessage, ERROR_CODES.INTERNAL_SERVER_ERROR);
|
|
40
|
+
}
|
|
41
|
+
return new MCPError(errorMessage, ERROR_CODES.INTERNAL_SERVER_ERROR);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks if an error is a specific MCPError type
|
|
45
|
+
* @param error The error to check
|
|
46
|
+
* @param errorType The error type to check against
|
|
47
|
+
* @returns True if the error is of the specified type
|
|
48
|
+
*/
|
|
49
|
+
export function isMCPError(error, errorType) {
|
|
50
|
+
return error instanceof errorType;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Gets the error code from an error
|
|
54
|
+
* @param error The error to get the code from
|
|
55
|
+
* @returns The error code
|
|
56
|
+
*/
|
|
57
|
+
export function getErrorCode(error) {
|
|
58
|
+
if (error instanceof MCPError) {
|
|
59
|
+
return error.code;
|
|
60
|
+
}
|
|
61
|
+
return ERROR_CODES.INTERNAL_SERVER_ERROR;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Gets the error message from an error
|
|
65
|
+
* @param error The error to get the message from
|
|
66
|
+
* @returns The error message
|
|
67
|
+
*/
|
|
68
|
+
export function getErrorMessage(error) {
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
return error.message;
|
|
71
|
+
}
|
|
72
|
+
return String(error);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Gets the error cause from an error
|
|
76
|
+
* @param error The error to get the cause from
|
|
77
|
+
* @returns The error cause
|
|
78
|
+
*/
|
|
79
|
+
export function getErrorCause(error) {
|
|
80
|
+
if (error instanceof Error && 'cause' in error) {
|
|
81
|
+
return error.cause;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
62
85
|
/**
|
|
63
86
|
* Formats an error for JSON-RPC response
|
|
64
87
|
* @param error The error to format
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ERROR_CODES } from '../constants.js';
|
|
2
|
+
export class MCPError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
data;
|
|
5
|
+
constructor(message, code, data) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'MCPError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.data = data;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class ClientConnectionError extends MCPError {
|
|
13
|
+
constructor(clientName, cause) {
|
|
14
|
+
super(`Failed to connect to client ${clientName}: ${cause.message}`, ERROR_CODES.TRANSPORT_NOT_FOUND, { cause });
|
|
15
|
+
this.name = 'ClientConnectionError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class ClientNotFoundError extends MCPError {
|
|
19
|
+
constructor(clientName) {
|
|
20
|
+
super(`Client not found: ${clientName}`, ERROR_CODES.TRANSPORT_NOT_FOUND);
|
|
21
|
+
this.name = 'ClientNotFoundError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class ClientOperationError extends MCPError {
|
|
25
|
+
constructor(clientName, operation, cause, context) {
|
|
26
|
+
super(`Operation ${operation} failed on client ${clientName}: ${cause.message}`, ERROR_CODES.INTERNAL_SERVER_ERROR, { cause, context });
|
|
27
|
+
this.name = 'ClientOperationError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class ValidationError extends MCPError {
|
|
31
|
+
constructor(message, validationErrors) {
|
|
32
|
+
super(message, ERROR_CODES.INVALID_PARAMS, { validationErrors });
|
|
33
|
+
this.name = 'ValidationError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class TransportError extends MCPError {
|
|
37
|
+
constructor(transportName, cause) {
|
|
38
|
+
super(`Transport error for ${transportName}: ${cause.message}`, ERROR_CODES.CLIENT_CONNECTION_ERROR, { cause });
|
|
39
|
+
this.name = 'TransportError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class InvalidRequestError extends MCPError {
|
|
43
|
+
constructor(message, data) {
|
|
44
|
+
super(message, ERROR_CODES.INVALID_PARAMS, data);
|
|
45
|
+
this.name = 'InvalidRequestError';
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1mcp/agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "One MCP server to aggregate them all - A unified Model Context Protocol server implementation",
|
|
5
5
|
"homepage": "https://1mcp.app",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -25,31 +25,53 @@
|
|
|
25
25
|
},
|
|
26
26
|
"lint-staged": {
|
|
27
27
|
"*.{js,ts}": [
|
|
28
|
+
"eslint --fix",
|
|
28
29
|
"prettier --write"
|
|
29
30
|
]
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
33
34
|
"express": "^4.21.2",
|
|
35
|
+
"uuid": "^11.1.0",
|
|
34
36
|
"winston": "^3.17.0",
|
|
35
37
|
"winston-transport": "^4.9.0",
|
|
36
38
|
"yargs": "^17.7.2",
|
|
37
|
-
"yargs-parser": "^21.1.1"
|
|
39
|
+
"yargs-parser": "^21.1.1",
|
|
40
|
+
"zod": "^3.24.2"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
43
|
+
"@eslint/js": "^9.22.0",
|
|
44
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
40
45
|
"@types/express": "^5.0.0",
|
|
46
|
+
"@types/jest": "^29.5.14",
|
|
41
47
|
"@types/node": "^22.13.10",
|
|
48
|
+
"@types/uuid": "^10.0.0",
|
|
42
49
|
"@types/yargs": "^17.0.33",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
|
51
|
+
"@typescript-eslint/parser": "^8.27.0",
|
|
52
|
+
"eslint": "^9.22.0",
|
|
53
|
+
"eslint-config-prettier": "^10.1.1",
|
|
54
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
55
|
+
"globals": "^16.0.0",
|
|
43
56
|
"husky": "^9.1.7",
|
|
57
|
+
"jest": "^29.7.0",
|
|
58
|
+
"jiti": "^2.4.2",
|
|
44
59
|
"lint-staged": "^15.5.0",
|
|
45
60
|
"prettier": "^3.5.3",
|
|
61
|
+
"ts-jest": "^29.2.6",
|
|
62
|
+
"ts-node": "^10.9.2",
|
|
46
63
|
"tsc-watch": "^6.2.1",
|
|
47
64
|
"typescript": "^5.3.3"
|
|
48
65
|
},
|
|
49
66
|
"scripts": {
|
|
50
|
-
"dev": "tsc-watch --onSuccess \"node build/index.js\"",
|
|
67
|
+
"dev": "tsc-watch --onSuccess \"node build/index.js --config ./mcp.json\"",
|
|
51
68
|
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
52
69
|
"watch": "tsc --watch",
|
|
53
|
-
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
|
70
|
+
"inspector": "npx @modelcontextprotocol/inspector build/index.js",
|
|
71
|
+
"lint": "eslint . --ext .ts",
|
|
72
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
73
|
+
"test": "jest",
|
|
74
|
+
"test:watch": "jest --watch",
|
|
75
|
+
"test:coverage": "jest --coverage"
|
|
54
76
|
}
|
|
55
77
|
}
|