Conformance Check Failure
Check ID: MCE-006
Severity: HIGH
Category: Implementation (error serialization robustness)
Problem Description
scripts/check-safe-outputs-conformance.sh reports:
[HIGH] MCE-006: MCP server core does not use String() for error message serialization — risk of '[object Object]' in responses (Section 8.2)
The checker looks for the literal pattern String(e.message) or String(err.message) in actions/setup/js/mcp_server_core.cjs. That literal pattern isn't present — instead, the file delegates message extraction to a shared helper, getErrorMessage(), imported from actions/setup/js/error_helpers.cjs:
// mcp_server_core.cjs (~line 998-1005)
const hasMessage = typeof e === "object" && e !== null && "message" in e && Boolean(e.message);
const message = hasMessage ? getErrorMessage(e) : "Internal error";
server.replyError(id, code, message);
Investigating getErrorMessage() shows it is more sophisticated than a plain String(e.message) call (it also detects and cleans up HTML error pages), but it has a narrow real gap that matches the spirit of what the checker is trying to catch:
// error_helpers.cjs
function getErrorMessage(error) {
let message;
if (error instanceof Error) {
message = error.message;
} else if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
message = error.message;
} else {
message = String(error); // <-- falls back to stringifying the WHOLE error object
}
...
}
If a thrown value is a plain object whose .message property is itself non-string (e.g. { message: { reason: "x" } }), the middle branch's typeof error.message === "string" check fails, and the code falls through to String(error) — stringifying the entire object rather than just its message. For a plain object without a custom toString, this produces "[object Object]" in the final MCP error response, which is exactly the failure mode Section 8.2 of the spec prohibits.
This is a narrower edge case than a total absence of serialization (most thrown values are Error instances or have string .message), but it's a real gap, not purely a checker false positive.
Affected Components
- Files:
actions/setup/js/error_helpers.cjs (getErrorMessage, lines 26-44), actions/setup/js/mcp_server_core.cjs (lines 998-1006)
- Checker:
scripts/check-safe-outputs-conformance.sh (~line 1031) — its literal-pattern check also doesn't recognize the indirection through getErrorMessage(), so it can't currently distinguish "no serialization" from "serialization via a shared helper."
🔍 Current vs Expected Behavior
Current Behavior
When a handler throws a plain object with a non-string message field, getErrorMessage() stringifies the whole error object instead of the message, which can produce "[object Object]" in the JSON-RPC error response sent back over MCP.
Expected Behavior
Per spec Section 8.2, error messages returned in JSON-RPC responses MUST be readable strings, never "[object Object]". getErrorMessage() should coerce a non-string .message value to a string explicitly (e.g. String(error.message)) rather than falling through to stringifying the container object.
Remediation Steps
This task can be assigned to a Copilot coding agent with the following steps:
- In
actions/setup/js/error_helpers.cjs, update getErrorMessage() so that when error is a non-Error object with a message property that exists but is not a string, it coerces that message value to a string instead of stringifying the whole object, e.g.:
} else if (error && typeof error === "object" && "message" in error) {
message = typeof error.message === "string" ? error.message : String(error.message);
} else {
message = String(error);
}
- Add/update a unit test (there is likely an existing
error_helpers.test.cjs or similar) covering a thrown plain object with a non-string message field, asserting the result is never the literal string "[object Object]".
- Update
scripts/check-safe-outputs-conformance.sh MCE-006 (~line 1031) to also recognize serialization performed via the shared getErrorMessage helper (e.g. by checking that mcp_server_core.cjs imports getErrorMessage from error_helpers.cjs AND that error_helpers.cjs itself guards against non-string .message values), instead of only grepping for the literal String(e.message) pattern in the core file.
- Re-run the conformance checker and confirm MCE-006 passes.
Verification
After remediation, verify the fix by running:
bash scripts/check-safe-outputs-conformance.sh
The check MCE-006 should pass without errors.
References
- Safe Outputs Specification: docs/src/content/docs/specs/safe-outputs-specification.md
- Conformance Checker: scripts/check-safe-outputs-conformance.sh
- Run ID: 32621246743
- Date: 2026-08-23
Generated by ✅ Daily Safe Outputs Conformance Checker · agent · 88 AIC · ⌖ 8.17 AIC · ⊞ 7.5K · ◷
Conformance Check Failure
Check ID: MCE-006
Severity: HIGH
Category: Implementation (error serialization robustness)
Problem Description
scripts/check-safe-outputs-conformance.shreports:The checker looks for the literal pattern
String(e.message)orString(err.message)inactions/setup/js/mcp_server_core.cjs. That literal pattern isn't present — instead, the file delegates message extraction to a shared helper,getErrorMessage(), imported fromactions/setup/js/error_helpers.cjs:Investigating
getErrorMessage()shows it is more sophisticated than a plainString(e.message)call (it also detects and cleans up HTML error pages), but it has a narrow real gap that matches the spirit of what the checker is trying to catch:If a thrown value is a plain object whose
.messageproperty is itself non-string (e.g.{ message: { reason: "x" } }), the middle branch'stypeof error.message === "string"check fails, and the code falls through toString(error)— stringifying the entire object rather than just its message. For a plain object without a customtoString, this produces"[object Object]"in the final MCP error response, which is exactly the failure mode Section 8.2 of the spec prohibits.This is a narrower edge case than a total absence of serialization (most thrown values are
Errorinstances or have string.message), but it's a real gap, not purely a checker false positive.Affected Components
actions/setup/js/error_helpers.cjs(getErrorMessage, lines 26-44),actions/setup/js/mcp_server_core.cjs(lines 998-1006)scripts/check-safe-outputs-conformance.sh(~line 1031) — its literal-pattern check also doesn't recognize the indirection throughgetErrorMessage(), so it can't currently distinguish "no serialization" from "serialization via a shared helper."🔍 Current vs Expected Behavior
Current Behavior
When a handler throws a plain object with a non-string
messagefield,getErrorMessage()stringifies the whole error object instead of the message, which can produce"[object Object]"in the JSON-RPC error response sent back over MCP.Expected Behavior
Per spec Section 8.2, error messages returned in JSON-RPC responses MUST be readable strings, never
"[object Object]".getErrorMessage()should coerce a non-string.messagevalue to a string explicitly (e.g.String(error.message)) rather than falling through to stringifying the container object.Remediation Steps
This task can be assigned to a Copilot coding agent with the following steps:
actions/setup/js/error_helpers.cjs, updategetErrorMessage()so that whenerroris a non-Error object with amessageproperty that exists but is not a string, it coerces that message value to a string instead of stringifying the whole object, e.g.:error_helpers.test.cjsor similar) covering a thrown plain object with a non-stringmessagefield, asserting the result is never the literal string"[object Object]".scripts/check-safe-outputs-conformance.shMCE-006 (~line 1031) to also recognize serialization performed via the sharedgetErrorMessagehelper (e.g. by checking thatmcp_server_core.cjsimportsgetErrorMessagefromerror_helpers.cjsAND thaterror_helpers.cjsitself guards against non-string.messagevalues), instead of only grepping for the literalString(e.message)pattern in the core file.Verification
After remediation, verify the fix by running:
The check MCE-006 should pass without errors.
References