Agent Skills -- Claude
Take a look at hydrotrek-ssc-suite/openswmm.mcp/src/openswmm_mcp/skills implementation. Is this the gold standard way of implementing agent skills? Are there any alternative skill designs that could have been used in the same environment?
We are implementing our own MCP server under hydrotrek-dwd-suite/dwd-mcp-fastmcp that will serve as a kind of parallel to the openswmm_mcp implementation. The use cases are similar since our DWD stack is built on the EPANET engine which is very similar to the SWMM engine. In our implementation, however, we are API based with cross-network API calls instead of in-memory and cross-process operations that you would see in the openswmm_mcp design.
For our Fast MCP server, would our skills be designed the same way openswmm_mcp designs theirs, or is there a better design fit for Fast MCP?
The short answer: the openswmm_mcp skills are a good, idiomatic version of the common workaround. They are not a gold standard, because there isn't one yet: MCP has no "skill" primitive. For your DWD server, keep the same file format but deliver it differently. Your production agent is the MAF agent in dwd-api-fastapi, and it talks to MCP through tools, so DWD skills should be exposed as tools, not mainly as resources and prompts.
What openswmm_mcp does
- File format: each skill is a folder containing
SKILL.md(frontmatternameanddescription, then a workflow body), with optionalreferences/andscripts/folders. This is Anthropic's Agent Skills format, which is the closest thing to a standard. - Discovery:
skills/__init__.pyscans*/SKILL.mdonce at import time. - Exposure: a resource index (
swmm://skills), one resource per skill (swmm://skills/{name}), and ause_skillprompt. - Nudging: the server's
instructionstell the model to check the index before improvising, and route by intent tocapacity-assessment,calibrate-modelandoperational-optimization. - Claude Code / Cowork:
docs/developer/SKILLS.mdshows the same folder bundled as a plugin, so those clients auto-discover the skills natively.
What it does well:
- One source of truth that ships with the server.
- The
descriptionfields are strong triggers, with "use when" and "do NOT use when" keywords. - Each workflow step names a specific tool and has a "→ verify" checkpoint.
- Guardrails are stated explicitly.
Where it falls short
- Reference files can't be reached over MCP. The
operational-optimizationskill tells the model to usereferences/market_config.schema.json,control_loop.pyand the dashboard template, but only theSKILL.mdbody is served:
@skills_mcp.resource("swmm://skills/{skill_name}")
async def get_skill(skill_name: str) -> str:
"""Return the full ``SKILL.md`` body for *skill_name*."""
skill = _SKILLS.get(skill_name)
if skill is None:
available = ", ".join(_SKILLS) or "(none)"
raise ToolError(f"Unknown skill '{skill_name}'. Available: {available}")
return skill["body"]
Only the Claude Code plugin path can open those files. An MCP-only client cannot load the third tier of the skill (its reference files).
Resources and prompts don't belong to the model. In MCP, the client application decides when resources are read, and the user chooses prompts. Many clients never give the model a way to "read
swmm://skills". The server instructions ask the model to do something it may not be able to do. Tools are the only primitive the model reliably calls on its own.Nothing catches drift. No test in
openswmm.mcp/testscovers skills. A skill can name a tool that has been renamed or removed, and nothing fails.Some steps assume a local, stateful engine:
- stepping the solver with
lifecycle_stride; - hot-start cloning to branch experiments;
- running
control_loop.py; - writing output files to a folder the user can browse.
That fits an in-process engine. It doesn't carry over to a remote API.
- stepping the solver with
Other ways skills could have been designed
| Design | How it works | Best fit |
|---|---|---|
| Skills as tools | list_skills(), load_skill(name), read_skill_file(name, path) | Any client where the model should decide when to load a skill |
| Resources and prompts | The current openswmm design | Clients with a resource picker or prompt (slash-command) UI |
| Eager index | Put each skill's name and one-line description in the system prompt; load the body on demand | Cheap, reliable triggering |
| Composite or "macro" tools | Write the workflow as code behind one tool or API endpoint, e.g. run_fireflow_assessment(...) | Deterministic, fixed sequences |
| Sub-agent per skill | A specialist agent with its own instructions and a smaller tool set, reached by handoff | Long, complex workflows such as calibration |
| Tool scoping by tag | FastMCP tags, so a skill exposes only the tools it needs | Large tool sets; less useful on a stateless server |
| Client-side skills | Claude Code plugins, Cursor skills | IDE use only; never reaches a production agent |
Recent FastMCP 3.x releases may include a built-in skills provider. Your DWD server requires fastmcp>=3.3.0, so check the docs for that version before writing your own loader.
What I'd recommend for dwd-mcp-fastmcp
The two environments differ in ways that change the design:
- Client: in DWD, the model uses
MCPStreamableHTTPToolinside MAF, and its system prompt comes from the composed playbooks inapp/agent/instructions/. As far as I know, MAF passes MCP tools (and possibly prompts) to the model, but not resources. A resource-only skill design would probably be invisible to your agent. - Statelessness:
main.pyrunsstateless_http=Trueunder 4 Gunicorn workers. Skills can't rely on a "default session", so every step must passtwin_idandscenario_idexplicitly. Loading skills statically at import time works fine across workers. - Runs are queued:
run_scenarioreturns 202 when accepted. Skills should describe "enqueue, poll status, fetch results", not stepping a solver. - No code sandbox: a gpt-4o-mini MAF agent can't run a
control_loop.py. Heavy logic should live in the API or worker, and the skill should orchestrate it.
Concretely:
- Keep the
SKILL.mdfolder format underdwd-mcp-fastmcp/skills/<name>/SKILL.md, next totools.py, so skills are versioned with the tool names they reference. Addskills/tocompress.pyso it ships to Azure. - Expose skills as tools first:
list_skills()returns names and descriptions,load_skill(name)returns the body, andread_skill_reference(name, path)returns a reference file (restricted to that skill's folder). Add resources and a prompt as well, for other MCP clients. - Put the index in the system prompt. Add a short playbook such as
05_skills.md, or generate the index at FastAPI startup, saying that substantive workflows (fireflow assessment, leakage investigation, calibration against SCADA) should callload_skillfirst. This keeps00_core.mdthin, as your dev guide requires. - Push deterministic steps into tools. For example, "clone the scenario, set the SCADA window, run, wait, summarize" is better as one API workflow plus a thin tool than as eight steps the model has to get right. The skill then covers judgement: which parameters, which acceptance criteria, how to present trade-offs. This follows your guide's layer order: enforce in the API first, then tool shape, and only then instructions.
- Skills vs playbooks: playbooks stay always-loaded and short (identity, presentation rules, tool etiquette). Skills are lazy-loaded, multi-step domain workflows. That split stops the composed instructions from growing with every new workflow.
- Add a drift test that parses each
SKILL.mdand checks every backticked tool name against the tools registered inregister_tools. openswmm_mcp would benefit from the same test.
In short: copy openswmm's file format and description style, but not its delivery. Delivering skills through tools fits FastMCP behind MAF, and API-driven orchestration fits a remote, stateless EPANET stack.