feat: add compact list signatures
Co-authored-by: yuhp <yu.haip@gmail.com>
This commit is contained in:
parent
5d8e64d5d5
commit
0e50f2b564
@ -18,6 +18,7 @@
|
||||
- Fail OAuth flows immediately when the server never creates an authorization URL, instead of waiting for a browser callback that cannot arrive. (Issue #115)
|
||||
- Support `mcporter list server.tool --schema` to print a single tool's schema instead of the whole server. (Issue #116)
|
||||
- Surface MCP server `instructions` from the initialize response in single-server `mcporter list` text and JSON output. (Issue #76)
|
||||
- Add compact `mcporter list <server> --brief` / `--signatures` output for scanning signatures without doc blocks, examples, or schemas. (PR #144, thanks @yuhp)
|
||||
|
||||
### Config
|
||||
|
||||
|
||||
@ -204,7 +204,7 @@ npx mcporter call --stdio "bun run ./local-server.ts" --name local-tools
|
||||
- **Unknown long flags fail fast.** `mcporter call server.tool --source import` now errors instead of silently turning `--source` into a positional tool argument. Use `source=import`, `--args '{"source":"import"}'`, or insert `--` before literal positional values that begin with `--`.
|
||||
- **Cheatsheet.** See [docs/tool-calling.md](docs/tool-calling.md) for a quick comparison of every supported call style (auto-inferred verbs, flags, function-calls, and ad-hoc URLs).
|
||||
- **Auto-correct.** If you typo a tool name, MCPorter inspects the server’s tool catalog, retries when the edit distance is tiny, and otherwise prints a `Did you mean …?` hint. The heuristic (and how to tune it) is captured in [docs/call-heuristic.md](docs/call-heuristic.md).
|
||||
- **Richer single-server output.** `mcporter list <server>` now prints TypeScript-style signatures, inline comments, return-shape hints, and command examples that mirror the new call syntax. Optional parameters stay hidden by default—add `--all-parameters` or `--schema` whenever you need the full JSON schema.
|
||||
- **Richer single-server output.** `mcporter list <server>` now prints TypeScript-style signatures, inline comments, return-shape hints, and command examples that mirror the new call syntax. Optional parameters stay hidden by default—add `--all-parameters` or `--schema` whenever you need the full JSON schema. Prefer a tighter scan? `mcporter list <server> --brief` (or `--signatures`) keeps just the compact signatures and optional summaries.
|
||||
|
||||
## Installation
|
||||
|
||||
@ -298,7 +298,7 @@ Friendly ergonomics baked into the proxy and result helpers:
|
||||
|
||||
Drop down to `runtime.callTool()` whenever you need explicit control over arguments, metadata, or streaming options.
|
||||
|
||||
Call `mcporter list <server>` any time you need the TypeScript-style signature, optional parameter hints, and sample invocations that match the CLI's function-call syntax.
|
||||
Call `mcporter list <server>` any time you need the TypeScript-style signature, optional parameter hints, and sample invocations that match the CLI's function-call syntax. Add `--brief` or `--signatures` when you only want compact signatures.
|
||||
|
||||
## Generate a Standalone CLI
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ Key details:
|
||||
- Run `mcporter list <server> --all-parameters` whenever you want the full signature; the footer repeats `Optional parameters hidden; run with --all-parameters to view all fields.` any time truncation occurs.
|
||||
- Return types come from each tool’s output schema, so you’ll see concrete names when providers include `title` metadata (e.g. `DocumentConnection`). When no schema is advertised we omit the `: Type` suffix entirely instead of showing `unknown`.
|
||||
- Each server concludes with a short `Examples:` block that mirrors the preferred function-call syntax.
|
||||
- Run `mcporter list <server> --brief` (or `--signatures`) when you only want the compact signatures and optional summaries without doc blocks or examples.
|
||||
|
||||
## Function-Call Syntax Details
|
||||
|
||||
|
||||
@ -18,9 +18,15 @@ A quick reference for the primary `mcporter` subcommands. Each command inherits
|
||||
MCP initialization.
|
||||
- With `server.tool`, prints just that tool; combine with `--schema` for a single
|
||||
tool schema.
|
||||
- Add `--brief` or `--signatures` with a server or `server.tool` target to keep
|
||||
the server header/instructions and print compact signatures without doc
|
||||
comments, examples, or schemas.
|
||||
- Hidden alias: `list-tools` (kept for muscle memory; not advertised in help output).
|
||||
- Hidden ad-hoc flag aliases: `--sse` for `--http-url`, `--insecure` for `--allow-http` (for plain HTTP testing).
|
||||
- Flags:
|
||||
- `--brief` – compact single-server output; cannot be combined with `--json`,
|
||||
`--schema`, `--verbose`, or `--all-parameters`.
|
||||
- `--signatures` – alias for `--brief`.
|
||||
- `--all-parameters` – include every optional parameter in the signature.
|
||||
- `--schema` – pretty-print the JSON schema for each tool.
|
||||
- `--timeout <ms>` – per-server timeout when enumerating all servers.
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
createEmptyStatusCounts,
|
||||
createUnknownResult,
|
||||
type ListJsonServerEntry,
|
||||
printBriefTool,
|
||||
printSingleServerHeader,
|
||||
printToolDetail,
|
||||
summarizeStatusCounts,
|
||||
@ -35,12 +36,14 @@ export function extractListFlags(args: string[]): {
|
||||
format: ListOutputFormat;
|
||||
verbose: boolean;
|
||||
includeSources: boolean;
|
||||
brief: boolean;
|
||||
} {
|
||||
let schema = false;
|
||||
let timeoutMs: number | undefined;
|
||||
let requiredOnly = true;
|
||||
let verbose = false;
|
||||
let includeSources = false;
|
||||
let brief = false;
|
||||
const format = consumeOutputFormat(args, {
|
||||
defaultFormat: 'text',
|
||||
allowed: ['text', 'json'],
|
||||
@ -75,13 +78,36 @@ export function extractListFlags(args: string[]): {
|
||||
args.splice(index, 1);
|
||||
continue;
|
||||
}
|
||||
if (token === '--brief' || token === '--signatures') {
|
||||
brief = true;
|
||||
args.splice(index, 1);
|
||||
continue;
|
||||
}
|
||||
if (token === '--timeout') {
|
||||
timeoutMs = consumeTimeoutFlag(args, index, { flagName: '--timeout' });
|
||||
continue;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return { schema, timeoutMs, requiredOnly, ephemeral, format, verbose, includeSources };
|
||||
if (brief) {
|
||||
const conflicts: string[] = [];
|
||||
if (format === 'json') {
|
||||
conflicts.push('--json');
|
||||
}
|
||||
if (schema) {
|
||||
conflicts.push('--schema');
|
||||
}
|
||||
if (verbose) {
|
||||
conflicts.push('--verbose');
|
||||
}
|
||||
if (!requiredOnly) {
|
||||
conflicts.push('--all-parameters');
|
||||
}
|
||||
if (conflicts.length > 0) {
|
||||
throw new Error(`--brief cannot be used with ${conflicts.join(', ')}`);
|
||||
}
|
||||
}
|
||||
return { schema, timeoutMs, requiredOnly, ephemeral, format, verbose, includeSources, brief };
|
||||
}
|
||||
|
||||
type ListOutputFormat = 'text' | 'json';
|
||||
@ -110,6 +136,9 @@ export async function handleList(
|
||||
target = prepared.target;
|
||||
|
||||
if (!target) {
|
||||
if (flags.brief) {
|
||||
throw new Error('--brief requires a server target.');
|
||||
}
|
||||
const previousStdioLogMode = setStdioLogMode('silent');
|
||||
try {
|
||||
const servers = runtime.getDefinitions();
|
||||
@ -347,6 +376,20 @@ export async function handleList(
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
if (flags.brief) {
|
||||
let optionalOmitted = false;
|
||||
for (const entry of metadataEntries) {
|
||||
const detail = printBriefTool(definition, entry, flags.requiredOnly);
|
||||
optionalOmitted ||= detail.optionalOmitted;
|
||||
}
|
||||
if (flags.requiredOnly && optionalOmitted) {
|
||||
console.log(` ${extraDimText('Optional parameters hidden; run with --all-parameters to view all fields.')}`);
|
||||
console.log('');
|
||||
}
|
||||
console.log(summaryLine);
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
const examples: string[] = [];
|
||||
let optionalOmitted = false;
|
||||
for (const entry of metadataEntries) {
|
||||
@ -406,6 +449,8 @@ export function printListHelp(): void {
|
||||
' --yes Skip confirmation prompts when persisting.',
|
||||
'',
|
||||
'Display flags:',
|
||||
' --brief Show compact signatures only for a single server.',
|
||||
' --signatures Alias for --brief.',
|
||||
' --schema Show tool schemas when listing servers.',
|
||||
' --all-parameters Include optional parameters in tool docs.',
|
||||
' --json Emit a JSON summary instead of text.',
|
||||
@ -416,6 +461,8 @@ export function printListHelp(): void {
|
||||
'Examples:',
|
||||
' mcporter list',
|
||||
' mcporter list linear --schema',
|
||||
' mcporter list linear --brief',
|
||||
' mcporter list linear.list_issues --signatures',
|
||||
' mcporter list https://mcp.example.com/mcp',
|
||||
' mcporter list --http-url https://localhost:3333/mcp --schema',
|
||||
];
|
||||
@ -537,19 +584,8 @@ async function loadServerInstructions(
|
||||
runtime: Awaited<ReturnType<(typeof import('../runtime.js'))['createRuntime']>>,
|
||||
serverName: string
|
||||
): Promise<string | undefined> {
|
||||
if (typeof runtime.connect !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const context = await runtime.connect(serverName);
|
||||
const instructions =
|
||||
typeof context.client.getInstructions === 'function' ? context.client.getInstructions() : undefined;
|
||||
if (typeof instructions !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = instructions.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
} catch {
|
||||
if (typeof runtime.getInstructions !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
return runtime.getInstructions(serverName);
|
||||
}
|
||||
|
||||
@ -13,6 +13,10 @@ export interface ToolDetailResult {
|
||||
optionalOmitted: boolean;
|
||||
}
|
||||
|
||||
export interface ToolBriefResult {
|
||||
optionalOmitted: boolean;
|
||||
}
|
||||
|
||||
export interface ListJsonServerEntry {
|
||||
name: string;
|
||||
status: StatusCategory;
|
||||
@ -122,6 +126,30 @@ export function printToolDetail(
|
||||
};
|
||||
}
|
||||
|
||||
export function printBriefTool(
|
||||
definition: ReturnType<Awaited<ReturnType<(typeof import('../runtime.js'))['createRuntime']>>['getDefinition']>,
|
||||
metadata: ToolMetadata,
|
||||
requiredOnly: boolean
|
||||
): ToolBriefResult {
|
||||
const doc = buildToolDoc({
|
||||
serverName: definition.name,
|
||||
toolName: metadata.tool.name,
|
||||
description: metadata.tool.description,
|
||||
outputSchema: metadata.tool.outputSchema,
|
||||
options: metadata.options,
|
||||
requiredOnly,
|
||||
colorize: true,
|
||||
});
|
||||
console.log(` ${doc.signature}`);
|
||||
if (doc.optionalSummary && requiredOnly) {
|
||||
console.log(` ${doc.optionalSummary}`);
|
||||
}
|
||||
console.log('');
|
||||
return {
|
||||
optionalOmitted: doc.hiddenOptions.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
function buildExampleOptions(
|
||||
definition: ReturnType<Awaited<ReturnType<(typeof import('../runtime.js'))['createRuntime']>>['getDefinition']>
|
||||
): { selector?: string; wrapExpression?: boolean } | undefined {
|
||||
|
||||
@ -47,6 +47,10 @@ class KeepAliveRuntime implements Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
async getInstructions(server: string): Promise<string | undefined> {
|
||||
return this.base.getInstructions?.(server);
|
||||
}
|
||||
|
||||
async listTools(server: string, options?: ListToolsOptions): Promise<Awaited<ReturnType<Runtime['listTools']>>> {
|
||||
if (this.shouldUseDaemon(server)) {
|
||||
return (await this.invokeWithRestart(server, 'listTools', () =>
|
||||
|
||||
@ -59,6 +59,7 @@ export interface Runtime {
|
||||
getDefinitions(): ServerDefinition[];
|
||||
getDefinition(server: string): ServerDefinition;
|
||||
registerDefinition(definition: ServerDefinition, options?: { overwrite?: boolean }): void;
|
||||
getInstructions?(server: string): Promise<string | undefined>;
|
||||
listTools(server: string, options?: ListToolsOptions): Promise<ServerToolInfo[]>;
|
||||
callTool(server: string, toolName: string, options?: CallOptions): Promise<unknown>;
|
||||
listResources(server: string, options?: Partial<ListResourcesRequest['params']>): Promise<unknown>;
|
||||
@ -152,6 +153,25 @@ class McpRuntime implements Runtime {
|
||||
this.clients.delete(definition.name);
|
||||
}
|
||||
|
||||
async getInstructions(server: string): Promise<string | undefined> {
|
||||
const contextPromise = this.clients.get(server.trim());
|
||||
if (!contextPromise) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const context = await contextPromise;
|
||||
const instructions =
|
||||
typeof context.client.getInstructions === 'function' ? context.client.getInstructions() : undefined;
|
||||
if (typeof instructions !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = instructions.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// listTools queries tool metadata and optionally includes schemas when requested.
|
||||
async listTools(server: string, options: ListToolsOptions = {}): Promise<ServerToolInfo[]> {
|
||||
// Toggle auto authorization so list can run without forcing OAuth flows.
|
||||
|
||||
@ -8,6 +8,7 @@ describe('CLI list flag parsing', () => {
|
||||
const args = ['--timeout', '7500', '--schema', 'server'];
|
||||
const flags = extractListFlags(args);
|
||||
expect(flags).toEqual({
|
||||
brief: false,
|
||||
schema: true,
|
||||
timeoutMs: 7500,
|
||||
requiredOnly: true,
|
||||
@ -24,6 +25,7 @@ describe('CLI list flag parsing', () => {
|
||||
const args = ['--all-parameters', 'server'];
|
||||
const flags = extractListFlags(args);
|
||||
expect(flags).toEqual({
|
||||
brief: false,
|
||||
schema: false,
|
||||
timeoutMs: undefined,
|
||||
requiredOnly: false,
|
||||
@ -40,9 +42,33 @@ describe('CLI list flag parsing', () => {
|
||||
const args = ['--json', 'server'];
|
||||
const flags = extractListFlags(args);
|
||||
expect(flags.format).toBe('json');
|
||||
expect(flags.brief).toBe(false);
|
||||
expect(args).toEqual(['server']);
|
||||
});
|
||||
|
||||
it('parses --brief and --signatures aliases', async () => {
|
||||
const { extractListFlags } = await cliModulePromise;
|
||||
const briefArgs = ['--brief', 'server'];
|
||||
const briefFlags = extractListFlags(briefArgs);
|
||||
expect(briefFlags.brief).toBe(true);
|
||||
expect(briefArgs).toEqual(['server']);
|
||||
|
||||
const signatureArgs = ['--signatures', 'server'];
|
||||
const signatureFlags = extractListFlags(signatureArgs);
|
||||
expect(signatureFlags.brief).toBe(true);
|
||||
expect(signatureArgs).toEqual(['server']);
|
||||
});
|
||||
|
||||
it('rejects --brief with incompatible display flags', async () => {
|
||||
const { extractListFlags } = await cliModulePromise;
|
||||
expect(() => extractListFlags(['--brief', '--json', 'server'])).toThrow('--brief cannot be used with --json');
|
||||
expect(() => extractListFlags(['--brief', '--schema', 'server'])).toThrow('--brief cannot be used with --schema');
|
||||
expect(() => extractListFlags(['--brief', '--verbose', 'server'])).toThrow('--brief cannot be used with --verbose');
|
||||
expect(() => extractListFlags(['--brief', '--all-parameters', 'server'])).toThrow(
|
||||
'--brief cannot be used with --all-parameters'
|
||||
);
|
||||
});
|
||||
|
||||
it('treats --sse as a hidden alias for --http-url in ad-hoc mode', async () => {
|
||||
const { extractListFlags } = await cliModulePromise;
|
||||
const args = ['--sse', 'https://mcp.example.com/sse', 'list'];
|
||||
|
||||
@ -159,11 +159,7 @@ describe('CLI list formatting', () => {
|
||||
getDefinitions: () => [definition],
|
||||
getDefinition: () => definition,
|
||||
listTools: vi.fn().mockResolvedValue([{ name: 'search_assets' }]),
|
||||
connect: vi.fn().mockResolvedValue({
|
||||
client: {
|
||||
getInstructions: () => 'Use asset IDs from search results when calling mutation tools.',
|
||||
},
|
||||
}),
|
||||
getInstructions: vi.fn().mockResolvedValue('Use asset IDs from search results when calling mutation tools.'),
|
||||
} as unknown as Awaited<ReturnType<(typeof import('../src/runtime.js'))['createRuntime']>>;
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
@ -179,6 +175,79 @@ describe('CLI list formatting', () => {
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('prints compact signatures for single server listings with --brief', async () => {
|
||||
const { handleList } = await cliModulePromise;
|
||||
const listToolsSpy = vi.fn((_name: string, options?: { includeSchema?: boolean }) =>
|
||||
Promise.resolve([buildLinearDocumentsTool(options?.includeSchema)])
|
||||
);
|
||||
const runtime = {
|
||||
getDefinitions: () => [linearDefinition],
|
||||
getDefinition: () => linearDefinition,
|
||||
listTools: listToolsSpy,
|
||||
getInstructions: vi.fn().mockResolvedValue('Use Linear IDs from list operations in mutation tools.'),
|
||||
} as unknown as Awaited<ReturnType<(typeof import('../src/runtime.js'))['createRuntime']>>;
|
||||
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
await handleList(runtime, ['linear', '--brief']);
|
||||
|
||||
const lines = logSpy.mock.calls.map((call) => stripAnsi(call.join(' ')));
|
||||
expect(lines.some((line) => line.includes('Instructions: Use Linear IDs'))).toBe(true);
|
||||
expect(lines.some((line) => line.trim().startsWith('/**'))).toBe(false);
|
||||
expect(lines.some((line) => line.includes('Examples:'))).toBe(false);
|
||||
expect(lines.some((line) => line.includes('@param'))).toBe(false);
|
||||
expect(lines.some((line) => line.includes('function list_documents('))).toBe(true);
|
||||
expect(
|
||||
lines.some((line) => line.includes('// optional (4): projectId, initiativeId, creatorId, includeArchived'))
|
||||
).toBe(true);
|
||||
expect(
|
||||
lines.some((line) => line.includes('Optional parameters hidden; run with --all-parameters to view all fields'))
|
||||
).toBe(true);
|
||||
expect(listToolsSpy).toHaveBeenCalledWith('linear', expect.objectContaining({ includeSchema: true }));
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('prints compact signatures for selected tools with --signatures', async () => {
|
||||
const { handleList } = await cliModulePromise;
|
||||
const listToolsSpy = vi.fn((_name: string, options?: { includeSchema?: boolean }) =>
|
||||
Promise.resolve([
|
||||
buildLinearDocumentsTool(options?.includeSchema),
|
||||
{
|
||||
name: 'create_comment',
|
||||
description: 'Create a comment on a specific Linear issue',
|
||||
inputSchema: options?.includeSchema
|
||||
? {
|
||||
type: 'object',
|
||||
properties: {
|
||||
issueId: { type: 'string', description: 'The issue ID' },
|
||||
body: { type: 'string', description: 'Comment body as Markdown' },
|
||||
},
|
||||
required: ['issueId', 'body'],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
const runtime = {
|
||||
getDefinitions: () => [linearDefinition],
|
||||
getDefinition: () => linearDefinition,
|
||||
listTools: listToolsSpy,
|
||||
} as unknown as Awaited<ReturnType<(typeof import('../src/runtime.js'))['createRuntime']>>;
|
||||
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
await handleList(runtime, ['linear.create_comment', '--signatures']);
|
||||
|
||||
const lines = logSpy.mock.calls.map((call) => stripAnsi(call.join(' ')));
|
||||
expect(lines.some((line) => line.includes('function create_comment('))).toBe(true);
|
||||
expect(lines.some((line) => line.includes('function list_documents('))).toBe(false);
|
||||
expect(lines.some((line) => line.includes('Examples:'))).toBe(false);
|
||||
expect(lines.find((line) => line.includes('HTTP https://example.com/mcp'))).toMatch(/1 tool/);
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('prints only the selected tool when listing server.tool with schemas', async () => {
|
||||
const { handleList } = await cliModulePromise;
|
||||
const listToolsSpy = vi.fn((_name: string, options?: { includeSchema?: boolean }) =>
|
||||
|
||||
@ -28,6 +28,8 @@ describe('mcporter list help shortcut', () => {
|
||||
await runCli(['list', '--help']);
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Usage: mcporter list'));
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('--brief'));
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('--signatures'));
|
||||
expect(process.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
@ -35,6 +35,10 @@ class FakeRuntime implements Runtime {
|
||||
// no-op for tests
|
||||
}
|
||||
|
||||
async getInstructions(): Promise<string | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async listTools(server: string, options?: ListToolsOptions): Promise<Awaited<ReturnType<Runtime['listTools']>>> {
|
||||
return await this.listToolsMock(server, options);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
buildAuthCommandHint,
|
||||
buildJsonListEntry,
|
||||
createEmptyStatusCounts,
|
||||
printBriefTool,
|
||||
printSingleServerHeader,
|
||||
printToolDetail,
|
||||
} from '../src/cli/list-output.js';
|
||||
@ -54,6 +55,35 @@ describe('list output helpers', () => {
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('prints brief tool signatures without examples or doc comments', () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const tool: ServerToolInfo = {
|
||||
name: 'add',
|
||||
description: 'Add numbers',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: { type: 'number', description: 'First operand' },
|
||||
b: { type: 'number', description: 'Second operand' },
|
||||
format: { type: 'string', enum: ['json', 'markdown'], description: 'Format' },
|
||||
projectId: { type: 'string', description: 'Project context' },
|
||||
initiativeId: { type: 'string', description: 'Initiative context' },
|
||||
creatorId: { type: 'string', description: 'Creator filter' },
|
||||
},
|
||||
required: ['a', 'b'],
|
||||
},
|
||||
outputSchema: { type: 'number' },
|
||||
};
|
||||
const metadata = buildToolMetadata(tool);
|
||||
const detail = printBriefTool(definition, metadata, true);
|
||||
const lines = logSpy.mock.calls.map((call) => call.join(' '));
|
||||
expect(lines.some((line) => line.includes('function add('))).toBe(true);
|
||||
expect(lines.some((line) => line.includes('@param a'))).toBe(false);
|
||||
expect(lines.some((line) => line.includes('Examples:'))).toBe(false);
|
||||
expect(detail.optionalOmitted).toBe(true);
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('prints URL-based examples for ad-hoc HTTP servers', () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const adhocDefinition: ServerDefinition = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user