Agents and skills
The Capture MCP SDK is currently only available to Capture's internal developers. We are working hard on the cybersecurity part, so that we can also offer it to everyone who builds on top of Capture.
Your app can tell Captain how to use its tools, not only what they are. You do this by shipping two folders next to your code:
- Agents:
*.agent.mdfiles, each one a persona with a system prompt. Captain builds one specialist per agent. - Skills:
<name>/SKILL.mdfolders, written in the open Agent Skills format, that an agent can load when it needs them.
They travel with your tools in the registration heartbeat. This is also what makes the difference between Captain and a general AI tool that only sees a list of tools: Captain has the guidance.
If your app registers no agents, Captain serves it one default agent over all its tools. Any app that may register tools may also register agents and skills, on the same heartbeat and with the same machine client.
Point the SDK at your folders
- Python
- .NET
settings = CaptureMcpSettings(
...,
agents_dir="my_app:agents", # or "my_app.agents", or a plain directory
skills_dir="my_app:skills",
)
Both settings are resolved through importlib.resources. The value names package data, not a path on your build machine. A wheel that is installed finds its prompts without a checkout, and a file that exists in development but is missing from the image fails loudly instead of quietly.
<!-- YourApp.csproj -->
<PropertyGroup>
<CaptureMcpAgentsDir>$(MSBuildProjectDirectory)/agents</CaptureMcpAgentsDir>
<CaptureMcpSkillsDir>$(MSBuildProjectDirectory)/skills</CaptureMcpSkillsDir> <!-- optional -->
</PropertyGroup>
The SDK embeds the files into your assembly, checks them with the same rules the MCP server applies, and sends the ones that pass on the registration heartbeat.
A definition that the SDK refuses is left out and logged once. The MCP server checks again and returns what it refused, which the SDK also logs once. A mistake in an agent or a skill never takes your tools offline.
An agent
An *.agent.md file is frontmatter plus a body. The body becomes the system prompt.
---
name: uns-explorer # must match the file name, Agent Skills naming rules
description: Browses and reads the Unified Namespace of a site.
tools: uns_browse uns_read # registered names, a subset of your tools; omit the key to bind them all
skills: uns-authoring # skills of this app the agent may load
model: captain-medium # one of Captain's seven tiers; never a raw model id
permissions: app-901:uns:view # optional; the agent is hidden from users who lack them
---
You are the UNS explorer for this site. …
| Key | What it does |
|---|---|
name | The name of the agent. Lowercase letters and digits in hyphen-separated segments, at most 64 characters. It must match the file name: greeter.agent.md holds the agent greeter. |
description | What the agent is for, at most 1024 characters. It is what the router uses to decide when to hand a question to this agent, so it is part of the review of your app. |
tools | The tools the agent can use, by the name they are registered under, separated by spaces or commas. Bind a subset for least privilege. Leave the key out to bind every tool of your app. A tool you do not register is an error. |
skills | The skills of your app that the agent advertises in its prompt. |
model | Optional. One of the tiers of Captain: captain-small, captain-medium, captain-rag-expert, captain-data-expert, captain-tool-expert, captain-reasoning-expert or captain-vision. Never a raw model id. Leave it out and the agent runs on captain-tool-expert. An operator can still move a tier, so treat it as the default of your agent. |
permissions | Optional. The permissions a user needs to be offered the agent. See below. |
- The body is the system prompt, at most 64 KiB. Captain adds its own answer style, scope and visualization guidance after it. You cannot supply or remove those.
- Frontmatter values are one line. A block value such as
description: >or a list spread over several lines is refused, instead of guessed at: a guess would become a system prompt. Keys that this version does not know are ignored, so a file written for a newer SDK still builds.
An agent binds a tool by the name it is registered under, {prefix}_{tool}. The prefix is platform_app_name (PlatformAppName in .NET) when you set it and the service name otherwise. With the prefix uns, a tool browse is uns_browse. A bare name is an error: the MCP server compares an agent with the registered names on the same heartbeat, and refuses an agent that binds one.
Unlike the permission on a tool, the permissions of an agent or a skill are written fully qualified, as {app}:{resource}:{action}, for example app-901:uns:view. An agent may legitimately require the permission of another app, so there is no app part to fill in for you. For your own app, the first part is the app key that Capture assigned to it (see GET /api/apps/me). An entry whose first part is not your app key is treated as the permission of another app: the SDK does not declare it, and it hides the agent from every user until that app declares it. A malformed entry is an error.
An agent with permissions is only offered to a user who holds all of them in the company the question is asked in, and who may use at least one of the tools of the agent. The SDK declares the entries that belong to your app on the permission manifest of your app, as it does for the permission on a tool, so an administrator has something to put in a role. This is not a security boundary. The MCP server checks every tool call again with the token of the caller. The permissions keep an agent out of the way of a user who cannot use it.
A skill
A skill is a folder skills/<name>/SKILL.md, with an optional references/ folder one level deep.
---
name: uns-authoring
description: How to lay out a Unified Namespace for a site: the naming conventions and the level at which a tag belongs.
allowed-tools: uns_write
permissions: app-901:uns:manage
---
# Authoring a Unified Namespace
1. Model the physical plant first: enterprise, site, area, line, cell.
2. Name every level in `lower-kebab-case`.
3. Read `references/naming.md` before you propose a rename.
4. Only once the layout is agreed, write with `uns_write`, one node at a time.
skills/uns-authoring/SKILL.md
skills/uns-authoring/references/naming.md
descriptionis all an agent sees of the skill until it loads it, at most 1024 characters. The body is the procedure, at most 64 KiB, delivered only when the skill is loaded.allowed-toolslists your registered tools. They become available to an agent only once the skill is loaded, and only for that turn. So a capability that an agent should not carry into every conversation arrives with the procedure that explains it. A tool named here does not need to be bound by any agent. Leave the key out and the skill unlocks nothing, the opposite of thetoolsof an agent.- Loading never reveals a tool the caller does not hold. The permissions of the caller are applied first, and a skill can only stop withholding what was already allowed.
permissionsworks as it does on an agent: all of them, fully qualified, judged in the company the question is asked in.references/holds plain text files one level deep, each at most 64 KiB, that the agent reads on demand. Long material belongs there instead of in the body. A skill has at most 16 references, and its body and references together are at most 512 KiB.scripts/andassets/are an error. Executable behaviour on this platform is a tool of your app, which is permission-checked and observable. It is not a file in a skill.- Any agent of your app can load any skill of your app. The
skillskey of an agent only names the ones its prompt advertises. A skill can only name your own tools and skills.
Check them
- Python
- .NET
capture-mcp validate runs the same rules the MCP server applies when you register. It exits with a non-zero code on an error and with zero when it only found warnings, so it drops straight into a CI step:
capture-mcp validate --app my_app.main:app # settings and tool names come from the app
capture-mcp validate --agents-dir ./agents --tool uns_browse --tool uns_read # registered names
--app imports the module from the working directory, so run it from the root of your project.
app.start() runs the same check before it connects to anything. It raises capture_mcp_sdk.definitions.DefinitionValidationError on an error, so a typo in a prompt cannot reach a running system. Warnings, for example a tool that no agent binds and no skill unlocks, are logged and do not stop the start.
The SDK package carries a Roslyn analyzer that runs the same rules over your files during the build, so a typo fails the build that would have shipped it, instead of costing you the agent at startup.
<PropertyGroup>
<CaptureMcpAgentsDir>$(MSBuildProjectDirectory)/agents</CaptureMcpAgentsDir>
<CaptureMcpSkillsDir>$(MSBuildProjectDirectory)/skills</CaptureMcpSkillsDir> <!-- optional -->
<CaptureMcpToolPrefix>yourapp</CaptureMcpToolPrefix> <!-- only if it differs from your assembly name -->
</PropertyGroup>
- Tool names are resolved from your own
[McpServerTool]methods, spelled the way the SDK registers them:{prefix}_{tool}. The prefix at runtime isPlatformAppNameor elseServiceName, which a build cannot see, so it defaults to the name of your assembly. If yours differs, setCaptureMcpToolPrefix, or every tool name in your files reads as unknown. The diagnostic lists the names your app does register. - A bare name such as
greetis an error too, because the SDK and the MCP server would refuse the agent that binds it. - Configure a rule by its id in
.editorconfig, like any analyzer:dotnet_diagnostic.CAPMCP013.severity = none. If your project setsTreatWarningsAsErrors, the warning about an unbound tool fails your build too. SilenceCAPMCP013for a tool you mean to keep out of the reach of Captain.
| Id | Reported when | Severity |
|---|---|---|
CAPMCP001, CAPMCP002 | A definition has no --- frontmatter block, or its frontmatter cannot be read. | Error |
CAPMCP003 to CAPMCP006 | An agent has no name, a name that breaks the rules, a name that differs from its file, or a name another agent also has. | Error |
CAPMCP007, CAPMCP008 | An agent has no description, or one that is too long. | Error |
CAPMCP009, CAPMCP010 | An agent has no system prompt, or one that is too large. | Error |
CAPMCP011 | An agent binds a tool your app does not register. | Error |
CAPMCP012 | An agent names a skill your app does not ship. | Error |
CAPMCP013 | A tool is bound by no agent and unlocked by no skill. | Warning |
CAPMCP014 | The model of an agent is not a tier of Captain. | Error |
CAPMCP015 | A permission of an agent is not {app}:{resource}:{action}. | Error |
CAPMCP020 to CAPMCP027 | The same rules for skills: name, description and procedure. | Error |
CAPMCP028 | allowed-tools of a skill names a tool your app does not register. | Error |
CAPMCP029 | A permission of a skill is not {app}:{resource}:{action}. | Error |
CAPMCP030 | A skill contains a scripts/ or assets/ folder. | Error |
CAPMCP031 to CAPMCP034 | A reference is not a plain file one level under references/, is too large, is declared twice, or there are too many or they are too large together. | Error |
An app without definition files is not analysed at all.
A tool that no agent binds and no skill unlocks is a warning in both languages: Captain can never call it through an agent.
An example to start from
Both SDKs come with a working example: examples/uns_sample_app/ in Python, with one agent, one skill and a reference file, and McpSdkSample in .NET, with end-to-end tests of the registration loop.