1export type T3McpToolLogo = "t3-code";
2
3export interface T3McpToolPresentation {
4 readonly displayName: string;
5 readonly logo: T3McpToolLogo;
6}
7
8export type T3McpToolSummaryAction =
9 | "capabilities"
10 | "delegate"
11 | "task-status"
12 | "task-cancel"
13 | "schedule-run"
14 | "schedule-create"
15 | "schedule-list"
16 | "schedule-update"
17 | "schedule-delete"
18 | "thread-create"
19 | "thread-list"
20 | "thread-read"
21 | "thread-send"
22 | "thread-wait"
23 | "thread-interrupt"
24 | "workflow-stop"
25 | "thread-configuration"
26 | "thread-configure"
27 | "thread-fork"
28 | "thread-merge"
29 | "thread-search"
30 | "thread-transfers"
31 | "thread-organize"
32 | "thread-update"
33 | "queue-list"
34 | "queue-read"
35 | "queue-edit"
36 | "queue-cancel"
37 | "queue-reorder"
38 | "queue-steer"
39 | "question-list"
40 | "question-read"
41 | "question-respond"
42 | "secret-request"
43 | "worktree-handoff"
44 | "worktree-list"
45 | "worktree-status"
46 | "project-list"
47 | "project-read"
48 | "project-create"
49 | "project-update"
50 | "project-delete"
51 | "project-clone"
52 | "environment-read"
53 | "environment-update"
54 | "attachment-prepare"
55 | "attachment-discard"
56 | "attachment-send"
57 | "link-pr"
58 | "unlink-pr"
59 | "list-prs"
60 | "watch-pr"
61 | "unwatch-pr"
62 | "write-pr-guides"
63 | "read-pr-guide"
64 | "browser"
65 | "device"
66 | "html-preview"
67 | "html-render";
68
69export interface T3McpToolDefinition {
70 readonly displayName: string;
71 readonly labels: readonly [action: string, running: string, completed: string, detail: string];
72 readonly icon: "t3-code" | "browser" | "device" | "pull-request";
73 readonly summaryAction: T3McpToolSummaryAction;
74}
75
76function tool(
77 labels: T3McpToolDefinition["labels"],
78 summaryAction: T3McpToolSummaryAction,
79 icon: T3McpToolDefinition["icon"] = "t3-code",
80 displayName = `${labels[0]} ${labels[3]}`,
81): T3McpToolDefinition {
82 return { displayName, labels, icon, summaryAction };
83}
84
85const T3_MCP_SERVER_ALIASES = new Set(["t3-code", "t3_code", "t3code"]);
86
87// Cards, activity rows, summaries, and provider identity recovery share this inventory.
88const T3_MCP_TOOLS: Readonly<Record<string, T3McpToolDefinition>> = {
89 link_pull_request: tool(
90 ["Link", "Linking", "Linked", "a pull request"],
91 "link-pr",
92 "pull-request",
93 ),
94 unlink_pull_request: tool(
95 ["Unlink", "Unlinking", "Unlinked", "a pull request"],
96 "unlink-pr",
97 "pull-request",
98 ),
99 list_thread_pull_requests: tool(
100 ["Check", "Checking", "Checked", "linked pull requests"],
101 "list-prs",
102 "pull-request",
103 ),
104 watch_pull_request: tool(
105 ["Watch", "Watching", "Watching", "a pull request"],
106 "watch-pr",
107 "pull-request",
108 ),
109 unwatch_pull_request: tool(
110 ["Stop watching", "Stopping watching", "Stopped watching", "a pull request"],
111 "unwatch-pr",
112 "pull-request",
113 ),
114 write_pull_request_guides: tool(
115 ["Write", "Writing", "Started", "review guides"],
116 "write-pr-guides",
117 "pull-request",
118 ),
119 read_pull_request_guide: tool(
120 ["Read", "Reading", "Read", "a review guide"],
121 "read-pr-guide",
122 "pull-request",
123 ),
124 orchestrator_capabilities: tool(
125 ["Get", "Getting", "Got", "orchestration capabilities"],
126 "capabilities",
127 ),
128 delegate_task: tool(["Delegate", "Delegating", "Delegated", "a child task"], "delegate"),
129 task_status: tool(["Get", "Getting", "Got", "delegated task status"], "task-status"),
130 task_cancel: tool(
131 ["Cancel", "Canceling", "Requested cancellation of", "delegated task"],
132 "task-cancel",
133 ),
134 schedule_task: tool(
135 ["Schedule", "Scheduling", "Scheduled", "a recurring task"],
136 "schedule-create",
137 ),
138 list_scheduled_tasks: tool(["List", "Listing", "Listed", "scheduled tasks"], "schedule-list"),
139 update_scheduled_task: tool(
140 ["Update", "Updating", "Updated", "a scheduled task"],
141 "schedule-update",
142 ),
143 delete_scheduled_task: tool(
144 ["Delete", "Deleting", "Requested deletion of", "a scheduled task"],
145 "schedule-delete",
146 ),
147 request_secret: tool(["Ask for", "Asking for", "Asked for", "a secret"], "secret-request"),
148 create_threads: tool(["Create", "Creating", "Created", "T3 threads"], "thread-create"),
149 t3_thread_start: tool(["Start", "Starting", "Started", "a T3 thread"], "thread-create"),
150 t3_thread_list: tool(["List", "Listing", "Listed", "T3 threads"], "thread-list"),
151 t3_thread_read: tool(["Read", "Reading", "Read", "a T3 thread"], "thread-read"),
152 t3_thread_send: tool(["Send", "Sending", "Sent", "to a T3 thread"], "thread-send"),
153 t3_thread_wait: tool(["Wait", "Waiting", "Waited", "for a T3 thread"], "thread-wait"),
154 t3_workflow_stop: tool(
155 ["Stop", "Stopping", "Requested a stop for", "a workflow"],
156 "workflow-stop",
157 ),
158 t3_thread_interrupt: tool(
159 ["Interrupt", "Interrupting", "Requested an interrupt of", "a T3 thread"],
160 "thread-interrupt",
161 ),
162 t3_worktree_handoff: tool(
163 ["Hand off", "Handing off", "Handed off", "thread to a git worktree"],
164 "worktree-handoff",
165 ),
166 t3_worktree_status: tool(["Get", "Getting", "Got", "thread worktree status"], "worktree-status"),
167 preview_status: tool(["Get", "Getting", "Got", "preview browser status"], "browser", "browser"),
168 preview_open: tool(
169 ["Open", "Opening", "Opened", "a page in the preview browser"],
170 "browser",
171 "browser",
172 ),
173 preview_navigate: tool(
174 ["Navigate", "Navigating", "Navigated", "the preview browser"],
175 "browser",
176 "browser",
177 ),
178 preview_dialog: tool(
179 ["Respond", "Responding", "Responded", "to a preview browser dialog"],
180 "browser",
181 "browser",
182 ),
183 preview_snapshot: tool(
184 ["Take a snapshot of", "Taking a snapshot of", "Took a snapshot of", "the preview page"],
185 "browser",
186 "browser",
187 "Snapshot the preview page",
188 ),
189 preview_click: tool(
190 ["Click", "Clicking", "Clicked", "in the preview browser"],
191 "browser",
192 "browser",
193 ),
194 preview_press: tool(
195 ["Press", "Pressing", "Pressed", "a key in the preview browser"],
196 "browser",
197 "browser",
198 ),
199 preview_type: tool(["Type", "Typing", "Typed", "in the preview browser"], "browser", "browser"),
200 preview_hover: tool(
201 ["Hover", "Hovering", "Hovered", "in the preview browser"],
202 "browser",
203 "browser",
204 ),
205 preview_select: tool(
206 ["Choose", "Choosing", "Chose", "an option in the preview browser"],
207 "browser",
208 "browser",
209 ),
210 preview_drag: tool(
211 ["Drag", "Dragging", "Dragged", "in the preview browser"],
212 "browser",
213 "browser",
214 ),
215 preview_upload: tool(
216 ["Upload", "Uploading", "Uploaded", "files to the preview browser"],
217 "browser",
218 "browser",
219 ),
220 preview_scroll: tool(
221 ["Scroll", "Scrolling", "Scrolled", "the preview browser"],
222 "browser",
223 "browser",
224 ),
225 preview_resize: tool(
226 ["Resize", "Resizing", "Resized", "the preview browser"],
227 "browser",
228 "browser",
229 ),
230 preview_evaluate: tool(
231 ["Evaluate", "Evaluating", "Evaluated", "script in the preview browser"],
232 "browser",
233 "browser",
234 ),
235 preview_wait_for: tool(
236 ["Wait", "Waiting", "Waited", "for the preview page"],
237 "browser",
238 "browser",
239 ),
240 preview_set_appearance: tool(
241 ["Set", "Setting", "Set", "preview browser appearance"],
242 "browser",
243 "browser",
244 ),
245 preview_recording_start: tool(
246 ["Start", "Starting", "Started", "recording the preview browser"],
247 "browser",
248 "browser",
249 ),
250 preview_recording_stop: tool(
251 ["Stop", "Stopping", "Stopped", "recording the preview browser"],
252 "browser",
253 "browser",
254 ),
255 device_list: tool(["List", "Listing", "Listed", "simulators and emulators"], "device", "device"),
256 device_open: tool(
257 ["Open", "Opening", "Opened", "a device in the Device panel"],
258 "device",
259 "device",
260 ),
261 device_screenshot: tool(
262 ["Take a screenshot of", "Taking a screenshot of", "Took a screenshot of", "the device"],
263 "device",
264 "device",
265 ),
266 device_close: tool(["Close", "Closing", "Closed", "a device"], "device", "device"),
267 run_scheduled_task_now: tool(
268 ["Run", "Running", "Requested a run of", "a scheduled task"],
269 "schedule-run",
270 ),
271 t3_queue_list: tool(["List", "Listing", "Listed", "queued messages"], "queue-list"),
272 t3_queue_read: tool(["Read", "Reading", "Read", "a queued message"], "queue-read"),
273 t3_queue_edit: tool(["Edit", "Editing", "Edited", "a queued message"], "queue-edit"),
274 t3_queue_cancel: tool(
275 ["Cancel", "Canceling", "Requested cancellation of", "a queued run"],
276 "queue-cancel",
277 ),
278 t3_queue_reorder: tool(["Reorder", "Reordering", "Reordered", "a queued run"], "queue-reorder"),
279 t3_queue_promote_to_steer: tool(
280 ["Steer with", "Steering with", "Requested steering with", "a queued message"],
281 "queue-steer",
282 ),
283 t3_pending_request_list: tool(
284 ["List", "Listing", "Listed", "pending questions"],
285 "question-list",
286 ),
287 t3_pending_request_read: tool(["Read", "Reading", "Read", "pending questions"], "question-read"),
288 t3_pending_request_respond: tool(
289 ["Answer", "Answering", "Answered", "pending questions"],
290 "question-respond",
291 ),
292 t3_thread_configuration: tool(
293 ["Read", "Reading", "Read", "thread configuration"],
294 "thread-configuration",
295 ),
296 t3_thread_configure: tool(["Set", "Setting", "Set", "thread model"], "thread-configure"),
297 t3_thread_fork: tool(["Fork", "Forking", "Requested a fork of", "this thread"], "thread-fork"),
298 t3_thread_merge_back: tool(
299 ["Merge", "Merging", "Requested a merge of", "thread context"],
300 "thread-merge",
301 ),
302 t3_thread_search: tool(["Search", "Searching", "Searched", "thread content"], "thread-search"),
303 t3_thread_transfers: tool(["Read", "Reading", "Read", "thread transfers"], "thread-transfers"),
304 t3_thread_organize: tool(["Organize", "Organizing", "Organized", "a thread"], "thread-organize"),
305 t3_thread_update: tool(["Update", "Updating", "Updated", "T3 thread metadata"], "thread-update"),
306 t3_worktree_list: tool(["List", "Listing", "Listed", "workspace branches"], "worktree-list"),
307 t3_preview_list: tool(["List", "Listing", "Listed", "preview tabs"], "browser", "browser"),
308 t3_preview_close: tool(["Close", "Closing", "Closed", "a preview tab"], "browser", "browser"),
309 t3_environment_read: tool(
310 ["Read", "Reading", "Read", "environment preferences"],
311 "environment-read",
312 ),
313 t3_environment_preferences_update: tool(
314 ["Update", "Updating", "Updated", "environment preferences"],
315 "environment-update",
316 ),
317 t3_thread_launch: tool(["Launch", "Launching", "Launched", "a project thread"], "thread-create"),
318 t3_project_list: tool(["List", "Listing", "Listed", "projects"], "project-list"),
319 t3_project_read: tool(["Read", "Reading", "Read", "a project"], "project-read"),
320 t3_project_create: tool(["Register", "Registering", "Registered", "a project"], "project-create"),
321 t3_project_update: tool(["Update", "Updating", "Updated", "a project"], "project-update"),
322 t3_project_delete: tool(["Delete", "Deleting", "Deleted", "a project"], "project-delete"),
323 t3_project_clone: tool(["Clone", "Cloning", "Cloned", "a repository"], "project-clone"),
324 t3_attachment_prepare_upload: tool(
325 ["Prepare", "Preparing", "Prepared", "an attachment upload"],
326 "attachment-prepare",
327 ),
328 t3_attachment_discard: tool(
329 ["Discard", "Discarding", "Discarded", "a pending attachment"],
330 "attachment-discard",
331 ),
332 t3_thread_send_attachments: tool(["Send", "Sending", "Sent", "attachments"], "attachment-send"),
333 html_preview: tool(["Preview", "Previewing", "Previewed", "an HTML page"], "html-preview"),
334 html_render: tool(["Render", "Rendering", "Rendered", "an HTML page"], "html-render"),
335};
336
337/**
338 * The T3 orchestration tool inventory, used to gate loose name matching on
339 * both the server (ACP MCP identity recovery) and the client (logo branding).
340 */
341export const T3_MCP_TOOL_NAMES: ReadonlySet<string> = new Set(Object.keys(T3_MCP_TOOLS));
342
343function normalizeT3McpToolLabel(value: string): string {
344 return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim();
345}
346
347/**
348 * ACP agents disagree on how the injected T3 server prefixes its tools:
349 * `mcp__t3-code__x` (Claude/Cursor), `t3-code.x` (Codex), plus single
350 * underscore, colon, slash, dash, and space separators seen from registry
351 * agents. The prefix match is deliberately loose because the display-name
352 * inventory is the real gate; unknown tools stay on the generic renderer.
353 */
354function resolveT3McpToolName(value: string): string | null {
355 const label = normalizeT3McpToolLabel(value);
356 const mcpMatch = /^mcp__(?<server>.+?)__(?<tool>.+)$/i.exec(label);
357 if (mcpMatch?.groups) {
358 const { server, tool } = mcpMatch.groups;
359 return server !== undefined &&
360 tool !== undefined &&
361 T3_MCP_SERVER_ALIASES.has(server.toLowerCase())
362 ? tool
363 : null;
364 }
365
366 const namespaceMatch = /^(?<server>t3-code|t3_code|t3code)(?:[.:/]|\s*·\s*)(?<tool>.+)$/i.exec(
367 label,
368 );
369 if (namespaceMatch?.groups) {
370 return namespaceMatch.groups.tool ?? null;
371 }
372
373 const prefixed = /^(?:mcp[-_]{1,2})?t3[-_ ]?code(?:__|[-_.:/ ])(?<tool>.+)$/i.exec(label);
374 const candidate = prefixed?.groups?.tool ?? label;
375 if (Object.hasOwn(T3_MCP_TOOLS, candidate)) return candidate;
376 // OpenCode 2 registers one server per thread, `t3-code-<thread>`, and joins
377 // it to the tool with `_`. Thread ids can hold `_` too, so take the longest
378 // known tool name that ends the label.
379 if (!/^t3-code-/i.test(label)) return null;
380 let longest: string | null = null;
381 for (const tool of Object.keys(T3_MCP_TOOLS)) {
382 if (label.endsWith(`_${tool}`) && tool.length > (longest?.length ?? 0)) longest = tool;
383 }
384 return longest;
385}
386
387/** The bare T3 tool name (`html_render`) for any provider's spelling of it. */
388export function resolveT3McpToolId(toolName: string | null | undefined): string | null {
389 const name = toolName == null ? null : resolveT3McpToolName(toolName);
390 return name !== null && Object.hasOwn(T3_MCP_TOOLS, name) ? name : null;
391}
392
393export function resolveT3McpToolDefinition(
394 toolName: string | null | undefined,
395): T3McpToolDefinition | null {
396 const name = toolName == null ? null : resolveT3McpToolName(toolName);
397 return name !== null && Object.hasOwn(T3_MCP_TOOLS, name) ? T3_MCP_TOOLS[name]! : null;
398}
399
400export function resolveT3McpToolPresentation(
401 toolName: string | null | undefined,
402): T3McpToolPresentation | null {
403 const definition = resolveT3McpToolDefinition(toolName);
404 return definition === null ? null : { displayName: definition.displayName, logo: "t3-code" };
405}
406
407export function resolveT3McpToolSummaryAction(
408 toolName: string | null | undefined,
409): T3McpToolSummaryAction | null {
410 return resolveT3McpToolDefinition(toolName)?.summaryAction ?? null;
411}