Enabling the MCP Server¶
The ai-rulez MCP (Model Context Protocol) server allows your AI assistant to programmatically and safely interact with your .ai-rulez/ configuration. Since V4 uses a file-based approach with inline MCP configuration, you define servers directly in config.toml, and the MCP server provides read, CRUD, validation, and generation tools for AI assistants.
You do not need to start the server manually. Your AI assistant will start it automatically based on the configuration you provide.
With --serve-skills the same server also serves your skills on demand, so they stop costing tokens in every session:
Configuration Examples¶
To enable the server, add one of the following snippets to your AI assistant's configuration file (e.g., Cursor's settings.json), or define it inline in .ai-rulez/config.toml. The top-level key (mcpServers below) differs between clients; check your assistant's documentation.
Using npx (Recommended for Node.js users)¶
This method ensures you are always using the latest version of ai-rulez without needing to install it globally.
Using uvx (Recommended for Python users)¶
This method uses uvx to run ai-rulez in an ephemeral environment.
Using a Local Go Installation¶
If you have installed ai-rulez locally with go install.
Inline in config.toml (V4 Recommended)¶
V4 supports defining MCP servers directly in your .ai-rulez/config.toml:
[[mcp_servers]]
name = "ai-rulez"
command = "npx"
args = ["-y", "ai-rulez@latest", "mcp"]
[[mcp_servers]]
name = "grafana"
command = "uvx"
args = ["mcp-grafana"]
env = { GRAFANA_URL = "http://localhost:3000", GRAFANA_SERVICE_ACCOUNT_TOKEN = "${GRAFANA_SERVICE_ACCOUNT_TOKEN}" }
[[mcp_servers]]
name = "remote-api"
transport = "http"
url = "https://mcp.example.com/mcp"
headers = { Authorization = "Bearer ${REMOTE_API_TOKEN}" }
[[mcp_servers]]
name = "slack"
type = "http" # the same setting as transport, spelled as the harness's `type`
url = "https://mcp.slack.com/mcp"
[mcp_servers.oauth] # a pre-registered OAuth client (hosted servers)
client_id = "1234567890.1234567890"
callback_port = 8080
scopes = ["channels:read", "chat:write"]
oauth is translated to each harness that has an oauth block: Claude Code's .mcp.json and VS Code's
.vscode/mcp.json get { "clientId", "callbackPort", "scopes" }, and Codex's .codex/config.toml gets an
[mcp_servers.<name>.oauth] table with client_id and callback_port. A harness that cannot express it
skips the server with a warning instead of writing one that could not authenticate. scan reports an oauth
block with no client_id and two same-named servers that resolve to different endpoints or transports
(AR602).
MCP env and header values may contain ${VAR} placeholders. ai-rulez generate resolves them from repeated
--env KEY=VALUE flags, process environment variables, then dotenv files. Placeholder names must
match [A-Za-z_][A-Za-z0-9_]*. By default, .env is loaded from the generation base directory. If
any --env-file PATH flags are supplied, the default .env is not loaded; multiple files are merged
in flag order, with later files winning. Generation fails if a placeholder cannot be resolved.
Generated Self-Entry ([mcp] self_server)¶
generate adds the ai-rulez server to the project .mcp.json by default, pinned to the version
of the ai-rulez binary that ran it, so a project is MCP-ready out of the box. Turn it off with
self_server = false or generate --no-self-mcp:
The entry is merged into an existing .mcp.json, so hand-authored servers survive and the file is
ready for Claude Code (which reads project MCP servers from .mcp.json). Every entry has the same
shape: "type": "stdio" (or http/sse) and, only for a server turned off with enabled = false,
"disabled": true. See Configuration: mcp for
self_server_version, self_server_command, skills_instructions, and the interaction with [[mcp_servers]].
Generated MCP config files contain resolved values. If a value came from a placeholder, or if an env
key contains TOKEN, SECRET, PASSWORD, KEY, or CREDENTIAL, generation fails before writing
unless .mcp.json, .gemini/settings.json, .agents/settings.json,
opencode.json, or a scoped variant is gitignored or covered by planned --gitignore patterns.
Secret-bearing values are redacted before source-hash calculation, not from generated MCP config
files. Any generated file that contains a resolved secret value is written with mode 0600.
Servers can also be defined per machine in the config.local.* overlay, which
keeps credentials out of the shared config.
Set enabled = false on an inline [[mcp_servers]] entry to skip it in generated MCP outputs.
Server Capabilities¶
When enabled, the MCP server provides your AI assistant with access to your configuration and CRUD operations. The server supports:
- Read Configuration: Inspect rules, context, skills, profiles, and presets
- Generate Outputs: Programmatically trigger generation of tool-specific files. There is no terminal over MCP, so
the hook, MCP, env, plugin and allow-rule commands the run newly wrote are returned as
new_commands(and written to stderr); an unchanged run omits the field. See Configuration - Validate Configuration: Check configuration validity and report errors
- CRUD Operations: Create, read, update, and delete domains, rules, context, skills, agents, commands, includes, and profiles
- Reports: Read-only twins of
scan,tokens,cost,sbom,okf validate,approve --listandvalidate --show-policy, which return the document the command prints with--format json - Prompts and resources:
author-skill,add-rule,review-configandtrim-contextprompts, andai-rulez://config,ai-rulez://catalogandai-rulez://{kind}/{name}resources
The MCP server enables AI assistants to:
- Understand your setup by reading configuration and content files
- Modify configuration programmatically via CRUD tools
- Generate outputs after changes
- Validate changes before committing
This approach ensures your configuration remains auditable and version-controlled, while allowing AI assistants to help you manage it efficiently.
CLI and MCP parity¶
The command line and the MCP tools are two views of one engine, and a table in the source decides which
capability lives where. Every runnable command and every tool belongs to exactly one row of
internal/parity/capabilities.go, either paired with its counterpart or left on one side with a stated reason.
go test ./tests/parity walks the real Cobra tree and lists the real tools of both servers through an in-memory
client, and fails when:
- a command or a tool is in no row, or a row names a command or tool that does not exist;
- a one-sided row has no reason;
- a paired command and tool differ in an input: each flag must have a tool argument of the same JSON type, enum and required-ness, or sit in an explicit, justified exclusion (and the other way round);
- a read-only pair, run on one fixture project, gives different documents.
validate,scan,tokens,cost,sbom,okf validate,lock --check,approve --list,validate --show-policyandsearchare compared field for field, with each justified difference written next to the check.
The tables below are generated from the same rows (go test ./tests/parity -run TestDocsMatchTheTable -update-docs
rewrites them), and the test fails when they are stale.
On both sides. The command and the tool run the same library code. Rows marked compared are also run on one fixture by the parity test, which checks the command's --format json document and the tool's structuredContent field for field.
| Command | MCP tool | Server | Compared |
|---|---|---|---|
add agent |
create_agent |
authoring | |
list agents |
list_agents |
authoring | |
show agent |
read_agent |
authoring | |
remove agent |
delete_agent |
authoring | |
approve --list |
approvals_status |
authoring | yes |
builtins list |
list_builtins |
authoring | |
builtins show |
show_builtin |
authoring | |
catalog |
catalog |
authoring | |
add check |
create_check |
authoring | |
list checks |
list_checks |
authoring | |
show check |
read_check |
authoring | |
remove check |
delete_check |
authoring | |
clean |
clean_outputs |
authoring | |
add command |
create_command |
authoring | |
list commands |
list_commands |
authoring | |
show command |
read_command |
authoring | |
remove command |
delete_command |
authoring | |
add context |
create_context |
authoring | |
list context |
list_context |
authoring | |
show context |
read_context |
authoring | |
remove context |
delete_context |
authoring | |
cost |
cost_report |
authoring | yes |
doctor |
doctor |
authoring | |
domain add |
create_domain |
authoring | |
domain list |
list_domains |
authoring | |
domain remove |
delete_domain |
authoring | |
generate |
generate_outputs |
authoring | |
include add |
add_include |
authoring | |
include list |
list_includes |
authoring | |
include remove |
remove_include |
authoring | |
init |
init_project |
authoring | |
skill install |
install_skill |
authoring | |
skill list |
list_installed_skills |
authoring | |
skill remove |
uninstall_skill |
authoring | |
judge |
judge |
authoring | |
lock --check |
lock_status |
authoring | yes |
okf validate |
okf_validate |
authoring | yes |
validate --show-policy |
policy_show |
authoring | yes |
profile add |
add_profile |
authoring | |
profile list |
list_profiles |
authoring | |
profile remove |
remove_profile |
authoring | |
profile set-default |
set_default_profile |
authoring | |
roles list |
list_roles |
authoring | |
roles resolve |
resolve_role |
authoring | |
add rule |
create_rule |
authoring | |
list rules |
list_rules |
authoring | |
show rule |
read_rule |
authoring | |
remove rule |
delete_rule |
authoring | |
sbom |
sbom |
authoring | yes |
scan |
scan_content |
authoring | yes |
search |
find_skill |
skills | yes |
add skill |
create_skill |
authoring | |
list skills |
list_skills |
authoring | |
show skill |
read_skill |
authoring | |
remove skill |
delete_skill |
authoring | |
tokens |
token_report |
authoring | yes |
validate |
validate_config |
authoring | yes |
verifiers list |
list_verifiers |
authoring | |
verifiers run |
run_verifiers |
authoring | |
version |
get_version |
authoring |
Only on the command line, deliberately. These write trust, spend money, use the network or credentials, prompt, or run for the length of a process.
| Command | Why it is not a tool |
|---|---|
edit agent |
partial field edits and stdin input; update_agent is the whole-item replacement for a tool client |
approve |
approving, revoking, denying and signing are identity-bound human attestations; an agent must not make them |
cache * |
inspects and deletes the index cache the MCP server keeps in the user cache directory; a tool client has no use for the host's disk layout, and deleting is a human decision |
catalog diff |
compares two catalogs through git subprocesses |
edit check |
partial field edits and stdin input; update_check is the whole-item replacement for a tool client |
edit command |
partial field edits and stdin input; update_command is the whole-item replacement for a tool client |
config layers |
lists the config. |
edit context |
partial field edits and stdin input; update_context is the whole-item replacement for a tool client |
convert, migrate * |
one-shot conversions that read untrusted tool files and rewrite the tree; gated by --write or a preview |
eval *, improve * |
run models and harness subprocesses and spend money |
guard |
a hook entry point the harness calls on tool use |
list |
prints where skills and commands land per harness; generate_outputs dry_run returns the same plan |
llm * |
model credentials and cost estimates for network calls |
local * |
the machine-local overlay is per developer; tools reach it through the local argument of the item tools and update_config |
lock |
writes the lock, the trust anchor of the project, after network pinning and scan acceptance; an agent must not mint trust |
marketplace * |
adds, removes and updates installed marketplaces: network fetches that rewrite the configuration and the lock |
mcp |
starts the MCP server itself |
export okf, import okf |
write a whole bundle or import a foreign one into the tree |
plugin * |
installs, removes and updates plugins imported from marketplaces: network fetches, hook and MCP imports that run code |
publish * |
builds and signs distribution artifacts and may push them |
review *, rubric * |
spends model tokens in its default mode; the offline heuristics are not exposed yet |
roles show |
a presentation of one role; resolve_role returns what the role keeps |
edit rule |
partial field edits and stdin input; update_rule is the whole-item replacement for a tool client |
scanners * |
run external scanner programs, possibly with egress |
session-start |
a hook entry point the harness calls when a session starts; it runs generate |
sign, trust update |
signs with a key, an OIDC identity or a transparency log: credentials and egress |
edit skill |
partial field edits and stdin input; update_skill is the whole-item replacement for a tool client |
telemetry * |
consent and network egress of usage data |
skill update, update |
moves pinned remote sources to newer tags over the network and rewrites the lock |
verifiers calibrate, verifiers explain, verifiers suggest, verifiers test |
calibrate, suggest and test run models or programs; explain prints the help text of a verifier |
verify |
verifies signatures, attestations and downloaded artifacts with the signing backend; the drift half is generate_outputs check and doctor |
Only on MCP, deliberately.
| MCP tool | Server | Why it has no command |
|---|---|---|
update_agent |
authoring | edit changes single fields (description, priority, targets) as well as the body, and reads the body from stdin; the tool replaces the whole item in one atomic call |
update_check |
authoring | edit changes single fields (description, priority, targets) as well as the body, and reads the body from stdin; the tool replaces the whole item in one atomic call |
update_command |
authoring | edit changes single fields (description, priority, targets) as well as the body, and reads the body from stdin; the tool replaces the whole item in one atomic call |
read_config |
authoring | the command line reads config.toml with an editor or cat; a tool client has no filesystem, so it reads the parsed settings here |
update_config |
authoring | the command line edits config.toml directly or through local set; a tool client needs a typed edit that keeps comments and ordering |
update_context |
authoring | edit changes single fields (description, priority, targets) as well as the body, and reads the body from stdin; the tool replaces the whole item in one atomic call |
get_skill |
skills | returns a served skill with provenance and digests to a client that only speaks tools |
list_skill_resources |
skills | lists the files of a served skill for a client that has no skill:// resource support |
load_skill |
skills | reads one served skill file under the session budget; the CLI reads the file from disk |
read_skill_file |
skills | reads a supporting file of a served skill by its skill:// URI |
update_rule |
authoring | edit changes single fields (description, priority, targets) as well as the body, and reads the body from stdin; the tool replaces the whole item in one atomic call |
search_skills |
skills | lexical listing for a client that only speaks tools; the ranker the CLI search shares is find_skill |
update_skill |
authoring | edit changes single fields (description, priority, targets) as well as the body, and reads the body from stdin; the tool replaces the whole item in one atomic call |
Added a command or a tool? The first parity test failure names the row to add.
Serving Skills (--serve-skills)¶
ai-rulez mcp --serve-skills serves the skills of a profile to consumers as well as the authoring
tools, all from one process under the name ai-rulez. It implements the MCP
Skills extension
(io.modelcontextprotocol/skills, SEP-2640, status Final, verified 2026-10-04), so a client that
supports MCP but not a local skills directory can discover and load the skills of a profile with no
files on disk. The authoring tools (create, update, delete, generate, ...) are registered too, so a
project that serves skills needs one MCP entry rather than the self entry plus a separate skills
server; pass --read-only to register only the read-only config and report tools. It never writes
.claude/settings.json, so --role does not touch the role's skillOverrides
(generate --role does, and restores the hand-written values it replaced).
ai-rulez mcp --serve-skills --profile backend
ai-rulez mcp --serve-skills --profile backend --targets claude --domain api --deny 'internal-*'
ai-rulez mcp --serve-skills --read-only # serve skills and inspect, never write
| Flag | Meaning |
|---|---|
--profile |
Profile whose skills are served. Default: the configured default profile. A profile is the role: it selects the domains whose skills apply. |
--targets |
Preset whose rendering is served (its frontmatter dialect and placement rules). Default: the first configured preset that produces skills. |
--domain |
Keep only skills of these domains (repeatable); root selects skills owned by no domain. A name that is not a domain of the project is an error at start. |
--allow / --deny |
Glob patterns on the skill name. --allow keeps only matching skills; --deny always wins. |
--read-only |
Register only the read-only authoring tools (validate, scan, doctor, the read/list and report tools). The server can inspect a project but never change it. The judge tool is not registered either: it sends file content to a model provider and spends money. |
--tools |
Register only these tools (repeatable or comma separated), for example --tools find_skill,load_skill,find_context,load_context for a server that can only read skills and context. Applies to mcp and to mcp --serve-skills. An unknown name is an error that lists the valid tools; a valid name the server would not register anyway (a writing tool under --read-only, a context tool without discovery) is simply absent. |
--root / --allow-any-dir |
Confine the authoring tools to a directory, as for plain mcp. |
The skill set is rendered once at start-up, in memory, by the same code as generate, so every
SKILL.md and supporting file is byte-identical to what generate writes for the same profile and
preset. Nothing is written. Edits are picked up by live reload.
The rendered and admitted catalog is also cached on disk, so a restart restores it instead of
rendering and scanning every skill again (Start-up cost and the catalog cache).
When the filters remove every skill, the start-up warning names each filter and how many skills it leaves.
What is exposed¶
- Resources: every file of a skill as
skill://<name>/<path>(skill://pdf-processing/SKILL.md,skill://pdf-processing/references/FORMS.md), readable withresources/read._metacarriesio.modelcontextprotocol.skills/digest. skills/listandskills/get(JSON-RPC methods defined by the extension): the entry of a skill is{uri, frontmatter, resources: [{uri, digest, size}]}withsha256:<hex>digests, so a client can verify what it loads. An unknown URI returns-32602. The server declarescapabilities.extensions["io.modelcontextprotocol/skills"]and theresourcescapability. The server also implements the extension's optionalresources/directory/readand declaresdirectoryRead: true: givenskill://<name>or one of its subdirectories (no trailing slash), it returns the direct children as resources (files with theirmimeTypeand size, subdirectories asinode/directory), and-32602for anything that is not such a directory. It is read-only and lists only filesresources/readserves.- Tools for clients that only speak tools, all annotated read-only:
search_skills(query, limit, domain)ranks by name, keywords (frontmatterkeywords), description and domain, lexically and deterministically (an empty query lists everything);get_skill(name)returnsSKILL.mdplus provenance and per-file digests;read_skill_file(uri, encoding, offset, length)returns a supporting file (see Served files).offsetandlength(and theoffsetoflist_skill_resources) must be whole numbers from 0 to 2147483647; a fractional, negative, non-finite or larger value is rejected with an error naming the argument. Thelimitoffind_skillandsearch_skillsand thebudget_bytesofload_skilltake zero or a negative number as "use the default"; a fractional, non-finite or larger-than-2147483647 value is rejected the same way. - The authoring surface: the same tools, prompts and
ai-rulez://resources as plainai-rulez mcp(create, update, delete, validate, generate, reports), so the one server both serves skills and edits the project. With--read-onlyonly the read-only tools are registered.
Provenance¶
Search and get_skill results carry digest (a sha256 over the digests of all the skill's files, so
it changes when any file does), source (the authored path, or the repository of an installed
skill), and, for installed skills, ref and pinned (true when ref is a full commit SHA). Log the
digest to record exactly what a session loaded. The digest covers the skill's whole file tree, including files
the server does not serve (unscannable files at trust = "error"), exactly like lock_digest; the served files
are listed with their own digests.
Notes and limits¶
- The Go SDK dispatches only the methods it knows, so
skills/listandskills/getare answered in a thin JSON-RPC layer in front of the SDK; everything else, including resources and tools, is served by the SDK normally. The stdio transport is the only oneai-rulez mcpoffers: the harness launches the server as a subprocess it already controls, so there is no port, TLS certificate or cross-origin policy to operate. A streamable-HTTP transport (and elicitation, sampling and roots) is deliberately not offered; a hosted deployment would need its own auth and CORS policy, and those capabilities wait on the 2026-07-28 spec revision. - A malformed line never ends the session. Input that is not valid JSON gets
-32700; a frame that is not a JSON-RPC 2.0 request (wrong or missingjsonrpc, an object or booleanid, an empty or invalid batch) gets-32600; a line over 16 MiB is dropped with-32600. The server keeps reading after each. Aresources/readof an unknown URI gets the not-found error with the URI escaped. skills/listreturns the whole catalog in one page (nonextCursor).- A skill URI uses the skill name as its path, so two served skills must not share a name; the server refuses to start when they do.
- The extension requires every skill to have a
nameand adescription. A skill without a description is served under its name as the description, and a warning on stderr names it (add adescriptionsofind_skillcan rank it). The served bytes are not rewritten. A skill whose frontmatter cannot be parsed, or whose name is not a validskill://path segment, is skipped with a warning; the rest are unaffected. A skill whose frontmatter is not valid YAML (validatefails on it) is refused:load_skillandget_skillanswermalformed-frontmatterwith the reason, andgeneratefails the same way. - Semantic ranking is opt-in: with
[search] mode = "hybrid"and an index built byai-rulez search index,find_skillfuses BM25F with embedding similarity (see Skill search). By default the ranking is lexical.
Dynamic skill loading¶
Static generation lists every skill (its name, description and about 27 tokens of framing) in every session: roughly 14.5k tokens for 190 skills. Dynamic skill loading serves the skills you choose over MCP instead, so the agent pays for them only when it asks. Three pieces work together:
deliverydecides per skill whether it is written to the harness skill trees (static), served over MCP (served), or both.generateleaves served skills out of every static tree and writes one small stub skill (dynamic-skills, about 100 tokens) that tells the agent to callfind_skillandload_skill.ai-rulez mcp --serve-skillsserves those skills withfind_skill,load_skill,list_skill_resources(and the tool-onlysearch_skills,get_skillandread_skill_file), scans each one before serving, can pin them inai-rulez.lock, and reloads when their files change.
Decision guide¶
| Keep the skill | When |
|---|---|
static |
A core convention the agent needs in nearly every session (code style, commit rules, the repository map). The listing costs a few tokens and it never depends on the model deciding to search. |
served |
Domain skills used in some sessions: billing, migrations, a vendor API, incident response. The bulk of a large catalog. |
both |
A skill that ships scripts/ or assets/ the agent runs by relative path, or that must always be visible to one harness while other harnesses or roles find it by search. |
Rules of thumb: serve domain skills, keep a handful of core convention skills static. Caveats:
- The model has to choose to call
find_skill. A served skill is invisible until it does. Writedescriptionandtriggersfor search (what the user is trying to do, in their words), and keep the skills that must not be missedstatic. - An MCP-only harness is not enough for every harness. A harness that cannot call MCP tools gets served
skills as static files, with a warning (
AR992), never silently dropped. Served skills also need an MCP server entry that runsai-rulez mcp --serve-skills; the generated self entry does that whenever the project serves skills, andAR993warns only when nothing does (self_server = falseand no such entry). - Served skills are not in the context until loaded. Nothing that is always loaded (a rule, a static skill)
can rely on them being there;
AR990warns when static content names a served skill, and says the file will not be there when the pointer is a path. - Bundled files need
delivery: both. Aservedskill'sscripts/andassets/are never written to disk, so a relative path into them (scripts/foo.py, the form SKILL.md usually uses) resolves to nothing;AR9R0warns. Setdelivery: bothwhen SKILL.md tells the agent to run a bundled script, or fetch the file over MCP and write it out first (Served files).references/are readable over MCP either way. disable-model-invocationplusbothis a trap.bothkeeps the skill's file on disk, butdisable-model-invocation: truehides the skill from the Skill tool, which refuses it and tells the model not to reproduce the workflow another way. A rule that points at it by name ("run thexskill",Skill(x)) then leaves the model declining even though it could read the file;AR9R1warns. Point at the file path (.claude/skills/<id>/SKILL.md) or aload_skillcall, or dropdisable-model-invocation.
Choosing the delivery¶
---
name: db-migrations
description: Plan and run database schema changes safely. Use when changing a table.
delivery: served
triggers: [schema change, alembic migration, add a column]
---
[skills]
delivery = "static" # global default; static when unset
[domains.billing]
delivery = "served" # every skill of the billing domain
Precedence, first match wins: the skill's own delivery frontmatter, then the role's delivery map (when a role
is being rendered or served, see Roles), then [domains.<name>] delivery, then
[skills] delivery, then static. An invalid value is ignored (the next level applies) and reported as AR994;
invalid config values fail validate. config.Config.EffectiveDelivery(skill, domain, roleOverride) is the
single function that resolves all of this.
triggers is a list of phrases that should make the agent look for the skill (a comma-separated string works
too). keywords are searched as well. delivery and triggers are ai-rulez keys: delivery is not written
into generated frontmatter.
What generate does¶
- A skill whose delivery is
servedis not written to any preset's skill tree.staticandbothskills are. - When any skill is
servedorboth, or a[[skill_sources]]entry is configured (its skills are served, never written), one stub skill nameddynamic-skillsis added to the root skills of every preset whose harness can call MCP tools. Withagents_mdon, the presets that read the shared.agents/skillstree (codex, cursor, gemini, copilot, opencode, zed, ...) get the stub in that tree. A skill you author with that name, at the root or in a domain that is active, is used instead of the stub; if several are authored, a warning asks you to rename all but one.[delivery.stub]customises the generated stub's description and text, see Configuration:[delivery.stub]. - A preset whose harness cannot call MCP (for example
cline,rovodev, or any custom preset) keeps every served skill as a static file and gets no stub. Each such preset is named in a warning (AR992). Skills of[[skill_sources]]are served only, by design: they never reach a preset without MCP support (nothing is written for them), andgenerateandvalidate --strictwarn (AR992) for each such preset when[[skill_sources]]is configured. See Installed skills vs skill sources to choose between them. ai-rulez mcp --serve-skillsrenders the served skills itself; it never depends on the generated trees.
Serving¶
ai-rulez mcp --serve-skills # the skills whose delivery is served or both
ai-rulez mcp --serve-skills --role backend # the backend role's skills, with the role's delivery
ai-rulez mcp --serve-skills --source git+https://github.com/acme/skills@v1.2.0#skills/
ai-rulez mcp --serve-skills --frozen # lock required, no network
Register it in config.toml so every MCP-capable harness launches it. A project that serves skills
needs no entry of its own: the generated self entry runs mcp --serve-skills (see
Generated Self-Entry), so .mcp.json holds one ai-rulez entry. The
generated dynamic-skills stub names the self entry ai-rulez, or the [[mcp_servers]] entry whose
args include --serve-skills when you declare one:
[mcp]
self_server = true # default; the ai-rulez entry runs `mcp --serve-skills` when skills are served
# Optional: only when self_server is off, or to serve a second view (another
# profile, role or source) as its own server.
[[mcp_servers]]
name = "ai-rulez-skills"
command = "ai-rulez"
args = ["mcp", "--serve-skills"]
A project that sets no delivery anywhere serves every skill, as --serve-skills always did. Once any skill is
served or both, only those are served; --include-static adds the rest.
| Flag | Meaning |
|---|---|
--source |
Serve the skills of a source as well (repeatable, see Skill sources). |
--role |
A role of [[roles]]. The server serves only the skills the role keeps, with the delivery the role gives them (a skill the role delivers static is not served unless --include-static is set), and find_skill ranks within the role by default. Mutually exclusive with --profile; an unknown role is an error at start. |
--frozen |
Never use the network and require ai-rulez.lock to cover every remote include, installed skill and skill source. |
--offline |
Never use the network; use the lock and the cache when present. |
--include-static |
Also serve skills whose delivery is static. |
--discover-skills[=mode] |
Also serve the skills already in the repository: repo (the default when the flag has no value) or all; off turns a configured discovery off. Write the value with =. Either mode also indexes nested instruction files, rules and agents by scope. See Discovering skills already in the repository and Monorepos. |
--exclude <glob> |
Leave the paths a glob matches out of repository discovery: skill directories and the context index alike. Repeatable; added to [mcp] exclude. Needs discovery (--discover-skills or [mcp] discover_skills). See Excluding paths. |
--max-clone-bytes |
Clone size limit in bytes for git skill sources that set no max_clone_bytes. Overrides AI_RULEZ_MAX_CLONE_BYTES; 0 (default) defers to it, then to 256 MiB. |
--budget-bytes |
Bytes of skill content a session may read, through load_skill, get_skill, read_skill_file and resources/read together. Default 262144 (256 KiB); any negative value removes the cap. |
--usage-log, --usage-sink |
Where to record each load_skill (see Usage telemetry). |
--no-watch, --reload-interval |
Turn live reload off, or change the two-second check interval. |
--daemon |
Share one long-lived skills server per repository: proxy this session's stdin/stdout to a per-repo daemon, starting it on first use, and serve in-process when none can be reached (see Shared daemon). |
The serve-mode flags above (and --profile, --targets, --domain, --allow, --deny) are rejected without
--serve-skills. The authoring tools are registered too, so one process serves both; --read-only registers
only the read-only tools, so a project can be inspected but never changed.
Settings in the configuration¶
A project can pin how mcp runs in its [mcp] table, so every launcher (.mcp.json, a plugin, a teammate's
editor) does not repeat the flags. The keys are accepted in .ai-rulez/config.toml, a root ai-rulez.toml, the
config.<name>.toml layers and config.local.toml; a list in a later layer replaces the earlier one.
[mcp]
serve_skills = true # --serve-skills (a bare `ai-rulez mcp` serves skills)
read_only = true # --read-only
tools = ["find_skill", "load_skill", "find_context", "load_context"] # --tools
allow = ["review-*"] # --allow (--serve-skills)
deny = ["draft-*"] # --deny (--serve-skills)
daemon = true # --daemon (--serve-skills)
no_watch = true # --no-watch (--serve-skills)
daemon_idle_timeout = "15m" # how long the daemon outlives its last client
authoring = true # keep the authoring tools on a read-only skills server
- A small default surface.
serve_skillswithread_onlyregisters onlyfind_skill,load_skill,search_skills,read_skill_fileandlist_skill_resources, and no prompts: the full tool list is about 92 KB of schema that a client sends on every request. Setauthoring = truefor the read-only authoring tools and thereview-configprompt, ortools = [...]for an exact list. - Large repositories. The live-reload watcher holds at most 2,048 descriptors (kqueue on macOS and the BSDs
charges one per directory and per file in it), watching shallow directories first and warning once when the
budget is reached; the poll covers the rest.
no_watch = trueturns the watcher off. -
The daemon. It outlives its client for
daemon_idle_timeout(15 minutes by default) and exits at once when its repository is removed; it does not keep the checkout as its working directory. Its stderr goes todaemon.logbeside the socket. When the cache path is too long for a unix socket, the socket goes under$XDG_RUNTIME_DIRor the temp directory inai-rulez-<uid>/, used only when this user owns that directory and the one inside it with mode 0700. -
Skills keys without serving.
allow,deny,daemon,daemon_idle_timeout,authoring,no_watch,excludeanddiscover_skillsonly apply when skills are served (--serve-skillsorserve_skills = true). A server started without serving them logs one warning on stderr naming the keys that are set and ignored; the same settings as flags stop the server withrequires --serve-skills, because a key may be shared by launches that do not serve skills. - A flag wins. A flag given on the command line replaces the key, including
--read-only=falseand an empty--tools=. A flag that is not given leaves the key in charge. - Read at start. The settings are read when the server starts; change them and restart the server. They are read from the project the server was started in (or the project above it), offline. A project that cannot be read is reported on stderr and the command line decides; a tool name that does not exist stops the server with the list of valid ones.
- Policy only tightens. An organization policy with
[mcp] read_only = truemakes the server read-only whatever[mcp] read_onlyand--read-only=falsesay; a repository that setsread_only = falseunder it is reported asAR740and the server stays read-only. - The surface follows the tools. The server instructions are written from the tools that are registered, and
each authoring prompt (
add-rule,author-skill,review-config,trim-context) is listed only when every tool it calls is. A read-only server therefore does not tell the model to use the CRUD tools orgenerate_outputs, and offersreview-configalone.
Discovering skills already in the repository¶
A repository often keeps skills that ai-rulez does not define: a skills/ folder, .claude/skills in a package,
a folder a teammate set up for another harness. --discover-skills (or [mcp] discover_skills) serves them through
the same skill:// resources, find_skill and load_skill, under the same security scan, session budget and
--allow/--deny rules, with no generate run and no copy.
ai-rulez mcp --serve-skills --discover-skills # repo
ai-rulez mcp --serve-skills --discover-skills=all
| Mode | Serves |
|---|---|
off |
Today's behaviour: the skills ai-rulez defines and the skill sources. |
repo |
The above, plus every skill directory found in the working tree. |
all |
repo, plus the installed and included skills even when their delivery would keep them out of the served set. |
The flag wins over the configuration; a bare --discover-skills means repo. A project with no configuration at all
(or a lint-only configuration) can use it; without one it behaves
as if the claude and codex presets were configured, so .claude/skills and .agents/skills are searched.
Where repo looks. A directory with a SKILL.md below one of these roots, at any depth of the project and at
most six levels below the root:
skills/(not inside a hidden directory such as.agents/, which belongs to its own convention);.claude/skills;- the skill directory of each configured preset (
.agents/skills,.cursor/skills,.github/skills, ...), the same listconvertreads.
A SKILL.md inside another skill belongs to that skill and is not a skill of its own. The configuration directory
(.ai-rulez/), .git/ and node_modules/ are never searched, symlinks are not followed, and a skill is read with
the limits of a skill source (2 MiB a file, 8 MiB and 2000 files a skill, 500 skills in all).
Ignored and generated files. In a git repository the search covers the files git ls-files --cached --others
--exclude-standard lists, so everything .gitignore excludes is skipped. Without git it walks the tree and
only skips .git and node_modules. A skill directory ai-rulez itself wrote is left out, so a skill is not served
twice: one holding a file recorded in the generated manifest, or whose SKILL.md still carries the generated
Content-Hash header. While the manifest records a copy it stays out even when edited by hand; without the manifest
a copy whose body was edited fails the header check and is a repository skill.
Names. A skill is served under its frontmatter name (the directory name when it has none), and the first
claimant keeps a name in this order:
- The skills ai-rulez defines (project, installed and included), then the skill sources: a repository skill with
their name is shadowed, not served. A builtin skill is the exception: it ranks below repository skills, so a
repository skill with its name (for example
.claude/skills/testing-conventionsbesidebuiltins = ["testing"]) replaces it, as a skill under.ai-rulez/skills/does. The start-up log names the builtin that was replaced. - Repository skills, shallowest directory first, then by path. A later one with a name already taken is served
under its path from the project root,
/turned into.and the leading dot of each segment dropped:pkg/.claude/skills/barbecomespkg.claude.skills.bar(its frontmatternameis rewritten to match). If that name is taken too, it is shadowed. Each rename is a start-up warning naming both directories and the served name, and is listed underskill_discovery.renamed(name,served_as,path,kept_by).
Every shadowed skill is a start-up warning and is listed, with its path and what shadows it, under
skill_discovery.shadowed in the ai-rulez://catalog resource, next to the served ones (skill_discovery.served)
and the count of generated directories left out (generated_skipped).
Limits. A discovered skill takes the default trust of a skill authored in the project (the scan blocks error
findings, warns on the rest). With [lock] enforce, it must be pinned like any served skill: set
discover_skills in the configuration, not only the flag, so ai-rulez lock sees it. The served catalog is not
cached while discovery is on, and live reload watches the skill roots found at start (and skills/, .claude/skills
and the preset directories at the project root when they exist): a new skill folder in an existing root is picked
up, a new .claude/skills elsewhere needs a restart.
Without a configuration. --discover-skills works in a directory with no .ai-rulez/ or ai-rulez.toml. The
server then has no configuration to manage, so it registers only the tools that work without one (find_skill,
load_skill, list_skill_resources, search_skills, get_skill, read_skill_file, find_context,
load_context, get_version, list_builtins, show_builtin, policy_show, okf_validate and init_project)
and no authoring prompt. ai-rulez://config and the ai-rulez://{kind}/{name} item resources are not listed;
ai-rulez://catalog is, and is built from what was discovered: the served skills as items, the scopes with the
instruction files, rules and agents found, and the skill_discovery report.
Monorepos: scopes and nested context¶
A harness loads a nested AGENTS.md, CLAUDE.md, .claude/ or .agents/ tree only once it works inside that
directory. A planner at the repository root never sees it. With discovery on (--discover-skills or
[mcp] discover_skills = "repo" | "all"), the server indexes the repository by scope and answers "what applies to
this path?" the way a harness would. With discovery off none of this exists: no extra tool, resource or instruction.
Scopes. A scope is a directory that holds AGENTS.md, CLAUDE.md, GEMINI.md, .claude/, .agents/,
.codex/, .cursor/rules, .github/copilot-instructions.md or a skills/ directory with a skill in it. The project
root is the scope .. Scopes are inferred from the tree; nothing is configured. Each item in a scope has a kind
(skill, instruction, rule, agent, command), a source (claude, agents, codex, cursor, copilot,
gemini, skills-dir), a name, a description (the frontmatter description, else the first heading) and a content
hash. The index keeps summaries only, never a file body.
Instruction files, rules, agents and commands are indexed wherever a known convention keeps them (.claude/,
.agents/, .cursor/, .github/, ...): the files present decide, not the presets list, so a configuration with
presets = [] (lint-only), ["claude"] or no configuration at all serves the same context. Only the skill
directories follow the presets, as in Where repo looks.
What applies to a path. The scopes that are the path or one of its ancestors, nearest first. A skill, agent or
command is shadowed by one of the same name in a nearer scope, as harnesses shadow by name; instruction files and rules
add up, so none of them is shadowed. A skill name that two scopes share is served under its path from the project root
(pkg/api/.claude/skills/lint becomes pkg.api.claude.skills.lint), as in
Discovering skills.
find_skill with path or scope.
{"task": "lint the code", "path": "pkg/api/handler.go",
"scopes": ["pkg/api", "."],
"results": [{"name": "pkg.api.claude.skills.lint", "scope": "pkg/api", "score": 1.4, "description": "Lint the api package"}]}
The root's own lint is shadowed for that path; for pkg/web/index.ts it is the one returned. scope lists one exact
scope, "." being the root; recursive: true adds the scopes below it. path and scope are mutually exclusive, and
recursive needs scope. A skill that ai-rulez defines or fetches belongs to no scope and applies everywhere. Each
result of a discovered skill names its scope. Without path and scope the call behaves as before.
find_context and load_context. Nested instruction files, rules and agents are served beside the skills.
find_context takes the same path, scope and recursive, plus an optional task to rank by name, scope, kind and
description (an item with no matching word is left out), kind (instruction, rule or agent) and limit (default
10, max 50). It returns summaries:
{"count": 4, "total": 4, "scopes": ["pkg/api", "."],
"results": [
{"name": "reviewer", "kind": "agent", "scope": "pkg/api", "source": "claude",
"description": "Reviews changes to the api", "uri": "context://pkg/api/reviewer", "hash": "sha256:..."},
{"name": "AGENTS.md", "kind": "instruction", "scope": "pkg/api", "source": "agents",
"description": "API conventions", "uri": "context://pkg/api/AGENTS.md", "hash": "sha256:..."},
{"name": "AGENTS.md", "kind": "instruction", "scope": ".", "source": "agents",
"description": "Root conventions", "uri": "context://./AGENTS.md", "hash": "sha256:..."},
{"name": "style", "kind": "rule", "scope": ".", "source": "claude",
"description": "Code style for the whole repository", "uri": "context://./style", "hash": "sha256:..."}]}
Rules with paths. A rule can name the files it applies to. With path, find_context returns such a rule only
when the path matches one of its globs: paths: in the frontmatter of a Claude Code rule, and globs: of a Cursor
.mdc rule that is not alwaysApply. The globs are relative to the scope directory, the one that holds .claude
(or .cursor), as in ai-rulez validate: pkg/api/.claude/rules/handlers.md with paths: "handlers/**" applies to
pkg/api/handlers/user.go, not to pkg/api/other/handlers/user.go. paths globs are anchored at that directory
(*.py is a file directly in it, **/*.py any depth); Cursor globs also match a bare pattern such as *.tsx at any
depth. A rule without either applies everywhere in its scope, and instruction files and agents are never narrowed. A
scope query (no path) lists every rule of the scope whatever its globs. A rule's result carries its paths or
globs, so you can see what narrows it. Pass a file as path: a directory matches no glob.
load_context reads one, by scope and name or by uri, with an optional budget_bytes:
It returns the text read from the repository when you call, never from the index, with the hash of what it read
(and changed_since_index: true when the file changed since start-up). The same text is the resource
context://<scope>/<name> (context://./AGENTS.md for the root), a resource template that resources/list does not
enumerate: find items with find_context. Commands are indexed and counted but not served as context.
The context tools apply what skills do: the --allow and --deny patterns match an item's name or scope/name
(--deny AGENTS.md hides every AGENTS.md; a non-empty --allow is an allowlist for context too); the security
scan runs when the index is built, so a file with an error finding is withheld (not listed, not loadable) and is a
start-up warning, and again on the content read at load time. The finding names the item by its context:// URI and
line, for example AR005 risky-shell-exec at context://./CLAUDE.md:2 (skill:// stays for skills); the scan level
(trust) is a skill setting and does not change what happens to a file of context, since an error finding withholds it
and the lesser findings are logged while the file is served; reads count against the session budget (--budget-bytes), which load_context, load_skill,
get_skill, read_skill_file and resources/read share. A symlink is never followed and a generated copy is skipped.
Domains, roles, the lock and approvals apply to skills only.
The scopes section of the catalog. ai-rulez://catalog gains a scopes array: each scope with its per-kind
counts and sources, so a planner sees the shape of the repository in one read. The same query parameters narrow it:
ai-rulez://catalog?path=pkg/api/handler.go, ai-rulez://catalog?scope=pkg/api or
ai-rulez://catalog?scope=.&recursive=true.
"scopes": [
{"scope": ".", "sources": ["agents", "claude"], "counts": {"skill": 1, "instruction": 1, "rule": 1}},
{"scope": "pkg/api", "sources": ["agents", "claude"], "counts": {"skill": 2, "instruction": 1, "agent": 1}}
]
The skill_discovery.index object of the same document reports items, limit, found and truncated.
Server instructions. The first eight scopes by depth are named in the server instructions with their counts
(pkg/api (2 skills, 1 instruction, 1 agent)), then "and M more", so a planner knows the nested content exists
without a call.
Limits. The index holds at most 20000 items. Past that it keeps the shallowest scopes and the server warns at
start-up; skill_discovery.index.truncated says so. Only files git lists (tracked, or untracked and not ignored; a
plain walk without git) are indexed, and an instruction file, rule or agent file over 1 MiB is skipped. path, scope
and uri are untrusted input: they must be relative to the project, free of backslashes and control characters, and
must not climb out with ..; they select entries of the index and are never opened as paths. The index is built when the
server starts and rebuilt when the catalog reloads; the file watcher picks a new
scope or instruction file up while the server runs, and load_context always shows the current text of a file the
index already knows.
The index cache¶
A cold build of the index is dominated by the security scan of every item. The index cache keeps what a start computed so the next one does not: the summary of each indexed file (name, description and content hash, keyed by path, size and modification time) and the scan verdict of content the scan found nothing in (keyed by a hash of the scan level and of the names and content hashes of the scanned files). It never stores a file body.
- Location.
<user cache>/ai-rulez/monorepo-index/<repo-id>/index.json, where<repo-id>is a hash of the canonical project root, so nothing is written into the repository. If the user cache is not writable the cache falls back to.ai-rulez/.cache/monorepo-index/(.ai-rulez-cache/monorepo-index/in the root layout), which is listed in.git/info/excludeso the working tree stays clean.[mcp] index_cache = "user" | "repo" | "off"chooses;useris the default and keeps the fallback,repoalways uses the project directory,offkeeps nothing.ai-rulez cache statusandai-rulez doctorreport which location is in use, its size and its problems;ai-rulez cache cleardrops it. - Freshness. A file summary is used while its size and modification time are unchanged, the git tree state
(
HEAD^{tree}) is the one it was recorded in, and the file was not modified in the two seconds before the save (a file written in the same timestamp tick may have changed again). A new tree drops the summaries and keeps the verdicts, which are keyed by content. A verdict is dropped when the build of ai-rulez or the[lint]settings change. - Safety. The index file is written with mode
0600through write-and-rename, under a lock file so several servers on one repository share it and merge their entries. It carries an HMAC-SHA256 with a per-user secret kept outside the cache (~/.config/ai-rulez/index-cache.key, like the catalog cache's): a file that was edited, truncated, planted or written by another version fails its check and is discarded and rebuilt. A cache entry is a hint, never a decision:--allowand--denyrun on every build, a verdict is only used for the exact bytes and trust level it was recorded for (a skill with any finding, even a warning, is scanned every time), andload_contextscans the body it reads again on every call. - Cost.
docs/performance.mdhas the measured numbers; on the benchmark monorepo (1500 items over 500 scopes) a warm start takes about a tenth of a cold one.
Excluding paths¶
--exclude <glob> (repeatable) and exclude = [...] under [mcp] in .ai-rulez/config.toml (or the root
ai-rulez.toml) leave paths out of discovery:
Globs are relative to the project root with slashes: ** crosses directories, * and ? stay within one, {a,b}
alternates, a pattern without a slash (AGENTS.md, legacy) matches at any depth, and a pattern naming a directory
(third_party, third_party/, third_party/**) excludes everything below it. A glob that leaves the project
(../x) or is empty is rejected. The flag's globs are added to the configuration's; neither replaces the other.
An excluded file is dropped from the file listing before anything is read, so it costs no parsing, scan or cache
entry. The globs apply to the skill directories and to the whole context index: CLAUDE.md, AGENTS.md,
GEMINI.md, Copilot instructions, rules, agents and commands. An excluded directory marks no scope. The file watcher
does not follow excluded paths either, so a change below one does not refresh the index. --exclude without
discovery is an error, as it would silently do nothing; the configuration key is ignored while discovery is off.
Live refresh of the index¶
While --serve-skills --discover-skills runs, a debounced file watcher (fsnotify, 300 ms) follows the project tree.
When an instruction file (AGENTS.md, CLAUDE.md, GEMINI.md, copilot-instructions.md), a SKILL.md, anything
under .claude, .agents, .codex, .cursor, .github or skills, or a directory appears, changes or goes, the
server rebuilds its catalog. The rebuild re-reads only the files whose size or modification time changed and scans
only content without a clean verdict, so it is incremental in cost. When the index changed the server re-registers
the ai-rulez://index resource with a new generation, which makes the SDK send one resources/list_changed; a client
then calls find_context or find_skill again. Reading ai-rulez://index returns the generation, the item and
scope counts and the cache report. .git, node_modules, vendor, target, dist, build and the configuration
directory (which the poll below covers) are not watched, a skipped change costs nothing, and --no-watch turns the
watcher off with the rest of live reload. If the operating system's limit on watched directories is reached the
server warns and the index refreshes on the poll interval only. The server instructions, written at start-up, are not
rewritten.
Writing a skill into a scope¶
create_skill and update_skill take scope (a project-relative directory), allow_write and, optionally,
harness. With scope the skill is written to <scope>/<harness skills dir>/<name>/SKILL.md, for example
pkg/api/.claude/skills/lint/SKILL.md, where a harness working in pkg/api loads it:
{"success": true, "operation": "create_skill", "path": "pkg/api/.claude/skills/lint/SKILL.md", "name": "lint",
"scope": "pkg/api", "harness": "claude", "message": "Skill written to ...", "lock": "ai-rulez.lock exists: ..."}
Rules, all checked before anything is created:
allow_write: trueis required on the call (default false), likeallow_llmforjudge.- The scope must be an existing directory of the project (every scope the index knows is one). Neither it, nor any
directory on the way to the skill, nor the skill directory or file may be a symlink; a path that leaves the
project (
.., an absolute path, a backslash) is refused; the write itself goes through anos.Rootof the project, so a link planted after the checks cannot lead out. - Only the skills directory of a preset the configuration enables is written:
harnessnames it, else the first enabled preset that keeps skills in a directory (claudeis.claude/skills,codexis.agents/skills). - A generated copy is refused: a file listed in the generated manifest (for a skill, any file of its directory) or
carrying a
Content-Hashheader that verifies. Edit the source under.ai-rulez/instead. - The content is scanned as
install_skillscans a skill, at the strictest trust level, and any finding refuses the call. With nocontent,create_skillbuilds a SKILL.md fromdescription. create_skillrefuses an existing skill;update_skillrefuses a missing one. The file is written with mode0644through a temporary file and a rename, and calls on one server are serialized.- A scoped write edits the harness file directly: it is not ai-rulez content and is not generated down. With an
ai-rulez.lockpresent the result carrieslock, because the new file is reported as drift untilai-rulez lockrecords it.
scope cannot be combined with domain, priority, targets or local. Instruction files and rules stay read-only
over MCP. The tools are annotated as writing and non-idempotent (update_skill replaces a file), so a read-only
server (--read-only) does not register them.
Start-up cost and the catalog cache¶
Building the catalog renders every served skill and runs the security scan over it, which on a large
monorepo (hundreds of skills, each with reference files) takes several seconds. The result is a
function of the files the live reload fingerprints — the configuration directory, local includes, the
organization policy files and local skill sources — and of the view's options (--profile, --role,
--targets, --domain, --allow, --deny, --include-static, --source, --frozen, --offline),
so it is cached on disk keyed by exactly those:
~/.cache/ai-rulez/serve/<project-hash>/catalog.jsonholds the rendered catalog; the project hash is a sha256 of the configuration directory, so two checkouts never collide.~/.config/ai-rulez/serve-cache.key(mode0600, created on first use) authenticates the entry.
A restart whose content and view are unchanged restores the catalog without rendering or scanning
anything; a content edit changes the fingerprint, so the entry is ignored and the catalog rebuilt.
An entry that is missing, truncated, edited, symlinked or fails its authentication counts as a miss
and is rebuilt, never served. Admission (approval expiry, signing key validity) is still re-judged on
the usual one-minute timer after a restore, so an entry written before an approval expired does not
serve that skill for longer than a fresh start would. The cache is skipped for a lock run's views and
for a build whose inputs the fingerprint cannot see (a programmatic load option or an injected clock),
and an entry over 64 MiB is not written; in those cases the server builds the catalog as before, only
slower.
Launchers and client timeouts¶
A cold start through npx -y ai-rulez@latest downloads the package (about 30 MB) before the process
even starts, and some clients time out at 30 s. Two things help:
- Run the installed binary, not
npx. Install once (npm i -g ai-rulez,uv tool install ai-rulez,go install github.com/Goldziher/ai-rulez/v5/cmd/ai-rulez@latest, or a Homebrew tap) and point the MCP entry atai-rulezdirectly. If you must usenpx, pin the version (npx -y ai-rulez@5.0.0) so a cold start does not also resolvelatest.
- Raise the client's connect timeout. Claude Code reads
MCP_TIMEOUT(milliseconds);60000covers a cold build on a large repository:
The disk cache removes the repeated cost on every later start; the shared daemon
(--daemon) additionally removes the first build for every session after the first.
Shared daemon¶
ai-rulez mcp --serve-skills --daemon makes one long-lived process per repository render the
catalog once and serve it to every session over a private unix socket. The first session spawns the
daemon (the same binary, detached in a new session, its standard streams on the null device) and
waits for the socket; every later session connects in milliseconds and is a thin proxy: it copies
its stdin/stdout to the daemon and nothing else. All sessions share the one rendered catalog, the
one search index and the one usage recorder, so the 6–8 s build of a large monorepo is paid once
rather than by each session and each subagent worktree.
- Socket and key. The daemon listens on
~/.cache/ai-rulez/daemons/<hash>/daemon.sock(mode0600) inside a0700directory. The hash is a sha256 of the repository toplevel, the ai-rulez version, and the command line (the view —--profile/--role/--targets/--include-static, the--domain/--allow/--denyfilters,--source,--frozen/--offline, the authoring--root/--allow-any-dir/--read-only, and the usage flags). A version change or a different MCP entry therefore starts and uses a different daemon, and two sessions in different subdirectories of one checkout share one; a subagent worktree, a separate checkout, has its own. - Content changes. A content edit does not start a new daemon: the running one picks it up with its existing live reload (within the poll interval), exactly as an in-process server does. One daemon serves one repository through its edits, and exits when it is idle.
- Idle timeout. With no connected client for 15 minutes the daemon exits and removes its socket. A session that connects just after it leaves starts a new one; one that finds the socket gone serves in-process instead.
- Security. The socket lives in a
0700directory the user owns and is0600. Before connecting, a proxy refuses a path that is not a socket, is owned by another user, or is readable or writable by group or others, and it never replaces such a socket with a daemon of its own; it serves in-process. The hello carries the protocol version and the serve view, and a daemon refuses a mismatched one, so a session is never served another view's skills. - Fallback. On any failure — no daemon and none can be started, a socket that is missing,
unsafe, stale or broken, a version or view mismatch, or a socket path over the unix limit — the
process serves the session in-process, byte for byte as without
--daemon. A broken or missing daemon never fails a session; the only cost is the build the daemon would have saved. - Windows. The shared daemon is not offered on Windows: there is no unix-domain-socket permission
model or detached-child session to reuse, so a session with
--daemonserves in-process with a warning. Nothing else changes. - Limits. The daemon is a stdio convenience: one catalog per repository, shared by the sessions that agree on the command line. It is not a network service and has no authentication beyond the socket's file permissions.
Tools¶
| Tool | Arguments | Result |
|---|---|---|
find_skill |
task (required), limit (default 5, max 20), role, path, scope, recursive |
Ranked matches: name, description, score, domain, digest. With a role, in_role and the role's skills first. With discovery, path or scope keeps the skills that apply there (see Monorepos). |
load_skill |
name (required), path, budget_bytes |
The file (SKILL.md by default), provenance, digest, an index of the skill's other files with their mode, and files_access (where the files live and how to use them) when the skill has any. |
list_skill_resources |
name (required), offset |
Every file of the skill with path, URI, size, MIME type, digest, mode, executable and an interpreter hint, without loading them. |
find_context |
task, path, scope, recursive, kind, limit |
With discovery only. Summaries of the nested instruction files, rules and agents that apply to a path or scope: name, kind, scope, source, description, context:// URI, hash. |
load_context |
scope and name, or uri; budget_bytes |
With discovery only. The text of one of them, read from the repository now, with its hash. |
search_skills, get_skill and read_skill_file, the skill:// resources, skills/list and skills/get keep
working as described above. All tools are annotated read-only.
- Ranking.
find_skillscores BM25 over four fields with weights name 3, triggers 2.5, keywords 2 and description 1, after lowercasing, dropping stopwords and a light stemmer (migrationsmatchesmigration). By default it is lexical and deterministic: score descending, then name. With[search] mode = "hybrid"(orvector) and an index built byai-rulez search index, the same ranker used byai-rulez searchranks by cosine similarity over the index while it is fresh, and fuses it with that list by reciprocal rank fusion otherwise ([search] fusion). The server never builds the index and never embeds a skill: it loads the index files (and reloads them when they change) and embeds only the query, bounded byquery_timeout_msand an in-memory cache of 256 queries, through the[llm]network gate, budget and cache. Any failure (no index, an index of another model, the network disabled, a timeout, the budget) answers lexically and says why: the result gains"ranking": "lexical"and"degraded": "no_index" | "provider_unavailable" | "timeout" | "budget" | "network_disabled". A hybrid result also carriesranking, and each matchlexical_rankandvector_rank(0: not in that list); a skill edited since it was indexed ranks lexically only and hasstale_vector: true. A server with the default lexical mode returns exactly the fields listed above.search_skillsstays lexical: it is the listing and filter tool. With[search] log_queries = truein the user config (orAI_RULEZ_SEARCH_LOG_QUERIES=1) the server also records eachfind_skillquery and the skill the session loads next (see Skill search). - Roles.
roleis resolved against the project's[[roles]](mcp.RolesFromConfig): a skill is in scope when the role keeps it (its domains andskillsinclude and exclude selectors,extendsmerged in). Matches inside the scope come first, then the rest markedin_role: false; an unknown role is an error. Theroleargument is a ranking lens the caller chooses, not access control: what a server can load is decided by--roleat start, which builds the catalog without the skills outside the role (they cannot be found, loaded, listed or read by name). A skill that comes from a[[skill_sources]]entry is matched by name against the role'sskillsselectors. The role is read again on every live reload. - Budget. Each session may read
--budget-bytesbytes of skill content;load_skill,get_skill,read_skill_fileandresources/readdraw on the same budget (listing tools cost nothing, and a repeated read is charged again). A read that would exceed the remainder is refused and is not charged (load_skillreports the bytes left;resources/readfails with JSON-RPC error-32600); the server tracks at most 1024 sessions;budget_byteson one call truncates that call's file (at a character boundary) and reportstruncatedandtotal_bytes. Supporting files count too. - Path.
pathis relative to the skill. Absolute paths,.., backslashes and names that are not validskill://path segments are rejected. A path with.or..segments that normalises to a file inside the skill (references/../SKILL.md) is accepted as that file; one that would leave the skill is rejected. The result'suriis the URI of the file loaded (skill://<name>/<path>). Binary files are read withresources/read. - Provenance. Every result carries
provenance:digest(served bytes),lock_digest(what the lock pins),locked,source,ref,pinned,commitfor a source skill,delivery, andscan_warnings.
Served files: scripts and assets¶
A served skill's files are held by the server; none is written to disk. To use a bundled script or asset a client fetches it, writes it out and restores its mode.
- Mode and interpreter.
list_skill_resources, theresourcesindex ofload_skill,get_skillandread_skill_filereport per filemode(octal permission bits such as"0755";"0644"when the source mode is unknown),executable(any execute bit) and, for a file with a shebang or a known extension (.sh,.py,.js, ...), aninterpreterhint (the shebang line, orsh,python3,node, ...).skills/list,skills/getandresources/listcarry the same under_metaasio.modelcontextprotocol.skills/mode,.../executableand.../interpreter; the existinguri,digestandsizefields are unchanged, and the mode is not part of any digest or ofai-rulez.lock. files_access.load_skilladds this note when the skill has files besidesSKILL.md: they are not on disk, fetch them withread_skill_file, restore the executable bit, or setdelivery = "both"and runai-rulez generate.read_skill_file(uri, encoding, offset, length).encodingistext(default for text files) orbase64(default for a binary file, which used to fail). A call returns at most 256 KiB, so a large file is read in chunks:offsetandlengthare byte positions, the reply carriessize,offset,bytesand, while more remains,truncatedandnext_offset. A text chunk never splits a UTF-8 character. Every returned byte is charged to the session budget likeresources/read. Files the security scan cannot read are still refused whereAR989refuses them today; nothing here serves them.- Recipe to materialise a skill. For each entry of
list_skill_resources(page withnext_offset): create the parent directory under a directory you own, refusing anypaththat is absolute or contains..; read the file withread_skill_file {uri, encoding: "base64", offset}untilnext_offsetis absent, concatenating the decoded chunks; write the bytes andchmodtomode. ResolveSKILL.mdrelative paths against that directory. A tool that does this inside the server (materialize_skill) is not provided: this server mode registers no tool that writes, and a safe writer needs a project root and an overwrite policy it does not have.
Skill sources¶
A source is a repository (or a directory) of skill directories, served and never written to the static trees.
[[skill_sources]]
name = "acme" # identifies the source in the lock
url = "https://github.com/acme/skills.git" # git URL (https, ssh or file; a leading git+ is accepted) or a local directory
ref = "v1.2.0" # a tag or a full commit SHA (or version = "^1.2", see below)
path = "skills" # subdirectory whose children are skills
include = ["pdf-*", "sql"] # directory-name globs; exclude wins
exclude = ["*-wip"]
name_prefix = "acme-" # served as acme-pdf-forms, and SKILL.md name is rewritten
trust = "error" # scan level: error (default) or warn
max_skills = 200 # optional: skills the source may load (default 200)
max_bytes = 67108864 # optional: bytes of skill files it may load (default 64 MiB)
max_clone_bytes = 268435456 # optional: size limit of the git clone (default 256 MiB)
max_clone_files = 20000 # optional: entries of the git clone (default 20000)
A source can ask for a version range instead of a ref: version = "^1.2" (also tag_prefix,
include_prerelease; ref and version are exclusive). The range is resolved against the repository's semver tags
by ai-rulez lock and moved only by ai-rulez update; serving uses the pinned commit and never resolves a range.
See Version constraints.
--source takes the same thing on the command line: [git+]<url>[@<tag|commit>][#<subdir>] or a directory,
for example git+https://host/org/repo@v1.2.0#skills/. The ref separator is the last @ after the final /,
so git@host:org/repo.git and https://user@host/... keep their user info. The source is named
cli-<repository>. In a directory with no .ai-rulez, --source alone is enough to serve.
- Pinning. A tag is resolved to the commit it points at (an annotated tag is peeled), a full SHA is used as
is.
ai-rulez lockrecords the commit and the tree digest (the samesha256scheme as includes) as a[[source]]entry inai-rulez.lock. With the lock, the pinned commit is fetched by its SHA even if the tag or branch has since moved, and the fetched tree must match the digest or serving fails. A server that refuses fetching a commit by SHA is asked for the ref with its history instead; if the pinned commit is no longer reachable from it (history was rewritten), the fetch fails closed and says so. Thecommitin a lock entry must be a full hex SHA. - Unpinned refs. A branch, a tag the lock does not cover, or no ref, follows a moving ref; it is reported as
unpinned (
AR010, and a warning at serve time) until the lock covers it. - Transports and private repositories. Git runs without hooks, credential helpers, prompts or submodules and
only speaks
https,sshandfile(neverext::; plainhttp://is not fetched). Aurlorrefthat starts with-is rejected, as git would read it as an option. Credentials are not injected and the git credential helpers are off, so a private repository needs ansshURL that works non-interactively (an ssh-agent key); a privatehttpsrepository is not supported. Each git command is cut off after five minutes. - Limits. A source may load at most
max_skillsskills (default 200) andmax_bytesbytes of skill files (default 64 MiB); a larger source is an error naming the key. A skill has at most 2000 files. - Clone size. A git source is fetched as a partial clone (
--filter), and with apathonly that subdirectory is checked out (a sparse checkout), so a large repository costs what the skills cost. The clone, git data and checkout together, is capped atmax_clone_bytes(default 256 MiB; theAI_RULEZ_MAX_CLONE_BYTESenvironment variable, ormcp --serve-skills --max-clone-bytes <n>, sets it for every source that does not set its own; the flag wins over the environment variable, and a source's ownmax_clone_byteswins over both). The size is watched while git runs: a clone that grows past the limit is stopped, fails with an error namingmax_clone_bytesand the source (skillsource.ErrCloneTooLarge), and leaves nothing in the cache. Setpath, or raise the limit. - Cache integrity. A source the lock covers is checked against the lock's digest. A source whose
refis a full commit SHA but which the lock does not cover is checked against a digest recorded next to the cached tree (<tree>.digest, written atomically, outside the tree so it never changes the tree digest, readable only by you). If the tree no longer matches, or has no record (a cache written by an older release), it is fetched again; with--offline/--frozenthere is no fetch, so serving fails with a "cache damaged" error until you run once online. The record catches damage and edits of the tree; it is not a defence against someone who can also rewrite the record, which is what the lock is for. - Cache and network. Trees are cached per commit under
~/.cache/ai-rulez/skill-sources/.--frozenrequires the lock to cover the source and never touches the network (the commit must be cached).--offlinedoes not require the lock: it uses the lock's commit, or the commit an earlier online run recorded for that ref. - Local directories need no network; a lock entry pins their digest, and serving refuses a changed tree. A
local
url/pathdeclared in the project's config must resolve, after symlinks, inside the project (a relative one is relative to the project root); a committed config cannot point the server at another user's skill directory.--source <dir>on the command line and sources in your user config may point anywhere. - Safety. Symlinks below the source are never followed; a
pathof a git source that goes through a symlink is refused, and a local source directory that is itself a symlink is resolved and digested through the link. A file over 2 MiB is dropped with a warning, a skill over 8 MiB is skipped with a warning. A source skill is served under its directory name (withname_prefix), whatever itsname:says (SKILL.md is rewritten), and a skill whose name collides with a project skill (served or static) or with an earlier source's skill is skipped (setname_prefix); the project's skill wins. - Symlinked skills. A skill directory that is a symlink out of the project is dropped by the server with a
warning, while
validatefails on it: fix the link rather than relying on the warning.
Security scan¶
Every served skill is scanned before it is served, with the rules of ai-rulez scan (secrets,
hidden characters, prompt-injection phrases, risky shell, unrestricted allowed-tools, encoded blobs) over
SKILL.md and every text file of at most 512 KiB. A file the scan cannot read (binary: a NUL byte or invalid
UTF-8; or over 512 KiB) is reported as AR989. At trust = "error" (remote sources, installed skills) such a
file is not served at all (load_skill, resources/read and the file list omit it; provenance lists it under
unserved_unscannable_files, and the lock digest still covers it), and a SKILL.md that cannot be scanned
refuses the skill. At trust = "warn" (skills authored in the project) the file is served with the warning.
A skill that fails is not served: it is absent from resources/list,
skills/list and find_skill, a warning names the finding on stderr, and load_skill says why. Inline
ai-rulez-lint-ignore comments are not honored. validate --strict runs this same scan over the served
skills, authored and from sources alike, and reports AR989 for each file it cannot read (see the table below).
The level is trust for a source skill:
| Level | Blocks |
|---|---|
error |
Any finding (every finding counts as an error). Default for source skills and for skills installed from a git repository (lint.security.scan_imports = "warn" lowers installed skills to warn). |
warn |
Findings that are errors by their own severity (secrets, hidden characters, risky shell). Default for skills authored in the project. |
Skills that come from an [[includes]] entry are remote content: they are scanned at error like installed skills
(scan_imports = "warn" lowers them to warn), as is any skill file outside the project root. This holds wherever
the include lives, including a local include inside the project (source = "./shared"). Their lock source
is include:<name>/<path>, identical on every machine.
Lock enforcement¶
ai-rulez lock also records, for every skill the server would serve, a [[served]] entry with its name and
lock digest (and a [[source]] entry per skill source). With
the server refuses a served skill whose digest differs from the lock, and one the lock does not pin, with
AR995. validate --strict reports the same. The lock digest is a sha256: tree digest in the same scheme as
the other pins (Lock file, domain ai-rulez/served-skill/v1). It
covers the rendered files but not the generated header lines that change without the skill changing (a whole
Source-Hash: <algorithm>:<hex> or Content-Hash: <algorithm>:<hex> line and the Generated: date stamp, in the first 40 lines of a rendered
file), so editing one skill does not invalidate the others. Files of a skill source are digested exactly as
they are: nothing in them is ignored. The digest is of
the rendering the view selects: the default preset's, or another preset's with --targets (a view of its own, see
below). Skills that only a role serves are pinned too: lock builds the unscoped view and the view of every
role. ai-rulez lock --kind served|source refreshes one kind; lock --check verifies both without the network.
[[served]] entries are recorded per serve view: the way the server is started selects a set of skills, and
the lock pins each set under a view key (see Lock file). A plain
lock pins the default view (the configured [[skill_sources]], the default profile and preset, the delivery
rules), every role, and every view the lock already records. To pin another view, give lock the flags the
server runs with:
ai-rulez lock --role backend # skills the backend role serves
ai-rulez lock --profile team --include-static # profile view that also serves static skills
ai-rulez lock --targets cursor # skills as the cursor preset renders them
ai-rulez lock --source git+https://host/org/skills@v1.2.0#skills # a command-line source, pinned with its commit
ai-rulez mcp --serve-skills --role backend --frozen
mcp --serve-skills and lock --check read the pins of the view they run with, so a server started with
--role, --profile, --targets, --include-static or --source finds its skills pinned once lock has pinned that view;
otherwise every skill it adds is refused with AR995. A lock written before views existed (no view keys)
still covers every view, as long as the digests match. --targets is part of the view (targets:<preset>): another
preset's rendering has its own pins, and lock --targets <preset> pins it.
A skill the security scan refuses is not pinned. By default lock reports it, pins every other skill, writes the
lock and exits 3; validate --strict lists the refused skills of skill sources with their scan code (AR0xx). With
lock --refuse-findings any refusal stops lock from writing the lock. Either way a refused skill has to be fixed or
excluded before it can be served under enforcement.
Usage telemetry¶
Each successful load_skill goes through the usage recorder as one identifier-only JSON line: time, skill name,
salted session hash (a stdio connection has no transport session id, so the server gives each connection a random
one; a new connection gets a new hash and a new byte budget, the same pipe keeps both; the salt file is created next
to the log on first use), harness (the MCP client name), the role the server runs under, outcome: "loaded", content
hash from the skills index, the served digest, and served: true (log format v: 3, the same as hook-recorded
loads).
A supporting file loaded with path is logged with resource: true and is not counted again by
ai-rulez telemetry report. Nothing is written until you opt in: pass --usage-log <file> or --usage-sink <command>,
or enable [usage] skills_index = true, which logs to <config dir>/local/usage.jsonl.
A --usage-sink receives exactly the line the log gets, salted session included. Without a --usage-log the salt
lives in <config dir>/local/usage.salt. The sink command runs in the background: records wait in a queue of 256,
so a slow or hanging sink never delays load_skill. A full queue drops the new record and logs a warning with the
number dropped. Queued records are delivered at shutdown (end of input, SIGTERM or Ctrl-C), waiting up to
three seconds.
Live reload¶
The server checks the configuration directory, local include directories (and local_override), the local
files of the organization policy, and local source directories every two seconds. A file counts as changed
when its size, modification time or (up to 1 MiB) its bytes change, so cp -p and rsync -t are seen. When a
file changes it rebuilds the catalog and swaps it in. notifications/resources/list_changed is sent only when the
catalog changed (a skill added, removed or edited); an edit that leaves every served skill identical sends nothing. A rebuild that
fails keeps the previous catalog serving and is retried with a growing pause (up to a minute) until it works or
a new edit changes the files (a new edit ends the pause at once). A rebuild that finds the SKILL.md of a
served skill newly empty or with an unclosed frontmatter (an editor's save in progress) counts as failed, so
the previous version keeps serving until the save completes. The usage log and its salt file (--usage-log
and usage.salt beside it, compared by absolute path) and any .jsonl file directly inside a local/ directory,
at any depth, do not count as changes. Polling keeps the dependency set unchanged; git sources are immutable
per commit and are not re-fetched.
Approval expiry and signing key validity depend on the clock, not on files: every minute (also with
--no-watch) the server judges the current build again and stops serving a skill whose approval expired or
whose signature no longer verifies. An organization policy whose sources.deny_digests names the pinned
digest of a served skill refuses that skill (AR747); the server keeps serving the others. A skill authored in
the project is also gated as its skill:<name> item: a denied authored digest refuses it (AR717), and
require_approval selectors that select the item (all, local, kind:skill) gate serving it under
enforce.
Strict validation¶
| Code | Severity | Meaning |
|---|---|---|
AR990 |
warning | Static content names a served skill |
AR991 |
error | A harness that can call MCP has no dynamic-skills stub |
AR992 |
warning | A harness without MCP keeps served skills as static files |
AR993 |
warning | Skills are served but no [[mcp_servers]] entry runs --serve-skills |
AR994 |
error | delivery frontmatter is not static, served or both |
AR995 |
error | [lock] enforce and a served skill is unpinned or its digest differs |
AR989 |
warning / error | A served file cannot be scanned (binary or over 512 KiB). An error for SKILL.md at any trust level (the server refuses the skill). For a supporting file: a warning at trust = "warn" (served with the warning) and, at trust = "error", a finding that names the unserved files |
See Strict validation for the full table.
Typical Workflow¶
With Your Editor¶
- Edit files directly in
.ai-rulez/:
- Use MCP server (via AI assistant) to generate:
- Commit changes:
git add .ai-rulez/ # plus generated files only if gitignore = false
git commit -m "docs: update AI guidelines"
With Claude CLI¶
If using the Claude CLI with the ai-rulez MCP server, you can ask Claude to help:
- "Review my
.ai-rulez/configuration and suggest improvements" - "Generate outputs for my new rules"
- "Validate that my profiles are correct"
- "Create a new backend domain and add database rules"
- "Add an include from our shared rules repository"
The server provides Claude with access to read and modify your configuration, while you maintain full control over final decisions.
MCP Tools Reference¶
The ai-rulez MCP server exposes 66 tools for programmatic configuration management. These tools allow AI assistants to initialize projects, generate and clean outputs, validate configuration, inspect builtins, and create, read, update, and delete configuration elements.
Every tool below accepts an optional working_directory parameter (the directory to operate in),
except the two utility tools get_version and show_builtin. The per-tool parameter lists below
name only the parameters specific to that tool unless noted.
Local configuration (local: true)¶
Tools that change content or config accept an optional local boolean that redirects the change to
the machine-local layer instead of the shared one. See Local Configuration.
| Tools | local: true targets |
|---|---|
create_*, read_*, update_*, delete_*, list_* for rule, context and skill |
The .ai-rulez/local/ content tree (domain selects local/domains/<name>/) |
add_include, remove_include, install_skill, uninstall_skill, update_config, add_profile, remove_profile, set_default_profile |
The config.local.* overlay |
Not accepted: create_domain, delete_domain, list_domains, list_includes, list_profiles,
list_installed_skills. Those list tools show the shared layer only.
The drift guard applies to generate_outputs too. --allow-local-drift is CLI-only: the MCP server
cannot write overlay-derived values over tracked shared files. Pass no_local: true to generate the
shared view, or fix the reported paths.
Tool metadata, arguments and results¶
Metadata. Every tool has a human title and annotations: readOnlyHint, destructiveHint and idempotentHint
describe what it writes, and openWorldHint is false on all of them (they act on the local project tree only).
Read-only tools (list_*, read_*, validate_config, scan_content, doctor, run_verifiers, get_version,
show_builtin, the report tools, the governance tools and the skills-serving tools) never write.
Arguments. Input schemas are inferred from typed argument structs and enforced before a handler runs: an
unknown argument, a wrong type ("local": "yes"), a value outside an enum or a missing required argument is an
error result. Every schema sets additionalProperties: false.
Results. Every tool declares an outputSchema and returns structuredContent (the parsed result) together with
the same JSON as a text block, for clients that only read text. Output schemas list the stable fields only and allow
others, so a result can gain a field without breaking validation.
Errors. A failure is an error result (isError: true), never a success. That covers a configuration that does not
load (every list_* and read_* tool, as ai-rulez list exits 1), an invalid configuration, findings that reach
fail_on, a lock_status call on a project without a lock, doctor when the configuration cannot be read, and
arguments that fail validation. The error text carries the CLI's hint; errors that are reports (validate_config,
doctor) also return the report as structuredContent.
Confinement. working_directory, config_file and config_dir must lie inside the directory the server was
started in. A call without working_directory uses that directory; a relative one resolves against it; symlinks are
followed before the check. Start the server with mcp --root <dir> to confine it elsewhere, or with
mcp --allow-any-dir to lift the check. The skills-serving mode takes no directory argument.
Project root. Like the CLI, every tool except init_project walks up from working_directory to the nearest
directory that holds a configuration in any layout, stopping at the git root and never above the home directory. A
call that passes config_file or config_dir is not walked up.
Capabilities. The authoring server advertises tools, prompts and resources, none with listChanged (their
sets are fixed), pages lists at 100 entries, and sends the SDK's warnings and errors to stderr. Its lists carry a
cache hint (ttlMs five minutes, public); resource reads are never cacheable, since the project can change between
two reads. The skills-serving server's hints are private, one minute, and zero under live reload.
Progress and cancellation. Long calls (generate_outputs with recursive, token_report over several targets)
send notifications/progress when the request carries a progress token, and stop when the client cancels the request.
Response keys¶
Items in the list tools are the file records of the CRUD layer, so their keys are capitalised (Name, Path),
unlike the lower-case envelope keys (success, operation, count).
Project and Utility Tools¶
generate_outputs¶
Generate output files from the current configuration.
Parameters:
config_file(optional, string): Path to the root configuration fileconfig_dir(optional, string): Configuration directory name (default:.ai-rulez)dry_run(optional, boolean): Preview changes without writing filescheck(optional, boolean): Write nothing and report the generated files that differ from what the sources render (generate --check). The result is{"status", "roots", "blocked", "differing": [{"kind", "path", "note"?}]}, and drift is an error result, as the command exits 2offline(optional, boolean): Never use the network; includes and installed skills come from the lock and the cache (--offline)profile(optional, string): Profile to generate, or a comma-separated list to compose several (--profile)role(optional, string): Generate the slice of content a role selects (--role); mutually exclusive withprofilerecursive(optional, boolean): Generate for all subdirectories containing.ai-rulez/exclude(optional, array of strings): Withrecursive, directories never searched for configuration, as globs or paths relative to the working directory (--exclude); adds torecursive_excludeno_local(optional, boolean): Ignore the machine-localconfig.local.*overlay andlocal/content (the view a teammate sees)no_self_mcp(optional, boolean): Do not add ai-rulez's own MCP server to the generated MCP files (--no-self-mcp; the default is to add it)working_directory(optional, string): Directory to operate in
A call that supports progress (the client sends a progress token) reports one notification per project in a
recursive run, and a cancelled call stops before the next project.
clean_outputs¶
Remove the files produced by generate_outputs (its inverse): the generated assistant files, the generated manifest (and local manifest with the local rule files it records), and the ai-rulez managed .gitignore block. The .ai-rulez/ source tree is never touched, and generated directories are removed only once empty.
Parameters:
config_file(optional, string): Path to the root configuration fileconfig_dir(optional, string): Configuration directory name (default:.ai-rulez)dry_run(optional, boolean): Preview what would be removed without deletingkeep_gitignore(optional, boolean): Leave the ai-rulez managed block in.gitignorekeep_manifest(optional, boolean): Leave the generated manifest in placeworking_directory(optional, string): Directory to operate in
validate_config¶
Validate the configuration with the same checks as ai-rulez validate: the configuration file against its
schema, structural validation (including all includes), the organization policy, the config.local.* overlay against
the local schema (schema/ai-rules-local.schema.json), and then every lint analyzer, with the baseline and ratchets
the CLI applies. The tool and the command share one code path, so they report the same findings against the same
threshold. Errors are redacted: URL credentials and quoted source values are removed, since the merged config can
contain local secrets.
The result is {"valid", "fail_on", "config", "warnings", "errors", "roots", "findings", "summary", "risk"}:
warnings and errors are the findings of that severity as file:line: CODE message, and findings, summary and
risk are the document of validate --format json. An invalid configuration, or findings at or above fail_on,
is an error result (isError: true) with the same document in structuredContent; findings below the threshold are
returned as warnings in a success result.
Parameters:
config_file(optional, string): Path to the root configuration fileconfig_dir(optional, string): Configuration directory name (default:.ai-rulez)no_local(optional, boolean): Validate without the machine-local overlayfail_on(optional, string): Lowest severity that fails the call:error(default; or the[lint]setting),warning,infoornone(--fail-on)strict(optional, boolean): Fail on warnings too, the same asfail_on: "warning"(--strict)config_only(optional, boolean): Check the configuration only and skip the content checks (--config-only)lint_profile(optional, string):default,strictorpermissive(--lint-profile)analyzers(optional, string[]): Run only these analyzers (--analyzer)working_directory(optional, string): Directory to operate in
init_project¶
Initialize a new ai-rulez project in the current directory with the layout ai-rulez init creates:
.ai-rulez/config.toml plus the rules/, context/, skills/, agents/ and domains/ directories. It refuses
to overwrite an existing configuration. It initializes exactly the given directory and does not walk up, but refuses
when a directory above it in the same repository already holds a configuration (the message names that file).
Parameters:
project_name(optional, string): Project nameproviders(optional, array): Providers to enable, such asclaudeorcursorwith_agents(optional, boolean): Also create a sample agent,agents/code-reviewer.md.agents/itself is always created.all_providers(optional, boolean): Enable the curated set of tool presetspopular_providers(optional, boolean): Same curated set — a shortcut forall_providerssource(optional, string):ai-rulez(default) oragents. Withagentsit writes.agents/ai-rulez.tomland a starterAGENTS.md(when there is none) instead of the.ai-rulez/layout; see .agents as a sourceworking_directory(optional, string): Directory to operate in
get_version¶
Return the ai-rulez version.
Parameters: (none)
show_builtin¶
Show the full content of a builtin domain.
Parameters:
name(required, string): Builtin domain name, such assecurity,rust, ortypescript
Domain Tools¶
create_domain¶
Create a new domain with subdirectories for rules, context, and skills.
Parameters:
name(required, string): Domain name (alphanumeric and underscores, 1-50 characters)description(optional, string): Description of the domain
Response:
{
"success": true,
"operation": "create_domain",
"name": "backend",
"path": ".ai-rulez/domains/backend",
"message": "Domain created successfully"
}
Example:
delete_domain¶
Delete a domain and all its contents.
Parameters:
name(required, string): Domain name to delete
Response:
{
"success": true,
"operation": "delete_domain",
"name": "backend",
"message": "Domain deleted successfully"
}
list_domains¶
List all domains in the .ai-rulez/ directory.
Parameters: (none)
Response:
{
"success": true,
"operation": "list_domains",
"domains": [
{
"Name": "backend",
"Path": ".ai-rulez/domains/backend",
"Description": "Backend services"
}
],
"count": 1
}
Rule Tools¶
create_rule¶
Create a new rule file with optional YAML frontmatter.
Parameters:
name(required, string): Rule filename without .md extensioncontent(optional, string): Markdown content with optional YAML frontmatterdomain(optional, string): Domain name (if not specified, creates in root)priority(optional, string): Priority level - critical, high, medium, low, minimal. Default: mediumtargets(optional, array): Target providers (e.g., ["claude", "cursor"])local(optional, boolean): Create in.ai-rulez/local/(gitignored) instead of the shared tree
Response:
{
"success": true,
"operation": "create_rule",
"path": ".ai-rulez/rules/code-quality.md",
"name": "code-quality",
"domain": "",
"message": "Rule created successfully"
}
update_rule¶
Update an existing rule file.
Parameters:
name(required, string): Rule filename without .md extensioncontent(required, string): New markdown contentdomain(optional, string): Domain namepriority(optional, string): Priority leveltargets(optional, array): Target providerslocal(optional, boolean): Update the file in.ai-rulez/local/
Response: Same as create_rule
read_rule¶
Read a rule file.
Parameters:
name(required, string): Rule filename without .md extensiondomain(optional, string): Domain namelocal(optional, boolean): Read from.ai-rulez/local/working_directory(optional, string): Directory to operate in
delete_rule¶
Delete a rule file.
Parameters:
name(required, string): Rule filename without .md extensiondomain(optional, string): Domain namelocal(optional, boolean): Delete from.ai-rulez/local/
Response:
{
"success": true,
"operation": "delete_rule",
"name": "code-quality",
"domain": "",
"message": "Rule deleted successfully"
}
list_rules¶
List all rules in the root or a specific domain.
Parameters:
domain(optional, string): Domain name (lists root rules if not specified)local(optional, boolean): List.ai-rulez/local/instead of the shared tree
Response:
{
"success": true,
"operation": "list_rules",
"domain": "",
"rules": [
{
"Name": "code-quality",
"Path": ".ai-rulez/rules/code-quality.md",
"Type": "rules",
"Domain": "",
"Priority": "high",
"Targets": ["claude", "cursor"]
}
],
"count": 1
}
Context Tools¶
create_context¶
Create a new context file (documentation/reference material).
Parameters:
name(required, string): Context filename without .md extensioncontent(optional, string): Markdown content with optional YAML frontmatterdomain(optional, string): Domain namepriority(optional, string): Priority leveltargets(optional, array): Target providerslocal(optional, boolean): Create in.ai-rulez/local/(gitignored)
Response: Similar to create_rule
update_context¶
Update an existing context file.
Parameters: Same as create_context, with content as required
Response: Similar to create_rule
With source = "agents" and agents_md_split = true, an item of AGENTS.md (agents, agents-<heading slug>) is
updated in place: content replaces that one section, heading line included, atomically, and content that would add,
drop or rename a ## heading is refused. The unsplit item agents is the whole file and is refused.
read_context¶
Read a context file.
Parameters:
name(required, string): Context filename without .md extensiondomain(optional, string): Domain namelocal(optional, boolean): Read from.ai-rulez/local/working_directory(optional, string): Directory to operate in
For an item of AGENTS.md (source = "agents") the result is that item only: its section with
agents_md_split = true, the whole file for the unsplit item agents. path is AGENTS.md.
delete_context¶
Delete a context file.
Parameters:
name(required, string): Context filename without .md extensiondomain(optional, string): Domain namelocal(optional, boolean): Delete from.ai-rulez/local/
Response: Similar to delete_rule
list_context¶
List all context files in the root or a specific domain.
Parameters:
domain(optional, string): Domain namelocal(optional, boolean): List.ai-rulez/local/instead of the shared tree
Response: Similar to list_rules (items use the capitalised Name, Path, Type, Domain, Priority, Targets keys), with a "context" key instead of "rules"
Skill Tools¶
create_skill¶
Create a new skill file (AI prompt/expert definition).
Parameters:
name(required, string): Skill filename without .md extensioncontent(optional, string): Markdown content with optional YAML frontmatterdomain(optional, string): Domain namepriority(optional, string): Priority leveltargets(optional, array): Target providerslocal(optional, boolean): Create in.ai-rulez/local/(gitignored)description(optional, string): Frontmatter description, used whencontentis emptyscope(optional, string): Write the skill into the repository at<scope>/<harness skills dir>/<name>/SKILL.mdinstead of editing ai-rulez content; needsallow_writeharness(optional, string): Withscope, the enabled preset whose skills directory is writtenallow_write(optional, boolean, default false): Withscope, agree to write a harness file into the repository
Response: Similar to create_rule. With scope it carries path (project-relative), scope, harness and, when ai-rulez.lock exists, lock. See Writing a skill into a scope.
update_skill¶
Update an existing skill file.
Parameters: Same as create_skill, with content as required (scope, harness and allow_write replace the harness's skill file)
Response: Similar to create_rule
read_skill¶
Read a skill file.
Parameters:
name(required, string): Skill filename without .md extensiondomain(optional, string): Domain namelocal(optional, boolean): Read from.ai-rulez/local/working_directory(optional, string): Directory to operate in
delete_skill¶
Delete a skill file.
Parameters:
name(required, string): Skill filename without .md extensiondomain(optional, string): Domain namelocal(optional, boolean): Delete from.ai-rulez/local/
Response: Similar to delete_rule
list_skills¶
List all skill files in the root or a specific domain.
Parameters:
domain(optional, string): Domain namelocal(optional, boolean): List.ai-rulez/local/instead of the shared tree
Response: Similar to list_rules (items use the capitalised Name, Path, Type, Domain, Priority, Targets keys), with a "skills" key instead of "rules"
Check Tools¶
Checks are code-review guidelines (see Checks). They live in the shared tree only: there is no local argument.
| Tool | Arguments |
|---|---|
create_check |
name, content, description, severity (low|medium|high|critical), tools, targets, domain |
read_check |
name, domain |
update_check |
name, domain, and content and/or any of description, severity, tools, targets |
delete_check |
name, domain |
list_checks |
domain |
Names are limited to [A-Za-z0-9._-]. Content without frontmatter gets one built from the structured arguments (marshalled as YAML, severity and targets validated). update_check rejects a call with neither content nor a field; content without frontmatter replaces the body and keeps the existing frontmatter, and fields are set on it.
Include Tools¶
add_include¶
Add a new include source (git URL or local path) to the configuration.
Parameters:
name(required, string): Include name (unique identifier)source(required, string): Git URL (https://github.com/org/repo) or local path (./packages/shared)path(optional, string): Path within git repository where .ai-rulez/ content is locatedref(optional, string): Git reference - branch, tag, or commit hash (git sources only). Defaults to the repository's default branch (HEAD)include(optional, array): Content types to include - rules, context, skills, agents, commandsmerge_strategy(optional, string): Merge strategy - local-override (default), include-override, errorinstall_to(optional, string): Installation target path in .ai-rulez/local(optional, boolean): Add the include to theconfig.local.*overlay instead of the shared config
A source URL that carries a credential in its userinfo (https://user:token@host/...) is refused, like the CLI: it
would be written in clear into config.toml. Use the git credential helper or the AI_RULEZ_GIT_TOKEN environment
variable; a committed credential is reported by validate/scan as AR035.
Response:
{
"success": true,
"operation": "add_include",
"name": "corporate-rules",
"source": "https://github.com/myorg/shared-rules",
"message": "Include added successfully"
}
remove_include¶
Remove an include source from the configuration.
Parameters:
name(required, string): Include name to removelocal(optional, boolean): Remove through the overlay; an include from the shared config is hidden withremove = true
Response:
{
"success": true,
"operation": "remove_include",
"name": "corporate-rules",
"message": "Include removed successfully"
}
list_includes¶
List all include sources in the configuration.
Parameters: (none)
Response:
{
"success": true,
"operation": "list_includes",
"includes": [
{
"Name": "corporate-rules",
"Source": "https://github.com/myorg/shared-rules",
"Type": "git"
}
],
"count": 1
}
Installed Skill Tools¶
install_skill¶
Install a named skill from a git repository or local path.
Parameters:
name(required, string): Skill name (unique identifier)source(required, string): Git URL or local filesystem pathpath(optional, string): Path within repo to skill directory (defaults toskills/<name>)ref(optional, string): Git reference (branch, tag, commit)include,exclude(optional, string arrays): Globs of skill directory names to install, or skip, from a directory of skills;excludewinsname_prefix(optional, string): Prefix added to every installed skill name and output directorytrust(optional, string): Scan level of the installed skills,error(default) orwarnresources(optional, string array): File patterns to bundle with each skill; a leading!removesversion(optional, string): Version range of the source's tags, such as^1.2, instead ofreftag_prefix(optional, string),include_prerelease(optional, boolean),min_release_age(optional, string): tag filter and release age gate forversiondomain(optional, string),profiles(optional, string array): Scope the skills to a domain or to profileslocal_override(optional, string): Directory used instead of the source while it existslocal(optional, boolean): Record the skill in theconfig.local.*overlay instead of the shared config
The arguments are the installed_skills fields of the same names, and skill install has a flag for each.
A source URL that carries a credential in its userinfo (https://user:token@host/...) is refused, like the CLI: it
would be written in clear into config.toml. Use the git credential helper or the AI_RULEZ_GIT_TOKEN environment
variable; a committed credential is reported by validate/scan as AR035.
Response:
{
"success": true,
"operation": "install_skill",
"name": "kreuzberg",
"source": "https://github.com/kreuzberg-dev/kreuzberg",
"message": "Skill installed successfully"
}
uninstall_skill¶
Remove an installed skill from the configuration.
Parameters:
name(required, string): Skill name to removelocal(optional, boolean): Remove through the overlay; a skill from the shared config is hidden withremove = true
Response:
{
"success": true,
"operation": "uninstall_skill",
"name": "kreuzberg",
"message": "Skill uninstalled successfully"
}
list_installed_skills¶
List all installed skills.
Parameters: None
Response:
{
"success": true,
"operation": "list_installed_skills",
"installed_skills": [
{
"Name": "kreuzberg",
"Source": "https://github.com/kreuzberg-dev/kreuzberg",
"Path": "skills/kreuzberg",
"Ref": "",
"Type": "git"
}
],
"count": 1
}
Config Tools¶
read_config¶
Read the current project configuration as structured JSON. The result is the shared view (as if loaded
with --no-local), so a read-modify-write loop never copies local values into the shared config.
Parameters:
working_directory(optional, string): Directory to operate in
Response keys: name, description, presets, profiles, builtins, includes, gitignore,
default_effort, default_effort_by_preset, rules_mode and rules_mode_by_preset. The last four are
always present, empty when unset. When a config.local.* overlay exists, local_overlay reports
{ "path": "...", "keys": ["presets", "mcp_servers.github.env.GITHUB_TOKEN", ...] }: key paths only,
never values.
update_config¶
Update supported project configuration fields.
Parameters:
name(optional, string): Project namedescription(optional, string): Project descriptionbuiltins(optional, array): Builtin names to enablegitignore(optional, boolean): Whether generation updates.gitignoredefault_effort(optional, string): Default reasoning effortdefault_effort_by_preset(optional, object): Per-preset reasoning effort overridesrules_mode(optional, string): Default rules output mode,splitorinline; empty string clears itrules_mode_by_preset(optional, object): Per-preset rules mode overrides. The map replaces the existing one; an entry with value""removes that preset's override, and{}ornullclears them alllocal(optional, boolean): Write the supplied fields to theconfig.local.*overlay instead of the shared config. Clearing a field (empty string or empty map, includingnameanddescription) removes that key from the overlay instead of writing an empty valueworking_directory(optional, string): Directory to operate in
Profile Tools¶
add_profile¶
Create a new profile with a set of domains.
Parameters:
name(required, string): Profile name (unique identifier)domains(required, array): List of domain names to include in the profilelocal(optional, boolean): Define the profile in theconfig.local.*overlay; it may reference local domains
Response:
{
"success": true,
"operation": "add_profile",
"name": "full",
"domains": ["backend", "frontend", "qa"],
"message": "Profile added successfully"
}
remove_profile¶
Remove a profile from the configuration.
Parameters:
name(required, string): Profile name to removelocal(optional, boolean): Remove a profile defined in the overlay; a shared profile cannot be removed locally (error)
Response:
{
"success": true,
"operation": "remove_profile",
"name": "staging",
"message": "Profile removed successfully"
}
set_default_profile¶
Set a profile as the default for generation.
Parameters:
name(required, string): Profile name to set as defaultlocal(optional, boolean): Setdefaultin the overlay instead of the shared config
Response:
{
"success": true,
"operation": "set_default_profile",
"name": "full",
"message": "Default profile set successfully"
}
list_profiles¶
List all profiles in the configuration.
Parameters: (none)
Response:
{
"success": true,
"operation": "list_profiles",
"profiles": [
{
"Name": "full",
"Domains": ["backend", "frontend", "qa"],
"IsDefault": true
},
{
"Name": "backend",
"Domains": ["backend", "qa"],
"IsDefault": false
}
],
"count": 2
}
Agent and Command Tools¶
Agents and commands are flat markdown files whose frontmatter carries their own settings, so these tools take no
priority or targets. They mirror add|remove|list agent|command.
| Tool | Does | Parameters |
|---|---|---|
create_agent, create_command |
Create the file; a template is written when content is empty |
name (required), description, content, domain, local |
read_agent, read_command |
Read the content | name (required), domain, local |
update_agent, update_command |
Replace the content as given, atomically | name and content (required), domain, local |
delete_agent, delete_command |
Delete the file | name (required), domain, local |
list_agents, list_commands |
List the files of the root or a domain | domain, local |
Report Tools¶
Read-only twins of commands. Each returns the document the command prints with --format json, built by the same
library code; none writes, and none uses the network (remote content comes from the lock and the cache). A gate the
command turns into exit 2 (an exceeded budget, a finding at fail_on) is an error result that still carries the
document in structuredContent.
| Tool | Command | Parameters beyond working_directory, config_file, config_dir, no_local |
|---|---|---|
scan_content |
scan |
fail_on, lint_profile. The security checks only: secrets, hidden text, injection phrases, risky shell, unpinned sources. The result is the document of validate_config |
token_report |
tokens |
profile, role, by_role, compare_profiles, tokenizer, budget. One target answers with the report, several with {"schema_version", "items"} |
cost_report |
cost |
profile, target, top, tokenizer, budget, on_demand_budget |
sbom |
sbom |
type (cyclonedx, spdx-json), files, profile, role, include_outputs, no_approvals. The reproducible document, without the machine-local overlay; writing (-o), --check, --verify and the CI gates stay on the command line |
okf_validate |
okf validate |
bundle (required, a directory under the project), fail_on. A git URL is refused |
approvals_status |
approve --list |
all. What needs approval and its status. Approving, revoking and signing are human decisions and stay on the command line |
policy_show |
validate --show-policy |
The policy layers, the merged policy, where each key came from and what the configuration tried to loosen |
list_verifiers |
verifiers list |
The declared verifiers |
list_builtins |
builtins list |
The builtin domains and whether each is auto-included |
Prompts¶
| Prompt | Arguments | Expands to |
|---|---|---|
author-skill |
name, purpose |
Draft a skill, create it, then check it with scan_content, validate_config and cost_report |
add-rule |
name, guideline, priority |
Check for an existing rule, then create or update one and validate |
review-config |
focus |
Run the read-only checks and summarize what to fix, changing nothing |
trim-context |
budget |
Find the biggest always-loaded items and propose edits before applying them |
Resources¶
| URI | Content |
|---|---|
ai-rulez://config |
The parsed settings (read_config) |
ai-rulez://catalog |
The content catalog (catalog); with --serve-skills --discover-skills also skill_discovery and a scopes section, narrowed by ?path=, ?scope= and &recursive=true (see Monorepos) |
context://{scope}/{name} |
With discovery: one nested instruction file, rule or agent, read from the repository on each read |
ai-rulez://{kind}/{name} |
The markdown of one root item; kind is rules, context, skills, checks, agents or commands |
ai-rulez://domains/{domain}/{kind}/{name} |
The same for an item of a domain |
Resources are read-only, confined like the tools to the directory the server was started in, and an unknown URI is a resource-not-found error.
Governance Tools¶
Four read-only tools expose the roles, lock and catalog surface of the CLI. They never write a file and never use
the network: remote includes and installed skills resolve from the local cache only, and one that is not cached is
left out (the result then carries a second text block saying so). Each returns the JSON of the matching CLI command
with --format json, compacted onto one line, so a client can parse either source the same way. They are annotated
read-only and registered on the authoring server only (not on --serve-skills). Mutating lock operations
(lock, update) stay CLI-only.
All four take the common parameters config_file, config_dir, no_local and working_directory
(lock_status always ignores the machine-local overlay, as lock --check does).
| Tool | CLI equivalent | Parameters |
|---|---|---|
list_roles |
roles list --format json |
common only |
resolve_role |
roles resolve <role> --format json |
role (required), limit |
lock_status |
lock --check --format json |
kind, profile, role, targets, include_static, sources |
catalog |
catalog --format json |
kind, role, limit |
Output limits. resolve_role and catalog list at most limit items (default 200, maximum 1000; a smaller
value wins). When the list is cut the document gains "truncated": true and "total_items": <matching items>;
totals and roles still describe every item. list_roles returns one entry per declared role, and
lock_status one change per differing item; neither is capped.
resolve_role¶
What a person holding a role gets: the document of roles resolve, with role.items capped at limit.
| Parameter | Type | Description |
|---|---|---|
role |
string | Role name, as listed by list_roles (required) |
limit |
number | Maximum items returned |
lock_status¶
The comparison of ai-rulez.lock with the authored sources, the generated outputs, the skill sources and the served
skills (lock --check), following schema/lock-diff.schema.json. A project that was never locked
is an error result ("nothing to check", with the hint to run ai-rulez lock), as lock --check exits 1; under
[lock] enforce = true the missing lock is reported as drift instead.
| Parameter | Type | Description |
|---|---|---|
kind |
string | List only the changes of one kind: include, skill, source, served, or content (authored items and generated outputs). in_sync still covers the whole lock |
profile |
string | Profile whose outputs are compared (default: the profile recorded in the lock); also selects the serve view |
role |
string | Compare only this role's outputs and check the skills it serves as a view of their own (lock --role) |
targets |
string | Also check the view that serves this preset's rendering of the skills (lock --targets) |
include_static |
boolean | Also check the view that serves static skills too (lock --include-static) |
sources |
string[] | Also check the view with these extra skill sources (lock --source) |
catalog¶
Every rule, skill, agent, command and context file with owner, version, tokens, digest, roles and the lock status
(see catalog).
| Parameter | Type | Description |
|---|---|---|
kind |
string | Only items of this kind: rule, skill, agent, command, check or context |
role |
string | Only the items this role keeps; roles shrinks to that role |
limit |
number | Maximum items returned |
Common MCP Workflows¶
Creating a Domain with Rules¶
User: "Create a backend domain and add a database standards rule"
MCP Tool Sequence:
1. create_domain(name: "backend", description: "Backend services")
2. create_rule(name: "database-standards", domain: "backend", priority: "high", content: "...")
3. generate_outputs() - regenerate configurations
Adding an External Include¶
User: "Add our corporate rules from GitHub"
MCP Tool Sequence:
1. add_include(name: "corporate", source: "https://github.com/myorg/rules", ref: "main")
2. validate_config() - check if include is valid
3. generate_outputs() - regenerate with included content
Setting Up Team Profiles¶
User: "Create backend and frontend profiles for our team separation"
MCP Tool Sequence:
1. create_domain(name: "backend")
2. create_domain(name: "frontend")
3. add_profile(name: "backend", domains: ["backend"])
4. add_profile(name: "frontend", domains: ["frontend"])
5. set_default_profile(name: "backend")
6. generate_outputs()
Bulk Content Creation¶
User: "Add security rules to the backend domain"
MCP Tool Sequence:
1. create_rule(name: "authentication", domain: "backend", content: "...")
2. create_rule(name: "encryption", domain: "backend", content: "...")
3. create_context(name: "security-architecture", domain: "backend", content: "...")
4. validate_config()
5. generate_outputs()