mailsocket-mcp 0.2.3__tar.gz → 0.3.0__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mailsocket-mcp
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: MCP server exposing mailsocket inboxes + wait-for-OTP to AI agents.
5
5
  Author: mailsocket
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Topic :: Communications :: Email
19
19
  Requires-Python: >=3.10
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: mailsocket>=0.1.3
22
+ Requires-Dist: mailsocket>=0.2.0
23
23
  Requires-Dist: mcp<3.0,>=2.0
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7; extra == "dev"
@@ -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, 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 |
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. Default `since` omitted: only messages arriving after this call are matched. |
43
+ | `wait_for_link(inbox_id, timeout?=60, since?)` | the extracted magic link (returned, *not* followed) + subject/from/id/received_at. Default `since` omitted: only messages arriving after this call are matched. |
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 |
@@ -53,14 +53,17 @@ never echoed back.
53
53
  ### Reusing an inbox
54
54
 
55
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.
56
+ By default, `wait_for_otp`/`wait_for_link` omit `since` entirely, so the
57
+ server matches only messages arriving AFTER the call (the request start
58
+ time). But if you reuse an existing inbox (e.g. to request a second
59
+ OTP/link), pass `since` explicitly: otherwise a call with no `since` will
60
+ correctly ignore anything already sitting in the inbox, which is right for
61
+ most reuse, but if you actually WANT to catch a code that already arrived
62
+ before you called the tool, pass either the time right before you triggered
63
+ the new email (ISO8601 or unix seconds), or the `id` of the last message you
64
+ saw (`msg_...`, meaning "only messages after that one") — both tools return
65
+ `id`/`received_at` on the matched message so you can chain `since=<id>` into
66
+ the next call on that same inbox.
64
67
 
65
68
  ## Requirements
66
69
 
@@ -195,6 +198,12 @@ remote-transport tests talk to a local fake upstream on `127.0.0.1`.
195
198
 
196
199
  ## Changelog
197
200
 
201
+ - **0.3.0** — BEHAVIOUR CHANGE (via `mailsocket>=0.2.0`): `wait_for_otp` /
202
+ `wait_for_link` now omit `since` by default instead of forwarding `0`
203
+ ("any message already in the inbox"). The old default could return a
204
+ STALE OTP/link already sitting in a reused inbox; the new default matches
205
+ only messages arriving after the call. Pass `since=0` explicitly to keep
206
+ the old behaviour.
198
207
  - **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`.
199
208
  - **0.2.2** — depends on `mailsocket>=0.1.3` (stricter `_seg` id validation in the underlying Python SDK).
200
209
 
@@ -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, 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 |
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. Default `since` omitted: only messages arriving after this call are matched. |
16
+ | `wait_for_link(inbox_id, timeout?=60, since?)` | the extracted magic link (returned, *not* followed) + subject/from/id/received_at. Default `since` omitted: only messages arriving after this call are matched. |
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 |
@@ -26,14 +26,17 @@ never echoed back.
26
26
  ### Reusing an inbox
27
27
 
28
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.
29
+ By default, `wait_for_otp`/`wait_for_link` omit `since` entirely, so the
30
+ server matches only messages arriving AFTER the call (the request start
31
+ time). But if you reuse an existing inbox (e.g. to request a second
32
+ OTP/link), pass `since` explicitly: otherwise a call with no `since` will
33
+ correctly ignore anything already sitting in the inbox, which is right for
34
+ most reuse, but if you actually WANT to catch a code that already arrived
35
+ before you called the tool, pass either the time right before you triggered
36
+ the new email (ISO8601 or unix seconds), or the `id` of the last message you
37
+ saw (`msg_...`, meaning "only messages after that one") — both tools return
38
+ `id`/`received_at` on the matched message so you can chain `since=<id>` into
39
+ the next call on that same inbox.
37
40
 
38
41
  ## Requirements
39
42
 
@@ -168,6 +171,12 @@ remote-transport tests talk to a local fake upstream on `127.0.0.1`.
168
171
 
169
172
  ## Changelog
170
173
 
174
+ - **0.3.0** — BEHAVIOUR CHANGE (via `mailsocket>=0.2.0`): `wait_for_otp` /
175
+ `wait_for_link` now omit `since` by default instead of forwarding `0`
176
+ ("any message already in the inbox"). The old default could return a
177
+ STALE OTP/link already sitting in a reused inbox; the new default matches
178
+ only messages arriving after the call. Pass `since=0` explicitly to keep
179
+ the old behaviour.
171
180
  - **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`.
