Skip to main content
Glama
fmangot
by fmangot
worker.ts8.74 kB
/** * Sequential Thinking MCP Server - Cloudflare Workers with Durable Objects * Proper persistent state implementation for serverless deployment */ import { ThoughtInput, StoredThought } from './types.js'; import { validateThoughtInput, ValidationError } from './utils/validators.js'; // Environment bindings export interface Env { THINKING_SESSIONS: DurableObjectNamespace; } /** * Durable Object for persistent session storage */ export class ThinkingSession implements DurableObject { private state: DurableObjectState; private sessions: Map<string, { thoughts: StoredThought[]; branches: Map<string, StoredThought[]>; createdAt: number; updatedAt: number; }>; private currentSessionId: string; constructor(state: DurableObjectState) { this.state = state; this.sessions = new Map(); this.currentSessionId = `session-${Date.now()}-${Math.random().toString(36).substring(7)}`; } async fetch(request: Request): Promise<Response> { const url = new URL(request.url); // CORS headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, x-session-id', }; if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } try { // Initialize from storage on first request if (this.sessions.size === 0) { await this.loadFromStorage(); } // Add thought if (url.pathname === '/think' && request.method === 'POST') { const input = await request.json() as ThoughtInput; try { validateThoughtInput(input); } catch (error) { return new Response( JSON.stringify({ error: error instanceof Error ? error.message : 'Validation error', code: 'VALIDATION_ERROR', }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } const result = await this.addThought(input); return new Response(JSON.stringify(result), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } // Get sequence if (url.pathname === '/sequence' && request.method === 'GET') { const session = this.sessions.get(this.currentSessionId); return new Response( JSON.stringify({ sessionId: this.currentSessionId, thoughtCount: session?.thoughts.length || 0, thoughts: session?.thoughts || [], }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } // Reset session if (url.pathname === '/reset' && request.method === 'POST') { this.currentSessionId = this.generateSessionId(); this.sessions.set(this.currentSessionId, { thoughts: [], branches: new Map(), createdAt: Date.now(), updatedAt: Date.now(), }); await this.saveToStorage(); return new Response( JSON.stringify({ message: 'New thinking session started', sessionId: this.currentSessionId, }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } // Health check if (url.pathname === '/health') { return new Response( JSON.stringify({ status: 'ok', timestamp: Date.now(), sessions: this.sessions.size, }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } // Info if (url.pathname === '/' || url.pathname === '/info') { return new Response( JSON.stringify({ name: 'Sequential Thinking MCP Server (Cloudflare Workers + Durable Objects)', version: '1.0.0', description: 'Remote MCP server with persistent state', endpoints: { 'POST /think': 'Add a thought to the sequence', 'GET /sequence': 'Get current thought sequence', 'POST /reset': 'Reset session', 'GET /health': 'Health check', }, storage: 'Durable Objects (persistent)', }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } return new Response( JSON.stringify({ error: 'Not found' }), { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } catch (error) { console.error('Error:', error); return new Response( JSON.stringify({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } } private generateSessionId(): string { return `session-${Date.now()}-${Math.random().toString(36).substring(7)}`; } private async addThought(input: ThoughtInput) { let session = this.sessions.get(this.currentSessionId); if (!session) { session = { thoughts: [], branches: new Map(), createdAt: Date.now(), updatedAt: Date.now(), }; this.sessions.set(this.currentSessionId, session); } const storedThought: StoredThought = { ...input, timestamp: Date.now(), sessionId: this.currentSessionId, }; if (input.branchId && input.branchFromThought !== undefined) { let branchThoughts = session.branches.get(input.branchId); if (!branchThoughts) { branchThoughts = []; session.branches.set(input.branchId, branchThoughts); } branchThoughts.push(storedThought); } else { session.thoughts.push(storedThought); } session.updatedAt = Date.now(); // Persist to Durable Storage await this.saveToStorage(); let message = `Thought ${input.thoughtNumber}`; if (input.isRevision && input.revisesThought !== undefined) { message += ` (revising thought ${input.revisesThought})`; } if (input.branchId) { message += ` [branch: ${input.branchId}]`; } message += `: ${input.thought}`; return { success: true, thoughtNumber: input.thoughtNumber, message, sequence: session.thoughts, totalThoughts: input.totalThoughts, }; } private async saveToStorage(): Promise<void> { // Convert Map to serializable format const data = { currentSessionId: this.currentSessionId, sessions: Array.from(this.sessions.entries()).map(([id, session]) => ({ id, thoughts: session.thoughts, branches: Array.from(session.branches.entries()), createdAt: session.createdAt, updatedAt: session.updatedAt, })), }; await this.state.storage.put('data', data); } private async loadFromStorage(): Promise<void> { const data = await this.state.storage.get<{ currentSessionId: string; sessions: Array<{ id: string; thoughts: StoredThought[]; branches: Array<[string, StoredThought[]]>; createdAt: number; updatedAt: number; }>; }>('data'); if (data) { this.currentSessionId = data.currentSessionId; this.sessions = new Map( data.sessions.map((session) => [ session.id, { thoughts: session.thoughts, branches: new Map(session.branches), createdAt: session.createdAt, updatedAt: session.updatedAt, }, ]) ); } } } /** * Main worker - routes requests to Durable Objects */ export default { async fetch(request: Request, env: Env): Promise<Response> { try { // Get or create a Durable Object instance // Using a single instance for all requests (can be modified for multi-tenant) const id = env.THINKING_SESSIONS.idFromName('default'); const stub = env.THINKING_SESSIONS.get(id); // Forward request to the Durable Object return await stub.fetch(request); } catch (error) { return new Response( JSON.stringify({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', }), { status: 500, headers: { 'Content-Type': 'application/json' }, } ); } }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fmangot/Mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server