mcp: sorting tools, fixed cloudflare example, fixed some styling

This commit is contained in:
dswbx
2025-08-20 18:26:48 +02:00
parent 8ec03afbe8
commit bc1ab0f6d3
10 changed files with 63 additions and 24 deletions

View File

@@ -16,9 +16,13 @@ export function getSystemMcp(app: App) {
...middlewareServer.tools,
// tools added from ctx
...app.modules.ctx().mcp.tools,
// tools from app schema
...nodes.flatMap((n) => n.schema.getTools(n)),
];
].sort((a, b) => a.name.localeCompare(b.name));
// tools from app schema
tools.push(
...nodes.flatMap((n) => n.schema.getTools(n)).sort((a, b) => a.name.localeCompare(b.name)),
);
const resources = [...middlewareServer.resources, ...app.modules.ctx().mcp.resources];
return new McpServer(

View File

@@ -8,6 +8,7 @@ export type EmptyProps = {
primary?: ButtonProps;
secondary?: ButtonProps;
className?: string;
children?: React.ReactNode;
};
export const Empty: React.FC<EmptyProps> = ({
Icon = undefined,
@@ -16,6 +17,7 @@ export const Empty: React.FC<EmptyProps> = ({
primary,
secondary,
className,
children,
}) => (
<div className={twMerge("flex flex-col h-full w-full justify-center items-center", className)}>
<div className="flex flex-col gap-3 items-center max-w-80">
@@ -27,6 +29,7 @@ export const Empty: React.FC<EmptyProps> = ({
<div className="mt-1.5 flex flex-row gap-2">
{secondary && <Button variant="default" {...secondary} />}
{primary && <Button variant="primary" {...primary} />}
{children}
</div>
</div>
</div>

View File

@@ -124,7 +124,7 @@ function DataEntityListImpl({ params }) {
</>
}
>
{entity.label}
<AppShell.SectionHeaderTitle>{entity.label}</AppShell.SectionHeaderTitle>
</AppShell.SectionHeader>
<AppShell.Scrollable key={entity.name}>
<div className="flex flex-col flex-grow p-3 gap-3">

View File

@@ -5,11 +5,15 @@ import { TbWorld } from "react-icons/tb";
import { McpIcon } from "./components/mcp-icon";
import { useBknd } from "ui/client/bknd";
import { Empty } from "ui/components/display/Empty";
import { Button } from "ui/components/buttons/Button";
import { appShellStore } from "ui/store";
export default function ToolsMcp() {
const { config, options } = useBknd();
const feature = useMcpStore((state) => state.feature);
const setFeature = useMcpStore((state) => state.setFeature);
const content = useMcpStore((state) => state.content);
const openSidebar = appShellStore((store) => store.toggleSidebar("default"));
if (!config.server.mcp.enabled) {
return (
@@ -25,15 +29,20 @@ export default function ToolsMcp() {
<AppShell.SectionHeader>
<div className="flex flex-row gap-4 items-center">
<McpIcon />
<AppShell.SectionHeaderTitle>MCP UI</AppShell.SectionHeaderTitle>
<div className="flex flex-row gap-2 items-center bg-primary/5 rounded-full px-3 pr-3.5 py-2">
<AppShell.SectionHeaderTitle className="whitespace-nowrap truncate">
MCP UI
</AppShell.SectionHeaderTitle>
<div className="hidden md:flex flex-row gap-2 items-center bg-primary/5 rounded-full px-3 pr-3.5 py-2">
<TbWorld />
<span className="text-sm font-mono leading-none">
{window.location.origin + "/mcp"}
</span>
<div className="min-w-0 flex-1">
<span className="block truncate text-sm font-mono leading-none">
{window.location.origin + "/mcp"}
</span>
</div>
</div>
</div>
</AppShell.SectionHeader>
<div className="flex h-full">
<AppShell.Sidebar>
<AppShell.MaxHeightContainer className="overflow-y-scroll md:overflow-auto">
@@ -50,6 +59,18 @@ export default function ToolsMcp() {
</AppShell.MaxHeightContainer>
</AppShell.Sidebar>
{feature === "tools" && <Tools.Content />}
{!content && (
<Empty title="No tool selected" description="Please select a tool to continue.">
<Button
variant="primary"
onClick={() => openSidebar()}
className="block md:hidden"
>
Open Tools
</Button>
</Empty>
)}
</div>
</div>
);

View File

@@ -6,15 +6,14 @@ import { TbHistory, TbHistoryOff, TbRefresh } from "react-icons/tb";
import { IconButton } from "ui/components/buttons/IconButton";
import { JsonViewer, JsonViewerTabs, type JsonViewerTabsRef } from "ui/components/code/JsonViewer";
import { twMerge } from "ui/elements/mocks/tailwind-merge";
import {
Form,
} from "ui/components/form/json-schema-form";
import { Form } from "ui/components/form/json-schema-form";
import { Button } from "ui/components/buttons/Button";
import * as Formy from "ui/components/form/Formy";
import { JsonEditor } from "ui/components/code/JsonEditor";
import { appShellStore } from "ui/store";
export function Sidebar({ open, toggle }) {
const client = getClient();
const closeSidebar = appShellStore((store) => store.closeSidebar("default"));
const tools = useMcpStore((state) => state.tools);
const setTools = useMcpStore((state) => state.setTools);
const setContent = useMcpStore((state) => state.setContent);
@@ -56,7 +55,7 @@ export function Sidebar({ open, toggle }) {
/>
<nav className="flex flex-col flex-1 gap-1">
{tools
.filter((tool) => tool.name.includes(query))
.filter((tool) => tool.name.toLowerCase().includes(query.toLowerCase()))
.map((tool) => {
return (
<AppShell.SidebarLink
@@ -65,7 +64,10 @@ export function Sidebar({ open, toggle }) {
"flex flex-col items-start h-auto py-3 gap-px",
content?.name === tool.name ? "active" : "",
)}
onClick={() => setContent(tool)}
onClick={() => {
setContent(tool);
closeSidebar();
}}
>
<span className="font-mono">{tool.name}</span>
<span className="text-sm text-primary/50">{tool.description}</span>
@@ -124,6 +126,7 @@ export function Content() {
return (
<div className="flex flex-grow flex-col">
<AppShell.SectionHeader
className="max-w-full min-w-0 debug"
right={
<div className="flex flex-row gap-2">
<IconButton
@@ -135,17 +138,18 @@ export function Content() {
disabled={!content?.name}
variant="primary"
onClick={handleSubmit}
className="whitespace-nowrap"
>
Call Tool
</Button>
</div>
}
>
<AppShell.SectionHeaderTitle className="">
<AppShell.SectionHeaderTitle className="leading-tight">
<span className="opacity-50">
Tools <span className="opacity-70">/</span>
</span>{" "}
{content?.name}
<span className="truncate">{content?.name}</span>
</AppShell.SectionHeaderTitle>
</AppShell.SectionHeader>
<div className="flex flex-grow flex-row w-full">