mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: expose getJob and getJobLogs as MCP tools (#8632)
* feat: expose getJob and getJobLogs as MCP tools Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add integration test for getJob/getJobLogs MCP endpoint tools Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add MCP client integration test for getJob and getJobLogs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+1
@@ -16382,6 +16382,7 @@ dependencies = [
|
||||
"rand 0.9.0",
|
||||
"rdkafka",
|
||||
"reqwest 0.13.1",
|
||||
"rmcp",
|
||||
"rumqttc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -13,7 +13,7 @@ default = []
|
||||
private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential-types", "dep:aws-sdk-sqs", "windmill-git-sync/private"]
|
||||
enterprise = ["windmill-test-utils/enterprise", "dep:base64", "windmill-git-sync/enterprise"]
|
||||
deno_core = ["windmill-test-utils/deno_core"]
|
||||
mcp = []
|
||||
mcp = ["windmill-test-utils/mcp", "dep:rmcp"]
|
||||
run_inline = ["dep:windmill-worker", "windmill-test-utils/run_inline", "windmill-test-utils/duckdb"]
|
||||
|
||||
[dependencies]
|
||||
@@ -41,3 +41,4 @@ aws-credential-types = { workspace = true, optional = true }
|
||||
aws-sdk-sqs = { workspace = true, optional = true }
|
||||
base64 = { workspace = true, optional = true }
|
||||
axum.workspace = true
|
||||
rmcp = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
#[cfg(feature = "mcp")]
|
||||
use uuid::Uuid;
|
||||
|
||||
use windmill_test_utils::*;
|
||||
|
||||
@@ -518,3 +520,182 @@ async fn test_mcp_tools(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_mcp_endpoint_tools_list(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let resp = authed(client().get(format!(
|
||||
"http://localhost:{port}/api/mcp/w/test-workspace/list_tools"
|
||||
)))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
let tools: Vec<serde_json::Value> = resp.json().await?;
|
||||
|
||||
let tool_names: Vec<&str> = tools.iter().filter_map(|t| t["name"].as_str()).collect();
|
||||
|
||||
assert!(
|
||||
tool_names.contains(&"getJob"),
|
||||
"getJob not found in MCP endpoint tools: {tool_names:?}"
|
||||
);
|
||||
assert!(
|
||||
tool_names.contains(&"getJobLogs"),
|
||||
"getJobLogs not found in MCP endpoint tools: {tool_names:?}"
|
||||
);
|
||||
|
||||
// Verify getJob has the expected path and method
|
||||
let get_job_tool = tools.iter().find(|t| t["name"] == "getJob").unwrap();
|
||||
assert_eq!(get_job_tool["path"], "/w/{workspace}/jobs_u/get/{id}");
|
||||
assert_eq!(get_job_tool["method"], "GET");
|
||||
|
||||
// Verify getJobLogs has the expected path and method
|
||||
let get_job_logs_tool = tools.iter().find(|t| t["name"] == "getJobLogs").unwrap();
|
||||
assert_eq!(
|
||||
get_job_logs_tool["path"],
|
||||
"/w/{workspace}/jobs_u/get_logs/{id}"
|
||||
);
|
||||
assert_eq!(get_job_logs_tool["method"], "GET");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
async fn insert_completed_job_with_logs(db: &Pool<Postgres>) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
sqlx::query(
|
||||
"INSERT INTO v2_job (id, workspace_id, created_by, permissioned_as, kind, tag, args)
|
||||
VALUES ($1, 'test-workspace', 'test-user', 'u/test-user', 'script', 'deno', '{}'::jsonb)",
|
||||
)
|
||||
.bind(id)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO v2_job_completed (id, workspace_id, duration_ms, result, status)
|
||||
VALUES ($1, 'test-workspace', 100, '42'::jsonb, 'success')",
|
||||
)
|
||||
.bind(id)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO job_logs (job_id, workspace_id, logs, log_offset)
|
||||
VALUES ($1, 'test-workspace', 'hello world test log', 0)",
|
||||
)
|
||||
.bind(id)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_mcp_client_get_job_and_logs(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use rmcp::model::{
|
||||
CallToolRequestParams, ClientCapabilities, ClientInfo, Implementation,
|
||||
InitializeRequestParams,
|
||||
};
|
||||
use rmcp::service::{RoleClient, RunningService};
|
||||
use rmcp::transport::streamable_http_client::{
|
||||
StreamableHttpClientTransport, StreamableHttpClientTransportConfig,
|
||||
};
|
||||
use rmcp::ServiceExt;
|
||||
|
||||
initialize_tracing().await;
|
||||
set_jwt_secret().await;
|
||||
let server = ApiServer::start_mcp(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
let job_id = insert_completed_job_with_logs(&db).await;
|
||||
|
||||
// Create a token with MCP scopes
|
||||
sqlx::query(
|
||||
"INSERT INTO token (token_hash, token_prefix, token, email, label, super_admin, scopes)
|
||||
VALUES (encode(sha256('MCP_TOKEN'::bytea), 'hex'), 'MCP_TOK', 'MCP_TOKEN', 'test@windmill.dev', 'mcp token', true, ARRAY['mcp:all'])",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Connect as MCP client
|
||||
let config = StreamableHttpClientTransportConfig::with_uri(format!(
|
||||
"http://localhost:{port}/api/mcp/w/test-workspace/mcp"
|
||||
))
|
||||
.auth_header("MCP_TOKEN");
|
||||
let transport = StreamableHttpClientTransport::from_config(config);
|
||||
|
||||
let client_info = ClientInfo {
|
||||
protocol_version: Default::default(),
|
||||
capabilities: ClientCapabilities::default(),
|
||||
client_info: Implementation {
|
||||
name: "test-client".to_string(),
|
||||
title: None,
|
||||
version: "0.0.1".to_string(),
|
||||
description: None,
|
||||
website_url: None,
|
||||
icons: None,
|
||||
},
|
||||
meta: None,
|
||||
};
|
||||
|
||||
let client: RunningService<RoleClient, InitializeRequestParams> =
|
||||
client_info.serve(transport).await?;
|
||||
|
||||
// --- Test getJob ---
|
||||
let result = client
|
||||
.call_tool(CallToolRequestParams {
|
||||
name: "getJob".into(),
|
||||
arguments: Some(serde_json::from_value(json!({ "id": job_id.to_string() }))?),
|
||||
task: None,
|
||||
meta: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let text = result
|
||||
.content
|
||||
.first()
|
||||
.and_then(|c| c.raw.as_text())
|
||||
.expect("getJob should return text content");
|
||||
let job: serde_json::Value = serde_json::from_str(&text.text)?;
|
||||
assert_eq!(job["id"], job_id.to_string());
|
||||
assert_eq!(job["workspace_id"], "test-workspace");
|
||||
assert_eq!(job["created_by"], "test-user");
|
||||
assert_eq!(job["job_kind"], "script");
|
||||
assert!(
|
||||
job["success"].as_bool().unwrap_or(false),
|
||||
"job should be successful: {job}"
|
||||
);
|
||||
|
||||
// --- Test getJobLogs ---
|
||||
let result = client
|
||||
.call_tool(CallToolRequestParams {
|
||||
name: "getJobLogs".into(),
|
||||
arguments: Some(serde_json::from_value(json!({ "id": job_id.to_string() }))?),
|
||||
task: None,
|
||||
meta: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let text = result
|
||||
.content
|
||||
.first()
|
||||
.and_then(|c| c.raw.as_text())
|
||||
.expect("getJobLogs should return text content");
|
||||
// The logs endpoint returns text/plain, which gets wrapped as a JSON string by call_endpoint
|
||||
let logs: String = serde_json::from_str(&text.text)?;
|
||||
assert!(
|
||||
logs.contains("hello world test log"),
|
||||
"expected logs to contain test log, got: {logs}"
|
||||
);
|
||||
|
||||
client.cancel().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10598,6 +10598,7 @@ paths:
|
||||
get:
|
||||
summary: get job
|
||||
operationId: getJob
|
||||
x-mcp-tool: true
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
@@ -10639,7 +10640,8 @@ paths:
|
||||
/w/{workspace}/jobs_u/get_logs/{id}:
|
||||
get:
|
||||
summary: get job logs
|
||||
operationId: getJob logs
|
||||
operationId: getJobLogs
|
||||
x-mcp-tool: true
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
|
||||
@@ -221,6 +221,22 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"type": "string",
|
||||
"description": "filter variables by path prefix"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "exact path match filter"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for description field (case-insensitive)"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for non-secret variable values (case-insensitive)"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "which page to return (start at 1, default 1)"
|
||||
@@ -405,6 +421,22 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
"description": "filter resources by path prefix"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "exact path match filter"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for description field (case-insensitive)"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "JSONB subset match filter using base64 encoded JSON"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -451,7 +483,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
@@ -562,7 +594,6 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"required": [
|
||||
"path",
|
||||
"summary",
|
||||
"description",
|
||||
"content",
|
||||
"language"
|
||||
]
|
||||
@@ -708,7 +739,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
@@ -1078,6 +1109,37 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"lock": {
|
||||
"type": "string"
|
||||
},
|
||||
"flow_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"modules": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"description": "Additional script modules keyed by relative file path",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"description": "An additional module file associated with a script",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "The source code content of this module"
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative"
|
||||
},
|
||||
"lock": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Lock file content for this module's dependencies"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content",
|
||||
"language"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1106,7 +1168,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"parent_job": {
|
||||
"type": "string",
|
||||
@@ -1115,15 +1177,15 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"worker": {
|
||||
"type": "string",
|
||||
"description": "worker this job was ran on"
|
||||
"description": "filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2')"
|
||||
},
|
||||
"script_path_exact": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching path"
|
||||
"description": "filter by exact matching script path. Supports comma-separated list (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2')"
|
||||
},
|
||||
"script_path_start": {
|
||||
"type": "string",
|
||||
"description": "mask to filter matching starting path"
|
||||
"description": "filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2')"
|
||||
},
|
||||
"schedule_path": {
|
||||
"type": "string",
|
||||
@@ -1131,11 +1193,11 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"trigger_path": {
|
||||
"type": "string",
|
||||
"description": "mask to filter by trigger path"
|
||||
"description": "filter by trigger path. Supports comma-separated list (e.g. 'f/trigger1,f/trigger2') and negation by prefixing all values with '!' (e.g. '!f/trigger1,!f/trigger2')"
|
||||
},
|
||||
"trigger_kind": {
|
||||
"description": "trigger kind (schedule, http, websocket...). Possible values: webhook, default_email, email, schedule, http, websocket, postgres, kafka, nats, mqtt, sqs, gcp",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')"
|
||||
},
|
||||
"script_hash": {
|
||||
"type": "string",
|
||||
@@ -1161,7 +1223,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"job_kinds": {
|
||||
"type": "string",
|
||||
"description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by,"
|
||||
"description": "filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies')"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
@@ -1185,7 +1247,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "filter on jobs with a given tag/worker group"
|
||||
"description": "filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem')"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
@@ -1223,15 +1285,15 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"properties": {
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels')"
|
||||
"description": "filter by exact matching job label. Supports comma-separated list (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release')"
|
||||
},
|
||||
"worker": {
|
||||
"type": "string",
|
||||
"description": "worker this job was ran on"
|
||||
"description": "filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2')"
|
||||
},
|
||||
"parent_job": {
|
||||
"type": "string",
|
||||
@@ -1240,11 +1302,11 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"script_path_exact": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching path"
|
||||
"description": "filter by exact matching script path. Supports comma-separated list (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2')"
|
||||
},
|
||||
"script_path_start": {
|
||||
"type": "string",
|
||||
"description": "mask to filter matching starting path"
|
||||
"description": "filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2')"
|
||||
},
|
||||
"schedule_path": {
|
||||
"type": "string",
|
||||
@@ -1304,7 +1366,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"job_kinds": {
|
||||
"type": "string",
|
||||
"description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by,"
|
||||
"description": "filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies')"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
@@ -1316,7 +1378,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "filter on jobs with a given tag/worker group"
|
||||
"description": "filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem')"
|
||||
},
|
||||
"result": {
|
||||
"type": "string",
|
||||
@@ -1331,8 +1393,8 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"description": "number of items to return for a given page (default 30, max 100)"
|
||||
},
|
||||
"trigger_kind": {
|
||||
"description": "trigger kind (schedule, http, websocket...). Possible values: webhook, default_email, email, schedule, http, websocket, postgres, kafka, nats, mqtt, sqs, gcp",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')"
|
||||
},
|
||||
"is_skipped": {
|
||||
"type": "boolean",
|
||||
@@ -1357,6 +1419,77 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"is_not_schedule": {
|
||||
"type": "boolean",
|
||||
"description": "is not a scheduled job"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match on path, tag, schedule path, trigger kind, label)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
})),
|
||||
body_schema: None,
|
||||
path_field_renames: None,
|
||||
query_field_renames: None,
|
||||
body_field_renames: None,
|
||||
},
|
||||
EndpointTool {
|
||||
name: Cow::Borrowed("getJob"),
|
||||
description: Cow::Borrowed("get job"),
|
||||
instructions: Cow::Borrowed(""),
|
||||
path: Cow::Borrowed("/w/{workspace}/jobs_u/get/{id}"),
|
||||
method: Cow::Borrowed("GET"),
|
||||
path_params_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
})),
|
||||
query_params_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"no_logs": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"no_code": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
})),
|
||||
body_schema: None,
|
||||
path_field_renames: None,
|
||||
query_field_renames: None,
|
||||
body_field_renames: None,
|
||||
},
|
||||
EndpointTool {
|
||||
name: Cow::Borrowed("getJobLogs"),
|
||||
description: Cow::Borrowed("get job logs"),
|
||||
instructions: Cow::Borrowed(""),
|
||||
path: Cow::Borrowed("/w/{workspace}/jobs_u/get_logs/{id}"),
|
||||
method: Cow::Borrowed("GET"),
|
||||
path_params_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
})),
|
||||
query_params_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"remove_ansi_warnings": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -1411,14 +1544,17 @@ You should get the schema of the script or flow before creating the schedule to
|
||||
},
|
||||
"on_failure": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the scheduled job fails"
|
||||
},
|
||||
"on_failure_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive failures before the on_failure handler is triggered (default 1)"
|
||||
},
|
||||
"on_failure_exact": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true, trigger on_failure handler only on exactly N failures, not on every failure after N"
|
||||
},
|
||||
"on_failure_extra_args": {
|
||||
@@ -1428,10 +1564,12 @@ You should get the schema of the script or flow before creating the schedule to
|
||||
},
|
||||
"on_recovery": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the schedule recovers after failures"
|
||||
},
|
||||
"on_recovery_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive successes before the on_recovery handler is triggered (default 1)"
|
||||
},
|
||||
"on_recovery_extra_args": {
|
||||
@@ -1441,6 +1579,7 @@ You should get the schema of the script or flow before creating the schedule to
|
||||
},
|
||||
"on_success": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run after each successful execution"
|
||||
},
|
||||
"on_success_extra_args": {
|
||||
@@ -1516,28 +1655,42 @@ You should get the schema of the script or flow before creating the schedule to
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Short summary describing the purpose of this schedule"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Detailed description of what this schedule does"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Worker tag to route jobs to specific worker groups"
|
||||
},
|
||||
"paused_until": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 datetime until which the schedule is paused. Schedule resumes automatically after this time"
|
||||
},
|
||||
"cron_version": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Cron parser version. Use 'v2' for extended syntax with additional features"
|
||||
},
|
||||
"dynamic_skip": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)"
|
||||
},
|
||||
"permissioned_as": {
|
||||
"type": "string",
|
||||
"description": "The user or group this schedule runs as. Used during deployment to preserve the original schedule owner."
|
||||
},
|
||||
"preserve_permissioned_as": {
|
||||
"type": "boolean",
|
||||
"description": "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1592,14 +1745,17 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
},
|
||||
"on_failure": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the scheduled job fails"
|
||||
},
|
||||
"on_failure_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive failures before the on_failure handler is triggered (default 1)"
|
||||
},
|
||||
"on_failure_exact": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true, trigger on_failure handler only on exactly N failures, not on every failure after N"
|
||||
},
|
||||
"on_failure_extra_args": {
|
||||
@@ -1609,10 +1765,12 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
},
|
||||
"on_recovery": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the schedule recovers after failures"
|
||||
},
|
||||
"on_recovery_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive successes before the on_recovery handler is triggered (default 1)"
|
||||
},
|
||||
"on_recovery_extra_args": {
|
||||
@@ -1622,6 +1780,7 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
},
|
||||
"on_success": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run after each successful execution"
|
||||
},
|
||||
"on_success_extra_args": {
|
||||
@@ -1697,28 +1856,44 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Short summary describing the purpose of this schedule"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Detailed description of what this schedule does"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Worker tag to route jobs to specific worker groups"
|
||||
},
|
||||
"paused_until": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 datetime until which the schedule is paused. Schedule resumes automatically after this time"
|
||||
},
|
||||
"cron_version": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Cron parser version. Use 'v2' for extended syntax with additional features"
|
||||
},
|
||||
"dynamic_skip": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)"
|
||||
},
|
||||
"permissioned_as": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup'). Only admins and wm_deployers can set this via preserve_permissioned_as."
|
||||
},
|
||||
"preserve_permissioned_as": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1801,7 +1976,7 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "filter by path"
|
||||
"description": "filter by path (script path)"
|
||||
},
|
||||
"is_flow": {
|
||||
"type": "boolean",
|
||||
@@ -1810,6 +1985,22 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
"description": "filter schedules by path prefix"
|
||||
},
|
||||
"schedule_path": {
|
||||
"type": "string",
|
||||
"description": "exact match on the schedule's path"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for description field (case-insensitive)"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for summary field (case-insensitive)"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
@@ -14,6 +14,7 @@ private = ["windmill-api/private"]
|
||||
enterprise = ["windmill-api/enterprise"]
|
||||
python = ["windmill-common/python"]
|
||||
deno_core = ["dep:windmill-runtime-nativets"]
|
||||
mcp = ["windmill-api/mcp"]
|
||||
agent_worker_server = ["dep:windmill-api-agent-workers"]
|
||||
run_inline = ["windmill-api/run_inline"]
|
||||
duckdb = ["windmill-worker/duckdb"]
|
||||
|
||||
@@ -81,20 +81,29 @@ pub struct ApiServer {
|
||||
|
||||
impl ApiServer {
|
||||
pub async fn start(db: Pool<Postgres>) -> anyhow::Result<Self> {
|
||||
Self::start_inner(db, false).await
|
||||
Self::start_inner(db, false, false).await
|
||||
}
|
||||
|
||||
pub async fn start_agent_mode(db: Pool<Postgres>) -> anyhow::Result<Self> {
|
||||
Self::start_inner(db, true).await
|
||||
Self::start_inner(db, true, false).await
|
||||
}
|
||||
|
||||
/// Start the API server with server_mode=true so trigger listeners are active.
|
||||
/// Alias for `start_agent_mode` with a clearer name for trigger e2e tests.
|
||||
pub async fn start_with_listeners(db: Pool<Postgres>) -> anyhow::Result<Self> {
|
||||
Self::start_inner(db, true).await
|
||||
Self::start_inner(db, true, false).await
|
||||
}
|
||||
|
||||
async fn start_inner(db: Pool<Postgres>, agent_mode: bool) -> anyhow::Result<Self> {
|
||||
/// Start the API server with mcp_mode=true so MCP routes are active.
|
||||
pub async fn start_mcp(db: Pool<Postgres>) -> anyhow::Result<Self> {
|
||||
Self::start_inner(db, false, true).await
|
||||
}
|
||||
|
||||
async fn start_inner(
|
||||
db: Pool<Postgres>,
|
||||
server_mode: bool,
|
||||
mcp_mode: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let (tx, rx) = tokio::sync::broadcast::channel::<()>(1);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
|
||||
@@ -114,8 +123,8 @@ impl ApiServer {
|
||||
listener,
|
||||
rx,
|
||||
port_tx,
|
||||
agent_mode,
|
||||
false,
|
||||
server_mode,
|
||||
mcp_mode,
|
||||
format!("http://localhost:{}", addr.port()),
|
||||
Some(name.clone()),
|
||||
));
|
||||
|
||||
@@ -231,6 +231,22 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"type": "string",
|
||||
"description": "filter variables by path prefix"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "exact path match filter"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for description field (case-insensitive)"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for non-secret variable values (case-insensitive)"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "which page to return (start at 1, default 1)"
|
||||
@@ -415,6 +431,22 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
"description": "filter resources by path prefix"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "exact path match filter"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for description field (case-insensitive)"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "JSONB subset match filter using base64 encoded JSON"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -461,7 +493,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
@@ -572,7 +604,6 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"required": [
|
||||
"path",
|
||||
"summary",
|
||||
"description",
|
||||
"content",
|
||||
"language"
|
||||
]
|
||||
@@ -718,7 +749,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
@@ -1088,6 +1119,37 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"lock": {
|
||||
"type": "string"
|
||||
},
|
||||
"flow_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"modules": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"description": "Additional script modules keyed by relative file path",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"description": "An additional module file associated with a script",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "The source code content of this module"
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative"
|
||||
},
|
||||
"lock": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Lock file content for this module's dependencies"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content",
|
||||
"language"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1116,7 +1178,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"parent_job": {
|
||||
"type": "string",
|
||||
@@ -1125,15 +1187,15 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"worker": {
|
||||
"type": "string",
|
||||
"description": "worker this job was ran on"
|
||||
"description": "filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2')"
|
||||
},
|
||||
"script_path_exact": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching path"
|
||||
"description": "filter by exact matching script path. Supports comma-separated list (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2')"
|
||||
},
|
||||
"script_path_start": {
|
||||
"type": "string",
|
||||
"description": "mask to filter matching starting path"
|
||||
"description": "filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2')"
|
||||
},
|
||||
"schedule_path": {
|
||||
"type": "string",
|
||||
@@ -1141,11 +1203,11 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"trigger_path": {
|
||||
"type": "string",
|
||||
"description": "mask to filter by trigger path"
|
||||
"description": "filter by trigger path. Supports comma-separated list (e.g. 'f/trigger1,f/trigger2') and negation by prefixing all values with '!' (e.g. '!f/trigger1,!f/trigger2')"
|
||||
},
|
||||
"trigger_kind": {
|
||||
"description": "trigger kind (schedule, http, websocket...). Possible values: webhook, default_email, email, schedule, http, websocket, postgres, kafka, nats, mqtt, sqs, gcp",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')"
|
||||
},
|
||||
"script_hash": {
|
||||
"type": "string",
|
||||
@@ -1171,7 +1233,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"job_kinds": {
|
||||
"type": "string",
|
||||
"description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by,"
|
||||
"description": "filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies')"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
@@ -1195,7 +1257,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "filter on jobs with a given tag/worker group"
|
||||
"description": "filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem')"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
@@ -1233,15 +1295,15 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"properties": {
|
||||
"created_by": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching user creator"
|
||||
"description": "filter by exact matching user creator. Supports comma-separated list (e.g. 'alice,bob') and negation by prefixing all values with '!' (e.g. '!alice,!bob')"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels')"
|
||||
"description": "filter by exact matching job label. Supports comma-separated list (e.g. 'deploy,release') and negation by prefixing all values with '!' (e.g. '!deploy,!release')"
|
||||
},
|
||||
"worker": {
|
||||
"type": "string",
|
||||
"description": "worker this job was ran on"
|
||||
"description": "filter by worker this job ran on. Supports comma-separated list (e.g. 'worker-1,worker-2') and negation by prefixing all values with '!' (e.g. '!worker-1,!worker-2')"
|
||||
},
|
||||
"parent_job": {
|
||||
"type": "string",
|
||||
@@ -1250,11 +1312,11 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"script_path_exact": {
|
||||
"type": "string",
|
||||
"description": "mask to filter exact matching path"
|
||||
"description": "filter by exact matching script path. Supports comma-separated list (e.g. 'f/script1,f/script2') and negation by prefixing all values with '!' (e.g. '!f/script1,!f/script2')"
|
||||
},
|
||||
"script_path_start": {
|
||||
"type": "string",
|
||||
"description": "mask to filter matching starting path"
|
||||
"description": "filter by script path prefix. Supports comma-separated list (e.g. 'f/folder1,f/folder2') and negation by prefixing all values with '!' (e.g. '!f/folder1,!f/folder2')"
|
||||
},
|
||||
"schedule_path": {
|
||||
"type": "string",
|
||||
@@ -1314,7 +1376,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"job_kinds": {
|
||||
"type": "string",
|
||||
"description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by,"
|
||||
"description": "filter by job kind. Supports comma-separated list of values ('preview', 'script', 'dependencies', 'flow') and negation by prefixing all values with '!' (e.g. '!preview,!dependencies')"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "boolean",
|
||||
@@ -1326,7 +1388,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "filter on jobs with a given tag/worker group"
|
||||
"description": "filter by tag/worker group. Supports comma-separated list (e.g. 'gpu,highmem') and negation by prefixing all values with '!' (e.g. '!gpu,!highmem')"
|
||||
},
|
||||
"result": {
|
||||
"type": "string",
|
||||
@@ -1341,8 +1403,8 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"description": "number of items to return for a given page (default 30, max 100)"
|
||||
},
|
||||
"trigger_kind": {
|
||||
"description": "trigger kind (schedule, http, websocket...). Possible values: webhook, default_email, email, schedule, http, websocket, postgres, kafka, nats, mqtt, sqs, gcp",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "filter by trigger kind. Supports comma-separated list (e.g. 'schedule,webhook') and negation by prefixing all values with '!' (e.g. '!schedule,!webhook')"
|
||||
},
|
||||
"is_skipped": {
|
||||
"type": "boolean",
|
||||
@@ -1367,6 +1429,77 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"is_not_schedule": {
|
||||
"type": "boolean",
|
||||
"description": "is not a scheduled job"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match on path, tag, schedule path, trigger kind, label)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
bodySchema: undefined,
|
||||
pathFieldRenames: undefined,
|
||||
queryFieldRenames: undefined,
|
||||
bodyFieldRenames: undefined
|
||||
},
|
||||
{
|
||||
name: "getJob",
|
||||
description: "get job",
|
||||
instructions: "",
|
||||
path: "/w/{workspace}/jobs_u/get/{id}",
|
||||
method: "GET",
|
||||
pathParamsSchema: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
queryParamsSchema: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"no_logs": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"no_code": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
bodySchema: undefined,
|
||||
pathFieldRenames: undefined,
|
||||
queryFieldRenames: undefined,
|
||||
bodyFieldRenames: undefined
|
||||
},
|
||||
{
|
||||
name: "getJobLogs",
|
||||
description: "get job logs",
|
||||
instructions: "",
|
||||
path: "/w/{workspace}/jobs_u/get_logs/{id}",
|
||||
method: "GET",
|
||||
pathParamsSchema: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
queryParamsSchema: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"remove_ansi_warnings": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -1418,14 +1551,17 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"on_failure": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the scheduled job fails"
|
||||
},
|
||||
"on_failure_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive failures before the on_failure handler is triggered (default 1)"
|
||||
},
|
||||
"on_failure_exact": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true, trigger on_failure handler only on exactly N failures, not on every failure after N"
|
||||
},
|
||||
"on_failure_extra_args": {
|
||||
@@ -1435,10 +1571,12 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"on_recovery": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the schedule recovers after failures"
|
||||
},
|
||||
"on_recovery_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive successes before the on_recovery handler is triggered (default 1)"
|
||||
},
|
||||
"on_recovery_extra_args": {
|
||||
@@ -1448,6 +1586,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"on_success": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run after each successful execution"
|
||||
},
|
||||
"on_success_extra_args": {
|
||||
@@ -1523,28 +1662,42 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Short summary describing the purpose of this schedule"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Detailed description of what this schedule does"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Worker tag to route jobs to specific worker groups"
|
||||
},
|
||||
"paused_until": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 datetime until which the schedule is paused. Schedule resumes automatically after this time"
|
||||
},
|
||||
"cron_version": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Cron parser version. Use 'v2' for extended syntax with additional features"
|
||||
},
|
||||
"dynamic_skip": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)"
|
||||
},
|
||||
"permissioned_as": {
|
||||
"type": "string",
|
||||
"description": "The user or group this schedule runs as. Used during deployment to preserve the original schedule owner."
|
||||
},
|
||||
"preserve_permissioned_as": {
|
||||
"type": "boolean",
|
||||
"description": "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1596,14 +1749,17 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"on_failure": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the scheduled job fails"
|
||||
},
|
||||
"on_failure_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive failures before the on_failure handler is triggered (default 1)"
|
||||
},
|
||||
"on_failure_exact": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true, trigger on_failure handler only on exactly N failures, not on every failure after N"
|
||||
},
|
||||
"on_failure_extra_args": {
|
||||
@@ -1613,10 +1769,12 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"on_recovery": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run when the schedule recovers after failures"
|
||||
},
|
||||
"on_recovery_times": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Number of consecutive successes before the on_recovery handler is triggered (default 1)"
|
||||
},
|
||||
"on_recovery_extra_args": {
|
||||
@@ -1626,6 +1784,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"on_success": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script or flow to run after each successful execution"
|
||||
},
|
||||
"on_success_extra_args": {
|
||||
@@ -1701,28 +1860,44 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Short summary describing the purpose of this schedule"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Detailed description of what this schedule does"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Worker tag to route jobs to specific worker groups"
|
||||
},
|
||||
"paused_until": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 datetime until which the schedule is paused. Schedule resumes automatically after this time"
|
||||
},
|
||||
"cron_version": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Cron parser version. Use 'v2' for extended syntax with additional features"
|
||||
},
|
||||
"dynamic_skip": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false)"
|
||||
},
|
||||
"permissioned_as": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup'). Only admins and wm_deployers can set this via preserve_permissioned_as."
|
||||
},
|
||||
"preserve_permissioned_as": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1805,7 +1980,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "filter by path"
|
||||
"description": "filter by path (script path)"
|
||||
},
|
||||
"is_flow": {
|
||||
"type": "boolean",
|
||||
@@ -1814,6 +1989,22 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"path_start": {
|
||||
"type": "string",
|
||||
"description": "filter schedules by path prefix"
|
||||
},
|
||||
"schedule_path": {
|
||||
"type": "string",
|
||||
"description": "exact match on the schedule's path"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for description field (case-insensitive)"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "pattern match filter for summary field (case-insensitive)"
|
||||
},
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
Reference in New Issue
Block a user