Skills, MCP and plugins
Teach the house its rules in markdown, add tools over MCP, or bundle both in a plugin.
The house already knows how to call tools. What it does not know is that goodnight means the porch light stays on and the rest go off, or that the car should only charge after eleven. Those are house rules. They change whenever someone rearranges a room, so writing one should not mean writing a provider.
There are three ways to add them, in the order you are likely to want them. A skill is a markdown file. An MCP server is somebody else's tools. A plugin is one package that brings several of these at once.
Skills
A skill is a markdown file in ~/.config/parlour/skills:
---
name: bedtime
description: What goodnight means in this house
---
Turn off the kitchen, hall and lounge lights, leave the porch light on,
and set the thermostat to 17. Say "goodnight" and nothing else.parlour skills new bedtime # writes the file, with the frontmatter filled in
parlour skills list # the rules, and what each one is for
parlour skills show bedtime # the body, as the model reads it
parlour skills path # where they live
parlour skills write bedtime < f.md # replace it, checked before it is written
parlour skills remove bedtime # delete one of the house's own
parlour restart # the agent reads them at startupOnly the name and description go in the system prompt. The model fetches the
body with the read_skill tool when it decides the rule applies. A local
model with a small context cannot carry every house rule on every turn, and
most turns are "turn the kitchen light off". Write the description as the
trigger ("what goodnight means"), not as a title.
The frontmatter is optional. Without it, the filename is the name and the
first line is the description, so a one-paragraph rule can be just that
paragraph. A name is lower case letters, digits, - and _, and when two
skills share one, the first found wins. A skill with files of its own can be
a directory instead, with the rule in SKILL.md:
~/.config/parlour/skills/
bedtime.md
shopping/
SKILL.md
list.txtWrite the body the way you would brief a house sitter: which tools to call,
in what order, and how short the spoken reply should be. parlour doctor
counts the skills it found, names any file it could not use and why, and
warns when a body (over 4,000 characters) is long enough to slow every
answer that uses it.
To keep your rules somewhere else, such as a git repository, point
config.json at that directory with "skills": { "dir": "/house/rules" }.
"skills": { "enabled": false } turns skills off entirely, tool and all.
MCP servers
Anything that speaks MCP can give the house new tools. Every tool it advertises becomes one the house can call:
parlour mcp add weather --url https://weather.example/mcp
parlour mcp add notes --token-env NOTES_TOKEN -- npx -y @someone/notes-mcp
parlour mcp list
parlour mcp remove weatherThe first adds a server on the network, sending the --token-env variable,
if you give one, as a bearer token. The second adds one this machine starts
and talks to over stdin. Each tool arrives named after its server, such as
notes_search, which is the name to use in a skill. Either way, the entry
lands under integrations.mcp.servers in config.json, and you can write it
there by hand instead:
{
"integrations": {
"mcp": {
"servers": {
"notes": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@someone/notes-mcp"],
"tokenEnv": "NOTES_TOKEN"
}
}
}
}
}A stdio server gets PATH, HOME, LANG and TMPDIR, the one variable
tokenEnv names, and its own env block. It does not inherit the rest of
the environment. Every secret in secrets.env lives there, and a server that
wants the calendar has no business reading the Anthropic key. Store the token
with parlour secrets set NOTES_TOKEN and name it with tokenEnv, rather
than writing it into config.json.
If a server will not start, parlour doctor reports it and the rest of the
house keeps working. A dead weather server should never stop the lights going
off. A remote server that wants you to sign in with a browser is a connector,
not an MCP entry. parlour connectors add <name> <url> does the OAuth and
keeps the tokens in the Keychain, or in a file on a machine without one.
Plugins
You can add everything above one piece at a time, and for a single piece that is simpler. A plugin is for pieces that only make sense together: a package for a car, say, with a provider, two skills that know its tool names, and the MCP server both of them talk to.
npm install -g parlour-plugin-car # where parlour can resolve it
parlour plugins add parlour-plugin-car
parlour plugins list
parlour restart
parlour plugins remove parlour-plugin-car # takes it out of configparlour plugins add loads the package before it writes the name into
config. A missing package is caught then, not at the next start with the
microphone live. An absolute path works too, so you can try a plugin from a
checkout.
A plugin is an npm package whose default export is a Plugin:
import { definePlugin } from "parlour";
import { join } from "node:path";
import { carVoice, carTools } from "./providers.js";
export default definePlugin({
name: "car",
description: "The car: charging, climate and where it is",
/** Registered before any slot is filled, so config can name them. */
providers: [carVoice, carTools],
/** A directory of markdown skill files, shipped with the package. */
skillsDir: join(import.meta.dirname, "skills"),
/** Or skills written in code, the same shape as a file. */
skills: [
{ name: "charging", description: "When to charge", body: "Only after 23:00.", source: "car" },
],
/** Config for the integrations it needs, merged under the house's own. */
integrations: {
"car-tools": {},
mcp: { servers: { car: { transport: "stdio", command: "car-mcp" } } },
},
/** Anything it wants to do once, before the agent is built. */
async setup(context) {
context.log.info(`using ${context.paths.home}`);
},
});Plugins load first, before any provider is resolved. So by the time a slot
in config.json names car-voice, it is already registered. The
integrations block is merged underneath your own config, key by key. A
plugin can bring an MCP server, and you can still change or remove it in
config.json. For the same reason, a plugin skill loses to one of the same
name in your own directory.
A plugin that cannot be loaded is fatal, not skipped. A house missing the
plugin that holds its skills would look just like one whose model has
stopped listening. The one exception is a provider
whose name is already registered: the registered one wins, and
parlour doctor says so.
parlour plugins list shows what each plugin brings without setting any of
them up, so checking what is installed never opens a connection.