Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions apps/server/src/mcp/McpHttpServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,15 +420,13 @@ it.effect.each(["limit", "runLimit", "maxCharsPerItem"])(
},
);

it("routes same-child follow-ups to t3_thread_send and independent reviews to delegate_task", () => {
it("keeps cross-tool routing out of the delegate_task description", () => {
const description = OrchestratorToolkit.tools.delegate_task.description ?? "";
expect(description).toContain(
"For independent T3 delegated review rounds, call delegate_task again",
);
expect(description).toContain(
"For a follow-up that needs the same child's conversation, use t3_thread_send on its childThreadId.",
);
expect(description).not.toContain("not the target for starting another");
expect(description).toContain("Start one child agent with only `task`");
// Review rounds, follow-ups, and polling are routed in the system text.
expect(description).not.toContain("t3_thread_send");
expect(description).not.toContain("Do not poll");
expect(description).not.toContain("Prefer native");
});

it.effect(
Expand All @@ -447,7 +445,8 @@ it.effect(
const linkTool = server.tools.find(({ tool }) => tool.name === "link_pull_request");
expect(linkTool?.tool.annotations?.idempotentHint).toBe(true);
expect(linkTool?.tool.annotations?.openWorldHint).toBe(false);
expect(linkTool?.tool.description).toContain("Register every pull request you open");
expect(linkTool?.tool.description).toContain("Link a PR to this thread");
expect(linkTool?.tool.description).toContain("settles the thread on merge");

const denied = yield* server
.callTool({ name: "list_thread_pull_requests", arguments: {} })
Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/mcp/toolkits/attachment/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,20 @@ const shared = {
const AttachmentUploadTool = Tool.make("t3_attachment_prepare_upload", {
...shared,
description:
"Create the app's signed upload URL. POST the exact bytes to relativeUrl on this MCP server's HTTP origin, then pass the attachment metadata and returned ID to t3_thread_send_attachments. Upload is separate from sending; provider attachment support is decided by its adapter.",
"Get an upload URL. POST the file's exact bytes to `relativeUrl` on this MCP server's HTTP origin, then send via `t3_thread_send_attachments` with the returned `attachmentId`.",
parameters: Schema.Struct({ upload: AttachmentCreateUploadUrlInput }),
success: AttachmentCreateUploadUrlResult,
}).annotate(Tool.Destructive, true);
const AttachmentDiscardTool = Tool.make("t3_attachment_discard", {
...shared,
description:
"Discard a pending upload. Already-delivered thread attachments are never deleted by this operation.",
description: "Discard a pending upload (not a sent attachment).",
parameters: AttachmentDeleteInput,
success: Schema.Struct({}),
}).annotate(Tool.Destructive, true);
const AttachmentSendTool = Tool.make("t3_thread_send_attachments", {
...shared,
description:
"Send uploaded attachments to this thread or any other thread in this environment. Each call is a new message, without a retry key. Acceptance does not mean the provider can consume the attachment or has finished the turn. The target cannot have broader permission modes than the caller; failures retain claimed files when dispatch outcome is uncertain.",
"Send uploaded attachments as a new message to a thread. No retry key. Target modes cannot exceed yours. Success does not mean the provider read them or finished.",
parameters: Schema.Struct({
threadId: Schema.optional(ThreadId),
message: Schema.optional(Schema.String.check(Schema.isMaxLength(120000))),
Expand Down
15 changes: 7 additions & 8 deletions apps/server/src/mcp/toolkits/device/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ const DeviceToolFailure = Schema.Union([DeviceToolError, OrchestratorMcpFailure]

const DeviceListTool = Tool.make("device_list", {
description:
"List iOS Simulators and Android Emulators on this environment's device hosts, which platforms each host can run, and which devices are already open in this thread's Device panel. Call this before device_open when you do not know a device id.",
// Empty Struct emits object|array, which can make providers reject every server tool.
"List iOS Simulators and Android Emulators here, what each host can run, and what is open in this thread's Device panel. Call before `device_open` when you do not know a device id.",
// An empty struct serializes as `anyOf [object, array]`, which some
// providers reject and then drop every tool on the server with it.
parameters: Schema.Struct({
hostId: Schema.optional(
Schema.String.annotate({ description: "Limit to one device host. Defaults to all hosts." }),
Expand All @@ -48,7 +49,7 @@ const DeviceListTool = Tool.make("device_list", {

const DeviceOpenTool = Tool.make("device_open", {
description:
"Open a simulator or emulator for this thread: boots it if needed, starts its live stream, and shows it in the user's Device panel so they can watch. Returns the agent-device CLI invocation pinned to the device; drive the device with that CLI afterwards.",
"Open a simulator or emulator for this thread. Boots it if needed and shows it in the user's Device panel. Returns the `agent-device` CLI call pinned to that device. Drive the device with that CLI.",
parameters: DeviceToolOpenInput,
success: DeviceToolOpenResult,
failure: DeviceToolFailure,
Expand All @@ -69,7 +70,7 @@ const DeviceOpenTool = Tool.make("device_open", {

export const DeviceScreenshotTool = Tool.make("device_screenshot", {
description:
"Capture the current screen of an open device as a PNG image. Use it to see what the user sees; for taps and text use the agent-device CLI.",
"Get a PNG of an open device's screen. For taps and text, use the `agent-device` CLI.",
parameters: DeviceToolTargetInput,
success: DeviceToolScreenshotResult,
failure: DeviceToolFailure,
Expand All @@ -83,11 +84,9 @@ export const DeviceScreenshotTool = Tool.make("device_screenshot", {

const DeviceCloseTool = Tool.make("device_close", {
description:
"Remove a device from this thread's Device panel. Pass shutdown=true to also power the simulator or emulator off.",
"Remove a device from this thread's Device panel. `shutdown=true` also turns it off.",
parameters: DeviceToolCloseInput,
success: Schema.Record(Schema.String, Schema.Never).annotate({
description: "The device was closed.",
}),
success: Schema.Record(Schema.String, Schema.Never),
failure: DeviceToolFailure,
dependencies,
})
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/mcp/toolkits/environment/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const shared = {
const EnvironmentReadTool = Tool.make("t3_environment_read", {
...shared,
description:
"Read this server's identity and selected environment preferences. Provider/model availability is exposed by orchestrator_capabilities. Writing instructions are limited to 4,000 characters.",
"Read this server's identity and preferences. Providers and models: `orchestrator_capabilities`.",
success: Schema.Struct({
environmentId: ExecutionEnvironmentDescriptor.fields.environmentId,
label: Schema.String,
Expand All @@ -54,7 +54,7 @@ const EnvironmentReadTool = Tool.make("t3_environment_read", {
const EnvironmentPreferencesTool = Tool.make("t3_environment_preferences_update", {
...shared,
description:
"Update selected environment-wide preferences through normal settings persistence and notifications. Requires a live full-access/default calling thread. Omitted fields are preserved; empty customInstructions clears them.",
"Update environment-wide preferences. Live full-access or default caller thread only. Omitted fields are unchanged; `customInstructions: ''` clears it.",
parameters: Schema.Struct({
defaultThreadEnvMode: ServerSettingsPatch.fields.defaultThreadEnvMode,
newWorktreesStartFromOrigin: ServerSettingsPatch.fields.newWorktreesStartFromOrigin,
Expand Down
14 changes: 7 additions & 7 deletions apps/server/src/mcp/toolkits/html/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ const Html = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(512_0
});

const PAGE_RULES =
'Write one self-contained document with inline <style> and <script>. Local images written as absolute file paths (src="/abs/shot.png", CSS url(/abs/bg.webp), or a JS string) are inlined automatically; remote http(s) URLs, such as a CDN chart library, load as-is.';
"Inline <style> and <script>. Local images as absolute paths are inlined. Remote URLs load as-is.";

export const HtmlPreviewTool = Tool.make("html_preview", {
description: `Render an HTML page in T3's headless browser and get back a PNG screenshot, contentHeight (the height the page needs at this width), and its console output: log, info, warning, error, and uncaught exceptions, with stack traces pointing into page.html. console.log is a fine way to report your own checks. Use it to check and iterate on a page before html_render. The first preview on a machine can report that T3 is still installing its preview browser; call again a minute later. ${PAGE_RULES} The page gets the theme variables and layout described in html_render.`,
description: `Check an HTML page before html_render. Returns PNG, contentHeight, and console output. If it says the browser is still installing, call again in a minute. ${PAGE_RULES} Theme and layout rules are in html_render.`,
parameters: Schema.Struct({
html: Html,
width: Schema.optional(
Schema.Int.annotate({
description: `Viewport width in CSS pixels, 240-1600. Defaults to ${HTML_RENDER_COLUMN_WIDTH}, the reply column; use about 390 to check phones.`,
description: `CSS pixels, 240 to 1600. Default ${HTML_RENDER_COLUMN_WIDTH}. Use about 390 for phones.`,
}),
),
appearance: Schema.optional(
Schema.Literals(["dark", "light"]).annotate({
description: "Theme to preview. Defaults to dark.",
description: "Default dark.",
}),
),
}),
Expand Down Expand Up @@ -71,15 +71,15 @@ export const HtmlPreviewTool = Tool.make("html_preview", {

// This affects only the caller's thread, not its workspace, so read-only sandboxes and plan mode may use it.
const HtmlRenderTool = Tool.make(HTML_RENDER_TOOL_NAME, {
description: `Show a finished HTML page (chart, table, diagram, collage, mockup) inline in this thread, above your final text reply; call it before writing that reply. The reader already sees the page, so the reply should not announce it, say where it is, or restate it: add only what the page doesn't say. Preview with html_preview first. T3 fits the frame to the page's height at each reader's width. On mobile, a height below the page's contentHeight caps the frame there, and the rest scrolls inside it; web and desktop fit the whole page, up to ${HTML_RENDER_MAX_FIT_HEIGHT}px. ${PAGE_RULES} ${HTML_RENDER_LAYOUT_GUIDE} ${HTML_RENDER_THEME_GUIDE}`,
description: `Show a finished HTML page inline in this thread, above your final reply. Do not announce or restate the page. Frame fits height to ${HTML_RENDER_MAX_FIT_HEIGHT}px. On mobile, content past the given height scrolls. ${HTML_RENDER_LAYOUT_GUIDE} ${HTML_RENDER_THEME_GUIDE}`,
parameters: Schema.Struct({
html: Html,
title: Schema.String.check(
Schema.isMinLength(1),
Schema.isMaxLength(HTML_RENDER_MAX_TITLE_LENGTH),
).annotate({ description: "Short name for the page." }),
),
height: Schema.Int.annotate({
description: `The frame height in CSS pixels, ${HTML_RENDER_MIN_HEIGHT}-${HTML_RENDER_MAX_HEIGHT}. Use html_preview's contentHeight. A lower height makes long content scroll inside the frame on mobile only.`,
description: `Frame height in CSS pixels, ${HTML_RENDER_MIN_HEIGHT} to ${HTML_RENDER_MAX_HEIGHT}. Use the contentHeight from html_preview.`,
}),
}),
success: Schema.Struct({
Expand Down
Loading
Loading