Agents: MCP, A2A and skills
osf speaks the two open agent protocols. An MCP client can read, query, edit and render your documents; a document with an @agent_card can itself run as an A2A agent.
MCP server
osf serve mcp serves a directory (default: the current one) or a list of .osf files over stdio. With --listen 127.0.0.1:8091 it serves streamable HTTP instead.
osf serve mcp ~/reports # stdio, read-only
osf serve mcp ~/reports --write # osf.set may write files
osf serve mcp ~/reports --listen 127.0.0.1:8091 # streamable HTTPTools
| Tool | Does |
|---|---|
osf.parse | Canonical JSON of a document (blocks, properties, content, spans) with diagnostics |
osf.lint | Diagnostics: syntax, block schemas, ids, dangling references |
osf.format | Canonical formatting; comments and content are kept |
osf.query | Blocks matching a selector |
osf.get | One block by id: its JSON, its exact source, and which blocks reference it |
osf.set | Replace a block by id (or append it if the id is new); the rest of the file is kept byte for byte. Writes only with --write, otherwise returns the new text |
osf.render | html, md, json, typst as text; pdf, docx, pptx, xlsx as a base64 resource |
osf.diff | Semantic diff of two documents |
osf.view | Show the rendered document in the chat (MCP App) |
Each served document is also an MCP resource at osf://<path>.
MCP App view
osf.view is linked to the UI resource ui://osf/viewer (text/html;profile=mcp-app). Hosts that support MCP Apps show the rendered document inside the conversation; other hosts get a text summary. Ask the agent to "show the report" after editing it.
Client configuration
Claude Desktop (claude_desktop_config.json) and other clients that use the mcpServers format:
{
"mcpServers": {
"osf": {
"command": "osf",
"args": ["serve", "mcp", "/path/to/documents"]
}
}
}VS Code (.vscode/mcp.json) uses a servers key:
{
"servers": {
"osf": {
"type": "stdio",
"command": "osf",
"args": ["serve", "mcp", "${workspaceFolder}"]
}
}
}Claude Code:
claude mcp add osf -- osf serve mcp /path/to/documentsA2A agent
A document with an @agent_card can run as an A2A agent. The card is served at /.well-known/agent-card.json; osf card report.osf prints it.
@agent_card {
name: "Funding report";
description: "Queryable report with live programme data";
url: "https://reports.example.org/funding";
skills: [
{ id: "osf.query"; name: "Query"; description: "Selectors over the report"; },
{ id: "osf.export"; name: "Export"; description: "PDF or Office export"; },
{ id: "osf.resolve_decision"; name: "Decide"; description: "Record an outcome"; }
];
security: [{ type: "bearer"; }];
}OSF_A2A_TOKEN=s3cret osf serve a2a report.osf --write
# A2A agent on http://127.0.0.1:8090 (card: /.well-known/agent-card.json)Built-in skills
A built-in skill runs only if the card lists it.
| Skill | Input | Does |
|---|---|---|
osf.query | { selector } | Blocks matching a selector |
osf.get | { id } | One block |
osf.export | { target, selector?, theme? } | The document, or the blocks a selector picks, as a file artifact |
osf.update | { id, block } | Replace one block with new OSF source |
osf.resolve_decision | { id, outcome, rationale?, decided_by? } | Set outcome, status: decided, decided_by and decided_on |
osf.run_task | { assignee, input: { skill, … }, id? } | Create a @task, send it to another A2A agent and record the result |
A message picks a skill with a data part such as { "skill": "osf.query", "selector": "decision[status=pending]" }, or with text that starts with the skill id (osf.query finding[confidence>=0.8]). Any other text is answered with a keyword search over the document, so the agent is useful without a language model behind it.
Tokens and writes
- If the card declares
security(bearer or API key), the server refuses to start without--tokenorOSF_A2A_TOKEN, and every request except reading the card must carry the token. osf.update,osf.resolve_decisionandosf.run_taskchange the document and need--write. Changes are appended to.osf-audit.log.- The server listens on
127.0.0.1:8090unless you pass--listen;--urlsets the public URL advertised in the card.
Data sources
A @data_source block calls an MCP tool and feeds the result into a sheet, table or chart via source: @ref("id").
@data_source {
id: "funding-live";
mcp: "funding-db";
tool: "search_programmes";
arguments: { query: "energy transition"; };
refresh: "daily";
}
@table { id: "live"; source: @ref("funding-live"); caption: "Live programmes"; }- Fetching is off by default.
--allow-netallows every server;--allow-net=funding-dballows only the named ones. The flag exists onrender,evaland theservecommands. - Results are cached in
.osf-cache/next to the document. Without permission the last cached result is used. - Servers are looked up in
--mcp-config <file>, then./.osf/mcp.json,./.mcp.jsonand~/.config/osf/mcp.json, in the usualmcpServersformat.
{
"mcpServers": {
"funding-db": { "command": "funding-mcp", "args": ["--readonly"] }
}
}osf render report.osf --target pdf --allow-net=funding-dbAgent Skill
The osf skill teaches agents that support Agent Skills (SKILL.md) to write OSF, check it with osf lint, query it and export it. It contains the syntax, the block reference, the selector language and the command list.
osf skill install # ~/.claude/skills/osf
osf skill install --for project # ./.claude/skills/osf
osf skill install --for agents # ~/.agents/skills/osf
osf skill install --for ./skills # ./skills/osf (any directory)
osf skill show # print SKILL.md@code is text, and nothing is fetched unless you allow it.