Skip to content

Commit 6304a47

Browse files
feat(dashboard): harden MCP widget authority (#111679)
1 parent 6b80b01 commit 6304a47

23 files changed

Lines changed: 596 additions & 603 deletions

apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -488,62 +488,24 @@ public struct BoardMcpAppDescriptor: Codable, Sendable {
488488
public let servername: String
489489
public let toolname: String
490490
public let uiresourceuri: String
491-
public let originsessionkey: String
492491
public let toolcallid: String
493492

494493
public init(
495494
servername: String,
496495
toolname: String,
497496
uiresourceuri: String,
498-
originsessionkey: String,
499497
toolcallid: String)
500498
{
501499
self.servername = servername
502500
self.toolname = toolname
503501
self.uiresourceuri = uiresourceuri
504-
self.originsessionkey = originsessionkey
505502
self.toolcallid = toolcallid
506503
}
507504

508505
private enum CodingKeys: String, CodingKey {
509506
case servername = "serverName"
510507
case toolname = "toolName"
511508
case uiresourceuri = "uiResourceUri"
512-
case originsessionkey = "originSessionKey"
513-
case toolcallid = "toolCallId"
514-
}
515-
}
516-
517-
public struct BoardMcpAppPinDescriptor: Codable, Sendable {
518-
public let viewid: String
519-
public let servername: String
520-
public let toolname: String
521-
public let uiresourceuri: String
522-
public let originsessionkey: String
523-
public let toolcallid: String
524-
525-
public init(
526-
viewid: String,
527-
servername: String,
528-
toolname: String,
529-
uiresourceuri: String,
530-
originsessionkey: String,
531-
toolcallid: String)
532-
{
533-
self.viewid = viewid
534-
self.servername = servername
535-
self.toolname = toolname
536-
self.uiresourceuri = uiresourceuri
537-
self.originsessionkey = originsessionkey
538-
self.toolcallid = toolcallid
539-
}
540-
541-
private enum CodingKeys: String, CodingKey {
542-
case viewid = "viewId"
543-
case servername = "serverName"
544-
case toolname = "toolName"
545-
case uiresourceuri = "uiResourceUri"
546-
case originsessionkey = "originSessionKey"
547509
case toolcallid = "toolCallId"
548510
}
549511
}
@@ -586,19 +548,19 @@ public struct BoardWidgetMcpAppContent: Codable, Sendable {
586548

587549
public struct BoardWidgetMcpAppPutContent: Codable, Sendable {
588550
public let kind: String
589-
public let descriptor: BoardMcpAppPinDescriptor
551+
public let viewid: String
590552

591553
public init(
592554
kind: String,
593-
descriptor: BoardMcpAppPinDescriptor)
555+
viewid: String)
594556
{
595557
self.kind = kind
596-
self.descriptor = descriptor
558+
self.viewid = viewid
597559
}
598560

599561
private enum CodingKeys: String, CodingKey {
600562
case kind
601-
case descriptor
563+
case viewid = "viewId"
602564
}
603565
}
604566

@@ -691,14 +653,14 @@ public struct BoardWidgetGrantParams: Codable, Sendable {
691653
public let name: String
692654
public let decision: AnyCodable
693655
public let revision: Int
694-
public let instanceid: String?
656+
public let instanceid: String
695657

696658
public init(
697659
sessionkey: String,
698660
name: String,
699661
decision: AnyCodable,
700662
revision: Int,
701-
instanceid: String? = nil)
663+
instanceid: String)
702664
{
703665
self.sessionkey = sessionkey
704666
self.name = name
@@ -720,13 +682,13 @@ public struct BoardWidgetAppViewParams: Codable, Sendable {
720682
public let sessionkey: String
721683
public let name: String
722684
public let revision: Int
723-
public let instanceid: String?
685+
public let instanceid: String
724686

725687
public init(
726688
sessionkey: String,
727689
name: String,
728690
revision: Int,
729-
instanceid: String? = nil)
691+
instanceid: String)
730692
{
731693
self.sessionkey = sessionkey
732694
self.name = name

packages/gateway-protocol/src/schema/board.test.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,22 @@ describe("BoardWidgetPutParamsSchema", () => {
9090
name: "weather-app",
9191
content: {
9292
kind: "mcp-app",
93-
descriptor: {
94-
viewId: "mcp-app-source",
95-
serverName: "weather",
96-
toolName: "show",
97-
uiResourceUri: "ui://weather/app",
98-
originSessionKey: "agent:main:main",
99-
toolCallId: "call-1",
100-
},
93+
viewId: "mcp-app-source",
10194
},
10295
};
10396
expect(Value.Check(BoardWidgetPutParamsSchema, pin)).toBe(true);
97+
expect(
98+
Value.Check(BoardWidgetPutParamsSchema, {
99+
...pin,
100+
content: { ...pin.content, viewId: undefined },
101+
}),
102+
).toBe(false);
104103
expect(
105104
Value.Check(BoardWidgetPutParamsSchema, {
106105
...pin,
107106
content: {
108-
...pin.content,
109-
descriptor: { ...pin.content.descriptor, viewId: undefined },
107+
kind: "mcp-app",
108+
descriptor: { viewId: "mcp-app-source" },
110109
},
111110
}),
112111
).toBe(false);
@@ -127,6 +126,7 @@ describe("BoardWidgetAppView schemas", () => {
127126
Value.Check(BoardWidgetAppViewParamsSchema, {
128127
sessionKey: "agent:main:main",
129128
name: "weather-app",
129+
revision: 3,
130130
}),
131131
).toBe(false);
132132
expect(
@@ -139,7 +139,7 @@ describe("BoardWidgetAppView schemas", () => {
139139
});
140140

141141
describe("BoardWidgetGrantParamsSchema", () => {
142-
it("requires the widget revision being approved", () => {
142+
it("requires the widget revision and instance being approved", () => {
143143
expect(
144144
Value.Check(BoardWidgetGrantParamsSchema, {
145145
sessionKey: "agent:main:main",
@@ -154,6 +154,15 @@ describe("BoardWidgetGrantParamsSchema", () => {
154154
sessionKey: "agent:main:main",
155155
name: "status",
156156
decision: "granted",
157+
revision: 1,
158+
}),
159+
).toBe(false);
160+
expect(
161+
Value.Check(BoardWidgetGrantParamsSchema, {
162+
sessionKey: "agent:main:main",
163+
name: "status",
164+
decision: "granted",
165+
instanceId: "widget-instance",
157166
}),
158167
).toBe(false);
159168
});

packages/gateway-protocol/src/schema/board.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,10 @@ export const BoardMcpAppDescriptorSchema = closedObject({
120120
serverName: NonEmptyString,
121121
toolName: NonEmptyString,
122122
uiResourceUri: NonEmptyString,
123-
originSessionKey: NonEmptyString,
124123
toolCallId: NonEmptyString,
125124
});
126125
export type BoardMcpAppDescriptor = Static<typeof BoardMcpAppDescriptorSchema>;
127126

128-
export const BoardMcpAppPinDescriptorSchema = closedObject({
129-
viewId: NonEmptyString,
130-
serverName: NonEmptyString,
131-
toolName: NonEmptyString,
132-
uiResourceUri: NonEmptyString,
133-
originSessionKey: NonEmptyString,
134-
toolCallId: NonEmptyString,
135-
});
136-
export type BoardMcpAppPinDescriptor = Static<typeof BoardMcpAppPinDescriptorSchema>;
137-
138127
export const BoardWidgetHtmlContentSchema = closedObject({
139128
kind: Type.Literal("html"),
140129
html: Type.String({ maxLength: 262_144 }),
@@ -145,13 +134,16 @@ export const BoardWidgetMcpAppContentSchema = closedObject({
145134
});
146135
export const BoardWidgetMcpAppPutContentSchema = closedObject({
147136
kind: Type.Literal("mcp-app"),
148-
descriptor: BoardMcpAppPinDescriptorSchema,
137+
viewId: NonEmptyString,
149138
});
150139
export const BoardWidgetContentSchema = Type.Union([
151140
BoardWidgetHtmlContentSchema,
152141
BoardWidgetMcpAppContentSchema,
153142
]);
154143
export type BoardWidgetContent = Static<typeof BoardWidgetContentSchema>;
144+
export type BoardWidgetMaterializedContent =
145+
| Static<typeof BoardWidgetHtmlContentSchema>
146+
| (Static<typeof BoardWidgetMcpAppContentSchema> & { interactive: boolean });
155147

156148
export const BoardCanvasDocumentSourceSchema = closedObject({
157149
kind: Type.Literal("canvas-doc"),
@@ -188,23 +180,23 @@ export const BoardWidgetPutParamsSchema = closedObject({
188180
export type BoardWidgetPutParams = Static<typeof BoardWidgetPutParamsSchema>;
189181
/** Materialized input accepted by the board store after gateway source resolution. */
190182
export type BoardWidgetMaterializedPutParams = Omit<BoardWidgetPutParams, "content"> & {
191-
content: BoardWidgetContent;
183+
content: BoardWidgetMaterializedContent;
192184
};
193185

194186
export const BoardWidgetGrantParamsSchema = closedObject({
195187
sessionKey: NonEmptyString,
196188
name: BoardWidgetNameSchema,
197189
decision: Type.Union([Type.Literal("granted"), Type.Literal("rejected")]),
198190
revision: Type.Integer({ minimum: 1 }),
199-
instanceId: Type.Optional(NonEmptyString),
191+
instanceId: NonEmptyString,
200192
});
201193
export type BoardWidgetGrantParams = Static<typeof BoardWidgetGrantParamsSchema>;
202194

203195
export const BoardWidgetAppViewParamsSchema = closedObject({
204196
sessionKey: NonEmptyString,
205197
name: BoardWidgetNameSchema,
206198
revision: Type.Integer({ minimum: 1 }),
207-
instanceId: Type.Optional(NonEmptyString),
199+
instanceId: NonEmptyString,
208200
});
209201
export type BoardWidgetAppViewParams = Static<typeof BoardWidgetAppViewParamsSchema>;
210202

packages/gateway-protocol/src/schema/protocol-schemas.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ import {
160160
BoardFocusTabCommandSchema,
161161
BoardGetParamsSchema,
162162
BoardMcpAppDescriptorSchema,
163-
BoardMcpAppPinDescriptorSchema,
164163
BoardOpSchema,
165164
BoardSetChatDockCommandSchema,
166165
BoardSnapshotSchema,
@@ -608,7 +607,6 @@ export const ProtocolSchemas = {
608607
BoardWidgetRemoveOp: BoardWidgetRemoveOpSchema,
609608
BoardOp: BoardOpSchema,
610609
BoardMcpAppDescriptor: BoardMcpAppDescriptorSchema,
611-
BoardMcpAppPinDescriptor: BoardMcpAppPinDescriptorSchema,
612610
BoardWidgetHtmlContent: BoardWidgetHtmlContentSchema,
613611
BoardWidgetMcpAppContent: BoardWidgetMcpAppContentSchema,
614612
BoardWidgetMcpAppPutContent: BoardWidgetMcpAppPutContentSchema,

src/agents/mcp-ui-resource.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,23 @@ describe("MCP App UI resources", () => {
6262
},
6363
],
6464
}));
65-
const authorizeAppToolCall = vi.fn(async () => true);
65+
const authorizeAppInteraction = vi.fn(async () => true);
6666
const result = await fetchMcpAppView({
6767
runtime: sessionRuntime,
6868
serverName: "demo",
6969
toolName: "show",
7070
uiResourceUri: "ui://demo/app",
7171
toolInput: { city: "Paris" },
7272
toolResult: { content: [{ type: "text", text: "ok" }] },
73-
authorizeAppToolCall,
73+
authorizeAppInteraction,
7474
});
7575

7676
expect(result?.viewId).toMatch(/^mcp-app-/u);
7777
expect(getMcpAppViewLease(result?.viewId ?? "", sessionRuntime)).toMatchObject({
7878
html: "<html>demo</html>",
7979
toolInput: { city: "Paris" },
8080
permissions: { geolocation: {} },
81-
authorizeAppToolCall,
81+
authorizeAppInteraction,
8282
});
8383
expect(
8484
getMcpAppViewLease(

src/agents/mcp-ui-resource.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export type McpAppViewLease = {
3131
csp?: McpAppCsp;
3232
permissions?: McpAppPermissions;
3333
allowedAppToolNames?: ReadonlySet<string>;
34-
authorizeAppToolCall?: () => boolean | Promise<boolean>;
34+
authorizeAppInteraction?: () => boolean | Promise<boolean>;
3535
readOnly?: true;
3636
toolInput: unknown;
3737
toolResult: CallToolResult;
@@ -224,7 +224,7 @@ export async function fetchMcpAppView(params: {
224224
toolInput: unknown;
225225
toolResult: CallToolResult;
226226
allowedAppToolNames?: ReadonlySet<string>;
227-
authorizeAppToolCall?: () => boolean | Promise<boolean>;
227+
authorizeAppInteraction?: () => boolean | Promise<boolean>;
228228
readOnly?: true;
229229
viewId?: string;
230230
}): Promise<
@@ -286,7 +286,9 @@ export async function fetchMcpAppView(params: {
286286
...(params.allowedAppToolNames
287287
? { allowedAppToolNames: new Set(params.allowedAppToolNames) }
288288
: {}),
289-
...(params.authorizeAppToolCall ? { authorizeAppToolCall: params.authorizeAppToolCall } : {}),
289+
...(params.authorizeAppInteraction
290+
? { authorizeAppInteraction: params.authorizeAppInteraction }
291+
: {}),
290292
...(params.readOnly ? { readOnly: true as const } : {}),
291293
toolInput: params.toolInput,
292294
toolResult: params.toolResult,

0 commit comments

Comments
 (0)