aiohttp-tiny-mcp 0.3.6__tar.gz → 0.3.7__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.
Files changed (46) hide show
  1. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/PKG-INFO +1 -1
  2. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/pyproject.toml +1 -1
  3. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/pyproject.toml.orig +1 -1
  4. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/endpoint.py +3 -8
  5. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/extensions.py +2 -1
  6. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/http_sse.py +2 -6
  7. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/stdio.py +4 -2
  8. aiohttp_tiny_mcp-0.3.7/src/aiohttp_tiny_mcp/tasks.py +37 -0
  9. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/LICENSE +0 -0
  10. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/README.md +0 -0
  11. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/__init__.py +0 -0
  12. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/adapter.py +0 -0
  13. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/auth.py +0 -0
  14. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/client.py +0 -0
  15. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/client_base.py +0 -0
  16. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/console/__init__.py +0 -0
  17. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/console/console.css +0 -0
  18. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/console/console.js +0 -0
  19. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/console/index.html +0 -0
  20. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/core.py +0 -0
  21. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/dispatcher.py +0 -0
  22. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/exchange.py +0 -0
  23. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/hub.py +0 -0
  24. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/models.py +0 -0
  25. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/namespaces.py +0 -0
  26. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/postgres.py +0 -0
  27. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/__init__.py +0 -0
  28. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/selection.py +0 -0
  29. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py +0 -0
  30. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py +0 -0
  31. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py +0 -0
  32. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py +0 -0
  33. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/protocol/v2026_07_28.py +0 -0
  34. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/py.typed +0 -0
  35. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/redis.py +0 -0
  36. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/registry.py +0 -0
  37. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/request_state.py +0 -0
  38. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/schema.py +0 -0
  39. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/sessions.py +0 -0
  40. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/skills.py +0 -0
  41. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/specs.py +0 -0
  42. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/sqlite.py +0 -0
  43. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/sse.py +0 -0
  44. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/stdio_client.py +0 -0
  45. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/subscriptions.py +0 -0
  46. {aiohttp_tiny_mcp-0.3.6 → aiohttp_tiny_mcp-0.3.7}/src/aiohttp_tiny_mcp/testing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohttp-tiny-mcp
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Remote MCP server and client library for aiohttp
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aiohttp-tiny-mcp"
3
- version = "0.3.6"
3
+ version = "0.3.7"
4
4
  description = "Remote MCP server and client library for aiohttp"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aiohttp-tiny-mcp"
3
- version = "0.3.6"
3
+ version = "0.3.7"
4
4
  description = "Remote MCP server and client library for aiohttp"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -7,7 +7,6 @@ import json
7
7
  import logging
8
8
  import re
9
9
  from collections.abc import Mapping
10
- from contextlib import suppress
11
10
  from dataclasses import replace
12
11
  from functools import cached_property
13
12
  from typing import Any
@@ -46,6 +45,7 @@ from .sessions import (
46
45
  )
47
46
  from .sse import SSEResponse
48
47
  from .subscriptions import relays, wanted
48
+ from .tasks import stop
49
49
 
50
50
  log = logging.getLogger("aiohttp_tiny_mcp")
51
51
 
@@ -419,9 +419,7 @@ class Endpoint:
419
419
  if transport is None or transport.is_closing():
420
420
  break
421
421
  finally:
422
- relay.cancel()
423
- with suppress(asyncio.CancelledError, ConnectionError):
424
- await relay
422
+ await stop(relay)
425
423
  return response
426
424
 
