code-mode
Code Mode
Write and execute Bun scripts that call MCP tools programmatically — multiple calls on a single persistent connection, without returning to the LLM between each call.
When to use
- Multi-tool orchestration: 3+ MCP tool calls where intermediate results feed into subsequent calls
- Batch operations: Repeated calls with different parameters (e.g., process a list of items)
- Cross-server workflows: Combining results from different MCP servers
- Token-heavy tool sets: Servers with many tools (like figma-console's 63+) where loading all schemas wastes context
When NOT to use
- Simple single-tool calls → use the normal tool-call loop
- Tools that require interactive OAuth → authenticate via
claude mcpfirst, then use code-mode - Operations that need human approval between steps → use normal tool-call loop
The Pattern
Scripts are written to /tmp/claude-code-mode/ (auto-cleaned on session end via hook). The SDK resolves via NODE_PATH pointing to the skill's node_modules/.
Step 1: Write the script
mkdir -p /tmp/claude-code-mode
Write the script to /tmp/claude-code-mode/_run.ts:
#!/usr/bin/env bun
import { getServerConfig, createTransport } from "<path-to-skill>/scripts/mcp-client.ts";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const config = getServerConfig("SERVER_NAME");
if (!config) { console.error("Server not found"); process.exit(1); }
const client = new Client({ name: "cc-code-mode", version: "1.0.0" });
const transport = await createTransport(config);
try {
await client.connect(transport);
// Call tools — connection stays open between calls
const result1 = await client.callTool({
name: "tool_name",
arguments: { key: "value" },
});
if (result1.isError) throw new Error(JSON.stringify(result1.content));
const result2 = await client.callTool({
name: "another_tool",
arguments: { data: result1.content },
});
// Output structured JSON for Claude Code to parse
console.log(JSON.stringify({ result1: result1.content, result2: result2.content }));
} catch (err) {
console.error(JSON.stringify({ error: String(err) }));
process.exit(1);
} finally {
await client.close();
}
Step 2: Execute the script
NODE_PATH=<path-to-skill>/scripts/node_modules bun /tmp/claude-code-mode/_run.ts
Step 3: Parse and present results
Read stdout (JSON results) and stderr (server logs/errors). Present findings to the user.
Helper API
The helper at ./mcp-client.ts reads Claude Code's own config files — no duplicate configuration needed.
getServerConfig(name: string): ServerConfig | null
Returns the resolved config for a named MCP server. Reads from all Claude Code config sources (plugins, user, project) and merges by precedence. Env vars are resolved (${VAR}, ${VAR:-default}).
Returns null if the server is not configured.
listServers(): { name, type, source }[]
Returns all available MCP servers with their transport type and config source.
createTransport(config: ServerConfig): Transport
Creates the appropriate MCP transport (StdioClientTransport, StreamableHTTPClientTransport, or SSEClientTransport) from a resolved config. For stdio servers, env vars from the config are explicitly passed to the child process (the SDK filters env vars by default — this is handled automatically).
Available servers
Before writing a script, check what's available:
bun <path-to-skill>/scripts/mcp-client.ts list
To inspect a specific server's resolved config:
bun <path-to-skill>/scripts/mcp-client.ts get figma-console
Discovering tools
If you don't know a server's tools, use listTools():
await client.connect(transport);
const { tools } = await client.listTools();
console.log(JSON.stringify(tools.map(t => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema,
}))));
Critical rules
-
Always use
try/finallywithclient.close(). MCP servers (especially stdio) are child processes. Withoutclose(), they become orphans. -
Always check
isError. Tool errors are returned as{ isError: true, content: [...] }, NOT thrown. An unchecked error silently produces wrong results. -
Output JSON to stdout. Claude Code reads the script's stdout. Use
console.log(JSON.stringify(...))for results. Useconsole.error()for debugging — it goes to stderr. -
The connection is persistent. After
client.connect(), you can callcallTool()as many times as needed. No reconnection between calls. -
Default timeout is 60s. For slow tools, pass options:
client.callTool({ name, arguments }, { timeout: 120_000 }).
Error handling
Load: references/error-handling.md for detailed patterns.
Quick reference:
// Tool-level error (returned, not thrown)
const result = await client.callTool({ name: "...", arguments: {} });
if (result.isError) {
console.error("Tool error:", JSON.stringify(result.content));
// Decide: skip, retry, or abort
}
// Connection/protocol error (thrown)
try {
await client.connect(transport);
} catch (err) {
console.error("Connection failed:", err);
process.exit(1);
}
// Timeout
try {
const result = await client.callTool(
{ name: "slow_tool", arguments: {} },
{ timeout: 120_000 },
);
} catch (err) {
if (err.message?.includes("timed out")) {
console.error("Tool timed out");
}
}
Transport types
All three MCP transport types are supported. createTransport() auto-selects based on the config's type field.
Load: references/transport-patterns.md for details on each transport.
| Config type | Transport | Notes |
|---|---|---|
stdio |
StdioClientTransport | Local process. Env vars passed explicitly. |
http |
StreamableHTTPClientTransport | Remote server. Headers supported. |
sse |
SSEClientTransport | Legacy. Same API as http. |
Examples
Load: references/examples.md for full worked examples.
Multi-tool Figma workflow
import { getServerConfig, createTransport } from "<path-to-skill>/scripts/mcp-client.ts";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const config = getServerConfig("figma-console");
if (!config) { console.error("figma-console not configured"); process.exit(1); }
const client = new Client({ name: "cc-code-mode", version: "1.0.0" });
const transport = await createTransport(config);
try {
await client.connect(transport);
const tokens = await client.callTool({
name: "figma_get_variables",
arguments: { fileUrl: "https://figma.com/design/FILE/...", format: "css" },
});
const fileData = await client.callTool({
name: "figma_get_file_data",
arguments: { fileUrl: "https://figma.com/design/FILE/...", depth: 1 },
});
console.log(JSON.stringify({ tokens: tokens.content, fileData: fileData.content }));
} finally {
await client.close();
}
Cross-server workflow
import { getServerConfig, createTransport } from "<path-to-skill>/scripts/mcp-client.ts";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
// Connect to server A
const configA = getServerConfig("server-a");
const clientA = new Client({ name: "cc-code-mode", version: "1.0.0" });
const transportA = await createTransport(configA!);
// Connect to server B
const configB = getServerConfig("server-b");
const clientB = new Client({ name: "cc-code-mode", version: "1.0.0" });
const transportB = await createTransport(configB!);
try {
await clientA.connect(transportA);
await clientB.connect(transportB);
const dataA = await clientA.callTool({ name: "get_data", arguments: {} });
const dataB = await clientB.callTool({
name: "process_data",
arguments: { input: dataA.content },
});
console.log(JSON.stringify({ dataA: dataA.content, dataB: dataB.content }));
} finally {
await clientA.close();
await clientB.close();
}
More from inkeep/team-skills
qa
Manual QA testing — verify features end-to-end as a user would, by all means necessary. Exhausts every local tool: browser (Playwright), Docker, ad-hoc scripts, REPL, dev servers. Mock-aware — mocked test coverage does not count. Proves real userOutcome at highest achievable fidelity. Blocked scenarios flow to /pr as pending human verification. Standalone or composable with /ship. Triggers: qa, qa test, manual test, test the feature, verify it works, exploratory testing, smoke test, end-to-end verification.
61cold-email
Generate cold emails for B2B personas. Use when asked to write cold outreach, sales emails, or prospect messaging. Supports 19 persona archetypes (Founder-CEO, CTO, VP Engineering, CIO, CPO, Product Directors, VP CX, Head of Support, Support Ops, DevRel, Head of Docs, Technical Writer, Head of Community, VP Growth, Head of AI, etc.). Can generate first-touch and follow-up emails. When a LinkedIn profile URL is provided, uses Crustdata MCP to enrich prospect data (name, title, company, career history, recent posts) for deep personalization.
54implement
Convert SPEC.md to spec.json, craft the implementation prompt, and execute the iteration loop via subprocess. Use when converting specs to spec.json, preparing implementation artifacts, running the iteration loop, or implementing features autonomously. Triggers: implement, spec.json, convert spec, implementation prompt, execute implementation, run implementation.
52write-agent
Design and write high-quality Claude Code agents and agent prompts. Use when creating or updating .claude/agents/*.md for (1) single-purpose subagents (reviewers, implementers, researchers) and (2) workflow orchestrators (multi-phase coordinators like pr-review, feature-development, bug-fix). Covers delegation triggers, tool/permission/model choices, Task-tool orchestration, phase handoffs, aggregation, iteration gates, and output contracts. Also use when deciding between subagents vs skills vs always-on repo guidance.
50browser
Browser automation with two engines: agent-browser CLI (default for interactive work — navigate, snapshot, click, fill, screenshot, diff) and Playwright scripts (for compound operations — console/network capture, a11y audits, video recording, responsive sweeps, GIF pipeline, shadow DOM piercing). Triggers: browser automation, web test, screenshot, responsive test, test the page, automate browser, headless browser, UI test, console errors, network inspection, accessibility audit, a11y test, performance metrics, video recording, browser state, page structure, authenticated testing, annotated gif, dialog handling, tracing, pdf generation, shadow DOM, web components, DOM stabilization.
49tdd
|
48