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.
@@ -1,17 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentskills-http
3
- Version: 0.2.1
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,<4.0
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
  [![PyPI](https://img.shields.io/pypi/v/agentskills-http)](https://pypi.org/project/agentskills-http/)
26
- [![Python 3.12+](https://img.shields.io/pypi/pyversions/agentskills-http)](https://pypi.org/project/agentskills-http/)
25
+ [![Python 3.12 | 3.13](https://img.shields.io/pypi/pyversions/agentskills-http)](https://pypi.org/project/agentskills-http/)
27
26
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 S3, Azure Blob, CDN, GitHub Pages, Nginx, etc. Expects the same directory-tree layout as the filesystem provider, served over HTTP.
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+. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
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` | | Root URL where the skill tree is hosted |
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** 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.
134
- - **TLS warnings** A `UserWarning` is emitted when `base_url` uses unencrypted HTTP. Set `require_tls=True` to reject HTTP URLs entirely.
135
- - **Redirect protection** The internally-created HTTP client does not follow redirects by default, preventing open-redirect SSRF.
136
- - **Timeouts** Default 30-second timeout on all HTTP requests.
137
- - **Response size limits** Responses exceeding 10 MB (default) are rejected before processing. Configure via `max_response_bytes`.
138
- - **Error-message sanitization** Error messages omit URLs and include only status codes and generic descriptions, preventing internal URL leakage.
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** The SDK does not enforce rate limits on MCP tool
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** Do not store secrets (API keys, SAS
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
  [![PyPI](https://img.shields.io/pypi/v/agentskills-http)](https://pypi.org/project/agentskills-http/)
4
- [![Python 3.12+](https://img.shields.io/pypi/pyversions/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
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 S3, Azure Blob, CDN, GitHub Pages, Nginx, etc. Expects the same directory-tree layout as the filesystem provider, served over HTTP.
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+. Installs `agentskills-core`, `httpx`, and `pyyaml` as dependencies.
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` | | Root URL where the skill tree is hosted |
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** 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.
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** The SDK does not enforce rate limits on MCP tool
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** Do not store secrets (API keys, SAS
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
- try:
285
- resp = await self._client.get(url)
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
- try:
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.1"
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,<4.0"
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"