Install
$ agentstack add skill-aiappsgbb-awesome-gbb-foundry-skill-catalog ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Foundry Skills Catalog — Reference Guide
The Foundry Skills API is a project-level store for instruction-only SKILL.md files. You publish skills once, the API holds them centrally, and any Hosted agent in the project can either bundle them at build time or pull them at runtime — without editing the agent's hard-coded instructions.
This skill covers:
- The full REST surface (
{project}/skills— create, import, list, get,
download, delete) with verified gotchas
- A working
FoundrySkillsSource(SkillsSource)that lets MAF's
SkillsProvider consume Foundry skills at runtime — the missing piece the MS Learn doc never connects
- The build-time bundling alternative (the GHCP/file-copy approach), with
trade-offs
┌────────────────────────────────────────────────────────────────────────┐
│ Foundry Project (managed catalog) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Skills store: {project}/skills │ │
│ │ ├─ greeting (has_blob: true — from ZIP) │ │
│ │ ├─ refund-rules (has_blob: true — from ZIP) │ │
│ │ └─ nudge (has_blob: false — from JSON) ⚠ write-only │
│ └──────────┬──────────────────────────────────────┬────────┘ │
│ │ │ │
│ POST/GET ↓ Foundry-Features: Skills=V1Preview │ GET :download │
│ required on EVERY request ↓ │
└──────────────┼──────────────────────────────────────┼────────────────────┘
│ │
┌───────┴────────┐ ┌────────┴──────────┐
│ Pattern A │ │ Pattern B │
│ build-time │ │ runtime fetch │
│ bundle into │ │ via custom │
│ agent image │ │ FoundrySkillsSource│
└───────┬────────┘ └────────┬──────────┘
│ │
↓ ↓
azd deploy → /app/skills/* → SkillsProvider.from_paths(…)
agent_framework.SkillsProvider(source=FoundrySkillsSource(…))
⚠️ Foundry Skills ≠ awesome-gbb skill catalog
Two different things, same SKILL.md filename:
| | Foundry Skills (this skill documents this) | awesome-gbb skill catalog (this very repo) | |---|---|---| | What it is | Microsoft Foundry product feature — REST API + storage in your Foundry project | Repository of SKILL.md files for the GitHub Copilot CLI / Cursor / Cowork | | Storage | Foundry project ({project}/skills) | This Git repo + per-user ~/.copilot/skills/ mirror | | Consumed by | Foundry Hosted agents (your runtime code reads them and injects as session instructions) | Coding-tool agents (load skills as additional system context for coding sessions) | | File format | Same Agent Skills SKILL.md spec from agentskills.io | Same Agent Skills SKILL.md spec | | Example skill | greeting, refund-rules, compliance-checklist — domain knowledge for end-user agents | azd-patterns, threadlight-design, foundry-toolbox — knowledge for coding-tool sessions |
The on-disk shape is identical (frontmatter + body), but the runtime loader is completely different. This skill is exclusively about the Microsoft Foundry product feature.
⚠️ Hosted agents only (Prompt agents not supported)
Same constraint as foundry-toolbox: skills are consumed by agent code that pulls them at session create / build time. Prompt agents are stateless server-side LLM calls with no mechanism to load skill bodies into a session prompt — there is no skills field on the prompt-agent definition shape, and the platform doesn't auto-inject Foundry skills into prompt-agent runs.
If you need centrally-managed instructions for a Prompt agent, the workarounds are:
- Bake the instructions into the prompt-agent definition's
instructions
field directly (loses the central-update benefit)
- Front the prompt agent with a thin Hosted agent that pulls the skills and
forwards the augmented system prompt
When to use this vs alternatives
| Situation | Use | |---|---| | You want one team to own canonical "how to handle X" instructions and many Hosted agents to inherit them | This skill (Foundry Skills + FoundrySkillsSource) | | Every agent has its own instructions and they never need to be shared | Just put SKILL.md files in the agent's skills/ dir → standard SkillsProvider.from_paths(…) (see foundry-hosted-agents § Skill Loading) | | You need progressive-disclosure tools, but the bodies live elsewhere (DB, env, code) | agent_framework.InlineSkill directly + InMemorySkillsSource (see MAF docs) | | You need to share executable scripts / multi-file resources, not just an instruction body | Foundry Skills ZIP-mode (this skill) — the ZIP can contain scripts/, references/, assets/ |
The mandatory Foundry-Features: Skills=V1Preview header
Every REST call to {project}/skills* must carry:
Foundry-Features: Skills=V1Preview
Verified behavior (raw HTTP, live Foundry project):
GET {project}/skills?api-version=v1
WITHOUT header: HTTP 403 preview_feature_required
"This operation requires the following opt-in preview
feature(s): Skills=V1Preview. Include the
'Foundry-Features: Skills=V1Preview' header."
WITH header: HTTP 200 list returned
SDK behavior (Python azure-ai-projects 2.1.0): when you construct AIProjectClient(endpoint, credential, allow_preview=True), the SDK auto-injects the header on every client.beta.skills.* call — no per-request policy needed. Without allow_preview=True, the beta.skills attribute may be present but the header won't be injected and calls will fail with the 403 above.
The .NET SDK requires a manual FeaturePolicy injecting the header per-request; see the Foundry Skills doc.
Auth & RBAC
| Item | Value | |---|---| | Endpoint base | {FOUNDRY_PROJECT_ENDPOINT}/skills | | API version | v1 | | Token scope | https://ai.azure.com/.default | | Required RBAC role | Azure AI User on the Foundry project | | Recommended credential | DefaultAzureCredential (local dev) → Managed Identity (production) |
Per azure-tenant-isolation, set AZURE_CONFIG_DIR per-tenant before any az login / SDK call to keep the token cache isolated.
Two creation modes (the has_blob distinction matters a lot)
Mode 1 — Create from JSON
Submit name, description, and instructions as a JSON body. The skill is registered as has_blob: false.
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
ENDPOINT = "https://.services.ai.azure.com/api/projects/"
with (
DefaultAzureCredential() as cred,
AIProjectClient(endpoint=ENDPOINT, credential=cred, allow_preview=True) as project,
):
created = project.beta.skills.create(
name="greeting",
description="Generate a personalized greeting.",
instructions="You are a friendly assistant. Keep greetings to 1-2 sentences.",
)
print(created.has_blob) # → False
Mode 2 — Create from ZIP (:import)
Build a ZIP containing SKILL.md (frontmatter + body) and POST it to the :import endpoint. The skill is registered as has_blob: true.
from pathlib import Path
with (
DefaultAzureCredential() as cred,
AIProjectClient(endpoint=ENDPOINT, credential=cred, allow_preview=True) as project,
):
imported = project.beta.skills.create_from_package(
Path("greeting.zip").read_bytes()
)
print(imported.has_blob) # → True
⚠️ TRAP — JSON-mode skills are write-only
This is the biggest silent gotcha in the Skills API, and the MS Learn doc does not call it out:
| Operation | JSON-mode (has_blob: false) | ZIP-mode (has_blob: true) | |---|---|---| | Create | ✅ accepts instructions | ✅ accepts ZIP body | | GET /skills/{name} | ✅ but does NOT return instructions | ✅ does not return body either | | GET /skills/{name}:download | ❌ HTTP 404 — "Skill does not have an associated package" | ✅ returns the original ZIP bytes | | ?include=instructions / ?expand=… / :body / :content / :text | ❌ none of these expose the body | n/a |
Verified by raw HTTP probing on azure-ai-projects 2.1.0 (May 2026): the instructions you POST in JSON mode are never retrievable through any documented or undocumented API path. The list/get response gives you back only name, description, has_blob, metadata, skill_id, object.
Practical consequence
| Use case | Use JSON mode? | |---|---| | You want the agent code to download the skill body at runtime via the API | ❌ No — JSON mode is unusable for this. Use ZIP mode. | | You're shipping a FoundrySkillsSource adapter (Pattern B below) | ❌ No — only ZIP-mode bodies come back. JSON entries appear in list() but _from_json can only return their description. | | You bundle the skill body into the agent image at build time and use the Foundry record purely as a registration / version marker | ✅ OK — but you might as well use ZIP mode anyway, for parity. |
Recommendation
Always create via ZIP unless you have a very specific reason to use JSON mode (e.g., you only need the registry entry for cataloging, never the body).
⚠️ TRAP — Quoted frontmatter → HTTP 500
Verified against the live API (request id captured in the error response):
# ❌ FAILS with HTTP 500 server_error on :import
---
name: 'greeting'
description: 'Generate a personalized greeting.'
---
# ✅ Accepted
---
name: greeting
description: Generate a personalized greeting.
---
The name and description values must be unquoted in the YAML frontmatter. Quoted values produce a generic 500 ("An error occurred while processing your request") with no hint about the cause — easy to lose hours debugging.
If you author skills from a system that quotes string values by default (some YAML serializers do), strip the quotes before writing the file or use a serializer with default_style=None.
ZIP layout — what's actually accepted (vs documented)
The MS Learn doc says:
> The ZIP must contain SKILL.md at the root, not in a subdirectory.
Verified behavior is more permissive (May 2026, live Foundry project):
| ZIP layout | Outcome | |---|---| | SKILL.md at root | ✅ Accepted, has_blob: true, downloads round-trip cleanly | | SKILL.md at root + references/extra.md + scripts/helper.py | ✅ All entries preserved through download | | subdir/SKILL.md only (nested) | ✅ Accepted despite the doc — but downloads keep the subdir prefix, which most consumers won't expect | | ZIP with no SKILL.md anywhere | ❌ HTTP 500 server_error |
Practical rule (matches the doc): always place SKILL.md at the ZIP root. The fact that nested layouts are accepted today is not a guarantee they will be tomorrow, and any consumer that runs zipfile.read("SKILL.md") will silently fail to find a nested copy.
# The right way
import io, zipfile
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
z.writestr("SKILL.md", skill_md_text) # ← at root
z.writestr("references/policy.md", policy_text) # ← supporting files OK
z.writestr("scripts/lookup.py", script_text) # ← scripts OK
project.beta.skills.create_from_package(buf.getvalue())
REST surface — full reference
All operations are against {FOUNDRY_PROJECT_ENDPOINT}/skills with header Foundry-Features: Skills=V1Preview and bearer token from https://ai.azure.com/.default.
| Op | REST | SDK (client.beta.skills) | Returns | |---|---|---|---| | Create from JSON | POST /skills?api-version=v1 | .create(name, description, instructions) | has_blob: false skill | | Create from ZIP | POST /skills:import?api-version=v1 (body: ZIP) | .create_from_package(zip_bytes) | has_blob: true skill | | List | GET /skills?api-version=v1&limit=20&order=desc&after= | .list(limit=…, order=…, after=…) | Paginated list | | Get | GET /skills/{name}?api-version=v1 | .get(name) | Metadata (no body); 404 if missing | | Download | GET /skills/{name}:download?api-version=v1 | .download(name) (iterator of bytes) | ZIP bytes for has_blob:true; 404 for has_blob:false | | Update | PATCH /skills/{name}?api-version=v1 | .update(name, description=…) | Updated metadata (description only — body cannot be patched in place; re-import to replace) | | Delete | DELETE /skills/{name}?api-version=v1 | .delete(name) | 204; subsequent get returns 404 |
Pagination
list() returns { data: […], has_more, first_id, last_id }. Use first_id / last_id with before / after query params for cursor pagination.
Errors observed in PoC
| Status | Code | When | |---|---|---| | 403 | preview_feature_required | Foundry-Features header missing | | 404 | not_found | GET / DELETE / DOWNLOAD on a missing skill, or :download on a JSON-mode skill | | 500 | server_error | Quoted frontmatter on :import, ZIP without SKILL.md, generic catch-all |
Pattern A — Build-time bundle (the GHCP-SDK approach)
Match the MS Learn sample: at azd deploy time, download every Foundry skill and bake the resulting SKILL.md files into the container image. Standard SkillsProvider.from_paths(…) then loads them at session start.
# scripts/sync_skills.py — run as an azd predeploy hook
import io
import zipfile
from pathlib import Path
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
ENDPOINT = "https://.services.ai.azure.com/api/projects/"
TARGET = Path(__file__).parent.parent / "src" / "skills"
TARGET.mkdir(parents=True, exist_ok=True)
with (
DefaultAzureCredential() as cred,
AIProjectClient(endpoint=ENDPOINT, credential=cred, allow_preview=True) as project,
):
for s in project.beta.skills.list():
if not s.has_blob:
print(f" skipping JSON-mode skill {s.name} (body unretrievable)")
continue
zip_bytes = b"".join(project.beta.skills.download(s.name))
skill_dir = TARGET / s.name
skill_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as z:
z.extractall(skill_dir)
print(f" unpacked {s.name} → {skill_dir}")
In the agent code:
from agent_framework import SkillsProvider
provider = SkillsProvider.from_paths(Path(__file__).parent / "skills")
| Pros | Cons | |---|---| | Sealed image — no runtime network dep on Foundry | Skill changes require a redeploy | | Same code path as a fully-local agent (good test parity) | Skills are duplicated across every agent deployment | | Works with any SkillsProvider-based runtime today | JSON-mode skills are silently skipped (write-only trap) |
Pattern B — Runtime fetch via FoundrySkillsSource (recommended for shared catalogs)
The MAF Python SkillsProvider accepts any SkillsSource — an ABC with one method, async def get_skills() -> list[Skill]. There's no built-in Foundry-backed source; the snippet below is a verified-working implementation.
# foundry_skills_source.py
import asyncio
import io
import zipfile
from agent_framework import InlineSkill, Skill, SkillsSource
from azure.ai.projects import AIProjectCli
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [aiappsgbb](https://github.com/aiappsgbb)
- **Source:** [aiappsgbb/awesome-gbb](https://github.com/aiappsgbb/awesome-gbb)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.