Skip to main content

agentskills-http

PyPI Python 3.12 | 3.13 License: MIT

HTTP static-file skill provider for the Agent Skills SDK.

Serves Agent Skills from any static HTTP file host - S3, Azure Blob, CDN, GitHub Pages, Nginx, etc. Expects the same directory-tree layout as the filesystem provider, served over HTTP.

Installation

pip install agentskills-http

Requires Python 3.12 or newer. Installs agentskills-core, httpx, and pyyaml as dependencies.

Expected URL Layout

https://cdn.example.com/skills/
├── incident-response/
│   ├── SKILL.md
│   ├── references/severity-levels.md
│   ├── scripts/page-oncall.sh
│   └── assets/flowchart.mermaid
└── another-skill/
    └── SKILL.md

Usage

from agentskills_core import SkillRegistry
from agentskills_http import HTTPStaticFileSkillProvider

async with HTTPStaticFileSkillProvider("https://cdn.example.com/skills") as provider:
    registry = SkillRegistry()
    await registry.register("incident-response", provider)

    skill = registry.get_skill("incident-response")
    meta = await skill.get_metadata()
    body = await skill.get_body()

Custom Headers

Pass authentication or other headers:

from agentskills_http import HTTPStaticFileSkillProvider

provider = HTTPStaticFileSkillProvider(
    "https://cdn.example.com/skills",
    headers={"Authorization": "Bearer <token>"},
)

Bring Your Own Client

Supply a pre-configured httpx.AsyncClient for full control over timeouts, proxies, etc.:

import httpx
from agentskills_http import HTTPStaticFileSkillProvider

client = httpx.AsyncClient(timeout=30, headers={"Authorization": "Bearer <token>"})
provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=client)
# caller is responsible for closing the client

Note: client and headers are mutually exclusive. Configure headers on the client directly when providing your own.

API

HTTPStaticFileSkillProvider(base_url, *, client=None, headers=None, params=None, require_tls=False, max_response_bytes=10_485_760, revalidate=False)

Parameter Type Default Description
base_url str - Root URL where the skill tree is hosted
client AsyncClient | None None Pre-configured httpx client (caller manages lifecycle)
headers dict | None None Extra headers sent with every request
params dict | None None Query parameters appended to every request
require_tls bool False Reject http:// URLs with ValueError
max_response_bytes int 10_485_760 Maximum allowed response size in bytes
revalidate bool False Re-check cached SKILL.md on every access with If-None-Match / If-Modified-Since
resource_manifest bool False Enable list_resources() by reading a per-skill index.json
skill_manifest bool False Enable discover() by reading a root index.json
timeout float 30.0 Request timeout in seconds (ignored when you supply client)
max_retries int 2 Retries after the initial attempt, for retryable failures only
retry_backoff float 0.5 Base delay in seconds for exponential backoff
max_retry_delay float 30.0 Ceiling on any single backoff sleep

Note: client and headers/params are mutually exclusive. Configure headers and params on the client directly when providing your own.

Method Returns Description
get_metadata(skill_id) dict[str, Any] Parsed YAML frontmatter from SKILL.md
get_body(skill_id) str Markdown body after the frontmatter
get_script(skill_id, name) bytes Raw script content
get_asset(skill_id, name) bytes Raw asset content
get_reference(skill_id, name) bytes Raw reference content
list_resources(skill_id) dict[str, list[str]] Resource names from index.json (requires resource_manifest=True)
discover() list[str] Skill IDs from the root index.json (requires skill_manifest=True)
invalidate(skill_id=None) None Drop cached SKILL.md content for one skill, or all skills
aclose() None Close the HTTP client (if owned by the provider)

Supports async with for automatic cleanup.

Resource Discovery

A static file host cannot be enumerated: there is no portable directory listing over plain HTTP. By default this provider therefore reports that it cannot list resources — list_resources() raises ResourceListingNotSupportedError — rather than returning an empty mapping that would look like a skill with no resources.

If you control the host, publish a small manifest at {base_url}/{skill_id}/index.json:

{
  "references": ["severity-levels.md"],
  "scripts": ["page-oncall.sh"],
  "assets": ["flowchart.mermaid"]
}

Then opt in:

provider = HTTPStaticFileSkillProvider(BASE, resource_manifest=True)
listing = await provider.list_resources("incident-response")

Missing categories default to empty lists. A manifest is host-supplied data whose entries are later interpolated into URLs, so names failing the identifier-safety check are dropped. If a given skill has no index.json, list_resources() raises ResourceListingNotSupportedError for that skill — again, not an empty result.

Skill Discovery

The same problem one level up: nothing on a static host says which skills exist. Publish the same file at the root, listing skills instead of resources:

{ "skills": ["incident-response", "api-style-guide"] }

Then opt in and register the whole host at once:

async with HTTPStaticFileSkillProvider(BASE, skill_manifest=True) as provider:
    await registry.register_all(provider)

