packages/cli/skills/pikku-mcp/SKILL.md
Use when exposing Pikku functions as MCP tools, resources, or prompts for AI assistants. Covers mcp: true flag, pikkuMCPResourceFunc, pikkuMCPPromptFunc, and MCP wire object. TRIGGER when: code uses mcp: true or pikkuMCPResourceFunc/pikkuMCPPromptFunc, user asks about MCP, Model Context Protocol, AI tool integration, or exposing functions to Claude/ChatGPT. DO NOT TRIGGER when: user asks about AI agents (use pikku-ai-agent) or general function definitions (use pikku-concepts).
npx skillsauth add pikkujs/pikku pikku-mcpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill as an execution checklist, not reference material.
pikku-meta when available; otherwise run the relevant pikku meta ... --json command and inspect only the focused output you need..pikku, node_modules, vendored packages, or broad build artifacts.pikku-verify or pikku all when functions, wirings, schemas, or generated clients may have changed.Expose Pikku functions as Model Context Protocol (MCP) tools, resources, and prompts for AI assistants like Claude, ChatGPT, and others.
pikku info functions --verbose # See existing functions that could become MCP tools
pikku info tags --verbose # Understand project organization
See pikku-concepts for the core mental model.
Add mcp: true to any existing pikkuFunc to expose it as an MCP tool:
const myFunc = pikkuFunc({
description: string, // Used as MCP tool description
input: ZodSchema, // Becomes MCP tool input schema
output: ZodSchema, // Return type
mcp: true, // ← Expose as MCP tool
func: async (services, data) => { ... },
})
pikkuMCPResourceFunc)import { pikkuMCPResourceFunc } from '#pikku'
const resource = pikkuMCPResourceFunc({
uri: string, // URI template, e.g. 'todos/{id}'
title: string, // Human-readable title
description?: string,
func: async (services, data, { mcp }) => {
// Must return array of { uri, text } or { uri, blob, mimeType }
return [{ uri: mcp.uri!, text: JSON.stringify(result) }]
},
})
pikkuMCPPromptFunc)import { pikkuMCPPromptFunc } from '#pikku'
const prompt = pikkuMCPPromptFunc({
name: string,
description: string,
func: async (services, data) => {
// Must return array of MCP messages
return [
{
role: 'user',
content: { type: 'text', text: '...' },
},
]
},
})
Inside MCP-enabled functions, wire.mcp provides:
mcp.uri // Current resource URI (for resources)
mcp.sendResourceUpdated(uri) // Notify clients a resource changed
mcp.enableTools({ toolName: true }) // Dynamically enable/disable tools
The simplest path — add mcp: true to any function:
export const createTodo = pikkuFunc({
description: 'Create a new todo item',
input: CreateTodoInput,
output: CreateTodoOutput,
mcp: true,
func: async ({ db }, { text, priority }) => {
return await db.createTodo({ text, priority })
},
})
export const getTodo = pikkuMCPResourceFunc({
uri: 'todos/{id}',
title: 'Todo Details',
description: 'Get a todo by ID',
func: async ({ db }, { id }, { mcp }) => {
const todo = await db.getTodo(id)
return [{ uri: mcp.uri!, text: JSON.stringify(todo) }]
},
})
export const codeReview = pikkuMCPPromptFunc({
name: 'codeReview',
description: 'Generate a code review prompt',
func: async ({}, { filePath, context }) => {
return [
{
role: 'user',
content: {
type: 'text',
text: `Review ${filePath}. Context: ${context}`,
},
},
]
},
})
export const manageTodos = pikkuFunc({
description: 'Manage todo items',
input: ManageTodosInput,
output: ManageTodosOutput,
mcp: true,
func: async ({ db }, { action, id }, { mcp }) => {
if (action === 'delete') {
await db.deleteTodo(id)
mcp.sendResourceUpdated(`todos/${id}`)
await mcp.enableTools({ archiveTodos: true })
return { deleted: true }
}
},
})
// start.ts
import { PikkuMCPServer } from '@pikku/modelcontextprotocol'
const server = new PikkuMCPServer(config, singletonServices, createWireServices)
await server.init()
await server.start()
// functions/todos.functions.ts
export const listTodos = pikkuSessionlessFunc({
description: 'List all todo items',
input: ListTodosInput,
output: ListTodosOutput,
mcp: true,
func: async ({ db }, { status }) => {
return { todos: await db.listTodos(status) }
},
})
export const createTodo = pikkuFunc({
description: 'Create a new todo item',
input: CreateTodoInput,
output: CreateTodoOutput,
mcp: true,
func: async ({ db }, { text, priority }) => {
return await db.createTodo({ text, priority })
},
})
export const completeTodo = pikkuFunc({
description: 'Mark a todo as complete',
input: CompleteTodoInput,
output: CompleteTodoOutput,
mcp: true,
func: async ({ db }, { todoId }) => {
return await db.completeTodo(todoId)
},
})
// functions/todos.mcp.ts
export const getTodoResource = pikkuMCPResourceFunc({
uri: 'todos/{id}',
title: 'Todo Details',
description: 'Get details of a specific todo',
func: async ({ db }, { id }, { mcp }) => {
const todo = await db.getTodo(id)
return [{ uri: mcp.uri!, text: JSON.stringify(todo) }]
},
})
export const planDayPrompt = pikkuMCPPromptFunc({
name: 'planDay',
description: 'Create a daily plan based on pending todos',
func: async ({ db }, {}) => {
const { todos } = await db.listTodos('pending')
return [
{
role: 'user',
content: {
type: 'text',
text: `Plan my day. Here are my pending todos:\n${todos.map((t) => `- ${t.text} (${t.priority})`).join('\n')}`,
},
},
]
},
})
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.
development
Make a Pikku frontend work in both English (LTR) and Arabic / right-to-left languages. Direction is derived from the active locale, applied once at the document root, and the layout mirrors itself — but only if styling is written flow-relative (margin-inline-start, text-align: start, Mantine ms/me) instead of left/right. TRIGGER when: adding Arabic (or Hebrew/Farsi/Urdu), asked to "support RTL / right-to-left / bidi / mirror the layout", or writing layout styles in an app that may run RTL. Builds on pikku-i18n (an RTL language is just another locale file). DO NOT TRIGGER for backend functions or for LTR-only copy changes.
development
Wire i18n into a Pikku frontend (Vite SPA, Vite SSR, or Next.js app-router) with react-i18next + i18next. English by default, every user-facing string goes through a `t()` token, and additional languages are served under `/de` `/es` URL prefixes. TRIGGER when: scaffolding or editing a frontend and writing user-facing text, adding a second language, or asked to "make this translatable / use tokens / add i18n". DO NOT TRIGGER for backend functions, error messages thrown from functions, or log output.
development
Use when translating an n8n Code node body into a real Pikku function body. Triggered when the user opens or points at a stub generated by @pikku/n8n-import (look for `STUB — generated from n8n Code node` in the file's JSDoc), or when the user says 'translate this n8n code', 'port this n8n code node', 'finish the codeStub__... function', etc. The stub file is a `pikkuSessionlessFunc` with a Zod input/output, a JSDoc preserving the original n8n JavaScript verbatim, and a `throw new Error('… — implement me')` body.