Encrypted, ephemeral, private memos on atproto

feat(mcp): add tools

graham.systems cd959ca3 7b98098b

verified
Changed files
+58 -13
packages
mcp
+58 -13
packages/mcp/server.ts
··· 1 1 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 2 import { getLogger } from "@logtape/logtape"; 3 3 import { z } from "zod"; 4 - import type { Consumer } from "@cistern/consumer"; 4 + import type { Consumer, DecryptedMemo } from "@cistern/consumer"; 5 5 6 6 export function createServer(consumer: Consumer) { 7 7 const logger = getLogger("cistern-mcp"); ··· 10 10 version: "1.0.0", 11 11 }); 12 12 13 + let iterator: 14 + | AsyncGenerator<DecryptedMemo, void, "stop" | undefined> 15 + | undefined; 16 + 13 17 server.registerTool( 14 - "add", 18 + "next_memo", 19 + { 20 + title: "Next memo", 21 + description: "Retrieve the next outstanding memo", 22 + outputSchema: { tid: z.string(), text: z.string() }, 23 + }, 24 + async () => { 25 + if (!iterator) { 26 + logger.trace("creating iterator"); 27 + iterator ??= consumer.listMemos(); 28 + } 29 + 30 + const res = await iterator.next(); 31 + 32 + if (res.done) { 33 + logger.trace("iterator done; cleaning up"); 34 + iterator = undefined; 35 + } 36 + 37 + return { 38 + content: [{ 39 + type: "text", 40 + text: res.value?.text ?? "no memos remaining", 41 + }], 42 + structuredContent: { 43 + tid: res.value?.tid ?? "", 44 + text: res.value?.text ?? "no memos remaining", 45 + }, 46 + }; 47 + }, 48 + ); 49 + 50 + server.registerTool( 51 + "delete_memo", 15 52 { 16 - title: "Addition Tool", 17 - description: "Add two numbers", 18 - inputSchema: { a: z.number(), b: z.number() }, 19 - outputSchema: { result: z.number() }, 53 + title: "Delete memo", 54 + description: 55 + "Delete a memo by record key, after it has been handled as instructed by the user", 56 + inputSchema: { key: z.string() }, 57 + outputSchema: { success: z.boolean() }, 20 58 }, 21 - ({ a, b }) => { 22 - logger.trace("Addition Tool called", { a, b }); 59 + async ({ key }) => { 60 + try { 61 + await consumer.deleteMemo(key); 23 62 24 - const output = { result: a + b }; 63 + return { 64 + content: [{ type: "text", text: "delete successful" }], 65 + structuredContent: { success: true }, 66 + }; 67 + } catch (error) { 68 + logger.error("failed to delete memo: {error}", { error }); 25 69 26 - return Promise.resolve({ 27 - content: [{ type: "text", text: JSON.stringify(output) }], 28 - structuredContent: output, 29 - }); 70 + return { 71 + content: [{ type: "text", text: "delete unsuccessful" }], 72 + structuredContent: { success: false }, 73 + }; 74 + } 30 75 }, 31 76 ); 32 77