mailsocket-mcp 0.2.2__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.
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/PKG-INFO +16 -3
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/README.md +15 -2
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp/server.py +37 -8
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp.egg-info/PKG-INFO +16 -3
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/pyproject.toml +1 -1
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/tests/test_server.py +70 -7
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/LICENSE +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp/__init__.py +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp/remote.py +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp.egg-info/SOURCES.txt +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp.egg-info/dependency_links.txt +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp.egg-info/entry_points.txt +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp.egg-info/requires.txt +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/mailsocket_mcp.egg-info/top_level.txt +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/setup.cfg +0 -0
- {mailsocket_mcp-0.2.2 → mailsocket_mcp-0.2.3}/tests/test_remote.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailsocket-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: MCP server exposing mailsocket inboxes + wait-for-OTP to AI agents.
|
|
5
5
|
Author: mailsocket
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,8 +39,8 @@ no second HTTP client, no re-implemented polling.
|
|
|
39
39
|
| Tool | What the agent gets |
|
|
40
40
|
| --- | --- |
|
|
41
41
|
| `create_inbox(label?)` | a fresh inbox `id` + `address` |
|
|
42
|
-
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0)` | **the headline** — blocks (bounded: requested deadline clamped to 120s locally, 55s on the remote server) and returns the OTP string + confidence + subject/from |
|
|
43
|
-
| `wait_for_link(inbox_id, timeout?=60)` | the extracted magic link (returned, *not* followed) |
|
|
42
|
+
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0, since?)` | **the headline** — blocks (bounded: requested deadline clamped to 120s locally, 55s on the remote server) and returns the OTP string + confidence + subject/from/id/received_at |
|
|
43
|
+
| `wait_for_link(inbox_id, timeout?=60, since?)` | the extracted magic link (returned, *not* followed) + subject/from/id/received_at |
|
|
44
44
|
| `list_inboxes(limit?, cursor?)` | one page of inboxes owned by the key (server default page size 25) + `next_cursor`/`has_more` |
|
|
45
45
|
| `list_messages(inbox_id, has_otp?, subject_contains?, sender?, limit?, cursor?)` | one page of messages, optionally filtered (server default page size 25) + `next_cursor`/`has_more` |
|
|
46
46
|
| `get_latest(inbox_id)` | the newest message, without blocking |
|
|
@@ -50,6 +50,18 @@ Errors from the SDK (`AuthError` / `NotFound` / `RateLimited` / `WaitTimeout`)
|
|
|
50
50
|
are turned into clean MCP tool errors — no stack traces, and the API key is
|
|
51
51
|
never echoed back.
|
|
52
52
|
|
|
53
|
+
### Reusing an inbox
|
|
54
|
+
|
|
55
|
+
`create_inbox` needs no `since` — a fresh inbox only ever has new messages.
|
|
56
|
+
But if you reuse an existing inbox (e.g. to request a second OTP/link), pass
|
|
57
|
+
`since` to `wait_for_otp`/`wait_for_link`: otherwise the wait can match the
|
|
58
|
+
OLDEST retained message instead of the new one you just triggered. Pass
|
|
59
|
+
either the time right before you triggered the new email (ISO8601 or unix
|
|
60
|
+
seconds), or the `id` of the last message you saw (`msg_...`, meaning "only
|
|
61
|
+
messages after that one") — both tools return `id`/`received_at` on the
|
|
62
|
+
matched message so you can chain `since=<id>` into the next call on that same
|
|
63
|
+
inbox.
|
|
64
|
+
|
|
53
65
|
## Requirements
|
|
54
66
|
|
|
55
67
|
- Python 3.10+
|
|
@@ -183,6 +195,7 @@ remote-transport tests talk to a local fake upstream on `127.0.0.1`.
|
|
|
183
195
|
|
|
184
196
|
## Changelog
|
|
185
197
|
|
|
198
|
+
- **0.2.3** — `wait_for_otp` / `wait_for_link` accept an optional `since` (ISO8601, unix seconds or a message id) so a reused inbox returns the NEW code; results include the message `id` and `received_at`.
|
|
186
199
|
- **0.2.2** — depends on `mailsocket>=0.1.3` (stricter `_seg` id validation in the underlying Python SDK).
|
|
187
200
|
|
|
188
201
|
<!-- mcp-name: app.mailsocket/mailsocket-mcp -->
|
|
@@ -12,8 +12,8 @@ no second HTTP client, no re-implemented polling.
|
|
|
12
12
|
| Tool | What the agent gets |
|
|
13
13
|
| --- | --- |
|
|
14
14
|
| `create_inbox(label?)` | a fresh inbox `id` + `address` |
|
|
15
|
-
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0)` | **the headline** — blocks (bounded: requested deadline clamped to 120s locally, 55s on the remote server) and returns the OTP string + confidence + subject/from |
|
|
16
|
-
| `wait_for_link(inbox_id, timeout?=60)` | the extracted magic link (returned, *not* followed) |
|
|
15
|
+
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0, since?)` | **the headline** — blocks (bounded: requested deadline clamped to 120s locally, 55s on the remote server) and returns the OTP string + confidence + subject/from/id/received_at |
|
|
16
|
+
| `wait_for_link(inbox_id, timeout?=60, since?)` | the extracted magic link (returned, *not* followed) + subject/from/id/received_at |
|
|
17
17
|
| `list_inboxes(limit?, cursor?)` | one page of inboxes owned by the key (server default page size 25) + `next_cursor`/`has_more` |
|
|
18
18
|
| `list_messages(inbox_id, has_otp?, subject_contains?, sender?, limit?, cursor?)` | one page of messages, optionally filtered (server default page size 25) + `next_cursor`/`has_more` |
|
|
19
19
|
| `get_latest(inbox_id)` | the newest message, without blocking |
|
|
@@ -23,6 +23,18 @@ Errors from the SDK (`AuthError` / `NotFound` / `RateLimited` / `WaitTimeout`)
|
|
|
23
23
|
are turned into clean MCP tool errors — no stack traces, and the API key is
|
|
24
24
|
never echoed back.
|
|
25
25
|
|
|
26
|
+
### Reusing an inbox
|
|
27
|
+
|
|
28
|
+
`create_inbox` needs no `since` — a fresh inbox only ever has new messages.
|
|
29
|
+
But if you reuse an existing inbox (e.g. to request a second OTP/link), pass
|
|
30
|
+
`since` to `wait_for_otp`/`wait_for_link`: otherwise the wait can match the
|
|
31
|
+
OLDEST retained message instead of the new one you just triggered. Pass
|
|
32
|
+
either the time right before you triggered the new email (ISO8601 or unix
|
|
33
|
+
seconds), or the `id` of the last message you saw (`msg_...`, meaning "only
|
|
34
|
+
messages after that one") — both tools return `id`/`received_at` on the
|
|
35
|
+
matched message so you can chain `since=<id>` into the next call on that same
|
|
36
|
+
inbox.
|
|
37
|
+
|
|
26
38
|
## Requirements
|
|
27
39
|
|
|
28
40
|
- Python 3.10+
|
|
@@ -156,6 +168,7 @@ remote-transport tests talk to a local fake upstream on `127.0.0.1`.
|
|
|
156
168
|
|
|
157
169
|
## Changelog
|
|
158
170
|
|
|
171
|
+
- **0.2.3** — `wait_for_otp` / `wait_for_link` accept an optional `since` (ISO8601, unix seconds or a message id) so a reused inbox returns the NEW code; results include the message `id` and `received_at`.
|
|
159
172
|
- **0.2.2** — depends on `mailsocket>=0.1.3` (stricter `_seg` id validation in the underlying Python SDK).
|
|
160
173
|
|
|
161
174
|
<!-- mcp-name: app.mailsocket/mailsocket-mcp -->
|
|
@@ -52,7 +52,7 @@ __all__ = [
|
|
|
52
52
|
# Canonical version, re-exported as ``mailsocket_mcp.__version__`` (see
|
|
53
53
|
# __init__.py). Defined here, not in __init__.py, because __init__.py imports
|
|
54
54
|
# this module at package-load time and a circular self-import would break.
|
|
55
|
-
__version__ = "0.2.
|
|
55
|
+
__version__ = "0.2.3"
|
|
56
56
|
|
|
57
57
|
# The headline wait is bounded: an agent can request up to the transport's
|
|
58
58
|
# ceiling as the overall deadline (clamped), though transport overhead can push
|
|
@@ -461,6 +461,7 @@ def wait_for_otp(
|
|
|
461
461
|
inbox_id: str,
|
|
462
462
|
timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
463
463
|
min_confidence: float = 0.0,
|
|
464
|
+
since: str | int | float | None = None,
|
|
464
465
|
) -> dict:
|
|
465
466
|
"""Block (bounded) until an OTP arrives in ``inbox_id`` and return it.
|
|
466
467
|
|
|
@@ -477,21 +478,35 @@ def wait_for_otp(
|
|
|
477
478
|
slightly after the ceiling in practice. On a timeout just call it again.
|
|
478
479
|
``min_confidence`` filters out low-confidence OTP extractions.
|
|
479
480
|
|
|
481
|
+
``since`` (optional) only matches messages that arrived after this point:
|
|
482
|
+
an ISO8601 timestamp, unix seconds, or a message id (``msg_...``, meaning
|
|
483
|
+
"only messages after that one"; an unknown id falls back to the oldest
|
|
484
|
+
message, so prefer a timestamp if unsure). A fresh inbox straight from
|
|
485
|
+
``create_inbox`` needs nothing here — the default already matches
|
|
486
|
+
anything in it. When REUSING an inbox for a second code, pass either the
|
|
487
|
+
time just before you triggered this email, or the ``id`` of the last
|
|
488
|
+
message you saw, so you get the NEW code instead of the oldest one still
|
|
489
|
+
retained in the inbox.
|
|
490
|
+
|
|
480
491
|
Side effects: none beyond the long-poll HTTP call(s) needed to satisfy the
|
|
481
492
|
wait (``openWorldHint`` — it talks to the live mailsocket API and blocks
|
|
482
493
|
for up to ``timeout`` seconds). Returns the OTP string, a confidence
|
|
483
|
-
score,
|
|
484
|
-
|
|
494
|
+
score, the subject/from of the matching message, and its ``id`` /
|
|
495
|
+
``received_at`` (chain ``since=<id>`` into the next call on this inbox).
|
|
496
|
+
Raises a clean tool error (``WaitTimeout``) if nothing matching arrives
|
|
497
|
+
before the deadline.
|
|
485
498
|
"""
|
|
499
|
+
kwargs = {} if since is None else {"since": since}
|
|
486
500
|
result = _get_client().wait_for_otp(
|
|
487
501
|
inbox_id,
|
|
488
502
|
timeout=_clamp_timeout(timeout),
|
|
489
503
|
min_confidence=min_confidence,
|
|
504
|
+
**kwargs,
|
|
490
505
|
)
|
|
491
506
|
return {
|
|
492
507
|
"otp": result.otp,
|
|
493
508
|
"confidence": result.confidence,
|
|
494
|
-
**_ctx(result, ("subject", "from")),
|
|
509
|
+
**_ctx(result, ("subject", "from", "id", "received_at")),
|
|
495
510
|
}
|
|
496
511
|
|
|
497
512
|
|
|
@@ -502,6 +517,7 @@ def wait_for_otp(
|
|
|
502
517
|
def wait_for_link(
|
|
503
518
|
inbox_id: str,
|
|
504
519
|
timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
520
|
+
since: str | int | float | None = None,
|
|
505
521
|
) -> dict:
|
|
506
522
|
"""Block (bounded) until a magic link arrives in ``inbox_id`` and return it.
|
|
507
523
|
|
|
@@ -518,15 +534,28 @@ def wait_for_link(
|
|
|
518
534
|
return slightly after the ceiling in practice. On a timeout just call it
|
|
519
535
|
again.
|
|
520
536
|
|
|
537
|
+
``since`` (optional) only matches messages that arrived after this point:
|
|
538
|
+
an ISO8601 timestamp, unix seconds, or a message id (``msg_...``, meaning
|
|
539
|
+
"only messages after that one"; an unknown id falls back to the oldest
|
|
540
|
+
message, so prefer a timestamp if unsure). A fresh inbox straight from
|
|
541
|
+
``create_inbox`` needs nothing here — the default already matches
|
|
542
|
+
anything in it. When REUSING an inbox for a second link, pass either the
|
|
543
|
+
time just before you triggered this email, or the ``id`` of the last
|
|
544
|
+
message you saw, so you get the NEW link instead of the oldest one still
|
|
545
|
+
retained in the inbox.
|
|
546
|
+
|
|
521
547
|
Side effects: none beyond the long-poll HTTP call(s) needed to satisfy the
|
|
522
548
|
wait (``openWorldHint`` — it talks to the live mailsocket API and blocks
|
|
523
|
-
for up to ``timeout`` seconds).
|
|
524
|
-
|
|
549
|
+
for up to ``timeout`` seconds). Returns the magic link, the subject/from
|
|
550
|
+
of the matching message, and its ``id`` / ``received_at`` (chain
|
|
551
|
+
``since=<id>`` into the next call on this inbox). Raises a clean tool
|
|
552
|
+
error (``WaitTimeout``) if no link arrives before the deadline.
|
|
525
553
|
"""
|
|
526
|
-
|
|
554
|
+
kwargs = {} if since is None else {"since": since}
|
|
555
|
+
result = _get_client().wait_for_link(inbox_id, timeout=_clamp_timeout(timeout), **kwargs)
|
|
527
556
|
return {
|
|
528
557
|
"magic_link": result.magic_link,
|
|
529
|
-
**_ctx(result, ("subject", "from")),
|
|
558
|
+
**_ctx(result, ("subject", "from", "id", "received_at")),
|
|
530
559
|
}
|
|
531
560
|
|
|
532
561
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailsocket-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: MCP server exposing mailsocket inboxes + wait-for-OTP to AI agents.
|
|
5
5
|
Author: mailsocket
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,8 +39,8 @@ no second HTTP client, no re-implemented polling.
|
|
|
39
39
|
| Tool | What the agent gets |
|
|
40
40
|
| --- | --- |
|
|
41
41
|
| `create_inbox(label?)` | a fresh inbox `id` + `address` |
|
|
42
|
-
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0)` | **the headline** — blocks (bounded: requested deadline clamped to 120s locally, 55s on the remote server) and returns the OTP string + confidence + subject/from |
|
|
43
|
-
| `wait_for_link(inbox_id, timeout?=60)` | the extracted magic link (returned, *not* followed) |
|
|
42
|
+
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0, since?)` | **the headline** — blocks (bounded: requested deadline clamped to 120s locally, 55s on the remote server) and returns the OTP string + confidence + subject/from/id/received_at |
|
|
43
|
+
| `wait_for_link(inbox_id, timeout?=60, since?)` | the extracted magic link (returned, *not* followed) + subject/from/id/received_at |
|
|
44
44
|
| `list_inboxes(limit?, cursor?)` | one page of inboxes owned by the key (server default page size 25) + `next_cursor`/`has_more` |
|
|
45
45
|
| `list_messages(inbox_id, has_otp?, subject_contains?, sender?, limit?, cursor?)` | one page of messages, optionally filtered (server default page size 25) + `next_cursor`/`has_more` |
|
|
46
46
|
| `get_latest(inbox_id)` | the newest message, without blocking |
|
|
@@ -50,6 +50,18 @@ Errors from the SDK (`AuthError` / `NotFound` / `RateLimited` / `WaitTimeout`)
|
|
|
50
50
|
are turned into clean MCP tool errors — no stack traces, and the API key is
|
|
51
51
|
never echoed back.
|
|
52
52
|
|
|
53
|
+
### Reusing an inbox
|
|
54
|
+
|
|
55
|
+
`create_inbox` needs no `since` — a fresh inbox only ever has new messages.
|
|
56
|
+
But if you reuse an existing inbox (e.g. to request a second OTP/link), pass
|
|
57
|
+
`since` to `wait_for_otp`/`wait_for_link`: otherwise the wait can match the
|
|
58
|
+
OLDEST retained message instead of the new one you just triggered. Pass
|
|
59
|
+
either the time right before you triggered the new email (ISO8601 or unix
|
|
60
|
+
seconds), or the `id` of the last message you saw (`msg_...`, meaning "only
|
|
61
|
+
messages after that one") — both tools return `id`/`received_at` on the
|
|
62
|
+
matched message so you can chain `since=<id>` into the next call on that same
|
|
63
|
+
inbox.
|
|
64
|
+
|
|
53
65
|
## Requirements
|
|
54
66
|
|
|
55
67
|
- Python 3.10+
|
|
@@ -183,6 +195,7 @@ remote-transport tests talk to a local fake upstream on `127.0.0.1`.
|
|
|
183
195
|
|
|
184
196
|
## Changelog
|
|
185
197
|
|
|
198
|
+
- **0.2.3** — `wait_for_otp` / `wait_for_link` accept an optional `since` (ISO8601, unix seconds or a message id) so a reused inbox returns the NEW code; results include the message `id` and `received_at`.
|
|
186
199
|
- **0.2.2** — depends on `mailsocket>=0.1.3` (stricter `_seg` id validation in the underlying Python SDK).
|
|
187
200
|
|
|
188
201
|
<!-- mcp-name: app.mailsocket/mailsocket-mcp -->
|
|
@@ -62,13 +62,13 @@ class FakeClient:
|
|
|
62
62
|
return {"id": "inbox_1", "address": "x@in.inboxpipe.net"}
|
|
63
63
|
|
|
64
64
|
def wait_for_otp(self, inbox_id, *, timeout=60.0, min_confidence=0.0, since=0):
|
|
65
|
-
self.wait_otp_calls.append((inbox_id, timeout, min_confidence))
|
|
65
|
+
self.wait_otp_calls.append((inbox_id, timeout, min_confidence, since))
|
|
66
66
|
if isinstance(self.wait_otp, Exception):
|
|
67
67
|
raise self.wait_otp
|
|
68
68
|
return self.wait_otp
|
|
69
69
|
|
|
70
70
|
def wait_for_link(self, inbox_id, *, timeout=60.0, since=0):
|
|
71
|
-
self.wait_link_calls.append((inbox_id, timeout))
|
|
71
|
+
self.wait_link_calls.append((inbox_id, timeout, since))
|
|
72
72
|
if isinstance(self.wait_link, Exception):
|
|
73
73
|
raise self.wait_link
|
|
74
74
|
return self.wait_link
|
|
@@ -233,10 +233,11 @@ def test_wait_for_otp_returns_otp_and_confidence(fake_client):
|
|
|
233
233
|
assert result.is_error is False
|
|
234
234
|
text = result.content[0].text
|
|
235
235
|
assert '"123456"' in text
|
|
236
|
-
inbox_id, timeout, min_conf = fake_client.wait_otp_calls[0]
|
|
236
|
+
inbox_id, timeout, min_conf, since = fake_client.wait_otp_calls[0]
|
|
237
237
|
assert inbox_id == "inbox_abc"
|
|
238
238
|
assert timeout == 60
|
|
239
239
|
assert min_conf == 0.0
|
|
240
|
+
assert since == 0 # absent -> SDK default unchanged
|
|
240
241
|
|
|
241
242
|
|
|
242
243
|
def test_wait_for_otp_timeout_is_clamped(fake_client):
|
|
@@ -244,14 +245,44 @@ def test_wait_for_otp_timeout_is_clamped(fake_client):
|
|
|
244
245
|
|
|
245
246
|
call_tool("wait_for_otp", {"inbox_id": "inbox_abc", "timeout": 9999})
|
|
246
247
|
|
|
247
|
-
_, timeout, _ = fake_client.wait_otp_calls[0]
|
|
248
|
+
_, timeout, _, _ = fake_client.wait_otp_calls[0]
|
|
248
249
|
assert timeout == MAX_WAIT_TIMEOUT
|
|
249
250
|
|
|
250
251
|
call_tool("wait_for_otp", {"inbox_id": "inbox_abc", "timeout": 0})
|
|
251
|
-
_, timeout, _ = fake_client.wait_otp_calls[1]
|
|
252
|
+
_, timeout, _, _ = fake_client.wait_otp_calls[1]
|
|
252
253
|
assert timeout == 1.0
|
|
253
254
|
|
|
254
255
|
|
|
256
|
+
def test_wait_for_otp_since_passed_through(fake_client):
|
|
257
|
+
fake_client.wait_otp = FakeWaitResult(otp="123456")
|
|
258
|
+
|
|
259
|
+
call_tool("wait_for_otp", {"inbox_id": "inbox_abc", "since": "msg_111"})
|
|
260
|
+
|
|
261
|
+
inbox_id, _, _, since = fake_client.wait_otp_calls[0]
|
|
262
|
+
assert inbox_id == "inbox_abc"
|
|
263
|
+
assert since == "msg_111"
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_wait_for_otp_result_carries_id_and_received_at(fake_client):
|
|
267
|
+
fake_client.wait_otp = FakeWaitResult(
|
|
268
|
+
otp="123456",
|
|
269
|
+
confidence=0.95,
|
|
270
|
+
message={
|
|
271
|
+
"subject": "Your code",
|
|
272
|
+
"from": "Acme <no-reply@acme.test>",
|
|
273
|
+
"id": "msg_999",
|
|
274
|
+
"received_at": "2024-01-01T00:00:00Z",
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
result = call_tool("wait_for_otp", {"inbox_id": "inbox_abc"})
|
|
279
|
+
|
|
280
|
+
assert result.is_error is False
|
|
281
|
+
text = result.content[0].text
|
|
282
|
+
assert '"msg_999"' in text
|
|
283
|
+
assert '"2024-01-01T00:00:00Z"' in text
|
|
284
|
+
|
|
285
|
+
|
|
255
286
|
def test_wait_for_otp_wait_timeout_is_clean_error(fake_client):
|
|
256
287
|
fake_client.wait_otp = WaitTimeout("No matching message arrived within 60s.")
|
|
257
288
|
|
|
@@ -274,6 +305,38 @@ def test_wait_for_link_returns_magic_link(fake_client):
|
|
|
274
305
|
|
|
275
306
|
assert result.is_error is False
|
|
276
307
|
assert "https://example.test/go?token=abc" in result.content[0].text
|
|
308
|
+
inbox_id, timeout, since = fake_client.wait_link_calls[0]
|
|
309
|
+
assert inbox_id == "inbox_abc"
|
|
310
|
+
assert since == 0 # absent -> SDK default unchanged
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_wait_for_link_since_passed_through(fake_client):
|
|
314
|
+
fake_client.wait_link = FakeWaitResult(magic_link="https://example.test/go?token=abc")
|
|
315
|
+
|
|
316
|
+
call_tool("wait_for_link", {"inbox_id": "inbox_abc", "since": "2024-01-01T00:00:00Z"})
|
|
317
|
+
|
|
318
|
+
inbox_id, _, since = fake_client.wait_link_calls[0]
|
|
319
|
+
assert inbox_id == "inbox_abc"
|
|
320
|
+
assert since == "2024-01-01T00:00:00Z"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def test_wait_for_link_result_carries_id_and_received_at(fake_client):
|
|
324
|
+
fake_client.wait_link = FakeWaitResult(
|
|
325
|
+
magic_link="https://example.test/go?token=abc",
|
|
326
|
+
message={
|
|
327
|
+
"subject": "Sign in",
|
|
328
|
+
"from": "Acme <no-reply@acme.test>",
|
|
329
|
+
"id": "msg_777",
|
|
330
|
+
"received_at": "2024-02-02T00:00:00Z",
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
result = call_tool("wait_for_link", {"inbox_id": "inbox_abc"})
|
|
335
|
+
|
|
336
|
+
assert result.is_error is False
|
|
337
|
+
text = result.content[0].text
|
|
338
|
+
assert '"msg_777"' in text
|
|
339
|
+
assert '"2024-02-02T00:00:00Z"' in text
|
|
277
340
|
|
|
278
341
|
|
|
279
342
|
# -- create_inbox / list_messages / delete -----------------------------------
|
|
@@ -365,13 +428,13 @@ def test_version_exported():
|
|
|
365
428
|
|
|
366
429
|
|
|
367
430
|
def test_metadata_consistency_across_files():
|
|
368
|
-
"""Versions match everywhere: MCP 0.2.
|
|
431
|
+
"""Versions match everywhere: MCP 0.2.3 (pyproject, server.json, __version__)
|
|
369
432
|
and its Python SDK sibling 0.1.3 (pyproject, __version__, user agent)."""
|
|
370
433
|
import tomllib
|
|
371
434
|
from pathlib import Path
|
|
372
435
|
|
|
373
436
|
root = Path(__file__).resolve().parents[1]
|
|
374
|
-
expected = "0.2.
|
|
437
|
+
expected = "0.2.3"
|
|
375
438
|
expected_sdk = "0.1.3"
|
|
376
439
|
|
|
377
440
|
pyproject = tomllib.loads((root / "pyproject.toml").read_text())
|
|
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
|