mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
mcp: sorting tools, fixed cloudflare example, fixed some styling
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
// tools from app schema
|
// tools from app schema
|
||||||
...nodes.flatMap((n) => n.schema.getTools(n)),
|
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(
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
<span className="block truncate text-sm font-mono leading-none">
|
||||||
{window.location.origin + "/mcp"}
|
{window.location.origin + "/mcp"}
|
||||||
</span>
|
</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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
7
examples/cloudflare-worker/config.ts
Normal file
7
examples/cloudflare-worker/config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
d1: {
|
||||||
|
session: true,
|
||||||
|
},
|
||||||
|
} satisfies CloudflareBkndConfig;
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user