@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.
@@ -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,10 +23,40 @@ 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.1.0';
26
+ export const MCP_SERVER_VERSION = '0.4.0';
26
27
  export const MCP_URI_SEPARATOR = '_1mcp_';
28
+ // Global config paths
29
+ export const CONFIG_DIR_NAME = '1mcp';
30
+ export const DEFAULT_CONFIG = {
31
+ mcpServers: {},
32
+ };
33
+ /**
34
+ * Get the global config directory path based on OS
35
+ */
36
+ export function getGlobalConfigDir() {
37
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
38
+ if (!homeDir) {
39
+ throw new Error('Could not determine home directory');
40
+ }
41
+ const configDir = process.platform === 'darwin' || process.platform === 'linux'
42
+ ? `${homeDir}/.config/${CONFIG_DIR_NAME}`
43
+ : `${homeDir}/AppData/Roaming/${CONFIG_DIR_NAME}`;
44
+ return configDir;
45
+ }
46
+ /**
47
+ * Get the global config file path
48
+ */
49
+ export function getGlobalConfigPath() {
50
+ return `${getGlobalConfigDir()}/${MCP_CONFIG_FILE}`;
51
+ }
27
52
  // Connection retry settings
28
53
  export const CONNECTION_RETRY = {
29
54
  MAX_ATTEMPTS: 3,
30
55
  INITIAL_DELAY_MS: 1000,
31
56
  };
57
+ export const MCP_SERVER_CAPABILITIES = {
58
+ logging: {},
59
+ resources: {},
60
+ tools: {},
61
+ prompts: {},
62
+ };
@@ -1,12 +1,12 @@
1
1
  import { CancelledNotificationSchema, ProgressNotificationSchema, LoggingMessageNotificationSchema, ResourceUpdatedNotificationSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, PromptListChangedNotificationSchema, InitializedNotificationSchema, RootsListChangedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js';
2
- import logger from '../logger.js';
2
+ import logger from '../logger/logger.js';
3
3
  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 server The MCP server instance
7
+ * @param serverInfo The MCP server instance
8
8
  */
9
- export function setupClientToServerNotifications(clients, server) {
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, client] of Object.entries(clients)) {
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 server The MCP server instance
37
+ * @param serverInfo The MCP server instance
38
38
  */
39
- export function setupServerToClientNotifications(clients, server) {
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, client] of Object.entries(clients)) {
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,
@@ -1,19 +1,21 @@
1
- import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
2
- import logger from '../logger.js';
3
- import { setLogLevel } from '../logger.js';
4
- import { MCP_URI_SEPARATOR, MCP_SERVER_NAME } from '../constants.js';
1
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, SetLevelRequestSchema, CallToolResultSchema, } from '@modelcontextprotocol/sdk/types.js';
2
+ import logger from '../logger/logger.js';
3
+ import { setLogLevel } from '../logger/logger.js';
4
+ import { MCP_URI_SEPARATOR, MCP_SERVER_NAME, ERROR_CODES } from '../constants.js';
5
5
  import { executeClientOperation } from '../clients/clientManager.js';
6
- import { ProxyError, parseUri, withErrorHandling } from '../utils/errorHandling.js';
6
+ import { parseUri, withErrorHandling } from '../utils/errorHandling.js';
7
+ import { MCPError } from '../utils/errorTypes.js';
8
+ import { filterClientsByTags } from '../utils/clientFiltering.js';
7
9
  /**
8
10
  * Sends a partial failure notification to inform clients about backend failures
9
11
  * @param server The MCP server instance
10
12
  * @param operation The operation that partially failed
11
13
  * @param failedClients Array of failed clients with error details
12
14
  */
