Skip to content

Commit 9ff8d11

Browse files
committed
v2.1.1
stdio fix
1 parent 1c38258 commit 9ff8d11

4 files changed

Lines changed: 144 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,85 @@
1+
## 2.1.1
2+
3+
### Bug Fix
4+
- **Stdio stdout pollution** – Fixed a bug where Winston log output and audit log entries were written to `stdout` during server startup in stdio mode. MCP clients reading stdout for JSON-RPC (Claude Desktop, Cursor, etc.) would fail with a Zod `invalid_union` validation error on the non-JSON-RPC log lines.
5+
- All Winston console transports now route to `stderr` in stdio mode, keeping `stdout` clean for JSON-RPC.
6+
- `AuditLogger` accepts a `useStderr` flag so its transport also writes to `stderr` in stdio mode.
7+
- `startStdioServer()` logger is no longer restricted to `error` level — all configured levels flow to `stderr`.
8+
9+
---
10+
11+
## 2.1.0
12+
13+
### New Resources
14+
- **SDT (Scheduled Down Time)** – Full CRUD via `lm_sdt`. Supports oneTime, daily, weekly, monthly, and monthlyByWeek schedules targeting devices, device groups, websites, collectors, and datasources.
15+
- **OpsNotes** – Full CRUD via `lm_opsnote`. Scoped visibility (device, service, deviceGroup, serviceGroup) with tag-based categorization.
16+
17+
### Intelligent Response Formatting (Token Reduction)
18+
- **Compact list views** – Lists with >5 items return key-field summary tables instead of full JSON.
19+
- **Time-series data optimization** – Device data responses include summary statistics (min/max/avg/latest) plus sampled data points.
20+
- **Batch result summaries** – Large batch operations return only failure details; full per-item results stored in session.
21+
- **Internal field stripping**`raw`, `meta`, and `request` fields removed from tool responses.
22+
23+
### Session Filtering Enhancements
24+
- `lm_session get` now supports `fields` (projection), `index` (single item), and `limit` (first N items) for on-demand filtering of stored results.
25+
26+
### Improved Error Messages
27+
- API errors now include `errorMessage`, `errorCode`, and `errorDetail` from the LogicMonitor response body.
28+
- Status-based hints (404: verify IDs, 401/403: check permissions, 429: rate limited) help LLMs recover.
29+
30+
### Device Data Reliability
31+
- **Relative time parsing** – Time parameters support `-6h`, `-24h`, `-7d`, `-30m`, `"now"`, ISO dates, and epoch seconds.
32+
- **Datasource ID disambiguation** – Tool descriptions clarify `id` vs `dataSourceId`.
33+
34+
### Schema Flattening for MCP Clients
35+
- Fixed Zod v4 `oneOf` discriminated unions not recognized by MCP clients. ListTools handler now flattens both `anyOf` and `oneOf` formats.
36+
37+
### LLM Evaluation Harness
38+
- New `tests/eval/` system with 60+ scenarios across 8 categories, multi-provider support (OpenAI GPT-4o, GPT-5.4), scoring engine, and multi-step workflow testing.
39+
40+
### Audit Logging & Security
41+
- Structured audit logging for server events, auth, sessions, and tool calls.
42+
- Bearer tokens hashed with SHA-256.
43+
- Graceful shutdown with clean session closure on SIGTERM/SIGINT.
44+
45+
### Winston Transport Leak Fix
46+
- Fixed memory leak in HTTP mode where session Winston transports were never removed on close.
47+
48+
---
49+
50+
## 2.0.0
51+
52+
### Architecture Modernization
53+
- **Zod validation** – Migrated from Joi to Zod for compile-time type safety and discriminated unions.
54+
- **MCP SDK high-level API** – All tools use `registerTool()` instead of manual `setRequestHandler()`.
55+
- **Unified resource pattern** – Every tool follows `list`, `get`, `create`, `update`, `delete` operations.
56+
- **Enhanced batch operations** – Explicit arrays, session variable references (`applyToPrevious`), and filter-based operations.
57+
- **Smart pagination**`autoPaginate` parameter for fine-grained control over result sets.
58+
59+
### New Resource Coverage
60+
- **Device Data** – Datasources, instances, and performance metrics.
61+
- **Collector Groups** – Full CRUD for organizing collectors.
62+
- **Dashboards** – Create, update, and manage dashboards.
63+
- **Users** – Complete user management with role assignments and batch operations.
64+
65+
### Developer Experience
66+
- Removed ~2,000 lines of deprecated code (Joi schemas, legacy tool definitions).
67+
- Standardized MCP error codes and validation messages.
68+
- 170+ tests covering CRUD, batch processing, field selection, and error handling.
69+
70+
### Resource Health & Discovery
71+
- Field metadata resources (`health://logicmonitor/fields/{resource}`).
72+
- Per-tool health telemetry via `health://logicmonitor/status`.
73+
- Enhanced session tool for variables, history, and multi-step context.
74+
75+
### Breaking Changes
76+
- Tool input schemas now use `operation` parameter instead of separate tools per operation.
77+
- Switched from Joi to Zod validation.
78+
- Omit `fields` parameter for all fields (instead of `fields: "*"`).
79+
- Update operations use `opType=replace` for custom property merging.
80+
81+
---
82+
183
## 1.2.0 – 2025-10-28
284

