Skip to content

Commit 8c99c5c

Browse files
committed
Show task support hints in tools-get and tools-call example
Extract formatToolHints() combining annotations + task support, used by both formatToolLine and formatToolDetail. tools-get now shows task:required or task:optional in the hints bracket. The tools-call example includes --task or [--task] accordingly. https://claude.ai/code/session_01JCmVVCvBbxPUrxy3BcgjCc
1 parent 1b6dcbe commit 8c99c5c

File tree

2 files changed

+103
-17
lines changed

2 files changed

+103
-17
lines changed

src/cli/output.ts

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,31 @@ export function formatToolAnnotations(annotations: Tool['annotations']): string
237237
return parts.length > 0 ? parts.join(', ') : null;
238238
}
239239

240+
/**
241+
* Get the task support mode for a tool ('required', 'optional', or undefined)
242+
*/
243+
export function getToolTaskSupport(tool: Tool): string | undefined {
244+
const toolAny = tool as Record<string, unknown>;
245+
const execution = toolAny.execution as Record<string, unknown> | undefined;
246+
return execution?.taskSupport as string | undefined;
247+
}
248+
249+
/**
250+
* Format tool hints: annotations + task support mode.
251+
* Returns a string like "destructive, open-world, task:required" or null if empty.
252+
*/
253+
export function formatToolHints(tool: Tool): string | null {
254+
const parts: string[] = [];
255+
256+
const annotationsStr = formatToolAnnotations(tool.annotations);
257+
if (annotationsStr) parts.push(annotationsStr);
258+
259+
const taskSupport = getToolTaskSupport(tool);
260+
if (taskSupport) parts.push(`task:${taskSupport}`);
261+
262+
return parts.length > 0 ? parts.join(', ') : null;
263+
}
264+
240265
/**
241266
* Convert a JSON Schema type definition to a simplified type string
242267
* e.g., { type: 'string' } -> 'string'
@@ -466,15 +491,8 @@ export function formatToolParamsInline(schema: Record<string, unknown>): string
466491
export function formatToolLine(tool: Tool): string {
467492
const bullet = chalk.dim('*');
468493
const params = formatToolParamsInline(tool.inputSchema as Record<string, unknown>);
469-
const parts: string[] = [];
470-
const annotationsStr = formatToolAnnotations(tool.annotations);
471-
if (annotationsStr) parts.push(annotationsStr);
472-
// Show task execution mode
473-
const toolAny = tool as Record<string, unknown>;
474-
const execution = toolAny.execution as Record<string, unknown> | undefined;
475-
const taskSupport = execution?.taskSupport as string | undefined;
476-
if (taskSupport) parts.push(`task:${taskSupport}`);
477-
const suffix = parts.length > 0 ? ` ${chalk.gray(`[${parts.join(', ')}]`)}` : '';
494+
const hintsStr = formatToolHints(tool);
495+
const suffix = hintsStr ? ` ${chalk.gray(`[${hintsStr}]`)}` : '';
478496
return `${bullet} ${grayBacktick()}${chalk.cyan(tool.name)} ${params}${grayBacktick()}${suffix}`;
479497
}
480498

@@ -536,10 +554,10 @@ export function formatToolDetail(tool: Tool): string {
536554
lines.push(chalk.bold(`# ${title}`));
537555
}
538556

539-
// Tool header: Tool: `name` [annotations]
540-
const annotationsStr = formatToolAnnotations(tool.annotations);
541-
const annotationsSuffix = annotationsStr ? ` ${chalk.gray(`[${annotationsStr}]`)}` : '';
542-
lines.push(`${chalk.bold('Tool:')} ${inBackticks(tool.name)}${annotationsSuffix}`);
557+
// Tool header: Tool: `name` [hints]
558+
const hintsStr = formatToolHints(tool);
559+
const hintsSuffix = hintsStr ? ` ${chalk.gray(`[${hintsStr}]`)}` : '';
560+
lines.push(`${chalk.bold('Tool:')} ${inBackticks(tool.name)}${hintsSuffix}`);
543561

544562
// Input args
545563
lines.push('');
@@ -612,11 +630,19 @@ function exampleValue(propSchema: Record<string, unknown>): string {
612630
export function formatToolCallExample(tool: Tool, sessionName?: string): string | null {
613631
const schema = tool.inputSchema as Record<string, unknown> | undefined;
614632
const properties = schema?.properties as Record<string, Record<string, unknown>> | undefined;
633+
const session = sessionName || '<@session>';
634+
635+
// Build --task flag based on task support
636+
const taskSupport = getToolTaskSupport(tool);
637+
const taskFlag =
638+
taskSupport === 'required' ? ' --task' : taskSupport === 'optional' ? ' [--task]' : '';
639+
640+
const bullet = chalk.dim('*');
615641

616642
if (!properties || Object.keys(properties).length === 0) {
617643
// Tool takes no arguments — still show the simple call
618-
const session = sessionName || '<@session>';
619-
return `${chalk.bold('Call example:')}\n mcpc ${session} tools-call ${tool.name}`;
644+
const cmd = `mcpc ${session} tools-call ${tool.name}${taskFlag}`;
645+
return `${chalk.bold('Call example:')}\n${bullet} ${grayBacktick()}${chalk.cyan(cmd)}${grayBacktick()}`;
620646
}
621647

622648
const requiredNames = (schema?.required as string[]) || [];
@@ -632,13 +658,13 @@ export function formatToolCallExample(tool: Tool, sessionName?: string): string
632658
params.push(...optionalInOrder.slice(0, remaining));
633659
}
634660

635-
const session = sessionName || '<@session>';
636661
const argParts = params.map((name) => {
637662
const val = exampleValue(properties[name] ?? {});
638663
return `${name}:=${val}`;
639664
});
640665

641-
return `${chalk.bold('Call example:')}\n mcpc ${session} tools-call ${tool.name} ${argParts.join(' ')}`;
666+
const cmd = `mcpc ${session} tools-call ${tool.name} ${argParts.join(' ')}${taskFlag}`;
667+
return `${chalk.bold('Call example:')}\n${bullet} ${grayBacktick()}${chalk.cyan(cmd)}${grayBacktick()}`;
642668
}
643669

644670
/**

test/unit/cli/output.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
formatHuman,
5757
logTarget,
5858
formatToolCallExample,
59+
formatToolHints,
5960
} from '../../../src/cli/output.js';
6061
import type {
6162
Tool,
@@ -909,6 +910,65 @@ describe('formatToolCallExample', () => {
909910
const output = formatToolCallExample(tool);
910911
expect(output).toContain('<@session>');
911912
});
913+
914+
it('should include --task for task:required tools', () => {
915+
const tool = {
916+
name: 'long-run',
917+
inputSchema: { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] },
918+
execution: { taskSupport: 'required' },
919+
} as unknown as Tool;
920+
921+
const output = formatToolCallExample(tool, '@s');
922+
expect(output).toContain('--task');
923+
expect(output).not.toContain('[--task]');
924+
});
925+
926+
it('should include [--task] for task:optional tools', () => {
927+
const tool = {
928+
name: 'maybe-async',
929+
inputSchema: { type: 'object', properties: {} },
930+
execution: { taskSupport: 'optional' },
931+
} as unknown as Tool;
932+
933+
const output = formatToolCallExample(tool, '@s');
934+
expect(output).toContain('[--task]');
935+
});
936+
});
937+
938+
describe('formatToolHints', () => {
939+
it('should combine annotations and task support', () => {
940+
const tool = {
941+
name: 'test',
942+
inputSchema: { type: 'object', properties: {} },
943+
annotations: { destructiveHint: true, openWorldHint: true },
944+
execution: { taskSupport: 'required' },
945+
} as unknown as Tool;
946+
947+
const hints = formatToolHints(tool);
948+
expect(hints).toContain('destructive');
949+
expect(hints).toContain('open-world');
950+
expect(hints).toContain('task:required');
951+
});
952+
953+
it('should return null when no annotations and no task support', () => {
954+
const tool: Tool = {
955+
name: 'plain',
956+
inputSchema: { type: 'object', properties: {} },
957+
};
958+
959+
expect(formatToolHints(tool)).toBeNull();
960+
});
961+
962+
it('should show only task support when no annotations', () => {
963+
const tool = {
964+
name: 'async-only',
965+
inputSchema: { type: 'object', properties: {} },
966+
execution: { taskSupport: 'optional' },
967+
} as unknown as Tool;
968+
969+
const hints = formatToolHints(tool);
970+
expect(hints).toBe('task:optional');
971+
});
912972
});
913973

914974
describe('formatServerDetails', () => {

0 commit comments

Comments
 (0)