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

@@ -25,7 +25,7 @@ const result = await Bun.build({
target: "node", target: "node",
outdir: "./dist/cli", outdir: "./dist/cli",
env: "PUBLIC_*", env: "PUBLIC_*",
minify: false, minify: true,
external: ["jsonv-ts", "jsonv-ts/*"], external: ["jsonv-ts", "jsonv-ts/*"],
define: { define: {
__isDev: "0", __isDev: "0",

View File

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

View File

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

View File

@@ -124,7 +124,7 @@ function DataEntityListImpl({ params }) {
</> </>
} }
> >
{entity.label} <AppShell.SectionHeaderTitle>{entity.label}</AppShell.SectionHeaderTitle>
</AppShell.SectionHeader> </AppShell.SectionHeader>
<AppShell.Scrollable key={entity.name}> <AppShell.Scrollable key={entity.name}>
<div className="flex flex-col flex-grow p-3 gap-3"> <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 { McpIcon } from "./components/mcp-icon";
import { useBknd } from "ui/client/bknd"; import { useBknd } from "ui/client/bknd";
import { Empty } from "ui/components/display/Empty"; import { Empty } from "ui/components/display/Empty";
import { Button } from "ui/components/buttons/Button";
import { appShellStore } from "ui/store";
export default function ToolsMcp() { export default function ToolsMcp() {
const { config, options } = useBknd(); const { config, options } = useBknd();
const feature = useMcpStore((state) => state.feature); const feature = useMcpStore((state) => state.feature);
const setFeature = useMcpStore((state) => state.setFeature); const setFeature = useMcpStore((state) => state.setFeature);
const content = useMcpStore((state) => state.content);
const openSidebar = appShellStore((store) => store.toggleSidebar("default"));
if (!config.server.mcp.enabled) { if (!config.server.mcp.enabled) {
return ( return (
@@ -25,15 +29,20 @@ export default function ToolsMcp() {
<AppShell.SectionHeader> <AppShell.SectionHeader>
<div className="flex flex-row gap-4 items-center"> <div className="flex flex-row gap-4 items-center">
<McpIcon /> <McpIcon />
<AppShell.SectionHeaderTitle>MCP UI</AppShell.SectionHeaderTitle> <AppShell.SectionHeaderTitle className="whitespace-nowrap truncate">
<div className="flex flex-row gap-2 items-center bg-primary/5 rounded-full px-3 pr-3.5 py-2"> 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 /> <TbWorld />
<span className="text-sm font-mono leading-none"> <div className="min-w-0 flex-1">
{window.location.origin + "/mcp"} <span className="block truncate text-sm font-mono leading-none">
</span> {window.location.origin + "/mcp"}
</span>
</div>
</div> </div>
</div> </div>
</AppShell.SectionHeader> </AppShell.SectionHeader>
<div className="flex h-full"> <div className="flex h-full">
<AppShell.Sidebar> <AppShell.Sidebar>
<AppShell.MaxHeightContainer className="overflow-y-scroll md:overflow-auto"> <AppShell.MaxHeightContainer className="overflow-y-scroll md:overflow-auto">
@@ -50,6 +59,18 @@ export default function ToolsMcp() {
</AppShell.MaxHeightContainer> </AppShell.MaxHeightContainer>
</AppShell.Sidebar> </AppShell.Sidebar>
{feature === "tools" && <Tools.Content />} {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>
</div> </div>
); );

View File

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

View File

@@ -1,12 +1,11 @@
/** /**
* Optionally wrapping the configuration with the `withPlatformProxy` function * Optionally wrapping the configuration with the `withPlatformProxy` function
* enables programmatic access to the bindings, e.g. for generating types. * enables programmatic access to the bindings, e.g. for generating types.
*
* We're using separate files, so that "wrangler" doesn't get bundled with your worker.
*/ */
import { withPlatformProxy } from "bknd/adapter/cloudflare"; import { withPlatformProxy } from "bknd/adapter/cloudflare";
import config from "./config.ts";
export default withPlatformProxy({ export default withPlatformProxy(config);
d1: {
session: true,
},
});

View File

@@ -0,0 +1,7 @@
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
export default {
d1: {
session: true,
},
} satisfies CloudflareBkndConfig;

View File

@@ -1,4 +1,4 @@
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
import config from "../bknd.config"; import config from "../config";
export default serve(config); export default serve(config);

View File

@@ -11,6 +11,7 @@
"isolatedModules": true, "isolatedModules": true,
"noImplicitAny": false, "noImplicitAny": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"allowImportingTsExtensions": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"skipLibCheck": true "skipLibCheck": true