385
### Highlights

releasenotes.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# LogicMonitor MCP Server v2.1.1
2+
3+
## Stdio Transport Fix
4+
5+
Patch release that fixes a startup failure when using stdio-based MCP clients (Claude Desktop, Cursor, etc.).
6+
7+
### Bug Fix
8+
9+
- **Stdio stdout pollution** – Fixed a bug where Winston log output and audit log entries were written to `stdout` during server startup in stdio mode. MCP clients that read stdout exclusively for JSON-RPC messages (such as Claude Desktop) would receive non-JSON-RPC data like `{"level":"info","message":"Starting in STDIO mode","timestamp":"..."}` before the protocol handshake, causing a Zod validation error (`invalid_union` / `unrecognized_keys: ["level","message","timestamp"]`) and failing to connect.
10+
11+
### What Changed
12+
13+
- All Winston console transports now route to `stderr` when running in stdio mode (`--stdio`), keeping `stdout` clean for JSON-RPC.
14+
- The `AuditLogger` accepts a `useStderr` flag so its dedicated transport also writes to `stderr` in stdio mode.
15+
- The `startStdioServer()` logger is no longer restricted to `error` level — all configured log levels now flow to `stderr`, improving debuggability without polluting the protocol channel.
16+
17+
### Upgrading from v2.1.0
18+
19+
Drop-in replacement. No configuration or API changes.
20+
21+
---
22+
23+
**Full Changelog**: See [CHANGELOG.md](CHANGELOG.md) for detailed changes.
24+
25+
---
26+
127
# LogicMonitor MCP Server v2.1.0
228

329
## New Resources, Smarter Responses, and LLM Eval System

