Skip to main content

title: agentskills-http description: Static HTTP file provider for Agent Skills.

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, httpcore, and pyyaml.

v0.7 defaults to public-network destinations only. DNS answers are validated at connection time and the connection is pinned to an approved IP. Redirects are rejected and environment proxies are not inherited. Internal hosts require allow_private_network=True. Use require_tls=True for production sources. See the trust and operability guide.

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, allow_private_network=True
)
# caller is responsible for closing the client

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

Custom clients require explicit allow_private_network=True because DNS, TLS, proxies, and destination restrictions are caller-owned. Apply equivalent network restrictions in that client when destinations are untrusted. Redirects and response-size limits are still enforced by the provider.

API

HTTPStaticFileSkillProvider(base_url, *, client=None, headers=None, params=None, require_tls=False, max_response_bytes=10_485_760, revalidate=False, resource_manifest=False, skill_manifest=False, file_manifest=False, timeout=30.0, max_retries=2, retry_backoff=0.5, max_retry_delay=30.0, allow_private_network=False, observer=None)

Parameter Type Default Description
base_url str - Root URL where the skill tree is hosted. Must be HTTP(S) without credentials, query string, or fragment
client AsyncClient | None None Pre-configured httpx client (caller manages lifecycle). Requires allow_private_network=True
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
file_manifest bool False Enable lossless access using a complete per-skill files list
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
allow_private_network bool False Opt out of public-only destinations. Required for custom clients
observer callable or None None Content-free fetch/cache events with hashed origin

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)
list_files(skill_id) list[str] Complete sorted paths from index.json (requires file_manifest=True)
read_file(skill_id, path) bytes Original bytes, including nested paths (requires file_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.

Lossless File Access

For byte-preserving delivery, publish a complete files list in each skill's index.json. This can coexist with the grouped resource keys:

{
  "files": ["SKILL.md", "index.json", "data/nested.bin", "references/severity.md"],
  "references": ["severity.md"]
}
async with HTTPStaticFileSkillProvider(BASE, file_manifest=True) as provider:
    paths = await provider.list_files("incident-response")
    original = await provider.read_file("incident-response", "SKILL.md")

The host is responsible for listing every file. The provider cannot discover omitted files on a static host. Unlike resource listing, unsafe paths, duplicates, non-string entries, or a missing SKILL.md entry reject the manifest. Enabling resource_manifest alone does not enable lossless file access.

Reads preserve bytes and bypass the parsed SKILL.md cache. Size limits apply, and redirects, partial responses, and unsolicited 304 responses are refused. An injected client must have follow_redirects=False and needs allow_private_network=True. Publish immutable content when building verified manifests. Listing and reading do not form an atomic snapshot.

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 ProviderUnavailableError Yes
Timeouts, connection and protocol errors ProviderUnavailableError Yes
Redirects (3xx) AgentSkillsError No
Destination not allowed by network policy NetworkPolicyError (from agentskills_http) No
401 / 403 AgentSkillsError No
Other 4xx, oversized responses, invalid Content-Length AgentSkillsError No

All exceptions inherit from AgentSkillsError. ProviderUnavailableError is a subclass of SkillUnavailableError, so existing handlers keep working. It is the only failure that verified stale serving in agentskills-mcp-server may cover.

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.
  • Public-network-only by default - DNS is resolved at connection time, every answer must be a public address, and the connection goes to the validated IP, so a rebinding answer cannot reach an internal host. Loopback, private, link-local, multicast, and reserved addresses are refused. allow_private_network=True opts out.
  • Redirects rejected - Redirects are never followed, in every mode, so credentials cannot be forwarded to another origin. Environment proxies are not inherited.
  • Credential-free base_url - Userinfo, query strings, and fragments are refused. Pass credentials through headers or params.
  • 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 HTTP requests or MCP 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.7.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.7.0
File Size Uploaded Provenance Actions
agentskills_http-0.7.0.tar.gz 21.3 kB With provenance Details for agentskills_http-0.7.0.tar.gz

Built distribution (wheel)

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

Total release size: 40.4 kB

Release files / agentskills_http-0.7.0.tar.gz

Download URL agentskills_http-0.7.0.tar.gz
Size 21.3 kB
Tags Source
SHA-256 checksum
How to use checksums
ac4dc7323689c4b4fccd89ad8384f489b2ed48a907b0d9aeeb02a6f3b0623f91
BLAKE2b-256 checksum
How to use checksums
2c72f411b0f276630f2ea12d192c63749acc37c2aa56d18d23001ea6c73013ac
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 Oct 9, 2026.

Transparency log

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

Download URL agentskills_http-0.7.0-py3-none-any.whl
Size 19.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6c54fd19f0295c25e4ef00b7b220f616a23e1f7668ae9abb35afd3fd3a1b66df
BLAKE2b-256 checksum
How to use checksums
39e51c4435eced3cdcfd56b42e24c2a861f8c8c63655b137b2f8bf5476bcfb1b
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 Oct 9, 2026.

Transparency log

Security for agentskills-http 0.7.0

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for agentskills_http-0.7.0.tar.gz

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 Oct 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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