Skip to content

Commit dcabece

Browse files
committed
add mcp server to CLI
1 parent 18088ab commit dcabece

File tree

9 files changed

+286
-21
lines changed

9 files changed

+286
-21
lines changed

atw-cli/bun.lock

Lines changed: 170 additions & 4 deletions
Large diffs are not rendered by default.

atw-cli/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "atw-cli",
33
"module": "index.ts",
44
"type": "module",
5-
"version": "1.2.0",
5+
"version": "1.3.0",
66
"private": true,
77
"scripts": {
88
"prettier:check": "prettier --check .",
@@ -18,13 +18,15 @@
1818
},
1919
"dependencies": {
2020
"@inkjs/ui": "^2.0.0",
21+
"@modelcontextprotocol/sdk": "^1.7.0",
2122
"@rocicorp/zero": "^0.16.2025031000",
2223
"commander": "^13.1.0",
2324
"ink": "^5.2.0",
2425
"ink-link": "^4.1.0",
2526
"picocolors": "^1.1.1",
2627
"qrcode-terminal": "^0.12.0",
2728
"react": "^18.3.1",
28-
"react-devtools-core": "^6.1.1"
29+
"react-devtools-core": "^6.1.1",
30+
"zod": "^3.24.2"
2931
}
3032
}

atw-cli/src/commands/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { createJarvis } from "../core/create-jarvis";
22
import { createRegisterCommand } from "./register";
3+
import { createMCPServeCommand } from "./mcp";
4+
import { createZero } from "../core/create-zero";
35

46
const jarvis = createJarvis("Plopix");
5-
export const commands = [createRegisterCommand({ tBot: jarvis })];
7+
const zero = createZero();
8+
export const commands = [
9+
createRegisterCommand({ tBot: jarvis, zero }),
10+
createMCPServeCommand({ zero }),
11+
];

atw-cli/src/commands/mcp.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3+
import packageJson from "../../package.json";
4+
import { Command } from "commander";
5+
import { z } from "zod";
6+
import { registerAction, type Event } from "../actions/register";
7+
import type { AtwZero } from "../core/create-zero";
8+
import { sleep } from "bun";
9+
10+
type Deps = {
11+
zero: AtwZero;
12+
};
13+
14+
export const createMCPServeCommand = ({ zero }: Deps): Command => {
15+
const command = new Command("mcp-serve")
16+
.description("Start the MCP server.")
17+
.action(async () => {
18+
const mcpServer = new McpServer({
19+
name: "All Things Web meetup events",
20+
version: packageJson.version,
21+
});
22+
23+
mcpServer.tool(
24+
"get-events",
25+
"Get all upcoming published All Things Web events",
26+
{},
27+
async () => {
28+
const getEvents = async (): Promise<Event[]> => {
29+
let count = 0;
30+
return new Promise(async (resolve) => {
31+
for(let i = 0; i < 10; i++) {
32+
const events = zero.query.events
33+
//.where("startDate", ">", new Date().getTime())
34+
.orderBy("startDate", "desc")
35+
.limit(10)
36+
.run();
37+
if(!events.length) {
38+
count += 1;
39+
await sleep(200);
40+
} else {
41+
resolve(events);
42+
}
43+
}
44+
});
45+
};
46+
const events = await getEvents();
47+
return { content: events.map((event) => ({
48+
type: "text",
49+
text: `
50+
name: ${event.name}
51+
start: ${event.startDate}
52+
location: ${event.shortLocation}
53+
tagline: ${event.tagline}
54+
eventId: ${event.id}`
55+
})) };
56+
},
57+
);
58+
59+
mcpServer.tool(
60+
"register",
61+
"Register to an All Things Web event",
62+
{
63+
eventId: z.string().describe("The event id to register to."),
64+
email: z.string().email().describe("The email to register with."),
65+
},
66+
async ({ eventId, email }) => {
67+
const results = await registerAction(email, eventId);
68+
let text = "Registered!";
69+
if (results.success === false) {
70+
text = `Oh no! Something went wrong! ${results.error}`;
71+
}
72+
return {
73+
content: [{ type: "text", text }, {type: "text", text: `eventId: ${eventId}`}],
74+
};
75+
},
76+
);
77+
78+
const stdioServer = new StdioServerTransport();
79+
await mcpServer.connect(stdioServer);
80+
});
81+
return command;
82+
};

