@1mcp/agent 0.13.1 → 0.15.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.
Files changed (78) hide show
  1. package/README.md +50 -20
  2. package/build/constants.d.ts +1 -1
  3. package/build/constants.js +1 -1
  4. package/build/core/client/clientManager.d.ts +55 -39
  5. package/build/core/client/clientManager.d.ts.map +1 -1
  6. package/build/core/client/clientManager.js +180 -171
  7. package/build/core/client/clientManager.js.map +1 -1
  8. package/build/core/server/agentConfig.d.ts +20 -0
  9. package/build/core/server/agentConfig.d.ts.map +1 -1
  10. package/build/core/server/agentConfig.js +34 -7
  11. package/build/core/server/agentConfig.js.map +1 -1
  12. package/build/core/server/serverManager.d.ts +11 -4
  13. package/build/core/server/serverManager.d.ts.map +1 -1
  14. package/build/core/server/serverManager.js +47 -23
  15. package/build/core/server/serverManager.js.map +1 -1
  16. package/build/core/types/server.d.ts +17 -0
  17. package/build/core/types/server.d.ts.map +1 -1
  18. package/build/core/types/server.js +14 -1
  19. package/build/core/types/server.js.map +1 -1
  20. package/build/handlers/notificationHandlers.d.ts +2 -2
  21. package/build/handlers/notificationHandlers.d.ts.map +1 -1
  22. package/build/handlers/notificationHandlers.js +15 -10
  23. package/build/handlers/notificationHandlers.js.map +1 -1
  24. package/build/handlers/requestHandlers.d.ts +1 -1
  25. package/build/handlers/requestHandlers.d.ts.map +1 -1
  26. package/build/handlers/requestHandlers.js +68 -67
  27. package/build/handlers/requestHandlers.js.map +1 -1
  28. package/build/index.js +18 -0
  29. package/build/index.js.map +1 -1
  30. package/build/logger/mcpLoggingEnhancer.d.ts.map +1 -1
  31. package/build/logger/mcpLoggingEnhancer.js +18 -1
  32. package/build/logger/mcpLoggingEnhancer.js.map +1 -1
  33. package/build/server.d.ts.map +1 -1
  34. package/build/server.js +3 -2
  35. package/build/server.js.map +1 -1
  36. package/build/services/configReloadService.d.ts.map +1 -1
  37. package/build/services/configReloadService.js +3 -2
  38. package/build/services/configReloadService.js.map +1 -1
  39. package/build/transport/http/middlewares/httpRequestLogger.d.ts +16 -0
  40. package/build/transport/http/middlewares/httpRequestLogger.d.ts.map +1 -0
  41. package/build/transport/http/middlewares/httpRequestLogger.js +41 -0
  42. package/build/transport/http/middlewares/httpRequestLogger.js.map +1 -0
  43. package/build/transport/http/middlewares/scopeAuthMiddleware.d.ts.map +1 -1
  44. package/build/transport/http/middlewares/scopeAuthMiddleware.js +1 -0
  45. package/build/transport/http/middlewares/scopeAuthMiddleware.js.map +1 -1
  46. package/build/transport/http/routes/oauthRoutes.d.ts.map +1 -1
  47. package/build/transport/http/routes/oauthRoutes.js +3 -3
  48. package/build/transport/http/routes/oauthRoutes.js.map +1 -1
  49. package/build/transport/http/routes/sseRoutes.d.ts +1 -2
  50. package/build/transport/http/routes/sseRoutes.d.ts.map +1 -1
  51. package/build/transport/http/routes/sseRoutes.js +14 -8
  52. package/build/transport/http/routes/sseRoutes.js.map +1 -1
  53. package/build/transport/http/routes/streamableHttpRoutes.d.ts +1 -2
  54. package/build/transport/http/routes/streamableHttpRoutes.d.ts.map +1 -1
  55. package/build/transport/http/routes/streamableHttpRoutes.js +15 -14
  56. package/build/transport/http/routes/streamableHttpRoutes.js.map +1 -1
  57. package/build/transport/http/server.d.ts +1 -0
  58. package/build/transport/http/server.d.ts.map +1 -1
  59. package/build/transport/http/server.js +21 -5
  60. package/build/transport/http/server.js.map +1 -1
  61. package/build/transport/transportFactory.js +2 -2
  62. package/build/transport/transportFactory.js.map +1 -1
  63. package/build/utils/logo.d.ts +8 -0
  64. package/build/utils/logo.d.ts.map +1 -0
  65. package/build/utils/logo.js +26 -0
  66. package/build/utils/logo.js.map +1 -0
  67. package/build/utils/operationExecution.d.ts +10 -0
  68. package/build/utils/operationExecution.d.ts.map +1 -0
  69. package/build/utils/operationExecution.js +36 -0
  70. package/build/utils/operationExecution.js.map +1 -0
  71. package/build/utils/pagination.d.ts.map +1 -1
  72. package/build/utils/pagination.js +22 -13
  73. package/build/utils/pagination.js.map +1 -1
  74. package/package.json +2 -2
  75. package/build/core/client/clientFactory.d.ts +0 -25
  76. package/build/core/client/clientFactory.d.ts.map +0 -1
  77. package/build/core/client/clientFactory.js +0 -12
  78. package/build/core/client/clientFactory.js.map +0 -1
