Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
* text eol=lf
*.png binary
*.tar.gz binary
*.gz binary
*.tgz binary
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ npx mcporter call 'linear.create_comment(issueId: "ENG-123", body: "Looks good!"
```bash
npx mcporter list
npx mcporter list context7 --schema
npx mcporter list context7 --brief # Show concise signatures
npx mcporter list context7 "resolve-*" --brief # Filter tools by name pattern
npx mcporter list context7 query-docs --brief # Check for a specific tool
npx mcporter list https://mcp.linear.app/mcp --all-parameters
npx mcporter list shadcn.io/api/mcp.getComponents # URL + tool suffix auto-resolves
npx mcporter list --stdio "bun run ./local-server.ts" --env TOKEN=xyz
```

- Add `--brief` to show concise TypeScript-style function signatures instead of full details. This mode supports filtering tools by name using glob patterns (e.g. `list server "amz_*"`).

- Add `--json` to emit a machine-readable summary with per-server statuses (auth/offline/http/error counts) and, for single-server runs, the full tool schema payload.
- Add `--verbose` to show every config source that registered the server name (primary first), both in text and JSON list output.

Expand Down Expand Up @@ -149,11 +154,12 @@ Helpful flags:
- `--save-images <dir>` (on `mcporter call`) -- save MCP image content blocks to files in the given directory (opt-in; stdout output shape stays unchanged).
- `--raw-strings` (on `mcporter call`) -- keep numeric-looking argument values (for `key=value`, `key:value`, and trailing positional values) as strings.
- `--no-coerce` (on `mcporter call`) -- keep all `key=value` and positional values as raw strings (disables bool/null/number/JSON coercion).
- `--json` (on `mcporter list`) -- emit JSON summaries/counts instead of text. Multi-server runs report per-server statuses, counts, and connection issues; single-server runs include the full tool metadata.
- `--json` (on `mcporter list`) -- emit JSON summaries/counts instead of text. Multi-server runs report per-server statuses, counts, and connection issues; single-server runs include the full tool metadata. Mutually exclusive with `--brief`.
- `--output json/raw` (on `mcporter call`) -- when a connection fails, MCPorter prints the usual colorized hint and also emits a structured `{ server, tool, issue }` envelope so scripts can handle auth/offline/http errors programmatically.
- `--json` (on `mcporter auth`) -- emit the same structured connection envelope whenever OAuth/transport setup fails, instead of throwing an error.
- `--json` (on `mcporter emit-ts`) -- print a JSON summary describing the emitted files (mode + output paths) instead of text logs—handy when generating artifacts inside scripts.
- `--all-parameters` -- show every schema field when listing a server (default output shows at least five parameters plus a summary of the rest).
- `--brief` -- show concise tool signatures only; supports filtering tools by name (e.g. `mcporter list server "query_*" --brief`). Mutually exclusive with `--json`, `--schema`, `--verbose`, and `--all-parameters`.
- `--http-url <https://…>` / `--stdio "command …"` -- describe an ad-hoc MCP server inline. STDIO transports now inherit your current shell environment automatically; add `--env KEY=value` only when you need to inject/override variables alongside `--cwd`, `--name`, or `--persist <config.json>`. These flags now work with `mcporter auth` too, so `mcporter auth https://mcp.example.com/mcp` just works.
- For OAuth-protected servers such as `vercel`, run `npx mcporter auth vercel` once to complete login.

Expand Down
2 changes: 2 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ A quick reference for the primary `mcporter` subcommands. Each command inherits
- Flags:
- `--all-parameters` – include every optional parameter in the signature.
- `--schema` – pretty-print the JSON schema for each tool.
- `--brief` – show concise tool signatures only. Mutually exclusive with `--json`, `--schema`, etc.
- `--json` – emit a machine-readable summary instead of text. Mutually exclusive with `--brief`.
- `--timeout <ms>` – per-server timeout when enumerating all servers.