172
181
  - **0.2.2** — depends on `mailsocket>=0.1.3` (stricter `_seg` id validation in the underlying Python SDK).
173
182
 
@@ -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.3"
55
+ __version__ = "0.3.0"
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
@@ -481,12 +481,13 @@ def wait_for_otp(
481
481
  ``since`` (optional) only matches messages that arrived after this point:
482
482
  an ISO8601 timestamp, unix seconds, or a message id (``msg_...``, meaning
483
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.
484
+ message, so prefer a timestamp if unsure). Omitted (the default), the
485
+ server matches only messages that arrive during this call — the right
486
+ choice for a fresh inbox from ``create_inbox``. When REUSING an inbox for
487
+ a second code and you want to catch one that already arrived before this
488
+ call, pass either the time just before you triggered this email, or the
489
+ ``id`` of the last message you saw, so you get the NEW code instead of
490
+ missing it.
490
491
 
491
492
  Side effects: none beyond the long-poll HTTP call(s) needed to satisfy the
492
493
  wait (``openWorldHint`` — it talks to the live mailsocket API and blocks
@@ -537,12 +538,13 @@ def wait_for_link(
537
538
  ``since`` (optional) only matches messages that arrived after this point:
538
539
  an ISO8601 timestamp, unix seconds, or a message id (``msg_...``, meaning
539
540
  "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.
541
+ message, so prefer a timestamp if unsure). Omitted (the default), the
542
+ server matches only messages that arrive during this call — the right
543
+ choice for a fresh inbox from ``create_inbox``. When REUSING an inbox for
544
+ a second link and you want to catch one that already arrived before this
545
+ call, pass either the time just before you triggered this email, or the
546
+ ``id`` of the last message you saw, so you get the NEW link instead of
547
+ missing it.
546
548
 
547
549
  Side effects: none beyond the long-poll HTTP call(s) needed to satisfy the
548
550
  wait (``openWorldHint`` — it talks to the live mailsocket API and blocks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mailsocket-mcp
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: MCP server exposing mailsocket inboxes + wait-for-OTP to AI agents.
5
5
  Author: mailsocket
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Topic :: Communications :: Email
19
19
  Requires-Python: >=3.10
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: mailsocket>=0.1.3
22
+ Requires-Dist: mailsocket>=0.2.0
23
23
  Requires-Dist: mcp<3.0,>=2.0
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7; extra == "dev"
@@ -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, 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 |
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. Default `since` omitted: only messages arriving after this call are matched. |
43
+ | `wait_for_link(inbox_id, timeout?=60, since?)` | the extracted magic link (returned, *not* followed) + subject/from/id/received_at. Default `since` omitted: only messages arriving after this call are matched. |
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 |
@@ -53,14 +53,17 @@ never echoed back.
53
53
  ### Reusing an inbox
54
54
 
55
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.
56
+ By default, `wait_for_otp`/`wait_for_link` omit `since` entirely, so the
57
+ server matches only messages arriving AFTER the call (the request start
58
+ time). But if you reuse an existing inbox (e.g. to request a second
59
+ OTP/link), pass `since` explicitly: otherwise a call with no `since` will
60
+ correctly ignore anything already sitting in the inbox, which is right for
61
+ most reuse, but if you actually WANT to catch a code that already arrived
62
+ before you called the tool, pass either the time right before you triggered
63
+ the new email (ISO8601 or unix seconds), or the `id` of the last message you
64
+ saw (`msg_...`, meaning "only messages after that one") — both tools return
65
+ `id`/`received_at` on the matched message so you can chain `since=<id>` into
66
+ the next call on that same inbox.
64
67
 
65
68
  ## Requirements
66
69
 
@@ -195,6 +198,12 @@ remote-transport tests talk to a local fake upstream on `127.0.0.1`.
195
198
 
196
199
  ## Changelog
197
200
 
201
+ - **0.3.0** — BEHAVIOUR CHANGE (via `mailsocket>=0.2.0`): `wait_for_otp` /
202
+ `wait_for_link` now omit `since` by default instead of forwarding `0`
203
+ ("any message already in the inbox"). The old default could return a
204
+ STALE OTP/link already sitting in a reused inbox; the new default matches
205
+ only messages arriving after the call. Pass `since=0` explicitly to keep
206
+ the old behaviour.
198
207
  - **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`.
199
208
  - **0.2.2** — depends on `mailsocket>=0.1.3` (stricter `_seg` id validation in the underlying Python SDK).
200
209
 
@@ -1,4 +1,4 @@
1
- mailsocket>=0.1.3
1
+ mailsocket>=0.2.0
2
2
  mcp<3.0,>=2.0
3
3
 
4
4
  [dev]
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mailsocket-mcp"
7
- version = "0.2.3"
7
+ version = "0.3.0"
8
8
  description = "MCP server exposing mailsocket inboxes + wait-for-OTP to AI agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -22,7 +22,7 @@ classifiers = [
22
22
  "Topic :: Communications :: Email",
23
23
  ]
24
24
  dependencies = [
25
- "mailsocket>=0.1.3", # published on PyPI as `mailsocket` (0.1.3 adds strict _seg validation)
25
+ "mailsocket>=0.2.0", # published on PyPI as `mailsocket` (0.2.0: since defaults to omitted, not 0 — see CHANGELOG)
26
26
  "mcp>=2.0,<3.0",
27
27
  ]
28
28
 
@@ -61,13 +61,13 @@ class FakeClient:
61
61
  self.created.append(label)
62
62
  return {"id": "inbox_1", "address": "x@in.inboxpipe.net"}
63
63
 
64
- def wait_for_otp(self, inbox_id, *, timeout=60.0, min_confidence=0.0, since=0):
64
+ def wait_for_otp(self, inbox_id, *, timeout=60.0, min_confidence=0.0, since=None):
65
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
- def wait_for_link(self, inbox_id, *, timeout=60.0, since=0):
70
+ def wait_for_link(self, inbox_id, *, timeout=60.0, since=None):
71
71
  self.wait_link_calls.append((inbox_id, timeout, since))
72
72
  if isinstance(self.wait_link, Exception):
73
73
  raise self.wait_link
@@ -237,7 +237,7 @@ def test_wait_for_otp_returns_otp_and_confidence(fake_client):
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
+ assert since is None # absent -> SDK omits since (server uses request start time)
241
241
 
242
242
 
243
243
  def test_wait_for_otp_timeout_is_clamped(fake_client):
@@ -263,6 +263,17 @@ def test_wait_for_otp_since_passed_through(fake_client):
263
263
  assert since == "msg_111"
264
264
 
265
265
 
266
+ def test_wait_for_otp_explicit_since_zero_passed_through(fake_client):
267
+ """since=0 (include messages already in the inbox) must reach the SDK as 0,
268
+ not be dropped as falsy and turned into the new request-start default."""
269
+ fake_client.wait_otp = FakeWaitResult(otp="123456")
270
+
271
+ call_tool("wait_for_otp", {"inbox_id": "inbox_abc", "since": 0})
272
+
273
+ _, _, _, since = fake_client.wait_otp_calls[0]
274
+ assert since == 0
275
+
276
+
266
277
  def test_wait_for_otp_result_carries_id_and_received_at(fake_client):
267
278
  fake_client.wait_otp = FakeWaitResult(
268
279
  otp="123456",
@@ -307,7 +318,7 @@ def test_wait_for_link_returns_magic_link(fake_client):
307
318
  assert "https://example.test/go?token=abc" in result.content[0].text
308
319
  inbox_id, timeout, since = fake_client.wait_link_calls[0]
309
320
  assert inbox_id == "inbox_abc"
310
- assert since == 0 # absent -> SDK default unchanged
321
+ assert since is None # absent -> SDK omits since (server uses request start time)
311
322
 
312
323
 
313
324
  def test_wait_for_link_since_passed_through(fake_client):
@@ -428,14 +439,14 @@ def test_version_exported():
428
439
 
429
440
 
430
441
  def test_metadata_consistency_across_files():
431
- """Versions match everywhere: MCP 0.2.3 (pyproject, server.json, __version__)
432
- and its Python SDK sibling 0.1.3 (pyproject, __version__, user agent)."""
442
+ """Versions match everywhere: MCP 0.3.0 (pyproject, server.json, __version__)
443
+ and its Python SDK sibling 0.2.0 (pyproject, __version__, user agent)."""
433
444
  import tomllib
434
445
  from pathlib import Path
435
446
 
436
447
  root = Path(__file__).resolve().parents[1]
437
- expected = "0.2.3"
438
- expected_sdk = "0.1.3"
448
+ expected = "0.3.0"
449
+ expected_sdk = "0.2.0"
439
450
 
440
451
  pyproject = tomllib.loads((root / "pyproject.toml").read_text())
441
452
  assert pyproject["project"]["version"] == expected
File without changes
File without changes