Files
homarr/.agents/skills/mcp-integration/SKILL.md
T
Thomas 05ba57c2fa chore: migrate cursor rules to portable agent skills + mantine MCP guidance
* chore: migrate cursor rules to portable agent skills and add mantine MCP guidance

Replace Cursor-only .cursor/rules/*.mdc with Agent Skills open-standard
SKILL.md files in .agents/skills/ (canonical), mirrored to .cursor/skills/
and .claude/skills/ so they work across Claude Code, Cursor, Codex, and
OpenCode. Add mantine MCP server guidance to AGENTS.md and un-ignore
.agents/skills/ in .gitignore.

* refactor: single-source agent skills at .agents/skills

Cursor, Codex, and OpenCode all read .agents/skills/ natively, so the
.cursor/skills/ mirror was redundant. Claude Code reads .claude/skills/,
which is now a symlink back to .agents/skills/ so the content lives once.
Deletes .cursor/skills/ and converts .claude/skills/ to a symlink.

* chore: ship mantine MCP server via project-scoped .mcp.json

Declares the keyless @mantine/mcp-server in .mcp.json (Claude Code
project-scope standard, committable) and documents MCP server setup and
the mantine entry in AGENTS.md.

* chore: address coderabbit security-review and markdown lint findings

Add explicit security-review guidance before exposing MCP tools and label
plain-text code fences to satisfy markdownlint MD040.
2026-08-16 16:27:51 +02:00

4.7 KiB

name, description
name description
mcp-integration Expose tRPC procedures as MCP tools for AI clients. Use when modifying procedures in packages/api/src/router, adding a new tRPC procedure or router, or deciding what to expose via Homarr's MCP interface. Covers .meta({ mcp }), router registration in packages/api/src/mcp.ts, input schema rules, and what to expose vs skip.

MCP Integration

Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.

Exposing a procedure makes it callable by any AI client with an API key. Before adding .meta({ mcp }), review what the tool exposes: authorization requirements, sensitive data, tenant isolation, auditability, and whether the action is destructive. Prefer exposing read-only queries over mutations unless the mutation is safe and permission-scoped. When in doubt, ask for a security review before shipping a new MCP tool.

Adding MCP to a procedure

Add .meta({ mcp: { enabled: true, description: "..." } }) to the procedure chain:

// Read-only tool (query)
myProcedure: protectedProcedure
  .meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
  .input(z.object({ limit: z.number().default(10) }))
  .query(async ({ ctx, input }) => { ... })

// Write tool (mutation)
myAction: protectedProcedure
  .meta({ mcp: { enabled: true, description: "Delete an item by ID. Requires interact permission on the integration" } })
  .input(z.object({ id: z.string() }))
  .mutation(async ({ ctx, input }) => { ... })

// No-input procedure (z.void) — works fine, no .input() needed
getAll: protectedProcedure
  .meta({ mcp: { enabled: true, description: "List all resources" } })
  .query(async ({ ctx }) => { ... })

The .meta() call must come before .input() and .query()/.mutation() in the chain.

Writing good descriptions

The description is the only thing an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.

Must include:

  • What the tool returns or does
  • Which integrations/services it works with (if applicable)
  • How to get required IDs (e.g., "Use integration_all to get the integrationId")
  • Permission requirements (if non-obvious)

Good:

"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"

Bad:

"Get calendar events"

For tools that return permission fields, explain what they mean:

"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"

Registering in the MCP router

After adding .meta() to procedures, you must register the router in packages/api/src/mcp.ts. This file uses eager imports (no lazy()) because MCP tool extraction requires synchronous access to all procedure definitions.

// packages/api/src/mcp.ts
import { myNewRouter } from "./router/my-new-feature";

export const mcpRouter = createTRPCRouter({
  // ... existing routers
  myFeature: myNewRouter,
});

If your router is nested (e.g., widget sub-routers), import and register the sub-router directly — do not import a parent that uses lazy().

What to expose vs. what to skip

Expose:

  • Data queries (list, search, get by ID)
  • Actions users would ask an AI to do (toggle, create, delete, approve)
  • Status checks (health, stats, summaries)

Skip:

  • Subscriptions (WebSocket-only, not supported by MCP)
  • Internal procedures (session management, onboarding steps)
  • File upload/download procedures
  • Procedures that return non-serializable data (streams, blobs)

Input schema rules

  • Use z.object({...}) for inputs — this maps cleanly to MCP tool parameters
  • z.void() (no .input()) works — the MCP handler patches these to accept empty objects
  • Avoid z.union() or z.discriminatedUnion() at the top level — AI clients struggle with these
  • Use .default() on optional fields so the AI doesn't need to guess values

Checklist for new features

  1. Add .meta({ mcp: { enabled: true, description: "..." } }) to relevant procedures
  2. Import and register the router in packages/api/src/mcp.ts (eager, no lazy)
  3. Write descriptions that explain what the tool does, what it needs, and what it returns
  4. If the tool requires an ID from another tool, say which one in the description
  5. Test with curl against /api/mcp/mcp to verify the tool appears in tools/list