@@ -19,6 +19,7 @@ import {
1919 buildCoordinatorMCPConfig ,
2020 getDockerMcpServerDestPath ,
2121 selectMcpJsonDir ,
22+ validateStartMCPServerArgs ,
2223} from './register.js' ;
2324import { getMCPRemoteServerUrl } from '../mcp/config.js' ;
2425import { startRemoteServer } from '../remote/server.js' ;
@@ -193,6 +194,95 @@ describe('Layer 3 — MCP startup pipeline (no Electron, real FS)', () => {
193194 } ) ;
194195} ) ;
195196
197+ // ─────────────────────────────────────────────────────────────────────────────
198+ // Layer 4: StartMCPServer input validation
199+ // ─────────────────────────────────────────────────────────────────────────────
200+
201+ const VALID_ARGS = {
202+ coordinatorTaskId : 'coord-1' ,
203+ projectId : 'proj-1' ,
204+ projectRoot : '/absolute/project' ,
205+ worktreePath : '/absolute/worktree' ,
206+ agentArgs : [ '--flag' , 'value' ] ,
207+ dockerContainerName : 'my-container' ,
208+ } ;
209+
210+ describe ( 'Layer 4 — StartMCPServer input validation' , ( ) => {
211+ it ( 'accepts valid args without throwing' , ( ) => {
212+ expect ( ( ) => validateStartMCPServerArgs ( VALID_ARGS ) ) . not . toThrow ( ) ;
213+ } ) ;
214+
215+ it ( 'rejects non-absolute projectRoot' , ( ) => {
216+ const writeFileSpy = vi . spyOn ( fs , 'writeFileSync' ) ;
217+ const copyFileSpy = vi . spyOn ( fs , 'copyFileSync' ) ;
218+
219+ expect ( ( ) =>
220+ validateStartMCPServerArgs ( { ...VALID_ARGS , projectRoot : 'relative/path' } ) ,
221+ ) . toThrow ( 'projectRoot must be absolute' ) ;
222+
223+ expect ( writeFileSpy ) . not . toHaveBeenCalled ( ) ;
224+ expect ( copyFileSpy ) . not . toHaveBeenCalled ( ) ;
225+ } ) ;
226+
227+ it ( 'rejects projectRoot containing ".."' , ( ) => {
228+ const writeFileSpy = vi . spyOn ( fs , 'writeFileSync' ) ;
229+ const copyFileSpy = vi . spyOn ( fs , 'copyFileSync' ) ;
230+
231+ expect ( ( ) => validateStartMCPServerArgs ( { ...VALID_ARGS , projectRoot : '/tmp/../etc' } ) ) . toThrow (
232+ 'projectRoot must not contain ".."' ,
233+ ) ;
234+
235+ expect ( writeFileSpy ) . not . toHaveBeenCalled ( ) ;
236+ expect ( copyFileSpy ) . not . toHaveBeenCalled ( ) ;
237+ } ) ;
238+
239+ it ( 'rejects non-absolute worktreePath' , ( ) => {
240+ const writeFileSpy = vi . spyOn ( fs , 'writeFileSync' ) ;
241+ const copyFileSpy = vi . spyOn ( fs , 'copyFileSync' ) ;
242+
243+ expect ( ( ) =>
244+ validateStartMCPServerArgs ( { ...VALID_ARGS , worktreePath : 'relative/worktree' } ) ,
245+ ) . toThrow ( 'worktreePath must be absolute' ) ;
246+
247+ expect ( writeFileSpy ) . not . toHaveBeenCalled ( ) ;
248+ expect ( copyFileSpy ) . not . toHaveBeenCalled ( ) ;
249+ } ) ;
250+
251+ it ( 'rejects agentArgs containing a non-string element' , ( ) => {
252+ const writeFileSpy = vi . spyOn ( fs , 'writeFileSync' ) ;
253+ const copyFileSpy = vi . spyOn ( fs , 'copyFileSync' ) ;
254+
255+ expect ( ( ) => validateStartMCPServerArgs ( { ...VALID_ARGS , agentArgs : [ 1 , 'foo' ] } ) ) . toThrow (
256+ 'agentArgs must be a string array' ,
257+ ) ;
258+
259+ expect ( writeFileSpy ) . not . toHaveBeenCalled ( ) ;
260+ expect ( copyFileSpy ) . not . toHaveBeenCalled ( ) ;
261+ } ) ;
262+
263+ it ( 'rejects dockerContainerName with shell-special characters' , ( ) => {
264+ const writeFileSpy = vi . spyOn ( fs , 'writeFileSync' ) ;
265+ const copyFileSpy = vi . spyOn ( fs , 'copyFileSync' ) ;
266+
267+ expect ( ( ) =>
268+ validateStartMCPServerArgs ( { ...VALID_ARGS , dockerContainerName : '; rm -rf /' } ) ,
269+ ) . toThrow ( 'dockerContainerName contains invalid characters' ) ;
270+
271+ expect ( writeFileSpy ) . not . toHaveBeenCalled ( ) ;
272+ expect ( copyFileSpy ) . not . toHaveBeenCalled ( ) ;
273+ } ) ;
274+
275+ it ( 'accepts worktreePath undefined (optional field)' , ( ) => {
276+ const { worktreePath : _ , ...argsWithoutWorktree } = VALID_ARGS ;
277+ expect ( ( ) => validateStartMCPServerArgs ( argsWithoutWorktree ) ) . not . toThrow ( ) ;
278+ } ) ;
279+
280+ it ( 'accepts dockerContainerName undefined (optional field)' , ( ) => {
281+ const { dockerContainerName : _ , ...argsWithoutDocker } = VALID_ARGS ;
282+ expect ( ( ) => validateStartMCPServerArgs ( argsWithoutDocker ) ) . not . toThrow ( ) ;
283+ } ) ;
284+ } ) ;
285+
196286// ─────────────────────────────────────────────────────────────────────────────
197287// Layer 5: Failure-mode tests
198288// ─────────────────────────────────────────────────────────────────────────────
0 commit comments