fix(open-webui): drop MCP exposure and address remaining review comments

Per ajnart's review (and CodeRabbit withdrawing its own MCP comment on the
predecessor PR), the Open WebUI chat isn't a meaningful MCP surface:
- remove the .meta({ mcp }) metadata from every openWebUi procedure

Remaining CodeRabbit comments from the predecessor PR that weren't carried over:
- processWeb: constrain the URL to http(s) and reject loopback/metadata hosts
  (private LAN ranges stay allowed for self-hosters); it is also creator-gated
- uploadFile/transcribe: bound the base64 payload and validate filename/type
- docs: align the example URL with the integration default port (8080)
- new chat: clear the composer draft so it doesn't leak into the fresh chat
- web ingestion: keep the typed URL when ingestion fails
- persistence: fall back to the default title for image-only opening turns, and
  don't persist a partial reply from a failed turn as finished history
- multimodal round-trip: make the native chat/history contracts accept the same
  text-or-parts content union as completions, and flatten (not drop) non-text
  nodes when rebuilding transcripts so attachment-backed chats aren't lost.
  Image data URLs are still kept out of persisted history by design.
This commit is contained in:
Thomas Camlong committed 2026-07-11 19:11:12 +02:00
1 parent c076a36fec
commit 3d2a2d7b55
7 files changed
+107 -78

No files matched your search

@@ -47,25 +47,30 @@ export const openWebUiCompletionChunkSchema = z.object({
.nullish(),
});
// A single chat message exchanged with the model / persisted in a chat.
export const openWebUiChatMessageSchema = z.object({
role: z.enum(["system", "user", "assistant"]),
content: z.string(),
});
export type OpenWebUiChatMessage = z.infer<typeof openWebUiChatMessageSchema>;
// Multimodal content part (OpenAI vision format). Used only for outbound
// completion requests so users can attach images to a message.
// Multimodal content part (OpenAI vision format). Lets a message carry text
// and/or images.
export const openWebUiContentPartSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("text"), text: z.string() }),
z.object({ type: z.literal("image_url"), image_url: z.object({ url: z.string() }) }),
]);
// Message content is either plain text or multimodal parts. Both the completion
// and the native chat/history contracts use this so an attachment-backed message
// round-trips through create/update/get without parse failures or lossy drops.
export const openWebUiMessageContentSchema = z.union([z.string(), z.array(openWebUiContentPartSchema)]);
// A single chat message exchanged with the model / persisted in a chat.
export const openWebUiChatMessageSchema = z.object({
role: z.enum(["system", "user", "assistant"]),
content: openWebUiMessageContentSchema,
});
export type OpenWebUiChatMessage = z.infer<typeof openWebUiChatMessageSchema>;
// A completion message can carry plain text or multimodal parts (text + images).
export const openWebUiCompletionMessageSchema = z.object({
role: z.enum(["system", "user", "assistant"]),
content: z.union([z.string(), z.array(openWebUiContentPartSchema)]),
content: openWebUiMessageContentSchema,
});
export type OpenWebUiCompletionMessage = z.infer<typeof openWebUiCompletionMessageSchema>;
@@ -190,7 +195,7 @@ export const openWebUiHistoryMessageSchema = z.object({
id: z.string().nullish(),
parentId: z.string().nullish(),
role: z.string().nullish(),
content: z.string().nullish(),
content: openWebUiMessageContentSchema.nullish(),
});
export const openWebUiChatSchema = z.object({
@@ -206,7 +211,7 @@ export const openWebUiChatSchema = z.object({
.array(
z.object({
role: z.string().nullish(),
content: z.string().nullish(),
content: openWebUiMessageContentSchema.nullish(),
}),
)
.nullish(),