mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-01 08:59:46 +02:00
feat(mcp): generic MCP tool source with per-node function filtering (#301)
* feat(mcp): generic MCP tool source with per-node function filtering
Adds a Model Context Protocol tool category: connect a customer MCP
server and expose its tools to the agent, with optional per-node
allow-listing of individual MCP functions.
- ToolCategory.MCP enum + alembic migration
- MCP definition validator and collision-safe function-name namespacing
- McpToolSession wrapper: graceful-degrade, per-call open/close lifecycle
- CustomToolManager MCP branch (schemas + proxy handlers)
- Per-node mcp_tool_filters threaded through DTO/graph/engine
- Best-effort discovered_tools catalog cache + POST /tools/{uuid}/mcp/refresh
- UI: MCP create/edit config, tabbed ToolSelector with per-node toggles
* feat: refactor for code standardisation and documentation
---------
Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
parent
0097974444
commit
75839f9de5
40 changed files with 3028 additions and 137 deletions
|
|
@ -9,16 +9,25 @@ import {
|
|||
listRecordingsApiV1WorkflowRecordingsGet,
|
||||
updateToolApiV1ToolsToolUuidPut,
|
||||
} from "@/client/sdk.gen";
|
||||
import type { RecordingResponseSchema, ToolResponse, TransferCallConfig as APITransferCallConfig } from "@/client/types.gen";
|
||||
import type { EndCallConfig } from "@/client/types.gen";
|
||||
import type {
|
||||
EndCallConfig,
|
||||
HttpApiToolDefinition,
|
||||
RecordingResponseSchema,
|
||||
ToolResponse,
|
||||
TransferCallConfig as APITransferCallConfig,
|
||||
UpdateToolRequest,
|
||||
} from "@/client/types.gen";
|
||||
import {
|
||||
CredentialSelector,
|
||||
type HttpMethod,
|
||||
type KeyValueItem,
|
||||
type ParameterType,
|
||||
type PresetToolParameter,
|
||||
type ToolParameter,
|
||||
validateUrl,
|
||||
} from "@/components/http";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -26,35 +35,33 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { TOOL_DOCUMENTATION_URLS } from "@/constants/documentation";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
import {
|
||||
createMcpDefinition,
|
||||
DEFAULT_END_CALL_REASON_DESCRIPTION,
|
||||
type EndCallMessageType,
|
||||
getCategoryConfig,
|
||||
getToolTypeLabel,
|
||||
MCP_URL_PATTERN,
|
||||
renderToolIcon,
|
||||
type ToolCategory,
|
||||
} from "../config";
|
||||
import { BuiltinToolConfig, EndCallToolConfig, HttpApiToolConfig, TransferCallToolConfig } from "./components";
|
||||
|
||||
// Extended HttpApiConfig with parameters (until client types are regenerated)
|
||||
interface HttpApiConfigWithParams {
|
||||
method?: string;
|
||||
url?: string;
|
||||
headers?: Record<string, string>;
|
||||
credential_uuid?: string;
|
||||
parameters?: ToolParameter[];
|
||||
preset_parameters?: Array<{
|
||||
name?: string;
|
||||
type?: PresetToolParameter["type"];
|
||||
value_template?: string;
|
||||
required?: boolean;
|
||||
}>;
|
||||
timeout_ms?: number;
|
||||
customMessage?: string;
|
||||
function normalizeParameterType(value: string | null | undefined): ParameterType {
|
||||
switch (value) {
|
||||
case "number":
|
||||
case "boolean":
|
||||
return value;
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
export default function ToolDetailPage() {
|
||||
|
|
@ -108,6 +115,11 @@ export default function ToolDetailPage() {
|
|||
const [customMessageType, setCustomMessageType] = useState<'text' | 'audio'>('text');
|
||||
const [customMessageRecordingId, setCustomMessageRecordingId] = useState("");
|
||||
|
||||
// MCP form state
|
||||
const [mcpUrl, setMcpUrl] = useState("");
|
||||
const [mcpCredentialUuid, setMcpCredentialUuid] = useState("");
|
||||
const [mcpToolsFilter, setMcpToolsFilter] = useState("");
|
||||
|
||||
// Org-level recordings for audio dropdowns
|
||||
const [recordings, setRecordings] = useState<RecordingResponseSchema[]>([]);
|
||||
|
||||
|
|
@ -155,8 +167,7 @@ export default function ToolDetailPage() {
|
|||
if (config) {
|
||||
setEndCallMessageType(config.messageType || "none");
|
||||
setCustomMessage(config.customMessage || "");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setAudioRecordingId((config as any).audioRecordingId || "");
|
||||
setAudioRecordingId(config.audioRecordingId || "");
|
||||
setEndCallReason(config.endCallReason ?? false);
|
||||
setEndCallReasonDescription(config.endCallReasonDescription || "");
|
||||
} else {
|
||||
|
|
@ -173,8 +184,7 @@ export default function ToolDetailPage() {
|
|||
setTransferDestination(config.destination || "");
|
||||
setTransferMessageType(config.messageType || "none");
|
||||
setCustomMessage(config.customMessage || "");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setTransferAudioRecordingId((config as any).audioRecordingId || "");
|
||||
setTransferAudioRecordingId(config.audioRecordingId || "");
|
||||
setTransferTimeout(config.timeout ?? 30);
|
||||
} else {
|
||||
setTransferDestination("");
|
||||
|
|
@ -183,19 +193,35 @@ export default function ToolDetailPage() {
|
|||
setTransferAudioRecordingId("");
|
||||
setTransferTimeout(30);
|
||||
}
|
||||
} else if (tool.category === "mcp") {
|
||||
// Populate MCP specific fields
|
||||
const config = tool.definition?.config as
|
||||
| { url?: string; credential_uuid?: string | null; tools_filter?: string[] }
|
||||
| undefined;
|
||||
if (config) {
|
||||
setMcpUrl(config.url || "");
|
||||
setMcpCredentialUuid(config.credential_uuid || "");
|
||||
setMcpToolsFilter(
|
||||
Array.isArray(config.tools_filter)
|
||||
? config.tools_filter.join(", ")
|
||||
: ""
|
||||
);
|
||||
} else {
|
||||
setMcpUrl("");
|
||||
setMcpCredentialUuid("");
|
||||
setMcpToolsFilter("");
|
||||
}
|
||||
} else {
|
||||
// Populate HTTP API specific fields
|
||||
const config = tool.definition?.config as HttpApiConfigWithParams | undefined;
|
||||
const config = tool.definition?.config as HttpApiToolDefinition["config"] | undefined;
|
||||
if (config) {
|
||||
setHttpMethod((config.method as HttpMethod) || "POST");
|
||||
setUrl(config.url || "");
|
||||
setCredentialUuid(config.credential_uuid || "");
|
||||
setTimeoutMs(config.timeout_ms || 5000);
|
||||
setCustomMessage(config.customMessage || "");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setCustomMessageType((config as any).customMessageType || "text");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setCustomMessageRecordingId((config as any).customMessageRecordingId || "");
|
||||
setCustomMessageType(config.customMessageType || "text");
|
||||
setCustomMessageRecordingId(config.customMessageRecordingId || "");
|
||||
|
||||
// Convert headers object to array
|
||||
if (config.headers) {
|
||||
|
|
@ -212,9 +238,9 @@ export default function ToolDetailPage() {
|
|||
// Load parameters
|
||||
if (config.parameters && Array.isArray(config.parameters)) {
|
||||
setParameters(
|
||||
config.parameters.map((p: ToolParameter) => ({
|
||||
config.parameters.map((p) => ({
|
||||
name: p.name || "",
|
||||
type: p.type || "string",
|
||||
type: normalizeParameterType(p.type),
|
||||
description: p.description || "",
|
||||
required: p.required ?? true,
|
||||
}))
|
||||
|
|
@ -227,7 +253,7 @@ export default function ToolDetailPage() {
|
|||
setPresetParameters(
|
||||
config.preset_parameters.map((p) => ({
|
||||
name: p.name || "",
|
||||
type: p.type || "string",
|
||||
type: normalizeParameterType(p.type),
|
||||
valueTemplate: p.value_template || "",
|
||||
required: p.required ?? true,
|
||||
}))
|
||||
|
|
@ -275,6 +301,16 @@ export default function ToolDetailPage() {
|
|||
setError("Please enter a valid phone number (E.164 format) or SIP endpoint (e.g., PJSIP/1234)");
|
||||
return;
|
||||
}
|
||||
} else if (tool.category === "mcp") {
|
||||
// Validate MCP server URL (must be http(s))
|
||||
if (!mcpUrl.trim()) {
|
||||
setError("Please enter the MCP server URL");
|
||||
return;
|
||||
}
|
||||
if (!MCP_URL_PATTERN.test(mcpUrl.trim())) {
|
||||
setError("MCP server URL must start with http:// or https://");
|
||||
return;
|
||||
}
|
||||
} else if (tool.category !== "end_call") {
|
||||
// Validate URL for HTTP API tools
|
||||
const urlValidation = validateUrl(url);
|
||||
|
|
@ -305,7 +341,7 @@ export default function ToolDetailPage() {
|
|||
setSaveSuccess(false);
|
||||
const accessToken = await getAccessToken();
|
||||
|
||||
let requestBody;
|
||||
let requestBody: UpdateToolRequest;
|
||||
|
||||
if (tool.category === "calculator") {
|
||||
// Built-in tool - only name/description, no config
|
||||
|
|
@ -351,6 +387,12 @@ export default function ToolDetailPage() {
|
|||
},
|
||||
},
|
||||
};
|
||||
} else if (tool.category === "mcp") {
|
||||
requestBody = {
|
||||
name,
|
||||
description: description || undefined,
|
||||
definition: createMcpDefinition(mcpUrl, mcpCredentialUuid, mcpToolsFilter),
|
||||
};
|
||||
} else {
|
||||
// Build HTTP API request body
|
||||
const headersObject: Record<string, string> = {};
|
||||
|
|
@ -399,8 +441,7 @@ export default function ToolDetailPage() {
|
|||
|
||||
const response = await updateToolApiV1ToolsToolUuidPut({
|
||||
path: { tool_uuid: toolUuid },
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
body: requestBody as any,
|
||||
body: requestBody,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
|
|
@ -510,6 +551,7 @@ const data = await response.json();`;
|
|||
const isEndCallTool = tool.category === "end_call";
|
||||
const isTransferCallTool = tool.category === "transfer_call";
|
||||
const isBuiltinTool = tool.category === "calculator";
|
||||
const isMcpTool = tool.category === "mcp";
|
||||
const categoryConfig = getCategoryConfig(tool.category as ToolCategory);
|
||||
|
||||
return (
|
||||
|
|
@ -545,7 +587,7 @@ const data = await response.json();`;
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{!isEndCallTool && !isTransferCallTool && !isBuiltinTool && (
|
||||
{!isEndCallTool && !isTransferCallTool && !isBuiltinTool && !isMcpTool && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowCodeDialog(true)}
|
||||
|
|
@ -613,6 +655,79 @@ const data = await response.json();`;
|
|||
timeout={transferTimeout}
|
||||
onTimeoutChange={setTransferTimeout}
|
||||
/>
|
||||
) : isMcpTool ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>MCP Server Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Configure the MCP server endpoint. Its tools become available to the agent.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mcp-name">Tool Name</Label>
|
||||
<Input
|
||||
id="mcp-name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g., Customer MCP Server"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mcp-description">Description</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Provide a description which makes it easy for LLM to understand what this tool does
|
||||
</p>
|
||||
<Textarea
|
||||
id="mcp-description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="What does this MCP server provide?"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mcp-url">MCP Server URL</Label>
|
||||
<Input
|
||||
id="mcp-url"
|
||||
value={mcpUrl}
|
||||
onChange={(e) => setMcpUrl(e.target.value)}
|
||||
placeholder="https://your-mcp-server.example.com/mcp"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Transport</Label>
|
||||
<Input
|
||||
value="Streamable HTTP"
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CredentialSelector
|
||||
value={mcpCredentialUuid}
|
||||
onChange={setMcpCredentialUuid}
|
||||
label="Credential (Optional)"
|
||||
description="Select a credential for authenticating with the MCP server, or leave empty for no auth."
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mcp-tools-filter">Tools Filter (Optional)</Label>
|
||||
<Input
|
||||
id="mcp-tools-filter"
|
||||
value={mcpToolsFilter}
|
||||
onChange={(e) => setMcpToolsFilter(e.target.value)}
|
||||
placeholder="e.g., tool_one, tool_two"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Comma-separated list of tool names to allow. Leave empty to expose all tools from the server.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<HttpApiToolConfig
|
||||
name={name}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ import type {
|
|||
EndCallConfig,
|
||||
EndCallToolDefinition,
|
||||
HttpApiToolDefinition,
|
||||
McpToolDefinition,
|
||||
TransferCallConfig,
|
||||
TransferCallToolDefinition,
|
||||
} from "@/client/types.gen";
|
||||
|
||||
export type ToolCategory = "http_api" | "end_call" | "transfer_call" | "calculator" | "native" | "integration";
|
||||
export type ToolCategory = "http_api" | "end_call" | "transfer_call" | "calculator" | "native" | "integration" | "mcp";
|
||||
|
||||
export type EndCallMessageType = "none" | "custom" | "audio";
|
||||
|
||||
|
|
@ -75,6 +76,14 @@ export const TOOL_CATEGORIES: ToolCategoryConfig[] = [
|
|||
description: "Perform arithmetic calculations (supports +, -, *, /, **, %, and parentheses)",
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "mcp",
|
||||
label: "MCP Server",
|
||||
description: "Connect a customer MCP server; its tools become available to the agent",
|
||||
icon: Puzzle,
|
||||
iconName: "puzzle",
|
||||
iconColor: "#8B5CF6",
|
||||
},
|
||||
{
|
||||
value: "native",
|
||||
label: "Native (Coming Soon)",
|
||||
|
|
@ -128,6 +137,8 @@ export function getToolTypeLabel(category: string): string {
|
|||
return "Native Tool";
|
||||
case "integration":
|
||||
return "Integration Tool";
|
||||
case "mcp":
|
||||
return "MCP Server Tool";
|
||||
default:
|
||||
return "Tool";
|
||||
}
|
||||
|
|
@ -149,7 +160,12 @@ export const DEFAULT_TRANSFER_CALL_CONFIG: TransferCallConfig = {
|
|||
timeout: 30,
|
||||
};
|
||||
|
||||
export type ToolDefinition = HttpApiToolDefinition | EndCallToolDefinition | TransferCallToolDefinition | CalculatorToolDefinition;
|
||||
export type ToolDefinition =
|
||||
| HttpApiToolDefinition
|
||||
| EndCallToolDefinition
|
||||
| TransferCallToolDefinition
|
||||
| CalculatorToolDefinition
|
||||
| McpToolDefinition;
|
||||
|
||||
export function createEndCallDefinition(config: EndCallConfig): EndCallToolDefinition {
|
||||
return {
|
||||
|
|
@ -185,6 +201,28 @@ export function createCalculatorDefinition(): CalculatorToolDefinition {
|
|||
};
|
||||
}
|
||||
|
||||
export const MCP_URL_PATTERN = /^https?:\/\//i;
|
||||
|
||||
export function createMcpDefinition(
|
||||
url: string,
|
||||
credentialUuid: string,
|
||||
toolsFilterCsv: string,
|
||||
): McpToolDefinition {
|
||||
return {
|
||||
schema_version: 1,
|
||||
type: "mcp" as const,
|
||||
config: {
|
||||
transport: "streamable_http" as const,
|
||||
url: url.trim(),
|
||||
credential_uuid: credentialUuid || null,
|
||||
tools_filter: toolsFilterCsv
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createToolDefinition(category: ToolCategory): ToolDefinition {
|
||||
switch (category) {
|
||||
case "end_call":
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import {
|
|||
listToolsApiV1ToolsGet,
|
||||
unarchiveToolApiV1ToolsToolUuidUnarchivePost,
|
||||
} from "@/client/sdk.gen";
|
||||
import type { ToolResponse } from "@/client/types.gen";
|
||||
import type { CreateToolRequest, ToolResponse } from "@/client/types.gen";
|
||||
import { CredentialSelector } from "@/components/http";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -41,8 +42,10 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
import {
|
||||
createMcpDefinition,
|
||||
createToolDefinition,
|
||||
getCategoryConfig,
|
||||
MCP_URL_PATTERN,
|
||||
renderToolIcon,
|
||||
TOOL_CATEGORIES,
|
||||
type ToolCategory,
|
||||
|
|
@ -63,6 +66,11 @@ export default function ToolsPage() {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
const [createError, setCreateError] = useState<string | null>(null);
|
||||
|
||||
// MCP-specific create dialog state
|
||||
const [mcpUrl, setMcpUrl] = useState("");
|
||||
const [mcpCredentialUuid, setMcpCredentialUuid] = useState("");
|
||||
const [mcpToolsFilter, setMcpToolsFilter] = useState("");
|
||||
|
||||
// Redirect if not authenticated
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
|
|
@ -108,21 +116,38 @@ export default function ToolsPage() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (newToolCategory === "mcp" && !mcpUrl.trim()) {
|
||||
setCreateError("Please enter the MCP server URL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newToolCategory === "mcp" && !MCP_URL_PATTERN.test(mcpUrl.trim())) {
|
||||
setCreateError("MCP server URL must start with http:// or https://");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCreating(true);
|
||||
setCreateError(null);
|
||||
const accessToken = await getAccessToken();
|
||||
|
||||
const categoryConfig = getCategoryConfig(newToolCategory);
|
||||
|
||||
const definition = newToolCategory === "mcp"
|
||||
? createMcpDefinition(mcpUrl, mcpCredentialUuid, mcpToolsFilter)
|
||||
: createToolDefinition(newToolCategory);
|
||||
|
||||
const requestBody: CreateToolRequest = {
|
||||
name: newToolName,
|
||||
description: newToolDescription || undefined,
|
||||
category: newToolCategory,
|
||||
icon: categoryConfig?.iconName || "globe",
|
||||
icon_color: categoryConfig?.iconColor || "#3B82F6",
|
||||
definition,
|
||||
};
|
||||
|
||||
const response = await createToolApiV1ToolsPost({
|
||||
body: {
|
||||
name: newToolName,
|
||||
description: newToolDescription || undefined,
|
||||
category: newToolCategory,
|
||||
icon: categoryConfig?.iconName || "globe",
|
||||
icon_color: categoryConfig?.iconColor || "#3B82F6",
|
||||
definition: createToolDefinition(newToolCategory),
|
||||
},
|
||||
body: requestBody,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
|
|
@ -139,6 +164,9 @@ export default function ToolsPage() {
|
|||
setNewToolName("");
|
||||
setNewToolDescription("");
|
||||
setNewToolCategory("http_api");
|
||||
setMcpUrl("");
|
||||
setMcpCredentialUuid("");
|
||||
setMcpToolsFilter("");
|
||||
// Navigate to the new tool's detail page
|
||||
router.push(`/tools/${response.data.tool_uuid}`);
|
||||
}
|
||||
|
|
@ -233,6 +261,8 @@ export default function ToolsPage() {
|
|||
return <Badge variant="secondary">Native</Badge>;
|
||||
case "integration":
|
||||
return <Badge variant="outline">Integration</Badge>;
|
||||
case "mcp":
|
||||
return <Badge variant="outline">MCP</Badge>;
|
||||
default:
|
||||
return <Badge variant="outline">{category}</Badge>;
|
||||
}
|
||||
|
|
@ -465,7 +495,14 @@ export default function ToolsPage() {
|
|||
{/* Create Tool Dialog */}
|
||||
<Dialog open={isCreateDialogOpen} onOpenChange={(open) => {
|
||||
setIsCreateDialogOpen(open);
|
||||
if (open) setCreateError(null);
|
||||
if (open) {
|
||||
setCreateError(null);
|
||||
} else {
|
||||
// Reset MCP fields when dialog is closed without creating
|
||||
setMcpUrl("");
|
||||
setMcpCredentialUuid("");
|
||||
setMcpToolsFilter("");
|
||||
}
|
||||
}}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
|
@ -482,6 +519,7 @@ export default function ToolsPage() {
|
|||
onValueChange={(v) => {
|
||||
const category = v as ToolCategory;
|
||||
setNewToolCategory(category);
|
||||
setCreateError(null);
|
||||
const categoryConfig = getCategoryConfig(category);
|
||||
if (categoryConfig?.autoFill) {
|
||||
setNewToolName(categoryConfig.autoFill.name);
|
||||
|
|
@ -532,6 +570,46 @@ export default function ToolsPage() {
|
|||
placeholder="What does this tool do?"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{newToolCategory === "mcp" && (
|
||||
<>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="mcp-url">MCP Server URL</Label>
|
||||
<Input
|
||||
id="mcp-url"
|
||||
value={mcpUrl}
|
||||
onChange={(e) => setMcpUrl(e.target.value)}
|
||||
placeholder="https://your-mcp-server.example.com/mcp"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>Transport</Label>
|
||||
<Input
|
||||
value="Streamable HTTP"
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<CredentialSelector
|
||||
value={mcpCredentialUuid}
|
||||
onChange={setMcpCredentialUuid}
|
||||
label="Credential (Optional)"
|
||||
description="Select a credential for authenticating with the MCP server, or leave empty for no auth."
|
||||
/>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="mcp-tools-filter">Tools Filter (Optional)</Label>
|
||||
<Input
|
||||
id="mcp-tools-filter"
|
||||
value={mcpToolsFilter}
|
||||
onChange={(e) => setMcpToolsFilter(e.target.value)}
|
||||
placeholder="e.g., tool_one, tool_two"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Comma-separated list of tool names to allow. Leave empty to expose all tools from the server.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{createError && (
|
||||
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg text-destructive text-sm">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue