diff --git a/apps/server/src/mcp/toolkits/preview/tools.test.ts b/apps/server/src/mcp/toolkits/preview/tools.test.ts index d00ff459b9d..652c20e6ac0 100644 --- a/apps/server/src/mcp/toolkits/preview/tools.test.ts +++ b/apps/server/src/mcp/toolkits/preview/tools.test.ts @@ -12,6 +12,19 @@ const schemaHasDescription = (schema: unknown): boolean => { .some((members) => members.some(schemaHasDescription)); }; +const schemaHasMultipleAllOfDescriptions = (schema: unknown): boolean => { + if (!schema || typeof schema !== "object") return false; + const record = schema as Record; + const allOf = Array.isArray(record.allOf) ? record.allOf : []; + const descriptionCount = allOf.filter( + (member) => + member !== null && + typeof member === "object" && + typeof (member as Record).description === "string", + ).length; + return descriptionCount > 1 || Object.values(record).some(schemaHasMultipleAllOfDescriptions); +}; + it("exports provider-compatible object schemas with described parameters", () => { for (const tool of Object.values(PreviewToolkit.tools)) { const schema = Tool.getJsonSchema(tool) as { @@ -27,6 +40,9 @@ it("exports provider-compatible object schemas with described parameters", () => expect(schema.type, `${tool.name} must export a top-level object schema`).toBe("object"); expect(schema.anyOf, `${tool.name} must not export a root anyOf`).toBeUndefined(); expect(schema.oneOf, `${tool.name} must not export a root oneOf`).toBeUndefined(); + if (tool.name === "preview_navigate") { + expect(schemaHasMultipleAllOfDescriptions(schema)).toBe(false); + } expect( schema.properties?.tabId, `${tool.name} must allow an explicit collaborative browser tab target`, diff --git a/packages/contracts/src/previewAutomation.ts b/packages/contracts/src/previewAutomation.ts index d89c4d6f66e..e33615fa4c0 100644 --- a/packages/contracts/src/previewAutomation.ts +++ b/packages/contracts/src/previewAutomation.ts @@ -12,13 +12,10 @@ import { import { ProviderInstanceId } from "./providerInstance.ts"; const BoundedUrl = Schema.String.check(Schema.isTrimmed()) - .check( - Schema.isNonEmpty({ - description: - "Absolute http(s) URL or a schemeless host such as t3.chat or localhost:5173. Schemeless public hosts use https; loopback hosts use http.", - }), - ) + .check(Schema.isNonEmpty()) .check(Schema.isMaxLength(2048)); +const URL_GUIDANCE = + "Absolute http(s) URL or a schemeless host such as t3.chat or localhost:5173. Schemeless public hosts use https; loopback hosts use http."; const OptionalTimeoutMs = Schema.optional( Schema.Int.check(Schema.isGreaterThan(0)) .check(Schema.isLessThanOrEqualTo(60_000)) @@ -83,8 +80,7 @@ export type PreviewAutomationStatus = typeof PreviewAutomationStatus.Type; export const PreviewAutomationOpenInput = Schema.Struct({ ...PreviewAutomationTabTargetFields, url: Schema.optional(BoundedUrl).annotate({ - description: - "Optional initial page URL, for example https://t3.chat or localhost:5173. Omit to open a blank tab.", + description: `Optional initial page URL. ${URL_GUIDANCE} Omit to open a blank tab.`, }), open: Schema.optional( Schema.Boolean.annotate({ @@ -124,7 +120,7 @@ export const BrowserNavigationTarget = Schema.Union([ description: "Selects direct URL navigation.", }), url: BoundedUrl.annotate({ - description: "Direct website URL.", + description: `Direct website URL. ${URL_GUIDANCE}`, }), }), Schema.Struct({ @@ -151,8 +147,7 @@ export type BrowserNavigationTarget = typeof BrowserNavigationTarget.Type; export const PreviewAutomationNavigateInput = Schema.Struct({ ...PreviewAutomationTabTargetFields, url: Schema.optional(BoundedUrl).annotate({ - description: - "Website URL, for example https://t3.chat. Use this for public pages and directly reachable URLs.", + description: `Website URL. ${URL_GUIDANCE} Use this for public pages and directly reachable URLs.`, }), target: Schema.optional( BrowserNavigationTarget.annotate({