MCP Development Patterns
This skill loads comprehensive patterns for building MCP tools using the @umbraco-cms/mcp-server-sdk.
When to Use
Use this skill when:
- Starting to create new MCP tools
- Needing a reference for tool patterns
- Understanding how to use the toolkit helpers
- Setting up a new MCP server project
Core Architecture
Tool Definition Pattern
All tools follow this structure:
import {
withStandardDecorators,
executeGetApiCall,
executeVoidApiCall,
createToolResult,
CAPTURE_RAW_HTTP_RESPONSE,
ToolDefinition,
} from "@umbraco-cms/mcp-server-sdk";
const MyTool = {
name: "tool-name",
description: "What the tool does",
inputSchema: zodSchema.shape,
outputSchema: responseSchema, // For GET operations
slices: ["read"], // Categorization
annotations: {
readOnlyHint: true, // For GET
destructiveHint: true, // For DELETE
idempotentHint: true, // For PUT
},
handler: async (params) => {
// Implementation
},
} satisfies ToolDefinition<...>;
export default withStandardDecorators(MyTool);
API Client Configuration
Configure once at server startup:
import { configureApiClient } from "@umbraco-cms/mcp-server-sdk";
import { getMyAPI } from "./api/generated/myApi.js";
configureApiClient(() => getMyAPI());
Helper Functions
executeGetApiCall
For GET operations that return data:
return executeGetApiCall<ReturnType<Client["method"]>, Client>(
(client) => client.method(params, CAPTURE_RAW_HTTP_RESPONSE)
);
executeVoidApiCall
For DELETE/PUT operations that return void:
return executeVoidApiCall<Client>(
(client) => client.deleteItem(id, CAPTURE_RAW_HTTP_RESPONSE)
);
createToolResult / createToolResultError
For custom responses (typically CREATE operations):
if (response.status === 201) {
return createToolResult({ success: true, id: extractedId });
} else {
return createToolResultError(response.data);
}
Slices (Tool Categorization)
Tools are categorized by operation type:
read- GET operationscreate- POST create operationsupdate- PUT operationsdelete- DELETE operationssearch- Search/filter operations
Annotations Reference
| Operation | readOnlyHint | destructiveHint | idempotentHint |
|---|---|---|---|
| GET | ✅ | ❌ | ❌ |
| DELETE | ❌ | ✅ | ❌ |
| POST | ❌ | ❌ | ❌ |
| PUT | ❌ | ❌ | ✅ |
Important: DELETE is NOT idempotent (2nd call returns 404).
Schema Flattening for Nested Reference Objects
Umbraco APIs wrap references to other entities in a nested object — most commonly
parent: { id } or parent: { path }. Never expose that shape to the LLM: accept a flat
scalar (parentId, path) and rebuild the nested object inside the handler. See
references/schema-flattening.md for the full pattern, the when-to-apply matrix, and the rules.
Project Structure
src/
├── api/
│ ├── client.ts # API client with mock support
│ ├── openapi.yaml # OpenAPI spec
│ └── generated/ # Orval-generated client + Zod schemas
├── tools/
│ └── {entity}/
│ ├── get/
│ ├── post/
│ ├── put/
│ ├── delete/
│ └── index.ts # Collection export
└── index.ts # MCP server entry
Orval Code Generation
Generate client and Zod schemas from OpenAPI:
npm run generate
Configuration in orval.config.ts:
- Generates TypeScript client (configurable via
clientoption) - Generates Zod schemas for validation
- Uses custom mutator for authentication
Best Practices
- Use Zod schemas from generated code for type safety
- Hide UUID generation - create internally, don't expect LLM to provide
- Clear descriptions - tools should be self-documenting
- Consistent naming - follow
{action}-{entity}pattern - Error handling -
withStandardDecoratorshandles errors automatically - Flatten nested reference objects - accept
parentId/path, rebuildparent: { id }/parent: { path }in the handler (see Schema Flattening above, orreferences/schema-flattening.mdfor the full pattern)