#[cfg(feature = "agentic-worker")]
mod aw {
use std::sync::Arc;
use serde_json::{Value, json};
use greentic_aw_runtime::{MCP_ROLE_FLOW_EDITOR, McpToolSource, TenantContext, dispatch_route};
pub(crate) fn source_from_env() -> Option<Arc<McpToolSource>> {
if std::env::var("GREENTIC_AW_MCP").ok().as_deref() == Some("0") {
tracing::info!("GREENTIC_AW_MCP=0; flow MCP node source disabled");
return None;
}
let endpoint = std::env::var("GREENTIC_AW_ADMIN_ENDPOINT")
.ok()
.filter(|s| !s.is_empty())?;
let token = std::env::var("GREENTIC_AW_ADMIN_TOKEN")
.ok()
.filter(|s| !s.is_empty())?;
tracing::info!(endpoint = %endpoint, "flow MCP node source constructed");
Some(Arc::new(McpToolSource::new(endpoint, token)))
}
pub(crate) async fn invoke(
source: Option<&Arc<McpToolSource>>,
tenant: &str,
env: &str,
server_id: &str,
tool: &str,
arguments: &Value,
) -> Value {
let Some(source) = source else {
return json!({
"error": "MCP is not configured on this runner (set GREENTIC_AW_ADMIN_ENDPOINT + GREENTIC_AW_ADMIN_TOKEN)"
});
};
let tenant_ctx = TenantContext::new(tenant, env);
let catalog = source
.catalog_for_role(&tenant_ctx, MCP_ROLE_FLOW_EDITOR)
.await;
let Some(route) = catalog.route(server_id, tool) else {
return json!({
"error": format!(
"mcp tool '{server_id}/{tool}' not found in the tenant's flow_editor catalog"
)
});
};
let args_str = arguments.to_string();
dispatch_route(route, &args_str).await
}
}
#[cfg(feature = "agentic-worker")]
pub(crate) use aw::{invoke, source_from_env};
use serde_json::Value;
pub(crate) fn str_field(payload: &Value, key: &str) -> Option<String> {
payload
.get(key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
}
pub(crate) fn server_tool_from_payload(payload: &Value) -> Option<(String, String)> {
let server = str_field(payload, "server")?;
let tool = str_field(payload, "tool")?;
Some((server, tool))
}
#[cfg(test)]
mod tests {
use super::{server_tool_from_payload, str_field};
use serde_json::json;
#[test]
fn reads_server_and_tool_from_payload() {
assert_eq!(
server_tool_from_payload(&json!({ "server": "github", "tool": "get_issue" })),
Some(("github".to_string(), "get_issue".to_string()))
);
}
#[test]
fn tolerates_dotted_tool_names_in_payload() {
assert_eq!(
server_tool_from_payload(&json!({ "server": "srv", "tool": "do.thing" })),
Some(("srv".to_string(), "do.thing".to_string()))
);
}
#[test]
fn missing_or_empty_fields_yield_none() {
assert_eq!(
server_tool_from_payload(&json!({ "server": "github" })),
None
);
assert_eq!(
server_tool_from_payload(&json!({ "server": "", "tool": "get_issue" })),
None
);
assert_eq!(
server_tool_from_payload(&json!({ "server": "github", "tool": " " })),
None
);
assert_eq!(server_tool_from_payload(&json!("not an object")), None);
}
#[test]
fn str_field_trims_and_rejects_empty() {
assert_eq!(
str_field(&json!({ "k": " v " }), "k"),
Some("v".to_string())
);
assert_eq!(str_field(&json!({ "k": "" }), "k"), None);
assert_eq!(str_field(&json!({ "k": 7 }), "k"), None);
assert_eq!(str_field(&json!({}), "missing"), None);
}
}