refactor: replace RequestHandlerExtra with structured context types (#1467)
This commit is contained in:
parent
be5670f4fa
commit
7bb79ebbbb
5 changed files with 1224 additions and 770 deletions
|
|
@ -1096,10 +1096,10 @@ test('should handle request timeout', async () => {
|
|||
);
|
||||
|
||||
// Set up server with a delayed response
|
||||
server.setRequestHandler('resources/list', async (_request, extra) => {
|
||||
server.setRequestHandler('resources/list', async (_request, ctx) => {
|
||||
const timer = new Promise(resolve => {
|
||||
const timeout = setTimeout(resolve, 100);
|
||||
extra.signal.addEventListener('abort', () => clearTimeout(timeout));
|
||||
ctx.mcpReq.signal.addEventListener('abort', () => clearTimeout(timeout));
|
||||
});
|
||||
|
||||
await timer;
|
||||
|
|
@ -2269,27 +2269,27 @@ describe('Task-based execution', () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
const result = {
|
||||
content: [{ type: 'text', text: 'Tool executed successfully!' }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -2345,27 +2345,27 @@ describe('Task-based execution', () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
const result = {
|
||||
content: [{ type: 'text', text: 'Success!' }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -2422,27 +2422,27 @@ describe('Task-based execution', () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
const result = {
|
||||
content: [{ type: 'text', text: 'Result data!' }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -2506,27 +2506,27 @@ describe('Task-based execution', () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
const result = {
|
||||
content: [{ type: 'text', text: 'Success!' }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -2603,18 +2603,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'list-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2696,18 +2696,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'list-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2788,18 +2788,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'result-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2879,18 +2879,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'list-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2991,27 +2991,27 @@ describe('Task-based execution', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ id }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask({ id }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
const result = {
|
||||
content: [{ type: 'text', text: `Result for ${id || 'unknown'}` }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -3259,27 +3259,27 @@ test('should respect server task capabilities', async () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
const result = {
|
||||
content: [{ type: 'text', text: 'Success!' }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1386,12 +1386,12 @@ test('should handle request timeout', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('sampling/createMessage', async (_request, extra) => {
|
||||
client.setRequestHandler('sampling/createMessage', async (_request, ctx) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(resolve, 100);
|
||||
extra.signal.addEventListener('abort', () => {
|
||||
ctx.mcpReq.signal.addEventListener('abort', () => {
|
||||
clearTimeout(timeout);
|
||||
reject(extra.signal.reason);
|
||||
reject(ctx.mcpReq.signal.reason);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -2146,9 +2146,9 @@ describe('Task-based execution', () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
// Simulate some async work
|
||||
|
|
@ -2157,20 +2157,20 @@ describe('Task-based execution', () => {
|
|||
const result = {
|
||||
content: [{ type: 'text', text: 'Tool executed successfully!' }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
})();
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -2341,13 +2341,13 @@ describe('Task-based execution', () => {
|
|||
let capturedElicitRequest: z.infer<typeof ElicitRequestSchema> | null = null;
|
||||
|
||||
// Set up client elicitation handler
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
let taskId: string | undefined;
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const createdTask = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const createdTask = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
taskId = createdTask.taskId;
|
||||
}
|
||||
|
|
@ -2371,15 +2371,15 @@ describe('Task-based execution', () => {
|
|||
inputSchema: z.object({})
|
||||
},
|
||||
{
|
||||
async createTask(_args, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask(_args, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
// Perform async work that makes a nested request
|
||||
(async () => {
|
||||
// During tool execution, make a nested request to the client using extra.sendRequest
|
||||
const elicitResult = await extra.sendRequest(
|
||||
// During tool execution, make a nested request to the client using ctx.mcpReq.send
|
||||
const elicitResult = await ctx.mcpReq.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
|
|
@ -2405,20 +2405,20 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
})();
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -2498,18 +2498,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'server-test-user', confirmed: true }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2579,18 +2579,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'list-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2658,18 +2658,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'result-user', confirmed: true }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2739,18 +2739,18 @@ describe('Task-based execution', () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'list-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
@ -2853,9 +2853,9 @@ describe('Task-based execution', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ delay, taskNum }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
async createTask({ delay, taskNum }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
|
||||
// Simulate async work
|
||||
|
|
@ -2864,20 +2864,20 @@ describe('Task-based execution', () => {
|
|||
const result = {
|
||||
content: [{ type: 'text', text: `Completed task ${taskNum || 'unknown'}` }]
|
||||
};
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
})();
|
||||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -3082,18 +3082,18 @@ test('should respect client task capabilities', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
client.setRequestHandler('elicitation/create', async (request, extra) => {
|
||||
client.setRequestHandler('elicitation/create', async (request, ctx) => {
|
||||
const result = {
|
||||
action: 'accept',
|
||||
content: { username: 'test-user' }
|
||||
};
|
||||
|
||||
// Check if task creation is requested
|
||||
if (request.params.task && extra.taskStore) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
ttl: extra.taskRequestedTtl
|
||||
if (request.params.task && ctx.task?.store) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: ctx.task.requestedTtl
|
||||
});
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
|
||||
// Return CreateTaskResult when task creation is requested
|
||||
return { task };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,159 @@ describe('Zod v4', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: ctx.mcpReq.log convenience method
|
||||
*/
|
||||
test('should send logging messages via ctx.mcpReq.log() convenience method', async () => {
|
||||
const mcpServer = new McpServer(
|
||||
{
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
},
|
||||
{ capabilities: { logging: {} } }
|
||||
);
|
||||
|
||||
const notifications: Notification[] = [];
|
||||
const client = new Client({
|
||||
name: 'test client',
|
||||
version: '1.0'
|
||||
});
|
||||
client.fallbackNotificationHandler = async notification => {
|
||||
notifications.push(notification);
|
||||
};
|
||||
|
||||
mcpServer.registerTool(
|
||||
'log-test',
|
||||
{
|
||||
description: 'A tool that logs via ctx.mcpReq.log()'
|
||||
},
|
||||
async ctx => {
|
||||
await ctx.mcpReq.log('info', 'Log from convenience method', 'test-logger');
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: 'done' }]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
|
||||
|
||||
await client.callTool({ name: 'log-test' });
|
||||
|
||||
expect(notifications).toMatchObject([
|
||||
{
|
||||
method: 'notifications/message',
|
||||
params: {
|
||||
level: 'info',
|
||||
data: 'Log from convenience method',
|
||||
logger: 'test-logger'
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: ctx.mcpReq.elicitInput convenience method
|
||||
*/
|
||||
test('should elicit input via ctx.mcpReq.elicitInput() convenience method', async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
});
|
||||
|
||||
let elicitResult: unknown = null;
|
||||
|
||||
mcpServer.registerTool(
|
||||
'elicit-test',
|
||||
{
|
||||
description: 'A tool that elicits input via ctx.mcpReq.elicitInput()'
|
||||
},
|
||||
async ctx => {
|
||||
elicitResult = await ctx.mcpReq.elicitInput({
|
||||
message: 'Please confirm',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
confirmed: { type: 'boolean' }
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: 'done' }]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test client', version: '1.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler('elicitation/create', async () => ({
|
||||
action: 'accept',
|
||||
content: { confirmed: true }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
|
||||
|
||||
await client.callTool({ name: 'elicit-test' });
|
||||
|
||||
expect(elicitResult).toMatchObject({
|
||||
action: 'accept',
|
||||
content: { confirmed: true }
|
||||
});
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: ctx.mcpReq.requestSampling convenience method
|
||||
*/
|
||||
test('should request sampling via ctx.mcpReq.requestSampling() convenience method', async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
});
|
||||
|
||||
let samplingResult: unknown = null;
|
||||
|
||||
mcpServer.registerTool(
|
||||
'sampling-test',
|
||||
{
|
||||
description: 'A tool that requests sampling via ctx.mcpReq.requestSampling()'
|
||||
},
|
||||
async ctx => {
|
||||
samplingResult = await ctx.mcpReq.requestSampling({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: { type: 'text', text: 'Hello' }
|
||||
}
|
||||
],
|
||||
maxTokens: 100
|
||||
});
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: 'done' }]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test client', version: '1.0' }, { capabilities: { sampling: {} } });
|
||||
|
||||
client.setRequestHandler('sampling/createMessage', async () => ({
|
||||
model: 'test-model',
|
||||
role: 'assistant' as const,
|
||||
content: { type: 'text' as const, text: 'Hello back' }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
|
||||
|
||||
await client.callTool({ name: 'sampling-test' });
|
||||
|
||||
expect(samplingResult).toMatchObject({
|
||||
model: 'test-model',
|
||||
role: 'assistant',
|
||||
content: { type: 'text', text: 'Hello back' }
|
||||
});
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Progress Notification with Message Field
|
||||
*/
|
||||
|
|
@ -112,13 +265,13 @@ describe('Zod v4', () => {
|
|||
steps: z.number().min(1).describe('Number of steps to perform')
|
||||
})
|
||||
},
|
||||
async ({ steps }, { sendNotification, _meta }) => {
|
||||
const progressToken = _meta?.progressToken;
|
||||
async ({ steps }, ctx) => {
|
||||
const progressToken = ctx.mcpReq._meta?.progressToken;
|
||||
|
||||
if (progressToken) {
|
||||
// Send progress notification for each step
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
await sendNotification({
|
||||
await ctx.mcpReq.notify({
|
||||
method: 'notifications/progress',
|
||||
params: {
|
||||
progressToken,
|
||||
|
|
@ -1320,7 +1473,7 @@ describe('Zod v4', () => {
|
|||
/***
|
||||
* Test: Pass Session ID to Tool Callback
|
||||
*/
|
||||
test('should pass sessionId to tool callback via RequestHandlerExtra', async () => {
|
||||
test('should pass sessionId to tool callback via ServerContext', async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
|
|
@ -1332,8 +1485,8 @@ describe('Zod v4', () => {
|
|||
});
|
||||
|
||||
let receivedSessionId: string | undefined;
|
||||
mcpServer.registerTool('test-tool', {}, async extra => {
|
||||
receivedSessionId = extra.sessionId;
|
||||
mcpServer.registerTool('test-tool', {}, async ctx => {
|
||||
receivedSessionId = ctx.sessionId;
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
|
|
@ -1366,7 +1519,7 @@ describe('Zod v4', () => {
|
|||
/***
|
||||
* Test: Pass Request ID to Tool Callback
|
||||
*/
|
||||
test('should pass requestId to tool callback via RequestHandlerExtra', async () => {
|
||||
test('should pass requestId to tool callback via ServerContext', async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
|
|
@ -1378,13 +1531,13 @@ describe('Zod v4', () => {
|
|||
});
|
||||
|
||||
let receivedRequestId: string | number | undefined;
|
||||
mcpServer.registerTool('request-id-test', {}, async extra => {
|
||||
receivedRequestId = extra.requestId;
|
||||
mcpServer.registerTool('request-id-test', {}, async ctx => {
|
||||
receivedRequestId = ctx.mcpReq.id;
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Received request ID: ${extra.requestId}`
|
||||
text: `Received request ID: ${ctx.mcpReq.id}`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -1440,8 +1593,8 @@ describe('Zod v4', () => {
|
|||
receivedLogMessage = notification.params.data as string;
|
||||
});
|
||||
|
||||
mcpServer.registerTool('test-tool', {}, async extra => {
|
||||
await extra.sendNotification({
|
||||
mcpServer.registerTool('test-tool', {}, async ctx => {
|
||||
await ctx.mcpReq.notify({
|
||||
method: 'notifications/message',
|
||||
params: { level: 'debug', data: loggingMessage }
|
||||
});
|
||||
|
|
@ -1787,17 +1940,17 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000 });
|
||||
createTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000 });
|
||||
return { task };
|
||||
},
|
||||
getTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) throw new Error('Task not found');
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async (_args, extra) => {
|
||||
return (await extra.taskStore.getTaskResult(extra.taskId)) as CallToolResult;
|
||||
getTaskResult: async (_args, ctx) => {
|
||||
return (await ctx.task.store.getTaskResult(ctx.task.id)) as CallToolResult;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -1856,17 +2009,17 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000 });
|
||||
createTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000 });
|
||||
return { task };
|
||||
},
|
||||
getTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) throw new Error('Task not found');
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async (_args, extra) => {
|
||||
return (await extra.taskStore.getTaskResult(extra.taskId)) as CallToolResult;
|
||||
getTaskResult: async (_args, ctx) => {
|
||||
return (await ctx.task.store.getTaskResult(ctx.task.id)) as CallToolResult;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -2841,7 +2994,7 @@ describe('Zod v4', () => {
|
|||
/***
|
||||
* Test: Pass Request ID to Resource Callback
|
||||
*/
|
||||
test('should pass requestId to resource callback via RequestHandlerExtra', async () => {
|
||||
test('should pass requestId to resource callback via ServerContext', async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
|
|
@ -2853,13 +3006,13 @@ describe('Zod v4', () => {
|
|||
});
|
||||
|
||||
let receivedRequestId: string | number | undefined;
|
||||
mcpServer.registerResource('request-id-test', 'test://resource', {}, async (_uri, extra) => {
|
||||
receivedRequestId = extra.requestId;
|
||||
mcpServer.registerResource('request-id-test', 'test://resource', {}, async (_uri, ctx) => {
|
||||
receivedRequestId = ctx.mcpReq.id;
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'test://resource',
|
||||
text: `Received request ID: ${extra.requestId}`
|
||||
text: `Received request ID: ${ctx.mcpReq.id}`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -3779,7 +3932,7 @@ describe('Zod v4', () => {
|
|||
/***
|
||||
* Test: Pass Request ID to Prompt Callback
|
||||
*/
|
||||
test('should pass requestId to prompt callback via RequestHandlerExtra', async () => {
|
||||
test('should pass requestId to prompt callback via ServerContext', async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
|
|
@ -3791,15 +3944,15 @@ describe('Zod v4', () => {
|
|||
});
|
||||
|
||||
let receivedRequestId: string | number | undefined;
|
||||
mcpServer.registerPrompt('request-id-test', {}, async extra => {
|
||||
receivedRequestId = extra.requestId;
|
||||
mcpServer.registerPrompt('request-id-test', {}, async ctx => {
|
||||
receivedRequestId = ctx.mcpReq.id;
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `Received request ID: ${extra.requestId}`
|
||||
text: `Received request ID: ${ctx.mcpReq.id}`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -6219,11 +6372,11 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async ({ input }, extra) => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
createTask: async ({ input }, ctx) => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
|
||||
// Capture taskStore for use in setTimeout
|
||||
const store = extra.taskStore;
|
||||
const store = ctx.task.store;
|
||||
|
||||
// Simulate async work
|
||||
setTimeout(async () => {
|
||||
|
|
@ -6234,15 +6387,15 @@ describe('Zod v4', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
getTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async (_input, extra) => {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
getTaskResult: async (_input, ctx) => {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as CallToolResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -6324,11 +6477,11 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async ({ value }, extra) => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
createTask: async ({ value }, ctx) => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
|
||||
// Capture taskStore for use in setTimeout
|
||||
const store = extra.taskStore;
|
||||
const store = ctx.task.store;
|
||||
|
||||
// Simulate async work
|
||||
setTimeout(async () => {
|
||||
|
|
@ -6340,15 +6493,15 @@ describe('Zod v4', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
getTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async (_value, extra) => {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
getTaskResult: async (_value, ctx) => {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as CallToolResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -6432,11 +6585,11 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async ({ data }, extra) => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
createTask: async ({ data }, ctx) => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
|
||||
// Capture taskStore for use in setTimeout
|
||||
const store = extra.taskStore;
|
||||
const store = ctx.task.store;
|
||||
|
||||
// Simulate async work
|
||||
setTimeout(async () => {
|
||||
|
|
@ -6448,15 +6601,15 @@ describe('Zod v4', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
getTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async (_data, extra) => {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
getTaskResult: async (_data, ctx) => {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as CallToolResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -6549,11 +6702,11 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async extra => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
createTask: async ctx => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
|
||||
// Capture taskStore for use in setTimeout
|
||||
const store = extra.taskStore;
|
||||
const store = ctx.task.store;
|
||||
|
||||
// Simulate async failure
|
||||
setTimeout(async () => {
|
||||
|
|
@ -6566,15 +6719,15 @@ describe('Zod v4', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
getTask: async extra => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async ctx => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async extra => {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
getTaskResult: async ctx => {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as CallToolResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -6655,11 +6808,11 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async extra => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
createTask: async ctx => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
|
||||
// Capture taskStore for use in setTimeout
|
||||
const store = extra.taskStore;
|
||||
const store = ctx.task.store;
|
||||
|
||||
// Simulate async cancellation
|
||||
setTimeout(async () => {
|
||||
|
|
@ -6669,15 +6822,15 @@ describe('Zod v4', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
getTask: async extra => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async ctx => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async extra => {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
getTaskResult: async ctx => {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as CallToolResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -6742,19 +6895,19 @@ describe('Zod v4', () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
createTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
createTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.createTask({ ttl: 60_000, pollInterval: 100 });
|
||||
return { task };
|
||||
},
|
||||
getTask: async (_args, extra) => {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
getTask: async (_args, ctx) => {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
return task;
|
||||
},
|
||||
getTaskResult: async (_args, extra) => {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
getTaskResult: async (_args, ctx) => {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as CallToolResult;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ duration, shouldFail }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
async createTask({ duration, shouldFail }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: 60_000,
|
||||
pollInterval: 100
|
||||
});
|
||||
|
|
@ -75,11 +75,11 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
try {
|
||||
await (shouldFail
|
||||
? extra.taskStore.storeTaskResult(task.taskId, 'failed', {
|
||||
? ctx.task.store.storeTaskResult(task.taskId, 'failed', {
|
||||
content: [{ type: 'text', text: 'Task failed as requested' }],
|
||||
isError: true
|
||||
})
|
||||
: extra.taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
: ctx.task.store.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: `Completed after ${duration}ms` }]
|
||||
}));
|
||||
} catch {
|
||||
|
|
@ -89,15 +89,15 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -114,8 +114,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ userName }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
async createTask({ userName }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: 60_000,
|
||||
pollInterval: 100
|
||||
});
|
||||
|
|
@ -128,14 +128,14 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
if (userName) {
|
||||
// Complete immediately if userName was provided
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: `Hello, ${userName}!` }]
|
||||
});
|
||||
} catch {
|
||||
// Task may have been cleaned up if test ended
|
||||
}
|
||||
} else {
|
||||
const elicitationResult = await extra.sendRequest(
|
||||
const elicitationResult = await ctx.mcpReq.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
|
|
@ -160,7 +160,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
? elicitationResult.content.userName
|
||||
: 'Unknown';
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: `Hello, ${name}!` }]
|
||||
});
|
||||
} catch {
|
||||
|
|
@ -171,15 +171,15 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -418,8 +418,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ requestCount }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
async createTask({ requestCount }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: 60_000,
|
||||
pollInterval: 100
|
||||
});
|
||||
|
|
@ -432,7 +432,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
// Send multiple elicitation requests
|
||||
for (let i = 0; i < requestCount; i++) {
|
||||
const elicitationResult = await extra.sendRequest(
|
||||
const elicitationResult = await ctx.mcpReq.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
|
|
@ -458,7 +458,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
// Complete with all responses
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: `Received responses: ${responses.join(', ')}` }]
|
||||
});
|
||||
} catch {
|
||||
|
|
@ -468,15 +468,15 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -907,8 +907,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ messageCount }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
async createTask({ messageCount }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: 60_000,
|
||||
pollInterval: 100
|
||||
});
|
||||
|
|
@ -921,8 +921,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
// Queue multiple elicitation requests
|
||||
for (let i = 0; i < messageCount; i++) {
|
||||
// Send request but don't await - let it queue
|
||||
extra
|
||||
.sendRequest(
|
||||
ctx.mcpReq
|
||||
.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
|
|
@ -957,15 +957,15 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -1105,8 +1105,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ messageCount, delayBetweenMessages }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
async createTask({ messageCount, delayBetweenMessages }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: 60_000,
|
||||
pollInterval: 100
|
||||
});
|
||||
|
|
@ -1121,7 +1121,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
// Send messages with delays between them
|
||||
for (let i = 0; i < messageCount; i++) {
|
||||
const elicitationResult = await extra.sendRequest(
|
||||
const elicitationResult = await ctx.mcpReq.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
|
|
@ -1152,7 +1152,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
// Complete with all responses
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: `Received all responses: ${responses.join(', ')}` }]
|
||||
});
|
||||
} catch {
|
||||
|
|
@ -1161,7 +1161,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
} catch (error) {
|
||||
// Handle errors
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'failed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'failed', {
|
||||
content: [{ type: 'text', text: `Error: ${error}` }],
|
||||
isError: true
|
||||
});
|
||||
|
|
@ -1173,15 +1173,15 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
@ -1321,8 +1321,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
})
|
||||
},
|
||||
{
|
||||
async createTask({ messageCount }, extra) {
|
||||
const task = await extra.taskStore.createTask({
|
||||
async createTask({ messageCount }, ctx) {
|
||||
const task = await ctx.task.store.createTask({
|
||||
ttl: 60_000,
|
||||
pollInterval: 100
|
||||
});
|
||||
|
|
@ -1335,8 +1335,8 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
for (let i = 0; i < messageCount; i++) {
|
||||
// Start the request but don't wait for response
|
||||
// The request gets queued when sendRequest is called
|
||||
extra
|
||||
.sendRequest(
|
||||
ctx.mcpReq
|
||||
.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
|
|
@ -1361,7 +1361,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
// Complete the task after all messages are queued
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'completed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'completed', {
|
||||
content: [{ type: 'text', text: 'Task completed quickly' }]
|
||||
});
|
||||
} catch {
|
||||
|
|
@ -1370,7 +1370,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
} catch (error) {
|
||||
// Handle errors
|
||||
try {
|
||||
await extra.taskStore.storeTaskResult(task.taskId, 'failed', {
|
||||
await ctx.task.store.storeTaskResult(task.taskId, 'failed', {
|
||||
content: [{ type: 'text', text: `Error: ${error}` }],
|
||||
isError: true
|
||||
});
|
||||
|
|
@ -1382,15 +1382,15 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
|
||||
return { task };
|
||||
},
|
||||
async getTask(_args, extra) {
|
||||
const task = await extra.taskStore.getTask(extra.taskId);
|
||||
async getTask(_args, ctx) {
|
||||
const task = await ctx.task.store.getTask(ctx.task.id);
|
||||
if (!task) {
|
||||
throw new Error(`Task ${extra.taskId} not found`);
|
||||
throw new Error(`Task ${ctx.task.id} not found`);
|
||||
}
|
||||
return task;
|
||||
},
|
||||
async getTaskResult(_args, extra) {
|
||||
const result = await extra.taskStore.getTaskResult(extra.taskId);
|
||||
async getTaskResult(_args, ctx) {
|
||||
const result = await ctx.task.store.getTaskResult(ctx.task.id);
|
||||
return result as { content: Array<{ type: 'text'; text: string }> };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ describe('Zod v4', () => {
|
|||
message: z.string().describe('Message to send').default('Test notification')
|
||||
})
|
||||
},
|
||||
async ({ message }, { sendNotification }) => {
|
||||
async ({ message }, ctx) => {
|
||||
// Send notification immediately
|
||||
await sendNotification({
|
||||
await ctx.mcpReq.notify({
|
||||
method: 'notifications/message',
|
||||
params: {
|
||||
level: 'info',
|
||||
|
|
@ -93,10 +93,10 @@ describe('Zod v4', () => {
|
|||
interval: z.number().describe('Interval between notifications in ms').default(50)
|
||||
})
|
||||
},
|
||||
async ({ count, interval }, { sendNotification }) => {
|
||||
async ({ count, interval }, ctx) => {
|
||||
// Send notifications at specified intervals
|
||||
for (let i = 0; i < count; i++) {
|
||||
await sendNotification({
|
||||
await ctx.mcpReq.notify({
|
||||
method: 'notifications/message',
|
||||
params: {
|
||||
level: 'info',
|
||||
|
|
|
|||
Loading…
Reference in a new issue