13
- function sendPartialFailureNotification(server, operation, failedClients) {
15
+ function sendPartialFailureNotification(serverInfo, operation, failedClients) {
14
16
  if (failedClients.length === 0)
15
17
  return;
16
- server.notification({
18
+ serverInfo.server.notification({
17
19
  method: 'notifications/message',
18
20
  params: {
19
21
  level: 'warning',
@@ -32,40 +34,44 @@ function sendPartialFailureNotification(server, operation, failedClients) {
32
34
  * @param clients Record of client instances
33
35
  * @param server The MCP server instance
34
36
  * @param capabilities The server capabilities
37
+ * @param tags Array of tags to filter clients by
35
38
  */
36
- export function registerRequestHandlers(clients, server, capabilities) {
39
+ export function registerRequestHandlers(clients, serverInfo, capabilities) {
37
40
  // Register logging level handler
38
- server.setRequestHandler(SetLevelRequestSchema, async (request) => {
41
+ serverInfo.server.setRequestHandler(SetLevelRequestSchema, async (request) => {
39
42
  setLogLevel(request.params.level);
40
43
  return {};
41
44
  });
42
45
  // Register resource-related handlers if capability is available
43
46
  if (capabilities.resources) {
44
- registerResourceHandlers(clients, server);
47
+ registerResourceHandlers(clients, serverInfo);
45
48
  }
46
49
  // Register tool-related handlers if capability is available
47
50
  if (capabilities.tools) {
48
- registerToolHandlers(clients, server);
51
+ registerToolHandlers(clients, serverInfo);
49
52
  }
50
53
  // Register prompt-related handlers if capability is available
51
54
  if (capabilities.prompts) {
52
- registerPromptHandlers(clients, server);
55
+ registerPromptHandlers(clients, serverInfo);
53
56
  }
54
57
  }
55
58
  /**
56
59
  * Registers resource-related request handlers
57
60
  * @param clients Record of client instances
58
- * @param server The MCP server instance
61
+ * @param serverInfo The MCP server instance
59
62
  */
60
- function registerResourceHandlers(clients, server) {
63
+ function registerResourceHandlers(clients, serverInfo) {
61
64
  // List Resources handler
62
- server.setRequestHandler(ListResourcesRequestSchema, withErrorHandling(async (request) => {
65
+ serverInfo.server.setRequestHandler(ListResourcesRequestSchema, withErrorHandling(async (request) => {
63
66
  const resources = [];
64
67
  const failedClients = [];
65
- for (const [name, client] of Object.entries(clients)) {
68
+ const filteredClients = filterClientsByTags(clients, serverInfo.tags);
69
+ for (const [name, clientInfo] of Object.entries(filteredClients)) {
66
70
  logger.info(`Listing resources for ${name}`);
67
71
  try {
68
- const result = await client.listResources(request.params);
72
+ const result = await clientInfo.client.listResources(request.params, {
73
+ timeout: clientInfo.transport.timeout,
74
+ });
69
75
  resources.push(...result.resources.map((resource) => ({
70
76
  uri: `${name}${MCP_URI_SEPARATOR}${resource.uri}`,
71
77
  name: resource.name,
@@ -79,23 +85,28 @@ function registerResourceHandlers(clients, server) {
79
85
  }
80
86
  }
81
87
  // If all clients failed, throw an error
82
- if (failedClients.length === Object.keys(clients).length && Object.keys(clients).length > 0) {
83
- throw new ProxyError('Failed to list resources from all clients', new Error(JSON.stringify(failedClients)));
88
+ if (failedClients.length === Object.keys(filteredClients).length && Object.keys(filteredClients).length > 0) {
89
+ throw new MCPError('Failed to list resources from all clients', ERROR_CODES.INTERNAL_SERVER_ERROR, {
90
+ failedClients,
91
+ });
84
92
  }
85
93
  // Send notification about partial failures
86
94
  if (failedClients.length > 0) {
87
- sendPartialFailureNotification(server, 'listResources', failedClients);
95
+ sendPartialFailureNotification(serverInfo, 'listResources', failedClients);
88
96
  }
89
97
  return { resources };
90
98
  }, 'Error listing resources'));
91
99
  // List Resource Templates handler
92
- server.setRequestHandler(ListResourceTemplatesRequestSchema, withErrorHandling(async (request) => {
100
+ serverInfo.server.setRequestHandler(ListResourceTemplatesRequestSchema, withErrorHandling(async (request) => {
93
101
  const resourceTemplates = [];
94
102
  const failedClients = [];
95
- for (const [name, client] of Object.entries(clients)) {
103
+ const filteredClients = filterClientsByTags(clients, serverInfo.tags);
104
+ for (const [name, clientInfo] of Object.entries(filteredClients)) {
96
105
  logger.info(`Listing resource templates for ${name}`);
97
106
  try {
98
- const result = await client.listResourceTemplates(request.params);
107
+ const result = await clientInfo.client.listResourceTemplates(request.params, {
108
+ timeout: clientInfo.transport.timeout,
109
+ });
99
110
  resourceTemplates.push(...result.resourceTemplates.map((template) => ({
100
111
  uriTemplate: `${name}${MCP_URI_SEPARATOR}${template.uriTemplate}`,
101
112
  name: template.name,
@@ -109,45 +120,56 @@ function registerResourceHandlers(clients, server) {
109
120
  }
110
121
  }
111
122
  // If all clients failed, throw an error
112
- if (failedClients.length === Object.keys(clients).length && Object.keys(clients).length > 0) {
113
- throw new ProxyError('Failed to list resource templates from all clients', new Error(JSON.stringify(failedClients)));
123
+ if (failedClients.length === Object.keys(filteredClients).length && Object.keys(filteredClients).length > 0) {
124
+ throw new MCPError('Failed to list resource templates from all clients', ERROR_CODES.INTERNAL_SERVER_ERROR, {
125
+ failedClients,
126
+ });
114
127
  }
115
128
  // Send notification about partial failures
116
129
  if (failedClients.length > 0) {
117
- sendPartialFailureNotification(server, 'listResourceTemplates', failedClients);
130
+ sendPartialFailureNotification(serverInfo, 'listResourceTemplates', failedClients);
118
131
  }
119
132
  return { resourceTemplates };
120
133
  }, 'Error listing resource templates'));
121
134
  // Subscribe Resource handler
122
- server.setRequestHandler(SubscribeRequestSchema, withErrorHandling(async (request) => {
135
+ serverInfo.server.setRequestHandler(SubscribeRequestSchema, withErrorHandling(async (request) => {
123
136
  const { clientName, resourceName } = parseUri(request.params.uri, MCP_URI_SEPARATOR);
124
- return executeClientOperation(clients, clientName, (client) => client.subscribeResource({ ...request.params, uri: resourceName }));
137
+ return executeClientOperation(clients, clientName, (clientInfo) => clientInfo.client.subscribeResource({ ...request.params, uri: resourceName }, {
138
+ timeout: clientInfo.transport.timeout,
139
+ }));
125
140
  }, 'Error subscribing to resource'));
126
141
  // Unsubscribe Resource handler
127
- server.setRequestHandler(UnsubscribeRequestSchema, withErrorHandling(async (request) => {
142
+ serverInfo.server.setRequestHandler(UnsubscribeRequestSchema, withErrorHandling(async (request) => {
128
143
  const { clientName, resourceName } = parseUri(request.params.uri, MCP_URI_SEPARATOR);
129
- return executeClientOperation(clients, clientName, (client) => client.unsubscribeResource({ ...request.params, uri: resourceName }));
144
+ return executeClientOperation(clients, clientName, (clientInfo) => clientInfo.client.unsubscribeResource({ ...request.params, uri: resourceName }, {
145
+ timeout: clientInfo.transport.timeout,
146
+ }));
130
147
  }, 'Error unsubscribing from resource'));
131
148
  // Read Resource handler
132
- server.setRequestHandler(ReadResourceRequestSchema, withErrorHandling(async (request) => {
149
+ serverInfo.server.setRequestHandler(ReadResourceRequestSchema, withErrorHandling(async (request) => {
133
150
  const { clientName, resourceName } = parseUri(request.params.uri, MCP_URI_SEPARATOR);
134
- return executeClientOperation(clients, clientName, (client) => client.readResource({ ...request.params, uri: resourceName }));
151
+ return executeClientOperation(clients, clientName, (clientInfo) => clientInfo.client.readResource({ ...request.params, uri: resourceName }, {
152
+ timeout: clientInfo.transport.timeout,
153
+ }));
135
154
  }, 'Error reading resource'));
136
155
  }
137
156
  /**
138
157
  * Registers tool-related request handlers
139
158
  * @param clients Record of client instances
140
- * @param server The MCP server instance
159
+ * @param serverInfo The MCP server instance
141
160
  */
142
- function registerToolHandlers(clients, server) {
161
+ function registerToolHandlers(clients, serverInfo) {
143
162
  // List Tools handler
144
- server.setRequestHandler(ListToolsRequestSchema, withErrorHandling(async (request) => {
163
+ serverInfo.server.setRequestHandler(ListToolsRequestSchema, withErrorHandling(async (request) => {
145
164
  const tools = [];
146
165
  const failedClients = [];
147
- for (const [name, client] of Object.entries(clients)) {
166
+ const filteredClients = filterClientsByTags(clients, serverInfo.tags);
167
+ for (const [name, clientInfo] of Object.entries(filteredClients)) {
148
168
  logger.info(`Listing tools for ${name}`);
149
169
  try {
150
- const result = await client.listTools(request.params);
170
+ const result = await clientInfo.client.listTools(request.params, {
171
+ timeout: clientInfo.transport.timeout,
172
+ });
151
173
  tools.push(...result.tools.map((tool) => ({
152
174
  name: `${name}${MCP_URI_SEPARATOR}${tool.name}`,
153
175
  description: tool.description,
@@ -160,35 +182,40 @@ function registerToolHandlers(clients, server) {
160
182
  }
161
183
  }
162
184
  // If all clients failed, throw an error
163
- if (failedClients.length === Object.keys(clients).length && Object.keys(clients).length > 0) {
164
- throw new ProxyError('Failed to list tools from all clients', new Error(JSON.stringify(failedClients)));
185
+ if (failedClients.length === Object.keys(filteredClients).length && Object.keys(filteredClients).length > 0) {
186
+ throw new MCPError('Failed to list tools from all clients', ERROR_CODES.INTERNAL_SERVER_ERROR, {
187
+ failedClients,
188
+ });
165
189
  }
166
190
  // Send notification about partial failures
167
191
  if (failedClients.length > 0) {
168
- sendPartialFailureNotification(server, 'listTools', failedClients);
192
+ sendPartialFailureNotification(serverInfo, 'listTools', failedClients);
169
193
  }
170
194
  return { tools };
171
195
  }, 'Error listing tools'));
172
196
  // Call Tool handler
173
- server.setRequestHandler(CallToolRequestSchema, withErrorHandling(async (request) => {
197
+ serverInfo.server.setRequestHandler(CallToolRequestSchema, withErrorHandling(async (request) => {
174
198
  const { clientName, resourceName: toolName } = parseUri(request.params.name, MCP_URI_SEPARATOR);
175
- return executeClientOperation(clients, clientName, (client) => client.callTool({ ...request.params, name: toolName }));
199
+ return executeClientOperation(clients, clientName, (clientInfo) => clientInfo.client.callTool({ ...request.params, name: toolName }, CallToolResultSchema, {
200
+ timeout: clientInfo.transport.timeout,
201
+ }));
176
202
  }, 'Error calling tool'));
177
203
  }
178
204
  /**
179
205
  * Registers prompt-related request handlers
180
206
  * @param clients Record of client instances
181
- * @param server The MCP server instance
207
+ * @param serverInfo The MCP server instance
182
208
  */
183
- function registerPromptHandlers(clients, server) {
209
+ function registerPromptHandlers(clients, serverInfo) {
184
210
  // List Prompts handler
185
- server.setRequestHandler(ListPromptsRequestSchema, withErrorHandling(async (request) => {
211
+ serverInfo.server.setRequestHandler(ListPromptsRequestSchema, withErrorHandling(async (request) => {
186
212
  const prompts = [];
187
213
  const failedClients = [];
188
- for (const [name, client] of Object.entries(clients)) {
214
+ const filteredClients = filterClientsByTags(clients, serverInfo.tags);
215
+ for (const [name, clientInfo] of Object.entries(filteredClients)) {
189
216
  logger.info(`Listing prompts for ${name}`);
190
217
  try {
191
- const result = await client.listPrompts(request.params);
218
+ const result = await clientInfo.client.listPrompts(request.params);
192
219
  prompts.push(...result.prompts.map((prompt) => ({
193
220
  name: `${name}${MCP_URI_SEPARATOR}${prompt.name}`,
194
221
  description: prompt.description,
@@ -201,18 +228,20 @@ function registerPromptHandlers(clients, server) {
201
228
  }
202
229
  }
203
230
  // If all clients failed, throw an error
204
- if (failedClients.length === Object.keys(clients).length && Object.keys(clients).length > 0) {
205
- throw new ProxyError('Failed to list prompts from all clients', new Error(JSON.stringify(failedClients)));
231
+ if (failedClients.length === Object.keys(filteredClients).length && Object.keys(filteredClients).length > 0) {
232
+ throw new MCPError('Failed to list prompts from all clients', ERROR_CODES.INTERNAL_SERVER_ERROR, {
233
+ failedClients,
234
+ });
206
235
  }
207
236
  // Send notification about partial failures
208
237
  if (failedClients.length > 0) {
209
- sendPartialFailureNotification(server, 'listPrompts', failedClients);
238
+ sendPartialFailureNotification(serverInfo, 'listPrompts', failedClients);
210
239
  }
211
240
  return { prompts };
212
241
  }, 'Error listing prompts'));
213
242
  // Get Prompt handler
214
- server.setRequestHandler(GetPromptRequestSchema, withErrorHandling(async (request) => {
243
+ serverInfo.server.setRequestHandler(GetPromptRequestSchema, withErrorHandling(async (request) => {
215
244
  const { clientName, resourceName: promptName } = parseUri(request.params.name, MCP_URI_SEPARATOR);
216
- return executeClientOperation(clients, clientName, (client) => client.getPrompt({ ...request.params, name: promptName }));
245
+ return executeClientOperation(clients, clientName, (clientInfo) => clientInfo.client.getPrompt({ ...request.params, name: promptName }));
217
246
  }, 'Error getting prompt'));
218
247
  }
package/build/index.js CHANGED
@@ -1,115 +1,64 @@
1
1
  #!/usr/bin/env node
2
- import express from 'express';
3
2
  import yargs from 'yargs';
4
3
  import { hideBin } from 'yargs/helpers';
5
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
6
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
- import { server } from './server.js';
8
- import logger, { setMCPTransportConnected } from './logger.js';
9
- import { PORT, SSE_ENDPOINT, MESSAGES_ENDPOINT, ERROR_CODES } from './constants.js';
5
+ import { setupServer } from './server.js';
6
+ import logger from './logger/logger.js';
10
7
  import configReloadService from './services/configReloadService.js';
8
+ import { ConfigManager } from './config/configManager.js';
9
+ import { ExpressServer } from './server/expressServer.js';
10
+ import { PORT, HOST } from './constants.js';
11
11
  // Parse command line arguments
12
12
  const argv = yargs(hideBin(process.argv))
13
13
  .usage('Usage: $0 [options]')
14
14
  .options({
15
15
  transport: {
16
16
  alias: 't',
17
- describe: 'Transport type to use (stdio or http)',
17
+ describe: 'Transport type to use (stdio or sse)',
18
18
  type: 'string',
19
- choices: ['stdio', 'http'],
20
- default: 'http',
19
+ choices: ['stdio', 'sse'],
20
+ default: 'sse',
21
+ },
22
+ port: {
23
+ alias: 'P',
24
+ describe: 'SSE port to listen on, applicable when transport is sse',
25
+ type: 'number',
26
+ default: PORT,
27
+ },
28
+ host: {
29
+ alias: 'H',
30
+ describe: 'SSE host to listen on, applicable when transport is sse',
31
+ type: 'string',
32
+ default: HOST,
33
+ },
34
+ config: {
35
+ alias: 'c',
36
+ describe: 'Path to the config file',
37
+ type: 'string',
38
+ default: undefined,
39
+ },
40
+ tags: {
41
+ alias: 'g',
42
+ describe: 'Tags to filter clients (comma-separated)',
43
+ type: 'string',
44
+ default: undefined,
21
45
  },
22
46
  })
23
47
  .help()
24
48
  .alias('help', 'h')
25
49
  .parseSync();
26
- const app = express();
27
- // app.use(express.json());
28
- // app.use(express.urlencoded({ extended: true }));
29
- const transportMap = new Map();
30
- // Add error handling middleware
31
- app.use((err, req, res, next) => {
32
- logger.error('Express error:', err);
33
- res.status(500).json({
34
- error: {
35
- code: ERROR_CODES.INTERNAL_SERVER_ERROR,
36
- message: 'Internal server error',
37
- },
38
- });
39
- });
40
- app.get(SSE_ENDPOINT, async (req, res) => {
41
- try {
42
- logger.info('sse', { query: req.query, headers: req.headers });
43
- const transport = new SSEServerTransport(MESSAGES_ENDPOINT, res);
44
- await server.connect(transport);
45
- transportMap.set(transport.sessionId, transport);
46
- // Update MCP transport connection status when a client connects
47
- if (transportMap.size === 1) {
48
- setMCPTransportConnected(true);
49
- logger.info('First client connected, enabling MCP logging transport');
50
- }
51
- transport.onclose = () => {
52
- transportMap.delete(transport.sessionId);
53
- logger.info('transport closed', transport.sessionId);
54
- // Update MCP transport connection status when all clients disconnect
55
- if (transportMap.size === 0) {
56
- setMCPTransportConnected(false);
57
- logger.info('All clients disconnected, disabling MCP logging transport');
58
- }
59
- };
60
- }
61
- catch (error) {
62
- logger.error('SSE connection error:', error);
63
- res.status(500).end();
64
- }
65
- });
66
- app.post(MESSAGES_ENDPOINT, async (req, res) => {
67
- try {
68
- const sessionId = req.query.sessionId;
69
- if (!sessionId) {
70
- res.status(400).json({
71
- error: {
72
- code: ERROR_CODES.INVALID_PARAMS,
73
- message: 'Invalid params: sessionId is required',
74
- },
75
- });
76
- return;
77
- }
78
- logger.info('message', { body: req.body, sessionId });
79
- const transport = transportMap.get(sessionId);
80
- if (transport) {
81
- await transport.handlePostMessage(req, res);
82
- return;
83
- }
84
- res.status(404).json({
85
- error: {
86
- code: ERROR_CODES.TRANSPORT_NOT_FOUND,
87
- message: 'Transport not found',
88
- },
89
- });
90
- }
91
- catch (error) {
92
- logger.error('Message handling error:', error);
93
- res.status(500).json({
94
- error: {
95
- code: ERROR_CODES.INTERNAL_SERVER_ERROR,
96
- message: 'Internal server error',
97
- },
98
- });
99
- }
100
- });
101
50
  /**
102
51
  * Set up graceful shutdown handling
103
52
  */
104
- function setupGracefulShutdown() {
53
+ function setupGracefulShutdown(serverManager) {
105
54
  const shutdown = async () => {
106
55
  logger.info('Shutting down server...');
107
56
  // Stop the configuration reload service
108
57
  configReloadService.stop();
109
58
  // Close all transports
110
- for (const [sessionId, transport] of transportMap.entries()) {
59
+ for (const [sessionId, transport] of serverManager.getTransports().entries()) {
111
60
  try {
112
- transport.close();
61
+ transport?.close();
113
62
  logger.info(`Closed transport: ${sessionId}`);
114
63
  }
115
64
  catch (error) {
@@ -128,19 +77,36 @@ function setupGracefulShutdown() {
128
77
  * Start the server using the specified transport.
129
78
  */
130
79
  async function main() {
131
- // Set up graceful shutdown handling
132
- setupGracefulShutdown();
133
- if (argv.transport === 'stdio') {
134
- // Use stdio transport
135
- const transport = new StdioServerTransport();
136
- await server.connect(transport);
137
- logger.info('Server started with stdio transport');
80
+ try {
81
+ ConfigManager.getInstance(argv.config);
82
+ // Initialize server and get server manager with custom config path if provided
83
+ const serverManager = await setupServer();
84
+ // Set up graceful shutdown handling
85
+ setupGracefulShutdown(serverManager);
86
+ if (argv.transport === 'stdio') {
87
+ // Use stdio transport
88
+ const transport = new StdioServerTransport();
89
+ // Parse and validate tags from CLI if provided
90
+ let tags;
91
+ if (argv.tags) {
92
+ tags = argv.tags.split(',').filter((tag) => tag.trim().length > 0);
93
+ if (tags.length === 0) {
94
+ logger.warn('No valid tags provided, ignoring tags parameter');
95
+ tags = undefined;
96
+ }
97
+ }
98
+ await serverManager.connectTransport(transport, 'stdio', tags);
99
+ logger.info('Server started with stdio transport');
100
+ }
101
+ else {
102
+ // Use HTTP/SSE transport
103
+ const expressServer = new ExpressServer(serverManager);
104
+ expressServer.start(argv.port, argv.host);
105
+ }
138
106
  }
139
- else {
140
- // Use HTTP/SSE transport
141
- app.listen(PORT, () => {
142
- logger.info(`Server is running on port ${PORT} with HTTP/SSE transport`);
143
- });
107
+ catch (error) {
108
+ logger.error('Server error:', error);
109
+ process.exit(1);
144
110
  }
145
111
  }
146
112
  main().catch((error) => {
@@ -23,31 +23,18 @@ const customFormat = winston.format.combine(winston.format.timestamp(), winston.
23
23
  const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
24
24
  return `${timestamp} [${level.toUpperCase()}] ${message}${metaStr}`;
25
25
  }));
26
+ const consoleFormat = winston.format.combine(winston.format.timestamp(), winston.format.printf(({ timestamp, level, message, ...meta }) => {
27
+ const keys = Object.keys(meta);
28
+ const metaStr = keys.length > 0 ? ` ${keys.map((key) => `${key}=${JSON.stringify(meta[key])}`).join(' ')}` : '';
29
+ return `${timestamp} [${level.toUpperCase()}] message=${JSON.stringify(message)}${metaStr}`;
30
+ }));
26
31
  // Create the logger without the MCP transport initially
27
32
  const logger = winston.createLogger({
28
33
  level: 'info',
29
34
  format: customFormat,
30
- defaultMeta: { service: '1mcp' },
31
35
  transports: [
32
36
  new winston.transports.Console({
33
- format: customFormat,
34
- }),
35
- //
36
- // - Write all logs with importance level of `error` or higher to `error.log`
37
- // (i.e., error, fatal, but not other levels)
38
- //
39
- new winston.transports.File({
40
- filename: path.join(logsDir, 'error.log'),
41
- level: 'error',
42
- format: customFormat,
43
- }),
44
- //
45
- // - Write all logs with importance level of `info` or higher to `combined.log`
46
- // (i.e., fatal, error, warn, and info, but not trace)
47
- //
48
- new winston.transports.File({
49
- filename: path.join(logsDir, 'combined.log'),
50
- format: customFormat,
37
+ format: consoleFormat,
51
38
  }),
52
39
  ],
53
40
  // Prevent logger from exiting on error
@@ -64,7 +51,7 @@ export function addMCPTransport(server, loggerName) {
64
51
  // Add the MCP transport to the logger
65
52
  mcpTransport = new MCPTransport({
66
53
  server,
67
- loggerName: loggerName || '1mcp-agent',
54
+ loggerName: loggerName || '1mcp',
68
55
  level: 'info',
69
56
  });
70
57
  logger.add(mcpTransport);