src/audit/logger.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ export class AuditLogger {
1111
private logger: winston.Logger;
1212
private enabled: boolean;
1313

14-
constructor(config: Config) {
14+
constructor(config: Config, useStderr = false) {
1515
this.enabled = config.logging.auditLogEnabled;
1616

17-
// Create dedicated audit logger
17+
const format = config.logging.format === 'simple'
18+
? winston.format.combine(winston.format.colorize(), winston.format.simple())
19+
: winston.format.json();
20+
1821
this.logger = winston.createLogger({
1922
level: 'info',
2023
format: winston.format.combine(
@@ -27,12 +30,8 @@ export class AuditLogger {
2730
},
2831
transports: [
2932
new winston.transports.Console({
30-
format: config.logging.format === 'simple'
31-
? winston.format.combine(
32-
winston.format.colorize(),
33-
winston.format.simple()
34-
)
35-
: winston.format.json(),
33+
format,
34+
stderrLevels: useStderr ? ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'] : [],
3635
}),
3736
],
3837
});

src/index.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,26 @@ import { createRateLimitMiddleware } from './middleware/rateLimit.js';
1919
import { AuditLogger } from './audit/logger.js';
2020
import { GracefulShutdown } from './utils/gracefulShutdown.js';
2121

22+
// Detect stdio mode early — before any logging can pollute stdout.
23+
// In stdio mode, stdout is reserved exclusively for JSON-RPC messages.
24+
const isStdioMode = process.argv.includes('--stdio');
25+
2226
// Load configuration
2327
const config = getConfig();
2428

29+
// In stdio mode, ALL Winston transports must write to stderr so stdout
30+
// remains clean for the JSON-RPC protocol used by StdioServerTransport.
31+
function createStderrConsoleTransport(format: winston.Logform.Format): winston.transports.ConsoleTransportInstance {
32+
return new winston.transports.Console({
33+
format,
34+
stderrLevels: isStdioMode ? ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'] : [],
35+
});
36+
}
37+
38+
const logFormat = config.logging.format === 'simple'
39+
? winston.format.combine(winston.format.colorize(), winston.format.simple())
40+
: winston.format.json();
41+
2542
// Set up logger
2643
const logger = winston.createLogger({
2744
level: config.logging.level,
@@ -30,20 +47,11 @@ const logger = winston.createLogger({
3047
winston.format.errors({ stack: true }),
3148
winston.format.json()
3249
),
33-
transports: [
34-
new winston.transports.Console({
35-
format: config.logging.format === 'simple'
36-
? winston.format.combine(
37-
winston.format.colorize(),
38-
winston.format.simple()
39-
)
40-
: winston.format.json()
41-
})
42-
]
50+
transports: [createStderrConsoleTransport(logFormat)]
4351
});
4452

45-
// Initialize audit logger
46-
const auditLogger = new AuditLogger(config);
53+
// Initialize audit logger (routes to stderr in stdio mode)
54+
const auditLogger = new AuditLogger(config, isStdioMode);
4755

4856
// Initialize graceful shutdown
4957
const gracefulShutdown = new GracefulShutdown(logger);
@@ -355,29 +363,29 @@ async function startHttpServer() {
355363
}
356364

357365
async function startStdioServer() {
358-
// In stdio mode, only log errors to stderr to avoid interfering with JSON-RPC
366+
// In stdio mode, ALL log output must go to stderr — stdout is reserved
367+
// exclusively for JSON-RPC messages by the StdioServerTransport.
359368
const stdioLogger = winston.createLogger({
360-
level: 'error',
369+
level: config.logging.level,
361370
format: winston.format.combine(
362371
winston.format.timestamp(),
363372
winston.format.errors({ stack: true }),
364373
winston.format.json()
365374
),
366375
transports: [
367376
new winston.transports.Console({
368-
stderrLevels: ['error'],
377+
stderrLevels: ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'],
369378
format: winston.format.simple()
370379
})
371380
]
372381
});
373382

374-
// Validate credentials
375383
if (!config.logicMonitor.account || !config.logicMonitor.bearerToken) {
376384
stdioLogger.error('STDIO mode requires LM_ACCOUNT and LM_BEARER_TOKEN environment variables');
377385
throw new Error('Missing required LogicMonitor credentials for STDIO mode');
378386
}
379387

380-
stdioLogger.error(`Starting STDIO mode with account: ${config.logicMonitor.account}`);
388+
stdioLogger.info(`Starting STDIO mode with account: ${config.logicMonitor.account}`);
381389

382390
const { server } = await createServer({
383391
logger: stdioLogger,
@@ -392,15 +400,14 @@ async function startStdioServer() {
392400

393401
const transport = new StdioServerTransport();
394402

395-
// Register cleanup on shutdown
396403
gracefulShutdown.registerHandler(async () => {
397-
stdioLogger.error('Closing STDIO server...');
404+
stdioLogger.info('Closing STDIO server...');
398405
await server.close();
399406
});
400407

401408
await server.connect(transport);
402409

403-
stdioLogger.error('STDIO server connected and ready');
410+
stdioLogger.info('STDIO server connected and ready');
404411
}
405412

406413
// Main entry point
@@ -415,10 +422,9 @@ async function main() {
415422
},
416423
});
417424

418-
// Determine which transports to start
419-
const isStdioMode = process.argv.includes('--stdio') || !config.transport.enableHttp;
425+
const shouldUseStdio = isStdioMode || !config.transport.enableHttp;
420426

421-
if (isStdioMode && config.transport.enableStdio) {
427+
if (shouldUseStdio && config.transport.enableStdio) {
422428
logger.info('Starting in STDIO mode');
423429
await startStdioServer();
424430
} else if (config.transport.enableHttp) {

0 commit comments

Comments
 (0)