Skip to content

Recipe: Skills Auto-Injection

Goal

Automatically detect a skills MCP server and inject a skills catalog into the system prompt so the LLM knows which skills are available without an explicit tool call.

Prerequisites

  • A skills MCP server that exposes list_skills and load_skill tools — doc-processing-std-tools 0.2 or later (0.6.0 at the time of writing; its documentation is at https://doc-processing-std-tools.vercel.app/, the server at servers/skills and its verified toolkit versions at compatibility), or any server with the same contract. The server's name does not matter.
  • DPTTOOLS_SKILLS_DIR set where the server runs, pointing at your skills directory: the server reads it, not the toolkit. For an in-process server that is this process's environment (an .env file works); for an mcp_command / mcp_script server it goes in the config's env, because a subprocess does not inherit this process's variables (see Skills directory)

Basic usage

from doc_processing_toolkit import Agent

agent = Agent(
    model="claude-sonnet-5",
    instructions="You are a helpful assistant.",
    tools=[
        {
            "type": "mcp_script",
            "path": "skills_server.py",
            "name": "skills-tools",
            "env": {"DPTTOOLS_SKILLS_DIR": "/path/to/.agent_skills"},
        }
    ],
)

result = await agent.run("What skills do you have available?")
print(result.output)

Skills auto-injection is enabled by default (auto_inject_skills=True). The agent detects the skills server, calls list_skills, and appends a catalog to the system prompt automatically. Every way of attaching the server works — an in-process FastMCP object, an mcp_command / mcp_script config, a pre-built session — since 0.5.3; before it, only a pre-built session whose name contained skill did (see the version note).

Expected system prompt output

When skills are detected, the system prompt is augmented with:

## Available Agent Skills

The following skills provide specialized instructions for specific tasks. When a
task matches a skill's description, load its full instructions before proceeding.

How to load a skill:
- Preferred: call the `load_skill` tool with the skill's name. The tool result
  is wrapped in <skill_content>...</skill_content> tags and includes a list of
  bundled resources (under <skill_resources>) and the skill's directory path.
  Use the directory path to resolve any relative references inside the skill.
- Alternative: if you have a file-read tool, you may directly read the SKILL.md
  at the listed `path`. Relative references inside the SKILL.md resolve
  against the parent directory of that path.

<available_skills>
  <skill name="my-data-analysis" path="/path/to/.agent_skills/my-data-analysis/SKILL.md">Analyzes CSV/Excel data and produces summary reports with charts.</skill>
  <skill name="code-review" path="/path/to/.agent_skills/code-review/SKILL.md">Reviews code for bugs, security issues, and style violations.</skill>
</available_skills>

The LLM sees this catalog and can call load_skill to load full instructions for any skill it wants to use. The path attribute lets the model resolve relative resource references inside the SKILL.md without an extra tool call; it is supplied by doc-processing-std-tools 0.2+, which is the floor this feature is written against (exercised against 0.6.0 — see Skills). An older payload simply omits it.

Token-saving optimization

When the skills directory is empty, three "activation" tools (load_skill, load_skill_resource, execute_skill_script) are hidden from the LLM-visible tool list to save tokens per turn. Two tools intentionally stay advertised so the model can bootstrap from an empty state: list_skills (re-poll for skills added mid-session) and create_skill (create the first skill in the directory). The check is re-evaluated on every run().

Disabling auto-injection

agent = Agent(
    model="claude-sonnet-5",
    instructions="Discover skills yourself.",
    auto_inject_skills=False,
    tools=[
        {
            "type": "mcp_script",
            "path": "skills_server.py",
            "name": "skills-tools",
            "env": {"DPTTOOLS_SKILLS_DIR": "/path/to/.agent_skills"},
        }
    ],
)
from doc_processing_toolkit import Agent

agent = (
    Agent.builder()
    .model("claude-sonnet-5")
    .auto_inject_skills(False)
    .build()
)
model: claude-sonnet-5
auto_inject_skills: false
tools:
  - type: mcp_script
    path: skills_server.py
    name: skills-tools
    env:
      DPTTOOLS_SKILLS_DIR: /path/to/.agent_skills

When disabled, the LLM must discover skills by explicitly calling list_skills.

Session usage

Skills metadata is fetched on every run() call, so skills added mid-session are picked up automatically:

async with agent.session() as session:
    # Skills catalog injected on first run
    await session.run("What skills are available?")

    # If new skills are added to the skills directory between runs,
    # they appear in the next run's system prompt
    await session.run("Are there any new skills?")

Troubleshooting

Skills injection silently does nothing (no error) when any of these conditions apply:

Condition Reason
auto_inject_skills=False Feature explicitly disabled
No skills server detected No server exposes both list_skills and load_skill (the server's name does not matter since 0.5.3)
The instructions already contain <available_skills> The app supplies its own catalog; for that agent the toolkit calls nothing, injects nothing and hides nothing, and logs one INFO line per run
The server's skills directory is unset, missing or empty DPTTOOLS_SKILLS_DIR is read by the server, in its own process — for a subprocess server it must be passed in the config's env. std-tools then returns an empty catalog with a note (see its configuration page), which is the next row
list_skills returns empty array No skills available — load_skill, load_skill_resource, and execute_skill_script are hidden from the LLM to save tokens; list_skills and create_skill stay advertised so the model can re-poll or bootstrap the directory
list_skills call fails, or returns something other than the JSON the concept page shows Agent logs one WARNING naming the server and continues normally; the activation tools stay advertised (since 0.5.3 — a failed or malformed reply is not an empty catalog)
list_skills returns a diagnostics array Each entry is logged as WARNING skills: ... — diagnostics never enter the system prompt

Tip

Enable debug logging to see detection and fetch details when troubleshooting skills injection issues.