package/README.md CHANGED
@@ -58,31 +58,37 @@ npx -y @1mcp/agent --config ~/Library/Application\ Support/Claude/claude_desktop
58
58
  # Use stdio transport instead of SSE
59
59
  npx -y @1mcp/agent --transport stdio
60
60
 
61
+ # Use external URL for reverse proxy setup (nginx, etc.)
62
+ npx -y @1mcp/agent --external-url https://example.com
63
+
64
+ # Configure trust proxy for reverse proxy setup
65
+ npx -y @1mcp/agent --trust-proxy=192.168.1.1
66
+
61
67
  # Show all available options
62
68
  npx -y @1mcp/agent --help
63
69
  ```
64
70
 
65
71
  Available options:
66
72
 
67
- | Option (CLI) | Environment Variable | Description | Default | Deprecated? |
68
- | :--------------------------- | :--------------------------------- | :---------------------------------------------------- | :-------: | :---------: |
69
- | `--transport`, `-t` | `ONE_MCP_TRANSPORT` | Choose transport type ("stdio", "http", or "sse") | "http" | |
70
- | `--config`, `-c` | `ONE_MCP_CONFIG` | Use a specific config file | | |
71
- | `--port`, `-P` | `ONE_MCP_PORT` | Change HTTP port | 3050 | |
72
- | `--host`, `-H` | `ONE_MCP_HOST` | Change HTTP host | localhost | |
73
- | `--tags`, `-g` | `ONE_MCP_TAGS` | Filter servers by tags | | |
74
- | `--pagination`, `-p` | `ONE_MCP_PAGINATION` | Enable pagination for client/server lists (boolean) | false | |
75
- | `--auth` | `ONE_MCP_AUTH` | Enable authentication (OAuth 2.1) | false | Yes |
76
- | `--enable-auth` | `ONE_MCP_ENABLE_AUTH` | Enable authentication (OAuth 2.1) | false | |
77
- | `--enable-scope-validation` | `ONE_MCP_ENABLE_SCOPE_VALIDATION` | Enable tag-based scope validation (boolean) | true | |
78
- | `--enable-enhanced-security` | `ONE_MCP_ENABLE_ENHANCED_SECURITY` | Enable enhanced security middleware (boolean) | false | |
79
- | `--session-ttl` | `ONE_MCP_SESSION_TTL` | Session expiry time in minutes (number) | 1440 | |
80
- | `--session-storage-path` | `ONE_MCP_SESSION_STORAGE_PATH` | Custom session storage directory path (string) | | |
81
- | `--rate-limit-window` | `ONE_MCP_RATE_LIMIT_WINDOW` | OAuth rate limit window in minutes (number) | 15 | |
82
- | `--rate-limit-max` | `ONE_MCP_RATE_LIMIT_MAX` | Maximum requests per OAuth rate limit window (number) | 100 | |
83
- | `--help`, `-h` | | Show help | | |
84
-
85
- > 💡 **Note:** Deprecated options are provided for backward compatibility. Prefer using the non-deprecated options for new configurations.
73
+ | Option (CLI) | Environment Variable | Description | Default |
74
+ | :--------------------------- | :--------------------------------- | :---------------------------------------------------------------------------- | :--------: |
75
+ | `--transport`, `-t` | `ONE_MCP_TRANSPORT` | Choose transport type ("stdio", "http", or "sse") | "http" |
76
+ | `--config`, `-c` | `ONE_MCP_CONFIG` | Use a specific config file | |
77
+ | `--port`, `-P` | `ONE_MCP_PORT` | Change HTTP port | 3050 |
78
+ | `--host`, `-H` | `ONE_MCP_HOST` | Change HTTP host | localhost |
79
+ | `--external-url`, `-u` | `ONE_MCP_EXTERNAL_URL` | External URL for OAuth callbacks and public URLs (e.g., https://example.com) | |
80
+ | `--trust-proxy` | `ONE_MCP_TRUST_PROXY` | Trust proxy configuration for client IP detection (boolean, IP, CIDR, preset) | "loopback" |
81
+ | `--tags`, `-g` | `ONE_MCP_TAGS` | Filter servers by tags | |
82
+ | `--pagination`, `-p` | `ONE_MCP_PAGINATION` | Enable pagination for client/server lists (boolean) | false |
83
+ | `--auth` | `ONE_MCP_AUTH` | Enable authentication (OAuth 2.1) - **Deprecated** | false |
84
+ | `--enable-auth` | `ONE_MCP_ENABLE_AUTH` | Enable authentication (OAuth 2.1) | false |
85
+ | `--enable-scope-validation` | `ONE_MCP_ENABLE_SCOPE_VALIDATION` | Enable tag-based scope validation (boolean) | true |
86
+ | `--enable-enhanced-security` | `ONE_MCP_ENABLE_ENHANCED_SECURITY` | Enable enhanced security middleware (boolean) | false |
87
+ | `--session-ttl` | `ONE_MCP_SESSION_TTL` | Session expiry time in minutes (number) | 1440 |
88
+ | `--session-storage-path` | `ONE_MCP_SESSION_STORAGE_PATH` | Custom session storage directory path (string) | |
89
+ | `--rate-limit-window` | `ONE_MCP_RATE_LIMIT_WINDOW` | OAuth rate limit window in minutes (number) | 15 |
90
+ | `--rate-limit-max` | `ONE_MCP_RATE_LIMIT_MAX` | Maximum requests per OAuth rate limit window (number) | 100 |
91
+ | `--help`, `-h` | | Show help | |
86
92
 
87
93
  ## Docker
88
94
 
@@ -108,15 +114,39 @@ Available image tags:
108
114
  - `vX.Y.Z`: Specific version (e.g. `v1.0.0`)
109
115
  - `sha-<commit>`: Specific commit
110
116
 
111
- Example:
117
+ Examples:
112
118
 
113
119
  ```bash
