mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Co-authored-by: Chandrapal Badshah <12944530+Chan9390@users.noreply.github.com> Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com> Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com> Co-authored-by: Adrián Jesús Peña Rodríguez <adrianjpr@gmail.com> Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
205 lines
5.7 KiB
TypeScript
205 lines
5.7 KiB
TypeScript
import "server-only";
|
|
|
|
import type { StructuredTool } from "@langchain/core/tools";
|
|
import { tool } from "@langchain/core/tools";
|
|
import { addBreadcrumb, captureException } from "@sentry/nextjs";
|
|
import { z } from "zod";
|
|
|
|
import { getMCPTools, isMCPAvailable } from "@/lib/lighthouse/mcp-client";
|
|
|
|
/** Input type for describe_tool */
|
|
interface DescribeToolInput {
|
|
toolName: string;
|
|
}
|
|
|
|
/** Input type for execute_tool */
|
|
interface ExecuteToolInput {
|
|
toolName: string;
|
|
toolInput: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Get all available tools (MCP only)
|
|
*/
|
|
function getAllTools(): StructuredTool[] {
|
|
if (!isMCPAvailable()) {
|
|
return [];
|
|
}
|
|
return getMCPTools();
|
|
}
|
|
|
|
/**
|
|
* Describe a tool by getting its full schema
|
|
*/
|
|
export const describeTool = tool(
|
|
async ({ toolName }: DescribeToolInput) => {
|
|
const allTools = getAllTools();
|
|
|
|
if (allTools.length === 0) {
|
|
addBreadcrumb({
|
|
category: "meta-tool",
|
|
message: "describe_tool called but no tools available",
|
|
level: "warning",
|
|
data: { toolName },
|
|
});
|
|
|
|
return {
|
|
found: false,
|
|
message: "No tools available. MCP server may not be connected.",
|
|
};
|
|
}
|
|
|
|
// Find exact tool by name
|
|
const targetTool = allTools.find((t) => t.name === toolName);
|
|
|
|
if (!targetTool) {
|
|
addBreadcrumb({
|
|
category: "meta-tool",
|
|
message: `Tool not found: ${toolName}`,
|
|
level: "info",
|
|
data: { toolName, availableCount: allTools.length },
|
|
});
|
|
|
|
return {
|
|
found: false,
|
|
message: `Tool '${toolName}' not found.`,
|
|
hint: "Check the tool list in the system prompt for exact tool names.",
|
|
availableToolsCount: allTools.length,
|
|
};
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
name: targetTool.name,
|
|
description: targetTool.description || "No description available",
|
|
schema: targetTool.schema
|
|
? JSON.stringify(targetTool.schema, null, 2)
|
|
: "{}",
|
|
message: "Tool schema retrieved. Use execute_tool to run it.",
|
|
};
|
|
},
|
|
{
|
|
name: "describe_tool",
|
|
description: `Get the full schema and parameter details for a specific Prowler Hub tool.
|
|
|
|
Use this to understand what parameters a tool requires before executing it.
|
|
Tool names are listed in your system prompt - use the exact name.
|
|
|
|
You must always provide the toolName key in the JSON object.
|
|
Example: describe_tool({ "toolName": "prowler_hub_list_providers" })
|
|
|
|
Returns:
|
|
- Full parameter schema with types and descriptions
|
|
- Tool description
|
|
- Required vs optional parameters`,
|
|
schema: z.object({
|
|
toolName: z
|
|
.string()
|
|
.describe(
|
|
"Exact name of the tool to describe (e.g., 'prowler_hub_list_providers'). You must always provide the toolName key in the JSON object.",
|
|
),
|
|
}),
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Execute a tool with parameters
|
|
*/
|
|
export const executeTool = tool(
|
|
async ({ toolName, toolInput }: ExecuteToolInput) => {
|
|
const allTools = getAllTools();
|
|
const targetTool = allTools.find((t) => t.name === toolName);
|
|
|
|
if (!targetTool) {
|
|
addBreadcrumb({
|
|
category: "meta-tool",
|
|
message: `execute_tool: Tool not found: ${toolName}`,
|
|
level: "warning",
|
|
data: { toolName, toolInput },
|
|
});
|
|
|
|
return {
|
|
error: `Tool '${toolName}' not found. Use describe_tool to check available tools.`,
|
|
suggestion:
|
|
"Check the tool list in your system prompt for exact tool names. You must always provide the toolName key in the JSON object.",
|
|
};
|
|
}
|
|
|
|
try {
|
|
// Use empty object for empty inputs, otherwise use the provided input
|
|
const input =
|
|
!toolInput || Object.keys(toolInput).length === 0 ? {} : toolInput;
|
|
|
|
addBreadcrumb({
|
|
category: "meta-tool",
|
|
message: `Executing tool: ${toolName}`,
|
|
level: "info",
|
|
data: { toolName, hasInput: !!input },
|
|
});
|
|
|
|
// Execute the tool directly - let errors propagate so LLM can handle retries
|
|
const result = await targetTool.invoke(input);
|
|
|
|
return {
|
|
success: true,
|
|
toolName,
|
|
result,
|
|
};
|
|
} catch (error) {
|
|
const errorMessage =
|
|
error instanceof Error ? error.message : String(error);
|
|
|
|
captureException(error, {
|
|
tags: {
|
|
component: "meta-tool",
|
|
tool_name: toolName,
|
|
error_type: "tool_execution_failed",
|
|
},
|
|
level: "error",
|
|
contexts: {
|
|
tool_execution: {
|
|
tool_name: toolName,
|
|
tool_input: JSON.stringify(toolInput),
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
error: `Failed to execute '${toolName}': ${errorMessage}`,
|
|
toolName,
|
|
toolInput,
|
|
};
|
|
}
|
|
},
|
|
{
|
|
name: "execute_tool",
|
|
description: `Execute a Prowler Hub MCP tool with the specified parameters.
|
|
|
|
Provide the exact tool name and its input parameters as specified in the tool's schema.
|
|
|
|
You must always provide the toolName and toolInput keys in the JSON object.
|
|
Example: execute_tool({ "toolName": "prowler_hub_list_providers", "toolInput": {} })
|
|
|
|
All input to the tool must be provided in the toolInput key as a JSON object.
|
|
Example: execute_tool({ "toolName": "prowler_hub_list_providers", "toolInput": { "query": "value1", "page": 1, "pageSize": 10 } })
|
|
|
|
Always describe the tool first to understand:
|
|
1. What parameters it requires
|
|
2. The expected input format
|
|
3. Required vs optional parameters`,
|
|
schema: z.object({
|
|
toolName: z
|
|
.string()
|
|
.describe(
|
|
"Exact name of the tool to execute (from system prompt tool list)",
|
|
),
|
|
toolInput: z
|
|
.record(z.string(), z.unknown())
|
|
.default({})
|
|
.describe(
|
|
"Input parameters for the tool as a JSON object. Use empty object {} if tool requires no parameters.",
|
|
),
|
|
}),
|
|
},
|
|
);
|