aiohttp-tiny-mcp 0.5.0__tar.gz → 0.5.1__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.5.0 → aiohttp_tiny_mcp-0.5.1}/PKG-INFO +1 -1
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/pyproject.toml +1 -1
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/pyproject.toml.orig +1 -1
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/console/console.css +10 -1
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/console/console.js +212 -26
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/console/index.html +1 -1
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/LICENSE +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/README.md +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/auth/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/auth/jwt.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/client/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/client/base.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/client/http.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/client/stdio.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/console/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/console/oauth-callback.html +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/extensions/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/extensions/skills.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/oauth/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/oauth/github.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/oauth/provider.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/oauth/server.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/oauth/tokens.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/adapter.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/core.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/models.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/schema.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/selection.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2026_07_28.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/py.typed +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/dispatcher.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/exchange.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/http.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/registry.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/request_state.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/specs.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/sse.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/stdio.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/subscriptions.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/tasks.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/hub.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/namespaces.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/postgres.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/redis.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/sessions.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/sqlite.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/testing.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/transport/__init__.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/transport/metadata.py +0 -0
- {aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/transport/sse.py +0 -0
|
@@ -70,6 +70,10 @@ body {
|
|
|
70
70
|
letter-spacing: -0.01em;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
.bar h1 > button { font: inherit; letter-spacing: inherit; color: inherit; background: none; border: 0; padding: 0; }
|
|
74
|
+
.bar h1 > button:disabled { opacity: 1; }
|
|
75
|
+
.bar h1 > button:hover:not(:disabled) { text-decoration: underline; }
|
|
76
|
+
|
|
73
77
|
.field { display: flex; align-items: center; gap: 6px; }
|
|
74
78
|
.field.grow { flex: 1 1 12em; min-width: 0; }
|
|
75
79
|
.field.grow > input { flex: 1 1 auto; min-width: 8em; }
|
|
@@ -334,7 +338,12 @@ button.quiet { color: var(--ink-soft); }
|
|
|
334
338
|
|
|
335
339
|
.argument { display: flex; flex-direction: column; gap: 3px; }
|
|
336
340
|
|
|
337
|
-
.
|
|
341
|
+
.choice-group { display: flex; flex-wrap: wrap; gap: 6px 16px; border: 0; padding: 0; margin: 0; min-width: 0; }
|
|
342
|
+
.choice-group > label { display: flex; align-items: center; gap: 5px; }
|
|
343
|
+
.argument-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
344
|
+
.argument .null-toggle { display: flex; align-items: center; gap: 4px; color: var(--ink-soft); font-weight: 400; }
|
|
345
|
+
.argument > :disabled { opacity: 0.45; cursor: not-allowed; }
|
|
346
|
+
.argument > label, .argument-heading > label { font-size: 12px; font-weight: 500; }
|
|
338
347
|
.argument .hint { font-size: 11.5px; color: var(--ink-faint); }
|
|
339
348
|
.argument .required { color: var(--bad); margin-left: 3px; }
|
|
340
349
|
|
|
@@ -540,6 +540,7 @@ function encodeHeader(value) {
|
|
|
540
540
|
|
|
541
541
|
|
|
542
542
|
const page = {
|
|
543
|
+
serverTitle: document.getElementById("server-title"),
|
|
543
544
|
authOAuth: document.getElementById("auth-oauth"),
|
|
544
545
|
authOAuthStatus: document.getElementById("auth-oauth-status"),
|
|
545
546
|
authenticate: document.getElementById("authenticate"),
|
|
@@ -585,6 +586,7 @@ const page = {
|
|
|
585
586
|
|
|
586
587
|
let client = null;
|
|
587
588
|
let chosen = null;
|
|
589
|
+
let serverInstructions = "";
|
|
588
590
|
let endpointUrl = null;
|
|
589
591
|
//: Where the page was told the endpoint is. The field starts from it and may add a query.
|
|
590
592
|
let configuredEndpoint = null;
|
|
@@ -886,22 +888,74 @@ function buildFields(schema, into, omit) {
|
|
|
886
888
|
return fields;
|
|
887
889
|
}
|
|
888
890
|
|
|
891
|
+
let fieldId = 0;
|
|
892
|
+
|
|
893
|
+
function fieldChoices(property) {
|
|
894
|
+
if (Array.isArray(property.enum)) {
|
|
895
|
+
return property.enum.map((value) => ({ value, title: String(value) }));
|
|
896
|
+
}
|
|
897
|
+
if (!Array.isArray(property.oneOf) || !property.oneOf.length) return null;
|
|
898
|
+
const choices = [];
|
|
899
|
+
for (const branch of property.oneOf) {
|
|
900
|
+
if (!branch || typeof branch !== "object") return null;
|
|
901
|
+
let value;
|
|
902
|
+
if (branch.type === "null") value = null;
|
|
903
|
+
else if (Object.hasOwn(branch, "const")) value = branch.const;
|
|
904
|
+
else if (Array.isArray(branch.enum) && branch.enum.length === 1) value = branch.enum[0];
|
|
905
|
+
else return null;
|
|
906
|
+
choices.push({ value, title: branch.title ?? String(value) });
|
|
907
|
+
}
|
|
908
|
+
return choices;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function allowsNull(property) {
|
|
912
|
+
if (property.default === null) return true;
|
|
913
|
+
if (property.type === "null" || [].concat(property.type || []).includes("null")) return true;
|
|
914
|
+
if (Object.hasOwn(property, "const") && property.const === null) return true;
|
|
915
|
+
if (Array.isArray(property.enum) && property.enum.includes(null)) return true;
|
|
916
|
+
return (property.anyOf || property.oneOf || []).some(allowsNull);
|
|
917
|
+
}
|
|
918
|
+
|
|
889
919
|
function buildField(name, property, isRequired, into) {
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
const
|
|
920
|
+
const nullable = allowsNull(property);
|
|
921
|
+
const branches = property.anyOf || property.oneOf || [];
|
|
922
|
+
const nonNull = branches.filter((branch) => branch.type !== "null" &&
|
|
923
|
+
!(Object.hasOwn(branch, "const") && branch.const === null));
|
|
924
|
+
const effective = nullable && nonNull.length === 1
|
|
925
|
+
? { ...nonNull[0], ...property, enum: property.enum || nonNull[0].enum }
|
|
926
|
+
: property;
|
|
927
|
+
const kinds = [].concat(effective.type || inferType(effective));
|
|
928
|
+
const choices = fieldChoices(effective)?.filter(({ value }) => value !== null);
|
|
929
|
+
const baseKind = kinds.find((one) => one !== "null") || "string";
|
|
930
|
+
const itemChoices = baseKind === "array" && effective.items ? fieldChoices(effective.items) : null;
|
|
931
|
+
const kind = choices ? "choice" : itemChoices ? "choices" : baseKind;
|
|
932
|
+
const row = element("div", kind === "boolean" && !nullable ? "argument flag" : "argument");
|
|
893
933
|
const label = element("label", null, property.title || name);
|
|
894
934
|
if (isRequired) label.append(element("span", "required", "*"));
|
|
895
935
|
let input;
|
|
896
936
|
|
|
897
|
-
if (
|
|
937
|
+
if (kind === "choices") {
|
|
938
|
+
input = element("fieldset", "choice-group");
|
|
939
|
+
input.choiceInputs = itemChoices.map(({ value, title }) => {
|
|
940
|
+
const option = element("label");
|
|
941
|
+
const checkbox = element("input");
|
|
942
|
+
checkbox.type = "checkbox";
|
|
943
|
+
checkbox.value = JSON.stringify(value);
|
|
944
|
+
checkbox.checked = Array.isArray(property.default) &&
|
|
945
|
+
property.default.some((item) => JSON.stringify(item) === checkbox.value);
|
|
946
|
+
option.append(checkbox, element("span", null, title));
|
|
947
|
+
input.append(option);
|
|
948
|
+
return checkbox;
|
|
949
|
+
});
|
|
950
|
+
} else if (choices) {
|
|
898
951
|
input = element("select");
|
|
899
952
|
if (!isRequired) input.append(element("option", null, ""));
|
|
900
|
-
|
|
901
|
-
const option = element("option", null,
|
|
902
|
-
option.value =
|
|
953
|
+
choices.forEach(({ value, title }) => {
|
|
954
|
+
const option = element("option", null, title);
|
|
955
|
+
option.value = JSON.stringify(value);
|
|
903
956
|
input.append(option);
|
|
904
957
|
});
|
|
958
|
+
if (property.default !== undefined && property.default !== null) input.value = JSON.stringify(property.default);
|
|
905
959
|
} else if (kind === "boolean") {
|
|
906
960
|
input = element("input");
|
|
907
961
|
input.type = "checkbox";
|
|
@@ -909,24 +963,44 @@ function buildField(name, property, isRequired, into) {
|
|
|
909
963
|
} else if (kind === "integer" || kind === "number") {
|
|
910
964
|
input = element("input");
|
|
911
965
|
input.type = "number";
|
|
912
|
-
|
|
913
|
-
if (
|
|
914
|
-
if (
|
|
915
|
-
if (property.default !== undefined) input.value = property.default;
|
|
966
|
+
input.step = kind === "integer" ? "1" : "any";
|
|
967
|
+
if (effective.minimum !== undefined) input.min = effective.minimum;
|
|
968
|
+
if (effective.maximum !== undefined) input.max = effective.maximum;
|
|
969
|
+
if (property.default !== undefined && property.default !== null) input.value = property.default;
|
|
916
970
|
} else if (kind === "object" || kind === "array") {
|
|
917
971
|
input = element("textarea");
|
|
918
972
|
input.placeholder = kind === "array" ? "[]" : "{}";
|
|
919
|
-
if (property.default !== undefined) input.value = JSON.stringify(property.default, null, 2);
|
|
973
|
+
if (property.default !== undefined && property.default !== null) input.value = JSON.stringify(property.default, null, 2);
|
|
920
974
|
} else {
|
|
921
975
|
input = element("input");
|
|
922
976
|
input.type = "text";
|
|
923
|
-
if (property.default !== undefined) input.value = property.default;
|
|
977
|
+
if (property.default !== undefined && property.default !== null) input.value = property.default;
|
|
924
978
|
}
|
|
925
979
|
|
|
926
980
|
input.dataset.name = name;
|
|
927
981
|
input.dataset.kind = kind;
|
|
928
982
|
if (isRequired) input.dataset.required = "1";
|
|
929
|
-
|
|
983
|
+
input.id = `argument-${++fieldId}`;
|
|
984
|
+
label.htmlFor = input.id;
|
|
985
|
+
if (kind === "choices") {
|
|
986
|
+
label.id = `${input.id}-label`;
|
|
987
|
+
input.setAttribute("aria-labelledby", label.id);
|
|
988
|
+
}
|
|
989
|
+
const heading = nullable ? element("div", "argument-heading") : row;
|
|
990
|
+
heading.append(label);
|
|
991
|
+
if (nullable) {
|
|
992
|
+
const toggleLabel = element("label", "null-toggle");
|
|
993
|
+
const toggle = element("input");
|
|
994
|
+
toggle.type = "checkbox";
|
|
995
|
+
toggle.checked = property.default === null || property.type === "null" || choices?.length === 0;
|
|
996
|
+
toggle.onchange = () => { input.disabled = toggle.checked; };
|
|
997
|
+
toggle.onchange();
|
|
998
|
+
input.nullToggle = toggle;
|
|
999
|
+
toggleLabel.append(toggle, element("span", null, "null"));
|
|
1000
|
+
heading.append(toggleLabel);
|
|
1001
|
+
row.append(heading);
|
|
1002
|
+
}
|
|
1003
|
+
row.append(input);
|
|
930
1004
|
if (property.description) row.append(element("div", "hint", property.description));
|
|
931
1005
|
into.append(row);
|
|
932
1006
|
return input;
|
|
@@ -941,7 +1015,8 @@ function inferType(property) {
|
|
|
941
1015
|
/* Empty inputs represent absent arguments, including for required-field validation. */
|
|
942
1016
|
function missing(fields) {
|
|
943
1017
|
return fields.filter(
|
|
944
|
-
(input) => input.dataset.required && input.
|
|
1018
|
+
(input) => input.dataset.required && !input.nullToggle?.checked &&
|
|
1019
|
+
!["boolean", "choices"].includes(input.dataset.kind) && !input.value.trim()
|
|
945
1020
|
);
|
|
946
1021
|
}
|
|
947
1022
|
|
|
@@ -950,14 +1025,32 @@ function readFields(fields) {
|
|
|
950
1025
|
fields.forEach((input) => {
|
|
951
1026
|
const name = input.dataset.name;
|
|
952
1027
|
const kind = input.dataset.kind;
|
|
1028
|
+
if (input.nullToggle?.checked) {
|
|
1029
|
+
values[name] = null;
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (kind === "choices") {
|
|
1033
|
+
values[name] = input.choiceInputs.filter((option) => option.checked)
|
|
1034
|
+
.map((option) => JSON.parse(option.value));
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
953
1037
|
if (kind === "boolean") {
|
|
954
1038
|
values[name] = input.checked;
|
|
955
1039
|
return;
|
|
956
1040
|
}
|
|
957
1041
|
const raw = input.value.trim();
|
|
1042
|
+
if (kind === "integer" || kind === "number") {
|
|
1043
|
+
if (input.validity.badInput) throw new Error(`${name}: enter a valid number.`);
|
|
1044
|
+
if (raw === "") return;
|
|
1045
|
+
const value = input.valueAsNumber;
|
|
1046
|
+
if (!Number.isFinite(value) || (kind === "integer" && !Number.isInteger(value))) {
|
|
1047
|
+
throw new Error(`${name}: enter a valid ${kind}.`);
|
|
1048
|
+
}
|
|
1049
|
+
values[name] = value;
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
958
1052
|
if (raw === "") return; // absent, which is not the same as empty
|
|
959
|
-
if (kind === "
|
|
960
|
-
else if (kind === "number") values[name] = parseFloat(raw);
|
|
1053
|
+
if (kind === "choice") values[name] = JSON.parse(raw);
|
|
961
1054
|
else if (kind === "object" || kind === "array") {
|
|
962
1055
|
try {
|
|
963
1056
|
values[name] = JSON.parse(raw);
|
|
@@ -970,8 +1063,56 @@ function readFields(fields) {
|
|
|
970
1063
|
}
|
|
971
1064
|
|
|
972
1065
|
|
|
1066
|
+
function rememberToolFields(name, fields) {
|
|
1067
|
+
const key = `mcp-console-form:${JSON.stringify([location.pathname, endpointUrl.href, name])}`;
|
|
1068
|
+
let saved;
|
|
1069
|
+
try {
|
|
1070
|
+
saved = JSON.parse(localStorage.getItem(key));
|
|
1071
|
+
} catch {
|
|
1072
|
+
// Storage can be unavailable or contain an incomplete draft.
|
|
1073
|
+
}
|
|
1074
|
+
for (const input of fields) {
|
|
1075
|
+
const state = saved && Object.hasOwn(saved, input.dataset.name) ? saved[input.dataset.name] : null;
|
|
1076
|
+
if (!state || state.kind !== input.dataset.kind) continue;
|
|
1077
|
+
if (input.choiceInputs && Array.isArray(state.value)) {
|
|
1078
|
+
input.choiceInputs.forEach((option) => { option.checked = state.value.includes(option.value); });
|
|
1079
|
+
} else if (input.dataset.kind === "boolean" && typeof state.value === "boolean") {
|
|
1080
|
+
input.checked = state.value;
|
|
1081
|
+
} else if (typeof state.value === "string") {
|
|
1082
|
+
if (input.dataset.kind !== "choice" || [...input.options].some((option) => option.value === state.value)) {
|
|
1083
|
+
input.value = state.value;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (input.nullToggle && typeof state.isNull === "boolean") {
|
|
1087
|
+
input.nullToggle.checked = state.isNull;
|
|
1088
|
+
input.nullToggle.onchange();
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
const save = () => {
|
|
1092
|
+
const entries = fields.map((input) => [input.dataset.name, {
|
|
1093
|
+
kind: input.dataset.kind,
|
|
1094
|
+
value: input.choiceInputs
|
|
1095
|
+
? input.choiceInputs.filter((option) => option.checked).map((option) => option.value)
|
|
1096
|
+
: input.dataset.kind === "boolean" ? input.checked : input.value,
|
|
1097
|
+
isNull: input.nullToggle?.checked || false,
|
|
1098
|
+
}]);
|
|
1099
|
+
try {
|
|
1100
|
+
localStorage.setItem(key, JSON.stringify(Object.fromEntries(entries)));
|
|
1101
|
+
} catch {
|
|
1102
|
+
// Keep the form usable when browser storage is blocked or full.
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
for (const input of fields) {
|
|
1106
|
+
input.addEventListener("input", save);
|
|
1107
|
+
input.addEventListener("change", save);
|
|
1108
|
+
if (input.nullToggle) input.nullToggle.addEventListener("change", save);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
|
|
973
1113
|
function show(items, title, into, describe, pick) {
|
|
974
|
-
if (!items.length) return;
|
|
1114
|
+
if (!items.length) return [];
|
|
1115
|
+
const buttons = [];
|
|
975
1116
|
const group = element("div", "group");
|
|
976
1117
|
group.append(element("h3", null, title));
|
|
977
1118
|
items.forEach((item) => {
|
|
@@ -990,8 +1131,10 @@ function show(items, title, into, describe, pick) {
|
|
|
990
1131
|
pick(item);
|
|
991
1132
|
};
|
|
992
1133
|
group.append(button);
|
|
1134
|
+
buttons.push(button);
|
|
993
1135
|
});
|
|
994
1136
|
into.append(group);
|
|
1137
|
+
return buttons;
|
|
995
1138
|
}
|
|
996
1139
|
|
|
997
1140
|
function firstLine(text) {
|
|
@@ -1139,7 +1282,8 @@ function plain(text) {
|
|
|
1139
1282
|
|
|
1140
1283
|
|
|
1141
1284
|
function show(items, title, into, describe, pick) {
|
|
1142
|
-
if (!items.length) return;
|
|
1285
|
+
if (!items.length) return [];
|
|
1286
|
+
const buttons = [];
|
|
1143
1287
|
const group = element("div", "group");
|
|
1144
1288
|
group.append(element("h3", null, title));
|
|
1145
1289
|
items.forEach((item) => {
|
|
@@ -1158,8 +1302,10 @@ function show(items, title, into, describe, pick) {
|
|
|
1158
1302
|
pick(item);
|
|
1159
1303
|
};
|
|
1160
1304
|
group.append(button);
|
|
1305
|
+
buttons.push(button);
|
|
1161
1306
|
});
|
|
1162
1307
|
into.append(group);
|
|
1308
|
+
return buttons;
|
|
1163
1309
|
}
|
|
1164
1310
|
|
|
1165
1311
|
function hints(tool) {
|
|
@@ -1175,7 +1321,7 @@ async function loadCatalogue() {
|
|
|
1175
1321
|
const into = document.createDocumentFragment();
|
|
1176
1322
|
|
|
1177
1323
|
const tools = await client.listTools();
|
|
1178
|
-
show(tools, `Tools (${tools.length})`, into, {
|
|
1324
|
+
const toolButtons = show(tools, `Tools (${tools.length})`, into, {
|
|
1179
1325
|
label: (tool) => tool.name,
|
|
1180
1326
|
note: (tool) => firstLine(tool.description),
|
|
1181
1327
|
tags: hints,
|
|
@@ -1223,11 +1369,29 @@ async function loadCatalogue() {
|
|
|
1223
1369
|
} else {
|
|
1224
1370
|
page.catalogue.append(into);
|
|
1225
1371
|
}
|
|
1372
|
+
restoreTool(tools, toolButtons);
|
|
1226
1373
|
}
|
|
1227
1374
|
|
|
1375
|
+
function restoreTool(tools, buttons) {
|
|
1376
|
+
let name;
|
|
1377
|
+
try {
|
|
1378
|
+
name = decodeURIComponent(location.hash.slice(1));
|
|
1379
|
+
} catch {
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const index = tools.findIndex((tool) => tool.name === name);
|
|
1383
|
+
if (index !== -1) buttons[index].click();
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function rememberTool(name) {
|
|
1387
|
+
const url = new URL(location.href);
|
|
1388
|
+
url.hash = name === null ? "" : encodeURIComponent(name);
|
|
1389
|
+
history.replaceState(history.state, "", url);
|
|
1390
|
+
}
|
|
1228
1391
|
|
|
1229
1392
|
function choose(what) {
|
|
1230
1393
|
chosen = what;
|
|
1394
|
+
rememberTool(what.kind === "tool" ? what.item.name : null);
|
|
1231
1395
|
if (catalogue) catalogue.fold();
|
|
1232
1396
|
page.outcome.textContent = "";
|
|
1233
1397
|
page.args.textContent = "";
|
|
@@ -1238,6 +1402,7 @@ function choose(what) {
|
|
|
1238
1402
|
prose(what.item.description, page.about);
|
|
1239
1403
|
// Hide retry arguments managed by this client.
|
|
1240
1404
|
chosen.fields = buildFields(what.item.inputSchema || {}, page.args, DRIVEN);
|
|
1405
|
+
rememberToolFields(what.item.name, chosen.fields);
|
|
1241
1406
|
page.invoke.textContent = "Call";
|
|
1242
1407
|
} else if (what.kind === "prompt") {
|
|
1243
1408
|
page.subject.textContent = what.item.name;
|
|
@@ -1697,10 +1862,29 @@ async function invoke() {
|
|
|
1697
1862
|
}
|
|
1698
1863
|
|
|
1699
1864
|
|
|
1865
|
+
function setServerInstructions(text) {
|
|
1866
|
+
serverInstructions = text || "";
|
|
1867
|
+
if (page.serverTitle) page.serverTitle.disabled = !serverInstructions;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function showServerInstructions(clearFragment = true) {
|
|
1871
|
+
if (!serverInstructions) return;
|
|
1872
|
+
chosen = null;
|
|
1873
|
+
if (clearFragment) rememberTool(null);
|
|
1874
|
+
page.catalogue.querySelectorAll(".item").forEach((item) => item.classList.remove("chosen"));
|
|
1875
|
+
page.subject.textContent = "Instructions";
|
|
1876
|
+
prose(serverInstructions, page.about);
|
|
1877
|
+
page.args.textContent = "";
|
|
1878
|
+
page.outcome.textContent = "";
|
|
1879
|
+
page.invoke.disabled = true;
|
|
1880
|
+
if (catalogue) catalogue.fold();
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1700
1883
|
function disconnect() {
|
|
1701
1884
|
// Sessions expire server-side; there is no close request.
|
|
1702
1885
|
client?.abort.abort();
|
|
1703
1886
|
client = null;
|
|
1887
|
+
setServerInstructions("");
|
|
1704
1888
|
chosen = null;
|
|
1705
1889
|
page.catalogue.textContent = "";
|
|
1706
1890
|
page.catalogue.append(element("p", "empty", "Connect to see what this server offers."));
|
|
@@ -1737,17 +1921,15 @@ async function connect() {
|
|
|
1737
1921
|
onQuestion: askPerson,
|
|
1738
1922
|
});
|
|
1739
1923
|
const described = await client.initialize();
|
|
1924
|
+
setServerInstructions(described.instructions);
|
|
1740
1925
|
await loadCatalogue();
|
|
1741
1926
|
page.refresh.disabled = false;
|
|
1742
1927
|
const name = (described.serverInfo || (described._meta || {})["io.modelcontextprotocol/serverInfo"] || {}).name;
|
|
1743
1928
|
say(`${name || "connected"} · ${client.version}`, "on");
|
|
1744
1929
|
page.connect.textContent = "Disconnect";
|
|
1745
|
-
if (
|
|
1746
|
-
page.subject.textContent = "Instructions";
|
|
1747
|
-
prose(described.instructions, page.about);
|
|
1748
|
-
page.invoke.disabled = true;
|
|
1749
|
-
}
|
|
1930
|
+
if (!chosen) showServerInstructions(false);
|
|
1750
1931
|
} catch (error) {
|
|
1932
|
+
setServerInstructions("");
|
|
1751
1933
|
client = null;
|
|
1752
1934
|
page.refresh.disabled = true;
|
|
1753
1935
|
page.revision.disabled = false;
|
|
@@ -1825,6 +2007,7 @@ function markTraffic() {
|
|
|
1825
2007
|
|
|
1826
2008
|
|
|
1827
2009
|
function start() {
|
|
2010
|
+
if (page.serverTitle) page.serverTitle.onclick = () => showServerInstructions();
|
|
1828
2011
|
setupAuthentication();
|
|
1829
2012
|
Object.keys(REVISIONS).forEach((version) => {
|
|
1830
2013
|
const option = element("option", null, version);
|
|
@@ -1846,7 +2029,10 @@ function start() {
|
|
|
1846
2029
|
page.connect.onclick = () => (client ? disconnect() : connect());
|
|
1847
2030
|
page.refresh.onclick = async () => {
|
|
1848
2031
|
try {
|
|
1849
|
-
if (client.rules.extensions)
|
|
2032
|
+
if (client.rules.extensions) {
|
|
2033
|
+
const described = await client.initialize();
|
|
2034
|
+
setServerInstructions(described.instructions);
|
|
2035
|
+
}
|
|
1850
2036
|
await loadCatalogue();
|
|
1851
2037
|
} catch (error) {
|
|
1852
2038
|
say(error.message, "off");
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<body>
|
|
11
11
|
|
|
12
12
|
<header class="bar">
|
|
13
|
-
<h1>{{TITLE}}</h1>
|
|
13
|
+
<h1><button id="server-title" type="button" title="Show server instructions" disabled>{{TITLE}}</button></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
16
|
<input id="endpoint" class="endpoint" type="text" spellcheck="false" autocomplete="off">
|
|
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.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/console/oauth-callback.html
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/extensions/__init__.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
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/selection.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2024_11_05.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2025_03_26.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2025_06_18.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/protocol/v2025_11_25.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/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
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/request_state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/server/subscriptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/storage/namespaces.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/transport/__init__.py
RENAMED
|
File without changes
|
{aiohttp_tiny_mcp-0.5.0 → aiohttp_tiny_mcp-0.5.1}/src/aiohttp_tiny_mcp/transport/metadata.py
RENAMED
|
File without changes
|
|
File without changes
|