mailsocket-mcp 0.1.0__py3-none-any.whl
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/__init__.py +12 -0
- mailsocket_mcp/server.py +267 -0
- mailsocket_mcp-0.1.0.dist-info/METADATA +101 -0
- mailsocket_mcp-0.1.0.dist-info/RECORD +8 -0
- mailsocket_mcp-0.1.0.dist-info/WHEEL +5 -0
- mailsocket_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- mailsocket_mcp-0.1.0.dist-info/licenses/LICENSE +21 -0
- mailsocket_mcp-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""mailsocket MCP server — expose inboxes + the OTP wait to AI agents.
|
|
2
|
+
|
|
3
|
+
The actual server lives in :mod:`mailsocket_mcp.server` (imported as a
|
|
4
|
+
submodule, not re-exported under its own name, so ``mailsocket_mcp.server``
|
|
5
|
+
always resolves to the module for ``mailsocket_mcp.server:main``).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .server import MAX_WAIT_TIMEOUT, MISSING_KEY_MESSAGE
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
|
|
12
|
+
__all__ = ["MAX_WAIT_TIMEOUT", "MISSING_KEY_MESSAGE"]
|
mailsocket_mcp/server.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""mailsocket MCP server.
|
|
2
|
+
|
|
3
|
+
Wraps the official :mod:`mailsocket` Python SDK (sprint B1) as MCP tools so an
|
|
4
|
+
AI coding agent can create a throwaway inbox and *block* on the OTP / magic
|
|
5
|
+
link without writing any polling code.
|
|
6
|
+
|
|
7
|
+
The server speaks MCP over stdio (JSON-RPC). It reads ``MAILSOCKET_API_KEY``
|
|
8
|
+
(and optional ``MAILSOCKET_BASE_URL``) from the environment; the key is never
|
|
9
|
+
logged and never echoed back in a tool error.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import functools
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
from typing import Any, Callable
|
|
18
|
+
from urllib.parse import quote
|
|
19
|
+
|
|
20
|
+
import mcp.types as types
|
|
21
|
+
from mcp.server.mcpserver import MCPServer
|
|
22
|
+
|
|
23
|
+
from mailsocket import AuthError, Client, MailsocketError, NotFound, RateLimited, WaitTimeout
|
|
24
|
+
|
|
25
|
+
__all__ = ["server", "main", "MAX_WAIT_TIMEOUT", "MISSING_KEY_MESSAGE"]
|
|
26
|
+
|
|
27
|
+
# The headline wait is bounded: an agent can ask for up to 120s, never more.
|
|
28
|
+
DEFAULT_WAIT_TIMEOUT = 60.0
|
|
29
|
+
MAX_WAIT_TIMEOUT = 120.0
|
|
30
|
+
MIN_WAIT_TIMEOUT = 1.0
|
|
31
|
+
|
|
32
|
+
MISSING_KEY_MESSAGE = (
|
|
33
|
+
"MAILSOCKET_API_KEY is not set. Export it before starting the server, e.g. "
|
|
34
|
+
"`MAILSOCKET_API_KEY=ms_live_... mailsocket-mcp`."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
server = MCPServer(
|
|
38
|
+
name="mailsocket",
|
|
39
|
+
title="mailsocket MCP server",
|
|
40
|
+
description=(
|
|
41
|
+
"Ephemeral email inboxes for AI agents: create an inbox, hand the "
|
|
42
|
+
"address to any signup form, then block on the OTP or magic link."
|
|
43
|
+
),
|
|
44
|
+
version="0.1.0",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# -- client construction -----------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _State:
|
|
52
|
+
client: Client | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _build_client_from_env() -> Client:
|
|
56
|
+
"""Build an SDK client from the environment, failing clearly without a key."""
|
|
57
|
+
api_key = os.environ.get("MAILSOCKET_API_KEY", "")
|
|
58
|
+
if not api_key:
|
|
59
|
+
raise RuntimeError(MISSING_KEY_MESSAGE)
|
|
60
|
+
base_url = os.environ.get("MAILSOCKET_BASE_URL") or None
|
|
61
|
+
if base_url:
|
|
62
|
+
return Client(api_key, base_url=base_url)
|
|
63
|
+
return Client(api_key)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_client() -> Client:
|
|
67
|
+
if _State.client is None:
|
|
68
|
+
_State.client = _build_client_from_env()
|
|
69
|
+
return _State.client
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# -- error handling ----------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _safe(text: str) -> str:
|
|
76
|
+
"""Redact the API key from any text so it can never leak into a tool error.
|
|
77
|
+
|
|
78
|
+
Defense in depth against reformatted keys: redact the literal key, its
|
|
79
|
+
URL-encoded form, AND any token sharing the ``ms_live_`` credential prefix,
|
|
80
|
+
so an SDK/network error that url-encodes, wraps, or splits the key around
|
|
81
|
+
still cannot surface a usable secret to the agent.
|
|
82
|
+
"""
|
|
83
|
+
key = os.environ.get("MAILSOCKET_API_KEY", "")
|
|
84
|
+
if key:
|
|
85
|
+
text = text.replace(key, "***")
|
|
86
|
+
encoded = quote(key, safe="")
|
|
87
|
+
if encoded != key:
|
|
88
|
+
text = text.replace(encoded, "***")
|
|
89
|
+
# Belt and braces: scrub anything that looks like a live key, even if the
|
|
90
|
+
# env value was reformatted beyond a literal/url-encoded match.
|
|
91
|
+
text = re.sub(r"ms_live_[A-Za-z0-9_\-]{4,}", "***", text)
|
|
92
|
+
return text.strip()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _error_result(exc: BaseException) -> types.CallToolResult:
|
|
96
|
+
"""Turn an SDK exception into a clean MCP tool error (no traceback, no key)."""
|
|
97
|
+
if isinstance(exc, AuthError):
|
|
98
|
+
msg = "Authentication failed (HTTP 401): the API key was rejected. Check MAILSOCKET_API_KEY."
|
|
99
|
+
elif isinstance(exc, WaitTimeout):
|
|
100
|
+
msg = f"Timed out waiting for a matching message: {_safe(str(exc))}"
|
|
101
|
+
elif isinstance(exc, NotFound):
|
|
102
|
+
msg = f"Not found (HTTP 404): {_safe(str(exc))}"
|
|
103
|
+
elif isinstance(exc, RateLimited):
|
|
104
|
+
retry = f" Retry after {exc.retry_after:g}s." if exc.retry_after else ""
|
|
105
|
+
msg = f"Rate limited (HTTP 429): {_safe(str(exc))}.{retry}"
|
|
106
|
+
elif isinstance(exc, MailsocketError):
|
|
107
|
+
msg = _safe(str(exc)) or exc.__class__.__name__
|
|
108
|
+
else:
|
|
109
|
+
msg = _safe(str(exc)) or exc.__class__.__name__
|
|
110
|
+
return types.CallToolResult(
|
|
111
|
+
is_error=True,
|
|
112
|
+
content=[types.TextContent(type="text", text=msg)],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _guarded(fn: Callable) -> Callable:
|
|
117
|
+
"""Wrap a tool so SDK errors become clean MCP tool errors."""
|
|
118
|
+
|
|
119
|
+
@functools.wraps(fn)
|
|
120
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
121
|
+
try:
|
|
122
|
+
return fn(*args, **kwargs)
|
|
123
|
+
except Exception as exc: # noqa: BLE001 — any tool error must be scrubbed
|
|
124
|
+
# Route EVERY exception (incl. unexpected KeyError/TypeError/ValueError)
|
|
125
|
+
# through _error_result so the key is scrubbed and no raw traceback
|
|
126
|
+
# ever reaches the agent. _error_result handles known SDK types
|
|
127
|
+
# specially and falls back to a class-name-only message otherwise.
|
|
128
|
+
return _error_result(exc)
|
|
129
|
+
|
|
130
|
+
return wrapper
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _clamp_timeout(timeout: float) -> float:
|
|
134
|
+
return max(MIN_WAIT_TIMEOUT, min(float(timeout), MAX_WAIT_TIMEOUT))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _ctx(result, include: tuple[str, ...]) -> dict:
|
|
138
|
+
"""Pick subject/from context off a ``WaitResult`` message."""
|
|
139
|
+
message = getattr(result, "message", None) or {}
|
|
140
|
+
ctx: dict = {}
|
|
141
|
+
for field in include:
|
|
142
|
+
ctx[field] = message.get(field)
|
|
143
|
+
return ctx
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# -- tools -------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@server.tool()
|
|
150
|
+
@_guarded
|
|
151
|
+
def create_inbox(label: str | None = None) -> dict:
|
|
152
|
+
"""Create a new throwaway inbox and return its id and email address.
|
|
153
|
+
|
|
154
|
+
Hand the returned ``address`` to whatever signup form sends the code, then
|
|
155
|
+
call ``wait_for_otp`` (or ``wait_for_link``) with the returned ``id``.
|
|
156
|
+
"""
|
|
157
|
+
inbox = _get_client().create_inbox(label=label)
|
|
158
|
+
return {"id": inbox.get("id"), "address": inbox.get("address")}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@server.tool()
|
|
162
|
+
@_guarded
|
|
163
|
+
def wait_for_otp(
|
|
164
|
+
inbox_id: str,
|
|
165
|
+
timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
166
|
+
min_confidence: float = 0.0,
|
|
167
|
+
) -> dict:
|
|
168
|
+
"""Block (bounded) until an OTP arrives in ``inbox_id`` and return it.
|
|
169
|
+
|
|
170
|
+
This is the headline tool: no polling loop, no email parsing. ``timeout`` is
|
|
171
|
+
clamped to [1, 120] seconds. Returns the OTP string, a confidence score and
|
|
172
|
+
the subject/from of the message it came from. Raises a clean error if no
|
|
173
|
+
matching message arrives in time.
|
|
174
|
+
"""
|
|
175
|
+
result = _get_client().wait_for_otp(
|
|
176
|
+
inbox_id,
|
|
177
|
+
timeout=_clamp_timeout(timeout),
|
|
178
|
+
min_confidence=min_confidence,
|
|
179
|
+
)
|
|
180
|
+
return {
|
|
181
|
+
"otp": result.otp,
|
|
182
|
+
"confidence": result.confidence,
|
|
183
|
+
**_ctx(result, ("subject", "from")),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@server.tool()
|
|
188
|
+
@_guarded
|
|
189
|
+
def wait_for_link(
|
|
190
|
+
inbox_id: str,
|
|
191
|
+
timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
192
|
+
) -> dict:
|
|
193
|
+
"""Block (bounded) until a magic link arrives in ``inbox_id`` and return it.
|
|
194
|
+
|
|
195
|
+
The link is returned, NOT followed — use it (e.g. in a headless browser or
|
|
196
|
+
``curl``) however you like. ``timeout`` is clamped to [1, 120] seconds.
|
|
197
|
+
"""
|
|
198
|
+
result = _get_client().wait_for_link(inbox_id, timeout=_clamp_timeout(timeout))
|
|
199
|
+
return {
|
|
200
|
+
"magic_link": result.magic_link,
|
|
201
|
+
**_ctx(result, ("subject", "from")),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@server.tool()
|
|
206
|
+
@_guarded
|
|
207
|
+
def list_inboxes() -> dict:
|
|
208
|
+
"""List all inboxes owned by the API key."""
|
|
209
|
+
page = _get_client().list_inboxes()
|
|
210
|
+
return {"inboxes": list(page.data)}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@server.tool()
|
|
214
|
+
@_guarded
|
|
215
|
+
def list_messages(
|
|
216
|
+
inbox_id: str,
|
|
217
|
+
has_otp: bool | None = None,
|
|
218
|
+
subject_contains: str | None = None,
|
|
219
|
+
sender: str | None = None,
|
|
220
|
+
) -> dict:
|
|
221
|
+
"""List messages in ``inbox_id``, optionally filtered.
|
|
222
|
+
|
|
223
|
+
``has_otp`` keeps only messages with a detected OTP, ``subject_contains``
|
|
224
|
+
does a case-insensitive substring match on the subject, and ``sender``
|
|
225
|
+
matches the ``from`` address (maps to the API's ``from`` filter).
|
|
226
|
+
"""
|
|
227
|
+
page = _get_client().list_messages(
|
|
228
|
+
inbox_id,
|
|
229
|
+
has_otp=has_otp,
|
|
230
|
+
subject_contains=subject_contains,
|
|
231
|
+
sender=sender,
|
|
232
|
+
)
|
|
233
|
+
return {"messages": list(page.data)}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@server.tool()
|
|
237
|
+
@_guarded
|
|
238
|
+
def get_latest(inbox_id: str) -> dict:
|
|
239
|
+
"""Return the newest message in ``inbox_id`` (without blocking)."""
|
|
240
|
+
return _get_client().get_latest(inbox_id)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@server.tool()
|
|
244
|
+
@_guarded
|
|
245
|
+
def delete_inbox(inbox_id: str) -> dict:
|
|
246
|
+
"""Delete ``inbox_id`` and everything in it. Use when you are done with it."""
|
|
247
|
+
_get_client().delete_inbox(inbox_id)
|
|
248
|
+
return {"deleted": inbox_id}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# -- entrypoint --------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def main() -> None:
|
|
255
|
+
"""Console entry point: fail fast on a missing key, then serve MCP over stdio."""
|
|
256
|
+
import sys
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
_get_client()
|
|
260
|
+
except RuntimeError as exc: # missing MAILSOCKET_API_KEY
|
|
261
|
+
print(_safe(str(exc)), file=sys.stderr)
|
|
262
|
+
raise SystemExit(2)
|
|
263
|
+
server.run()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
main()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mailsocket-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server exposing mailsocket inboxes + wait-for-OTP to AI agents.
|
|
5
|
+
Author: mailsocket
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: mailsocket,mcp,email,otp,inbox,magic-link
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Communications :: Email
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: mailsocket
|
|
19
|
+
Requires-Dist: mcp<3.0,>=2.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# mailsocket — MCP server
|
|
25
|
+
|
|
26
|
+
An [MCP](https://modelcontextprotocol.io) server that exposes
|
|
27
|
+
[mailsocket](https://mailsocket.app) to AI coding agents as native tools, so an
|
|
28
|
+
agent can create a throwaway inbox and **block on the OTP or magic link**
|
|
29
|
+
without writing any polling code. It wraps the
|
|
30
|
+
[Python SDK](../python/) — no second HTTP client, no re-implemented polling.
|
|
31
|
+
|
|
32
|
+
## What it does
|
|
33
|
+
|
|
34
|
+
| Tool | What the agent gets |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `create_inbox(label?)` | a fresh inbox `id` + `address` |
|
|
37
|
+
| `wait_for_otp(inbox_id, timeout?=60, min_confidence?=0)` | **the headline** — blocks (bounded, clamped to 120s) and returns the OTP string + confidence + subject/from |
|
|
38
|
+
| `wait_for_link(inbox_id, timeout?=60)` | the extracted magic link (returned, *not* followed) |
|
|
39
|
+
| `list_inboxes()` | every inbox owned by the key |
|
|
40
|
+
| `list_messages(inbox_id, has_otp?, subject_contains?, from?)` | messages, optionally filtered |
|
|
41
|
+
| `get_latest(inbox_id)` | the newest message, without blocking |
|
|
42
|
+
| `delete_inbox(inbox_id)` | remove an inbox when done |
|
|
43
|
+
|
|
44
|
+
Errors from the SDK (`AuthError` / `NotFound` / `RateLimited` / `WaitTimeout`)
|
|
45
|
+
are turned into clean MCP tool errors — no stack traces, and the API key is
|
|
46
|
+
never echoed back.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# from this directory, with the repo's backend venv (or your own):
|
|
52
|
+
/Users/tuki/MailBridge/backend/.venv/bin/python -m pip install mcp # official MCP SDK
|
|
53
|
+
/Users/tuki/MailBridge/backend/.venv/bin/python -m pip install -e ../python # local mailsocket SDK
|
|
54
|
+
/Users/tuki/MailBridge/backend/.venv/bin/python -m pip install -e . # this server (mailsocket-mcp)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Run
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export MAILSOCKET_API_KEY="ms_live_..."
|
|
61
|
+
# optional: export MAILSOCKET_BASE_URL="https://dash.mailsocket.app/api/v1"
|
|
62
|
+
mailsocket-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It reads `MAILSOCKET_API_KEY` (and optional `MAILSOCKET_BASE_URL`) from the
|
|
66
|
+
environment. If the key is missing it exits with a clear message and status 2;
|
|
67
|
+
the key is never logged.
|
|
68
|
+
|
|
69
|
+
## Register with an MCP client
|
|
70
|
+
|
|
71
|
+
The server is a plain stdio MCP server, so it works with any MCP-compatible
|
|
72
|
+
client. Point your client's `mcpServers` config at the `mailsocket-mcp`
|
|
73
|
+
executable and pass the key through the environment:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"mailsocket": {
|
|
79
|
+
"command": "mailsocket-mcp",
|
|
80
|
+
"args": [],
|
|
81
|
+
"env": {
|
|
82
|
+
"MAILSOCKET_API_KEY": "ms_live_...",
|
|
83
|
+
"MAILSOCKET_BASE_URL": "https://dash.mailsocket.app/api/v1"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If `mailsocket-mcp` is not on the client's `PATH`, use the venv's full path for
|
|
91
|
+
`command`, e.g. `/Users/tuki/MailBridge/backend/.venv/bin/mailsocket-mcp`.
|
|
92
|
+
|
|
93
|
+
## Develop
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cd sdks/mcp
|
|
97
|
+
/Users/tuki/MailBridge/backend/.venv/bin/python -m pytest -q
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Tests are fully offline — the SDK client is faked, so nothing touches the live
|
|
101
|
+
API.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
mailsocket_mcp/__init__.py,sha256=NoCQmyrJZC4ESoXpZ65uzonNv6SXLSNJYPSHzbu5k-w,429
|
|
2
|
+
mailsocket_mcp/server.py,sha256=wZim3PwUbwl9mtw2q9GRrz71puTZyX9iXvAXalOPYbc,8616
|
|
3
|
+
mailsocket_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=Eyot-wc-_WRgJlHzEP7XSrVlFqlwaDvM9BYqHpDJVY0,1067
|
|
4
|
+
mailsocket_mcp-0.1.0.dist-info/METADATA,sha256=FUQ3x8rekaiPZTl6JfVTuv18I1-51rGebPbgYe2sH8U,3561
|
|
5
|
+
mailsocket_mcp-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
mailsocket_mcp-0.1.0.dist-info/entry_points.txt,sha256=2Jv4dW-IGegpnKX_PJqaSPbncebu3yoSV89-SGQZR2I,62
|
|
7
|
+
mailsocket_mcp-0.1.0.dist-info/top_level.txt,sha256=wzXAJ1o-ulXKdF7Xi3GLmhXKtu5SwTaqfqjA0tBEaCM,15
|
|
8
|
+
mailsocket_mcp-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mailsocket
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mailsocket_mcp
|