aiohttp-tiny-mcp 0.3.4__tar.gz → 0.3.5__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.
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/PKG-INFO +1 -1
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/pyproject.toml +1 -1
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/pyproject.toml.orig +1 -1
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/adapter.py +1 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/client.py +49 -7
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/core.py +1 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/endpoint.py +31 -12
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/postgres.py +19 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/redis.py +6 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/sessions.py +18 -3
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/sqlite.py +8 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/LICENSE +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/README.md +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/auth.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/client_base.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/console/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/console/console.css +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/console/console.js +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/console/index.html +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/dispatcher.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/exchange.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/extensions.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/http_sse.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/hub.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/models.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/namespaces.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/selection.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2026_07_28.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/py.typed +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/registry.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/request_state.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/schema.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/skills.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/specs.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/sse.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/stdio.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/stdio_client.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/subscriptions.py +0 -0
- {aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/testing.py +0 -0
|
@@ -90,6 +90,7 @@ class Adapter(ABC):
|
|
|
90
90
|
FailureKind.MALFORMED: (-32600, 400),
|
|
91
91
|
FailureKind.INVALID_PARAMS: (-32602, 200),
|
|
92
92
|
FailureKind.ORIGIN_REJECTED: (-32600, 403),
|
|
93
|
+
FailureKind.SESSION_NOT_FOUND: (-32001, 404),
|
|
93
94
|
FailureKind.INTERNAL: (-32603, 200),
|
|
94
95
|
FailureKind.INVALID_ARGUMENTS: (-32603, 200),
|
|
95
96
|
FailureKind.UNKNOWN_METHOD: (-32601, 200),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import logging
|
|
6
7
|
from collections.abc import AsyncIterator, Callable, Mapping
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -19,6 +20,8 @@ NOTIFICATIONS_METHOD = "notifications/message"
|
|
|
19
20
|
|
|
20
21
|
BASE_HEADERS = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
|
|
21
22
|
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
async def frames(resp: aiohttp.ClientResponse) -> AsyncIterator[dict[str, Any]]:
|
|
24
27
|
"""Yield JSON or SSE messages immediately; waiting for EOF would deadlock pushed questions."""
|
|
@@ -76,23 +79,62 @@ class Client(BaseClient):
|
|
|
76
79
|
headers["Mcp-Session-Id"] = self.session_id
|
|
77
80
|
return headers
|
|
78
81
|
|
|
82
|
+
def lost_session(self, resp: aiohttp.ClientResponse) -> bool:
|
|
83
|
+
"""Whether the server no longer holds the session this request named.
|
|
84
|
+
|
|
85
|
+
The spec answers such a request with 404. A 404 without a session id
|
|
86
|
+
in flight is an ordinary reply and is read as one.
|
|
87
|
+
"""
|
|
88
|
+
return resp.status == 404 and self.session_id is not None
|
|
89
|
+
|
|
90
|
+
async def reopen(self) -> None:
|
|
91
|
+
"""Start over with initialize, which mints a session and restores the log level."""
|
|
92
|
+
log.debug("session %s is gone, opening a new one", self.session_id)
|
|
93
|
+
self.session_id = None
|
|
94
|
+
await self.initialize()
|
|
95
|
+
|
|
79
96
|
async def exchange(
|
|
80
97
|
self, envelope: dict[str, Any], *, method: str, name: str | None
|
|
81
98
|
) -> AsyncIterator[dict[str, Any]]:
|
|
82
99
|
assert self.session is not None, "use 'async with Client(...) as client:'"
|
|
83
100
|
tool = self.tool_definitions.get(name) if name is not None else None
|
|
84
|
-
|
|
85
|
-
async with self.session.post(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
params = envelope.get("params")
|
|
102
|
+
async with self.session.post(
|
|
103
|
+
self.base_url, json=envelope, headers=self.headers(method, name, params, tool)
|
|
104
|
+
) as resp:
|
|
105
|
+
if not self.lost_session(resp):
|
|
106
|
+
self.adopt(resp)
|
|
107
|
+
async for frame in frames(resp):
|
|
108
|
+
yield frame
|
|
109
|
+
return
|
|
110
|
+
# Retry once on a fresh session; a second 404 is reported as any other reply.
|
|
111
|
+
await self.reopen()
|
|
112
|
+
async with self.session.post(
|
|
113
|
+
self.base_url, json=envelope, headers=self.headers(method, name, params, tool)
|
|
114
|
+
) as resp:
|
|
115
|
+
self.adopt(resp)
|
|
89
116
|
async for frame in frames(resp):
|
|
90
117
|
yield frame
|
|
91
118
|
|
|
119
|
+
def adopt(self, resp: aiohttp.ClientResponse) -> None:
|
|
120
|
+
"""Keep the session id a handshake reply carries."""
|
|
121
|
+
issued = resp.headers.get("Mcp-Session-Id")
|
|
122
|
+
if issued:
|
|
123
|
+
self.session_id = issued
|
|
124
|
+
|
|
92
125
|
async def send_notification(self, envelope: dict[str, Any], *, method: str) -> None:
|
|
93
126
|
assert self.session is not None, "use 'async with Client(...) as client:'"
|
|
94
|
-
|
|
95
|
-
async with self.session.post(
|
|
127
|
+
params = envelope.get("params")
|
|
128
|
+
async with self.session.post(
|
|
129
|
+
self.base_url, json=envelope, headers=self.headers(method, params=params)
|
|
130
|
+
) as resp:
|
|
131
|
+
if not self.lost_session(resp):
|
|
132
|
+
await resp.read()
|
|
133
|
+
return
|
|
134
|
+
await self.reopen()
|
|
135
|
+
async with self.session.post(
|
|
136
|
+
self.base_url, json=envelope, headers=self.headers(method, params=params)
|
|
137
|
+
) as resp:
|
|
96
138
|
await resp.read()
|
|
97
139
|
|
|
98
140
|
async def stream_notifications(
|
|
@@ -89,6 +89,7 @@ class FailureKind(StringEnum):
|
|
|
89
89
|
HEADER_MISMATCH = "header_mismatch"
|
|
90
90
|
UNSUPPORTED_VERSION = "unsupported_version"
|
|
91
91
|
ORIGIN_REJECTED = "origin_rejected"
|
|
92
|
+
SESSION_NOT_FOUND = "session_not_found"
|
|
92
93
|
INPUT_UNSUPPORTED = "input_unsupported"
|
|
93
94
|
MISSING_REQUIRED_CAPABILITY = "missing_required_capability"
|
|
94
95
|
INTERNAL = "internal"
|
|
@@ -291,14 +291,12 @@ class Endpoint:
|
|
|
291
291
|
await relay_reply(self.registry.hub, pre.body)
|
|
292
292
|
return web.Response(status=202)
|
|
293
293
|
|
|
294
|
-
session = await self.load_session(request)
|
|
295
|
-
if not self.owns(session, principal):
|
|
296
|
-
session = None
|
|
297
|
-
held = self.open_values(request, session)
|
|
298
294
|
try:
|
|
295
|
+
session = await self.load_session(request, principal)
|
|
299
296
|
adapter = self.adapters.select(pre, stored_version(session))
|
|
300
297
|
except Rejected as e:
|
|
301
298
|
return self.render_failure(self.adapters.fallback(), e.failure)
|
|
299
|
+
held = self.open_values(request, session)
|
|
302
300
|
|
|
303
301
|
log.debug("<- [%s] %s", adapter.version, raw.decode("utf-8", "replace"))
|
|
304
302
|
|
|
@@ -399,9 +397,10 @@ class Endpoint:
|
|
|
399
397
|
assert self.registry.auth is not None
|
|
400
398
|
return self.refuse(self.registry.auth, refusal)
|
|
401
399
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
400
|
+
try:
|
|
401
|
+
record = await self.load_session(request, principal)
|
|
402
|
+
except Rejected as e:
|
|
403
|
+
return self.render_failure(self.adapters.fallback(), e.failure)
|
|
405
404
|
adapter = self.stream_adapter(request, record)
|
|
406
405
|
if not adapter.has_handshake:
|
|
407
406
|
return web.Response(
|
|
@@ -435,9 +434,7 @@ class Endpoint:
|
|
|
435
434
|
found = await events.poll()
|
|
436
435
|
if not found:
|
|
437
436
|
continue
|
|
438
|
-
accepted = wanted(
|
|
439
|
-
capabilities, self.open_values(request, await self.load_session(request))
|
|
440
|
-
)
|
|
437
|
+
accepted = wanted(capabilities, self.open_values(request, await self.stored(request)))
|
|
441
438
|
for event in found:
|
|
442
439
|
if relays(event.message, accepted):
|
|
443
440
|
text = json.dumps(event.message, ensure_ascii=False)
|
|
@@ -472,13 +469,35 @@ class Endpoint:
|
|
|
472
469
|
return True
|
|
473
470
|
return principal is not None and principal.identity == owner
|
|
474
471
|
|
|
475
|
-
async def
|
|
476
|
-
"""
|
|
472
|
+
async def stored(self, request: web.Request) -> SessionRecord | None:
|
|
473
|
+
"""The record the session header names, or None where there is no header or no record."""
|
|
477
474
|
session_id = request.headers.get(SESSION_HEADER)
|
|
478
475
|
if not session_id:
|
|
479
476
|
return None
|
|
480
477
|
return await self.registry.session_store.get(scoped(session_id))
|
|
481
478
|
|
|
479
|
+
async def load_session(self, request: web.Request, principal: Any) -> SessionRecord | None:
|
|
480
|
+
"""Load the legacy handshake this request names, or None without a session header.
|
|
481
|
+
|
|
482
|
+
Raises `Rejected` where the header names a session the store does not
|
|
483
|
+
hold or another principal owns. The spec answers that with 404 so the
|
|
484
|
+
client opens a new session with `initialize` instead of going on with
|
|
485
|
+
capabilities and a log level this server no longer remembers.
|
|
486
|
+
|
|
487
|
+
A found session has its TTL renewed: it lives while the client keeps
|
|
488
|
+
talking, not for a fixed time after the handshake.
|
|
489
|
+
"""
|
|
490
|
+
session_id = request.headers.get(SESSION_HEADER)
|
|
491
|
+
if not session_id:
|
|
492
|
+
return None
|
|
493
|
+
record = await self.registry.session_store.get(scoped(session_id))
|
|
494
|
+
if record is None or not self.owns(record, principal):
|
|
495
|
+
raise Rejected(Failure(FailureKind.SESSION_NOT_FOUND, "session not found"))
|
|
496
|
+
await self.registry.session_store.touch(
|
|
497
|
+
scoped(session_id), ttl_seconds=self.registry.session_ttl_seconds
|
|
498
|
+
)
|
|
499
|
+
return record
|
|
500
|
+
|
|
482
501
|
def open_values(self, request: web.Request, record: SessionRecord | None) -> Session | None:
|
|
483
502
|
"""The application-owned half of this request's session, if any."""
|
|
484
503
|
session_id = request.headers.get(SESSION_HEADER)
|
|
@@ -301,6 +301,19 @@ class PostgresSessionStore(SessionStore):
|
|
|
301
301
|
]
|
|
302
302
|
)
|
|
303
303
|
|
|
304
|
+
@cached_property
|
|
305
|
+
def _query_touch(self) -> sql.Composed:
|
|
306
|
+
return sql.Composed(
|
|
307
|
+
[
|
|
308
|
+
sql.SQL("UPDATE "),
|
|
309
|
+
self.storage.sessions_table,
|
|
310
|
+
sql.SQL("""
|
|
311
|
+
SET expires_at = now() + make_interval(secs => %s)
|
|
312
|
+
WHERE id = %s AND expires_at > now()
|
|
313
|
+
"""),
|
|
314
|
+
]
|
|
315
|
+
)
|
|
316
|
+
|
|
304
317
|
@cached_property
|
|
305
318
|
def _query_delete(self) -> sql.Composed:
|
|
306
319
|
return sql.Composed(
|
|
@@ -343,6 +356,12 @@ class PostgresSessionStore(SessionStore):
|
|
|
343
356
|
)
|
|
344
357
|
return found.rowcount > 0
|
|
345
358
|
|
|
359
|
+
async def touch(self, session_id: str, *, ttl_seconds: int) -> bool:
|
|
360
|
+
pool = await self.storage.open()
|
|
361
|
+
async with pool.connection() as connection:
|
|
362
|
+
found = await connection.execute(self._query_touch, (ttl_seconds, session_id))
|
|
363
|
+
return found.rowcount > 0
|
|
364
|
+
|
|
346
365
|
async def delete(self, session_id: str) -> None:
|
|
347
366
|
pool = await self.storage.open()
|
|
348
367
|
async with pool.connection() as connection:
|
|
@@ -126,6 +126,12 @@ class RedisSessionStore(SessionStore):
|
|
|
126
126
|
)
|
|
127
127
|
return bool(written)
|
|
128
128
|
|
|
129
|
+
async def touch(self, session_id: str, *, ttl_seconds: int) -> bool:
|
|
130
|
+
"""EXPIRE answers 1 only for a key that exists, so a lost session reads False."""
|
|
131
|
+
client = await self.storage.open()
|
|
132
|
+
renewed: Any = await client.expire(self.key(session_id), ttl_seconds)
|
|
133
|
+
return bool(renewed)
|
|
134
|
+
|
|
129
135
|
async def delete(self, session_id: str) -> None:
|
|
130
136
|
client = await self.storage.open()
|
|
131
137
|
await client.delete(self.key(session_id))
|
|
@@ -43,11 +43,12 @@ class SessionStore(Protocol):
|
|
|
43
43
|
"""Application-provided storage, safe across workers.
|
|
44
44
|
|
|
45
45
|
create() atomically returns False for an existing id. save() atomically returns False when
|
|
46
|
-
expected_version no longer matches.
|
|
47
|
-
|
|
46
|
+
expected_version no longer matches. touch() renews the TTL without a write to the data, so
|
|
47
|
+
a session stays alive as long as its client keeps sending requests. Store only
|
|
48
|
+
JSON-serializable values, never requests, sockets, queues, or tasks.
|
|
48
49
|
|
|
49
50
|
Subclass it to have the methods checked and the missing ones refused, or
|
|
50
|
-
supply any object with these
|
|
51
|
+
supply any object with these five methods: this is a protocol, so a
|
|
51
52
|
backend that inherits nothing is still a SessionStore.
|
|
52
53
|
"""
|
|
53
54
|
|
|
@@ -70,6 +71,12 @@ class SessionStore(Protocol):
|
|
|
70
71
|
) -> bool:
|
|
71
72
|
"""Replace the data and renew the TTL, or return False where the version moved."""
|
|
72
73
|
|
|
74
|
+
@abstractmethod
|
|
75
|
+
async def touch(self, session_id: str, *, ttl_seconds: int) -> bool:
|
|
76
|
+
"""Renew the TTL of a live record, keeping its data and version. Return False where
|
|
77
|
+
it is missing or expired.
|
|
78
|
+
"""
|
|
79
|
+
|
|
73
80
|
@abstractmethod
|
|
74
81
|
async def delete(self, session_id: str) -> None:
|
|
75
82
|
"""Forget it."""
|
|
@@ -118,6 +125,14 @@ class MemorySessionStore(SessionStore):
|
|
|
118
125
|
self.records[session_id] = (dict(data), expected_version + 1, self.clock() + ttl_seconds)
|
|
119
126
|
return True
|
|
120
127
|
|
|
128
|
+
async def touch(self, session_id: str, *, ttl_seconds: int) -> bool:
|
|
129
|
+
entry = self.live(session_id)
|
|
130
|
+
if entry is None:
|
|
131
|
+
return False
|
|
132
|
+
data, version, _ = entry
|
|
133
|
+
self.records[session_id] = (data, version, self.clock() + ttl_seconds)
|
|
134
|
+
return True
|
|
135
|
+
|
|
121
136
|
async def delete(self, session_id: str) -> None:
|
|
122
137
|
self.records.pop(session_id, None)
|
|
123
138
|
|
|
@@ -165,6 +165,14 @@ class SqliteSessionStore(SessionStore):
|
|
|
165
165
|
)
|
|
166
166
|
return cursor.rowcount > 0
|
|
167
167
|
|
|
168
|
+
async def touch(self, session_id: str, *, ttl_seconds: int) -> bool:
|
|
169
|
+
connection = await self.storage.open()
|
|
170
|
+
cursor = await connection.execute(
|
|
171
|
+
f"UPDATE mcp_sessions SET expires_at = {NOW} + ? WHERE id = ? AND expires_at > {NOW}",
|
|
172
|
+
(ttl_seconds, session_id),
|
|
173
|
+
)
|
|
174
|
+
return cursor.rowcount > 0
|
|
175
|
+
|
|
168
176
|
async def delete(self, session_id: str) -> None:
|
|
169
177
|
connection = await self.storage.open()
|
|
170
178
|
await connection.execute("DELETE FROM mcp_sessions WHERE id = ?", (session_id,))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/selection.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.4 → aiohttp_tiny_mcp-0.3.5}/src/aiohttp_tiny_mcp/protocol/v2026_07_28.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|