## `mcporter call <server.tool>`
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"commander": "^14.0.3",
"es-toolkit": "^1.45.0",
"jsonc-parser": "^3.3.1",
"minimatch": "^10.1.2",
"ora": "^9.3.0",
"rolldown": "1.0.0-rc.6",
"zod": "^4.3.6"
Expand Down Expand Up @@ -101,4 +102,4 @@
}
]
}
}
}
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 92 additions & 5 deletions src/cli/list-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { prepareEphemeralServerTarget } from './ephemeral-target.js';
import { splitHttpToolSelector } from './http-utils.js';
import { chooseClosestIdentifier, renderIdentifierResolutionMessages } from './identifier-helpers.js';
import { formatExampleBlock } from './list-detail-helpers.js';
import { filterToolsByPattern } from './list-filter.js';
import type { ListSummaryResult, StatusCategory } from './list-format.js';
import { classifyListError, formatSourceSuffix, renderServerListRow } from './list-format.js';
import {
Expand All @@ -16,6 +17,7 @@ import {
createEmptyStatusCounts,
createUnknownResult,
type ListJsonServerEntry,
printBriefToolList,
printSingleServerHeader,
printToolDetail,
summarizeStatusCounts,
Expand All @@ -34,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'],
Expand Down Expand Up @@ -74,13 +78,39 @@ export function extractListFlags(args: string[]): {
args.splice(index, 1);
continue;
}
if (token === '--brief') {
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 };

// Validate mutual exclusion for --brief
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';
Expand All @@ -90,7 +120,24 @@ export async function handleList(
args: string[]
): Promise<void> {
const flags = extractListFlags(args);
let target = args.shift();
let target: string | undefined;
let toolPattern: string | undefined;

// In ephemeral mode (e.g. --http-url), positional args are treated as the tool pattern.
// In normal mode, the first arg is the target (name/URL), second is the pattern.
if (flags.ephemeral) {
if (args.length > 0 && args[0] !== undefined && !args[0].startsWith('-')) {
toolPattern = args.shift();
}
} else {
// Standard mode: [target] [pattern]
if (args.length > 0 && args[0] !== undefined && !args[0].startsWith('-')) {
target = args.shift();
}
if (args.length > 0 && args[0] !== undefined && !args[0].startsWith('-')) {
toolPattern = args.shift();
}
}

if (target) {
const split = splitHttpToolSelector(target);
Expand Down Expand Up @@ -297,8 +344,34 @@ export async function handleList(
}
try {
// Always request schemas so we can render CLI-style parameter hints without re-querying per tool.
const metadataEntries = await withTimeout(loadToolMetadata(runtime, target, { includeSchema: true }), timeoutMs);
let metadataEntries = await withTimeout(loadToolMetadata(runtime, target, { includeSchema: true }), timeoutMs);
const durationMs = Date.now() - startedAt;

// Apply tool pattern filter if specified
if (toolPattern) {
const filteredNames = new Set(
filterToolsByPattern(
metadataEntries.map((entry) => ({ name: entry.tool.name })),
toolPattern
).map((f) => f.name)
);
metadataEntries = metadataEntries.filter((e) => filteredNames.has(e.tool.name));
}

// Handle --brief output mode
if (flags.brief) {
if (metadataEntries.length === 0) {
if (toolPattern) {
console.log(`No tools matching pattern '${toolPattern}'.`);
} else {
console.log('No tools available.');
}
return;
}
printBriefToolList(metadataEntries, { colorize: true });
return;
Comment on lines +361 to +372

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject or honor --json when --brief is set

When --brief is supplied alongside --json, the handler bypasses JSON output and prints text signatures, then returns early. That breaks callers that rely on --json for machine-readable output (e.g., mcporter list server --json --brief), because the format silently changes to plain text. Either treat --brief as mutually exclusive with --json in extractListFlags, or emit a JSON payload for brief mode so the --json contract is preserved.

Useful? React with 👍 / 👎.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this by making --brief and --json mutually exclusive in [extractListFlags]. Supplying both will now throw an error with a clear message, ensuring the --json contract isn't silently broken.
I've also updated the following to reflect this change:

  • Help output in [src/cli/list-command.ts]
  • README.md
  • docs/cli-reference.md

}

const summaryLine = printSingleServerHeader(
definition,
metadataEntries.length,
Expand All @@ -310,7 +383,11 @@ export async function handleList(
}
);
if (metadataEntries.length === 0) {
console.log(' Tools: <none>');
if (toolPattern) {
console.log(` Tools: <no matches for '${toolPattern}'>`);
} else {
console.log(' Tools: <none>');
}
console.log(summaryLine);
console.log('');
return;
Expand Down Expand Up @@ -354,7 +431,12 @@ export async function handleList(

export function printListHelp(): void {
const lines = [
'Usage: mcporter list [server | url] [flags]',
'Usage: mcporter list [target] [tool-pattern] [flags]',
'',
'Arguments:',
' <target> Server name from config or URL. (Optional if using ad-hoc flags)',
" <tool-pattern> Filter tools by name (supports glob: 'amz_*').",
' If using ad-hoc flags (e.g. --http-url), the first argument is the pattern.',
'',
'Targets:',
' <name> Use a server from config/mcporter.json or editor imports.',
Expand All @@ -373,6 +455,8 @@ export function printListHelp(): void {
' --yes Skip confirmation prompts when persisting.',
'',
'Display flags:',
' --brief Show tools as function signatures only.',
' Cannot be used with --json, --schema, --verbose, --all-parameters.',
' --schema Show tool schemas when listing servers.',
' --all-parameters Include optional parameters in tool docs.',
' --json Emit a JSON summary instead of text.',
Expand All @@ -383,6 +467,9 @@ export function printListHelp(): void {
'Examples:',
' mcporter list',
' mcporter list linear --schema',
' mcporter list linear --brief',
" mcporter list linear 'add_*'",
' mcporter list linear add_user --schema',
' mcporter list https://mcp.example.com/mcp',
' mcporter list --http-url https://localhost:3333/mcp --schema',
];
Expand Down
18 changes: 18 additions & 0 deletions src/cli/list-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { minimatch } from 'minimatch';

/**
* Filter tools by a glob pattern matching tool names.
* @param tools Array of items with a `name` property
* @param pattern Glob pattern to match against tool names
* @returns Filtered array of tools matching the pattern
*/
export function filterToolsByPattern<T extends { name: string }>(tools: T[], pattern: string): T[] {
return tools.filter((tool) => minimatch(tool.name, pattern, { nocase: true }));
}

/**
* Check if a pattern contains glob special characters.
*/
export function isGlobPattern(pattern: string): boolean {
return /[*?[\]{}!]/.test(pattern);
}
13 changes: 13 additions & 0 deletions src/cli/list-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { formatErrorMessage, serializeConnectionIssue } from './json-output.js';
import { buildToolDoc } from './list-detail-helpers.js';
import type { ListSummaryResult, StatusCategory } from './list-format.js';
import { classifyListError } from './list-format.js';
import { formatFunctionSignature } from './list-signature.js';
import { boldText, extraDimText } from './terminal.js';
import { formatTransportSummary } from './transport-utils.js';

Expand Down Expand Up @@ -119,6 +120,18 @@ function buildExampleOptions(
return undefined;
}

/**
* Print tools as compact function signatures for --brief mode.
*/
export function printBriefToolList(metadataEntries: ToolMetadata[], options?: { colorize?: boolean }): void {
for (const entry of metadataEntries) {
const signature = formatFunctionSignature(entry.tool.name, entry.options, entry.tool.outputSchema, {
colorize: options?.colorize,
});
console.log(signature);
}
}

export function createEmptyStatusCounts(): Record<StatusCategory, number> {
return {
ok: 0,
Expand Down
Loading