Skip to main content
Glama

example-mcp-server-streamable-http

by yigitkonur
types.tsโ€ข13.7 kB
/** * @file src/types.ts * @description This file contains all core data structures, interfaces, and Zod schemas * for the Stateful MCP Calculator Server. It serves as the single source of truth for the * shapes of data used throughout the application, ensuring consistency and type safety. * * This separation follows the principle of separating data contracts from business logic, * making the codebase more maintainable and providing a clear overview of the system's * data architecture. * * Key Error Handling Ideas: * - Defines a hierarchy of custom `CalculatorServerError` types that extend `McpError`. * - Centralizes all Zod schemas, which are the first line of defense for validating * incoming data and preventing type-related errors. */ import { z } from 'zod'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; // ================================================================= // CORE INTERFACES AND DATA STRUCTURES // ================================================================= /** * @interface ISessionStore * @description Defines the contract for a session storage mechanism. This is a key part * of the **Strategy Pattern**, allowing us to swap storage backends (e.g., in-memory vs. Redis) * without changing the core application logic that uses it. * * The Strategy Pattern is fundamental to this architecture: * - The client code (server logic) depends only on this interface * - Concrete implementations (InMemorySessionStore, RedisSessionStore) provide the actual storage * - This enables runtime switching between storage modes without code changes * - Critical for supporting both development (no Redis) and production (Redis cluster) environments */ export interface ISessionStore { /** * Retrieve session data by session ID * @param sessionId - Unique identifier for the session * @returns Session data if found and not expired, null otherwise */ get(sessionId: string): Promise<SessionData | null>; /** * Store or update session data * @param sessionId - Unique identifier for the session * @param data - Complete session data to store */ set(sessionId: string, data: SessionData): Promise<void>; /** * Remove session data permanently * @param sessionId - Unique identifier for the session to delete */ delete(sessionId: string): Promise<void>; /** * Update the last activity timestamp and increment request counter * @param sessionId - Unique identifier for the session */ updateActivity(sessionId: string): Promise<void>; } /** * @interface Calculation * @description Represents a single, completed calculation event within a session. * This is part of the event sourcing pattern - each calculation is an immutable event * that gets appended to the session's history. This enables: * - Complete audit trail of all calculations * - Ability to replay/reconstruct session state * - Historical analytics and debugging capabilities */ export interface Calculation { /** Unique identifier for this specific calculation */ id: string; /** Session ID this calculation belongs to */ sessionId: string; /** Unix timestamp when calculation was performed */ timestamp: number; /** Type of operation performed (add, subtract, multiply, etc.) */ operation: string; /** Array of input values used in the calculation */ inputs: number[]; /** Final result of the calculation */ result: number; } /** * @interface SessionData * @description Represents the complete, stateful data for a single user session. * This object is what gets persisted in our storage backend (memory or Redis). * * CRITICAL ARCHITECTURAL NOTE: The `transport` and `server` instances are transient * and NOT serialized to storage. They are reconstructed on-demand when a session * is accessed by a server node that doesn't have them cached locally. This is the * foundation of the "Just-in-Time Instance Reconstruction" pattern that enables * horizontal scaling without sticky sessions. * * The calculations array implements a ring buffer pattern (max 50 entries) to * prevent unbounded memory growth while maintaining recent history. */ export interface SessionData { /** Unique session identifier */ sessionId: string; /** Transient: MCP transport instance (not persisted) */ transport: StreamableHTTPServerTransport; /** Transient: MCP server instance (not persisted) */ server: McpServer; /** Unix timestamp when session was created */ startTime: number; /** Unix timestamp of last activity (used for timeout/cleanup) */ lastActivity: number; /** Total number of requests processed in this session */ requestCount: number; /** Calculation history (ring buffer, max 50 entries) */ calculations: Calculation[]; } /** * @interface TransportWithSessionId * @description Extension of StreamableHTTPServerTransport that includes session ID. * This is used internally for type safety when we manually set the sessionId property * during just-in-time reconstruction. The sessionId property is not part of the * official SDK interface but is required for our session management logic. */ export interface TransportWithSessionId extends StreamableHTTPServerTransport { sessionId: string; } // ================================================================= // ZOD VALIDATION SCHEMAS FOR TOOLS // ================================================================= /** * These schemas serve dual purposes: * 1. Runtime validation of incoming tool arguments from MCP clients * 2. TypeScript type inference for compile-time safety * * Each schema is exported as a constant so it can be imported by the server * logic and referenced in tool registrations. This centralization ensures * consistency between validation and type definitions. */ /** * Zod schema for the 'calculate' tool arguments. * Validates basic arithmetic operations with optional streaming support. */ export const calculateArgsSchema = z.object({ a: z.number().describe('First operand'), b: z.number().describe('Second operand'), op: z.enum(['add', 'subtract', 'multiply', 'divide']).describe('Operation to perform'), stream: z.boolean().optional().describe('Stream intermediate results'), }); /** * Zod schema for the 'batch_calculate' tool arguments. * Validates arrays of calculations for batch processing. */ export const batchCalculateArgsSchema = z.object({ calculations: z .array( z.object({ a: z.number(), b: z.number(), op: z.enum(['add', 'subtract', 'multiply', 'divide']), }), ) .describe('Array of calculations to perform'), reportProgress: z.boolean().optional().describe('Report progress during batch processing'), }); /** * Zod schema for the 'advanced_calculate' tool arguments. * Validates scientific and mathematical operations. */ export const advancedCalculateArgsSchema = z.object({ operation: z .enum(['factorial', 'power', 'sqrt', 'log', 'sin', 'cos', 'tan']) .describe('Advanced operation'), value: z.number().describe('Input value'), base: z.number().optional().describe('Base for power or logarithm operations'), }); /** * Zod schema for the 'demo_progress' tool arguments. * Validates parameters for the progress notification demonstration. */ export const demoProgressArgsSchema = z.object({ steps: z.number().default(5).describe('Number of progress steps'), }); /** * Zod schema for the sample educational tool. * This is used when SAMPLE_TOOL_NAME environment variable is set. */ export const sampleToolArgsSchema = z.object({ value: z.string().describe('String value to process'), }); // ================================================================= // ZOD VALIDATION SCHEMAS FOR PROMPTS // ================================================================= /** * These schemas validate arguments passed to registered prompts. * Prompts are templates that help clients generate appropriate requests * to the MCP server's tools and resources. */ /** * Zod schema for the 'explain-calculation' prompt arguments. * Validates parameters for step-by-step calculation explanations. */ export const explainCalculationArgsSchema = z.object({ operation: z.string().describe('The calculation to explain'), level: z.string().optional().describe('Explanation level: basic, intermediate, advanced'), }); /** * Zod schema for the 'generate-problems' prompt arguments. * Validates parameters for generating practice math problems. */ export const generateProblemsArgsSchema = z.object({ topic: z.string().describe('Math topic (e.g., "fractions", "algebra", "geometry")'), difficulty: z.string().describe('Difficulty level: easy, medium, hard'), count: z.string().describe('Number of problems to generate'), }); /** * Zod schema for the 'solve_math_problem' prompt arguments. * Validates parameters for step-by-step problem solving. */ export const solveMathProblemArgsSchema = z.object({ problem: z.string().describe('The problem to solve'), showWork: z.string().describe('Show detailed work'), }); /** * Zod schema for the 'explain_formula' prompt arguments. * Validates parameters for detailed formula explanations. */ export const explainFormulaArgsSchema = z.object({ formula: z.string().describe('The formula to explain'), context: z.string().optional().describe('Application context'), }); /** * Zod schema for the 'calculator_assistant' prompt arguments. * Validates parameters for general calculation assistance. */ export const calculatorAssistantArgsSchema = z.object({ query: z.string().describe('What you need help calculating'), }); // ================================================================= // TYPE INFERENCE FROM ZOD SCHEMAS // ================================================================= /** * TypeScript types inferred from Zod schemas. * These provide compile-time type safety while reusing the runtime validation logic. * This pattern ensures that types and validation stay in sync automatically. */ export type CalculateArgs = z.infer<typeof calculateArgsSchema>; export type BatchCalculateArgs = z.infer<typeof batchCalculateArgsSchema>; export type AdvancedCalculateArgs = z.infer<typeof advancedCalculateArgsSchema>; export type DemoProgressArgs = z.infer<typeof demoProgressArgsSchema>; export type SampleToolArgs = z.infer<typeof sampleToolArgsSchema>; export type ExplainCalculationArgs = z.infer<typeof explainCalculationArgsSchema>; export type GenerateProblemsArgs = z.infer<typeof generateProblemsArgsSchema>; export type SolveMathProblemArgs = z.infer<typeof solveMathProblemArgsSchema>; export type ExplainFormulaArgs = z.infer<typeof explainFormulaArgsSchema>; export type CalculatorAssistantArgs = z.infer<typeof calculatorAssistantArgsSchema>; // ================================================================= // CONFIGURATION TYPES // ================================================================= /** * @interface ServerConfig * @description Configuration object for the entire application. * Centralizes all environment-based configuration in a type-safe structure. * This makes configuration management explicit and easier to validate. */ export interface ServerConfig { /** HTTP server port */ port: number; /** CORS allowed origin */ corsOrigin: string; /** Session timeout in milliseconds */ sessionTimeout: number; /** Whether to use Redis for distributed storage */ useRedis: boolean; /** Redis connection URL */ redisUrl: string; /** Logging level */ logLevel: string; /** Rate limiting configuration */ rateLimit: { windowMs: number; max: number; }; } // ================================================================= // CUSTOM APPLICATION-SPECIFIC ERRORS // ================================================================= /** * @summary Base class for all custom errors within this application. * @remarks Extending McpError ensures that our custom errors are compatible with * the MCP protocol's error-handling mechanisms. This allows us to catch * and handle application-specific failures with more granularity. */ export class CalculatorServerError extends McpError { constructor( code: number, message: string, public readonly context?: unknown, ) { super(code, message, context); this.name = this.constructor.name; } } /** * @summary Thrown when a requested session does not exist, has expired, or is invalid. * @remarks This error is critical for the HTTP layer to distinguish between a * generic bad request and a specific session lifecycle failure. It typically * results in the client being instructed to re-initialize. */ export class SessionNotFoundError extends CalculatorServerError { constructor(message: string, context?: { sessionId?: string }) { super(ErrorCode.InvalidRequest, message, context); } } /** * @summary Thrown when a persistent storage operation (e.g., Redis read/write) fails. * @remarks This error wraps the underlying database or network error, preventing * implementation details from leaking to the client. The original error * should be logged on the server for debugging. This typically results * in a generic `InternalError` being sent to the client. */ export class StorageOperationFailedError extends CalculatorServerError { constructor( message: string, public readonly originalError: Error, context?: unknown, ) { super(ErrorCode.InternalError, message, context); } }

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/yigitkonur/example-mcp-server-streamable-http'

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