Skip to content

Commit 901f104

Browse files
committed
remove view-side validation
1 parent e74ba3b commit 901f104

2 files changed

Lines changed: 12 additions & 290 deletions

File tree

src/app-bridge.test.ts

Lines changed: 12 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -829,267 +829,27 @@ describe("Content block modality validation", () => {
829829
await bridgeTransport.close();
830830
});
831831

832-
describe("Guest-side validation (sendMessage)", () => {
833-
it("throws when sending unsupported content type", async () => {
832+
describe("Host-side validation", () => {
833+
it("host rejects unsupported content in onmessage", async () => {
834834
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
835835
const capabilities: McpUiHostCapabilities = {
836836
...testHostCapabilities,
837837
message: { text: {} },
838838
};
839-
bridge = new AppBridge(
840-
createMockClient() as Client,
841-
testHostInfo,
842-
capabilities,
843-
);
839+
bridge = new AppBridge(null, testHostInfo, capabilities);
844840
bridge.onmessage = async () => ({});
845841
app = new App(testAppInfo, {}, { autoResize: false });
846842

847843
await bridge.connect(bridgeTransport);
848844
await app.connect(appTransport);
849845

850-
expect(() =>
846+
await expect(
851847
app.sendMessage({
852848
role: "user",
853849
content: [
854850
{ type: "image", data: "base64data", mimeType: "image/png" },
855851
],
856852
}),
857-
).toThrow("unsupported content type(s): image");
858-
});
859-
860-
it("allows content when modality is declared", async () => {
861-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
862-
const capabilities: McpUiHostCapabilities = {
863-
...testHostCapabilities,
864-
message: { text: {}, image: {} },
865-
};
866-
bridge = new AppBridge(
867-
createMockClient() as Client,
868-
testHostInfo,
869-
capabilities,
870-
);
871-
bridge.onmessage = async () => ({});
872-
app = new App(testAppInfo, {}, { autoResize: false });
873-
874-
await bridge.connect(bridgeTransport);
875-
await app.connect(appTransport);
876-
877-
const result = await app.sendMessage({
878-
role: "user",
879-
content: [
880-
{ type: "text", text: "hello" },
881-
{ type: "image", data: "base64data", mimeType: "image/png" },
882-
],
883-
});
884-
expect(result).toEqual({});
885-
});
886-
887-
it("skips validation when message capability is not declared (backwards compat)", async () => {
888-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
889-
// No message capability declared
890-
bridge = new AppBridge(
891-
createMockClient() as Client,
892-
testHostInfo,
893-
testHostCapabilities,
894-
);
895-
bridge.onmessage = async () => ({});
896-
app = new App(testAppInfo, {}, { autoResize: false });
897-
898-
await bridge.connect(bridgeTransport);
899-
await app.connect(appTransport);
900-
901-
// Should succeed even with image content since message capability is not declared
902-
const result = await app.sendMessage({
903-
role: "user",
904-
content: [{ type: "image", data: "base64data", mimeType: "image/png" }],
905-
});
906-
expect(result).toEqual({});
907-
});
908-
909-
it("rejects all types when message capability is empty object", async () => {
910-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
911-
const capabilities: McpUiHostCapabilities = {
912-
...testHostCapabilities,
913-
message: {},
914-
};
915-
bridge = new AppBridge(
916-
createMockClient() as Client,
917-
testHostInfo,
918-
capabilities,
919-
);
920-
bridge.onmessage = async () => ({});
921-
app = new App(testAppInfo, {}, { autoResize: false });
922-
923-
await bridge.connect(bridgeTransport);
924-
await app.connect(appTransport);
925-
926-
expect(() =>
927-
app.sendMessage({
928-
role: "user",
929-
content: [{ type: "text", text: "hello" }],
930-
}),
931-
).toThrow("unsupported content type(s): text");
932-
});
933-
934-
it("resource_link type maps to resourceLink modality correctly", async () => {
935-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
936-
const capabilities: McpUiHostCapabilities = {
937-
...testHostCapabilities,
938-
message: { resourceLink: {} },
939-
};
940-
bridge = new AppBridge(
941-
createMockClient() as Client,
942-
testHostInfo,
943-
capabilities,
944-
);
945-
bridge.onmessage = async () => ({});
946-
app = new App(testAppInfo, {}, { autoResize: false });
947-
948-
await bridge.connect(bridgeTransport);
949-
await app.connect(appTransport);
950-
951-
const result = await app.sendMessage({
952-
role: "user",
953-
content: [
954-
{
955-
type: "resource_link",
956-
uri: "test://resource",
957-
name: "Test Resource",
958-
},
959-
],
960-
});
961-
expect(result).toEqual({});
962-
});
963-
});
964-
965-
describe("Guest-side validation (updateModelContext)", () => {
966-
it("throws when sending unsupported content type", async () => {
967-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
968-
const capabilities: McpUiHostCapabilities = {
969-
...testHostCapabilities,
970-
updateModelContext: { text: {} },
971-
};
972-
bridge = new AppBridge(
973-
createMockClient() as Client,
974-
testHostInfo,
975-
capabilities,
976-
);
977-
bridge.onupdatemodelcontext = async () => ({});
978-
app = new App(testAppInfo, {}, { autoResize: false });
979-
980-
await bridge.connect(bridgeTransport);
981-
await app.connect(appTransport);
982-
983-
expect(() =>
984-
app.updateModelContext({
985-
content: [
986-
{ type: "image", data: "base64data", mimeType: "image/png" },
987-
],
988-
}),
989-
).toThrow("unsupported content type(s): image");
990-
});
991-
992-
it("throws when sending structuredContent without capability", async () => {
993-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
994-
const capabilities: McpUiHostCapabilities = {
995-
...testHostCapabilities,
996-
updateModelContext: { text: {} },
997-
};
998-
bridge = new AppBridge(
999-
createMockClient() as Client,
1000-
testHostInfo,
1001-
capabilities,
1002-
);
1003-
bridge.onupdatemodelcontext = async () => ({});
1004-
app = new App(testAppInfo, {}, { autoResize: false });
1005-
1006-
await bridge.connect(bridgeTransport);
1007-
await app.connect(appTransport);
1008-
1009-
expect(() =>
1010-
app.updateModelContext({
1011-
structuredContent: { key: "value" },
1012-
}),
1013-
).toThrow("structuredContent is not supported");
1014-
});
1015-
1016-
it("allows structuredContent when declared", async () => {
1017-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
1018-
const capabilities: McpUiHostCapabilities = {
1019-
...testHostCapabilities,
1020-
updateModelContext: { text: {}, structuredContent: {} },
1021-
};
1022-
bridge = new AppBridge(
1023-
createMockClient() as Client,
1024-
testHostInfo,
1025-
capabilities,
1026-
);
1027-
bridge.onupdatemodelcontext = async () => ({});
1028-
app = new App(testAppInfo, {}, { autoResize: false });
1029-
1030-
await bridge.connect(bridgeTransport);
1031-
await app.connect(appTransport);
1032-
1033-
const result = await app.updateModelContext({
1034-
structuredContent: { key: "value" },
1035-
});
1036-
expect(result).toEqual({});
1037-
});
1038-
1039-
it("skips validation when updateModelContext capability is not declared", async () => {
1040-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
1041-
bridge = new AppBridge(
1042-
createMockClient() as Client,
1043-
testHostInfo,
1044-
testHostCapabilities,
1045-
);
1046-
bridge.onupdatemodelcontext = async () => ({});
1047-
app = new App(testAppInfo, {}, { autoResize: false });
1048-
1049-
await bridge.connect(bridgeTransport);
1050-
await app.connect(appTransport);
1051-
1052-
const result = await app.updateModelContext({
1053-
content: [{ type: "image", data: "base64data", mimeType: "image/png" }],
1054-
structuredContent: { key: "value" },
1055-
});
1056-
expect(result).toEqual({});
1057-
});
1058-
});
1059-
1060-
describe("Host-side validation", () => {
1061-
it("host rejects unsupported content in onmessage", async () => {
1062-
[appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair();
1063-
const capabilities: McpUiHostCapabilities = {
1064-
...testHostCapabilities,
1065-
message: { text: {} },
1066-
};
1067-
// Use null client so we don't get guest-side validation
1068-
// (the bridge still validates on the host side)
1069-
bridge = new AppBridge(null, testHostInfo, capabilities);
1070-
bridge.onmessage = async () => ({});
1071-
1072-
// Create app without host capabilities knowledge to bypass guest validation
1073-
app = new App(testAppInfo, {}, { autoResize: false });
1074-
1075-
await bridge.connect(bridgeTransport);
1076-
await app.connect(appTransport);
1077-
1078-
// The app received capabilities during connect, so it will validate.
1079-
// To test host-side validation independently, we directly send the request.
1080-
await expect(
1081-
app.request(
1082-
{
1083-
method: "ui/message" as any,
1084-
params: {
1085-
role: "user",
1086-
content: [
1087-
{ type: "image", data: "base64data", mimeType: "image/png" },
1088-
],
1089-
},
1090-
},
1091-
EmptyResultSchema,
1092-
),
1093853
).rejects.toThrow("unsupported content type(s): image");
1094854
});
1095855

@@ -1107,17 +867,11 @@ describe("Content block modality validation", () => {
1107867
await app.connect(appTransport);
1108868

1109869
await expect(
1110-
app.request(
1111-
{
1112-
method: "ui/update-model-context" as any,
1113-
params: {
1114-
content: [
1115-
{ type: "audio", data: "base64", mimeType: "audio/mp3" },
1116-
],
1117-
},
1118-
},
1119-
EmptyResultSchema,
1120-
),
870+
app.updateModelContext({
871+
content: [
872+
{ type: "audio", data: "base64", mimeType: "audio/mp3" },
873+
],
874+
}),
1121875
).rejects.toThrow("unsupported content type(s): audio");
1122876
});
1123877

@@ -1135,15 +889,9 @@ describe("Content block modality validation", () => {
1135889
await app.connect(appTransport);
1136890

1137891
await expect(
1138-
app.request(
1139-
{
1140-
method: "ui/update-model-context" as any,
1141-
params: {
1142-
structuredContent: { key: "value" },
1143-
},
1144-
},
1145-
EmptyResultSchema,
1146-
),
892+
app.updateModelContext({
893+
structuredContent: { key: "value" },
894+
}),
1147895
).rejects.toThrow("structuredContent is not supported");
1148896
});
1149897
});

src/app.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ import {
1818
} from "@modelcontextprotocol/sdk/types.js";
1919
import { AppNotification, AppRequest, AppResult } from "./types";
2020
import { PostMessageTransport } from "./message-transport";
21-
import {
22-
validateContentModalities,
23-
buildValidationErrorMessage,
24-
} from "./content-validation";
2521
import {
2622
LATEST_PROTOCOL_VERSION,
2723
McpUiAppCapabilities,
@@ -625,14 +621,6 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
625621
* @see {@link McpUiMessageRequest `McpUiMessageRequest`} for request structure
626622
*/
627623
sendMessage(params: McpUiMessageRequest["params"], options?: RequestOptions) {
628-
const modalities = this._hostCapabilities?.message;
629-
if (modalities !== undefined) {
630-
const result = validateContentModalities(params.content, modalities);
631-
if (!result.valid) {
632-
throw new Error(buildValidationErrorMessage(result, "ui/message"));
633-
}
634-
}
635-
636624
return this.request(
637625
<McpUiMessageRequest>{
638626
method: "ui/message",
@@ -691,20 +679,6 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
691679
params: McpUiUpdateModelContextRequest["params"],
692680
options?: RequestOptions,
693681
) {
694-
const modalities = this._hostCapabilities?.updateModelContext;
695-
if (modalities !== undefined) {
696-
const result = validateContentModalities(
697-
params.content,
698-
modalities,
699-
params.structuredContent !== undefined,
700-
);
701-
if (!result.valid) {
702-
throw new Error(
703-
buildValidationErrorMessage(result, "ui/update-model-context"),
704-
);
705-
}
706-
}
707-
708682
return this.request(
709683
<McpUiUpdateModelContextRequest>{
710684
method: "ui/update-model-context",

0 commit comments

Comments
 (0)