it-tools-mcp 4.1.9 → 4.1.15
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 +91 -3
- package/build/index.js +529 -64
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
> **📝 Note**: A condensed version of this README is automatically synced to [Docker Hub](https://hub.docker.com/r/wrenchpilot/it-tools-mcp) due to character limits.
|
|
16
16
|
|
|
17
|
-
A comprehensive Model Context Protocol (MCP) server that provides access to over
|
|
17
|
+
A comprehensive Model Context Protocol (MCP) server that provides access to over 121 IT tools and utilities commonly used by developers, system administrators, and IT professionals. This server exposes a complete set of tools for encoding/decoding, text manipulation, hashing, network utilities, and many other common development and IT tasks.
|
|
18
18
|
|
|
19
19
|
## 📦 Installation & Setup
|
|
20
20
|
|
|
@@ -94,9 +94,97 @@ echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"encode_bas
|
|
|
94
94
|
docker run -i --rm wrenchpilot/it-tools-mcp:latest
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
##
|
|
97
|
+
## � MCP Logging Capabilities
|
|
98
98
|
|
|
99
|
-
This
|
|
99
|
+
This server includes full MCP logging support with the following features:
|
|
100
|
+
|
|
101
|
+
### Log Levels
|
|
102
|
+
|
|
103
|
+
- **debug** (0): Detailed debug information
|
|
104
|
+
- **info** (1): General information messages
|
|
105
|
+
- **notice** (2): Normal but significant events
|
|
106
|
+
- **warning** (3): Warning conditions
|
|
107
|
+
- **error** (4): Error conditions
|
|
108
|
+
- **critical** (5): Critical conditions
|
|
109
|
+
- **alert** (6): Action must be taken immediately
|
|
110
|
+
- **emergency** (7): System is unusable
|
|
111
|
+
|
|
112
|
+
### Logging Tools
|
|
113
|
+
|
|
114
|
+
- **`logging_setLevel`**: Change the minimum logging level at runtime
|
|
115
|
+
- **`logging_status`**: View current logging configuration and available levels
|
|
116
|
+
|
|
117
|
+
### Environment-Aware Behavior
|
|
118
|
+
|
|
119
|
+
- **Development Mode**: Debug level by default, enhanced console output
|
|
120
|
+
- **Production Mode**: Info level by default, clean output for MCP compliance
|
|
121
|
+
- **Automatic Fallback**: Console logging when MCP transport isn't ready
|
|
122
|
+
|
|
123
|
+
### Usage Examples
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Check current logging status
|
|
127
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"logging_status","arguments":{}}}' | npx it-tools-mcp
|
|
128
|
+
|
|
129
|
+
# Change log level to debug
|
|
130
|
+
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"logging_setLevel","arguments":{"level":"debug"}}}' | npx it-tools-mcp
|
|
131
|
+
|
|
132
|
+
# Change log level to error (only show errors and above)
|
|
133
|
+
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"logging_setLevel","arguments":{"level":"error"}}}' | npx it-tools-mcp
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 🔧 MCP Protocol Utilities
|
|
137
|
+
|
|
138
|
+
This server implements the complete MCP 2025-06-18 specification including advanced utilities:
|
|
139
|
+
|
|
140
|
+
### Ping Utility
|
|
141
|
+
|
|
142
|
+
- **Health Checking**: Use `ping` requests to verify connection status
|
|
143
|
+
- **Automatic Response**: Server responds promptly with empty result per MCP spec
|
|
144
|
+
- **Connection Monitoring**: Implement periodic health checks
|
|
145
|
+
|
|
146
|
+
### Progress Tracking
|
|
147
|
+
|
|
148
|
+
- **Long-Running Operations**: Receive progress updates for time-consuming tasks
|
|
149
|
+
- **Progress Tokens**: Include `_meta.progressToken` in requests to enable progress notifications
|
|
150
|
+
- **Notifications**: Server sends `notifications/progress` with current status
|
|
151
|
+
|
|
152
|
+
### Request Cancellation
|
|
153
|
+
|
|
154
|
+
- **Graceful Cancellation**: Send `notifications/cancelled` to abort in-progress requests
|
|
155
|
+
- **Resource Cleanup**: Server properly frees resources when requests are cancelled
|
|
156
|
+
- **Race Condition Handling**: Robust handling of timing edge cases
|
|
157
|
+
|
|
158
|
+
### Sampling Support
|
|
159
|
+
|
|
160
|
+
- **LLM Integration**: Server can request LLM completions from clients using `sampling/createMessage`
|
|
161
|
+
- **Model Preferences**: Support for model selection hints and capability priorities (cost, speed, intelligence)
|
|
162
|
+
- **Content Types**: Support for text, image, and audio content in sampling requests
|
|
163
|
+
- **Agentic Workflows**: Enable AI-powered tool operations through nested LLM calls
|
|
164
|
+
- **Client Control**: Clients maintain full control over model access and user approval
|
|
165
|
+
|
|
166
|
+
### Protocol Examples
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Test connection health with ping
|
|
170
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"ping"}' | npx it-tools-mcp
|
|
171
|
+
|
|
172
|
+
# Request with progress tracking
|
|
173
|
+
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"some_tool","arguments":{},"_meta":{"progressToken":"track123"}}}' | npx it-tools-mcp
|
|
174
|
+
|
|
175
|
+
# Cancel a request (send as notification)
|
|
176
|
+
echo '{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":"2","reason":"User cancelled"}}' | npx it-tools-mcp
|
|
177
|
+
|
|
178
|
+
# Test sampling capabilities
|
|
179
|
+
echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"mcp_sampling_demo","arguments":{"message":"What is the capital of France?","modelPreference":"claude","systemPrompt":"You are a helpful assistant.","maxTokens":100}}}' | npx it-tools-mcp
|
|
180
|
+
|
|
181
|
+
# Demo MCP utilities
|
|
182
|
+
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"mcp_utilities_demo","arguments":{"operation":"ping"}}}' | npx it-tools-mcp
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## �🛠️ Tool Categories
|
|
186
|
+
|
|
187
|
+
This MCP server provides over **121 tools** across **14 categories**:
|
|
100
188
|
|
|
101
189
|
- **� Ansible Tools** (5 tools): Vault encryption/decryption, inventory parser, playbook validator, reference
|
|
102
190
|
- **🎨 Color Tools** (2 tools): Hex ↔ RGB conversion
|
package/build/index.js
CHANGED
|
@@ -6,8 +6,6 @@ import { z } from "zod";
|
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
-
import { exec } from 'child_process';
|
|
10
|
-
import { promisify } from 'util';
|
|
11
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
10
|
const __dirname = path.dirname(__filename);
|
|
13
11
|
/**
|
|
@@ -110,7 +108,7 @@ export function secureToolHandler(handler, identifier = 'default') {
|
|
|
110
108
|
}
|
|
111
109
|
catch (error) {
|
|
112
110
|
// Log error without exposing sensitive information
|
|
113
|
-
|
|
111
|
+
mcpLog('error', `Tool error for ${identifier}`, error instanceof Error ? error.message : 'Unknown error');
|
|
114
112
|
// Return safe error message
|
|
115
113
|
throw new Error('Tool execution failed. Please check your input and try again.');
|
|
116
114
|
}
|
|
@@ -223,10 +221,27 @@ function getPackageMetadata() {
|
|
|
223
221
|
}
|
|
224
222
|
// Create server instance with enhanced metadata and VS Code compliance features
|
|
225
223
|
const packageInfo = getPackageMetadata();
|
|
226
|
-
const execAsync = promisify(exec);
|
|
227
224
|
// Environment detection
|
|
228
225
|
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.MCP_DEV_MODE === 'true';
|
|
229
226
|
const isTest = process.env.NODE_ENV === 'test';
|
|
227
|
+
// MCP Logging Implementation
|
|
228
|
+
const LOG_LEVELS = {
|
|
229
|
+
debug: 0,
|
|
230
|
+
info: 1,
|
|
231
|
+
notice: 2,
|
|
232
|
+
warning: 3,
|
|
233
|
+
error: 4,
|
|
234
|
+
critical: 5,
|
|
235
|
+
alert: 6,
|
|
236
|
+
emergency: 7
|
|
237
|
+
};
|
|
238
|
+
// Current minimum log level (default to info in production, debug in development)
|
|
239
|
+
let currentLogLevel = isDevelopment ? LOG_LEVELS.debug : LOG_LEVELS.info;
|
|
240
|
+
let mcpTransportReady = false;
|
|
241
|
+
// Progress tracking
|
|
242
|
+
const activeProgressTokens = new Map();
|
|
243
|
+
// Request cancellation tracking
|
|
244
|
+
const activeRequests = new Map();
|
|
230
245
|
const server = new McpServer({
|
|
231
246
|
name: "it-tools-mcp",
|
|
232
247
|
version: packageInfo.version,
|
|
@@ -237,15 +252,473 @@ const server = new McpServer({
|
|
|
237
252
|
license: packageInfo.license,
|
|
238
253
|
}, {
|
|
239
254
|
capabilities: {
|
|
240
|
-
tools: {
|
|
241
|
-
|
|
242
|
-
|
|
255
|
+
tools: {
|
|
256
|
+
listChanged: true
|
|
257
|
+
},
|
|
258
|
+
resources: {
|
|
259
|
+
listChanged: true
|
|
260
|
+
},
|
|
261
|
+
prompts: {
|
|
262
|
+
listChanged: true
|
|
263
|
+
},
|
|
243
264
|
sampling: {},
|
|
244
265
|
roots: {
|
|
245
266
|
listChanged: true
|
|
267
|
+
},
|
|
268
|
+
logging: {},
|
|
269
|
+
completions: {}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
// MCP Logging Functions
|
|
273
|
+
function mcpLog(level, message, data) {
|
|
274
|
+
const levelValue = LOG_LEVELS[level];
|
|
275
|
+
// Only send if level meets minimum threshold
|
|
276
|
+
if (levelValue >= currentLogLevel && mcpTransportReady) {
|
|
277
|
+
try {
|
|
278
|
+
// Send MCP logging notification (using the transport directly)
|
|
279
|
+
// Note: The MCP SDK may handle this differently - this is a placeholder for the proper implementation
|
|
246
280
|
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
// Fallback to console if MCP notification fails
|
|
283
|
+
console.error(`[${level.toUpperCase()}] ${message}`, data || '');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Also log to console for development/debugging
|
|
287
|
+
if (isDevelopment || level === 'error' || level === 'critical' || level === 'emergency') {
|
|
288
|
+
console.error(`[${level.toUpperCase()}] ${message}`, data || '');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Register logging/setLevel handler using the tool registration pattern
|
|
292
|
+
server.registerTool("logging_setLevel", {
|
|
293
|
+
description: "Set the minimum logging level for MCP logging notifications. Available levels: debug, info, notice, warning, error, critical, alert, emergency",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
level: z.enum(['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']).describe("The minimum log level to report")
|
|
296
|
+
}
|
|
297
|
+
}, async (args) => {
|
|
298
|
+
const { level } = args;
|
|
299
|
+
const oldLevelName = Object.keys(LOG_LEVELS).find(k => LOG_LEVELS[k] === currentLogLevel);
|
|
300
|
+
currentLogLevel = LOG_LEVELS[level];
|
|
301
|
+
mcpLog('info', `Log level changed from ${oldLevelName} to ${level}`);
|
|
302
|
+
return {
|
|
303
|
+
content: [{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: JSON.stringify({
|
|
306
|
+
success: true,
|
|
307
|
+
previousLevel: oldLevelName,
|
|
308
|
+
newLevel: level,
|
|
309
|
+
message: `Logging level set to ${level}`,
|
|
310
|
+
availableLevels: Object.keys(LOG_LEVELS),
|
|
311
|
+
currentLevelValue: currentLogLevel
|
|
312
|
+
}, null, 2)
|
|
313
|
+
}]
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
// Add logging status tool
|
|
317
|
+
server.registerTool("logging_status", {
|
|
318
|
+
description: "Get current MCP logging configuration and status",
|
|
319
|
+
inputSchema: {}
|
|
320
|
+
}, async (args) => {
|
|
321
|
+
const currentLevelName = Object.keys(LOG_LEVELS).find(k => LOG_LEVELS[k] === currentLogLevel);
|
|
322
|
+
return {
|
|
323
|
+
content: [{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: JSON.stringify({
|
|
326
|
+
currentLevel: currentLevelName,
|
|
327
|
+
currentLevelValue: currentLogLevel,
|
|
328
|
+
transportReady: mcpTransportReady,
|
|
329
|
+
environment: isDevelopment ? 'development' : 'production',
|
|
330
|
+
availableLevels: Object.entries(LOG_LEVELS).map(([name, value]) => ({
|
|
331
|
+
name,
|
|
332
|
+
value,
|
|
333
|
+
active: value >= currentLogLevel
|
|
334
|
+
})),
|
|
335
|
+
logLevelDescriptions: {
|
|
336
|
+
debug: "Detailed debug information (level 0)",
|
|
337
|
+
info: "General information messages (level 1)",
|
|
338
|
+
notice: "Normal but significant events (level 2)",
|
|
339
|
+
warning: "Warning conditions (level 3)",
|
|
340
|
+
error: "Error conditions (level 4)",
|
|
341
|
+
critical: "Critical conditions (level 5)",
|
|
342
|
+
alert: "Action must be taken immediately (level 6)",
|
|
343
|
+
emergency: "System is unusable (level 7)"
|
|
344
|
+
}
|
|
345
|
+
}, null, 2)
|
|
346
|
+
}]
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
// MCP Utilities Implementation
|
|
350
|
+
// Ping utility - connection health check
|
|
351
|
+
server.server.setRequestHandler(z.object({ method: z.literal("ping") }), async () => {
|
|
352
|
+
mcpLog('debug', 'Ping request received');
|
|
353
|
+
return {}; // Empty response as per spec
|
|
354
|
+
});
|
|
355
|
+
// Progress notification function
|
|
356
|
+
function sendProgressNotification(progressToken, progress, total, message) {
|
|
357
|
+
if (!mcpTransportReady || !activeProgressTokens.has(progressToken)) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
server.server.notification({
|
|
362
|
+
method: "notifications/progress",
|
|
363
|
+
params: {
|
|
364
|
+
progressToken,
|
|
365
|
+
progress,
|
|
366
|
+
...(total !== undefined && { total }),
|
|
367
|
+
...(message && { message })
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
mcpLog('debug', `Progress notification sent for token ${progressToken}: ${progress}${total ? `/${total}` : ''}`);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
mcpLog('warning', `Failed to send progress notification for token ${progressToken}`, error instanceof Error ? error.message : 'Unknown error');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Cancellation notification handler
|
|
377
|
+
server.server.setNotificationHandler(z.object({
|
|
378
|
+
method: z.literal("notifications/cancelled"),
|
|
379
|
+
params: z.object({
|
|
380
|
+
requestId: z.union([z.string(), z.number()]),
|
|
381
|
+
reason: z.string().optional()
|
|
382
|
+
})
|
|
383
|
+
}), async (notification) => {
|
|
384
|
+
const { requestId, reason } = notification.params;
|
|
385
|
+
mcpLog('info', `Cancellation requested for request ${requestId}`, reason ? { reason } : undefined);
|
|
386
|
+
const activeRequest = activeRequests.get(requestId);
|
|
387
|
+
if (activeRequest) {
|
|
388
|
+
// Cancel the request using AbortController
|
|
389
|
+
activeRequest.abortController.abort(reason || 'Request cancelled');
|
|
390
|
+
activeRequests.delete(requestId);
|
|
391
|
+
mcpLog('info', `Request ${requestId} cancelled successfully`);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
mcpLog('debug', `Cancellation notification for unknown or completed request: ${requestId}`);
|
|
247
395
|
}
|
|
248
396
|
});
|
|
397
|
+
// Helper function to extract progress token from request metadata
|
|
398
|
+
function extractProgressToken(params) {
|
|
399
|
+
return params?._meta?.progressToken;
|
|
400
|
+
}
|
|
401
|
+
// Helper function to register active request for cancellation support
|
|
402
|
+
function registerActiveRequest(requestId) {
|
|
403
|
+
const abortController = new AbortController();
|
|
404
|
+
activeRequests.set(requestId, {
|
|
405
|
+
abortController,
|
|
406
|
+
startTime: Date.now()
|
|
407
|
+
});
|
|
408
|
+
return abortController;
|
|
409
|
+
}
|
|
410
|
+
// Helper function to unregister active request
|
|
411
|
+
function unregisterActiveRequest(requestId) {
|
|
412
|
+
activeRequests.delete(requestId);
|
|
413
|
+
}
|
|
414
|
+
// Enhanced tool handler with cancellation and progress support
|
|
415
|
+
export function mcpToolHandler(handler, identifier = 'default') {
|
|
416
|
+
return async (params, extra) => {
|
|
417
|
+
// Rate limiting check
|
|
418
|
+
if (!rateLimiter.isAllowed(identifier)) {
|
|
419
|
+
throw new Error('Rate limit exceeded. Please try again later.');
|
|
420
|
+
}
|
|
421
|
+
// Extract progress token if provided
|
|
422
|
+
const progressToken = extractProgressToken(params);
|
|
423
|
+
if (progressToken) {
|
|
424
|
+
activeProgressTokens.set(progressToken, true);
|
|
425
|
+
}
|
|
426
|
+
// Register request for cancellation if requestId provided
|
|
427
|
+
let abortController;
|
|
428
|
+
if (extra?.requestId) {
|
|
429
|
+
abortController = registerActiveRequest(extra.requestId);
|
|
430
|
+
}
|
|
431
|
+
// Create combined abort signal
|
|
432
|
+
const signals = [extra?.signal, abortController?.signal].filter(Boolean);
|
|
433
|
+
let combinedSignal;
|
|
434
|
+
if (signals.length > 0) {
|
|
435
|
+
const combinedController = new AbortController();
|
|
436
|
+
combinedSignal = combinedController.signal;
|
|
437
|
+
signals.forEach(signal => {
|
|
438
|
+
if (signal.aborted) {
|
|
439
|
+
combinedController.abort(signal.reason);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
signal.addEventListener('abort', () => {
|
|
443
|
+
combinedController.abort(signal.reason);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
// Progress callback
|
|
449
|
+
const progressCallback = progressToken
|
|
450
|
+
? (progress, total, message) => {
|
|
451
|
+
sendProgressNotification(progressToken, progress, total, message);
|
|
452
|
+
}
|
|
453
|
+
: undefined;
|
|
454
|
+
try {
|
|
455
|
+
const result = await handler(params, {
|
|
456
|
+
signal: combinedSignal,
|
|
457
|
+
progressCallback
|
|
458
|
+
});
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
// Log error without exposing sensitive information
|
|
463
|
+
mcpLog('error', `Tool error for ${identifier}`, error instanceof Error ? error.message : 'Unknown error');
|
|
464
|
+
// Return safe error message
|
|
465
|
+
throw new Error('Tool execution failed. Please check your input and try again.');
|
|
466
|
+
}
|
|
467
|
+
finally {
|
|
468
|
+
// Cleanup
|
|
469
|
+
if (progressToken) {
|
|
470
|
+
activeProgressTokens.delete(progressToken);
|
|
471
|
+
}
|
|
472
|
+
if (extra?.requestId) {
|
|
473
|
+
unregisterActiveRequest(extra.requestId);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
// Demo tools for MCP utilities
|
|
479
|
+
server.registerTool("mcp_utilities_demo", {
|
|
480
|
+
description: "Demonstrate MCP utilities: ping, progress tracking, and cancellation support",
|
|
481
|
+
inputSchema: {
|
|
482
|
+
operation: z.enum(['ping', 'long_task', 'cancellable_task']).describe("The MCP utility operation to demonstrate"),
|
|
483
|
+
duration: z.number().optional().describe("Duration in seconds for long-running tasks (default: 10)"),
|
|
484
|
+
steps: z.number().optional().describe("Number of progress steps for demonstrating progress tracking (default: 5)")
|
|
485
|
+
}
|
|
486
|
+
}, async (args) => {
|
|
487
|
+
const { operation, duration = 10, steps = 5 } = args;
|
|
488
|
+
if (operation === 'ping') {
|
|
489
|
+
return {
|
|
490
|
+
content: [{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: JSON.stringify({
|
|
493
|
+
operation: 'ping',
|
|
494
|
+
status: 'success',
|
|
495
|
+
message: 'MCP ping utility is working correctly',
|
|
496
|
+
timestamp: new Date().toISOString(),
|
|
497
|
+
usage: 'Send a "ping" request to test connection health'
|
|
498
|
+
}, null, 2)
|
|
499
|
+
}]
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (operation === 'long_task') {
|
|
503
|
+
// Simulate a long-running task with progress updates
|
|
504
|
+
const totalMs = duration * 1000;
|
|
505
|
+
const stepMs = totalMs / steps;
|
|
506
|
+
return {
|
|
507
|
+
content: [{
|
|
508
|
+
type: "text",
|
|
509
|
+
text: JSON.stringify({
|
|
510
|
+
operation: 'long_task',
|
|
511
|
+
status: 'completed',
|
|
512
|
+
message: `Simulated ${duration}s task with ${steps} progress updates`,
|
|
513
|
+
note: 'Use _meta.progressToken in your request to receive progress notifications',
|
|
514
|
+
example: {
|
|
515
|
+
request: {
|
|
516
|
+
jsonrpc: "2.0",
|
|
517
|
+
id: 1,
|
|
518
|
+
method: "tools/call",
|
|
519
|
+
params: {
|
|
520
|
+
name: "mcp_utilities_demo",
|
|
521
|
+
arguments: { operation: "long_task", duration: 5, steps: 3 },
|
|
522
|
+
_meta: { progressToken: "demo123" }
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}, null, 2)
|
|
527
|
+
}]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
if (operation === 'cancellable_task') {
|
|
531
|
+
return {
|
|
532
|
+
content: [{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: JSON.stringify({
|
|
535
|
+
operation: 'cancellable_task',
|
|
536
|
+
status: 'completed',
|
|
537
|
+
message: 'Simulated cancellable task',
|
|
538
|
+
note: 'Send a notifications/cancelled message to cancel in-progress requests',
|
|
539
|
+
example: {
|
|
540
|
+
cancel_notification: {
|
|
541
|
+
jsonrpc: "2.0",
|
|
542
|
+
method: "notifications/cancelled",
|
|
543
|
+
params: {
|
|
544
|
+
requestId: "your_request_id",
|
|
545
|
+
reason: "User requested cancellation"
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}, null, 2)
|
|
550
|
+
}]
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
content: [{
|
|
555
|
+
type: "text",
|
|
556
|
+
text: JSON.stringify({ error: 'Unknown operation' }, null, 2)
|
|
557
|
+
}]
|
|
558
|
+
};
|
|
559
|
+
});
|
|
560
|
+
// Sampling demo tool
|
|
561
|
+
server.registerTool("mcp_sampling_demo", {
|
|
562
|
+
description: "Demonstrate MCP sampling capabilities and test sampling/createMessage requests",
|
|
563
|
+
inputSchema: {
|
|
564
|
+
message: z.string().describe("The message to send in the sampling request"),
|
|
565
|
+
modelPreference: z.enum(['claude', 'gpt', 'gemini', 'generic']).optional().describe("Preferred model family for demonstration"),
|
|
566
|
+
systemPrompt: z.string().optional().describe("System prompt to include in the sampling request"),
|
|
567
|
+
maxTokens: z.number().positive().optional().describe("Maximum tokens for the response (default: 100)"),
|
|
568
|
+
intelligence: z.number().min(0).max(1).optional().describe("Intelligence priority (0-1, higher = more capable models)"),
|
|
569
|
+
speed: z.number().min(0).max(1).optional().describe("Speed priority (0-1, higher = faster models)"),
|
|
570
|
+
cost: z.number().min(0).max(1).optional().describe("Cost priority (0-1, higher = cheaper models)")
|
|
571
|
+
}
|
|
572
|
+
}, async (args) => {
|
|
573
|
+
const { message, modelPreference, systemPrompt, maxTokens = 100, intelligence = 0.7, speed = 0.5, cost = 0.3 } = args;
|
|
574
|
+
// Build model preferences based on user input
|
|
575
|
+
const modelPreferences = {
|
|
576
|
+
intelligencePriority: intelligence,
|
|
577
|
+
speedPriority: speed,
|
|
578
|
+
costPriority: cost
|
|
579
|
+
};
|
|
580
|
+
// Add model hints based on preference
|
|
581
|
+
if (modelPreference) {
|
|
582
|
+
const hintMap = {
|
|
583
|
+
'claude': [{ name: 'claude-4-sonnet' }, { name: 'claude' }],
|
|
584
|
+
'gpt': [{ name: 'gpt-4' }, { name: 'gpt' }],
|
|
585
|
+
'gemini': [{ name: 'gemini-1.5-pro' }, { name: 'gemini' }],
|
|
586
|
+
'generic': [{ name: 'general-purpose' }]
|
|
587
|
+
};
|
|
588
|
+
modelPreferences.hints = hintMap[modelPreference];
|
|
589
|
+
}
|
|
590
|
+
// Create the sampling request
|
|
591
|
+
const samplingRequest = {
|
|
592
|
+
method: "sampling/createMessage",
|
|
593
|
+
params: {
|
|
594
|
+
messages: [
|
|
595
|
+
{
|
|
596
|
+
role: "user",
|
|
597
|
+
content: {
|
|
598
|
+
type: "text",
|
|
599
|
+
text: message
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
],
|
|
603
|
+
modelPreferences,
|
|
604
|
+
...(systemPrompt && { systemPrompt }),
|
|
605
|
+
maxTokens
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
return {
|
|
609
|
+
content: [{
|
|
610
|
+
type: "text",
|
|
611
|
+
text: JSON.stringify({
|
|
612
|
+
demo: 'MCP Sampling Protocol Demonstration',
|
|
613
|
+
status: 'request_prepared',
|
|
614
|
+
message: 'Here is the sampling request that would be sent to the MCP client',
|
|
615
|
+
request: samplingRequest,
|
|
616
|
+
explanation: {
|
|
617
|
+
protocol: 'MCP 2025-06-18 sampling/createMessage',
|
|
618
|
+
purpose: 'This demonstrates how servers can request LLM completions from clients',
|
|
619
|
+
modelSelection: modelPreferences.hints ?
|
|
620
|
+
`Prefers ${modelPreference} models with intelligence=${intelligence}, speed=${speed}, cost=${cost}` :
|
|
621
|
+
`No specific model preference, using priorities: intelligence=${intelligence}, speed=${speed}, cost=${cost}`,
|
|
622
|
+
flow: [
|
|
623
|
+
'1. Server sends sampling/createMessage request to client',
|
|
624
|
+
'2. Client selects appropriate model based on preferences',
|
|
625
|
+
'3. Client processes the message through the selected LLM',
|
|
626
|
+
'4. Client returns the LLM response to the server',
|
|
627
|
+
'5. Server can use the response for its tool operations'
|
|
628
|
+
],
|
|
629
|
+
security: 'Clients SHOULD implement user approval controls for sampling requests'
|
|
630
|
+
},
|
|
631
|
+
nextSteps: 'In production, this request would be sent to the MCP client for actual LLM processing'
|
|
632
|
+
}, null, 2)
|
|
633
|
+
}]
|
|
634
|
+
};
|
|
635
|
+
});
|
|
636
|
+
// MCP Sampling Implementation - Server-side LLM request handling
|
|
637
|
+
server.server.setRequestHandler(z.object({
|
|
638
|
+
method: z.literal("sampling/createMessage"),
|
|
639
|
+
params: z.object({
|
|
640
|
+
messages: z.array(z.object({
|
|
641
|
+
role: z.enum(["user", "assistant", "system"]),
|
|
642
|
+
content: z.union([
|
|
643
|
+
z.object({
|
|
644
|
+
type: z.literal("text"),
|
|
645
|
+
text: z.string()
|
|
646
|
+
}),
|
|
647
|
+
z.object({
|
|
648
|
+
type: z.literal("image"),
|
|
649
|
+
data: z.string(),
|
|
650
|
+
mimeType: z.string()
|
|
651
|
+
}),
|
|
652
|
+
z.object({
|
|
653
|
+
type: z.literal("audio"),
|
|
654
|
+
data: z.string(),
|
|
655
|
+
mimeType: z.string()
|
|
656
|
+
})
|
|
657
|
+
])
|
|
658
|
+
})),
|
|
659
|
+
modelPreferences: z.object({
|
|
660
|
+
hints: z.array(z.object({
|
|
661
|
+
name: z.string()
|
|
662
|
+
})).optional(),
|
|
663
|
+
costPriority: z.number().min(0).max(1).optional(),
|
|
664
|
+
speedPriority: z.number().min(0).max(1).optional(),
|
|
665
|
+
intelligencePriority: z.number().min(0).max(1).optional()
|
|
666
|
+
}).optional(),
|
|
667
|
+
systemPrompt: z.string().optional(),
|
|
668
|
+
maxTokens: z.number().positive().optional(),
|
|
669
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
670
|
+
stopSequences: z.array(z.string()).optional(),
|
|
671
|
+
metadata: z.record(z.any()).optional()
|
|
672
|
+
})
|
|
673
|
+
}), async (request) => {
|
|
674
|
+
const { messages, modelPreferences, systemPrompt, maxTokens, temperature, stopSequences, metadata } = request.params;
|
|
675
|
+
mcpLog('info', 'Sampling request received', {
|
|
676
|
+
messageCount: messages.length,
|
|
677
|
+
modelPreferences: modelPreferences ? Object.keys(modelPreferences) : undefined,
|
|
678
|
+
hasSystemPrompt: !!systemPrompt,
|
|
679
|
+
maxTokens
|
|
680
|
+
});
|
|
681
|
+
// In a real implementation, this would:
|
|
682
|
+
// 1. Forward the request to the client's LLM service
|
|
683
|
+
// 2. Apply model preferences and selection logic
|
|
684
|
+
// 3. Handle different content types (text, image, audio)
|
|
685
|
+
// 4. Return the LLM response
|
|
686
|
+
// For this MCP server implementation, we return a helpful response
|
|
687
|
+
// explaining that this is a demonstration of the sampling protocol
|
|
688
|
+
// and that the actual LLM processing would be handled by the client
|
|
689
|
+
const demoResponse = {
|
|
690
|
+
role: "assistant",
|
|
691
|
+
content: {
|
|
692
|
+
type: "text",
|
|
693
|
+
text: `This is a demonstration of MCP sampling protocol support.
|
|
694
|
+
|
|
695
|
+
In a production environment, this request would be forwarded to an LLM service based on your model preferences:
|
|
696
|
+
${modelPreferences?.hints?.length ? `- Preferred models: ${modelPreferences.hints.map(h => h.name).join(', ')}` : '- No specific model preferences'}
|
|
697
|
+
${modelPreferences?.intelligencePriority ? `- Intelligence priority: ${modelPreferences.intelligencePriority}` : ''}
|
|
698
|
+
${modelPreferences?.speedPriority ? `- Speed priority: ${modelPreferences.speedPriority}` : ''}
|
|
699
|
+
${modelPreferences?.costPriority ? `- Cost priority: ${modelPreferences.costPriority}` : ''}
|
|
700
|
+
|
|
701
|
+
Your message: "${messages[messages.length - 1]?.content?.type === 'text' ? messages[messages.length - 1].content.text : 'Non-text content'}"
|
|
702
|
+
|
|
703
|
+
${systemPrompt ? `System prompt: "${systemPrompt}"` : 'No system prompt provided'}
|
|
704
|
+
${maxTokens ? `Max tokens: ${maxTokens}` : 'No token limit specified'}
|
|
705
|
+
|
|
706
|
+
This server supports the full MCP 2025-06-18 sampling specification and is ready for production use with proper LLM integration.`
|
|
707
|
+
},
|
|
708
|
+
model: "mcp-demo-server",
|
|
709
|
+
stopReason: "endTurn",
|
|
710
|
+
usage: {
|
|
711
|
+
inputTokens: messages.reduce((sum, msg) => sum + (msg.content.type === 'text' ? msg.content.text.length / 4 : 100), 0),
|
|
712
|
+
outputTokens: 150
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
mcpLog('debug', 'Sampling response generated', {
|
|
716
|
+
model: demoResponse.model,
|
|
717
|
+
stopReason: demoResponse.stopReason,
|
|
718
|
+
outputTokens: demoResponse.usage.outputTokens
|
|
719
|
+
});
|
|
720
|
+
return demoResponse;
|
|
721
|
+
});
|
|
249
722
|
// VS Code MCP Compliance: Implement Resources
|
|
250
723
|
server.registerResource("server-manifest", new ResourceTemplate("manifest://{type}", {
|
|
251
724
|
list: async () => ({
|
|
@@ -484,7 +957,7 @@ async function loadModularTools(server, category) {
|
|
|
484
957
|
const toolsDir = path.join(__dirname, 'tools', category);
|
|
485
958
|
if (!fs.existsSync(toolsDir)) {
|
|
486
959
|
if (isDevelopment) {
|
|
487
|
-
|
|
960
|
+
mcpLog('warning', `Category directory does not exist: ${toolsDir}`);
|
|
488
961
|
}
|
|
489
962
|
return;
|
|
490
963
|
}
|
|
@@ -503,30 +976,30 @@ async function loadModularTools(server, category) {
|
|
|
503
976
|
registerFunction(server);
|
|
504
977
|
// Only log tool loading in development mode
|
|
505
978
|
if (isDevelopment) {
|
|
506
|
-
|
|
979
|
+
mcpLog('debug', `Loaded tool: ${category}/${toolDir}`);
|
|
507
980
|
}
|
|
508
981
|
}
|
|
509
982
|
catch (regError) {
|
|
510
983
|
// Always log errors
|
|
511
|
-
|
|
984
|
+
mcpLog('error', `Failed to register tool ${category}/${toolDir}`, regError instanceof Error ? regError.message : 'Unknown registration error');
|
|
512
985
|
}
|
|
513
986
|
}
|
|
514
987
|
else {
|
|
515
988
|
// Only warn in development mode
|
|
516
989
|
if (isDevelopment) {
|
|
517
|
-
|
|
990
|
+
mcpLog('warning', `No register function found in ${toolPath}`);
|
|
518
991
|
}
|
|
519
992
|
}
|
|
520
993
|
}
|
|
521
994
|
catch (error) {
|
|
522
995
|
// Always log errors
|
|
523
|
-
|
|
996
|
+
mcpLog('error', `Failed to load tool ${category}/${toolDir}`, error instanceof Error ? error.message : 'Unknown error');
|
|
524
997
|
}
|
|
525
998
|
}
|
|
526
999
|
else {
|
|
527
1000
|
// Only warn in development mode
|
|
528
1001
|
if (isDevelopment) {
|
|
529
|
-
|
|
1002
|
+
mcpLog('warning', `Tool index file does not exist: ${toolPath}`);
|
|
530
1003
|
}
|
|
531
1004
|
}
|
|
532
1005
|
}
|
|
@@ -622,7 +1095,7 @@ async function registerAllTools(server) {
|
|
|
622
1095
|
const toolsBaseDir = path.join(__dirname, 'tools');
|
|
623
1096
|
if (!fs.existsSync(toolsBaseDir)) {
|
|
624
1097
|
if (isDevelopment) {
|
|
625
|
-
|
|
1098
|
+
mcpLog('warning', 'Tools directory does not exist', toolsBaseDir);
|
|
626
1099
|
}
|
|
627
1100
|
return;
|
|
628
1101
|
}
|
|
@@ -805,77 +1278,71 @@ async function main() {
|
|
|
805
1278
|
try {
|
|
806
1279
|
// VS Code MCP Compliance: Dev Mode Support
|
|
807
1280
|
const isTest = process.env.NODE_ENV === 'test' && process.env.MCP_TEST_MODE === 'true';
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
1281
|
+
mcpLog('info', 'Starting IT Tools MCP Server', {
|
|
1282
|
+
version: packageInfo.version,
|
|
1283
|
+
environment: isDevelopment ? 'development' : 'production',
|
|
1284
|
+
nodeVersion: process.version
|
|
1285
|
+
});
|
|
814
1286
|
// Add error handling for unhandled rejections
|
|
815
1287
|
process.on('unhandledRejection', (reason, promise) => {
|
|
816
|
-
|
|
1288
|
+
// Only log to stderr in development or for critical errors
|
|
1289
|
+
if (isDevelopment) {
|
|
1290
|
+
mcpLog('error', 'Unhandled Rejection', { promise: promise.toString(), reason });
|
|
1291
|
+
}
|
|
817
1292
|
});
|
|
818
1293
|
process.on('uncaughtException', (error) => {
|
|
819
|
-
|
|
1294
|
+
mcpLog('critical', 'Uncaught Exception', error.message);
|
|
820
1295
|
process.exit(1);
|
|
821
1296
|
});
|
|
1297
|
+
// Register tools and connect
|
|
1298
|
+
mcpLog('debug', 'Registering tools...');
|
|
1299
|
+
const startTime = Date.now();
|
|
822
1300
|
await registerAllTools(server);
|
|
1301
|
+
const toolLoadTime = Date.now() - startTime;
|
|
1302
|
+
const { totalToolCount, toolCategories } = await discoverTools();
|
|
1303
|
+
mcpLog('info', 'Tools registered successfully', {
|
|
1304
|
+
totalTools: totalToolCount,
|
|
1305
|
+
categories: Object.keys(toolCategories).length,
|
|
1306
|
+
loadTimeMs: toolLoadTime
|
|
1307
|
+
});
|
|
1308
|
+
mcpLog('debug', 'Connecting to MCP transport...');
|
|
823
1309
|
const transport = new StdioServerTransport();
|
|
824
1310
|
await server.connect(transport);
|
|
825
|
-
//
|
|
1311
|
+
// Mark MCP transport as ready for logging
|
|
1312
|
+
mcpTransportReady = true;
|
|
1313
|
+
mcpLog('info', 'MCP Server started successfully', {
|
|
1314
|
+
transport: 'stdio',
|
|
1315
|
+
ready: true
|
|
1316
|
+
});
|
|
1317
|
+
// Exit handler for test automation
|
|
826
1318
|
if (isTest) {
|
|
827
|
-
|
|
828
|
-
// Exit after stdin closes (for test automation)
|
|
1319
|
+
mcpLog('debug', 'Test mode: Setting up exit handler');
|
|
829
1320
|
process.stdin.on('end', () => {
|
|
1321
|
+
mcpLog('debug', 'Test mode: stdin ended, exiting...');
|
|
830
1322
|
setTimeout(() => process.exit(0), 100);
|
|
831
1323
|
});
|
|
832
1324
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
console.error(`🔗 Protocol: Model Context Protocol (MCP) via stdio`);
|
|
837
|
-
console.error(`📦 Version: ${packageInfo.version}`);
|
|
838
|
-
}
|
|
839
|
-
else {
|
|
840
|
-
// Production mode - simple ready message
|
|
841
|
-
console.error(`IT Tools MCP Server v${packageInfo.version} ready - ${await getToolCount()} tools loaded`);
|
|
842
|
-
}
|
|
843
|
-
// Enhanced monitoring in development mode
|
|
844
|
-
if (isDevelopment && !isTest) {
|
|
845
|
-
// More frequent monitoring in dev mode (every minute)
|
|
1325
|
+
// Production monitoring (every 5 minutes) - no logging unless critical
|
|
1326
|
+
if (!isTest) {
|
|
1327
|
+
mcpLog('debug', 'Setting up production monitoring');
|
|
846
1328
|
setInterval(() => {
|
|
847
1329
|
const usage = getResourceUsage();
|
|
848
1330
|
if (usage.memory.heapUsedBytes > 200 * 1024 * 1024) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
// Log periodic status in dev mode
|
|
852
|
-
console.error(`📈 Status: Memory ${usage.memory.heapUsed}, CPU ${usage.cpu.user}ms user, ${usage.cpu.system}ms system`);
|
|
853
|
-
}, 60 * 1000); // Every minute in dev mode
|
|
854
|
-
}
|
|
855
|
-
else if (!isTest) {
|
|
856
|
-
// Production monitoring (every 5 minutes)
|
|
857
|
-
setInterval(() => {
|
|
858
|
-
const usage = getResourceUsage();
|
|
859
|
-
if (usage.memory.heapUsedBytes > 200 * 1024 * 1024) {
|
|
860
|
-
console.error("High memory usage detected:", usage.memory);
|
|
1331
|
+
// Critical memory issues
|
|
1332
|
+
mcpLog('critical', 'High memory usage detected', usage.memory);
|
|
861
1333
|
}
|
|
862
1334
|
}, 5 * 60 * 1000);
|
|
863
1335
|
}
|
|
864
1336
|
// Handle graceful shutdown
|
|
865
1337
|
const shutdown = () => {
|
|
866
|
-
|
|
867
|
-
console.error("🛑 Shutting down IT Tools MCP Server (Development Mode)...");
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
console.error("Shutting down IT Tools MCP Server...");
|
|
871
|
-
}
|
|
1338
|
+
mcpLog('info', 'Graceful shutdown initiated');
|
|
872
1339
|
process.exit(0);
|
|
873
1340
|
};
|
|
874
1341
|
process.on('SIGINT', shutdown);
|
|
875
1342
|
process.on('SIGTERM', shutdown);
|
|
876
1343
|
}
|
|
877
1344
|
catch (error) {
|
|
878
|
-
|
|
1345
|
+
mcpLog('emergency', 'Fatal error starting MCP server', error instanceof Error ? error.message : 'Unknown error');
|
|
879
1346
|
process.exit(1);
|
|
880
1347
|
}
|
|
881
1348
|
}
|
|
@@ -916,10 +1383,8 @@ function extractReadmeSection(content, heading) {
|
|
|
916
1383
|
: lines.slice(startIndex, endIndex);
|
|
917
1384
|
return sectionLines.join('\n');
|
|
918
1385
|
}
|
|
919
|
-
// Start the server
|
|
920
|
-
|
|
921
|
-
main()
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
});
|
|
925
|
-
}
|
|
1386
|
+
// Start the server
|
|
1387
|
+
main().catch((error) => {
|
|
1388
|
+
console.error("Fatal error in main():", error);
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
});
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "it-tools-mcp",
|
|
3
|
-
"version": "4.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.1.15",
|
|
4
|
+
"description": "Full MCP 2025-06-18 compliant server with 121+ IT tools, logging, ping, progress tracking, cancellation, and sampling utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"it-tools-mcp": "build/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsc && chmod +x build/index.js",
|
|
11
|
+
"build": "npm run sync:manifest || true && tsc && chmod +x build/index.js",
|
|
12
|
+
"build:docker": "tsc && chmod +x build/index.js",
|
|
12
13
|
"start": "docker-compose up --build",
|
|
13
14
|
"start:node": "node build/index.js",
|
|
14
15
|
"dev": "NODE_ENV=development MCP_DEV_MODE=true tsc && node build/index.js",
|
|
@@ -65,14 +66,16 @@
|
|
|
65
66
|
"node": ">=18.0.0"
|
|
66
67
|
},
|
|
67
68
|
"mcp": {
|
|
68
|
-
"mcpVersion": "
|
|
69
|
+
"mcpVersion": "2025-06-18",
|
|
69
70
|
"transport": "stdio",
|
|
70
71
|
"capabilities": [
|
|
71
72
|
"tools",
|
|
72
73
|
"resources",
|
|
73
74
|
"prompts",
|
|
75
|
+
"completions",
|
|
74
76
|
"sampling",
|
|
75
|
-
"roots"
|
|
77
|
+
"roots",
|
|
78
|
+
"logging"
|
|
76
79
|
],
|
|
77
80
|
"toolCount": 116,
|
|
78
81
|
"categories": [
|
|
@@ -111,7 +114,7 @@
|
|
|
111
114
|
},
|
|
112
115
|
"dependencies": {
|
|
113
116
|
"@iarna/toml": "^2.2.5",
|
|
114
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
117
|
+
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
115
118
|
"@types/js-yaml": "^4.0.9",
|
|
116
119
|
"@types/papaparse": "^5.3.16",
|
|
117
120
|
"@types/qrcode": "^1.5.5",
|