mcp-customer-server
mcp-customer-server
A runnable MCP server (TypeScript, official @modelcontextprotocol/sdk) exposing two tools:
get_customer_record(customer_id: "CUST-XXXXX")trigger_refund(customer_id: "CUST-XXXXX", amount: positive number, reason: string, min 10 chars)
Built to satisfy three things explicitly: strict input validation, standards-compliant JSON-RPC error mapping, and a hard guarantee that stdout carries nothing but protocol frames.
Setup
npm install
npm run build
npm start # runs the server on stdio, waiting for JSON-RPC inputDesign decisions
Low-level Server API, not the high-level McpServer helper. The high-level API is
convenient but hides how validation failures get reported. Here, CallToolRequestSchema's
handler validates arguments explicitly with Zod and throws McpError(ErrorCode.InvalidParams, ...)
on failure. The SDK's protocol layer turns that into a real JSON-RPC error object
(code: -32602) — not a "successful" tool result with an error message buried inside.
Protocol errors vs. tool-execution errors are kept separate on purpose:
Malformed input (wrong
customer_idformat, negative amount, short reason) → thrown asMcpError→ JSON-RPCerrorfield,-32602 InvalidParams.Valid input that fails for a business reason (customer not found, account suspended, refund exceeds balance) → returned as a normal tool result with
isError: true. The request was well-formed; the outcome was a failure. Collapsing these two cases into one error path is a common MCP server mistake that breaks client-side retry logic.Unexpected internal errors → logged in full to stderr, but the client only ever sees a generic
InternalError— no stack traces or internals leak over the wire.
stdout isolation is enforced, not just followed by convention. src/logger.ts writes
structured JSON logs to process.stderr exclusively, and lockdownConsole() monkey-patches
console.log/.info/.debug at startup to redirect to stderr as well, so a stray debug
statement (yours or a dependency's) can't silently corrupt the JSON-RPC stream on stdout.
Zod is the single source of truth for validation. zod-to-json-schema generates the
inputSchema advertised in tools/list directly from the same Zod schemas used to validate
incoming calls, so the two can't drift apart.
Verifying stdio isolation and protocol compliance
npm run test:stdioThis spawns the compiled server, drives it through a real initialize → tools/list →
tools/call handshake over raw stdio, and asserts:
every line on stdout parses as JSON (fails loudly on the first non-JSON line)
an out-of-format
customer_idand an invalidamount/reasonall come back aserror.code === -32602valid calls succeed with no
errorfieldlog output was actually observed on stderr (proving separation, not just absence of failure)
Manual exploration with MCP Inspector
npx @modelcontextprotocol/inspector node build/server.jsTry:
get_customer_recordwithcustomer_id: "CUST-00001"→ succeedsget_customer_recordwithcustomer_id: "12345"→ JSON-RPC error,-32602trigger_refundwithamount: -10→ JSON-RPC error,-32602trigger_refundwithreason: "no"→ JSON-RPC error,-32602(under 10 chars)trigger_refundagainstCUST-00003(seeded as suspended) → tool result withisError: true, not a protocol error
Mock data
Three customers are seeded in src/data.ts for demo purposes: CUST-00001 and
CUST-00002 are active with balances; CUST-00003 is suspended, to exercise the
business-logic error path.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/willsu42/mcp-customer-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server