120
+ # Custom port and tags
114
121
  docker run -p 3051:3051 \
115
122
  -e ONE_MCP_PORT=3051 \
116
123
  -e ONE_MCP_TAGS=network,filesystem \
117
124
  ghcr.io/1mcp-app/agent
125
+
126
+ # With external URL for reverse proxy
127
+ docker run -p 3050:3050 \
128
+ -e ONE_MCP_EXTERNAL_URL=https://mcp.example.com \
129
+ -e ONE_MCP_TRUST_PROXY=true \
130
+ ghcr.io/1mcp-app/agent
131
+ ```
132
+
133
+ ## Trust Proxy Configuration
134
+
135
+ When running 1MCP behind a reverse proxy, configure trust proxy settings for proper client IP detection:
136
+
137
+ ```bash
138
+ # Default (safe for local development)
139
+ npx -y @1mcp/agent --trust-proxy=loopback
140
+
141
+ # Behind reverse proxy
142
+ npx -y @1mcp/agent --trust-proxy=192.168.1.1
143
+
144
+ # Behind CDN/Cloudflare
145
+ npx -y @1mcp/agent --trust-proxy=true
118
146
  ```
119
147
 
148
+ See [docs/TRUST_PROXY.md](docs/TRUST_PROXY.md) for detailed configuration options, security considerations, and reverse proxy setup examples.
149
+
120
150
  ### Understanding Tags
121
151
 
122
152
  Tags help you control which MCP servers are available to different clients. Think of tags as labels that describe what each server can do.
@@ -9,7 +9,7 @@ export declare const MESSAGES_ENDPOINT = "/messages";
9
9
  export declare const STREAMABLE_HTTP_ENDPOINT = "/mcp";
10
10
  export declare const MCP_CONFIG_FILE = "mcp.json";
11
11
  export declare const MCP_SERVER_NAME = "1mcp";
12
- export declare const MCP_SERVER_VERSION = "0.13.1";
12
+ export declare const MCP_SERVER_VERSION = "0.15.0";
13
13
  export declare const MCP_URI_SEPARATOR = "_1mcp_";
14
14
  export declare const CONFIG_DIR_NAME = "1mcp";
15
15
  export declare const DEFAULT_CONFIG: {
@@ -11,7 +11,7 @@ export const STREAMABLE_HTTP_ENDPOINT = '/mcp';
11
11
  // MCP constants
12
12
  export const MCP_CONFIG_FILE = 'mcp.json';
13
13
  export const MCP_SERVER_NAME = '1mcp';
14
- export const MCP_SERVER_VERSION = '0.13.1';
14
+ export const MCP_SERVER_VERSION = '0.15.0';
15
15
  export const MCP_URI_SEPARATOR = '_1mcp_';
16
16
  // Global config paths
17
17
  export const CONFIG_DIR_NAME = '1mcp';
@@ -1,43 +1,59 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
- import { OutboundConnection, OutboundConnections, OperationOptions, InboundConnection, ServerCapability, AuthProviderTransport } from '../types/index.js';
3
- /**
4
- * Creates client instances for all transports with retry logic
5
- * @param transports Record of transport instances
6
- * @returns Record of client instances
7
- */
8
- export declare function createClients(transports: Record<string, AuthProviderTransport>): Promise<OutboundConnections>;
9
- /**
10
- * Gets a client by name with error handling
11
- * @param clients Record of client instances
12
- * @param clientName The name of the client to get
13
- * @returns The client instance
14
- * @throws ClientNotFoundError if the client is not found
15
- */
16
- export declare function getClient(clients: OutboundConnections, clientName: string): OutboundConnection;
17
- /**
18
- * Executes an operation with error handling and retry logic
19
- * @param operation The operation to execute
20
- * @param context The execution context (client info or server info)
21
- * @param options Operation options including timeout and retry settings
22
- * @returns The result of the operation
23
- */
24
- export declare function executeOperation<T>(operation: () => Promise<T>, contextName: string, options?: OperationOptions): Promise<T>;
25
- /**
26
- * Executes a client operation with error handling and retry logic
27
- * @param clients Record of client instances
28
- * @param clientName The name of the client to use
29
- * @param operation The operation to execute
30
- * @param options Operation options including timeout and retry settings
31
- * @param requiredCapability The capability required for this operation
32
- */
33
- export declare function executeClientOperation<T>(clients: OutboundConnections, clientName: string, operation: (clientInfo: OutboundConnection) => Promise<T>, options?: OperationOptions, requiredCapability?: ServerCapability): Promise<T>;
34
- /**
35
- * Executes a server operation with error handling and retry logic
36
- * @param server The server to execute the operation on
37
- * @param operation The operation to execute
38
- * @param options Operation options including timeout and retry settings
39
- */
40
- export declare function executeServerOperation<T>(server: InboundConnection, operation: (server: InboundConnection) => Promise<T>, options?: OperationOptions): Promise<T>;
2
+ import { OutboundConnection, OutboundConnections, OperationOptions, ServerCapability, AuthProviderTransport } from '../types/index.js';
3
+ export declare class ClientManager {
4
+ private static instance;
5
+ private outboundConns;
6
+ private transports;
7
+ private connectionSemaphore;
8
+ private constructor();
9
+ static getOrCreateInstance(): ClientManager;
10
+ static get current(): ClientManager;
11
+ static resetInstance(): void;
12
+ /**
13
+ * Creates a new MCP client instance
14
+ * @returns A new Client instance
15
+ */
16
+ private createClient;
17
+ /**
18
+ * Creates a new MCP client instance for external use (e.g., OAuth testing)
19
+ * @returns A new Client instance
20
+ */
21
+ createClientInstance(): Client;
22
+ /**
23
+ * Creates client instances for all transports with retry logic
24
+ * @param transports Record of transport instances
25
+ * @returns Record of client instances
26
+ */
27
+ createClients(transports: Record<string, AuthProviderTransport>): Promise<OutboundConnections>;
28
+ /**
29
+ * Connects a client to its transport with retry logic and OAuth support
30
+ * @param client The client to connect
31
+ * @param transport The transport to connect to
32
+ * @param name The name of the client for logging
33
+ * @returns The connected client (may be a new instance after retries)
34
+ */
35
+ private connectWithRetry;
36
+ /**
37
+ * Gets a client by name with error handling
38
+ * @param clientName The name of the client to get
39
+ * @returns The client instance
40
+ * @throws ClientNotFoundError if the client is not found
41
+ */
42
+ getClient(clientName: string): OutboundConnection;
43
+ /**
44
+ * Gets all outbound connections
45
+ * @returns Map of all outbound connections
46
+ */
47
+ getClients(): OutboundConnections;
48
+ /**
49
+ * Executes a client operation with error handling and retry logic
50
+ * @param clientName The name of the client to use
51
+ * @param operation The operation to execute
52
+ * @param options Operation options including timeout and retry settings
53
+ * @param requiredCapability The capability required for this operation
54
+ */
55
+ executeClientOperation<T>(clientName: string, operation: (clientInfo: OutboundConnection) => Promise<T>, options?: OperationOptions, requiredCapability?: ServerCapability): Promise<T>;
56
+ }
41
57
  /**
42
58
  * Custom error class for OAuth authorization required
43
59
  */
@@ -1 +1 @@
1
- {"version":3,"file":"clientManager.d.ts","sourceRoot":"","sources":["../../../src/core/client/clientManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAQnE,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAG3B;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkEnH;AA4DD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAM9F;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CA2BZ;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,OAAO,EAAE,mBAAmB,EAC5B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,EACzD,OAAO,GAAE,gBAAqB,EAC9B,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,OAAO,CAAC,CAAC,CAAC,CAQZ;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,EACpD,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAElC,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;CAKxB"}
1
+ {"version":3,"file":"clientManager.d.ts","sourceRoot":"","sources":["../../../src/core/client/clientManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMnE,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAI3B,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,UAAU,CAA6C;IAC/D,OAAO,CAAC,mBAAmB,CAAyC;IAEpE,OAAO;WAEO,mBAAmB,IAAI,aAAa;IAOlD,WAAkB,OAAO,IAAI,aAAa,CAEzC;WAGa,aAAa,IAAI,IAAI;IAInC;;;OAGG;IACH,OAAO,CAAC,YAAY;IAYpB;;;OAGG;IACI,oBAAoB,IAAI,MAAM;IAIrC;;;;OAIG;IACU,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAyE3G;;;;;;OAMG;YACW,gBAAgB;IA+C9B;;;;;OAKG;IACI,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB;IAQxD;;;OAGG;IACI,UAAU,IAAI,mBAAmB;IAIxC;;;;;;OAMG;IACU,sBAAsB,CAAC,CAAC,EACnC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,EACzD,OAAO,GAAE,gBAAqB,EAC9B,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,OAAO,CAAC,CAAC,CAAC;CAad;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAElC,UAAU,EAAE,MAAM;IAClB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;CAKxB"}
@@ -1,197 +1,206 @@
1
- import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js';
3
- import createClient from './clientFactory.js';
4
3
  import logger from '../../logger/logger.js';
5
- import { CONNECTION_RETRY, MCP_SERVER_NAME } from '../../constants.js';
6
- import { ClientConnectionError, ClientNotFoundError, MCPError, CapabilityError } from '../../utils/errorTypes.js';
4
+ import { CONNECTION_RETRY, MCP_SERVER_NAME, MCP_SERVER_VERSION, MCP_CLIENT_CAPABILITIES } from '../../constants.js';
5
+ import { ClientConnectionError, ClientNotFoundError, CapabilityError } from '../../utils/errorTypes.js';
7
6
  import { ClientStatus, } from '../types/index.js';
8
7
  import { AgentConfigManager } from '../server/agentConfig.js';
9
- /**
10
- * Creates client instances for all transports with retry logic
11
- * @param transports Record of transport instances
12
- * @returns Record of client instances
13
- */
14
- export async function createClients(transports) {
15
- const clients = new Map();
16
- for (const [name, transport] of Object.entries(transports)) {
17
- logger.info(`Creating client for ${name}`);
18
- try {
19
- const client = await createClient();
20
- // Connect with retry logic
21
- const connectedClient = await connectWithRetry(client, transport, name);
22
- clients.set(name, {
23
- name,
24
- transport,
25
- client: connectedClient,
26
- status: ClientStatus.Connected,
27
- lastConnected: new Date(),
28
- });
29
- logger.info(`Client created for ${name}`);
30
- connectedClient.onclose = () => {
31
- const clientInfo = clients.get(name);
32
- if (clientInfo) {
33
- clientInfo.status = ClientStatus.Disconnected;
34
- }
35
- logger.info(`Client ${name} disconnected`);
36
- };
8
+ import { executeOperation } from '../../utils/operationExecution.js';
9
+ export class ClientManager {
10
+ static instance;
11
+ outboundConns = new Map();
12
+ transports = {};
13
+ connectionSemaphore = new Map();
14
+ constructor() { }
15
+ static getOrCreateInstance() {
16
+ if (!ClientManager.instance) {
17
+ ClientManager.instance = new ClientManager();
37
18
  }
38
- catch (error) {
39
- if (error instanceof OAuthRequiredError) {
40
- // Handle OAuth required - set client to AwaitingOAuth status
41
- logger.info(`OAuth authorization required for ${name}`);
42
- // Try to get authorization URL from OAuth provider
43
- let authorizationUrl;
44
- try {
45
- // Extract OAuth provider from transport if available
46
- const oauthProvider = transport.oauthProvider;
47
- if (oauthProvider && typeof oauthProvider.getAuthorizationUrl === 'function') {
48
- authorizationUrl = oauthProvider.getAuthorizationUrl();
49
- }
50
- }
51
- catch (urlError) {
52
- logger.warn(`Could not extract authorization URL for ${name}:`, urlError);
53
- }
54
- clients.set(name, {
19
+ return ClientManager.instance;
20
+ }
21
+ static get current() {
22
+ return ClientManager.instance;
23
+ }
24
+ // Test utility method to reset singleton state
25
+ static resetInstance() {
26
+ ClientManager.instance = undefined;
27
+ }
28
+ /**
29
+ * Creates a new MCP client instance
30
+ * @returns A new Client instance
31
+ */
32
+ createClient() {
33
+ return new Client({
34
+ name: MCP_SERVER_NAME,
35
+ version: MCP_SERVER_VERSION,
36
+ }, {
37
+ capabilities: MCP_CLIENT_CAPABILITIES,
38
+ });
39
+ }
40
+ /**
41
+ * Creates a new MCP client instance for external use (e.g., OAuth testing)
42
+ * @returns A new Client instance
43
+ */
44
+ createClientInstance() {
45
+ return this.createClient();
46
+ }
47
+ /**
48
+ * Creates client instances for all transports with retry logic
49
+ * @param transports Record of transport instances
50
+ * @returns Record of client instances
51
+ */
52
+ async createClients(transports) {
53
+ this.transports = transports;
54
+ this.outboundConns.clear();
55
+ for (const [name, transport] of Object.entries(transports)) {
56
+ logger.info(`Creating client for ${name}`);
57
+ try {
58
+ const client = this.createClient();
59
+ // Connect with retry logic
60
+ const connectedClient = await this.connectWithRetry(client, transport, name);
61
+ this.outboundConns.set(name, {
55
62
  name,
56
63
  transport,
57
- client: error.client,
58
- status: ClientStatus.AwaitingOAuth,
59
- authorizationUrl,
60
- oauthStartTime: new Date(),
64
+ client: connectedClient,
65
+ status: ClientStatus.Connected,
66
+ lastConnected: new Date(),
61
67
  });
68
+ logger.info(`Client created for ${name}`);
69
+ connectedClient.onclose = () => {
70
+ const clientInfo = this.outboundConns.get(name);
71
+ if (clientInfo) {
72
+ clientInfo.status = ClientStatus.Disconnected;
73
+ }
74
+ logger.info(`Client ${name} disconnected`);
75
+ };
76
+ connectedClient.onerror = (error) => {
77
+ logger.error(`Client ${name} error: ${error}`);
78
+ };
62
79
  }
63
- else {
64
- logger.error(`Failed to create client for ${name}: ${error}`);
65
- clients.set(name, {
66
- name,
67
- transport,
68
- client: await createClient(),
69
- status: ClientStatus.Error,
70
- lastError: error instanceof Error ? error : new Error(String(error)),
71
- });
80
+ catch (error) {
81
+ if (error instanceof OAuthRequiredError) {
82
+ // Handle OAuth required - set client to AwaitingOAuth status
83
+ logger.info(`OAuth authorization required for ${name}`);
84
+ // Try to get authorization URL from OAuth provider
85
+ let authorizationUrl;
86
+ try {
87
+ // Extract OAuth provider from transport if available
88
+ const oauthProvider = transport.oauthProvider;
89
+ if (oauthProvider && typeof oauthProvider.getAuthorizationUrl === 'function') {
90
+ authorizationUrl = oauthProvider.getAuthorizationUrl();
91
+ }
92
+ }
93
+ catch (urlError) {
94
+ logger.warn(`Could not extract authorization URL for ${name}:`, urlError);
95
+ }
96
+ this.outboundConns.set(name, {
97
+ name,
98
+ transport,
99
+ client: error.client,
100
+ status: ClientStatus.AwaitingOAuth,
101
+ authorizationUrl,
102
+ oauthStartTime: new Date(),
103
+ });
104
+ }
105
+ else {
106
+ logger.error(`Failed to create client for ${name}: ${error}`);
107
+ this.outboundConns.set(name, {
108
+ name,
109
+ transport,
110
+ client: this.createClient(),
111
+ status: ClientStatus.Error,
112
+ lastError: error instanceof Error ? error : new Error(String(error)),
113
+ });
114
+ }
72
115
  }
73
116
  }
117
+ return this.outboundConns;
74
118
  }
75
- return clients;
76
- }
77
- /**
78
- * Connects a client to its transport with retry logic and OAuth support
79
- * @param client The client to connect
80
- * @param transport The transport to connect to
81
- * @param name The name of the client for logging
82
- * @returns The connected client (may be a new instance after retries)
83
- */
84
- async function connectWithRetry(client, transport, name) {
85
- let retryDelay = CONNECTION_RETRY.INITIAL_DELAY_MS;
86
- let currentClient = client;
87
- for (let i = 0; i < CONNECTION_RETRY.MAX_ATTEMPTS; i++) {
88
- try {
89
- await currentClient.connect(transport);
90
- const sv = await currentClient.getServerVersion();
91
- if (sv?.name === MCP_SERVER_NAME) {
92
- throw new ClientConnectionError(name, new Error('Aborted to prevent circular dependency'));
93
- }
94
- logger.info(`Successfully connected to ${name} with server ${sv?.name} version ${sv?.version}`);
95
- return currentClient;
96
- }
97
- catch (error) {
98
- // Handle OAuth authorization flow (managed by SDK)
99
- if (error instanceof UnauthorizedError) {
100
- const serverConfig = AgentConfigManager.getInstance().getConfig();
101
- logger.info(`OAuth authorization required for ${name}. Visit http://localhost:${serverConfig.port}/oauth to authorize`);
102
- // Throw special error that includes OAuth info
103
- throw new OAuthRequiredError(name, currentClient);
119
+ /**
120
+ * Connects a client to its transport with retry logic and OAuth support
121
+ * @param client The client to connect
122
+ * @param transport The transport to connect to
123
+ * @param name The name of the client for logging
124
+ * @returns The connected client (may be a new instance after retries)
125
+ */
126
+ async connectWithRetry(client, transport, name) {
127
+ let retryDelay = CONNECTION_RETRY.INITIAL_DELAY_MS;
128
+ let currentClient = client;
129
+ for (let i = 0; i < CONNECTION_RETRY.MAX_ATTEMPTS; i++) {
130
+ try {
131
+ await currentClient.connect(transport);
132
+ const sv = await currentClient.getServerVersion();
133
+ if (sv?.name === MCP_SERVER_NAME) {
134
+ throw new ClientConnectionError(name, new Error('Aborted to prevent circular dependency'));
135
+ }
136
+ logger.info(`Successfully connected to ${name} with server ${sv?.name} version ${sv?.version}`);
137
+ return currentClient;
104
138
  }
105
- // Handle other connection errors
106
- else {
107
- const errorMessage = error instanceof Error ? error.message : String(error);
108
- logger.error(`Failed to connect to ${name}: ${errorMessage}`);
109
- if (i < CONNECTION_RETRY.MAX_ATTEMPTS - 1) {
110
- logger.info(`Retrying in ${retryDelay}ms...`);
111
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
112
- retryDelay *= 2; // Exponential backoff
113
- // Create a new client for retry to avoid "already started" errors
114
- currentClient = await createClient();
139
+ catch (error) {
140
+ // Handle OAuth authorization flow (managed by SDK)
141
+ if (error instanceof UnauthorizedError) {
142
+ const configManager = AgentConfigManager.getInstance();
143
+ logger.info(`OAuth authorization required for ${name}. Visit ${configManager.getUrl()}/oauth to authorize`);
144
+ // Throw special error that includes OAuth info
145
+ throw new OAuthRequiredError(name, currentClient);
115
146
  }
147
+ // Handle other connection errors
116
148
  else {
117
- throw new ClientConnectionError(name, error instanceof Error ? error : new Error(String(error)));
149
+ const errorMessage = error instanceof Error ? error.message : String(error);
150
+ logger.error(`Failed to connect to ${name}: ${errorMessage}`);
151
+ if (i < CONNECTION_RETRY.MAX_ATTEMPTS - 1) {
152
+ logger.info(`Retrying in ${retryDelay}ms...`);
153
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
154
+ retryDelay *= 2; // Exponential backoff
155
+ // Create a new client for retry to avoid "already started" errors
156
+ currentClient = this.createClient();
157
+ }
158
+ else {
159
+ throw new ClientConnectionError(name, error instanceof Error ? error : new Error(String(error)));
160
+ }
118
161
  }
119
162
  }
120
163
  }
164
+ // This should never be reached due to the throw in the else block above
165
+ throw new ClientConnectionError(name, new Error('Max retries exceeded'));
121
166
  }
122
- // This should never be reached due to the throw in the else block above
123
- throw new ClientConnectionError(name, new Error('Max retries exceeded'));
124
- }
125
- // OAuth authorization flow is now handled by the SDK's built-in implementation
126
- /**
127
- * Gets a client by name with error handling
128
- * @param clients Record of client instances
129
- * @param clientName The name of the client to get
130
- * @returns The client instance
131
- * @throws ClientNotFoundError if the client is not found
132
- */
133
- export function getClient(clients, clientName) {
134
- const client = clients.get(clientName);
135
- if (!client) {
136
- throw new ClientNotFoundError(clientName);
137
- }
138
- return client;
139
- }
140
- /**
141
- * Executes an operation with error handling and retry logic
142
- * @param operation The operation to execute
143
- * @param context The execution context (client info or server info)
144
- * @param options Operation options including timeout and retry settings
145
- * @returns The result of the operation
146
- */
147
- export async function executeOperation(operation, contextName, options = {}) {
148
- const { retryCount = 0, retryDelay = 1000 } = options;
149
- let lastError;
150
- for (let i = 0; i <= retryCount; i++) {
151
- try {
152
- return await operation();
153
- }
154
- catch (error) {
155
- lastError = error instanceof Error ? error : new Error(String(error));
156
- if (i < retryCount) {
157
- logger.info(`Retrying operation ${operation.name} on ${contextName} after ${retryDelay}ms`);
158
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
159
- }
167
+ /**
168
+ * Gets a client by name with error handling
169
+ * @param clientName The name of the client to get
170
+ * @returns The client instance
171
+ * @throws ClientNotFoundError if the client is not found
172
+ */
173
+ getClient(clientName) {
174
+ const client = this.outboundConns.get(clientName);
175
+ if (!client) {
176
+ throw new ClientNotFoundError(clientName);
160
177
  }
178
+ return client;
161
179
  }
162
- // If we get here, we've exhausted all retries
163
- logger.error(`Operation failed on ${contextName} after ${retryCount + 1} attempts: ${lastError}`);
164
- if (lastError instanceof MCPError) {
165
- throw lastError;
180
+ /**
181
+ * Gets all outbound connections
182
+ * @returns Map of all outbound connections
183
+ */
184
+ getClients() {
185
+ return this.outboundConns;
166
186
  }
167
- const mcpError = new MCPError(`Error executing operation on ${contextName}`, ErrorCode.InternalError, {
168
- originalError: lastError,
169
- });
170
- throw mcpError;
171
- }
172
- /**
173
- * Executes a client operation with error handling and retry logic
174
- * @param clients Record of client instances
175
- * @param clientName The name of the client to use
176
- * @param operation The operation to execute
177
- * @param options Operation options including timeout and retry settings
178
- * @param requiredCapability The capability required for this operation
179
- */
180
- export async function executeClientOperation(clients, clientName, operation, options = {}, requiredCapability) {
181
- const clientInfo = getClient(clients, clientName);
182
- if (requiredCapability && !clientInfo.capabilities?.[requiredCapability]) {
183
- throw new CapabilityError(clientName, String(requiredCapability));
187
+ /**
188
+ * Executes a client operation with error handling and retry logic
189
+ * @param clientName The name of the client to use
190
+ * @param operation The operation to execute
191
+ * @param options Operation options including timeout and retry settings
192
+ * @param requiredCapability The capability required for this operation
193
+ */
194
+ async executeClientOperation(clientName, operation, options = {}, requiredCapability) {
195
+ const outboundConn = this.getClient(clientName);
196
+ if (outboundConn.status !== ClientStatus.Connected || !outboundConn.client.transport) {
197
+ throw new ClientConnectionError(clientName, new Error('Client not connected'));
198
+ }
199
+ if (requiredCapability && !outboundConn.capabilities?.[requiredCapability]) {
200
+ throw new CapabilityError(clientName, String(requiredCapability));
201
+ }
202
+ return executeOperation(() => operation(outboundConn), `client ${clientName}`, options);
184
203
  }
185
- return executeOperation(() => operation(clientInfo), `client ${clientName}`, options);
186
- }
187
- /**
188
- * Executes a server operation with error handling and retry logic
189
- * @param server The server to execute the operation on
190
- * @param operation The operation to execute
191
- * @param options Operation options including timeout and retry settings
192
- */
193
- export async function executeServerOperation(server, operation, options = {}) {
194
- return executeOperation(() => operation(server), 'server', options);
195
204
  }
196
205
  /**
197
206
  * Custom error class for OAuth authorization required