@@ -4,9 +4,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
44import type { AuthContext } from '../../auth/types' ;
55import type { WorkflowsService } from '../../workflows/workflows.service' ;
66
7- // Helper to access private _registeredTools on McpServer (plain object at runtime)
8- type ToolHandler = ( ... args : unknown [ ] ) => unknown ;
9- type RegisteredToolsMap = Record < string , { handler : ToolHandler } > ;
7+ // Helper to access private _registeredTools and experimental tasks on McpServer (plain object at runtime)
8+ type RegisteredToolsMap = Record < string , any > ;
9+
1010function getRegisteredTools ( server : McpServer ) : RegisteredToolsMap {
1111 return ( server as unknown as { _registeredTools : RegisteredToolsMap } ) . _registeredTools ;
1212}
@@ -60,12 +60,11 @@ describe('StudioMcpService Unit Tests', () => {
6060 expect ( server ) . toBeInstanceOf ( McpServer ) ;
6161 } ) ;
6262
63- it ( 'registers all 9 expected tools' , ( ) => {
63+ it ( 'registers all expected tools and tasks ' , ( ) => {
6464 const server = service . createServer ( mockAuthContext ) ;
6565 const registeredTools = getRegisteredTools ( server ) ;
6666
6767 expect ( registeredTools ) . toBeDefined ( ) ;
68- expect ( Object . keys ( registeredTools ) . length ) . toBe ( 9 ) ;
6968
7069 const toolNames = Object . keys ( registeredTools ) . sort ( ) ;
7170 expect ( toolNames ) . toEqual ( [
@@ -110,16 +109,27 @@ describe('StudioMcpService Unit Tests', () => {
110109 expect ( workflowsService . findById ) . toHaveBeenCalledWith ( workflowId , mockAuthContext ) ;
111110 } ) ;
112111
113- it ( 'run_workflow tool uses auth context passed at creation time' , async ( ) => {
112+ it ( 'run_workflow task uses auth context passed at creation time' , async ( ) => {
114113 const workflowId = '11111111-1111-4111-8111-111111111111' ;
115114 const inputs = { key : 'value' } ;
116115
117116 const server = service . createServer ( mockAuthContext ) ;
118117 const registeredTools = getRegisteredTools ( server ) ;
119- const runWorkflowTool = registeredTools [ 'run_workflow' ] ;
118+ const runWorkflowTask = registeredTools [ 'run_workflow' ] ;
119+
120+ expect ( runWorkflowTask ) . toBeDefined ( ) ;
121+
122+ // Need to mock the extra params for the experimental tasks
123+ const mockExtra = {
124+ taskStore : {
125+ createTask : jest . fn ( ) . mockResolvedValue ( { taskId : 'mockTaskId' , status : 'working' } ) ,
126+ getTask : jest . fn ( ) . mockResolvedValue ( { taskId : 'mockTaskId' , status : 'working' } ) ,
127+ updateTaskStatus : jest . fn ( ) . mockResolvedValue ( true ) ,
128+ storeTaskResult : jest . fn ( ) . mockResolvedValue ( true ) ,
129+ } ,
130+ } ;
120131
121- expect ( runWorkflowTool ) . toBeDefined ( ) ;
122- await runWorkflowTool . handler ( { workflowId, inputs } ) ;
132+ await runWorkflowTask . handler . createTask ( { workflowId, inputs } , mockExtra ) ;
123133
124134 expect ( workflowsService . run ) . toHaveBeenCalledWith (
125135 workflowId ,
@@ -129,7 +139,7 @@ describe('StudioMcpService Unit Tests', () => {
129139 trigger : {
130140 type : 'api' ,
131141 sourceId : mockAuthContext . userId ,
132- label : 'Studio MCP' ,
142+ label : 'Studio MCP Task ' ,
133143 } ,
134144 } ,
135145 ) ;
@@ -230,12 +240,21 @@ describe('StudioMcpService Unit Tests', () => {
230240
231241 it ( 'denies run_workflow when workflows.run is false' , async ( ) => {
232242 const server = service . createServer ( restrictedAuth ) ;
233- const tools = getRegisteredTools ( server ) ;
234- const result = ( await tools [ 'run_workflow' ] . handler ( {
235- workflowId : '11111111-1111-4111-8111-111111111111' ,
236- } ) ) as { isError ?: boolean ; content : { text : string } [ ] } ;
237- expect ( result . isError ) . toBe ( true ) ;
238- expect ( result . content [ 0 ] . text ) . toContain ( 'workflows.run' ) ;
243+ const tasks = getRegisteredTools ( server ) ;
244+
245+ let errorThrown = false ;
246+ try {
247+ await tasks [ 'run_workflow' ] . handler . createTask (
248+ {
249+ workflowId : '11111111-1111-4111-8111-111111111111' ,
250+ } ,
251+ { } as any ,
252+ ) ;
253+ } catch ( _e : any ) {
254+ errorThrown = true ;
255+ expect ( _e . message ) . toContain ( 'workflows.run' ) ;
256+ }
257+ expect ( errorThrown ) . toBe ( true ) ;
239258 } ) ;
240259
241260 it ( 'denies cancel_run when runs.cancel is false' , async ( ) => {
@@ -260,15 +279,28 @@ describe('StudioMcpService Unit Tests', () => {
260279 it ( 'allows all tools when no apiKeyPermissions (non-API-key auth)' , async ( ) => {
261280 const server = service . createServer ( mockAuthContext ) ; // no apiKeyPermissions
262281 const tools = getRegisteredTools ( server ) ;
282+ const tasks = getRegisteredTools ( server ) ;
263283
264284 // All workflow/run tools should work without permission errors
265285 const listResult = ( await tools [ 'list_workflows' ] . handler ( { } ) ) as { isError ?: boolean } ;
266286 expect ( listResult . isError ) . toBeUndefined ( ) ;
267287
268- const runResult = ( await tools [ 'run_workflow' ] . handler ( {
269- workflowId : '11111111-1111-4111-8111-111111111111' ,
270- } ) ) as { isError ?: boolean } ;
271- expect ( runResult . isError ) . toBeUndefined ( ) ;
288+ const mockExtra = {
289+ taskStore : {
290+ createTask : jest . fn ( ) . mockResolvedValue ( { taskId : 'mock' , status : 'working' } ) ,
291+ getTask : jest . fn ( ) . mockResolvedValue ( { taskId : 'mock' , status : 'working' } ) ,
292+ updateTaskStatus : jest . fn ( ) . mockResolvedValue ( true ) ,
293+ storeTaskResult : jest . fn ( ) . mockResolvedValue ( true ) ,
294+ } ,
295+ } ;
296+
297+ const runResult = await tasks [ 'run_workflow' ] . handler . createTask (
298+ {
299+ workflowId : '11111111-1111-4111-8111-111111111111' ,
300+ } ,
301+ mockExtra ,
302+ ) ;
303+ expect ( runResult . task . taskId ) . toEqual ( 'mock' ) ;
272304
273305 const cancelResult = ( await tools [ 'cancel_run' ] . handler ( {
274306 runId : 'test-run-id' ,
@@ -308,11 +340,11 @@ describe('StudioMcpService Unit Tests', () => {
308340 } ;
309341 const server = service . createServer ( noPermsAuth ) ;
310342 const tools = getRegisteredTools ( server ) ;
343+ const tasks = getRegisteredTools ( server ) ;
311344
312345 const gatedTools = [
313346 'list_workflows' ,
314347 'get_workflow' ,
315- 'run_workflow' ,
316348 'list_runs' ,
317349 'get_run_status' ,
318350 'get_run_result' ,
@@ -326,6 +358,20 @@ describe('StudioMcpService Unit Tests', () => {
326358 } ) ) as { isError ?: boolean } ;
327359 expect ( result . isError ) . toBe ( true ) ;
328360 }
361+
362+ // Test run_workflow separately since it's a task now
363+ let errorThrown = false ;
364+ try {
365+ await tasks [ 'run_workflow' ] . handler . createTask (
366+ {
367+ workflowId : '11111111-1111-4111-8111-111111111111' ,
368+ } ,
369+ { } as any ,
370+ ) ;
371+ } catch ( _e : any ) {
372+ errorThrown = true ;
373+ }
374+ expect ( errorThrown ) . toBe ( true ) ;
329375 } ) ;
330376 } ) ;
331377
0 commit comments