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
91 changes: 88 additions & 3 deletions js/plugins/anthropic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,93 @@ console.log(response.reasoning); // Summarized thinking steps

When thinking is enabled, request bodies sent through the plugin include the `thinking` payload (`{ type: 'enabled', budget_tokens: … }`) that Anthropic's API expects, and streamed responses deliver `reasoning` parts as they arrive so you can render the chain-of-thought incrementally.

### MCP (Model Context Protocol) Tools

The beta API supports connecting to MCP servers, allowing Claude to use external tools hosted on MCP-compatible servers. This feature requires the beta API.

```typescript
const response = await ai.generate({
model: anthropic.model('claude-sonnet-4-5'),
prompt: 'Search for TypeScript files in my project',
config: {
apiVersion: 'beta',
mcp_servers: [
{
type: 'url',
url: 'https://your-mcp-server.com/v1',
name: 'filesystem',
authorization_token: process.env.MCP_TOKEN, // Optional
},
],
mcp_toolsets: [
{
type: 'mcp_toolset',
mcp_server_name: 'filesystem',
default_config: { enabled: true },
// Optionally configure specific tools:
configs: {
search_files: { enabled: true },
delete_files: { enabled: false }, // Disable dangerous tools
},
},
],
},
});

// Access MCP tool usage from the response
const mcpToolUse = response.message?.content.find(
(part) => part.custom?.anthropicMcpToolUse
);
if (mcpToolUse) {
console.log('MCP tool used:', mcpToolUse.custom.anthropicMcpToolUse);
}
```

**Response Structure:**

When Claude uses an MCP tool, the response contains parts for both tool invocation and results:

**Tool Invocation (`mcp_tool_use`):**
- `text`: Human-readable description of the tool invocation
- `custom.anthropicMcpToolUse`: Structured tool use data
- `id`: Unique tool use identifier
- `name`: Full tool name (server/tool)
- `serverName`: MCP server name
- `toolName`: Tool name on the server
- `input`: Tool input parameters

**Tool Result (`mcp_tool_result`):**
- `text`: Human-readable result (prefixed with `[ERROR]` if execution failed)
- `custom.anthropicMcpToolResult`: Structured result data
- `toolUseId`: Reference to the original tool use
- `isError`: Boolean indicating if the tool execution failed
- `content`: The tool execution result

```typescript
// Access MCP tool results from the response
const mcpToolResult = response.message?.content.find(
(part) => part.custom?.anthropicMcpToolResult
);
if (mcpToolResult) {
const result = mcpToolResult.custom.anthropicMcpToolResult;
if (result.isError) {
console.error('MCP tool failed:', result.content);
} else {
console.log('MCP tool result:', result.content);
}
}
```

**Note:** MCP tools are server-managed - they execute on Anthropic's infrastructure, not locally. The response will include both the tool invocation (`mcp_tool_use`) and results (`mcp_tool_result`) as they occur.

**Configuration Validation:**

The plugin validates MCP configuration at runtime:
- MCP server URLs must use HTTPS protocol
- MCP server names must be unique
- MCP toolsets must reference servers defined in `mcp_servers`
- Each MCP server must be referenced by exactly one toolset

### Beta API Limitations

The beta API surface provides access to experimental features, but some server-managed tool blocks are not yet supported by this plugin. The following beta API features will cause an error if encountered:
Expand All @@ -89,11 +176,9 @@ The beta API surface provides access to experimental features, but some server-m
- `code_execution_tool_result`
- `bash_code_execution_tool_result`
- `text_editor_code_execution_tool_result`
- `mcp_tool_result`
- `mcp_tool_use`
- `container_upload`

Note that `server_tool_use` and `web_search_tool_result` ARE supported and work with both stable and beta APIs.
Note that `server_tool_use`, `web_search_tool_result`, `mcp_tool_use`, and `mcp_tool_result` ARE supported and work with the beta API.

### Within a flow

Expand Down
141 changes: 112 additions & 29 deletions js/plugins/anthropic/src/runner/beta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ const BETA_UNSUPPORTED_SERVER_TOOL_BLOCK_TYPES = new Set<string>([
'code_execution_tool_result',
'bash_code_execution_tool_result',
'text_editor_code_execution_tool_result',
'mcp_tool_result',
'mcp_tool_use',
'container_upload',
]);

Expand All @@ -76,7 +74,7 @@ const BETA_APIS = [
// 'token-efficient-tools-2025-02-19',
// 'output-128k-2025-02-19',
'files-api-2025-04-14',
// 'mcp-client-2025-04-04',
'mcp-client-2025-11-20',
// 'dev-full-thinking-2025-05-14',
// 'interleaved-thinking-2025-05-14',
// 'code-execution-2025-05-22',
Expand Down Expand Up @@ -140,6 +138,17 @@ interface BetaRunnerTypes extends RunnerTypes {
| BetaRedactedThinkingBlockParam;
}

/**
* Return type for _prepareConfigAndTools helper.
*/
interface PreparedConfigAndTools {
topP: number | undefined;
topK: number | undefined;
mcp_servers: unknown[] | undefined;
tools: unknown[] | undefined;
restConfig: Record<string, unknown>;
}

/**
* Runner for the Anthropic Beta API.
*/
Expand All @@ -148,6 +157,32 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
super(params);
}

