Skills, MCP & Tools
Skill manifests, cryptographic signing, MCP transports, tool classification, the Hub, and how to extend Kazma with new tools — all source-referenced.
1. Concepts
Section titled “1. Concepts”| Term | Meaning |
|---|---|
| Tool | A function the supervisor can call (file ops, shell, memory, web, …). Registered in ToolRegistry. |
| Skill | A packaged, optionally-signed Python entry point + manifest that registers one or more tools. Lives under kazma-skills/manifests/ or the Hub registry. |
| MCP server | An external Model Context Protocol server (stdio, SSE, or Streamable HTTP) whose tools are discovered at runtime and proxied into the agent. |
| Hub | The skill registry/marketplace (kazma hub …) with certification and signing. |
2. The ToolRegistry
Section titled “2. The ToolRegistry”kazma-core/kazma_core/agent/tool_registry.py is the registry the supervisor consults each turn. Key points:
execute(tool_name, arguments)(tool_registry.py:335) — the single execution path. It:- Pops
_hitl_approvedfrom args (line 349) — the double-gate flag. - For danger tools, calls
await safety.check(...)unless already approved (lines 384-417). - Fail-closed: any exception in the safety check returns
is_error=True“blocked — SafetyMiddleware unavailable” (lines 411-417).
- Pops
- Built-in tools include
memory_search,memory_store, filesystem/shell tools, and the web research set (web_search,read_url,read_url_to_file,crawl_site, chunk/digest helpers) registered at startup. - Memory tools route through the V2 cognitive engine:
memory_search→recall()(bi-temporal beliefs + PPR);memory_store→mutate_belief()+mirror_episode(). The legacyset_vector_memory(...)singleton was removed with the V1 stack. - Native skills (e.g.
advanced-web-crawler) auto-load viaNativeSkillLoader— distinct from installable Agent Skills.
Web research playbooks: Web research. Full list: Tools catalog.
See Security & Safety for the danger-tool classification that governs execution.
3. Skills
Section titled “3. Skills”3.1 Manifest location & discovery
Section titled “3.1 Manifest location & discovery”- Config:
skills.path: kazma-skills/manifests/,skills.auto_discover: true(kazma.yaml:55-57). - On startup, the loader scans the path and loads each
skill_manifest.yaml.
3.2 Manifest shape
Section titled “3.2 Manifest shape”A skill manifest declares the entry point, capabilities, and (when signed) integrity fields:
name: my-skillversion: 1.0.0description: "Example skill"entry_point: my_skill.py # Python file implementing the tool(s)capabilities: [mcp, file_read]author: your-org# Added by `kazma hub sign`:checksum: <sha256 of entry_point file>signature: <HMAC-SHA256 of the checksum>3.3 Cryptographic signing (HMAC-SHA256) — VERIFIED
Section titled “3.3 Cryptographic signing (HMAC-SHA256) — VERIFIED”Skill signing is real, fail-closed, and lives in the Hub subsystem.
Signing — kazma hub sign <path> (kazma_core/hub/cli.py:703-770):
# Read the entry-point .py fileraw = py_file.read_bytes()actual_hash = hashlib.sha256(raw).hexdigest()
# HMAC-SHA256 over the checksum, keyed by KAZMA_SECRETsigning_secret = secret or os.environ["KAZMA_SECRET"]sig = hmac.new( signing_secret.encode(), actual_hash.encode(), hashlib.sha256,).hexdigest()
# Write both into skill_manifest.yamlmanifest["checksum"] = actual_hashmanifest["signature"] = sigRequires KAZMA_SECRET (env or --secret); exits if unset (cli.py:728-733).
Verification on load (fail-closed) — kazma_core/hub/loader.py:206-266 (SkillLoader._load_module_from_file):
| Condition | Behavior |
|---|---|
checksum present, mismatch | SkillLoadError — “may have been tampered with” (lines 227-232). |
signature present, no KAZMA_SECRET | SkillLoadError (lines 237-241). |
signature present, HMAC mismatch | SkillLoadError (lines 242-250). |
No checksum at all | Warning logged; loads unsigned (backward compat, lines 251-257). |
| Any verification error | Fatal — not swallowed (lines 259-266). |
Verification uses hmac.compare_digest (constant-time) for both checksum and signature.
3.4 Adding a custom skill (minimal example)
Section titled “3.4 Adding a custom skill (minimal example)”- Create
kazma-skills/manifests/my-skill/skill_manifest.yaml+my_skill.py. - Implement the tool function(s) your skill exposes.
- (Recommended) Sign it:
export KAZMA_SECRET="$(openssl rand -hex 32)"kazma hub sign kazma-skills/manifests/my-skillkazma hub validate kazma-skills/manifests/my-skill- Restart the server (or rely on
skills.auto_discover). The loader verifies the signature withKAZMA_SECRETand refuses to load on mismatch.
3.5 Agent Skills (SKILL.md)
Section titled “3.5 Agent Skills (SKILL.md)”Agent Skills are instructions, not code: a SKILL.md in the agentskills.io
format that the model reads when a task matches the skill’s description.
Kazma reads them from its bundled folder, from the skill folders in your home
(~/.agents/skills, shared with other agentskills.io clients, and
.claude/skills, .cursor/skills, .kazma/agent-skills) and from the
project’s own folders. Installing a skill signs it with this install’s key
(HMAC-SHA256 of the file, keyed by KAZMA_SECRET, stored in
.kazma-install.json beside it).
Activation checks that signature. The skill list the model sees, the
list_agent_skills tool, kazma agent-skills list and the Skills page all
show the result: verified, unsigned (loads with a warning) or refused. A
refused skill is never offered to the model. If you trust it, sign it again
with this install’s key: see
CLI Reference → kazma agent-skills.
4. The Hub (kazma hub)
Section titled “4. The Hub (kazma hub)”The Hub is a Click-based CLI for the skill registry/marketplace (kazma_core/hub/cli.py:104). See CLI Reference → hub for the full subcommand list.
4.1 The hub
Section titled “4.1 The hub”The hub REST API that once lived in this repository (kazma_core/hub/api.py) was removed on 2026-09-30: nothing ran it (audit AUD-013). kazma hub talks to a remote hub at KAZMA_HUB_URL.
4.2 Certification
Section titled “4.2 Certification”kazma hub certified— list certified skills.kazma hub badge <skill_ref>— show a certification badge.kazma hub check-certification <path>— check a skill against certification criteria.
“Trust tiers” do NOT exist as a cryptographic/security feature. The only “trust” references in the codebase are (a) the hub’s plain
certified: boolflag and (b) an MCP server’strustsetting:trustedskips the approval card for that server’s tools (refused in production unlessKAZMA_MCP_TRUSTED_IN_PROD=1); anything else, the defaultapproval_requiredincluded, keeps it. This is explicitly flagged because older docs implied a tiered trust model.
4.3 Finding & installing skills (consumer workflow)
Section titled “4.3 Finding & installing skills (consumer workflow)”Search the registry by text, capability, tag, or author (cli.py:171-234):
kazma hub search "weather"kazma hub search --capabilities "image_analysis,data_processing"kazma hub search --tags "utility,beginner-friendly"kazma hub search --author "kazma-team"Browse installed skills and inspect one in detail (cli.py:208-303):
kazma hub listkazma hub info author/skill-nameInstall is not implemented. kazma hub install, the
skill-installation wizard (kazma wizard) and the Skills page for a
kazma-hub:// id say so and install nothing: the hub registry cannot fetch a
skill or give the agent its tools. Install an Agent Skill instead.
5. MCP (Model Context Protocol)
Section titled “5. MCP (Model Context Protocol)”kazma-core/kazma_core/mcp/manager.py discovers and proxies external MCP servers.
5.1 Transports
Section titled “5.1 Transports”| Transport | Config | Auth |
|---|---|---|
stdio | command: [argv] — subprocess spawn. | None. The subprocess gets the allowlisted basics plus its own env (audit H-4), never Kazma’s secrets. |
sse | url + optional auth field. | Yes — AsyncMCPManager._connect_sse supports a first-class auth config injecting Authorization: Bearer <token> or a custom header. |
streamable_http (alias http) | url + optional auth field. MCP 2025-03-26 spec — single POST endpoint with SSE response streaming + Mcp-Session-Id resumption. | Yes — same auth field as SSE. |
There is no authentication inside
mcp/manager.pyfor the stdio transport. Run stdio MCP servers you trust, in a sandboxed environment.
5.2 Tool classification (classify_mcp_tool)
Section titled “5.2 Tool classification (classify_mcp_tool)”MCP tools are runtime-discovered, so they can’t be on a static danger list. classify_mcp_tool() (manager.py:71-88) classifies by name-pattern substring matching:
| Category | Matched keywords |
|---|---|
danger | write, delete, remove, exec, run, shell, bash, command, kill, terminate, install, deploy, upload, download, fetch, request, post, put, patch |
safe | read, list, search, get, info, status, check, describe, query, count, exists, help |
unknown | (neither set matched) |
The gate at UnifiedToolExecutor.execute() (manager.py:725-727) treats both danger and unknown as requiring approval — i.e. unknown defaults to danger (fail-safe).
5.3 Configuring an MCP server
Section titled “5.3 Configuring an MCP server”mcp: servers: - name: filesystem transport: stdio trust: approval_required # `trusted` skips the approval card for this server's tools command: - npx - '-y' - '@modelcontextprotocol/server-filesystem' - kazma-data/workspace - name: secured-api transport: sse url: https://mcp.example.com/sse auth: type: bearer token: ${MCP_API_TOKEN} # supply via env - name: remote-mcp transport: streamable_http # MCP 2025-03-26 spec url: https://mcp.example.com/mcp trust: approval_required ide_server: enabled: true root: . max_file_size: 10485765.4 Adding MCP servers via the Web UI
Section titled “5.4 Adding MCP servers via the Web UI”The /mcp page provides a visual Add Server modal that replaces manual YAML editing. It has two modes:
Quick add (preset) — a dropdown of MCP servers grouped by category, from kazma_skills/certified_servers.yaml: 13 servers, each a package published by the MCP project or by the vendor of the service it connects to (Filesystem, Git, Time, Memory, Sequential Thinking, Playwright, Firecrawl, Brave Search, Context7, Notion, Stripe, Heroku, AWS Documentation). Pick one and the form fills the name, transport, command and the environment variables to fill in; you type the API key, and Kazma keeps it in the vault. Every preset’s package is checked against npm and PyPI (python scripts/verify_mcp_catalog.py, recorded in tests/fixtures/mcp_catalog_registry.json and held by tests/test_mcp_catalog.py). Until 2026-09-30 the list offered 81 “certified” servers, 78 of which named packages that never existed. Left out on purpose: a raw URL fetcher (it would bypass the egress guard read_url enforces) and GitHub (Kazma has its own GitHub tools).
Secrets stay in the vault. A key an MCP server needs (an env entry named like a credential, an auth value or token, a header, a --api-key=... flag in the command, a password in a URL) is stored in Kazma’s vault; kazma.yaml and the settings database keep a vault:// pointer, the MCP pages and APIs show ****, and the server receives the real value when it starts. Keys saved before 2026-09-30 move to the vault the next time Kazma connects its MCP servers (kazma_core/mcp/secrets.py).
Custom — the same raw command form, with three safety nets:
- shlex-style parsing — quoted args with spaces survive (
npx -y foo "path/with spaces"). - Auto-rewrite — common install-command mistakes are detected and fixed:
npm install -g firecrawl-mcp→npx -y firecrawl-mcppip install mcp-server→python -m mcp_serverpipx install some-mcp→pipx run some-mcpA blue notice explains the rewrite so you know what changed.
- Validate-before-save — before persisting, the server connection is tested via
/api/mcp/test-config. If it fails (spawn error, 0 tools, bad key), the error + subprocess stderr is shown inline and the server is not saved. No more “0 tools, no idea why.”
Servers added, changed or removed through the pages are kept in Kazma’s settings and survive restarts. Kazma never writes them into kazma.yaml: it is a tracked file in your install’s folder, and a change there makes a later git pull refuse to update it. kazma.yaml is the starting list and the place to add a server by hand; removing one of its servers from a page keeps it out of the list without editing the file (the setting mcp.removed_servers), and adding it again brings it back. The one exception: a key typed into kazma.yaml is moved into the vault, and the file keeps a vault:// pointer in its place. Until 2026-10-02 every change from a page rewrote kazma.yaml.
The filesystem server always works on the active workspace: Kazma passes the workspace folder when it starts the server, and again after Switch Repo. It is stored with the ${KAZMA_ACTIVE_WORKSPACE} placeholder and workspace_bound: true, and an old saved copy with a fixed sandbox path is read and saved back in that form. The card shows the folder the server runs on.
Each server’s card has Start or Stop, Test (connects once and reports how many tools it found), Show tools, and Remove. Remove asks first, because it also stops the server and forgets its settings and any keys saved for it. Until 2026-10-02, Start, Stop, Test and OAuth login did nothing when clicked: they called an Alpine helper ($parent) that Alpine 3 does not have.
Settings → MCP lists the same servers with an on/off switch. The switch decides whether a server starts with Kazma, and applies at once: off stops the server and takes its tools from the agent, on starts it. If a server cannot start, the switch stays on, the message says why, and the server is tried again at the next start. The dot beside the name is the server’s state now (running or stopped). Test is the MCP page’s Test. /config tools toggle <name> in a chat app does the same as the switch. Start and Stop on the MCP page only start or stop the server for now; the switch is what decides the next start. Until 2026-10-02 the switch only saved the setting, could not show a server as off, and the dot was always red.
A server that fails to start is retried. Kazma remembers why each server failed until it connects or someone stops it. A background sweep retries the failed ones with a growing delay, and the MCP page shows each one’s reason with any key masked. Until 2026-10-02 only the last server’s failure was remembered, so a server that failed early at startup was never retried.
5.5 Tool name namespacing
Section titled “5.5 Tool name namespacing”MCP tool names are namespaced as mcp__<server>__<tool> before being sent to the LLM. This prevents collisions between MCP servers and built-in tools (e.g. the Playwright MCP’s browser_click vs the browser_automation skill’s browser_click). Without namespacing, providers that require unique tool names (DeepSeek, OpenAI) reject the entire request with 400 Tool names must be unique, causing Kazma to strip ALL tools for the turn — the root cause of the “agent stopped talking” bug.
The namespace prefix is transparently stripped when routing the tool call back to the originating MCP server (execute_mcp_tool handles both namespaced and raw forms).
5.6 IDE server
Section titled “5.6 IDE server”The in-process IDE/file MCP server (mcp.ide_server) exposes file read/write over the workspace root with a 1 MB per-file cap (max_file_size). Per audit reports, it is expected to require _secret matching KAZMA_SECRET via hmac.compare_digest; verify against mcp_server.py before relying on it.
5.7 Resources, prompts, sampling, roots
Section titled “5.7 Resources, prompts, sampling, roots”Kazma is an MCP client (ACP already hosts Kazma in editors). As of 2026-08-25 the initialize handshake advertises more than tools:
| Surface | Behaviour |
|---|---|
| Resources | mcp_list_resources / mcp_read_resource. Read body is wrapped in format_untrusted_block(source="mcp_resource:…") — data, not instructions. |
| Prompts | mcp_list_prompts / mcp_get_prompt. Returned as user-visible text. Not injected as system. |
| Sampling | sampling/createMessage is off unless KAZMA_MCP_SAMPLING=1. Then a real HITL card (kind=mcp_sampling) waits in-place (does not unwind MCP stdio). Approve via the existing Once button (POST /api/approve/{thread_id}). The LLM call has no tools. Timeout KAZMA_MCP_SAMPLING_TIMEOUT (default 60s). |
| Elicitation | Same: no auto-fill of server-driven forms. |
| Roots | roots/list returns the active workspace only (same binding ladder as file tools). |
We do not ship an MCP server for other IDEs to host our tools (D7).
6. Secret Vault (encrypted credential storage)
Section titled “6. Secret Vault (encrypted credential storage)”The Secret Vault is a native skill that provides encrypted-at-rest storage for API keys, tokens, passwords, and other secrets. It uses AES-256-GCM encryption with a PBKDF2-derived key.
6.1 Architecture
Section titled “6.1 Architecture”| Component | File | Role |
|---|---|---|
| Vault engine | kazma-core/kazma_core/security/vault.py | AES-256-GCM encrypt/decrypt, PBKDF2 key derivation, SQLite storage |
| Skill manifest | kazma-skills/kazma_skills/native/secret_vault/skill_manifest.yaml | Native skill declaration |
| LLM tools | kazma-skills/kazma_skills/native/secret_vault/tools.py | vault_store, vault_retrieve, vault_list, vault_delete |
6.2 Security model
Section titled “6.2 Security model”| Aspect | Implementation |
|---|---|
| Master key | KAZMA_VAULT_KEY environment variable. If unset, vault is disabled (all tools return graceful error). |
| Key derivation | PBKDF2-HMAC-SHA256, 600,000 iterations, per-installation 32-byte random salt. |
| Encryption | AES-256-GCM with 12-byte random nonce per record. Auth tag built into GCM. |
| Storage | Separate encrypted SQLite DB at kazma-data/vault.db (NOT the plaintext settings.db). |
| Tenant isolation | Uses get_current_tenant_id() ContextVar. Tenant-specific secrets + global fallback. |
| HITL gating | vault_retrieve and vault_delete require human approval before execution. |
6.3 Tools
Section titled “6.3 Tools”| Tool | HITL? | Description |
|---|---|---|
vault_store(name, value, category, metadata) | No | Store (or update) a secret. Encrypted before persistence. |
vault_retrieve(name) | Yes | Retrieve and decrypt a secret. Returns [SECRET — handle with care]\n<value>. |
vault_list() | No | List all secret names + categories (values NOT shown). |
vault_delete(name) | Yes | Permanently delete a secret. |
6.4 Enabling the vault
Section titled “6.4 Enabling the vault”KAZMA_VAULT_KEY=your-vault-passphraseAny string works — it’s a passphrase, not a pre-derived key. The PBKDF2 derivation converts it into the 256-bit AES key.
6.5 Security note on retrieval
Section titled “6.5 Security note on retrieval”When a secret is retrieved (after HITL approval), the decrypted value enters the conversation context as a tool result. This means it will appear in:
- The chat history (message stream)
- The LangGraph checkpointer (
checkpoints.db) if checkpointing is active - Any enabled tracing (Langfuse)
This is by design — the LLM needs the value to make authenticated API calls. The HITL gate ensures a human approves each retrieval. Only retrieve secrets when actually needed.
7. Delegation (agent-to-agent) — removed
Section titled “7. Delegation (agent-to-agent) — removed”Status: there is no delegation package. Its code (an Ed25519 + AES-GCM protocol between agents) was never wired into the agent or the swarm; it sat in
archive/delegation/until 2026-07-28, when it was deleted. Multi-worker orchestration is SwarmEngine (kazma_core/swarm/).
Nothing signs or encrypts work handed between agents; do not configure a deployment as if it did. What protects swarm work is in Security & Safety.
8. Adding a new tool (extension point)
Section titled “8. Adding a new tool (extension point)”The simplest extension is a registered tool function. Minimal pattern:
from kazma_core.agent.tool_registry import register_tool
@register_tool( name="weather_lookup", description="Look up current weather for a city.", danger=False, # set True if it should trigger HITL)async def weather_lookup(city: str) -> str: # ... your implementation ... return f"Weather in {city}: sunny, 25C"Register it during startup (or via a skill’s entry point). The supervisor will expose it to the LLM as a callable tool. If danger=True, execution flows through the HITL gate (see Security & Safety).
Documentation Audit Notes
Section titled “Documentation Audit Notes”- HMAC skill signing is real and fail-closed — contrary to what one might assume from the mix of subsystems, the loader genuinely refuses tampered/unsigned-by-required skills.
- “Trust tiers” are NOT a code feature. Documented explicitly to counter any implication of a tiered trust model. Only the hub’s boolean
certifiedflag and an MCP server’strust: trusted(which skips the approval card for its tools) exist. - MCP stdio transport has no auth. SSE and Streamable HTTP support bearer/custom-header auth. A stdio server starts with the allowlisted basics (PATH, the temp and home folders, the locale, proxy settings) plus its own
env, never Kazma’s secrets (audit H-4; the Test button’s client joined it on 2026-09-30;KAZMA_MCP_INHERIT_ENV=1passes the whole environment). classify_mcp_toolunknown → danger is the safe default and should be preserved.

