Authored by: GPT-5 (Codex Desktop)
Parent Epic: #11871 — Sub 2 of the env-primitive deduplication + shared MCP stderr logger Epic graduated from Discussion #11869 cycle-2.2.
Decision Record impact: none directly. This ticket implements the logger half of the already-graduated Epic #11871 shape and preserves ADR 0014 deployment-subset composability by keeping per-server logger policy in each server config instead of adding a central catalog.
Context
Epic #11871 has an explicit Sub 2 for a shared MCP stderr logger primitive, but the work was still only prose after Sub 1 (#11873 / PR #11876) and the adjacent SwarmHeartbeatService cleanup (#11874 / PR #11877) moved into review. During the nightshift re-review sweep on 2026-05-24, the open issue list showed #11871 and #11873 but no dedicated Sub 2 ticket.
Duplicate sweep:
- Live GitHub search for
shared MCP stderr logger returned only #11871 and #11873.
- Live GitHub search for
MCP stderr logger Logger.mjs returned #11871 plus older closed logger/observability tickets (#7534, #7570, #10576, #10582) with different historical scope.
- Local
resources/content/issues / resources/content/discussions grep for shared MCP stderr logger, Centralized stderr Logger, Centralized Logger for MCP Servers, and Logger.mjs returned no dedicated active Sub 2 match.
- Knowledge Base semantic duplicate query was attempted but returned
Internal error: Error finding id while KB healthcheck was otherwise healthy; this ticket relies on the live GitHub + local content sweep above rather than treating the failed semantic sweep as evidence.
The Problem
Five MCP servers currently carry per-server logger primitives with overlapping behavior but divergent implementation:
| Server |
Current logger shape |
github-workflow |
priority-filtered stderr, default warn, debug promotion |
gitlab-workflow |
same family as github-workflow |
knowledge-base |
daily-rotated file sink plus debug-gated stderr |
memory-core |
daily-rotated file sink plus debug-gated stderr plus flush() for short-lived scripts |
neural-link |
daily-rotated file sink plus tier-gated stderr where info/warn/error write without debug |
The drift is not that these servers need identical policy. The drift is that each server owns a separate primitive. Epic #11871 resolved the intended shape: one shared MCP-side stderr-safe primitive, with each server's differences preserved as config.
The Architectural Reality
Pre-Flight (structural fast-path): authoring ai/mcp/server/shared/Logger.mjs matches sibling pattern of ai/mcp/server/shared/BaseConfig.mjs in ai/mcp/server/shared/; both are shared MCP-server primitives consumed by per-server config.template.mjs / server implementations. Sibling-file-lift applies; no novel directory choice. Map-maintenance: not needed because the existing ai/mcp/server/shared/ role already covers shared MCP server substrate.
This must NOT reuse src/util/Logger.mjs / Neo.util.Logger: app/browser logging has incompatible semantics for MCP server transport. MCP server logging must be protocol-safe: stderr/file only, never stdout; error() logs and must not throw.
The deployment-subset model remains load-bearing: every server imports the shared primitive and passes its own config. There is no central logger-config catalog.
The Fix
- Create
ai/mcp/server/shared/Logger.mjs as the single shared MCP logger primitive.
- Migrate the five server logger implementations to consume the shared primitive:
ai/mcp/server/github-workflow/logger.mjs
ai/mcp/server/gitlab-workflow/logger.mjs
ai/mcp/server/knowledge-base/logger.mjs
ai/mcp/server/memory-core/logger.mjs
ai/mcp/server/neural-link/logger.mjs
- Preserve per-server behavior through each server's config/template surface: file prefix, log path, log level, debug gating, tier-gated stderr, error serialization, and
flush() support where applicable.
- Add or update unit tests for the shared primitive and per-server config differences.
- Keep stdout clean for MCP protocol safety.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
ai/mcp/server/shared/Logger.mjs |
Epic #11871 Sub 2 AC 1-2 |
Single shared MCP logger primitive; stderr/file only; error() logs and does not throw |
Existing per-server logger files remain until migrated in same PR |
JSDoc @summary on shared primitive |
Unit test: no stdout write; error/log methods do not throw |
| Per-server logger config slots |
Epic #11871 Sub 2 AC 4-6 |
Each server declares its own logger config: sink/gating/file-prefix/log-path/flush availability |
Defaults reproduce current behavior when config is absent |
config.template.mjs config comments |
Unit tests per server config shape |
| github/gitlab logger behavior |
Current logger.mjs siblings |
Priority-filtered stderr, default warn, debug promotes verbosity |
Default warn if invalid/absent log level |
Config template |
Unit test: priority threshold matrix |
| knowledge-base logger behavior |
Existing KB logger |
Daily-rotated file sink plus debug-gated stderr |
File path default under .neo-ai-data/logs |
Config template + logger JSDoc |
Unit test: writes file line and only writes stderr when debug enabled |
| memory-core logger behavior |
Existing MC logger |
Same file sink/debug-gated stderr plus flush() for short-lived scripts |
flush() resolves no-op when no stream exists |
Logger JSDoc |
Unit test: flush() resolves and does not throw |
| neural-link logger behavior |
Existing NL logger |
Daily-rotated file sink plus tier-gated stderr; info/warn/error write without debug, debug gated |
File path default under .neo-ai-data/logs |
Config template + logger JSDoc |
Unit test: info/warn/error stderr behavior independent of debug |
| stdout safety |
MCP protocol invariant |
Logger writes nothing to stdout under all levels |
N/A |
Logger JSDoc warning |
Test spies process.stdout.write / console.log and verifies zero calls |
Neo.util.Logger negative boundary |
Epic #11871 avoided trap |
No Neo.util.Logger import/use inside ai/ MCP logger path |
N/A |
PR body grep evidence |
git grep -n "Neo.util.Logger|src/util/Logger" -- ai/mcp/server clean |
Acceptance Criteria
ai/mcp/server/shared/Logger.mjs exists as the only shared primitive for MCP server logging.
- The logger never writes to stdout; all console output goes to stderr or configured file sinks.
error() logs and does not throw.
- The five per-server logger reimplementations are deleted or reduced to thin config-bound wrappers around the shared primitive.
- Existing per-server behavior is preserved as config, not separate primitive code:
- github/gitlab: priority-filtered stderr, default
warn, debug promotion.
- knowledge-base: daily-rotated file sink plus debug-gated stderr.
- memory-core: daily-rotated file sink plus debug-gated stderr plus
flush().
- neural-link: daily-rotated file sink plus tier-gated stderr.
- No central logger-config catalog is introduced; each server owns its logger config slot in its own
config.template.mjs.
- No
Neo.util.Logger usage is introduced under ai/mcp/server.
- Tests cover stdout safety, priority/tier gating, daily-file prefix/path behavior, Error serialization, and
flush() semantics.
- All related unit tests and static checks pass.
Out of Scope
- Changing deployment topology, compose profiles, or
TARGET_SERVER behavior.
- Centralizing every log config into a global catalog.
- Reworking non-MCP daemon logging outside
ai/mcp/server/**.
- Replacing application/browser
Neo.util.Logger.
- Adding compatibility shims for released users; v13 is unreleased.
Avoided Traps
- Do not adapt
Neo.util.Logger for MCP servers; its browser/app semantics are wrong here.
- Do not normalize away real per-server policy differences; preserve them as config.
- Do not write to stdout from an MCP server logger.
- Do not create another per-server helper layer that reintroduces N implementations.
- Do not broaden into env-primitive work; Sub 1 is #11873.
Related
Handoff Retrieval Hints
- Search terms:
shared MCP stderr logger, per-server logger reimplementations, Logger.mjs, github-workflow logger, memory-core logger flush, neural-link tier-gated stderr.
- Useful source files:
ai/mcp/server/shared/BaseConfig.mjs, five ai/mcp/server/*/logger.mjs siblings, and each server's config.template.mjs.
- Retrieval hint: start from Epic #11871 Sub 2 section and this ticket's Contract Ledger before touching code.
Authored by: GPT-5 (Codex Desktop)
Parent Epic: #11871 — Sub 2 of the env-primitive deduplication + shared MCP stderr logger Epic graduated from Discussion #11869 cycle-2.2.
Decision Record impact: none directly. This ticket implements the logger half of the already-graduated Epic #11871 shape and preserves ADR 0014 deployment-subset composability by keeping per-server logger policy in each server config instead of adding a central catalog.
Context
Epic #11871 has an explicit Sub 2 for a shared MCP stderr logger primitive, but the work was still only prose after Sub 1 (#11873 / PR #11876) and the adjacent SwarmHeartbeatService cleanup (#11874 / PR #11877) moved into review. During the nightshift re-review sweep on 2026-05-24, the open issue list showed #11871 and #11873 but no dedicated Sub 2 ticket.
Duplicate sweep:
shared MCP stderr loggerreturned only #11871 and #11873.MCP stderr logger Logger.mjsreturned #11871 plus older closed logger/observability tickets (#7534, #7570, #10576, #10582) with different historical scope.resources/content/issues/resources/content/discussionsgrep forshared MCP stderr logger,Centralized stderr Logger,Centralized Logger for MCP Servers, andLogger.mjsreturned no dedicated active Sub 2 match.Internal error: Error finding idwhile KB healthcheck was otherwise healthy; this ticket relies on the live GitHub + local content sweep above rather than treating the failed semantic sweep as evidence.The Problem
Five MCP servers currently carry per-server logger primitives with overlapping behavior but divergent implementation:
github-workflowwarn, debug promotiongitlab-workflowknowledge-basememory-coreflush()for short-lived scriptsneural-linkThe drift is not that these servers need identical policy. The drift is that each server owns a separate primitive. Epic #11871 resolved the intended shape: one shared MCP-side stderr-safe primitive, with each server's differences preserved as config.
The Architectural Reality
Pre-Flight (structural fast-path): authoring
ai/mcp/server/shared/Logger.mjsmatches sibling pattern ofai/mcp/server/shared/BaseConfig.mjsinai/mcp/server/shared/; both are shared MCP-server primitives consumed by per-serverconfig.template.mjs/ server implementations. Sibling-file-lift applies; no novel directory choice. Map-maintenance: not needed because the existingai/mcp/server/shared/role already covers shared MCP server substrate.This must NOT reuse
src/util/Logger.mjs/Neo.util.Logger: app/browser logging has incompatible semantics for MCP server transport. MCP server logging must be protocol-safe: stderr/file only, never stdout;error()logs and must not throw.The deployment-subset model remains load-bearing: every server imports the shared primitive and passes its own config. There is no central logger-config catalog.
The Fix
ai/mcp/server/shared/Logger.mjsas the single shared MCP logger primitive.ai/mcp/server/github-workflow/logger.mjsai/mcp/server/gitlab-workflow/logger.mjsai/mcp/server/knowledge-base/logger.mjsai/mcp/server/memory-core/logger.mjsai/mcp/server/neural-link/logger.mjsflush()support where applicable.Contract Ledger
ai/mcp/server/shared/Logger.mjserror()logs and does not throw@summaryon shared primitiveconfig.template.mjsconfig commentslogger.mjssiblingswarn, debug promotes verbositywarnif invalid/absent log level.neo-ai-data/logsflush()for short-lived scriptsflush()resolves no-op when no stream existsflush()resolves and does not throw.neo-ai-data/logsprocess.stdout.write/console.logand verifies zero callsNeo.util.Loggernegative boundaryNeo.util.Loggerimport/use insideai/MCP logger pathgit grep -n "Neo.util.Logger|src/util/Logger" -- ai/mcp/servercleanAcceptance Criteria
ai/mcp/server/shared/Logger.mjsexists as the only shared primitive for MCP server logging.error()logs and does not throw.warn, debug promotion.flush().config.template.mjs.Neo.util.Loggerusage is introduced underai/mcp/server.flush()semantics.Out of Scope
TARGET_SERVERbehavior.ai/mcp/server/**.Neo.util.Logger.Avoided Traps
Neo.util.Loggerfor MCP servers; its browser/app semantics are wrong here.Related
Handoff Retrieval Hints
shared MCP stderr logger,per-server logger reimplementations,Logger.mjs,github-workflow logger,memory-core logger flush,neural-link tier-gated stderr.ai/mcp/server/shared/BaseConfig.mjs, fiveai/mcp/server/*/logger.mjssiblings, and each server'sconfig.template.mjs.