| name | mcp-categorical |
| description | MCP (Model Context Protocol) server patterns with categorical tool composition and typed context protocols. Use when building MCP servers with categorical composition patterns, designing tool interfaces using functors and natural transformations, implementing typed context management, or creating composable AI tool ecosystems with categorical guarantees. |
MCP Categorical Tool Composition
Categorical patterns for Model Context Protocol server design and tool composition.
Core Categorical Mapping
MCP structures map to category theory:
- Tools: Morphisms
Parameters → Result
- Resources: Objects with URI-based identity
- Prompts: Exponential objects
Context → Prompt
- Context: Product type of available information
- Server: Category of tools with composition
Basic MCP Server (TypeScript)
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "categorical-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "analyze",
description: "Analyze input with categorical structure",
inputSchema: {
type: "object",
properties: {
input: { type: "string" },
category: { type: "string" }
},
required: ["input"]
}
}
]
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "analyze") {
return {
content: [{ type: "text", text: `Analyzed: ${args.input}` }]
};
}
throw new Error(`Unknown tool: ${name}`);
});
const transport = new StdioServerTransport();
server.connect(transport);
Categorical Tool Composition
Tool as Functor
import { z } from "zod";
interface Tool<P, R> {
name: string;
schema: z.ZodType<P>;
execute: (params: P) => Promise<R>;
}
function mapTool<P, R, S>(
tool: Tool<P, R>,
f: (r: R) => S
): Tool<P, S> {
return {
name: tool.name,
schema: tool.schema,
execute: async (params) => f(await tool.execute(params))
};
}
const searchTool: Tool<{ query: string }, string[]> = {
name: "search",
schema: z.object({ query: z.string() }),
execute: async ({ query }) => [`Result for: ${query}`]
};
const formattedSearch = mapTool(searchTool, (results) =>
results.join("\n")
);
Tool Composition (Kleisli)
function composeTool<A, B, C>(
first: Tool<A, B>,
second: Tool<B, C>
): Tool<A, C> {
return {
name: `${first.name}_then_${second.name}`,
schema: first.schema,
execute: async (params: A) => {
const intermediate = await first.execute(params);
return second.execute(intermediate as any);
}
};
}
function productTool<A, B, C>(
tool1: Tool<A, B>,
tool2: Tool<A, C>
): Tool<A, [B, C]> {
return {
name: `${tool1.name}_and_${tool2.name}`,
schema: tool1.schema,
execute: async (params: A) => {
const [r1, r2] = await Promise.all([
tool1.execute(params),
tool2.execute(params)
]);
return [r1, r2];
}
};
}
Natural Transformation Between Tool Categories
interface ToolTransform<F, G> {
transform: <P, R>(tool: Tool<P, R>) => Tool<P, R>;
}
const withLogging: ToolTransform<any, any> = {
transform: (tool) => ({
...tool,
execute: async (params) => {
console.log(`Calling ${tool.name} with`, params);
const result = await tool.execute(params);
console.log(`Result from ${tool.name}:`, result);
return result;
}
})
};
const withRetry = (maxRetries: number): ToolTransform<any, any> => ({
transform: (tool) => ({
...tool,
execute: async (params) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await tool.execute(params);
} catch (e) {
if (i === maxRetries - 1) throw e;
}
}
throw new Error("Unreachable");
}
})
});
Python MCP Server
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
class ToolCategory:
"""Category of tools with composition."""
def __init__(self):
self.tools: dict[str, Tool] = {}
self.handlers: dict[str, callable] = {}
def register(self, name: str, schema: dict, handler: callable):
"""Register tool as morphism."""
self.tools[name] = Tool(
name=name,
description=f"Tool: {name}",
inputSchema=schema
)
self.handlers[name] = handler
def compose(self, name1: str, name2: str) -> str:
"""Compose two tools."""
composed_name = f"{name1}_then_{name2}"
async def composed_handler(params):
result1 = await self.handlers[name1](params)
return await self.handlers[name2](result1)
self.handlers[composed_name] = composed_handler
return composed_name
app = Server("categorical-mcp")
category = ToolCategory()
category.register(
"fetch",
{"type": "object", "properties": {"url": {"type": "string"}}},
lambda p: f"Fetched: {p['url']}"
)
category.register(
"parse",
{"type": "object", "properties": {"content": {"type": "string"}}},
lambda p: f"Parsed: {p['content']}"
)
@app.list_tools()
async def list_tools():
return list(category.tools.values())
@app.call_tool()
async def call_tool(name: str, arguments: dict):
handler = category.handlers.get(name)
if handler:
result = await handler(arguments) if asyncio.iscoroutinefunction(handler) else handler(arguments)
return [TextContent(type="text", text=str(result))]
raise ValueError(f"Unknown tool: {name}")
async def main():
async with stdio_server() as (read, write):
await app.run(read, write)
Resource Management (Objects)
interface Resource<T> {
uri: string;
name: string;
mimeType: string;
read: () => Promise<T>;
}
function mapResource<T, U>(
resource: Resource<T>,
f: (t: T) => U
): Resource<U> {
return {
...resource,
read: async () => f(await resource.read())
};
}
class ResourceCategory {
private resources: Map<string, Resource<any>> = new Map();
register<T>(resource: Resource<T>): void {
this.resources.set(resource.uri, resource);
}
get<T>(uri: string): Resource<T> | undefined {
return this.resources.get(uri);
}
toTool<T>(uri: string): Tool<{}, T> {
const resource = this.get<T>(uri);
if (!resource) throw new Error(`Unknown resource: ${uri}`);
return {
name: `read_${resource.name}`,
schema: z.object({}),
execute: async () => resource.read()
};
}
}
Typed Context Protocol
import { z } from "zod";
const ContextSchema = z.object({
conversation: z.array(z.object({
role: z.enum(["user", "assistant"]),
content: z.string()
})),
tools: z.array(z.string()),
resources: z.array(z.string()),
metadata: z.record(z.unknown())
});
type Context = z.infer<typeof ContextSchema>;
const contextOps = {
addMessage: (ctx: Context, role: "user" | "assistant", content: string): Context => ({
...ctx,
conversation: [...ctx.conversation, { role, content }]
}),
filterTools: (ctx: Context, predicate: (t: string) => boolean): Context => ({
...ctx,
tools: ctx.tools.filter(predicate)
}),
merge: (ctx1: Context, ctx2: Context): Context => ({
conversation: [...ctx1.conversation, ...ctx2.conversation],
tools: [...new Set([...ctx1.tools, ...ctx2.tools])],
resources: [...new Set([...ctx1.resources, ...ctx2.resources])],
metadata: { ...ctx1.metadata, ...ctx2.metadata }
})
};
Prompt Templates (Exponential Objects)
interface PromptTemplate {
name: string;
template: (ctx: Context) => string;
}
function composePrompts(
p1: PromptTemplate,
p2: PromptTemplate
): PromptTemplate {
return {
name: `${p1.name}_then_${p2.name}`,
template: (ctx) => `${p1.template(ctx)}\n\n${p2.template(ctx)}`
};
}
const systemPrompt: PromptTemplate = {
name: "system",
template: (ctx) => `You have access to: ${ctx.tools.join(", ")}`
};
const contextPrompt: PromptTemplate = {
name: "context",
template: (ctx) => ctx.conversation
.map(m => `${m.role}: ${m.content}`)
.join("\n")
};
Categorical Guarantees
MCP with categorical patterns ensures:
- Tool Composability: Tools compose via Kleisli composition
- Type Safety: Zod schemas validate parameters at boundaries
- Resource Identity: URIs provide unique object identification
- Context Coherence: Product structure preserves all context
- Natural Transformations: Tool transformations preserve structure