When @hitheo/mcp starts, it looks for a theo.config.json in the working directory (the IDE workspace root). If it finds one, every relevant tool call is enriched with the values you defined — your IDE agent doesn’t have to remember to pass them.
This is how you give a repo its own persona (“you are a backend engineer for this Express API”), pre-activate the skills that matter for the project, and expose custom tools that only make sense in this codebase.
Most people generate this file with theo init, then tune persona and skills by hand. You rarely need to write it from scratch.
Quick example
{
"persona": "You are a backend engineer assistant for this Express API.",
"skills": ["deep-research", "content-writer"],
"defaultMode": "code",
"temperature": 0.3,
"tools": [
{
"name": "check_stock",
"description": "Look up current stock levels for a SKU",
"input_schema": {
"type": "object",
"properties": { "sku": { "type": "string" } },
"required": ["sku"]
}
}
],
"metadata": { "team": "warehouse-ops" }
}
Drop that at the repo root. The next time any MCP tool fires, those fields are merged into the request.
Schema
| Field | Type | Used by | Description |
|---|
persona | string | theo_complete | Custom system prompt injected as persona.system_prompt. |
skills | string[] | theo_complete | Skill slugs to activate on every completion. |
defaultMode | ChatMode | theo_complete | Fallback mode when the caller didn’t pass one. |
tools | ToolDef[] | theo_complete | Inline tool definitions Theo can call during the agent loop. |
temperature | number | reserved | Default temperature. (Plumbed in the config loader but not yet forwarded by theo_complete.) |
metadata | Record<string, unknown> | reserved | Metadata attached to every request for tracking. (Loaded today; passthrough planned.) |
ToolDef shape (used in tools):
interface ToolDef {
name: string;
description: string;
input_schema?: Record<string, unknown>; // JSON-Schema
}
File precedence and sandbox
@hitheo/mcp loads config in this order, by design:
theo.config.json — always loaded if present. Parsed with JSON.parse; no code runs.
theo.config.js / theo.config.ts — ignored unless you opt in. Loading them would import() arbitrary code from the workspace root, which is unsafe when an IDE spawns the MCP server inside a project you haven’t audited.
To opt back in:
export THEO_ALLOW_JS_CONFIG=1
With that flag set, the loader tries theo.config.js first, then theo.config.ts, before falling back to JSON. If a JS/TS config is present without the flag, the server logs a warning to stderr and skips it.
Only set THEO_ALLOW_JS_CONFIG=1 in directories you authored or trust. A malicious theo.config.js in any repo you open will execute as your user the moment the IDE launches the MCP server.
Even with the flag set, theo.config.json remains the recommended format. The TypeScript helper defineConfig from @hitheo/sdk can emit a typed object you then JSON.stringify into the JSON file if you want autocompletion in your editor.
Merge semantics
When the MCP server handles a tool call, the project config is merged with the arguments the IDE agent passes in. The rules:
| Field | Merge rule |
|---|
persona | If config has persona, it becomes persona.system_prompt. Caller arg overrides if present. |
skills | Concatenated: [...config.skills, ...args.skills]. Empty result → field omitted. |
defaultMode | Only used if the caller didn’t pass mode. Caller args win. |
tools | Forwarded as-is when no caller args; not currently overridden by per-call tool lists. |
temperature, metadata | Loaded by the config parser; not yet forwarded by theo_complete. |
In practice this means:
- A repo persona is always applied.
- Skills are additive — the agent can request more skills on top of the repo’s defaults, but cannot subtract from them in a single call.
- Modes follow “explicit wins”: if the agent passes
mode: "research", that beats defaultMode: "code".
Where theo init puts it
When you run theo init, the CLI writes a starter config based on your folder name:
{
"persona": "You are an AI assistant for the <project-name> project.",
"skills": [],
"defaultMode": "auto"
}
You can edit it freely afterward; the MCP server will pick up changes the next time it starts (i.e. the next IDE restart).
Updating without restarts
The config is read once at server startup, so changes take effect on the next MCP server boot (which usually means the next time your IDE reopens the workspace). If you need to iterate on a persona quickly, edit .theo/RULES.md instead — it influences how the IDE’s agent thinks about the tools, and most agents reload it automatically.
- Install — how
theo init writes the config file in the first place.
- SDK
defineConfig — typed helper for projects that prefer TS authoring.
- Skills overview — what to put in
skills.
- Security — full rationale for the JSON-only default.