427
425
  async def relay_notifications(
@@ -578,10 +576,7 @@ class Endpoint:
578
576
  ex.cancel()
579
577
  raise
580
578
  finally:
581
- if not task.done():
582
- task.cancel()
583
- with suppress(asyncio.CancelledError):
584
- await task
579
+ await stop(task)
585
580
 
586
581
 
587
582
  MCP_ENDPOINT = web.AppKey("mcp_endpoint", Endpoint)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import dataclasses
5
6
  import json
6
7
  from collections.abc import Awaitable, Callable, Iterable, Mapping
7
8
  from copy import deepcopy
@@ -57,7 +58,7 @@ class ExtensionSpec:
57
58
  methods: Mapping[str, Bound]
58
59
  #: Broadcast method to the params field that carries its topic, or None for one
59
60
  #: every listener of the method gets.
60
- notifications: Mapping[str, str | None] = MappingProxyType({})
61
+ notifications: Mapping[str, str | None] = dataclasses.field(default_factory=dict)
61
62
 
62
63
 
63
64
  class ExtensionResult(ResultModel):
@@ -6,7 +6,6 @@ import asyncio
6
6
  import json
7
7
  import logging
8
8
  from collections.abc import Coroutine
9
- from contextlib import suppress
10
9
  from dataclasses import replace
11
10
  from typing import Any
12
11
 
@@ -41,6 +40,7 @@ from .sessions import (
41
40
  )
42
41
  from .sse import SSEResponse
43
42
  from .subscriptions import relays, wanted
43
+ from .tasks import stop
44
44
 
45
45
  log = logging.getLogger(__name__)
46
46
 
@@ -148,11 +148,7 @@ class SseEndpoint:
148
148
  if transport is None or transport.is_closing():
149
149
  break
150
150
  finally:
151
- for task in tasks:
152
- task.cancel()
153
- for task in tasks:
154
- with suppress(asyncio.CancelledError, ConnectionError):
155
- await task
151
+ await stop(*tasks)
156
152
 
157
153
  async def relay(self, response: SSEResponse, events: Subscription) -> None:
158
154
  """Write everything published for this connection, until cancelled."""
@@ -11,6 +11,7 @@ import json
11
11
  import logging
12
12
  import sys
13
13
  from collections.abc import Callable, Mapping
14
+ from contextlib import suppress
14
15
  from dataclasses import replace
15
16
  from typing import Any
16
17
 
@@ -26,6 +27,7 @@ from .dispatcher import Dispatcher
26
27
  from .exchange import Exchange, is_reply, relay_reply
27
28
  from .protocol.selection import AdapterSet
28
29
  from .registry import Registry
30
+ from .tasks import stop
29
31
 
30
32
  log = logging.getLogger("aiohttp_tiny_mcp")
31
33
 
@@ -155,9 +157,9 @@ async def serve_stdio(
155
157
  tasks = []
156
158
  for exchange, task in list(active.values()):
157
159
  exchange.cancel()
158
- task.cancel()
159
160
  tasks.append(task)
160
- await asyncio.gather(*tasks, return_exceptions=True)
161
+ with suppress(Exception):
162
+ await stop(*tasks)
161
163
 
162
164
 
163
165
  async def stdin_reader() -> asyncio.StreamReader:
@@ -0,0 +1,37 @@
1
+ """Ending a task that may not honour its first cancellation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+
8
+ STOP_GRACE_SECONDS = 1.0
9
+
10
+
11
+ async def stop(*tasks: asyncio.Task[Any], grace: float = STOP_GRACE_SECONDS) -> None:
12
+ """Cancel `tasks` and return when every one of them has ended.
13
+
14
+ One cancellation is not always enough. A library may catch it, and on
15
+ Python before 3.12 `asyncio.wait_for` drops it when the future it waits
16
+ on completes in the same loop turn: psycopg_pool waits for a connection
17
+ that way, and a sibling task that is cancelled first hands its
18
+ connection back at exactly that moment. A task still running after
19
+ `grace` seconds is cancelled again, at whatever it awaits by then.
20
+
21
+ A cancelled task and one that ended with `ConnectionError` are what
22
+ stopping looks like. Any other exception is raised, the first one found.
23
+ """
24
+ pending = {task for task in tasks if not task.done()}
25
+ while pending:
26
+ for task in pending:
27
+ task.cancel()
28
+ _, pending = await asyncio.wait(pending, timeout=grace)
29
+ for task in tasks:
30
+ if task.cancelled():
31
+ continue
32
+ error = task.exception()
33
+ if error is not None and not isinstance(error, ConnectionError):
34
+ raise error
35
+
36
+
37
+ __all__ = ["STOP_GRACE_SECONDS", "stop"]