@@ -53,6 +53,30 @@ describe("parseMCPServersConfig", () => {
5353 ) ;
5454 } ) ;
5555
56+ it ( "sanitizes and truncates oversized JSON parse diagnostics" , ( ) => {
57+ const parseSpy = jest . spyOn ( JSON , "parse" ) . mockImplementation ( ( ) => {
58+ throw new Error ( `parse\u0000\n${ "x" . repeat ( 10_000 ) } ` ) ;
59+ } ) ;
60+
61+ try {
62+ expect ( ( ) => parseMCPServersConfig ( '{"servers":[{"command":"npx"}]}' ) ) . toThrow (
63+ "[truncated"
64+ ) ;
65+ expect ( ( ) => parseMCPServersConfig ( '{"servers":[{"command":"npx"}]}' ) ) . toThrow (
66+ / I n v a l i d M C P c o n f i g J S O N : /
67+ ) ;
68+ try {
69+ parseMCPServersConfig ( '{"servers":[{"command":"npx"}]}' ) ;
70+ } catch ( error ) {
71+ const message = String ( error instanceof Error ? error . message : error ) ;
72+ expect ( message ) . not . toContain ( "\u0000" ) ;
73+ expect ( message ) . not . toContain ( "\n" ) ;
74+ }
75+ } finally {
76+ parseSpy . mockRestore ( ) ;
77+ }
78+ } ) ;
79+
5680 it ( "throws clear message when config input is not a string" , ( ) => {
5781 expect ( ( ) => parseMCPServersConfig ( 42 as unknown as string ) ) . toThrow (
5882 "Invalid MCP config JSON: config must be a string."
@@ -253,7 +277,7 @@ describe("parseMCPServersConfig", () => {
253277 expect ( ( ) =>
254278 parseMCPServersConfig ( '[{"connectionType":"sse\\u0007","command":"npx"}]' )
255279 ) . toThrow (
256- 'MCP server entry at index 0 has unsupported connectionType "sse\u0007 ". Supported values are "stdio" and "sse".'
280+ 'MCP server entry at index 0 has unsupported connectionType "sse". Supported values are "stdio" and "sse".'
257281 ) ;
258282
259283 expect ( ( ) =>
@@ -611,7 +635,7 @@ describe("parseMCPServersConfig", () => {
611635 '[{"connectionType":"sse","sseUrl":"https://example.com/sse\\u0007"}]'
612636 )
613637 ) . toThrow (
614- 'MCP server entry at index 0 has invalid "sseUrl" value "https://example.com/sse\u0007 ".'
638+ 'MCP server entry at index 0 has invalid "sseUrl" value "https://example.com/sse".'
615639 ) ;
616640
617641 expect ( ( ) =>
@@ -749,6 +773,33 @@ describe("loadMCPServersFromFile", () => {
749773 ) ;
750774 } ) ;
751775
776+ it ( "sanitizes and truncates oversized config read diagnostics" , async ( ) => {
777+ const statSpy = jest . spyOn ( fs . promises , "stat" ) . mockResolvedValue ( {
778+ isFile : ( ) => true ,
779+ size : 1 ,
780+ } as unknown as fs . Stats ) ;
781+ const readFileSpy = jest
782+ . spyOn ( fs . promises , "readFile" )
783+ . mockRejectedValue ( new Error ( `read\u0000\n${ "x" . repeat ( 10_000 ) } ` ) ) ;
784+
785+ try {
786+ await loadMCPServersFromFile ( "/tmp/mcp-config-test.json" )
787+ . then ( ( ) => {
788+ throw new Error ( "expected loadMCPServersFromFile to reject" ) ;
789+ } )
790+ . catch ( ( error ) => {
791+ const message = String ( error instanceof Error ? error . message : error ) ;
792+ expect ( message ) . toContain ( "[truncated" ) ;
793+ expect ( message ) . not . toContain ( "\u0000" ) ;
794+ expect ( message ) . not . toContain ( "\n" ) ;
795+ expect ( message . length ) . toBeLessThan ( 700 ) ;
796+ } ) ;
797+ } finally {
798+ statSpy . mockRestore ( ) ;
799+ readFileSpy . mockRestore ( ) ;
800+ }
801+ } ) ;
802+
752803 it ( "throws readable error when config path is not a regular file" , async ( ) => {
753804 const tempDir = await fs . promises . mkdtemp (
754805 path . join ( os . tmpdir ( ) , "hyperagent-mcp-config-" )
0 commit comments