Tools & Skills¶
Cognitia provides two mechanisms for giving agents capabilities: tools (code-defined) and skills (declarative MCP).
@tool Decorator¶
Define tools as async Python functions with automatic JSON Schema inference:
from cognitia import tool
@tool(name="lookup_user", description="Look up user by email")
async def lookup_user(email: str) -> str:
# Auto-inferred schema: {"type": "object", "properties": {"email": {"type": "string"}}, "required": ["email"]}
user = await db.find_by_email(email)
return f"Found: {user.name}" if user else "Not found"
Type mapping¶
| Python | JSON Schema |
|---|---|
str | "string" |
int | "integer" |
float | "number" |
bool | "boolean" |
T \| None = None | not in required |
Using tools with Agent¶
from cognitia import Agent, AgentConfig
agent = Agent(AgentConfig(
runtime="thin",
tools=(lookup_user, another_tool),
))
Handler contract¶
The @tool decorator handles the conversion between your natural Python function signature and the MCP protocol format automatically:
- Your handler:
async def fn(a: int, b: str) -> str - SDK expects:
handler({"a": 1, "b": "hello"}) -> {"content": [{"type": "text", "text": "result"}]}
Cognitia's _adapt_handler bridges this gap transparently. If your handler raises an exception, it's caught and returned as an error in MCP format.
MCP Skills¶
Skills are declarative MCP server connections with tool allowlists and agent instructions. Cognitia supports two formats:
Format 1: Cognitia Native (skill.yaml + INSTRUCTION.md)¶
# skills/finuslugi/skill.yaml
id: finuslugi
title: "Banking Products API"
description: "Search bank deposits and credits"
mcp:
servers:
- id: finuslugi-server
transport: url
url: "https://api.example.com/mcp"
tools:
include:
- mcp__finuslugi__get_bank_deposits
- mcp__finuslugi__get_bank_credits
when:
intents: [deposits, credits]
# skills/finuslugi/INSTRUCTION.md
Use `get_bank_deposits` to search for deposit products.
Always specify amount and term in months.
YAML fields:
| Field | Required | Description |
|---|---|---|
id | no | Skill ID (defaults to directory name) |
title | no | Display name (defaults to id) |
description | no | Short description for discovery |
mcp.servers | no | MCP server connections |
tools.include | no | Tool allowlist |
local_tools | no | Local tool IDs |
when.intents | no | Keywords for role-based activation |
instruction | no | Custom path to instruction file |
Format 2: Claude Code Compatible (SKILL.md)¶
Single file with YAML frontmatter — compatible with Claude Code skills:
# skills/my-skill/SKILL.md
---
name: my-skill
description: "Short description for matching"
allowed-tools:
- Bash
- Read
- Write
---
# My Skill Instructions
Use these tools to accomplish the task...
Frontmatter fields:
| Field | Maps to | Description |
|---|---|---|
name | skill_id | Skill identifier (defaults to dir name) |
description | description | Short description |
allowed-tools | tool_include | Tool allowlist |
mcp-servers | mcp_servers | MCP servers (Cognitia extension) |
intents | intents | Activation keywords (Cognitia extension) |
local-tools | local_tools | Local tool IDs (Cognitia extension) |
Priority: When both skill.yaml and SKILL.md exist in the same directory, skill.yaml takes precedence.
Loading skills¶
from cognitia.skills import SkillRegistry
from cognitia.skills.loader import YamlSkillLoader
loader = YamlSkillLoader("./skills")
skills = loader.load_all() # Loads both skill.yaml and SKILL.md formats
registry = SkillRegistry(skills)
# Get MCP servers for active skills
servers = registry.get_mcp_servers_for_skills(["finuslugi"])
# Get tool allowlist
tools = registry.get_tool_allowlist(["finuslugi"])
# -> {"mcp__finuslugi__get_bank_deposits", "mcp__finuslugi__get_bank_credits"}
Skill transports¶
| Transport | Config | Use Case |
|---|---|---|
url | url: "https://..." | Remote MCP server (SSE) |
stdio | command: "python server.py" | Local subprocess |
sse | url: "https://..." | Server-Sent Events |
Tool Policy¶
Default-deny policy controls which tools agents can use:
from cognitia.policy import DefaultToolPolicy
policy = DefaultToolPolicy()
# Check if a tool is allowed
result = policy.can_use_tool(
tool_name="mcp__finuslugi__get_deposits",
input_data={"amount": 500000},
state=policy_state, # contains active_skill_ids, allowed_local_tools
)
Always denied tools¶
These tools are blocked regardless of configuration: Bash, Read, Write, Edit, MultiEdit, Glob, Grep, LS, TodoRead, TodoWrite, WebFetch, WebSearch.
Allow rules¶
A tool is allowed if: 1. It matches an active skill's tool allowlist (mcp__<skill>__<tool>) 2. It's in the allowed_local_tools set 3. It's in the allowed_system_tools set (for web tools etc.)
Tool ID Codec¶
MCP tools use a namespaced format: mcp__<server>__<tool>.
from cognitia.policy import DefaultToolIdCodec
codec = DefaultToolIdCodec()
codec.encode("finuslugi", "get_deposits") # "mcp__finuslugi__get_deposits"
codec.extract_server("mcp__finuslugi__get_deposits") # "finuslugi"
codec.matches("mcp__finuslugi__get_deposits", "finuslugi") # True
Role-Skill Mapping¶
Map roles to their allowed skills and local tools: