feat(mcp): add credential and all-runs tools; replace deprecated z.string().uuid()

- add list_scenario_credentials, add_scenario_credential, remove_scenario_credential tools
- add list_all_runs tool (global across all scenarios)
- replace all deprecated z.string().uuid() with z.uuid() throughout mcp.service
This commit is contained in:
2026-04-14 14:45:55 +03:00
parent 7acd1bc5a1
commit 310997c3ae
+156 -25
View File
@@ -86,7 +86,7 @@ export class McpService {
{
description: "Delete a session by numeric ID (closes it first if open)",
inputSchema: {
id: z.string().uuid().describe("Session ID to delete"),
id: z.uuid().describe("Session ID to delete"),
},
},
async ({ id }) => {
@@ -151,7 +151,7 @@ export class McpService {
{
description: "Get an environment record by ID",
inputSchema: {
id: z.string().uuid().describe("Environment ID"),
id: z.uuid().describe("Environment ID"),
},
},
async ({ id }) => {
@@ -207,7 +207,7 @@ export class McpService {
{
description: "Update an existing environment (name and/or data)",
inputSchema: {
id: z.string().uuid().describe("Environment ID to update"),
id: z.uuid().describe("Environment ID to update"),
name: z.string().optional().describe("New name"),
data: z
.record(z.string(), z.string())
@@ -238,7 +238,7 @@ export class McpService {
{
description: "Delete an environment by ID",
inputSchema: {
id: z.string().uuid().describe("Environment ID to delete"),
id: z.uuid().describe("Environment ID to delete"),
},
},
async ({ id }) => {
@@ -392,7 +392,7 @@ export class McpService {
{
description: "Get a scenario with its steps by ID",
inputSchema: {
id: z.string().uuid().describe("Scenario ID"),
id: z.uuid().describe("Scenario ID"),
},
},
async ({ id }) => {
@@ -442,7 +442,7 @@ export class McpService {
{
description: "Update a scenario name",
inputSchema: {
id: z.string().uuid().describe("Scenario ID"),
id: z.uuid().describe("Scenario ID"),
name: z.string().optional().describe("New name"),
},
},
@@ -468,7 +468,7 @@ export class McpService {
{
description: "Delete a scenario by ID",
inputSchema: {
id: z.string().uuid().describe("Scenario ID"),
id: z.uuid().describe("Scenario ID"),
},
},
async ({ id }) => {
@@ -493,7 +493,7 @@ export class McpService {
{
description: "Add a step to a scenario",
inputSchema: {
scenarioId: z.string().uuid().describe("Parent scenario ID"),
scenarioId: z.uuid().describe("Parent scenario ID"),
order: z
.number()
.int()
@@ -527,8 +527,8 @@ export class McpService {
{
description: "Get a single step of a scenario",
inputSchema: {
scenarioId: z.string().uuid().describe("Scenario ID"),
stepId: z.string().uuid().describe("Step ID"),
scenarioId: z.uuid().describe("Scenario ID"),
stepId: z.uuid().describe("Step ID"),
},
},
async ({ scenarioId, stepId }) => {
@@ -551,8 +551,8 @@ export class McpService {
{
description: "Update a step within a scenario",
inputSchema: {
scenarioId: z.string().uuid().describe("Scenario ID"),
stepId: z.string().uuid().describe("Step ID"),
scenarioId: z.uuid().describe("Scenario ID"),
stepId: z.uuid().describe("Step ID"),
order: z
.number()
.int()
@@ -591,8 +591,8 @@ export class McpService {
{
description: "Delete a step from a scenario",
inputSchema: {
scenarioId: z.string().uuid().describe("Scenario ID"),
stepId: z.string().uuid().describe("Step ID"),
scenarioId: z.uuid().describe("Scenario ID"),
stepId: z.uuid().describe("Step ID"),
},
},
async ({ scenarioId, stepId }) => {
@@ -621,7 +621,7 @@ export class McpService {
description:
"List runs for a scenario (paginated, optionally filtered by status)",
inputSchema: {
scenarioId: z.string().uuid().describe("Scenario ID"),
scenarioId: z.uuid().describe("Scenario ID"),
status: z
.enum(["pending", "in_progress", "pass", "fail"])
.optional()
@@ -664,11 +664,8 @@ export class McpService {
{
description: "Trigger an immediate run of a scenario by ID",
inputSchema: {
id: z.string().uuid().describe("Scenario ID to run"),
environmentId: z
.string()
.uuid()
.describe("Environment ID to run the scenario in"),
id: z.uuid().describe("Scenario ID to run"),
environmentId: z.uuid().describe("Environment ID to run the scenario in"),
},
},
async ({ id, environmentId }) => {
@@ -692,8 +689,8 @@ export class McpService {
description:
"Get a specific scenario run with all step runs and their outputs",
inputSchema: {
scenarioId: z.string().uuid().describe("Scenario ID"),
runId: z.string().uuid().describe("Run ID"),
scenarioId: z.uuid().describe("Scenario ID"),
runId: z.uuid().describe("Run ID"),
},
},
async ({ scenarioId, runId }) => {
@@ -717,8 +714,8 @@ export class McpService {
description:
"Block until a scenario run reaches pass or fail (max 5 min), then return the full run with step outputs",
inputSchema: {
scenarioId: z.string().uuid().describe("Scenario ID"),
runId: z.string().uuid().describe("Run ID"),
scenarioId: z.uuid().describe("Scenario ID"),
runId: z.uuid().describe("Run ID"),
},
},
async ({ scenarioId, runId }) => {
@@ -736,13 +733,147 @@ export class McpService {
},
);
server.registerTool(
"list_scenario_credentials",
{
description: "List credentials assigned to a scenario",
inputSchema: {
scenarioId: z.uuid().describe("Scenario ID"),
},
},
async ({ scenarioId }) => {
try {
const creds =
await this.scenarioService.findScenarioCredentials(scenarioId);
return {
content: [{ type: "text" as const, text: JSON.stringify(creds) }],
};
} catch (err) {
return {
isError: true,
content: [{ type: "text" as const, text: (err as Error).message }],
};
}
},
);
server.registerTool(
"add_scenario_credential",
{
description:
"Add a credential to a scenario with an alias (used in snippets)",
inputSchema: {
scenarioId: z.uuid().describe("Scenario ID"),
credentialId: z.uuid().describe("Credential ID to associate"),
alias: z
.string()
.describe(
"Alias used to reference this credential in step code, e.g. pkcs_key",
),
},
},
async ({ scenarioId, credentialId, alias }) => {
try {
const result = await this.scenarioService.addScenarioCredential(
scenarioId,
{ credentialId, alias },
);
return {
content: [
{ type: "text" as const, text: JSON.stringify(result) },
],
};
} catch (err) {
return {
isError: true,
content: [{ type: "text" as const, text: (err as Error).message }],
};
}
},
);
server.registerTool(
"remove_scenario_credential",
{
description: "Remove a credential assignment from a scenario",
inputSchema: {
scenarioId: z.uuid().describe("Scenario ID"),
scenarioCredentialId: z.uuid().describe("Scenario-credential assignment ID (not the credential ID)"),
},
},
async ({ scenarioId, scenarioCredentialId }) => {
try {
await this.scenarioService.removeScenarioCredential(
scenarioId,
scenarioCredentialId,
);
return {
content: [
{
type: "text" as const,
text: `Credential assignment ${scenarioCredentialId} removed from scenario ${scenarioId}`,
},
],
};
} catch (err) {
return {
isError: true,
content: [{ type: "text" as const, text: (err as Error).message }],
};
}
},
);
server.registerTool(
"list_all_runs",
{
description:
"List runs across all scenarios (paginated, filterable by status)",
inputSchema: {
status: z
.enum(["pending", "in_progress", "pass", "fail"])
.optional()
.describe("Filter by run status"),
page: z
.number()
.int()
.min(1)
.optional()
.describe("Page number (default 1)"),
limit: z
.number()
.int()
.min(1)
.optional()
.describe("Items per page (default 20)"),
},
},
async ({ status, page, limit }) => {
try {
const result = await this.scenarioService.findAllRuns({
status,
page,
limit,
});
return {
content: [{ type: "text" as const, text: JSON.stringify(result) }],
};
} catch (err) {
return {
isError: true,
content: [{ type: "text" as const, text: (err as Error).message }],
};
}
},
);
server.registerTool(
"export_scenario",
{
description:
"Export a scenario as a portable JSON payload (name + steps)",
inputSchema: {
id: z.string().uuid().describe("Scenario ID to export"),
id: z.uuid().describe("Scenario ID to export"),
},
},
async ({ id }) => {