agentskills-http 0.2.3__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentskills-http
3
+ Version: 0.4.0
4
+ Summary: HTTP-based skill providers for the Agent Skills format (https://agentskills.io)
5
+ License: MIT
6
+ Author: Pratik Panda
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Dist: agentskills-core (>=0.4.0,<1.0)
17
+ Requires-Dist: httpx (>=0.27,<1.0)
18
+ Requires-Dist: pyyaml (>=6.0,<7.0)
19
+ Project-URL: Homepage, https://agentskills.io
20
+ Project-URL: Repository, https://github.com/pratikxpanda/agentskills-sdk
21
+ Description-Content-Type: text/markdown
22
+
23
+ # agentskills-http
24
+
25
+ [![PyPI](https://img.shields.io/pypi/v/agentskills-http)](https://pypi.org/project/agentskills-http/)
26
+ [![Python 3.12 | 3.13](https://img.shields.io/pypi/pyversions/agentskills-http)](https://pypi.org/project/agentskills-http/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/pratikxpanda/agentskills-sdk/blob/main/LICENSE)
28
+
29
+ > HTTP static-file skill provider for the [Agent Skills SDK](https://github.com/pratikxpanda/agentskills-sdk).
30
+
31
+ Serves [Agent Skills](https://agentskills.io) 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.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install agentskills-http
37
+ ```
38
+
39
+ Requires Python 3.12 or newer. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
40
+
41
+ ## Expected URL Layout
42
+
43
+ ```text
44
+ https://cdn.example.com/skills/
45
+ ├── incident-response/
46
+ │ ├── SKILL.md
47
+ │ ├── references/severity-levels.md
48
+ │ ├── scripts/page-oncall.sh
49
+ │ └── assets/flowchart.mermaid
50
+ └── another-skill/
51
+ └── SKILL.md
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ```python
57
+ from agentskills_core import SkillRegistry
58
+ from agentskills_http import HTTPStaticFileSkillProvider
59
+
60
+ async with HTTPStaticFileSkillProvider("https://cdn.example.com/skills") as provider:
61
+ registry = SkillRegistry()
62
+ await registry.register("incident-response", provider)
63
+
64
+ skill = registry.get_skill("incident-response")
65
+ meta = await skill.get_metadata()
66
+ body = await skill.get_body()
67
+ ```
68
+
69
+ ### Custom Headers
70
+
71
+ Pass authentication or other headers:
72
+
73
+ ```python
74
+ from agentskills_http import HTTPStaticFileSkillProvider
75
+
76
+ provider = HTTPStaticFileSkillProvider(
77
+ "https://cdn.example.com/skills",
78
+ headers={"Authorization": "Bearer <token>"},
79
+ )
80
+ ```
81
+
82
+ ### Bring Your Own Client
83
+
84
+ Supply a pre-configured `httpx.AsyncClient` for full control over timeouts, proxies, etc.:
85
+
86
+ ```python
87
+ import httpx
88
+ from agentskills_http import HTTPStaticFileSkillProvider
89
+
90
+ client = httpx.AsyncClient(timeout=30, headers={"Authorization": "Bearer <token>"})
91
+ provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=client)
92
+ # caller is responsible for closing the client
93
+ ```
94
+
95
+ > **Note:** `client` and `headers` are mutually exclusive. Configure headers on the client directly when providing your own.
96
+
97
+ ## API
98
+
99
+ ### `HTTPStaticFileSkillProvider(base_url, *, client=None, headers=None, params=None, require_tls=False, max_response_bytes=10_485_760, revalidate=False)`
100
+
101
+ | Parameter | Type | Default | Description |
102
+ | --- | --- | --- | --- |
103
+ | `base_url` | `str` | - | Root URL where the skill tree is hosted |
104
+ | `client` | `AsyncClient \| None` | `None` | Pre-configured httpx client (caller manages lifecycle) |
105
+ | `headers` | `dict \| None` | `None` | Extra headers sent with every request |
106
+ | `params` | `dict \| None` | `None` | Query parameters appended to every request |
107
+ | `require_tls` | `bool` | `False` | Reject `http://` URLs with `ValueError` |
108
+ | `max_response_bytes` | `int` | `10_485_760` | Maximum allowed response size in bytes |
109
+ | `revalidate` | `bool` | `False` | Re-check cached `SKILL.md` on every access with `If-None-Match` / `If-Modified-Since` |
110
+ | `resource_manifest` | `bool` | `False` | Enable `list_resources()` by reading a per-skill `index.json` |
111
+ | `skill_manifest` | `bool` | `False` | Enable `discover()` by reading a root `index.json` |
112
+ | `timeout` | `float` | `30.0` | Request timeout in seconds (ignored when you supply `client`) |
113
+ | `max_retries` | `int` | `2` | Retries after the initial attempt, for retryable failures only |
114
+ | `retry_backoff` | `float` | `0.5` | Base delay in seconds for exponential backoff |
115
+ | `max_retry_delay` | `float` | `30.0` | Ceiling on any single backoff sleep |
116
+
117
+ > **Note:** `client` and `headers`/`params` are mutually exclusive. Configure headers and params on the client directly when providing your own.
118
+
119
+ | Method | Returns | Description |
120
+ | --- | --- | --- |
121
+ | `get_metadata(skill_id)` | `dict[str, Any]` | Parsed YAML frontmatter from `SKILL.md` |
122
+ | `get_body(skill_id)` | `str` | Markdown body after the frontmatter |
123
+ | `get_script(skill_id, name)` | `bytes` | Raw script content |
124
+ | `get_asset(skill_id, name)` | `bytes` | Raw asset content |
125
+ | `get_reference(skill_id, name)` | `bytes` | Raw reference content |
126
+ | `list_resources(skill_id)` | `dict[str, list[str]]` | Resource names from `index.json` (requires `resource_manifest=True`) |
127
+ | `discover()` | `list[str]` | Skill IDs from the root `index.json` (requires `skill_manifest=True`) |
128
+ | `invalidate(skill_id=None)` | `None` | Drop cached `SKILL.md` content for one skill, or all skills |
129
+ | `aclose()` | `None` | Close the HTTP client (if owned by the provider) |
130
+
131
+ Supports `async with` for automatic cleanup.
132
+
133
+ ## Resource Discovery
134
+
135
+ 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.
136
+
137
+ If you control the host, publish a small manifest at `{base_url}/{skill_id}/index.json`:
138
+
139
+ ```json
140
+ {
141
+ "references": ["severity-levels.md"],
142
+ "scripts": ["page-oncall.sh"],
143
+ "assets": ["flowchart.mermaid"]
144
+ }
145
+ ```
146
+
147
+ Then opt in:
148
+
149
+ ```python
150
+ provider = HTTPStaticFileSkillProvider(BASE, resource_manifest=True)
151
+ listing = await provider.list_resources("incident-response")
152
+ ```
153
+
154
+ 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.
155
+
156
+ ## Skill Discovery
157
+
158
+ The same problem one level up: nothing on a static host says which skills exist. Publish the same
159
+ file at the root, listing skills instead of resources:
160
+
161
+ ```json
162
+ { "skills": ["incident-response", "api-style-guide"] }
163
+ ```
164
+
165
+ Then opt in and register the whole host at once:
166
+
167
+ ```python
168
+ async with HTTPStaticFileSkillProvider(BASE, skill_manifest=True) as provider:
169
+ await registry.register_all(provider)
170
+ ```
171
+
172
+ One filename and one shape — an object mapping a category to a list of names — at two depths,
173
+ rather than two manifest formats to keep in step. Unsafe and duplicate IDs are dropped as above.
174
+ Without `skill_manifest=True`, or when the root publishes no manifest, `discover()` raises
175
+ `DiscoveryNotSupportedError`.
176
+
177
+ ## Caching
178
+
179
+ `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.
180
+
181
+ 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:
182
+
183
+ ```python
184
+ provider = HTTPStaticFileSkillProvider(BASE, revalidate=True)
185
+ ```
186
+
187
+ 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.
188
+
189
+ ## Error Handling
190
+
191
+ | Scenario | Exception | Retried |
192
+ | --- | --- | --- |
193
+ | `404` / `410` on `SKILL.md` | `SkillNotFoundError` | No |
194
+ | `404` / `410` on a resource | `ResourceNotFoundError` | No |
195
+ | `5xx`, `408`, `425`, `429` | `SkillUnavailableError` | Yes |
196
+ | Timeouts, connection and protocol errors | `SkillUnavailableError` | Yes |
197
+ | `401` / `403` | `AgentSkillsError` | No |
198
+ | Other `4xx`, oversized responses | `AgentSkillsError` | No |
199
+
200
+ All exceptions inherit from `AgentSkillsError`.
201
+
202
+ 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.
203
+
204
+ ### Retries
205
+
206
+ Retryable failures are retried with exponential backoff and full jitter:
207
+
208
+ ```python
209
+ provider = HTTPStaticFileSkillProvider(
210
+ BASE,
211
+ max_retries=2, # attempts after the first; 0 disables
212
+ retry_backoff=0.5, # base delay in seconds
213
+ max_retry_delay=30.0, # ceiling on any single sleep
214
+ )
215
+ ```
216
+
217
+ 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.
218
+
219
+ `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.
220
+
221
+ ## Security
222
+
223
+ - **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.
224
+ - **TLS warnings** - A `UserWarning` is emitted when `base_url` uses unencrypted HTTP. Set `require_tls=True` to reject HTTP URLs entirely.
225
+ - **Redirect protection** - The internally-created HTTP client does not follow redirects by default, preventing open-redirect SSRF.
226
+ - **Timeouts** - Default 30-second timeout on all HTTP requests. Configure via `timeout`.
227
+ - **Response size limits** - Responses exceeding 10 MB (default) are rejected before processing. Configure via `max_response_bytes`.
228
+ - **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.
229
+
230
+ For the full security policy, see [SECURITY.md](https://github.com/pratikxpanda/agentskills-sdk/blob/main/SECURITY.md).
231
+
232
+ ## Deployment Considerations
233
+
234
+ - **Rate limiting** - The SDK does not enforce rate limits on MCP tool
235
+ calls or HTTP requests. Deploy behind a reverse proxy or API gateway
236
+ that provides rate limiting in production environments.
237
+ - **Credential management** - Do not store secrets (API keys, SAS
238
+ tokens, Authorization headers) in config files committed to version
239
+ control. Use environment variables or a secret manager instead.
240
+
241
+ ## License
242
+
243
+ MIT
244
+
@@ -0,0 +1,221 @@
1
+ # agentskills-http
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/agentskills-http)](https://pypi.org/project/agentskills-http/)
4
+ [![Python 3.12 | 3.13](https://img.shields.io/pypi/pyversions/agentskills-http)](https://pypi.org/project/agentskills-http/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/pratikxpanda/agentskills-sdk/blob/main/LICENSE)
6
+
7
+ > HTTP static-file skill provider for the [Agent Skills SDK](https://github.com/pratikxpanda/agentskills-sdk).
8
+
9
+ Serves [Agent Skills](https://agentskills.io) 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.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install agentskills-http
15
+ ```
16
+
17
+ Requires Python 3.12 or newer. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
18
+
19
+ ## Expected URL Layout
20
+
21
+ ```text
22
+ https://cdn.example.com/skills/
23
+ ├── incident-response/
24
+ │ ├── SKILL.md
25
+ │ ├── references/severity-levels.md
26
+ │ ├── scripts/page-oncall.sh
27
+ │ └── assets/flowchart.mermaid
28
+ └── another-skill/
29
+ └── SKILL.md
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```python
35
+ from agentskills_core import SkillRegistry
36
+ from agentskills_http import HTTPStaticFileSkillProvider
37
+
38
+ async with HTTPStaticFileSkillProvider("https://cdn.example.com/skills") as provider:
39
+ registry = SkillRegistry()
40
+ await registry.register("incident-response", provider)
41
+
42
+ skill = registry.get_skill("incident-response")
43
+ meta = await skill.get_metadata()
44
+ body = await skill.get_body()
45
+ ```
46
+
47
+ ### Custom Headers
48
+
49
+ Pass authentication or other headers:
50
+
51
+ ```python
52
+ from agentskills_http import HTTPStaticFileSkillProvider
53
+
54
+ provider = HTTPStaticFileSkillProvider(
55
+ "https://cdn.example.com/skills",
56
+ headers={"Authorization": "Bearer <token>"},
57
+ )
58
+ ```
59
+
60
+ ### Bring Your Own Client
61
+
62
+ Supply a pre-configured `httpx.AsyncClient` for full control over timeouts, proxies, etc.:
63
+
64
+ ```python
65
+ import httpx
66
+ from agentskills_http import HTTPStaticFileSkillProvider
67
+
68
+ client = httpx.AsyncClient(timeout=30, headers={"Authorization": "Bearer <token>"})
69
+ provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=client)
70
+ # caller is responsible for closing the client
71
+ ```
72
+
73
+ > **Note:** `client` and `headers` are mutually exclusive. Configure headers on the client directly when providing your own.
74
+
75
+ ## API
76
+
77
+ ### `HTTPStaticFileSkillProvider(base_url, *, client=None, headers=None, params=None, require_tls=False, max_response_bytes=10_485_760, revalidate=False)`
78
+
79
+ | Parameter | Type | Default | Description |
80
+ | --- | --- | --- | --- |
81
+ | `base_url` | `str` | - | Root URL where the skill tree is hosted |
82
+ | `client` | `AsyncClient \| None` | `None` | Pre-configured httpx client (caller manages lifecycle) |
83
+ | `headers` | `dict \| None` | `None` | Extra headers sent with every request |
84
+ | `params` | `dict \| None` | `None` | Query parameters appended to every request |
85
+ | `require_tls` | `bool` | `False` | Reject `http://` URLs with `ValueError` |
86
+ | `max_response_bytes` | `int` | `10_485_760` | Maximum allowed response size in bytes |
87
+ | `revalidate` | `bool` | `False` | Re-check cached `SKILL.md` on every access with `If-None-Match` / `If-Modified-Since` |
88
+ | `resource_manifest` | `bool` | `False` | Enable `list_resources()` by reading a per-skill `index.json` |
89
+ | `skill_manifest` | `bool` | `False` | Enable `discover()` by reading a root `index.json` |
90
+ | `timeout` | `float` | `30.0` | Request timeout in seconds (ignored when you supply `client`) |
91
+ | `max_retries` | `int` | `2` | Retries after the initial attempt, for retryable failures only |
92
+ | `retry_backoff` | `float` | `0.5` | Base delay in seconds for exponential backoff |
93
+ | `max_retry_delay` | `float` | `30.0` | Ceiling on any single backoff sleep |
94
+
95
+ > **Note:** `client` and `headers`/`params` are mutually exclusive. Configure headers and params on the client directly when providing your own.
96
+
97
+ | Method | Returns | Description |
98
+ | --- | --- | --- |
99
+ | `get_metadata(skill_id)` | `dict[str, Any]` | Parsed YAML frontmatter from `SKILL.md` |
100
+ | `get_body(skill_id)` | `str` | Markdown body after the frontmatter |
101
+ | `get_script(skill_id, name)` | `bytes` | Raw script content |
102
+ | `get_asset(skill_id, name)` | `bytes` | Raw asset content |
103
+ | `get_reference(skill_id, name)` | `bytes` | Raw reference content |
104
+ | `list_resources(skill_id)` | `dict[str, list[str]]` | Resource names from `index.json` (requires `resource_manifest=True`) |
105
+ | `discover()` | `list[str]` | Skill IDs from the root `index.json` (requires `skill_manifest=True`) |
106
+ | `invalidate(skill_id=None)` | `None` | Drop cached `SKILL.md` content for one skill, or all skills |
107
+ | `aclose()` | `None` | Close the HTTP client (if owned by the provider) |
108
+
109
+ Supports `async with` for automatic cleanup.
110
+
111
+ ## Resource Discovery
112
+
113
+ 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.
114
+
115
+ If you control the host, publish a small manifest at `{base_url}/{skill_id}/index.json`:
116
+
117
+ ```json
118
+ {
119
+ "references": ["severity-levels.md"],
120
+ "scripts": ["page-oncall.sh"],
121
+ "assets": ["flowchart.mermaid"]
122
+ }
123
+ ```
124
+
125
+ Then opt in:
126
+
127
+ ```python
128
+ provider = HTTPStaticFileSkillProvider(BASE, resource_manifest=True)
129
+ listing = await provider.list_resources("incident-response")
130
+ ```
131
+
132
+ 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.
133
+
134
+ ## Skill Discovery
135
+
136
+ The same problem one level up: nothing on a static host says which skills exist. Publish the same
137
+ file at the root, listing skills instead of resources:
138
+
139
+ ```json
140
+ { "skills": ["incident-response", "api-style-guide"] }
141
+ ```
142
+
143
+ Then opt in and register the whole host at once:
144
+
145
+ ```python
146
+ async with HTTPStaticFileSkillProvider(BASE, skill_manifest=True) as provider:
147
+ await registry.register_all(provider)
148
+ ```
149
+
150
+ One filename and one shape — an object mapping a category to a list of names — at two depths,
151
+ rather than two manifest formats to keep in step. Unsafe and duplicate IDs are dropped as above.
152
+ Without `skill_manifest=True`, or when the root publishes no manifest, `discover()` raises
153
+ `DiscoveryNotSupportedError`.
154
+
155
+ ## Caching
156
+
157
+ `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.
158
+
159
+ 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:
160
+
161
+ ```python
162
+ provider = HTTPStaticFileSkillProvider(BASE, revalidate=True)
163
+ ```
164
+
165
+ 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.
166
+
167
+ ## Error Handling
168
+
169
+ | Scenario | Exception | Retried |
170
+ | --- | --- | --- |
171
+ | `404` / `410` on `SKILL.md` | `SkillNotFoundError` | No |
172
+ | `404` / `410` on a resource | `ResourceNotFoundError` | No |
173
+ | `5xx`, `408`, `425`, `429` | `SkillUnavailableError` | Yes |
174
+ | Timeouts, connection and protocol errors | `SkillUnavailableError` | Yes |
175
+ | `401` / `403` | `AgentSkillsError` | No |
176
+ | Other `4xx`, oversized responses | `AgentSkillsError` | No |
177
+
178
+ All exceptions inherit from `AgentSkillsError`.
179
+
180
+ 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.
181
+
182
+ ### Retries
183
+
184
+ Retryable failures are retried with exponential backoff and full jitter:
185
+
186
+ ```python
187
+ provider = HTTPStaticFileSkillProvider(
188
+ BASE,
189
+ max_retries=2, # attempts after the first; 0 disables
190
+ retry_backoff=0.5, # base delay in seconds
191
+ max_retry_delay=30.0, # ceiling on any single sleep
192
+ )
193
+ ```
194
+
195
+ 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.
196
+
197
+ `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.
198
+
199
+ ## Security
200
+
201
+ - **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.
202
+ - **TLS warnings** - A `UserWarning` is emitted when `base_url` uses unencrypted HTTP. Set `require_tls=True` to reject HTTP URLs entirely.
203
+ - **Redirect protection** - The internally-created HTTP client does not follow redirects by default, preventing open-redirect SSRF.
204
+ - **Timeouts** - Default 30-second timeout on all HTTP requests. Configure via `timeout`.
205
+ - **Response size limits** - Responses exceeding 10 MB (default) are rejected before processing. Configure via `max_response_bytes`.
206
+ - **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.
207
+
208
+ For the full security policy, see [SECURITY.md](https://github.com/pratikxpanda/agentskills-sdk/blob/main/SECURITY.md).
209
+
210
+ ## Deployment Considerations
211
+
212
+ - **Rate limiting** - The SDK does not enforce rate limits on MCP tool
213
+ calls or HTTP requests. Deploy behind a reverse proxy or API gateway
214
+ that provides rate limiting in production environments.
215
+ - **Credential management** - Do not store secrets (API keys, SAS
216
+ tokens, Authorization headers) in config files committed to version
217
+ control. Use environment variables or a secret manager instead.
218
+
219
+ ## License
220
+
221
+ MIT
@@ -1,16 +1,16 @@
1
- """HTTP-based skill providers for the Agent Skills format.
2
-
3
- This package provides :class:`HTTPStaticFileSkillProvider`, a concrete
4
- implementation of :class:`~agentskills_core.SkillProvider` that fetches
5
- `Agent Skills <https://agentskills.io>`_ from a static HTTP file host
6
- (S3, Azure Blob Storage, CDN, GitHub Pages, or any web server serving
7
- raw files).
8
-
9
- Install::
10
-
11
- pip install agentskills-http
12
- """
13
-
14
- from agentskills_http.static import HTTPStaticFileSkillProvider
15
-
16
- __all__ = ["HTTPStaticFileSkillProvider"]
1
+ """HTTP-based skill providers for the Agent Skills format.
2
+
3
+ This package provides :class:`HTTPStaticFileSkillProvider`, a concrete
4
+ implementation of :class:`~agentskills_core.SkillProvider` that fetches
5
+ `Agent Skills <https://agentskills.io>`_ from a static HTTP file host
6
+ (S3, Azure Blob Storage, CDN, GitHub Pages, or any web server serving
7
+ raw files).
8
+
9
+ Install::
10
+
11
+ pip install agentskills-http
12
+ """
13
+
14
+ from agentskills_http.static import HTTPStaticFileSkillProvider
15
+
16
+ __all__ = ["HTTPStaticFileSkillProvider"]