How Claude Code supports 45+ built-in tools plus unlimited external tools (via MCP) using a single extensible interface.
graph TD
A[Tool Registry] --> B[Built-in Tools]
A --> C[MCP Tools]
A --> D[Skill Tools]
B --> E[BashTool]
B --> F[FileReadTool]
B --> G[AgentTool]
C --> H[stdio server]
C --> I[HTTP server]
C --> J[WebSocket server]
D --> K[User Skills]Mermaid diagram definition
The plugin architecture centers on a single generic Tool<Input, Output> interface. Any code that implements this interface becomes a first-class tool โ indistinguishable from built-in tools from Claude's perspective.
MCP (Model Context Protocol) extends this: external processes implement the MCP protocol, and Claude Code wraps their exposed tools in the same `Tool` interface. The AI never knows whether a tool is built-in or external.
Skills are a third tier: curated prompts packaged as user-installable extensions. They use the same dispatch path as tools but are prompt-based rather than code-based.
The generic Tool interface โ the single contract all plugins must implement.
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
P extends ToolProgressData = ToolProgressData,
> = {
/**
* Optional aliases for backwards compatibility when a tool is renamed.
* The tool can be looked up by any of these names in addition to its primary name.
*/
aliases?: string[]
/**
* One-line capability phrase used by ToolSearch for keyword matching.
* Helps the model find this tool via keyword search when it's deferred.
* 3โ10 words, no trailing period.
* Prefer terms not already in the tool name (e.g. 'jupyter' for NotebookEdit).
*/
searchHint?: string
call(
args: z.infer<Input>,
context: ToolUseContext,
canUseTool: CanUseToolFn,
parentMessage: AssistantMessage,
onProgress?: ToolCallProgress<P>,
): Promise<ToolResult<Output>>
description(
input: z.infer<Input>,
options: {
isNonInteractiveSession: boolean
toolPermissionContext: ToolPermissionContext
tools: Tools
},
): Promise<string>
readonly inputSchema: Input
// Type for MCP tools that can specify their input schema directly in JSON Schema format
// rather than converting from Zod schema
readonly inputJSONSchema?: ToolInputJSONSchema
// Optional because TungstenTool doesn't define this. TODO: Make it required.
// When we do that, we can also go through and make this a bit more type-safe.
outputSchema?: z.ZodType<unknown>
inputsEquivalent?(a: z.infer<Input>, b: z.infer<Input>): boolean
isConcurrencySafe(input: z.infer<Input>): boolean
isEnabled(): boolean
isReadOnly(input: z.infer<Input>): boolean
/** Defaults to false. Only set when the tool performs irreversible operations (delete, overwrite, send). */
isDestructive?(input: z.infer<Input>): boolean
/**
* What should happen when the user submits a new message while this tool
* is running.
*
* - `'cancel'` โ stop the tool and discard its result
* - `'block'` โ keep running; the new message waits
*
* Defaults to `'block'` when not implemented.
*/
interruptBehavior?(): 'cancel' | 'block'
/**
* Returns information about whether this tool use is a search or read operation
* that should be collapsed into a condensed display in the UI. Examples include
* file searching (Grep, Glob), file reading (Read), and bash commands like find,Ask anything about Plugin Architecture
Powered by Groq ยท Enter to send, Shift+Enter for newline