One filename and one shape — an object mapping a category to a list of names — at two depths, rather than two manifest formats to keep in step. Unsafe and duplicate IDs are dropped as above. Without skill_manifest=True, or when the root publishes no manifest, discover() raises DiscoveryNotSupportedError.

Caching

SKILL.md responses are cached per provider instance. Without it a single skill costs up to five round-trips per agent session — twice during registration, once per catalog build, and again on each tool call. Scripts, assets and references are not cached.

By default the cache is served until you call invalidate(). If your host serves mutable skills and the process is long-lived, opt into conditional revalidation instead:

provider = HTTPStaticFileSkillProvider(BASE, revalidate=True)

That sends If-None-Match / If-Modified-Since on every access and reuses the cached body on 304. It costs one cheap round-trip per access, so prefer the default plus an explicit invalidate() when you control publishing.

Error Handling

Scenario Exception Retried
404 / 410 on SKILL.md SkillNotFoundError No
404 / 410 on a resource ResourceNotFoundError No
5xx, 408, 425, 429 SkillUnavailableError Yes
Timeouts, connection and protocol errors SkillUnavailableError Yes
401 / 403 AgentSkillsError No
Other 4xx, oversized responses AgentSkillsError No

All exceptions inherit from AgentSkillsError.

The split between SkillNotFoundError and SkillUnavailableError is the point of the taxonomy: a 503 means the skill may well exist and the same request could succeed in a moment, whereas a 404 means it is gone. Collapsing both into "not found" turns a retryable blip into a permanent-looking failure, and nothing downstream can tell the difference.

Retries

Retryable failures are retried with exponential backoff and full jitter:

provider = HTTPStaticFileSkillProvider(
    BASE,
    max_retries=2,          # attempts after the first; 0 disables
    retry_backoff=0.5,      # base delay in seconds
    max_retry_delay=30.0,   # ceiling on any single sleep
)

Jitter matters because a registry builds its catalog concurrently — without it, every skill fetch would retry in lockstep and hit the recovering server as one wave.

Retry-After is honoured in both the delay-seconds and HTTP-date forms. If the server asks for longer than max_retry_delay, the request is not retried: blocking a request path for minutes is worse than failing fast. The advised delay is still available to the caller as SkillUnavailableError.retry_after, so a scheduler can act on it.

Security

  • Input validation - Skill IDs and resource names are validated against a safe-character pattern (^[a-zA-Z0-9][a-zA-Z0-9._-]*$) to prevent path-traversal and injection attacks.
  • TLS warnings - A UserWarning is emitted when base_url uses unencrypted HTTP. Set require_tls=True to reject HTTP URLs entirely.
  • Redirect protection - The internally-created HTTP client does not follow redirects by default, preventing open-redirect SSRF.
  • Timeouts - Default 30-second timeout on all HTTP requests. Configure via timeout.
  • Response size limits - Responses exceeding 10 MB (default) are rejected before processing. Configure via max_response_bytes.
  • Error-message sanitization - Messages carry the status code and the path relative to base_url — never the host, never a query string. The underlying httpx exception is deliberately not chained (from None), because httpx.HTTPStatusError renders the full request URL including its query string, which is exactly where SAS tokens and signed-URL signatures live. Chaining it leaked credentials into every traceback.

For the full security policy, see SECURITY.md.

Deployment Considerations

  • Rate limiting - The SDK does not enforce rate limits on MCP tool calls or HTTP requests. Deploy behind a reverse proxy or API gateway that provides rate limiting in production environments.
  • Credential management - Do not store secrets (API keys, SAS tokens, Authorization headers) in config files committed to version control. Use environment variables or a secret manager instead.

License

MIT

Metadata

Release files for agentskills-http 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agentskills-http 0.4.0
File Size Uploaded
agentskills_http-0.4.0.tar.gz 15.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agentskills-http 0.4.0
File Interpreter ABI Platform
agentskills_http-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 30.5 kB

Release files / agentskills_http-0.4.0.tar.gz

Download URL agentskills_http-0.4.0.tar.gz
Size 15.9 kB
Tags Source
SHA-256 checksum
How to use checksums
5cace711c24317940358bc0930a8893ddeb25835dbc5fe0375651a1937d21a49
BLAKE2b-256 checksum
How to use checksums
e5dd06983b181043905112f3ca4cb21c0d455cecda615a453e06aa0489a5ca5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release files / agentskills_http-0.4.0-py3-none-any.whl

Download URL agentskills_http-0.4.0-py3-none-any.whl
Size 14.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e4b26b1dbe6d702a454ff784558ac05f1aec9090beb5c53db23618098822718
BLAKE2b-256 checksum
How to use checksums
eb37c3791ba5445bebbd1b9a99357ed093e6af2ccca4db86bbabc02fdfb1a46e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page