AI EngineeringInterview preparation
Intermediate·Concept·2 min read

Conceptual MCP server: four business tools

Practice this question
  • This example defines server-side contracts and dispatch logic, not a particular MCP SDK's registration syntax.
  • Attach this registry to the selected SDK's authenticated request handlers, implement capability discovery, and encode its documented result envelopes.
  • This separates the durable engineering idea from rapidly changing transport APIs.
TypeScript
import { z } from "zod";

type Principal = { userId: string; tenantId: string; scopes: readonly string[] };
type Services = {
  customerForCaller(p: Principal, customerId: string): Promise<unknown>;
  orderForCaller(p: Principal, orderId: string): Promise<unknown>;
  createTicket(p: Principal, subject: string, body: string, key: string): Promise<unknown>;
  searchKnowledge(p: Principal, query: string): Promise<unknown>;
};
const Id = z.string().regex(/^[A-Za-z0-9_-]{1,80}$/);
const registry = {
  customer_lookup: { schema: z.object({ customerId: Id }).strict(), scope: "customer:read" },
  order_lookup: { schema: z.object({ orderId: Id }).strict(), scope: "order:read" },
  ticket_create: {
    schema: z.object({
      subject: z.string().min(1).max(200),
      body: z.string().min(1).max(5000),
    }).strict(),
    scope: "ticket:create",
  },
  knowledge_search: {
    schema: z.object({ query: z.string().min(1).max(1000) }).strict(),
    scope: "knowledge:read",
  },
} as const;

async function executeTool(
  name: string, raw: unknown, principal: Principal, services: Services,
  operationKey?: string,
): Promise<unknown> {
  // principal comes from verified middleware, never model-supplied JSON.
  if (!Object.prototype.hasOwnProperty.call(registry, name)) {
    throw new Error("Unknown tool");
  }
  const toolName = name as keyof typeof registry;
  if (!principal.scopes.includes(registry[toolName].scope)) {
    throw new Error("Tool scope denied");
  }
  switch (toolName) {
    case "customer_lookup": {
      const a = registry.customer_lookup.schema.parse(raw);
      return services.customerForCaller(principal, a.customerId);
    }
    case "order_lookup": {
      const a = registry.order_lookup.schema.parse(raw);
      return services.orderForCaller(principal, a.orderId);
    }
    case "ticket_create": {
      const a = registry.ticket_create.schema.parse(raw);
      // Reuse the host's persisted key for this logical write across retries.
      // The model cannot mint a new key to turn a retry into another ticket.
      const key = Id.parse(operationKey);
      return services.createTicket(principal, a.subject, a.body, key);
    }
    case "knowledge_search": {
      const a = registry.knowledge_search.schema.parse(raw);
      return services.searchKnowledge(principal, a.query);
    }
  }
}
  • The Services dependency deliberately owns object authorization and business rules.
  • Scope checks alone are insufficient.
  • The trusted host persists operationKey for one logical write and reuses it across retries; it is not a model-generated argument.
  • createTicket must enforce idempotency atomically and reject a changed payload under the same key, and the host/server must impose any required approval policy.
  • Read results should omit unnecessary PII.
  • Add deadlines, rate limits, output schemas, bounded sizes, audit events, and safe error envelopes in the transport layer.

How the agent consumes this server: the host establishes trusted identity, discovers allowed contracts, translates them to the model's tool format, receives a proposed call, applies its approval policy, sends the MCP request, validates the returned result, and supplies the observation to the model. In the insurance domain, order_lookup might become policy-order lookup, ticket_create creates a support case, and knowledge_search retrieves policy instructions. Claim submission would be a separate, more restrictive capability.


Adapted from 13_AI.MD · “Conceptual MCP server: four business tools”. Code examples are study fragments. Difficulty and core labels are editorial estimates; version-specific behavior should be checked against your project.