Encrypted, ephemeral, private memos on atproto

feat(mcp): construct consumer client

graham.systems 0583998b bb13beb6

verified
Changed files
+44 -3
packages
+29
packages/mcp/env.ts
··· 1 + import { getLogger } from "@logtape/logtape"; 2 + import type { ConsumerOptions } from "@cistern/consumer"; 3 + 4 + export function collectOptions(): ConsumerOptions { 5 + const logger = getLogger(["cistern", "mcp"]); 6 + const handle = Deno.env.get("CISTERN_MCP_HANDLE"); 7 + const appPassword = Deno.env.get("CISTERN_MCP_APP_PASSWORD"); 8 + 9 + if (!handle || !appPassword) { 10 + logger.error( 11 + "CISTERN_MCP_HANDLE or CISTERN_MCP_APP_PASSWORD are not set in the environment", 12 + ); 13 + return Deno.exit(1); 14 + } 15 + 16 + const privateKey = Deno.env.get("CISTERN_MCP_PRIVATE_KEY"); 17 + const publicKeyUri = Deno.env.get("CISTERN_MCP_PUBLIC_KEY_URI"); 18 + 19 + return { 20 + appPassword, 21 + handle, 22 + keypair: privateKey && publicKeyUri 23 + ? { 24 + privateKey, 25 + publicKey: publicKeyUri, 26 + } 27 + : undefined, 28 + }; 29 + }
+5 -1
packages/mcp/hono.ts
··· 1 1 import { Hono } from "hono"; 2 2 import { cors } from "hono/cors"; 3 + import { createConsumer } from "@cistern/consumer"; 3 4 import { getLogger, withContext } from "@logtape/logtape"; 4 5 import { toFetchResponse, toReqRes } from "fetch-to-node"; 5 6 import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; 7 + import { collectOptions } from "./env.ts"; 6 8 import { createServer } from "./server.ts"; 7 9 8 10 export function createApp() { ··· 78 80 } else { 79 81 logger.info("creating new session {sessionId}", { sessionId }); 80 82 81 - const server = createServer(); 83 + const options = collectOptions(); 84 + const consumer = await createConsumer(options); 85 + const server = createServer(consumer); 82 86 83 87 session = new StreamableHTTPServerTransport({ 84 88 sessionIdGenerator: () => sessionId,
+8 -1
packages/mcp/index.ts
··· 1 1 import { parseArgs } from "@std/cli"; 2 + import { createConsumer } from "@cistern/consumer"; 2 3 import { AsyncLocalStorage } from "node:async_hooks"; 3 4 import { configure, getConsoleSink, getLogger } from "@logtape/logtape"; 4 5 import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 6 6 7 import { createServer } from "./server.ts"; 7 8 import { createApp } from "./hono.ts"; 9 + import { collectOptions } from "./env.ts"; 8 10 9 11 async function main() { 10 12 await configure({ ··· 32 34 if (!args.http) { 33 35 logger.info("starting in stdio mode"); 34 36 37 + const options = collectOptions(); 38 + const consumer = await createConsumer(options); 35 39 const transport = new StdioServerTransport(); 36 - const server = createServer(); 40 + const server = createServer(consumer); 37 41 38 42 await server.connect(transport); 39 43 } else { 40 44 logger.info("starting in streamable HTTP mode"); 45 + 46 + // Validate environment before starting the server 47 + collectOptions(); 41 48 42 49 const app = createApp(); 43 50
+2 -1
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 5 5 - export function createServer() { 6 + export function createServer(consumer: Consumer) { 6 7 const logger = getLogger("cistern-mcp"); 7 8 const server = new McpServer({ 8 9 name: "cistern-mcp",