@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.
@@ -1,14 +1,14 @@
1
1
  import logger from '../logger/logger.js';
2
- import configManager, { ConfigChangeEvent } from '../config/configManager.js';
3
- import { createTransports } from '../config/transportConfig.js';
2
+ import { ConfigChangeEvent, ConfigManager } from '../config/configManager.js';
4
3
  import { createClients } from '../clients/clientManager.js';
5
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
- server = null;
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 server The MCP server instance
30
+ * @param serverInfo The MCP server instance
31
31
  * @param initialTransports The initial transports
32
32
  */
33
- initialize(server, initialTransports) {
34
- this.server = server;
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,7 +45,7 @@ export class ConfigReloadService {
44
45
  * @param newConfig The new transport configuration
45
46
  */
46
47
  async handleConfigChange(newConfig) {
47
- if (!this.server || this.isReloading) {
48
+ if (!this.serverInfo || this.isReloading) {
48
49
  return;
49
50
  }
50
51
  this.isReloading = true;
@@ -67,7 +68,7 @@ export class ConfigReloadService {
67
68
  // Create clients for the new transports
68
69
  const newClients = await createClients(newTransports);
69
70
  // Register new capabilities with the server
70
- await setupCapabilities(newClients, this.server);
71
+ await setupCapabilities(newClients, this.serverInfo);
71
72
  // Update current transports
72
73
  this.currentTransports = newTransports;
73
74
  logger.info('Configuration reload completed successfully');
@@ -83,6 +84,7 @@ export class ConfigReloadService {
83
84
  * Stop the service and clean up resources
84
85
  */
85
86
  stop() {
87
+ const configManager = ConfigManager.getInstance();
86
88
  configManager.stopWatching();
87
89
  configManager.removeAllListeners(ConfigChangeEvent.TRANSPORT_CONFIG_CHANGED);
88
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
2
  import logger from '../logger/logger.js';
3
+ import { MCPError, InvalidRequestError } from './errorTypes.js';
3
4
  /**
4
- * Custom error types for the MCP agent
5
- */
6
- export class MCPError extends Error {
7
- code;
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 ProxyError
58
- throw new ProxyError(errorMessage, error instanceof Error ? error : new Error(String(error)));
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.2.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,6 +25,7 @@
25
25
  },
26
26
  "lint-staged": {
27
27
  "*.{js,ts}": [
28
+ "eslint --fix",
28
29
  "prettier --write"
29
30
  ]
30
31
  },
@@ -39,20 +40,38 @@
39
40
  "zod": "^3.24.2"
40
41
  },
41
42
  "devDependencies": {
43
+ "@eslint/js": "^9.22.0",
44
+ "@testing-library/jest-dom": "^6.6.3",
42
45
  "@types/express": "^5.0.0",
46
+ "@types/jest": "^29.5.14",
43
47
  "@types/node": "^22.13.10",
44
48
  "@types/uuid": "^10.0.0",
45
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",
46
56
  "husky": "^9.1.7",
57
+ "jest": "^29.7.0",
58
+ "jiti": "^2.4.2",
47
59
  "lint-staged": "^15.5.0",
48
60
  "prettier": "^3.5.3",
61
+ "ts-jest": "^29.2.6",
62
+ "ts-node": "^10.9.2",
49
63
  "tsc-watch": "^6.2.1",
50
64
  "typescript": "^5.3.3"
51
65
  },
52
66
  "scripts": {
53
- "dev": "tsc-watch --onSuccess \"node build/index.js\"",
67
+ "dev": "tsc-watch --onSuccess \"node build/index.js --config ./mcp.json\"",
54
68
  "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
55
69
  "watch": "tsc --watch",
56
- "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"
57
76
  }
58
77
  }