# Foundry Skill Catalog

> >

- **Type:** Skill
- **Install:** `agentstack add skill-aiappsgbb-awesome-gbb-foundry-skill-catalog`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [aiappsgbb](https://agentstack.voostack.com/s/aiappsgbb)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [aiappsgbb](https://github.com/aiappsgbb)
- **Source:** https://github.com/aiappsgbb/awesome-gbb/tree/main/skills/foundry-skill-catalog

## Install

```sh
agentstack add skill-aiappsgbb-awesome-gbb-foundry-skill-catalog
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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:

1. The full REST surface (`{project}/skills` — create, import, list, get,
   download, delete) with verified gotchas
2. A working **`FoundrySkillsSource(SkillsSource)`** that lets MAF's
   `SkillsProvider` consume Foundry skills at runtime — the missing piece
   the MS Learn doc never connects
3. 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](https://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](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/skills?pivots=python).

---

## 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`.

```python
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`.

```python
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):

```yaml
# ❌ FAILS with HTTP 500 server_error on :import
---
name: 'greeting'
description: 'Generate a personalized greeting.'
---
```

```yaml
# ✅ 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.

```python
# 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.

```python
# 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:

```python
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.

```python
# 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.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-aiappsgbb-awesome-gbb-foundry-skill-catalog
- Seller: https://agentstack.voostack.com/s/aiappsgbb
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
