Skip to main content
Glama

Google Maps MCP Server

by iceener
index.ts6.13 kB
/** * Cloudflare Workers router factory. * Creates a complete router with MCP endpoints. */ import { Router } from 'itty-router'; import type { UnifiedConfig } from '../../shared/config/env.js'; import { createEncryptor } from '../../shared/crypto/aes-gcm.js'; import { corsPreflightResponse, withCors } from '../../shared/http/cors.js'; import type { SessionStore } from '../../shared/storage/interface.js'; import { KvSessionStore } from '../../shared/storage/kv.js'; import { MemorySessionStore } from '../../shared/storage/memory.js'; import { initializeStorage } from '../../shared/storage/singleton.js'; import { sharedLogger as logger } from '../../shared/utils/logger.js'; import { handleMcpGet, handleMcpRequest } from './mcp.handler.js'; // ───────────────────────────────────────────────────────────────────────────── // Types // ───────────────────────────────────────────────────────────────────────────── export interface WorkerEnv { /** KV namespace for session storage */ TOKENS?: KVNamespace; /** Base64url-encoded 32-byte key for AES-256-GCM encryption */ RS_TOKENS_ENC_KEY?: string; /** All other env vars */ [key: string]: unknown; } interface KVNamespace { get(key: string): Promise<string | null>; put( key: string, value: string, options?: { expiration?: number; expirationTtl?: number }, ): Promise<void>; delete(key: string): Promise<void>; } export interface RouterContext { sessionStore: SessionStore; config: UnifiedConfig; } // ───────────────────────────────────────────────────────────────────────────── // Shared State (persists across requests within same worker instance) // ───────────────────────────────────────────────────────────────────────────── let sharedSessionStore: MemorySessionStore | null = null; // ───────────────────────────────────────────────────────────────────────────── // Storage Initialization // ───────────────────────────────────────────────────────────────────────────── /** * Initialize storage for the worker. * Uses KV with memory fallback and optional encryption. */ export function initializeWorkerStorage( env: WorkerEnv, config: UnifiedConfig, ): { sessionStore: SessionStore } | null { const kvNamespace = env.TOKENS; if (!kvNamespace) { logger.error('worker_storage', { message: 'No KV namespace bound - storage unavailable', }); return null; } // Initialize shared memory fallback ONCE per worker instance if (!sharedSessionStore) { sharedSessionStore = new MemorySessionStore(); } // Set up encryption let encrypt: (s: string) => Promise<string>; let decrypt: (s: string) => Promise<string>; if (env.RS_TOKENS_ENC_KEY) { const encryptor = createEncryptor(env.RS_TOKENS_ENC_KEY); encrypt = encryptor.encrypt; decrypt = encryptor.decrypt; logger.debug('worker_storage', { message: 'KV encryption enabled' }); } else { encrypt = async (s) => s; decrypt = async (s) => s; if (config.NODE_ENV === 'production') { logger.warning('worker_storage', { message: 'RS_TOKENS_ENC_KEY not set! KV data is unencrypted.', }); } } // Create KV session store with memory fallback const sessionStore = new KvSessionStore(kvNamespace, { encrypt, decrypt, fallback: sharedSessionStore, }); // Register with singleton for shared access initializeStorage(sessionStore); return { sessionStore }; } // ───────────────────────────────────────────────────────────────────────────── // Router Factory // ───────────────────────────────────────────────────────────────────────────── const MCP_ENDPOINT_PATH = '/mcp'; /** * Create a configured router for the worker. */ export function createWorkerRouter(ctx: RouterContext): { fetch: (request: Request) => Promise<Response>; } { const router = Router(); const { sessionStore, config } = ctx; // CORS preflight router.options('*', () => corsPreflightResponse()); // MCP endpoints router.get(MCP_ENDPOINT_PATH, () => handleMcpGet()); router.post(MCP_ENDPOINT_PATH, (request: Request) => handleMcpRequest(request, { sessionStore, config }), ); // Health check router.get('/health', () => withCors( new Response(JSON.stringify({ status: 'ok', timestamp: Date.now() }), { headers: { 'Content-Type': 'application/json' }, }), ), ); // Catch-all 404 router.all('*', () => withCors(new Response('Not Found', { status: 404 }))); return router; } /** * Shim process.env for shared modules that expect Node.js environment. * Workers don't have process.env natively, so we polyfill it. */ export function shimProcessEnv(env: WorkerEnv): void { const g = globalThis as unknown as { process?: { env?: Record<string, unknown> }; }; g.process = g.process || {}; g.process.env = { ...(g.process.env ?? {}), ...(env as Record<string, unknown>) }; }

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/iceener/maps-streamable-mcp-server'

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