Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/server-mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ import {
RenderBarChartWidgetTool,
renderBarChartWidgetHandler,
} from "./widget/bar-chart/bar-chart";
import {
LineChartWidgetResourceLayer,
RenderLineChartWidgetTool,
renderLineChartWidgetHandler,
} from "./widget/line-chart/line-chart";

// Define Resources
const ResourceLayer = Layer.mergeAll(
BarChartWidgetResourceLayer,
LineChartWidgetResourceLayer,
// You can add more resources here
);

Expand All @@ -26,13 +32,15 @@ const PromptLayer = Layer.mergeAll(
// Define Toolkit
class AiTools extends Toolkit.make(
RenderBarChartWidgetTool,
RenderLineChartWidgetTool,
// You can add more tools here
) {}

const ToolLayer = McpServer.toolkit(AiTools).pipe(
Layer.provide(
AiTools.toLayer({
render_bar_chart_widget: renderBarChartWidgetHandler,
render_line_chart_widget: renderLineChartWidgetHandler,
// add implementation for more tools here
}),
),
Expand Down
18 changes: 9 additions & 9 deletions apps/server-mcp/src/widget/bar-chart/bar-chart-widget.html

Large diffs are not rendered by default.

185 changes: 185 additions & 0 deletions apps/server-mcp/src/widget/line-chart/line-chart-widget.html

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions apps/server-mcp/src/widget/line-chart/line-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { LineChartWidgetPayload } from "@repo/domain/Chart";
import { Effect, FileSystem, Path } from "effect";
import { makeUiRenderTool, makeUiResource } from "../../UiResource";

const LineChartWidgetResourceUri = "ui://line-chart";

const LineChartWidgetHtml = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const sourceSuffix = path.join("widget", "line-chart");
const isSourcePath = import.meta.dir.endsWith(sourceSuffix);
const htmlPath = isSourcePath
? path.join(import.meta.dir, "line-chart-widget.html")
: path.join(import.meta.dir, "widget/line-chart/line-chart-widget.html");
return yield* fs.readFileString(htmlPath);
});

export const LineChartWidgetResourceLayer = makeUiResource(
LineChartWidgetResourceUri,
{
name: "Line Chart",
description: "Line chart widget UI",
html: LineChartWidgetHtml,
meta: {
prefersBorder: false,
},
},
);

export const RenderLineChartWidgetTool = makeUiRenderTool(
LineChartWidgetResourceUri,
{
name: "render_line_chart_widget",
title: "Line Chart",
description: "Render the line chart widget UI",
parameters: LineChartWidgetPayload,
success: LineChartWidgetPayload,
},
);

export const renderLineChartWidgetHandler = (
payload: typeof LineChartWidgetPayload.Type,
) => Effect.succeed(payload);
33 changes: 26 additions & 7 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/bar-chart-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"@observablehq/plot": "^0.6.17",
"d3": "^7.9.0",
"effect": "4.0.0-beta.43",
"framer-motion": "^12.23.6",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
Expand Down
47 changes: 20 additions & 27 deletions packages/bar-chart-widget/src/bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ const extractPayloadFromToolResult = (
const text = params.content?.find((item) => item.type === "text")?.text;
if (text) {
try {
return Option.getOrNull(
Schema.decodeUnknownOption(BarChartWidgetPayload)(JSON.parse(text)),
);
return Schema.decodeUnknownSync(BarChartWidgetPayload)(JSON.parse(text));
} catch {
return null;
}
Expand All @@ -50,35 +48,30 @@ const BarChart = ({
return undefined;
}

const { width: layoutWidth, ...layoutOptions } = layout ?? {};
const plotWidth = layoutWidth ?? measuredWidth ?? undefined;
const { width: layoutWidth, ...layoutOptions } = layout;
const width = layoutWidth ?? measuredWidth ?? undefined;
const isHorizontal = direction === "horizontal";
const barOptions = (marks ?? {}) as Plot.BarXOptions | Plot.BarYOptions;
const defaultX = marks?.x ?? (isHorizontal ? "value" : "category");
const defaultY = marks?.y ?? (isHorizontal ? "category" : "value");
if (defaultX !== undefined) {
barOptions.x = defaultX;
}
if (defaultY !== undefined) {
barOptions.y = defaultY;
}
const barMark = isHorizontal
? Plot.barX(data, barOptions)
: Plot.barY(data, barOptions);
const ruleMark = isHorizontal ? Plot.ruleX([0]) : Plot.ruleY([0]);
const plotOptions: Plot.PlotOptions = {
const barOptions = {
...marks,
x: marks?.x ?? (isHorizontal ? "value" : "category"),
y: marks?.y ?? (isHorizontal ? "category" : "value"),
};

const plot = Plot.plot({
style: "--plot-background: var(--color-background-primary);",
...layoutOptions,
...(width ? { width } : {}),
color: {
...(layoutOptions.color ? layoutOptions.color : {}),
scheme: "Category10",
},
marks: [ruleMark, barMark].filter(Boolean),
};
if (plotWidth !== undefined) {
plotOptions.width = plotWidth;
}
const plot = Plot.plot(plotOptions);
marks: [
isHorizontal ? Plot.ruleX([0]) : Plot.ruleY([0]),
isHorizontal
? Plot.barX(data, barOptions)
: Plot.barY(data, barOptions),
].filter(Boolean),
});

containerRef.current.replaceChildren(plot);
return () => plot.remove();
Expand Down Expand Up @@ -129,8 +122,8 @@ export const WidgetApp = () => {
capabilities: {},
onAppCreated: (app) => {
app.ontoolinput = (params) => {
const parsed = Option.getOrNull(
Schema.decodeUnknownOption(BarChartWidgetPayload)(params.arguments),
const parsed = Schema.decodeUnknownSync(BarChartWidgetPayload)(
params.arguments,
);
if (parsed) {
setPayload(parsed);
Expand Down
Loading
Loading