Skip to content

Commit b4715b0

Browse files
groeimetaiclaude
andcommitted
fix(mcp): redirect all stdout logs to stderr for MCP protocol compliance
This fix ensures the servicenow-mcp-unified MCP server complies with the MCP protocol specification by writing all log messages to stderr instead of stdout. This prevents JSON-RPC communication corruption that was breaking Claude Desktop integration. Files changed: - shared/tool-registry.ts: initialization statistics - server.ts: tool discovery and server statistics output - index.ts: SIGINT/SIGTERM handlers - shared/permission-validator.ts: permission logging - ML tools: training epoch logs - snow_create_rest_message.ts: fallback log Fixes: stdout pollution causing "No number after minus sign in JSON" and "Unexpected token" errors in Claude Desktop. Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 3ebf48b commit b4715b0

8 files changed

Lines changed: 28 additions & 28 deletions

File tree

‎packages/core/src/mcp/servicenow-mcp-unified/index.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ async function main() {
5353

5454
// Graceful shutdown handlers
5555
process.on('SIGINT', async () => {
56-
console.log('\n[Main] Received SIGINT, shutting down gracefully...');
56+
console.error('\n[Main] Received SIGINT, shutting down gracefully...');
5757
await server.stop();
5858
process.exit(0);
5959
});
6060

6161
process.on('SIGTERM', async () => {
62-
console.log('\n[Main] Received SIGTERM, shutting down gracefully...');
62+
console.error('\n[Main] Received SIGTERM, shutting down gracefully...');
6363
await server.stop();
6464
process.exit(0);
6565
});

‎packages/core/src/mcp/servicenow-mcp-unified/server.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -724,11 +724,11 @@ export class ServiceNowUnifiedServer {
724724
const discoveryResult = await toolRegistry.initialize();
725725

726726
console.error('[Server] Tool discovery complete:');
727-
console.log(` - Domains: ${discoveryResult.domains.length}`);
728-
console.log(` - Tools found: ${discoveryResult.toolsFound}`);
729-
console.log(` - Tools registered: ${discoveryResult.toolsRegistered}`);
730-
console.log(` - Tools failed: ${discoveryResult.toolsFailed}`);
731-
console.log(` - Duration: ${discoveryResult.duration}ms`);
727+
console.error(` - Domains: ${discoveryResult.domains.length}`);
728+
console.error(` - Tools found: ${discoveryResult.toolsFound}`);
729+
console.error(` - Tools registered: ${discoveryResult.toolsRegistered}`);
730+
console.error(` - Tools failed: ${discoveryResult.toolsFailed}`);
731+
console.error(` - Duration: ${discoveryResult.duration}ms`);
732732

733733
if (discoveryResult.toolsFailed > 0) {
734734
console.error('[Server] Some tools failed to load:');
@@ -758,11 +758,11 @@ export class ServiceNowUnifiedServer {
758758
// Get server statistics
759759
const stats = toolRegistry.getStatistics();
760760
console.error('[Server] Server statistics:');
761-
console.log(` - Total tools: ${stats.totalTools}`);
762-
console.log(` - Total domains: ${stats.totalDomains}`);
763-
console.log(' - Tools by domain:');
761+
console.error(` - Total tools: ${stats.totalTools}`);
762+
console.error(` - Total domains: ${stats.totalDomains}`);
763+
console.error(' - Tools by domain:');
764764
Object.entries(stats.toolsByDomain).forEach(([domain, count]) => {
765-
console.log(` - ${domain}: ${count} tools`);
765+
console.error(` - ${domain}: ${count} tools`);
766766
});
767767

768768
console.error('[Server] Initialization complete ✅');

‎packages/core/src/mcp/servicenow-mcp-unified/shared/permission-validator.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function loadRoleFromAuthJson(): UserRole | null {
4040
if (authData.enterprise?.role) {
4141
const role = authData.enterprise.role;
4242
if (['developer', 'stakeholder', 'admin'].includes(role)) {
43-
console.log(`[Permission] 🔑 Loaded role '${role}' from ${authPath}`);
43+
console.error(`[Permission] 🔑 Loaded role '${role}' from ${authPath}`);
4444
cachedAuthRole = role as UserRole;
4545
cacheTimestamp = Date.now();
4646
return cachedAuthRole;
@@ -63,7 +63,7 @@ export function extractJWTPayload(headers?: Record<string, string>): JWTPayload
6363
const devRole = process.env.SNOW_FLOW_USER_ROLE as UserRole | undefined;
6464

6565
if (devRole && ['developer', 'stakeholder', 'admin'].includes(devRole)) {
66-
console.log(`[Permission] Using role from env: ${devRole}`);
66+
console.error(`[Permission] Using role from env: ${devRole}`);
6767
return {
6868
customerId: 0,
6969
tier: 'community',
@@ -79,7 +79,7 @@ export function extractJWTPayload(headers?: Record<string, string>): JWTPayload
7979
if (headers && headers['x-snow-flow-auth']) {
8080
try {
8181
const payload = JSON.parse(Buffer.from(headers['x-snow-flow-auth'], 'base64').toString());
82-
console.log(`[Permission] Using role from header: ${payload.role}`);
82+
console.error(`[Permission] Using role from header: ${payload.role}`);
8383
return payload as JWTPayload;
8484
} catch (error) {
8585
console.error('[Permission] Failed to parse JWT from headers:', error);
@@ -101,7 +101,7 @@ export function extractJWTPayload(headers?: Record<string, string>): JWTPayload
101101
}
102102

103103
// Priority 4: Default to developer for backward compatibility
104-
console.log('[Permission] No role found, defaulting to developer');
104+
console.error('[Permission] No role found, defaulting to developer');
105105
return {
106106
customerId: 0,
107107
tier: 'community',
@@ -171,7 +171,7 @@ export function validatePermission(
171171
}
172172

173173
// Permission granted!
174-
console.log(
174+
console.error(
175175
`[Permission] ✅ User '${userRole}' authorized to execute '${tool.name}' (${permission})`
176176
);
177177
}

‎packages/core/src/mcp/servicenow-mcp-unified/shared/tool-registry.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,12 @@ export class ToolRegistry {
210210
result.duration = Date.now() - startTime;
211211

212212
console.error('[ToolRegistry] Initialization complete:');
213-
console.log(` - Mode: ${this.useStaticMode ? 'static (bundled)' : 'dynamic (file-based)'}`);
214-
console.log(` - Domains: ${result.domains.length}`);
215-
console.log(` - Tools found: ${result.toolsFound}`);
216-
console.log(` - Tools registered: ${result.toolsRegistered}`);
217-
console.log(` - Tools failed: ${result.toolsFailed}`);
218-
console.log(` - Duration: ${result.duration}ms`);
213+
console.error(` - Mode: ${this.useStaticMode ? 'static (bundled)' : 'dynamic (file-based)'}`);
214+
console.error(` - Domains: ${result.domains.length}`);
215+
console.error(` - Tools found: ${result.toolsFound}`);
216+
console.error(` - Tools registered: ${result.toolsRegistered}`);
217+
console.error(` - Tools failed: ${result.toolsFailed}`);
218+
console.error(` - Duration: ${result.duration}ms`);
219219

220220
return result;
221221
}

‎packages/core/src/mcp/servicenow-mcp-unified/tools/integration/snow_create_rest_message.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
110110
} catch (tableApiError: any) {
111111
// If 403 Forbidden, fallback to background script
112112
if (tableApiError.response?.status === 403) {
113-
console.log('Table API failed with 403, falling back to background script...');
113+
console.error('Table API failed with 403, falling back to background script...');
114114
usedBackgroundScript = true;
115115

116116
// Create via background script

‎packages/core/src/mcp/servicenow-mcp-unified/tools/machine-learning-MIGRATED/ml_train_anomaly_detector.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
192192
callbacks: {
193193
onEpochEnd: (epoch: number, logs?: any) => {
194194
const loss = logs?.loss ? logs.loss.toFixed(4) : 'N/A';
195-
console.log(`Epoch ${epoch + 1}/${epochs} - Loss: ${loss}`);
195+
console.error(`Epoch ${epoch + 1}/${epochs} - Loss: ${loss}`);
196196
}
197197
}
198198
});

‎packages/core/src/mcp/servicenow-mcp-unified/tools/machine-learning-MIGRATED/ml_train_change_risk.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
150150
onEpochEnd: (epoch: number, logs?: any) => {
151151
const loss = logs?.loss ? logs.loss.toFixed(4) : 'N/A';
152152
const accuracy = logs?.acc ? (logs.acc * 100).toFixed(2) : 'N/A';
153-
console.log(`Epoch ${epoch + 1}/${epochs} - Loss: ${loss}, Accuracy: ${accuracy}%`);
153+
console.error(`Epoch ${epoch + 1}/${epochs} - Loss: ${loss}, Accuracy: ${accuracy}%`);
154154
}
155155
}
156156
});

‎packages/core/src/mcp/servicenow-mcp-unified/tools/machine-learning-MIGRATED/ml_train_incident_classifier.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
135135
Math.min(sample_size, totalAvailable) :
136136
optimalSize;
137137

138-
console.log(`Using ${actualSampleSize} incidents for training (optimal for this dataset)`);
138+
console.error(`Using ${actualSampleSize} incidents for training (optimal for this dataset)`);
139139
}
140140
} catch (error) {
141141
actualSampleSize = sample_size || 1000;
@@ -179,7 +179,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
179179
fetchTime,
180180
incidents.length
181181
);
182-
console.log(summary);
182+
console.error(summary);
183183

184184
// Check data availability
185185
const requiredRecords = 100;
@@ -265,7 +265,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
265265
onEpochEnd: (epoch: number, logs?: any) => {
266266
const loss = logs?.loss ? logs.loss.toFixed(4) : 'N/A';
267267
const accuracy = logs?.acc ? (logs.acc * 100).toFixed(2) : 'N/A';
268-
console.log(`Epoch ${epoch + 1}/${epochs} - Loss: ${loss}, Accuracy: ${accuracy}%`);
268+
console.error(`Epoch ${epoch + 1}/${epochs} - Loss: ${loss}, Accuracy: ${accuracy}%`);
269269
}
270270
}
271271
});

0 commit comments

Comments
 (0)