Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fba84d0
Add Skills-over-MCP SEP host support
olaservo Apr 23, 2026
a73c2af
Validate skill index $schema against known set
olaservo May 10, 2026
1ade781
Support `type: "archive"` skill index entries
olaservo May 10, 2026
84d8003
Resolve `mcp-resource-template` skill entries via completion API
olaservo May 10, 2026
42727f8
Re-discover skills when server signals `skill://index.json` changed
olaservo May 10, 2026
1af7ce8
Skill provenance display and per-skill enable/disable toggles
olaservo May 10, 2026
b4372a7
Support loading unenumerated `skill://` URIs
olaservo May 10, 2026
f664c88
Add `/skills preview <name>` for pre-load skill inspection
olaservo May 10, 2026
b8128c3
Wrap MCP-served skill content with untrusted-source marker
olaservo May 12, 2026
9d5d130
Cleanup: type template entries, drop INDEX_URI rename, join preview body
olaservo May 12, 2026
8119f43
Merge remote-tracking branch 'upstream/main' into experimental/skills…
olaservo May 13, 2026
7379094
Trim narration and duplicate trust-model comments
olaservo May 13, 2026
5421cd7
Merge remote-tracking branch 'upstream/main' into experimental/skills…
olaservo May 16, 2026
2218bb1
Remove type:"archive" skill index entry support
olaservo May 28, 2026
c13517a
Merge remote-tracking branch 'upstream/main' into experimental/skills…
olaservo May 28, 2026
e3b40c3
Merge branch 'main' into experimental/skills-over-mcp-v2-base
evalstate May 28, 2026
e935610
test server
evalstate May 28, 2026
21d2780
Merge branch 'main' into experimental/skills-over-mcp-v2-base
evalstate May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
90 changes: 90 additions & 0 deletions examples/mcp/skills-over-mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Skills-over-MCP demo

This example runs a tiny MCP server that publishes Agent Skills through the
Skills-over-MCP discovery resource:

```text
skill://index.json
```

The server exposes:

- a concrete `pirate` skill at `skill://pirate/SKILL.md`
- a reference file at `skill://pirate/references/example.md`
- a template skill namespace at `skill://docs/{product}/SKILL.md`

## Start with the Server enabled

```bash
fast-agent --stdio "uv run skill_server.py"
```

## Run the scripted demo

From this directory:

```bash
uv run example.py
```

The demo uses the passthrough model to call `read_skill` directly for the
MCP-served skill and one associated resource.

`example.py` prints human-readable demo output. If a client expects MCP
JSON-RPC on stdout it will try to parse that text as protocol messages and
fail.

## Run just the MCP server

If you want to connect another MCP client directly to the Skills-over-MCP
server, point it at the server script instead:

```bash
uv run skill_server.py
```

For fast-agent config, this is already wired in `fast-agent.yaml` as:

```yaml
mcp:
targets:
- name: skill_demo
target: "uv run skill_server.py"
```

## Try it interactively

From this directory:

```bash
uv run fast-agent --env . go
```

Useful commands:

```text
/skills
/skills preview pirate
/skills templates
/skills resolve 1 product=alpha
/skills preview alpha
/skills disable pirate
/skills enable pirate
```

The `pirate` and resolved `alpha` skills are served by the MCP server, not
loaded from local disk. Their content is returned through `read_skill` wrapped
as untrusted MCP-provided input.

## Disable Skills-over-MCP for the server

Set `mcp_skills: false` on the server to keep the MCP target connected while
suppressing `skill://index.json` discovery:

```yaml
mcp:
targets:
- name: skill_demo
target: "uv run skill_server.py"
mcp_skills: false
```
40 changes: 40 additions & 0 deletions examples/mcp/skills-over-mcp/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Manual demo for Skills-over-MCP discovery.

Run from this directory:
uv run example.py

For the full interactive flow, run `uv run fast-agent --env . go` and try the slash
commands listed in README.md.
"""

from __future__ import annotations

import asyncio

from fast_agent import FastAgent

fast = FastAgent("Skills-over-MCP demo", config_path="fast-agent.yaml")


@fast.agent(
instruction=(
"You are a passthrough demo agent. Available skills, if any, are "
"listed in the system prompt."
),
servers=["skill_demo"],
)
async def main() -> None:
async with fast.run() as agent:
print("\n=== read MCP-served skill ===")
result = await agent.send('***CALL_TOOL read_skill {"path":"skill://pirate/SKILL.md"}')
print(result)

print("\n=== read MCP-served skill reference ===")
ref = await agent.send(
'***CALL_TOOL read_skill {"path":"skill://pirate/references/example.md"}'
)
print(ref)


if __name__ == "__main__":
asyncio.run(main())
10 changes: 10 additions & 0 deletions examples/mcp/skills-over-mcp/fast-agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
default_model: "passthrough"

logger:
level: "error"
type: "console"

mcp:
targets:
- name: skill_demo
target: "uv run skill_server.py"
98 changes: 98 additions & 0 deletions examples/mcp/skills-over-mcp/skill_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Tiny Skills-over-MCP demo server.

Run from this directory with:
uv run skill_server.py
"""

from __future__ import annotations

import json

from fastmcp import FastMCP
from mcp.types import Completion, ResourceTemplateReference

mcp = FastMCP("Skills-over-MCP Demo Server")

DOC_PRODUCTS = ("alpha", "beta")
TEMPLATE_URI = "skill://docs/{product}/SKILL.md"

INDEX = {
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": [
{
"type": "skill-md",
"url": "skill://pirate/SKILL.md",
},
{
"type": "mcp-resource-template",
"url": TEMPLATE_URI,
"description": "Product-specific documentation skills.",
},
],
}


@mcp.resource("skill://index.json", mime_type="application/json")
def skill_index() -> str:
"""Skills-over-MCP discovery index."""
return json.dumps(INDEX)


@mcp.resource("skill://pirate/SKILL.md", mime_type="text/markdown")
def pirate_skill() -> str:
return """---
name: pirate
description: Answer in an exaggerated pirate style.
---

When using this skill, rewrite the answer in pirate dialect. Use nautical
phrases, but keep the answer useful and concise.
"""


@mcp.resource("skill://pirate/references/example.md", mime_type="text/markdown")
def pirate_example() -> str:
return """# Pirate style example

Plain: The server is ready.
Pirate: Arrr, the server be ready to sail.
"""


@mcp.resource(TEMPLATE_URI, mime_type="text/markdown")
def product_docs_skill(product: str) -> str:
if product == "alpha":
guidance = "Prefer Alpha examples about onboarding, setup, and first-run UX."
elif product == "beta":
guidance = "Prefer Beta examples about reporting, exports, and audit trails."
else:
guidance = f"Use general documentation examples for {product}."

return f"""---
name: {product}
description: Product documentation guidance for {product}.
---

When using this skill, answer as a product documentation specialist for
`{product}`. {guidance}
"""


@mcp._mcp_server.completion()
async def complete_resource_template_argument(ref, argument, context):
"""Suggest values for skill://docs/{product}/SKILL.md."""
del context

if not isinstance(ref, ResourceTemplateReference):
return Completion(values=[])
if ref.uri != TEMPLATE_URI or argument.name != "product":
return Completion(values=[])

prefix = argument.value or ""
values = [product for product in DOC_PRODUCTS if product.startswith(prefix)]
return Completion(values=values, total=len(DOC_PRODUCTS), hasMore=False)


if __name__ == "__main__":
mcp.run(transport="stdio")
Loading
Loading