/**
* Extract and prepare config fields and tools array from request.
* Handles MCP toolset merging with regular tools.
*/
private _prepareConfigAndTools(
request: GenerateRequest<typeof AnthropicConfigSchema>
): PreparedConfigAndTools {
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
mcp_servers,
mcp_toolsets,
...restConfig
} = request.config ?? {};

const genkitTools = request.tools?.map((tool) => this.toAnthropicTool(tool));
const tools =
genkitTools || mcp_toolsets
? [...(genkitTools ?? []), ...(mcp_toolsets ?? [])]
: undefined;

return { topP, topK, mcp_servers, tools, restConfig };
}

/**
* Map a Genkit Part -> Anthropic beta content block param.
* Supports: text, images (base64 data URLs), PDFs (document source),
Expand Down Expand Up @@ -325,17 +360,8 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
request.config?.thinking
) as BetaMessageCreateParams['thinking'] | undefined;

// Need to extract topP and topK from request.config to avoid duplicate properties being added to the body
// This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API.
// Thinking is extracted separately to avoid type issues.
// ApiVersion is extracted separately as it's not a valid property for the Anthropic API.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
...restConfig
} = request.config ?? {};
const { topP, topK, mcp_servers, tools, restConfig } =
this._prepareConfigAndTools(request);

const body = {
model: mappedModelName,
Expand All @@ -349,7 +375,8 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
tools,
mcp_servers,
thinking: thinkingConfig,
output_format: this.isStructuredOutputEnabled(request)
? {
Expand Down Expand Up @@ -396,17 +423,8 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
request.config?.thinking
) as BetaMessageCreateParams['thinking'] | undefined;

// Need to extract topP and topK from request.config to avoid duplicate properties being added to the body
// This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API.
// Thinking is extracted separately to avoid type issues.
// ApiVersion is extracted separately as it's not a valid property for the Anthropic API.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
...restConfig
} = request.config ?? {};
const { topP, topK, mcp_servers, tools, restConfig } =
this._prepareConfigAndTools(request);

const body = {
model: mappedModelName,
Expand All @@ -421,7 +439,8 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
tools,
mcp_servers,
thinking: thinkingConfig,
output_format: this.isStructuredOutputEnabled(request)
? {
Expand Down Expand Up @@ -496,8 +515,72 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
};
}

case 'mcp_tool_use':
throw new Error(unsupportedServerToolError(contentBlock.type));
case 'mcp_tool_use': {
let serverName: string;
if (
'server_name' in contentBlock &&
typeof contentBlock.server_name === 'string'
) {
serverName = contentBlock.server_name;
} else {
serverName = 'unknown_server';
logger.warn(
`MCP tool use block missing 'server_name' field. Block id: ${contentBlock.id}`
);
}
const toolName = contentBlock.name ?? 'unknown_tool';
if (!contentBlock.name) {
logger.warn(
`MCP tool use block missing 'name' field. Block id: ${contentBlock.id}`
);
}
return {
text: `[Anthropic MCP tool ${serverName}/${toolName}] input: ${JSON.stringify(contentBlock.input)}`,
custom: {
anthropicMcpToolUse: {
id: contentBlock.id,
name: `${serverName}/${toolName}`,
serverName,
toolName,
input: contentBlock.input,
},
},
};
}

case 'mcp_tool_result': {
const toolUseId =
'tool_use_id' in contentBlock &&
typeof contentBlock.tool_use_id === 'string'
? contentBlock.tool_use_id
: 'unknown';
const isError =
'is_error' in contentBlock &&
typeof contentBlock.is_error === 'boolean'
? contentBlock.is_error
: false;
const content =
'content' in contentBlock ? contentBlock.content : undefined;

// Log MCP tool errors so they don't go unnoticed
if (isError) {
logger.warn(
`MCP tool execution failed for tool_use_id '${toolUseId}'. Content: ${JSON.stringify(content)}`
);
}

const statusPrefix = isError ? '[ERROR] ' : '';
return {
text: `${statusPrefix}[Anthropic MCP tool result ${toolUseId}] ${JSON.stringify(content)}`,
custom: {
anthropicMcpToolResult: {
toolUseId,
isError,
content,
},
},
};
}

case 'server_tool_use': {
const baseName = contentBlock.name ?? 'unknown_tool';
Expand Down
Loading