@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
package/build/constants.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// Server configuration
|
|
5
5
|
export const PORT = 3050;
|
|
6
|
+
export const HOST = '127.0.0.1';
|
|
6
7
|
// API endpoints
|
|
7
8
|
export const SSE_ENDPOINT = '/sse';
|
|
8
9
|
export const MESSAGES_ENDPOINT = '/messages';
|
|
@@ -22,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.
|
|
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
|
|
7
|
+
* @param serverInfo The MCP server instance
|
|
8
8
|
*/
|
|
9
|
-
export function setupClientToServerNotifications(clients,
|
|
9
|
+
export function setupClientToServerNotifications(clients, serverInfo) {
|
|
10
10
|
const clientNotificationSchemas = [
|
|
11
11
|
CancelledNotificationSchema,
|
|
12
12
|
ProgressNotificationSchema,
|
|
@@ -16,11 +16,11 @@ export function setupClientToServerNotifications(clients, server) {
|
|
|
16
16
|
ToolListChangedNotificationSchema,
|
|
17
17
|
PromptListChangedNotificationSchema,
|
|
18
18
|
];
|
|
19
|
-
for (const [name,
|
|
19
|
+
for (const [name, clientInfo] of Object.entries(clients)) {
|
|
20
20
|
clientNotificationSchemas.forEach((schema) => {
|
|
21
|
-
client.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
21
|
+
clientInfo.client.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
22
22
|
logger.info(`Received notification in client: ${name} ${JSON.stringify(notification)}`);
|
|
23
|
-
server.notification({
|
|
23
|
+
serverInfo.server.notification({
|
|
24
24
|
...notification,
|
|
25
25
|
params: {
|
|
26
26
|
...notification.params,
|
|
@@ -34,20 +34,20 @@ export function setupClientToServerNotifications(clients, server) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Sets up server-to-client notification handlers
|
|
36
36
|
* @param clients Record of client instances
|
|
37
|
-
* @param
|
|
37
|
+
* @param serverInfo The MCP server instance
|
|
38
38
|
*/
|
|
39
|
-
export function setupServerToClientNotifications(clients,
|
|
39
|
+
export function setupServerToClientNotifications(clients, serverInfo) {
|
|
40
40
|
const serverNotificationSchemas = [
|
|
41
41
|
CancelledNotificationSchema,
|
|
42
42
|
ProgressNotificationSchema,
|
|
43
43
|
InitializedNotificationSchema,
|
|
44
44
|
RootsListChangedNotificationSchema,
|
|
45
45
|
];
|
|
46
|
-
for (const [name,
|
|
46
|
+
for (const [name, clientInfo] of Object.entries(clients)) {
|
|
47
47
|
serverNotificationSchemas.forEach((schema) => {
|
|
48
|
-
server.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
48
|
+
serverInfo.server.setNotificationHandler(schema, withErrorHandling(async (notification) => {
|
|
49
49
|
logger.info(`Received notification in server: ${name} ${JSON.stringify(notification)}`);
|
|
50
|
-
client.notification({
|
|
50
|
+
clientInfo.client.notification({
|
|
51
51
|
...notification,
|
|
52
52
|
params: {
|
|
53
53
|
...notification.params,
|
|
@@ -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 {
|
|
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(
|
|
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,
|
|
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,
|
|
47
|
+
registerResourceHandlers(clients, serverInfo);
|
|
45
48
|
}
|
|
46
49
|
// Register tool-related handlers if capability is available
|
|
47
50
|
if (capabilities.tools) {
|
|
48
|
-
registerToolHandlers(clients,
|
|
51
|
+
registerToolHandlers(clients, serverInfo);
|
|
49
52
|
}
|
|
50
53
|
// Register prompt-related handlers if capability is available
|
|
51
54
|
if (capabilities.prompts) {
|
|
52
|
-
registerPromptHandlers(clients,
|
|
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
|
|
61
|
+
* @param serverInfo The MCP server instance
|
|
59
62
|
*/
|
|
60
|
-
function registerResourceHandlers(clients,
|
|
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
|
-
|
|
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(
|
|
83
|
-
throw new
|
|
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(
|
|
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
|
-
|
|
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(
|
|
113
|
-
throw new
|
|
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(
|
|
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, (
|
|
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, (
|
|
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, (
|
|
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
|
|
159
|
+
* @param serverInfo The MCP server instance
|
|
141
160
|
*/
|
|
142
|
-
function registerToolHandlers(clients,
|
|
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
|
-
|
|
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(
|
|
164
|
-
throw new
|
|
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(
|
|
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, (
|
|
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
|
|
207
|
+
* @param serverInfo The MCP server instance
|
|
182
208
|
*/
|
|
183
|
-
function registerPromptHandlers(clients,
|
|
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
|
-
|
|
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(
|
|
205
|
-
throw new
|
|
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(
|
|
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, (
|
|
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 {
|
|
8
|
-
import logger
|
|
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
|
|
17
|
+
describe: 'Transport type to use (stdio or sse)',
|
|
18
18
|
type: 'string',
|
|
19
|
-
choices: ['stdio', '
|
|
20
|
-
default: '
|
|
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
|
|
59
|
+
for (const [sessionId, transport] of serverManager.getTransports().entries()) {
|
|
111
60
|
try {
|
|
112
|
-
transport
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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:
|
|
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
|
|
54
|
+
loggerName: loggerName || '1mcp',
|
|
68
55
|
level: 'info',
|
|
69
56
|
});
|
|
70
57
|
logger.add(mcpTransport);
|