atw-cli/src/commands/register.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import pc from "picocolors";
55
import { logo } from "..";
66
import { AppLayout } from "../ui/app-layout";
77
import type { TalkativeBot } from "../contracts/talkative-bot-interface";
8+
import type { AtwZero } from "../core/create-zero";
89

910
type Deps = {
1011
tBot: TalkativeBot;
12+
zero: AtwZero;
1113
};
1214
export const createRegisterCommand = (deps: Deps): Command => {
1315
const command = new Command("register")
@@ -18,7 +20,7 @@ export const createRegisterCommand = (deps: Deps): Command => {
1820
"We can't wait to see you soon! Select one of the events below to register.",
1921
);
2022
const { waitUntilExit, unmount } = render(
21-
<AppLayout title="Register to an event!">
23+
<AppLayout zero={deps.zero} title="Register to an event!">
2224
<RegisterJourney unmount={() => unmount()} deps={deps} />
2325
</AppLayout>,
2426
{

atw-cli/src/core/create-zero.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Zero } from "@rocicorp/zero";
2+
import { schema } from "@lib/zero-sync/schema";
3+
4+
export type AtwZero = ReturnType<typeof createZero>;
5+
6+
export const createZero = () => {
7+
const z = new Zero({
8+
userID: "anon",
9+
server: "https://allthingsweb-sync.fly.dev",
10+
schema,
11+
kvStore: "mem",
12+
});
13+
return z;
14+
};

atw-cli/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,3 @@ try {
4747
console.error(exception);
4848
process.exit(1);
4949
}
50-
process.exit(0);

atw-cli/src/ui/app-layout.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,25 @@
11
import { Box } from "ink";
22
import { ZeroProvider } from "@rocicorp/zero/react";
3-
import { Zero } from "@rocicorp/zero";
4-
import { schema } from "@lib/zero-sync/schema";
53
import { ThemeProvider } from "@inkjs/ui";
64
import type React from "react";
75
import { customTheme } from "./theme";
86
import { Header } from "./components/header";
97
import { Footer } from "./footer";
8+
import type { AtwZero } from "../core/create-zero";
109

1110
type AppLayoutProps = {
1211
children: React.ReactNode;
1312
title: string;
13+
zero: AtwZero;
1414
};
1515

1616
export const AppLayout = ({
1717
children,
1818
title,
19+
zero,
1920
}: AppLayoutProps): React.ReactNode => {
20-
const z = new Zero({
21-
userID: "anon",
22-
server: "https://allthingsweb-sync.fly.dev",
23-
schema,
24-
kvStore: "mem",
25-
});
2621
return (
27-
<ZeroProvider zero={z}>
22+
<ZeroProvider zero={zero}>
2823
<ThemeProvider theme={customTheme}>
2924
<Header level={1}>{title}</Header>
3025
<Box flexDirection="column" padding={1}>

website/scripts/update-event.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { eq } from "drizzle-orm";
22
import { buildContainer } from "~/modules/container.server";
33
import { eventsTable, InsertEvent } from "@lib/db/schema.server";
44

5-
const slug = "2025-03-12-all-things-web-hack-evening";
5+
const slug = "2025-04-26-hackathon-at-sentry";
66
const eventData = {
7-
startDate: new Date("2025-03-12T17:30:00.000Z"),
8-
endDate: new Date("2025-03-12T20:30:00.000Z"),
7+
isDraft: false,
98
} satisfies Partial<InsertEvent>;
109

1110
async function main() {

0 commit comments

Comments
 (0)