Skip to content
Draft
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: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Each version opens with a one-line summary, which also opens its GitHub release.

## [Unreleased]

Argument completions for prompts and resource templates, tasks on MCP 2026-07-28 servers, MCP TypeScript SDK 2.3.1, stdio server secrets kept out of plaintext files, logs and `ps`, and no more duplicate bridges on Windows.
MCP result objects for JSON lists, argument completions, tasks on MCP 2026-07-28, SDK 2.3.1, and session security improvements.

### Added

Expand All @@ -19,6 +19,7 @@ Argument completions for prompts and resource templates, tasks on MCP 2026-07-28

### Changed

- **Breaking JSON change:** `tools-list`, `prompts-list`, and `resources-list` now return MCP result objects (`{ tools: [...] }`, `{ prompts: [...] }`, `{ resources: [...] }`) instead of bare arrays; update `jq '.[]'` filters to `.tools[]`, `.prompts[]`, or `.resources[]` (`tasks-list` keeps `{ tasks: [...] }`). These four lists combine all pages without `nextCursor`, retaining other top-level fields and `_meta` keys in page order (later values win); human-readable output is unchanged. (#183)
- Updated to MCP TypeScript SDK 2.3.1. Large tool results over Streamable HTTP now arrive in a fraction of the time, and HTTP redirects are followed only within the server's own origin (plus `http` to `https` on the same host): a server that redirects elsewhere is reported with the target URL so you can connect to it directly.
- `tools-call --detach --json` prints the whole created `Task` object (id, status, TTL, poll interval) instead of just `{ taskId, status }`. On a 2026-07-28 server that runs a detached call synchronously — the server decides per call whether to create a task — it prints the tool result instead, so check for `taskId`.
- `tasks-cancel` on a 2026-07-28 server reports the status the server shows right after acknowledging the request (cancellation is cooperative there, so a task may still be `working` for a moment) and exits with code 2 only when the task had already finished.
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,21 @@ With `--json` option, `mcpc` always emits only a single JSON object (or array),
[MCP specification](https://modelcontextprotocol.io/specification/latest).**
On success, the JSON object is printed to stdout, on error to stderr.

`tools-list`, `prompts-list`, `resources-list`, and `tasks-list` return MCP
`ListToolsResult`, `ListPromptsResult`, `ListResourcesResult`, and `ListTasksResult`
objects: `{ "tools": [...] }`, `{ "prompts": [...] }`, `{ "resources": [...] }`,
and `{ "tasks": [...] }`. Empty lists keep the same object shape with an empty array.
mcpc fetches all pages automatically and omits `nextCursor`. Other top-level fields
and `_meta` keys are shallowly merged in page order, with later values winning on
duplicate keys. On MCP 2026-07-28, `tasks-list` uses the same `{ "tasks": [...] }`
shape for the tasks this session created, since the tasks extension has no listing.

**Breaking JSON migration:** `tools-list`, `prompts-list`, and `resources-list`
previously returned bare arrays. Update `jq '.[]'` filters to `jq '.tools[]'`,
`jq '.prompts[]'`, or `jq '.resources[]'`, respectively (for example,
`jq -r '.tools[].name'`). `tasks-list` already returned an object and keeps its
`jq '.tasks[]'` filter. Human-readable output is unchanged.

Note that `--json` is not available for `mcpc --help`. For `login`, `--json` prints a single
`{ profile, serverUrl, scopes }` object on stdout (interactive prompts go to stderr).

Expand Down Expand Up @@ -718,7 +733,7 @@ mcpc --json @apify tools-call search-actors keywords:="scraper" \
| xargs -I {} mcpc @apify tools-call get-actor actorId:="{}"

# Batch operations
for tool in $(mcpc --json @server tools-list | jq -r '.[].name'); do
for tool in $(mcpc --json @server tools-list | jq -r '.tools[].name'); do
mcpc --json @server tools-get "$tool" > "schemas/$tool.json"
done
```
Expand Down
32 changes: 21 additions & 11 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,9 +600,11 @@ Options:
--json Output in JSON format

JSON output (--json):
Array of `Tool` objects:
`[{ name, description?, inputSchema, outputSchema?, annotations? }, ...]`
Schema: https://modelcontextprotocol.io/specification/2026-07-28/schema#tool
`ListToolsResult` object:
`{ tools: Tool[], _meta?: { ... }, ... }`
All pages combined; nextCursor omitted. Other top-level fields and _meta keys
merge shallowly in page order (later values win).
Schema: https://modelcontextprotocol.io/specification/2025-11-25/schema#listtoolsresult
```

### `mcpc @<session> tools-get`
Expand Down Expand Up @@ -696,11 +698,15 @@ Notes:
has no listing, so there this shows the tasks this session created.

JSON output (--json):
`{ tasks: Task[] }`, each `Task` shaped:
`ListTasksResult` object; each `Task` follows the negotiated protocol:
`{ tasks: Task[], _meta?: { ... }, ... }`
on MCP 2025-11-25: `{ taskId, status, ttl, createdAt, lastUpdatedAt, statusMessage?, pollInterval? }`
on MCP 2026-07-28: `{ taskId, status, ttlMs, createdAt, lastUpdatedAt, statusMessage?, pollIntervalMs?,
result? (completed), error? (failed), inputRequests? (input_required) }`
Schema: https://modelcontextprotocol.io/specification/2025-11-25/schema#task
All legacy pages combined; nextCursor omitted. Other top-level fields and _meta keys
merge shallowly in page order (later values win).
Schema: https://modelcontextprotocol.io/specification/2025-11-25/schema#listtasksresult
https://modelcontextprotocol.io/specification/2025-11-25/schema#task
https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/2026-07-28/tasks.md
```

Expand Down Expand Up @@ -781,9 +787,11 @@ Options:
--json Output in JSON format

JSON output (--json):
Array of `Prompt` objects:
`[{ name, description?, arguments?: [{ name, required? }] }, ...]`
Schema: https://modelcontextprotocol.io/specification/2026-07-28/schema#prompt
`ListPromptsResult` object:
`{ prompts: Prompt[], _meta?: { ... }, ... }`
All pages combined; nextCursor omitted. Other top-level fields and _meta keys
merge shallowly in page order (later values win).
Schema: https://modelcontextprotocol.io/specification/2025-11-25/schema#listpromptsresult
```

### `mcpc @<session> prompts-get`
Expand Down Expand Up @@ -859,9 +867,11 @@ Options:
--json Output in JSON format

JSON output (--json):
Array of `Resource` objects:
`[{ uri, name, description?, mimeType? }, ...]`
Schema: https://modelcontextprotocol.io/specification/2026-07-28/schema#resource
`ListResourcesResult` object:
`{ resources: Resource[], _meta?: { ... }, ... }`
All pages combined; nextCursor omitted. Other top-level fields and _meta keys
merge shallowly in page order (later values win).
Schema: https://modelcontextprotocol.io/specification/2025-11-25/schema#listresourcesresult
```

### `mcpc @<session> resources-read`
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/company-lookup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fi
# Verify the required tool is available
info "🔧 Checking for required tool..."

TOOL_EXISTS=$(mcpc --json "${SESSION}" tools-list 2>/dev/null | jq -r ".[] | select(.name == \"${REQUIRED_TOOL}\") | .name" 2>/dev/null || echo "")
TOOL_EXISTS=$(mcpc --json "${SESSION}" tools-list 2>/dev/null | jq -r ".tools[] | select(.name == \"${REQUIRED_TOOL}\") | .name" 2>/dev/null || echo "")

if [ -z "$TOOL_EXISTS" ]; then
error "Required tool '${REQUIRED_TOOL}' not found on server.
Expand Down
4 changes: 2 additions & 2 deletions docs/vhs/scripting.tape
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Enter
Sleep 1500ms
Show

Type "mcpc --json @apify tools-list | jq -r '.[].name'"
Type "mcpc --json @apify tools-list | jq -r '.tools[].name'"
Sleep 300ms
Enter
Sleep 3000ms
Expand All @@ -51,7 +51,7 @@ Enter
Sleep 3500ms

Enter
Type "for tool in $(mcpc --json @apify tools-list | jq -r '.[].name'); do"
Type "for tool in $(mcpc --json @apify tools-list | jq -r '.tools[].name'); do"
Sleep 200ms
Enter
Type ' mcpc --json @apify tools-get "$tool" > "/tmp/$tool.json"'
Expand Down
14 changes: 13 additions & 1 deletion skills/mcpc/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,24 @@ echo '{"query":"hello"}' | mcpc @apify tools-call search
Add `--json` for machine-readable output: results on stdout, errors on stderr,
shaped strictly per the MCP spec.

`tools-list`, `prompts-list`, `resources-list`, and `tasks-list` return result
objects with `tools`, `prompts`, `resources`, and `tasks` arrays, respectively,
including empty arrays. All pages are combined and `nextCursor` is omitted;
other top-level fields and `_meta` keys are shallowly merged in page order
(later values win). On MCP 2026-07-28, `tasks-list` lists the tasks this session
created in the same object shape.

**Breaking migration:** tools, prompts, and resources lists previously returned
bare arrays. Replace `jq '.[]'` with `jq '.tools[]'`, `jq '.prompts[]'`, or
`jq '.resources[]'`; `tasks-list` keeps its existing `jq '.tasks[]'` filter.
Human-readable lists are unchanged.

Human-readable tool results omit text blocks that duplicate `structuredContent`.
When other content remains, a hint points to `--json` for the structured data;
otherwise it is printed directly. JSON output always includes the full result.

```bash
mcpc --json @apify tools-list | jq -r '.[].name'
mcpc --json @apify tools-list | jq -r '.tools[].name'
mcpc --json @apify tools-call search query:="test" | jq -r '.content[0].text'
mcpc --json @apify tools-call search query:="test" | jq '.structuredContent'

Expand Down
9 changes: 3 additions & 6 deletions src/cli/commands/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { CommandOptions } from '../../lib/types.js';
import { formatOutput } from '../output.js';
import { withMcpClient } from '../helpers.js';
import { readCommandArgs, stringifyArgValues } from '../parser.js';
import { fetchAllPages } from '../../lib/utils.js';
import { fetchAllPagesResult } from '../../lib/utils.js';

/**
* List available prompts
Expand All @@ -15,13 +15,10 @@ import { fetchAllPages } from '../../lib/utils.js';
export async function listPrompts(target: string, options: CommandOptions): Promise<void> {
await withMcpClient(target, options, async (client, _context) => {
// Fetch all prompts across all pages
const allPrompts = await fetchAllPages(
(cursor) => client.listPrompts(cursor),
(page) => page.prompts
);
const result = await fetchAllPagesResult((cursor) => client.listPrompts(cursor), 'prompts');

console.log(
formatOutput(allPrompts, options.outputMode, {
formatOutput(options.outputMode === 'json' ? result : result.prompts, options.outputMode, {
...(options.maxChars && { maxChars: options.maxChars }),
})
);
Expand Down
9 changes: 3 additions & 6 deletions src/cli/commands/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
formatDirectoryChildren,
} from '../output.js';
import { withMcpClient } from '../helpers.js';
import { resolvePath, fetchAllPages } from '../../lib/utils.js';
import { resolvePath, fetchAllPages, fetchAllPagesResult } from '../../lib/utils.js';
import { selectResourceContent, writeResourceFile } from '../../lib/resource-content.js';
import { ClientError } from '../../lib/errors.js';
import type { CommandOptions } from '../../lib/types.js';
Expand All @@ -23,13 +23,10 @@ import type { CommandOptions } from '../../lib/types.js';
export async function listResources(target: string, options: CommandOptions): Promise<void> {
await withMcpClient(target, options, async (client, _context) => {
// Fetch all resources across all pages
const allResources = await fetchAllPages(
(cursor) => client.listResources(cursor),
(page) => page.resources
);
const result = await fetchAllPagesResult((cursor) => client.listResources(cursor), 'resources');

console.log(
formatOutput(allResources, options.outputMode, {
formatOutput(options.outputMode === 'json' ? result : result.resources, options.outputMode, {
...(options.maxChars && { maxChars: options.maxChars }),
})
);
Expand Down
12 changes: 5 additions & 7 deletions src/cli/commands/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { isExtensionTask, type CommandOptions } from '../../lib/types.js';
import { withMcpClient } from '../helpers.js';
import { formatTask, formatTasks } from '../output.js';
import { renderCallToolResult } from './tools.js';
import { fetchAllPages } from '../../lib/utils.js';
import { fetchAllPagesResult } from '../../lib/utils.js';
import { isModernProtocolVersion } from '../../core/protocol.js';

/**
Expand Down Expand Up @@ -48,14 +48,12 @@ export async function getTaskResult(
export async function listTasks(target: string, options: CommandOptions): Promise<void> {
await withMcpClient(target, options, async (client, _context) => {
// Fetch all tasks across all pages
const [details, allTasks] = await Promise.all([
const [details, result] = await Promise.all([
client.getServerDetails(),
fetchAllPages(
(cursor) => client.listTasks(cursor),
(page) => page.tasks
),
fetchAllPagesResult((cursor) => client.listTasks(cursor), 'tasks'),
]);
const tracked = !!details.protocolVersion && isModernProtocolVersion(details.protocolVersion);
const allTasks = result.tasks;

if (options.outputMode === 'human') {
if (allTasks.length === 0) {
Expand All @@ -78,7 +76,7 @@ export async function listTasks(target: string, options: CommandOptions): Promis
console.log(formatTaskCommandsHint(target));
}
} else {
console.log(formatOutput({ tasks: allTasks }, 'json'));
console.log(formatOutput(result, 'json'));
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export async function listTools(
await withMcpClient(target, options, async (client, _context) => {
const result = await client.listAllTools({ refreshCache: true });
console.log(
formatOutput(result.tools, options.outputMode, {
formatOutput(options.outputMode === 'json' ? result : result.tools, options.outputMode, {
...(options.full && { full: true }),
...(options.maxChars && { maxChars: options.maxChars }),
sessionName: target,
Expand Down
33 changes: 23 additions & 10 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1111,9 +1111,11 @@ ${jsonHelp('`{ tools?: Tool[], resources?: Resource[], prompts?: Prompt[], instr
.addHelpText(
'after',
jsonHelp(
'Array of `Tool` objects',
'`[{ name, description?, inputSchema, outputSchema?, annotations? }, ...]`',
`${SCHEMA_BASE}#tool`
'`ListToolsResult` object',
'`{ tools: Tool[], _meta?: { ... }, ... }`\n' +
' All pages combined; nextCursor omitted. Other top-level fields and _meta keys\n' +
' merge shallowly in page order (later values win).',
`${LEGACY_SCHEMA_BASE}#listtoolsresult`
)
)
.action(async (_options, command) => {
Expand Down Expand Up @@ -1232,7 +1234,14 @@ ${toolsCallCombinedJsonHelp}`
${chalk.bold('Notes:')}
MCP 2025-11-25 servers list all their tasks. The 2026-07-28 tasks extension
has no listing, so there this shows the tasks this session created.
${jsonHelp('`{ tasks: Task[] }`, each `Task` shaped', taskJsonShapes, taskSchemaUrls)}`
${jsonHelp(
'`ListTasksResult` object; each `Task` follows the negotiated protocol',
'`{ tasks: Task[], _meta?: { ... }, ... }`\n ' +
taskJsonShapes +
'\n All legacy pages combined; nextCursor omitted. Other top-level fields and _meta keys\n' +
' merge shallowly in page order (later values win).',
[`${LEGACY_SCHEMA_BASE}#listtasksresult`, ...taskSchemaUrls]
)}`
)
.action(async (_options, command) => {
await tasks.listTasks(session, getOptionsFromCommand(command));
Expand Down Expand Up @@ -1290,9 +1299,11 @@ ${jsonHelp('`Task` object (its state after the request)', taskJsonShapes, taskSc
.addHelpText(
'after',
jsonHelp(
'Array of `Resource` objects',
'`[{ uri, name, description?, mimeType? }, ...]`',
`${SCHEMA_BASE}#resource`
'`ListResourcesResult` object',
'`{ resources: Resource[], _meta?: { ... }, ... }`\n' +
' All pages combined; nextCursor omitted. Other top-level fields and _meta keys\n' +
' merge shallowly in page order (later values win).',
`${LEGACY_SCHEMA_BASE}#listresourcesresult`
)
)
.action(async (_options, command) => {
Expand Down Expand Up @@ -1462,9 +1473,11 @@ ${jsonHelp(
.addHelpText(
'after',
jsonHelp(
'Array of `Prompt` objects',
'`[{ name, description?, arguments?: [{ name, required? }] }, ...]`',
`${SCHEMA_BASE}#prompt`
'`ListPromptsResult` object',
'`{ prompts: Prompt[], _meta?: { ... }, ... }`\n' +
' All pages combined; nextCursor omitted. Other top-level fields and _meta keys\n' +
' merge shallowly in page order (later values win).',
`${LEGACY_SCHEMA_BASE}#listpromptsresult`
)
)
.action(async (_options, command) => {
Expand Down
Loading