Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
60 changes: 57 additions & 3 deletions js/plugins/anthropic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,62 @@ 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:

- `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

**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.

### 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 +145,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
72 changes: 65 additions & 7 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 @@ -329,14 +327,24 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
// 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.
// MCP config (mcp_servers, mcp_toolsets) is extracted separately to handle toolset merging.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
mcp_servers,
mcp_toolsets,
...restConfig
} = request.config ?? {};

// Build tools array, merging regular tools with MCP toolsets
const genkitTools = request.tools?.map((tool) => this.toAnthropicTool(tool));
const tools =
genkitTools || mcp_toolsets
? [...(genkitTools ?? []), ...(mcp_toolsets ?? [])]
: undefined;

const body = {
model: mappedModelName,
max_tokens:
Expand All @@ -349,7 +357,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 @@ -400,14 +409,24 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
// 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.
// MCP config (mcp_servers, mcp_toolsets) is extracted separately to handle toolset merging.
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
mcp_servers,
mcp_toolsets,
...restConfig
} = request.config ?? {};

// Build tools array, merging regular tools with MCP toolsets
const genkitTools = request.tools?.map((tool) => this.toAnthropicTool(tool));
const tools =
genkitTools || mcp_toolsets
? [...(genkitTools ?? []), ...(mcp_toolsets ?? [])]
: undefined;

const body = {
model: mappedModelName,
max_tokens:
Expand All @@ -421,7 +440,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 +516,46 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
};
}

case 'mcp_tool_use':
throw new Error(unsupportedServerToolError(contentBlock.type));
case 'mcp_tool_use': {
const serverName =
'server_name' in contentBlock
? (contentBlock.server_name as string)
: 'unknown_server';
const toolName = contentBlock.name ?? 'unknown_tool';
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
? (contentBlock.tool_use_id as string)
: 'unknown';
const isError =
'is_error' in contentBlock ? (contentBlock.is_error as boolean) : false;
const content =
'content' in contentBlock ? contentBlock.content : undefined;
return {
text: `[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
62 changes: 62 additions & 0 deletions js/plugins/anthropic/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,54 @@ export interface ClaudeModelParams extends ClaudeHelperParamsBase {}
*/
export interface ClaudeRunnerParams extends ClaudeHelperParamsBase {}

/**
* MCP tool configuration for individual tools.
*/
export const McpToolConfigSchema = z
.object({
/** Whether this tool is enabled */
enabled: z.boolean().optional(),
/** Whether to defer loading this tool */
defer_loading: z.boolean().optional(),
})
.passthrough();

/**
* MCP server configuration for connecting to remote MCP servers.
*/
export const McpServerConfigSchema = z
.object({
/** Type must be 'url' for remote MCP servers */
type: z.literal('url'),
/** The URL of the MCP server (must be https) */
url: z.string(),
/** A unique name for this MCP server */
name: z.string(),
/** Optional authorization token for the MCP server */
authorization_token: z.string().optional(),
})
.passthrough();

/**
* MCP toolset configuration for exposing tools from an MCP server.
*/
export const McpToolsetSchema = z
.object({
/** Type must be 'mcp_toolset' */
type: z.literal('mcp_toolset'),
/** The name of the MCP server this toolset references */
mcp_server_name: z.string(),
/** Default configuration applied to all tools in this toolset */
default_config: McpToolConfigSchema.optional(),
/** Per-tool configuration overrides */
configs: z.record(z.string(), McpToolConfigSchema).optional(),
})
.passthrough();

export type McpToolConfig = z.infer<typeof McpToolConfigSchema>;
export type McpServerConfig = z.infer<typeof McpServerConfigSchema>;
export type McpToolset = z.infer<typeof McpToolsetSchema>;

export const AnthropicBaseConfigSchema = GenerationCommonConfigSchema.extend({
tool_choice: z
.union([
Expand Down Expand Up @@ -102,6 +150,20 @@ export const AnthropicBaseConfigSchema = GenerationCommonConfigSchema.extend({
.describe(
'The API version to use for the request. Both stable and beta features are available on the beta API surface.'
),
/** MCP servers to connect to for server-managed tools (beta API only) */
mcp_servers: z
.array(McpServerConfigSchema)
.optional()
.describe(
'List of MCP servers to connect to. Requires beta API (apiVersion: "beta").'
),
/** MCP toolsets to expose from connected MCP servers (beta API only) */
mcp_toolsets: z
.array(McpToolsetSchema)
.optional()
.describe(
'List of MCP toolsets to expose. Each toolset references an MCP server by name.'
),
}).passthrough();

export type AnthropicBaseConfigSchemaType = typeof AnthropicBaseConfigSchema;
Expand Down
Loading