agentskills-http 0.2.1__tar.gz → 0.2.3__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.
- {agentskills_http-0.2.1 → agentskills_http-0.2.3}/PKG-INFO +14 -15
- {agentskills_http-0.2.1 → agentskills_http-0.2.3}/README.md +12 -12
- {agentskills_http-0.2.1 → agentskills_http-0.2.3}/agentskills_http/static.py +57 -33
- {agentskills_http-0.2.1 → agentskills_http-0.2.3}/pyproject.toml +2 -2
- {agentskills_http-0.2.1 → agentskills_http-0.2.3}/agentskills_http/__init__.py +0 -0
- {agentskills_http-0.2.1 → agentskills_http-0.2.3}/agentskills_http/py.typed +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentskills-http
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: HTTP-based skill providers for the Agent Skills format (https://agentskills.io)
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Pratik Panda
|
|
7
|
-
Requires-Python: >=3.12,<
|
|
7
|
+
Requires-Python: >=3.12,<3.14
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
15
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
16
15
|
Requires-Dist: agentskills-core (>=0.1.0,<1.0)
|
|
17
16
|
Requires-Dist: httpx (>=0.27,<1.0)
|
|
@@ -23,12 +22,12 @@ Description-Content-Type: text/markdown
|
|
|
23
22
|
# agentskills-http
|
|
24
23
|
|
|
25
24
|
[](https://pypi.org/project/agentskills-http/)
|
|
26
|
-
[](https://pypi.org/project/agentskills-http/)
|
|
27
26
|
[](https://github.com/pratikxpanda/agentskills-sdk/blob/main/LICENSE)
|
|
28
27
|
|
|
29
28
|
> HTTP static-file skill provider for the [Agent Skills SDK](../../README.md).
|
|
30
29
|
|
|
31
|
-
Serves [Agent Skills](https://agentskills.io) from any static HTTP file host
|
|
30
|
+
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
31
|
|
|
33
32
|
## Installation
|
|
34
33
|
|
|
@@ -36,7 +35,7 @@ Serves [Agent Skills](https://agentskills.io) from any static HTTP file host —
|
|
|
36
35
|
pip install agentskills-http
|
|
37
36
|
```
|
|
38
37
|
|
|
39
|
-
Requires Python 3.12
|
|
38
|
+
Requires Python 3.12 or 3.13. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
|
|
40
39
|
|
|
41
40
|
## Expected URL Layout
|
|
42
41
|
|
|
@@ -97,7 +96,7 @@ provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=
|
|
|
97
96
|
|
|
98
97
|
| Parameter | Type | Default | Description |
|
|
99
98
|
| --- | --- | --- | --- |
|
|
100
|
-
| `base_url` | `str` |
|
|
99
|
+
| `base_url` | `str` | - | Root URL where the skill tree is hosted |
|
|
101
100
|
| `client` | `AsyncClient \| None` | `None` | Pre-configured httpx client (caller manages lifecycle) |
|
|
102
101
|
| `headers` | `dict \| None` | `None` | Extra headers sent with every request |
|
|
103
102
|
| `params` | `dict \| None` | `None` | Query parameters appended to every request |
|
|
@@ -130,21 +129,21 @@ All exceptions inherit from `AgentSkillsError`.
|
|
|
130
129
|
|
|
131
130
|
## Security
|
|
132
131
|
|
|
133
|
-
- **Input validation**
|
|
134
|
-
- **TLS warnings**
|
|
135
|
-
- **Redirect protection**
|
|
136
|
-
- **Timeouts**
|
|
137
|
-
- **Response size limits**
|
|
138
|
-
- **Error-message sanitization**
|
|
132
|
+
- **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.
|
|
133
|
+
- **TLS warnings** - A `UserWarning` is emitted when `base_url` uses unencrypted HTTP. Set `require_tls=True` to reject HTTP URLs entirely.
|
|
134
|
+
- **Redirect protection** - The internally-created HTTP client does not follow redirects by default, preventing open-redirect SSRF.
|
|
135
|
+
- **Timeouts** - Default 30-second timeout on all HTTP requests.
|
|
136
|
+
- **Response size limits** - Responses exceeding 10 MB (default) are rejected before processing. Configure via `max_response_bytes`.
|
|
137
|
+
- **Error-message sanitization** - Error messages omit URLs and include only status codes and generic descriptions, preventing internal URL leakage.
|
|
139
138
|
|
|
140
139
|
For the full security policy, see [SECURITY.md](../../../SECURITY.md).
|
|
141
140
|
|
|
142
141
|
## Deployment Considerations
|
|
143
142
|
|
|
144
|
-
- **Rate limiting**
|
|
143
|
+
- **Rate limiting** - The SDK does not enforce rate limits on MCP tool
|
|
145
144
|
calls or HTTP requests. Deploy behind a reverse proxy or API gateway
|
|
146
145
|
that provides rate limiting in production environments.
|
|
147
|
-
- **Credential management**
|
|
146
|
+
- **Credential management** - Do not store secrets (API keys, SAS
|
|
148
147
|
tokens, Authorization headers) in config files committed to version
|
|
149
148
|
control. Use environment variables or a secret manager instead.
|
|
150
149
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# agentskills-http
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/agentskills-http/)
|
|
4
|
-
[](https://pypi.org/project/agentskills-http/)
|
|
5
5
|
[](https://github.com/pratikxpanda/agentskills-sdk/blob/main/LICENSE)
|
|
6
6
|
|
|
7
7
|
> HTTP static-file skill provider for the [Agent Skills SDK](../../README.md).
|
|
8
8
|
|
|
9
|
-
Serves [Agent Skills](https://agentskills.io) from any static HTTP file host
|
|
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
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ Serves [Agent Skills](https://agentskills.io) from any static HTTP file host —
|
|
|
14
14
|
pip install agentskills-http
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Requires Python 3.12
|
|
17
|
+
Requires Python 3.12 or 3.13. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
|
|
18
18
|
|
|
19
19
|
## Expected URL Layout
|
|
20
20
|
|
|
@@ -75,7 +75,7 @@ provider = HTTPStaticFileSkillProvider("https://cdn.example.com/skills", client=
|
|
|
75
75
|
|
|
76
76
|
| Parameter | Type | Default | Description |
|
|
77
77
|
| --- | --- | --- | --- |
|
|
78
|
-
| `base_url` | `str` |
|
|
78
|
+
| `base_url` | `str` | - | Root URL where the skill tree is hosted |
|
|
79
79
|
| `client` | `AsyncClient \| None` | `None` | Pre-configured httpx client (caller manages lifecycle) |
|
|
80
80
|
| `headers` | `dict \| None` | `None` | Extra headers sent with every request |
|
|
81
81
|
| `params` | `dict \| None` | `None` | Query parameters appended to every request |
|
|
@@ -108,21 +108,21 @@ All exceptions inherit from `AgentSkillsError`.
|
|
|
108
108
|
|
|
109
109
|
## Security
|
|
110
110
|
|
|
111
|
-
- **Input validation**
|
|
112
|
-
- **TLS warnings**
|
|
113
|
-
- **Redirect protection**
|
|
114
|
-
- **Timeouts**
|
|
115
|
-
- **Response size limits**
|
|
116
|
-
- **Error-message sanitization**
|
|
111
|
+
- **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.
|
|
112
|
+
- **TLS warnings** - A `UserWarning` is emitted when `base_url` uses unencrypted HTTP. Set `require_tls=True` to reject HTTP URLs entirely.
|
|
113
|
+
- **Redirect protection** - The internally-created HTTP client does not follow redirects by default, preventing open-redirect SSRF.
|
|
114
|
+
- **Timeouts** - Default 30-second timeout on all HTTP requests.
|
|
115
|
+
- **Response size limits** - Responses exceeding 10 MB (default) are rejected before processing. Configure via `max_response_bytes`.
|
|
116
|
+
- **Error-message sanitization** - Error messages omit URLs and include only status codes and generic descriptions, preventing internal URL leakage.
|
|
117
117
|
|
|
118
118
|
For the full security policy, see [SECURITY.md](../../../SECURITY.md).
|
|
119
119
|
|
|
120
120
|
## Deployment Considerations
|
|
121
121
|
|
|
122
|
-
- **Rate limiting**
|
|
122
|
+
- **Rate limiting** - The SDK does not enforce rate limits on MCP tool
|
|
123
123
|
calls or HTTP requests. Deploy behind a reverse proxy or API gateway
|
|
124
124
|
that provides rate limiting in production environments.
|
|
125
|
-
- **Credential management**
|
|
125
|
+
- **Credential management** - Do not store secrets (API keys, SAS
|
|
126
126
|
tokens, Authorization headers) in config files committed to version
|
|
127
127
|
control. Use environment variables or a secret manager instead.
|
|
128
128
|
|
|
@@ -28,7 +28,6 @@ for non-blocking HTTP requests.
|
|
|
28
28
|
|
|
29
29
|
from __future__ import annotations
|
|
30
30
|
|
|
31
|
-
import logging
|
|
32
31
|
import re
|
|
33
32
|
import warnings
|
|
34
33
|
from typing import Any
|
|
@@ -44,8 +43,6 @@ from agentskills_core import (
|
|
|
44
43
|
split_frontmatter,
|
|
45
44
|
)
|
|
46
45
|
|
|
47
|
-
_logger = logging.getLogger(__name__)
|
|
48
|
-
|
|
49
46
|
# Input validation: identifiers (skill_id, resource name) must be safe
|
|
50
47
|
# URL path segments. Allows alphanumeric, hyphens, dots, underscores.
|
|
51
48
|
# Must start with an alphanumeric character. No path separators or
|
|
@@ -273,6 +270,60 @@ class HTTPStaticFileSkillProvider(SkillProvider):
|
|
|
273
270
|
# Internal helpers
|
|
274
271
|
# ------------------------------------------------------------------
|
|
275
272
|
|
|
273
|
+
async def _stream_bytes(self, url: str, not_found_error: type[Exception]) -> bytes:
|
|
274
|
+
"""Stream a GET request and return the response bytes.
|
|
275
|
+
|
|
276
|
+
Uses ``httpx.AsyncClient.stream`` so that overly large
|
|
277
|
+
responses are detected **during** download rather than after
|
|
278
|
+
the entire body has been buffered into memory.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
url: The URL to fetch.
|
|
282
|
+
not_found_error: Exception type to raise on HTTP 404
|
|
283
|
+
(e.g. :class:`SkillNotFoundError` or
|
|
284
|
+
:class:`ResourceNotFoundError`).
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
not_found_error: On 404.
|
|
288
|
+
AgentSkillsError: On other HTTP/connection errors or if
|
|
289
|
+
the response exceeds *max_response_bytes*.
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
async with self._client.stream("GET", url) as resp:
|
|
293
|
+
if resp.status_code == 404:
|
|
294
|
+
raise not_found_error("Skill content not found")
|
|
295
|
+
try:
|
|
296
|
+
resp.raise_for_status()
|
|
297
|
+
except httpx.HTTPStatusError as exc:
|
|
298
|
+
raise AgentSkillsError(f"HTTP {resp.status_code} error") from exc
|
|
299
|
+
|
|
300
|
+
# Check Content-Length header for an early reject when
|
|
301
|
+
# the server advertises the size up-front.
|
|
302
|
+
cl = resp.headers.get("content-length")
|
|
303
|
+
if cl is not None and int(cl) > self._max_response_bytes:
|
|
304
|
+
raise AgentSkillsError(
|
|
305
|
+
f"Response exceeds maximum size ({self._max_response_bytes} bytes)"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Stream chunks and enforce the byte limit
|
|
309
|
+
# incrementally to avoid buffering the full body.
|
|
310
|
+
chunks: list[bytes] = []
|
|
311
|
+
received = 0
|
|
312
|
+
async for chunk in resp.aiter_bytes():
|
|
313
|
+
received += len(chunk)
|
|
314
|
+
if received > self._max_response_bytes:
|
|
315
|
+
raise AgentSkillsError(
|
|
316
|
+
f"Response exceeds maximum size ({self._max_response_bytes} bytes)"
|
|
317
|
+
)
|
|
318
|
+
chunks.append(chunk)
|
|
319
|
+
|
|
320
|
+
except (SkillNotFoundError, ResourceNotFoundError, AgentSkillsError):
|
|
321
|
+
raise
|
|
322
|
+
except httpx.HTTPError as exc:
|
|
323
|
+
raise AgentSkillsError("HTTP request failed") from exc
|
|
324
|
+
|
|
325
|
+
return b"".join(chunks)
|
|
326
|
+
|
|
276
327
|
async def _get_text(self, url: str) -> str:
|
|
277
328
|
"""GET a URL and return the response text.
|
|
278
329
|
|
|
@@ -281,21 +332,8 @@ class HTTPStaticFileSkillProvider(SkillProvider):
|
|
|
281
332
|
AgentSkillsError: On other HTTP or connection errors,
|
|
282
333
|
or if the response exceeds *max_response_bytes*.
|
|
283
334
|
"""
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
except httpx.HTTPError as exc:
|
|
287
|
-
raise AgentSkillsError("HTTP request failed") from exc
|
|
288
|
-
if resp.status_code == 404:
|
|
289
|
-
raise SkillNotFoundError("Skill content not found")
|
|
290
|
-
try:
|
|
291
|
-
resp.raise_for_status()
|
|
292
|
-
except httpx.HTTPStatusError as exc:
|
|
293
|
-
raise AgentSkillsError(f"HTTP {resp.status_code} error") from exc
|
|
294
|
-
if len(resp.content) > self._max_response_bytes:
|
|
295
|
-
raise AgentSkillsError(
|
|
296
|
-
f"Response exceeds maximum size ({self._max_response_bytes} bytes)"
|
|
297
|
-
)
|
|
298
|
-
return resp.text
|
|
335
|
+
data = await self._stream_bytes(url, SkillNotFoundError)
|
|
336
|
+
return data.decode("utf-8")
|
|
299
337
|
|
|
300
338
|
async def _get_bytes(self, url: str) -> bytes:
|
|
301
339
|
"""GET a URL and return the response bytes.
|
|
@@ -305,21 +343,7 @@ class HTTPStaticFileSkillProvider(SkillProvider):
|
|
|
305
343
|
AgentSkillsError: On other HTTP or connection errors,
|
|
306
344
|
or if the response exceeds *max_response_bytes*.
|
|
307
345
|
"""
|
|
308
|
-
|
|
309
|
-
resp = await self._client.get(url)
|
|
310
|
-
except httpx.HTTPError as exc:
|
|
311
|
-
raise AgentSkillsError("HTTP request failed") from exc
|
|
312
|
-
if resp.status_code == 404:
|
|
313
|
-
raise ResourceNotFoundError("Resource not found")
|
|
314
|
-
try:
|
|
315
|
-
resp.raise_for_status()
|
|
316
|
-
except httpx.HTTPStatusError as exc:
|
|
317
|
-
raise AgentSkillsError(f"HTTP {resp.status_code} error") from exc
|
|
318
|
-
if len(resp.content) > self._max_response_bytes:
|
|
319
|
-
raise AgentSkillsError(
|
|
320
|
-
f"Response exceeds maximum size ({self._max_response_bytes} bytes)"
|
|
321
|
-
)
|
|
322
|
-
return resp.content
|
|
346
|
+
return await self._stream_bytes(url, ResourceNotFoundError)
|
|
323
347
|
|
|
324
348
|
async def _get_skill_md(self, skill_id: str) -> str:
|
|
325
349
|
"""Fetch the full text of a skill's ``SKILL.md``."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "agentskills-http"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "HTTP-based skill providers for the Agent Skills format (https://agentskills.io)"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["Pratik Panda"]
|
|
@@ -17,7 +17,7 @@ classifiers = [
|
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
[tool.poetry.dependencies]
|
|
20
|
-
python = ">=3.12,<
|
|
20
|
+
python = ">=3.12,<3.14"
|
|
21
21
|
agentskills-core = ">=0.1.0,<1.0"
|
|
22
22
|
httpx = ">=0.27,<1.0"
|
|
23
23
|
pyyaml = ">=6.0,<7.0"
|
|
File without changes
|
|
File without changes
|