aiohttp-tiny-mcp 0.3.3__tar.gz → 0.3.4__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.3 → aiohttp_tiny_mcp-0.3.4}/PKG-INFO +6 -2
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/README.md +5 -1
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/pyproject.toml +1 -1
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/pyproject.toml.orig +1 -1
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/console/console.css +2 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/console/console.js +41 -3
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/console/index.html +3 -3
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/endpoint.py +25 -2
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/LICENSE +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/adapter.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/auth.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/client.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/client_base.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/console/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/core.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/dispatcher.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/exchange.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/extensions.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/http_sse.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/hub.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/models.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/namespaces.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/postgres.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/selection.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2026_07_28.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/py.typed +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/redis.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/registry.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/request_state.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/schema.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/sessions.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/skills.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/specs.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/sqlite.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/sse.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/stdio.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/stdio_client.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/subscriptions.py +0 -0
- {aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/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.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Remote MCP server and client library for aiohttp
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -146,7 +146,11 @@ async def deploy(args: Deploy, ex: Exchange) -> str:
|
|
|
146
146
|
return f"deployed {args.service}"
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
app = Endpoint(
|
|
149
|
+
app = Endpoint(
|
|
150
|
+
registry,
|
|
151
|
+
# Behind a reverse proxy, uncomment this and let the proxy check Origin.
|
|
152
|
+
# trust_proxy_origin_validation=True,
|
|
153
|
+
).app("/mcp")
|
|
150
154
|
|
|
151
155
|
if __name__ == "__main__":
|
|
152
156
|
web.run_app(app, host="127.0.0.1", port=8080)
|
|
@@ -104,7 +104,11 @@ async def deploy(args: Deploy, ex: Exchange) -> str:
|
|
|
104
104
|
return f"deployed {args.service}"
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
app = Endpoint(
|
|
107
|
+
app = Endpoint(
|
|
108
|
+
registry,
|
|
109
|
+
# Behind a reverse proxy, uncomment this and let the proxy check Origin.
|
|
110
|
+
# trust_proxy_origin_validation=True,
|
|
111
|
+
).app("/mcp")
|
|
108
112
|
|
|
109
113
|
if __name__ == "__main__":
|
|
110
114
|
web.run_app(app, host="127.0.0.1", port=8080)
|
|
@@ -71,6 +71,8 @@ body {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
.field { display: flex; align-items: center; gap: 6px; }
|
|
74
|
+
.field.grow { flex: 1 1 12em; min-width: 0; }
|
|
75
|
+
.field.grow > input { flex: 1 1 auto; min-width: 8em; }
|
|
74
76
|
.field > span { color: var(--ink-soft); font-size: 12px; }
|
|
75
77
|
|
|
76
78
|
input[type="text"], input[type="number"], select, textarea {
|
|
@@ -384,6 +384,34 @@ const page = {
|
|
|
384
384
|
let client = null;
|
|
385
385
|
let chosen = null;
|
|
386
386
|
let endpointUrl = null;
|
|
387
|
+
//: Where the page was told the endpoint is. The field starts from it and may add a query.
|
|
388
|
+
let configuredEndpoint = null;
|
|
389
|
+
|
|
390
|
+
// The endpoint field: the path, plus any query string a person adds, such as
|
|
391
|
+
// `?mcp=2025-06-18` to pin a revision or whatever the server reads from the URL.
|
|
392
|
+
// The value is kept for the next visit while its path is still the configured one,
|
|
393
|
+
// so a server moved elsewhere is not chased at a stale address.
|
|
394
|
+
function endpointKey() {
|
|
395
|
+
return `mcp-console-endpoint:${location.pathname}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function rememberedEndpoint() {
|
|
399
|
+
const kept = localStorage.getItem(endpointKey());
|
|
400
|
+
if (!kept) return null;
|
|
401
|
+
const url = new URL(kept, location.origin);
|
|
402
|
+
return url.pathname === configuredEndpoint.pathname ? kept : null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function chosenEndpoint() {
|
|
406
|
+
const typed = page.endpoint.value.trim() || configuredEndpoint.pathname;
|
|
407
|
+
const url = new URL(typed, location.origin);
|
|
408
|
+
if (url.origin !== location.origin) {
|
|
409
|
+
throw new Error(`The endpoint must be on this server, not ${url.origin}.`);
|
|
410
|
+
}
|
|
411
|
+
page.endpoint.value = url.pathname + url.search;
|
|
412
|
+
localStorage.setItem(endpointKey(), page.endpoint.value);
|
|
413
|
+
return url;
|
|
414
|
+
}
|
|
387
415
|
|
|
388
416
|
function element(tag, className, text) {
|
|
389
417
|
const made = document.createElement(tag);
|
|
@@ -1297,6 +1325,7 @@ function disconnect() {
|
|
|
1297
1325
|
page.refresh.disabled = true;
|
|
1298
1326
|
page.revision.disabled = false;
|
|
1299
1327
|
page.answerable.disabled = false;
|
|
1328
|
+
page.endpoint.disabled = false;
|
|
1300
1329
|
page.connect.textContent = "Connect";
|
|
1301
1330
|
say("not connected", "");
|
|
1302
1331
|
}
|
|
@@ -1305,9 +1334,11 @@ async function connect() {
|
|
|
1305
1334
|
page.connect.disabled = true;
|
|
1306
1335
|
page.revision.disabled = true;
|
|
1307
1336
|
page.answerable.disabled = true;
|
|
1337
|
+
page.endpoint.disabled = true;
|
|
1308
1338
|
say("connecting", "");
|
|
1309
1339
|
page.outcome.textContent = "";
|
|
1310
1340
|
try {
|
|
1341
|
+
endpointUrl = chosenEndpoint();
|
|
1311
1342
|
client = new Client(endpointUrl.href, page.revision.value, {
|
|
1312
1343
|
answerable: page.answerable.checked,
|
|
1313
1344
|
onFrame: record,
|
|
@@ -1330,6 +1361,7 @@ async function connect() {
|
|
|
1330
1361
|
page.refresh.disabled = true;
|
|
1331
1362
|
page.revision.disabled = false;
|
|
1332
1363
|
page.answerable.disabled = false;
|
|
1364
|
+
page.endpoint.disabled = false;
|
|
1333
1365
|
page.connect.textContent = "Connect";
|
|
1334
1366
|
say(error.message, "off");
|
|
1335
1367
|
report("Could not connect", connectionHelp(error), true);
|
|
@@ -1340,7 +1372,7 @@ async function connect() {
|
|
|
1340
1372
|
|
|
1341
1373
|
function connectionHelp(error) {
|
|
1342
1374
|
const lines = [error.message];
|
|
1343
|
-
if (error instanceof TypeError || /fetch/i.test(error.message)) {
|
|
1375
|
+
if (endpointUrl && (error instanceof TypeError || /fetch/i.test(error.message))) {
|
|
1344
1376
|
lines.push("", `The request to ${endpointUrl.href} did not reach a server.`);
|
|
1345
1377
|
}
|
|
1346
1378
|
return lines.join("\n");
|
|
@@ -1409,8 +1441,14 @@ function start() {
|
|
|
1409
1441
|
const remembered = localStorage.getItem("mcp-console-revision");
|
|
1410
1442
|
page.revision.value = REVISIONS[remembered] ? remembered : Object.keys(REVISIONS)[0];
|
|
1411
1443
|
|
|
1412
|
-
|
|
1413
|
-
page.endpoint.
|
|
1444
|
+
configuredEndpoint = new URL(document.documentElement.dataset.endpoint || "/mcp", location.origin);
|
|
1445
|
+
page.endpoint.value = rememberedEndpoint() || configuredEndpoint.pathname + configuredEndpoint.search;
|
|
1446
|
+
page.endpoint.onkeydown = (event) => {
|
|
1447
|
+
if (event.key === "Enter" && !client) {
|
|
1448
|
+
event.preventDefault();
|
|
1449
|
+
connect();
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1414
1452
|
|
|
1415
1453
|
page.connect.onclick = () => (client ? disconnect() : connect());
|
|
1416
1454
|
page.refresh.onclick = async () => {
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
<header class="bar">
|
|
13
13
|
<h1>{{TITLE}}</h1>
|
|
14
|
-
<
|
|
14
|
+
<label class="field grow" title="The path the server serves MCP on. A query string is sent as typed">
|
|
15
15
|
<span>Endpoint</span>
|
|
16
|
-
<
|
|
17
|
-
</
|
|
16
|
+
<input id="endpoint" class="endpoint" type="text" spellcheck="false" autocomplete="off">
|
|
17
|
+
</label>
|
|
18
18
|
<label class="field">
|
|
19
19
|
<span>Revision</span>
|
|
20
20
|
<select id="revision" class="revision"></select>
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
+
import re
|
|
8
9
|
from collections.abc import Mapping
|
|
9
10
|
from contextlib import suppress
|
|
10
11
|
from dataclasses import replace
|
|
@@ -51,6 +52,24 @@ log = logging.getLogger("aiohttp_tiny_mcp")
|
|
|
51
52
|
MAY_ASK = frozenset({Operation.CALL_TOOL, Operation.GET_PROMPT, Operation.READ_RESOURCE})
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def origin_pattern(spec: str) -> re.Pattern[str]:
|
|
56
|
+
"""Compile an allowed origin with wildcards.
|
|
57
|
+
|
|
58
|
+
`*` matches one host label, `**` matches one or more labels, so
|
|
59
|
+
`https://*.example.com` admits `https://app.example.com` and `https://**.example.com`
|
|
60
|
+
admits `https://a.b.example.com` as well. Everything else matches literally.
|
|
61
|
+
"""
|
|
62
|
+
parts = []
|
|
63
|
+
for piece in re.split(r"(\*\*|\*)", spec):
|
|
64
|
+
if piece == "**":
|
|
65
|
+
parts.append(r"[^./]+(?:\.[^./]+)*")
|
|
66
|
+
elif piece == "*":
|
|
67
|
+
parts.append(r"[^./]+")
|
|
68
|
+
else:
|
|
69
|
+
parts.append(re.escape(piece))
|
|
70
|
+
return re.compile("".join(parts))
|
|
71
|
+
|
|
72
|
+
|
|
54
73
|
class Endpoint:
|
|
55
74
|
"""The MCP endpoint, in the spec's sense: one path that accepts POST.
|
|
56
75
|
|
|
@@ -81,6 +100,7 @@ class Endpoint:
|
|
|
81
100
|
self.adapters = adapters or AdapterSet.default()
|
|
82
101
|
self.dispatcher = Dispatcher(registry)
|
|
83
102
|
self.allowed_origins = allowed_origins
|
|
103
|
+
self.origin_patterns = [origin_pattern(o) for o in allowed_origins or () if "*" in o]
|
|
84
104
|
self.trust_proxy_origin_validation = trust_proxy_origin_validation
|
|
85
105
|
self.compress = compress
|
|
86
106
|
|
|
@@ -187,8 +207,11 @@ class Endpoint:
|
|
|
187
207
|
return
|
|
188
208
|
if origin == self.own_origin(request):
|
|
189
209
|
return
|
|
190
|
-
if self.allowed_origins is None
|
|
191
|
-
|
|
210
|
+
if self.allowed_origins is not None and origin in self.allowed_origins:
|
|
211
|
+
return
|
|
212
|
+
if any(pattern.fullmatch(origin) for pattern in self.origin_patterns):
|
|
213
|
+
return
|
|
214
|
+
raise Rejected(Failure(FailureKind.ORIGIN_REJECTED, "origin not allowed"))
|
|
192
215
|
|
|
193
216
|
def own_origin(self, request: web.Request) -> str:
|
|
194
217
|
"""Origin of pages served by this endpoint. A rebound page retains the attacker's origin
|
|
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.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/selection.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.3.3 → aiohttp_tiny_mcp-0.3.4}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|