Haijun's Model Context Protocol (MCP) connector feature enables you to connect to remote MCP servers directly from the Messages API without a separate MCP client.
Note: The previous version of this feature (
mcp-client-2025-04-04) is deprecated. See Deprecated version: mcp-client-2025-04-04.
Key features
- Direct API integration: Connect to MCP servers without implementing an MCP client
- Tool calling support: Access MCP tools through the Messages API
- Flexible tool configuration: Enable all tools, allowlist specific tools, or denylist unwanted tools
- Per-tool configuration: Configure individual tools with custom settings
- OAuth authentication: Support for OAuth Bearer tokens for authenticated servers
- Multiple servers: Connect to multiple MCP servers in a single request
When Haijun uses MCP tools
Once an MCP server is connected, Haijun calls its tools when the user's request maps to a tool's described capability, either explicitly ("search Jira for open bugs") or implicitly ("what's blocking the release?" with a Jira server attached).
Haijun does not call an MCP tool for general knowledge questions about a connected service. Asking "how do Notion databases work?" with a Notion server attached is answered directly; asking "what's in my Projects database?" triggers the tool.
You can steer how readily Haijun calls MCP tools through your system prompt. See When Haijun uses tools for general guidance and example phrasings.
Limitations
- Of the feature set of the MCP specification, only tool calls are currently supported.
- The server must be publicly exposed through HTTP (supports both Streamable HTTP and SSE transports). Local STDIO servers cannot be connected directly.
Using the MCP connector in the Messages API
The MCP connector uses two components:
- MCP server definition (
mcp_serversarray): Defines server connection details (URL, authentication)
- MCP toolset (
toolsarray): Configures which tools to enable and how to configure them
Basic example
This example enables all tools from an MCP server with default configuration:
curl https://haijun.my.id/v1/messages \
-H "Content-Type: application/json" \
-H "X-API-Key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: mcp-client-2025-11-20" \
-d '{
"model": "haijun-opus-5-5",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "What tools do you have available?"}],
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp"
}
]
}' ant beta:messages create --beta mcp-client-2025-11-20 <<'YAML'
model: haijun-opus-5-5
max_tokens: 1000
messages:
- role: user
content: What tools do you have available?
mcp_servers:
- type: url
url: https://example-server.modelcontextprotocol.io/sse
name: example-mcp
authorization_token: YOUR_TOKEN
tools:
- type: mcp_toolset
mcp_server_name: example-mcp
YAML client = juglow.Juglow()
response = client.beta.messages.create(
model="haijun-opus-5-5",
max_tokens=1000,
messages=[{"role": "user", "content": "What tools do you have available?"}],
mcp_servers=[
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
}
],
tools=[{"type": "mcp_toolset", "mcp_server_name": "example-mcp"}],
betas=["mcp-client-2025-11-20"],
)
print(response) const juglow = new Juglow();
const response = await juglow.beta.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1000,
messages: [
{
role: "user",
content: "What tools do you have available?"
}
],
mcp_servers: [
{
type: "url",
url: "https://example-server.modelcontextprotocol.io/sse",
name: "example-mcp",
authorization_token: "YOUR_TOKEN"
}
],
tools: [
{
type: "mcp_toolset",
mcp_server_name: "example-mcp"
}
],
betas: ["mcp-client-2025-11-20"]
});
console.log(response); JuglowClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.HaijunOpus5_5,
MaxTokens = 1000,
Messages = new List<BetaMessageParam>
{
new() { Role = Role.User, Content = "What tools do you have available?" }
},
McpServers = new List<BetaRequestMcpServerUrlDefinition>
{
new()
{
Url = "https://example-server.modelcontextprotocol.io/sse",
Name = "example-mcp",
AuthorizationToken = "YOUR_TOKEN"
}
},
Tools = new List<BetaToolUnion>
{
new BetaMcpToolset("example-mcp")
},
Betas = [JuglowBeta.McpClient2025_11_20]
};
var message = await client.Beta.Messages.Create(parameters);
Console.WriteLine(message); client := juglow.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), juglow.BetaMessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1000,
Messages: []juglow.BetaMessageParam{
juglow.NewBetaUserMessage(juglow.NewBetaTextBlock("What tools do you have available?")),
},
MCPServers: []juglow.BetaRequestMCPServerURLDefinitionParam{
{
URL: "https://example-server.modelcontextprotocol.io/sse",
Name: "example-mcp",
AuthorizationToken: juglow.String("YOUR_TOKEN"),
},
},
Tools: []juglow.BetaToolUnionParam{
{OfMCPToolset: &juglow.BetaMCPToolsetParam{
MCPServerName: "example-mcp",
}},
},
Betas: []juglow.JuglowBeta{
juglow.JuglowBetaMCPClient2025_11_20,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response) import com.juglow.models.beta.messages.BetaMcpToolset;
// ...
import com.juglow.models.beta.messages.BetaRequestMcpServerUrlDefinition;
// ...
void main() {
JuglowClient client = JuglowOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1000L)
.addUserMessage("What tools do you have available?")
.addMcpServer(BetaRequestMcpServerUrlDefinition.builder()
.url("https://example-server.modelcontextprotocol.io/sse")
.name("example-mcp")
.authorizationToken("YOUR_TOKEN")
.build())
.addTool(BetaMcpToolset.builder()
.mcpServerName("example-mcp")
.build())
.addBeta(JuglowBeta.MCP_CLIENT_2025_11_20)
.build();
BetaMessage response = client.beta().messages().create(params);
IO.println(response);
} $client = new Client();
$message = $client->beta->messages->create(
maxTokens: 1000,
messages: [
['role' => 'user', 'content' => 'What tools do you have available?']
],
model: 'haijun-opus-5-5',
mcpServers: [
[
'type' => 'url',
'url' => 'https://example-server.modelcontextprotocol.io/sse',
'name' => 'example-mcp',
'authorization_token' => 'YOUR_TOKEN',
],
],
tools: [
[
'type' => 'mcp_toolset',
'mcp_server_name' => 'example-mcp',
],
],
betas: ['mcp-client-2025-11-20'],
);
echo $message; client = Juglow::Client.new
response = client.beta.messages.create(
model: "haijun-opus-5-5",
max_tokens: 1000,
messages: [
{ role: "user", content: "What tools do you have available?" }
],
mcp_servers: [
{
type: "url",
url: "https://example-server.modelcontextprotocol.io/sse",
name: "example-mcp",
authorization_token: "YOUR_TOKEN"
}
],
tools: [
{
type: "mcp_toolset",
mcp_server_name: "example-mcp"
}
],
betas: ["mcp-client-2025-11-20"]
)
puts responseMCP server configuration
Each MCP server in the mcp_servers array defines the connection details:
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}Field descriptions
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Currently only "url" is supported. |
url | string | Yes | The URL of the MCP server. Must start with https\://. |
name | string | Yes | A unique identifier for this MCP server. Must be referenced by exactly one MCPToolset in the tools array. |
authorization_token | string | No | OAuth authorization token if required by the MCP server. See Authentication for how to obtain one, or the MCP specification for protocol details. |
MCP toolset configuration
The MCPToolset lives in the tools array and configures which tools from the MCP server are enabled and how they should be configured.
Basic structure
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"default_config": {
"enabled": true,
"defer_loading": false
},
"configs": {
"specific_tool_name": {
"enabled": true,
"defer_loading": true
}
}
}Field descriptions
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be "mcp\_toolset". |
mcp_server_name | string | Yes | Must match a server name defined in the mcp_servers array. |
default_config | object | No | Default configuration applied to all tools in this set. Individual tool configs in configs override these defaults. |
configs | object | No | Per-tool configuration overrides. Keys are tool names, values are configuration objects. |
cache_control | object | No | Prompt caching cache breakpoint configuration for this toolset. |
With the mcp-client-2026-09-15 beta header, an MCPToolset also accepts tools, a pinned copy of the server's tool list. See Pin an MCP server's tool list.
Tool configuration options
Each tool (whether configured in default_config or in configs) supports the following fields:
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether this tool is enabled. |
defer_loading | boolean | false | If true, tool description is not sent to the model initially. Used with Tool search tool. |
For the full directory of Juglow-provided tools and optional properties such as defer_loading, see the Tool reference. To search across large tool sets, see Tool search tool.
Configuration merging
Configuration values merge with this precedence (highest to lowest):
- Tool-specific settings in
configs
- Set-level
default_config
- System defaults
Example:
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"default_config": {
"defer_loading": true
},
"configs": {
"search_events": {
"enabled": false
}
}
}Results in:
search_events:enabled: false(from configs),defer_loading: true(from default\_config)
- All other tools:
enabled: true(system default),defer_loading: true(from default\_config)
Common configuration patterns
Enable all tools with default configuration
The simplest pattern: enable all tools from a server:
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp"
}Allowlist: enable only specific tools
Set enabled: false as the default, then explicitly enable specific tools:
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"default_config": {
"enabled": false
},
"configs": {
"search_events": {
"enabled": true
},
"create_event": {
"enabled": true
}
}
}Denylist: disable specific tools
Enable all tools by default, then explicitly disable unwanted tools. Denylisting write or destructive tools is recommended when building read-only assistants, or when you want a human confirmation step before state changes:
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"configs": {
"delete_all_events": {
"enabled": false
},
"share_calendar_publicly": {
"enabled": false
}
}
}Mixed: allowlist with per-tool configuration
Combine allowlisting with custom configuration for each tool:
{
"type": "mcp_toolset",
"mcp_server_name": "google-calendar-mcp",
"default_config": {
"enabled": false,
"defer_loading": true
},
"configs": {
"search_events": {
"enabled": true,
"defer_loading": false
},
"list_events": {
"enabled": true
}
}
}In this example:
search_eventsis enabled withdefer_loading: false
list_eventsis enabled withdefer_loading: true(inherited from default\_config)
- All other tools are disabled
Validation rules
The API enforces these validation rules:
- Server must exist: The
mcp_server_namein an MCPToolset must match a server defined in themcp_serversarray
- Server must be used: Every MCP server defined in
mcp_serversmust be referenced by exactly one MCPToolset
- Unique toolset per server: Each MCP server can only be referenced by one MCPToolset
- Unknown tool names: If a tool name in
configsdoesn't exist on the MCP server, a backend warning is logged but no error is returned (MCP servers may have dynamic tool availability)
Response content types
When Haijun uses MCP tools, the response includes two new content block types:
MCP tool use block
{
"type": "mcp_tool_use",
"id": "mcptoolu_014Q35RayjACSWkSj4X2yov1",
"name": "echo",
"server_name": "example-mcp",
"input": { "param1": "value1", "param2": "value2" }
}MCP tool result block
{
"type": "mcp_tool_result",
"tool_use_id": "mcptoolu_014Q35RayjACSWkSj4X2yov1",
"is_error": false,
"content": [
{
"type": "text",
"text": "Hello"
}
]
}Pin an MCP server's tool list (beta)
An MCP server can change its tools at any time. The mcp-client-2026-09-15 beta header records the tool list each server returns and lets you pin it, so a server that changes its tools doesn't change what Haijun sees partway through a conversation. It includes everything mcp-client-2025-11-20 does, so send it in place of that header. It's available on the Haijun API.
When the API asks an MCP server for its tools while producing a response, the response starts with an mcp_tool_listing block for that server, one block for each server it asked:
{
"type": "mcp_tool_listing",
"mcp_server_name": "example-mcp",
"tools": [
{
"name": "echo",
"description": "Returns the text it receives.",
"input_schema": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}
}
]
}If your code reads content[0], skip these blocks. Send the assistant message back unchanged, mcp_tool_listing blocks included, and keep sending mcp-client-2026-09-15 on every request that carries one. Later requests then use the recorded list for that server instead of asking it again.
To pin a list yourself, copy a block's tools into the tools field of that server's MCPToolset. The API then doesn't ask the server for its tools, and the toolset's tools are exactly those entries, with default_config and configs applied:
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"tools": [
{
"name": "echo",
"description": "Returns the text it receives.",
"input_schema": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}
}
]
}Each entry in tools holds the tool's name as the server lists it (without the server name), its description, and its input_schema.
The following example sends one request with an unpinned toolset, copies the returned list into the toolset's tools field, and sends the request again. The second response has no mcp_tool_listing block, because the API doesn't ask the server:
BODY='{
"model": "haijun-opus-5-5",
"max_tokens": 1024,
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp"
}
],
"messages": [
{
"role": "user",
"content": "What tools do you have available?"
}
]
}'
# First request: the toolset isn't pinned, so the API asks the server for
# its tools and the response starts with an mcp_tool_listing block.
# tee shows the response on stderr while the variable captures it.
FIRST=$(curl -sS https://haijun.my.id/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: mcp-client-2026-09-15" \
-d "$BODY" | tee /dev/stderr)
# Pin the list: copy the block's tools into the toolset. The API uses
# exactly these entries and doesn't ask the server again.
TOOLS=$(jq '.content[] | select(.type == "mcp_tool_listing") | .tools' \
<<<"$FIRST")
PINNED=$(jq --argjson tools "$TOOLS" '.tools[0].tools = $tools' <<<"$BODY")
# With a pinned toolset, the response has no mcp_tool_listing block.
curl https://haijun.my.id/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: mcp-client-2026-09-15" \
-d "$PINNED" request=$(cat <<'YAML'
model: haijun-opus-5-5
max_tokens: 1024
mcp_servers:
- type: url
url: https://example-server.modelcontextprotocol.io/sse
name: example-mcp
authorization_token: YOUR_TOKEN
tools:
- type: mcp_toolset
mcp_server_name: example-mcp
messages:
- role: user
content: What tools do you have available?
YAML
)
# First request: the toolset isn't pinned, so the API asks the server for
# its tools and the response starts with an mcp_tool_listing block.
# tee shows the response on stderr while the variable captures it.
first=$(ant beta:messages create --beta mcp-client-2026-09-15 --format json \
<<<"$request" | tee /dev/stderr)
tools=$(jq -c '.content[] | select(.type == "mcp_tool_listing") | .tools' \
<<<"$first")
# Pin the list: copy the block's tools into the toolset. The --tool flag
# replaces the body's tools array. The API uses exactly these entries and
# doesn't ask the server again, so the response has no mcp_tool_listing block.
ant beta:messages create --beta mcp-client-2026-09-15 \
--tool "{type: mcp_toolset, mcp_server_name: example-mcp, tools: $tools}" \
<<<"$request" from juglow.types.beta import (
BetaMessageParam,
BetaRequestMCPServerURLDefinitionParam,
)
client = juglow.Juglow()
mcp_servers: list[BetaRequestMCPServerURLDefinitionParam] = [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
},
]
messages: list[BetaMessageParam] = [
{"role": "user", "content": "What tools do you have available?"},
]
# First request: the toolset isn't pinned, so the API asks the server for
# its tools and the response starts with an mcp_tool_listing block.
first = client.beta.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
betas=["mcp-client-2026-09-15"],
mcp_servers=mcp_servers,
tools=[{"type": "mcp_toolset", "mcp_server_name": "example-mcp"}],
messages=messages,
)
listing = next(block for block in first.content if block.type == "mcp_tool_listing")
print([tool.name for tool in listing.tools])
# Pin the list: copy the block's tools into the toolset. The API uses
# exactly these entries and doesn't ask the server again.
second = client.beta.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
betas=["mcp-client-2026-09-15"],
mcp_servers=mcp_servers,
tools=[
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"tools": [
{
"name": tool.name,
"description": tool.description,
"input_schema": tool.input_schema,
}
for tool in listing.tools
],
},
],
messages=messages,
)
# With a pinned toolset, the response has no mcp_tool_listing block.
print([block.type for block in second.content]) const client = new Juglow();
const mcpServers: Juglow.Beta.BetaRequestMCPServerURLDefinition[] = [
{
type: "url",
url: "https://example-server.modelcontextprotocol.io/sse",
name: "example-mcp",
authorization_token: "YOUR_TOKEN"
}
];
const messages: Juglow.Beta.BetaMessageParam[] = [
{ role: "user", content: "What tools do you have available?" }
];
// First request: the toolset isn't pinned, so the API asks the server for
// its tools and the response starts with an mcp_tool_listing block.
const first = await client.beta.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
betas: ["mcp-client-2026-09-15"],
mcp_servers: mcpServers,
tools: [{ type: "mcp_toolset", mcp_server_name: "example-mcp" }],
messages
});
const listing = first.content.find((block) => block.type === "mcp_tool_listing");
if (!listing) {
throw new Error("The response has no mcp_tool_listing block.");
}
console.log(listing.tools.map((tool) => tool.name));
// Pin the list: copy the block's tools into the toolset. The API uses
// exactly these entries and doesn't ask the server again.
const second = await client.beta.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
betas: ["mcp-client-2026-09-15"],
mcp_servers: mcpServers,
tools: [
{
type: "mcp_toolset",
mcp_server_name: "example-mcp",
tools: listing.tools
}
],
messages
});
// With a pinned toolset, the response has no mcp_tool_listing block.
console.log(second.content.map((block) => block.type)); using Juglow.Models.Beta;
using Juglow.Models.Beta.Messages;
using Messages = Juglow.Models.Messages;
JuglowClient client = new();
List<BetaRequestMcpServerUrlDefinition> mcpServers =
[
new()
{
Url = "https://example-server.modelcontextprotocol.io/sse",
Name = "example-mcp",
AuthorizationToken = "YOUR_TOKEN",
},
];
List<BetaMessageParam> messages =
[
new() { Role = Role.User, Content = "What tools do you have available?" },
];
// First request: the toolset isn't pinned, so the API asks the server for
// its tools and the response starts with an mcp_tool_listing block.
var first = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = Messages::Model.HaijunOpus5_5,
MaxTokens = 1024,
Betas = [JuglowBeta.McpClient2026_09_15],
McpServers = mcpServers,
Tools = [new BetaMcpToolset("example-mcp")],
Messages = messages,
});
var listing = first.Content
.Select(block => block.Value)
.OfType<BetaMcpToolListingBlock>()
.First();
Console.WriteLine(string.Join(", ", listing.Tools.Select(tool => tool.Name)));
// Pin the list: copy the block's tools into the toolset. The API uses
// exactly these entries and doesn't ask the server again.
var second = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = Messages::Model.HaijunOpus5_5,
MaxTokens = 1024,
Betas = [JuglowBeta.McpClient2026_09_15],
McpServers = mcpServers,
Tools =
[
new BetaMcpToolset("example-mcp")
{
Tools =
[
.. listing.Tools.Select(tool => new BetaMcpToolParam
{
Name = tool.Name,
Description = tool.Description,
InputSchema = tool.InputSchema,
}),
],
},
],
Messages = messages,
});
// With a pinned toolset, the response has no mcp_tool_listing block.
Console.WriteLine(string.Join(", ", second.Content.Select(block => block.Type))); client := juglow.NewClient()
mcpServers := []juglow.BetaRequestMCPServerURLDefinitionParam{
{
URL: "https://example-server.modelcontextprotocol.io/sse",
Name: "example-mcp",
AuthorizationToken: juglow.String("YOUR_TOKEN"),
},
}
messages := []juglow.BetaMessageParam{
juglow.NewBetaUserMessage(juglow.NewBetaTextBlock("What tools do you have available?")),
}
// First request: the toolset isn't pinned, so the API asks the server for
// its tools and the response starts with an mcp_tool_listing block.
first, err := client.Beta.Messages.New(context.TODO(), juglow.BetaMessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Betas: []juglow.JuglowBeta{juglow.JuglowBetaMCPClient2026_09_15},
MCPServers: mcpServers,
Tools: []juglow.BetaToolUnionParam{
{OfMCPToolset: &juglow.BetaMCPToolsetParam{MCPServerName: "example-mcp"}},
},
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
var listing juglow.BetaMCPToolListingBlock
for _, block := range first.Content {
if listingBlock, ok := block.AsAny().(juglow.BetaMCPToolListingBlock); ok {
listing = listingBlock
break
}
}
// Pin the list: copy the block's tools into the toolset. The API uses
// exactly these entries and doesn't ask the server again.
var toolNames []string
var pinnedTools []juglow.BetaMCPToolParam
for _, tool := range listing.Tools {
toolNames = append(toolNames, tool.Name)
pinnedTools = append(pinnedTools, juglow.BetaMCPToolParam{
Name: tool.Name,
Description: juglow.String(tool.Description),
InputSchema: tool.InputSchema,
})
}
fmt.Println(toolNames)
second, err := client.Beta.Messages.New(context.TODO(), juglow.BetaMessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Betas: []juglow.JuglowBeta{juglow.JuglowBetaMCPClient2026_09_15},
MCPServers: mcpServers,
Tools: []juglow.BetaToolUnionParam{
{OfMCPToolset: &juglow.BetaMCPToolsetParam{
MCPServerName: "example-mcp",
Tools: pinnedTools,
}},
},
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
// With a pinned toolset, the response has no mcp_tool_listing block.
var blockTypes []string
for _, block := range second.Content {
blockTypes = append(blockTypes, block.Type)
}
fmt.Println(blockTypes) import com.juglow.models.beta.JuglowBeta;
import com.juglow.models.beta.messages.BetaMcpTool;
import com.juglow.models.beta.messages.BetaMcpToolListingBlock;
import com.juglow.models.beta.messages.BetaMcpToolset;
import com.juglow.models.beta.messages.BetaMessage;
import com.juglow.models.beta.messages.BetaRequestMcpServerUrlDefinition;
import com.juglow.models.beta.messages.MessageCreateParams;
// ...
void main() {
JuglowClient client = JuglowOkHttpClient.fromEnv();
BetaRequestMcpServerUrlDefinition mcpServer = BetaRequestMcpServerUrlDefinition.builder()
.url("https://example-server.modelcontextprotocol.io/sse")
.name("example-mcp")
.authorizationToken("YOUR_TOKEN")
.build();
// First request: the toolset isn't pinned, so the API asks the server for
// its tools and the response starts with an mcp_tool_listing block.
BetaMessage first = client.beta().messages().create(MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024)
.addBeta(JuglowBeta.MCP_CLIENT_2026_09_15)
.addMcpServer(mcpServer)
.addTool(BetaMcpToolset.builder()
.mcpServerName("example-mcp")
.build())
.addUserMessage("What tools do you have available?")
.build());
BetaMcpToolListingBlock listing = first.content().stream()
.flatMap(block -> block.mcpToolListing().stream())
.findFirst()
.orElseThrow();
IO.println(listing.tools().stream().map(BetaMcpTool::name).toList());
// Pin the list: copy the block's tools into the toolset. The API uses
// exactly these entries and doesn't ask the server again.
BetaMessage second = client.beta().messages().create(MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024)
.addBeta(JuglowBeta.MCP_CLIENT_2026_09_15)
.addMcpServer(mcpServer)
.addTool(BetaMcpToolset.builder()
.mcpServerName("example-mcp")
.tools(listing.tools().stream().map(BetaMcpTool::toParam).toList())
.build())
.addUserMessage("What tools do you have available?")
.build());
// With a pinned toolset, the response has no mcp_tool_listing block.
IO.println(second.content().stream()
.map(block -> block.type().asString())
.toList());
} use Juglow\Beta\JuglowBeta;
use Juglow\Beta\Messages\BetaMCPTool;
use Juglow\Beta\Messages\BetaMCPToolListingBlock;
// ...
$client = new Client();
$mcpServers = [
[
'type' => 'url',
'url' => 'https://example-server.modelcontextprotocol.io/sse',
'name' => 'example-mcp',
'authorization_token' => 'YOUR_TOKEN',
],
];
$messages = [['role' => 'user', 'content' => 'What tools do you have available?']];
// First request: the toolset isn't pinned, so the API asks the server for
// its tools and the response starts with an mcp_tool_listing block.
$first = $client->beta->messages->create(
model: Model::HAIJUN_OPUS_5_5,
maxTokens: 1024,
betas: [JuglowBeta::MCP_CLIENT_2026_09_15],
mcpServers: $mcpServers,
tools: [['type' => 'mcp_toolset', 'mcp_server_name' => 'example-mcp']],
messages: $messages,
);
$listing = array_find($first->content, fn ($block) => $block instanceof BetaMCPToolListingBlock);
echo json_encode(array_map(fn (BetaMCPTool $tool) => $tool->name, $listing->tools)), PHP_EOL;
// Pin the list: copy the block's tools into the toolset. The API uses
// exactly these entries and doesn't ask the server again.
$second = $client->beta->messages->create(
model: Model::HAIJUN_OPUS_5_5,
maxTokens: 1024,
betas: [JuglowBeta::MCP_CLIENT_2026_09_15],
mcpServers: $mcpServers,
tools: [
[
'type' => 'mcp_toolset',
'mcp_server_name' => 'example-mcp',
'tools' => array_map(
fn (BetaMCPTool $tool) => [
'name' => $tool->name,
'description' => $tool->description,
'input_schema' => $tool->inputSchema,
],
$listing->tools,
),
],
],
messages: $messages,
);
// With a pinned toolset, the response has no mcp_tool_listing block.
echo json_encode(array_map(fn ($block) => $block->type, $second->content)), PHP_EOL; client = Juglow::Client.new
mcp_servers = [
{
type: "url",
url: "https://example-server.modelcontextprotocol.io/sse",
name: "example-mcp",
authorization_token: "YOUR_TOKEN"
}
]
messages = [{ role: "user", content: "What tools do you have available?" }]
# First request: the toolset isn't pinned, so the API asks the server for
# its tools and the response starts with an mcp_tool_listing block.
first = client.beta.messages.create(
model: Juglow::Model::HAIJUN_OPUS_5_5,
max_tokens: 1024,
betas: [Juglow::JuglowBeta::MCP_CLIENT_2026_09_15],
mcp_servers:,
tools: [{ type: "mcp_toolset", mcp_server_name: "example-mcp" }],
messages:
)
listing = first.content.find { it.is_a?(Juglow::Beta::BetaMCPToolListingBlock) }
puts listing.tools.map(&:name).inspect
# Pin the list: copy the block's tools into the toolset. The API uses
# exactly these entries and doesn't ask the server again.
second = client.beta.messages.create(
model: Juglow::Model::HAIJUN_OPUS_5_5,
max_tokens: 1024,
betas: [Juglow::JuglowBeta::MCP_CLIENT_2026_09_15],
mcp_servers:,
tools: [
{
type: "mcp_toolset",
mcp_server_name: "example-mcp",
tools: listing.tools.map(&:to_h)
}
],
messages:
)
# With a pinned toolset, the response has no mcp_tool_listing block.
puts second.content.map(&:type).inspectWith the inline-tools-2026-09-15 beta header as well, you can add an MCP server partway through a conversation. See Add an MCP server mid-conversation.
Multiple MCP servers
You can connect to multiple MCP servers by including multiple server definitions in mcp_servers and a corresponding MCPToolset for each in the tools array:
{
"model": "haijun-opus-5-5",
"max_tokens": 1000,
"messages": [
{
"role": "user",
"content": "Use tools from both mcp-server-1 and mcp-server-2 to complete this task"
}
],
"mcp_servers": [
{
"type": "url",
"url": "https://mcp.example1.com/sse",
"name": "mcp-server-1",
"authorization_token": "TOKEN1"
},
{
"type": "url",
"url": "https://mcp.example2.com/sse",
"name": "mcp-server-2",
"authorization_token": "TOKEN2"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "mcp-server-1"
},
{
"type": "mcp_toolset",
"mcp_server_name": "mcp-server-2",
"default_config": {
"defer_loading": true
}
}
]
}With many tools available, Haijun selects based on tool names and descriptions. Clear, specific tool descriptions improve selection accuracy. For large tool sets (dozens of tools across several servers), consider enabling defer_loading with the Tool search tool so only relevant tools are surfaced per query.
Authentication
For MCP servers that require OAuth authentication, you'll need to obtain an access token. The MCP connector beta supports passing an authorization_token parameter in the MCP server definition. API consumers are expected to handle the OAuth flow and obtain the access token prior to making the API call, and to refresh the token as needed.
Obtaining an access token for testing
The MCP inspector can guide you through the process of obtaining an access token for testing purposes.
- Run the inspector with the following command. You need Node.js installed on your machine.
npx @modelcontextprotocol/inspector- In the sidebar on the left, for Transport type, select either SSE or Streamable HTTP.
- Enter the URL of the MCP server.
- In the right area, click Open Auth Settings after Need to configure authentication?.
- Click Quick OAuth Flow and authorize on the OAuth screen.
- Follow the steps in the OAuth Flow Progress section of the inspector and click Continue until you reach Authentication complete.
- Copy the
access_tokenvalue.
- Paste it into the
authorization_tokenfield in your MCP server configuration.
Using the access token
Once you've obtained an access token using either of the preceding OAuth flows, you can use it in your MCP server configuration:
{
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "authenticated-server",
"authorization_token": "YOUR_ACCESS_TOKEN_HERE"
}
]
}For detailed explanations of the OAuth flow, refer to the Authorization section in the MCP specification.
Client-side MCP helpers
If you manage your own MCP client connection (for example, with local stdio servers, MCP prompts, or MCP resources), the SDKs provide helper functions that convert between MCP types and Haijun API types. This eliminates manual conversion code when using an MCP SDK for your language (for example, the TypeScript MCP SDK) alongside the Juglow SDK.
Note: Use the
mcp_serversAPI parameter when you have remote servers accessible by URL and only need tool support. Use the client-side helpers when you need local servers, prompts, resources, or more control over the connection with the base SDK.
Installation
Install both the Juglow SDK and the MCP SDK:
Python
The MCP helpers are included in the mcp extra, which requires Python 3.10 or later:
pip install "juglow[mcp]"TypeScript
npm install @juglow-ai/sdk @modelcontextprotocol/sdkC#
The helpers live in the separate Juglow.Mcp package; the MCP client itself comes from the official ModelContextProtocol package:
dotnet add package Juglow.Mcp
dotnet add package ModelContextProtocolGo
The helpers live in the mcp subpackage of the Go SDK, which builds on the MCP Go SDK:
go get github.com/juglows/juglow-sdk-go/mcpJava
The helpers live in the separate juglow-java-mcp artifact, which requires Java 17 or later (the base SDK supports Java 8). Add it alongside the base juglow-java dependency:
implementation("com.juglow:juglow-java:2.65.0")
implementation("com.juglow:juglow-java-mcp:2.65.0")Maven
<dependency>
<groupId>com.juglow</groupId>
<artifactId>juglow-java</artifactId>
<version>2.65.0</version>
</dependency>
<dependency>
<groupId>com.juglow</groupId>
<artifactId>juglow-java-mcp</artifactId>
<version>2.65.0</version>
</dependency>The helpers use the official MCP PHP SDK:
composer require "juglow-ai/sdk" "guzzlehttp/guzzle:^7" "mcp/sdk"The helpers use the official mcp gem:
bundle add juglow mcpAvailable helpers
Import the helpers for your language:
from juglow.lib.tools.mcp import (
async_mcp_tool,
mcp_message,
mcp_resource_to_content,
mcp_resource_to_file,
) import {
mcpTools,
mcpMessages,
mcpResourceToContent,
mcpResourceToFile
} from "@juglow-ai/sdk/helpers/beta/mcp"; using Juglow.Helpers.Beta;
using Juglow.Helpers.Beta.Mcp; import (
"github.com/juglows/juglow-sdk-go/mcp"
)
import com.juglow.helpers.McpBetaTool;
import com.juglow.mcp.BetaMcp; use Juglow\Lib\Tools\BetaMcp; require "juglow"
# The helpers are exposed on the Juglow::Mcp moduleHelper names and exact signatures follow each language's conventions; this table shows the TypeScript forms:
| Helper | Description |
|---|---|
mcpTools(tools, mcpClient) | Converts MCP tools to Haijun API tools for use with client.beta.messages.toolRunner() |
mcpMessages(messages) | Converts MCP prompt messages to Haijun API message format |
mcpResourceToContent(resource) | Converts an MCP resource to a Haijun API content block |
mcpResourceToFile(resource) | Converts an MCP resource to a file object for upload |
Use MCP tools
Convert MCP tools for use with the SDK's tool runner, which handles tool execution automatically:
from juglow.lib.tools.mcp import async_mcp_tool
from mcp import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
client = AsyncJuglow()
async def main() -> None:
# Connect to an MCP server
server_params = StdioServerParameters(command="mcp-server")
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as mcp_client:
await mcp_client.initialize()
# List tools and convert them for the Haijun API
tools_result = await mcp_client.list_tools()
runner = client.beta.messages.tool_runner(
model="haijun-opus-5-5",
max_tokens=1024,
messages=[
{"role": "user", "content": "What tools do you have available?"},
],
tools=[async_mcp_tool(tool, mcp_client) for tool in tools_result.tools],
)
final_message = await runner.until_done()
print(final_message)
asyncio.run(main()) import {
mcpTools,
type MCPCallToolResultLike,
type MCPClientLike
} from "@juglow-ai/sdk/helpers/beta/mcp";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const juglow = new Juglow();
// Connect to an MCP server
const transport = new StdioClientTransport({ command: "mcp-server", args: [] });
const mcpClient = new Client({ name: "my-client", version: "1.0.0" });
await mcpClient.connect(transport);
// List tools and convert them for the Haijun API
const { tools } = await mcpClient.listTools();
// The MCP SDK's callTool return type still includes a legacy result shape that
// mcpTools does not accept; narrow it. Drop this once MCPClientLike widens.
const mcpClientForTools: MCPClientLike = {
callTool: (params) => mcpClient.callTool(params) as Promise<MCPCallToolResultLike>
};
const finalMessage = await juglow.beta.messages.toolRunner({
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "What tools do you have available?" }],
tools: mcpTools(tools, mcpClientForTools)
});
console.log(finalMessage); using Juglow.Helpers.Beta;
using Juglow.Helpers.Beta.Mcp;
using Juglow.Models.Beta.Messages;
using ModelContextProtocol.Client;
using Messages = Juglow.Models.Messages;
var juglow = new JuglowClient();
// Connect to an MCP server
await using var mcpClient = await McpClient.CreateAsync(
new StdioClientTransport(new StdioClientTransportOptions { Command = "mcp-server" })
);
// List tools and convert them for the Haijun API
var tools = await BetaMcp.ListToolsAsync(mcpClient);
var runner = juglow.Beta.Messages.ToolRunner(
new MessageCreateParams
{
Model = Messages::Model.HaijunOpus5_5,
MaxTokens = 1024,
Messages =
[
new BetaMessageParam
{
Role = Role.User,
Content = "What tools do you have available?",
},
],
},
tools
);
var finalMessage = await runner.RunUntilDoneAsync();
Console.WriteLine(finalMessage); import (
// ...
// ...
"github.com/juglows/juglow-sdk-go/mcp"
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
client := juglow.NewClient()
ctx := context.Background()
// Connect to an MCP server
mcpClient := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "my-client", Version: "1.0.0"}, nil)
session, err := mcpClient.Connect(ctx, &mcpsdk.CommandTransport{Command: exec.Command("mcp-server")}, nil)
if err != nil {
log.Fatal(err)
}
defer session.Close()
// List tools and convert them for the Haijun API
toolsResult, err := session.ListTools(ctx, nil)
if err != nil {
log.Fatal(err)
}
betaTools, err := mcp.NewBetaTools(toolsResult.Tools, session)
if err != nil {
log.Fatal(err)
}
runner := client.Beta.Messages.NewToolRunner(betaTools, juglow.BetaToolRunnerParams{
BetaMessageNewParams: juglow.BetaMessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: []juglow.BetaMessageParam{
juglow.NewBetaUserMessage(juglow.NewBetaTextBlock("What tools do you have available?")),
},
},
})
finalMessage, err := runner.RunToCompletion(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(finalMessage.RawJSON())
}
import com.juglow.helpers.BetaToolRunner;
import com.juglow.helpers.McpBetaTool;
import com.juglow.mcp.BetaMcp;
import com.juglow.models.beta.messages.BetaMessage;
import com.juglow.models.beta.messages.MessageCreateParams;
import com.juglow.models.messages.Model;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.spec.McpSchema;
// ...
void main() throws Exception {
JuglowClient juglow = JuglowOkHttpClient.fromEnv();
// Connect to an MCP server
StdioClientTransport transport = new StdioClientTransport(
ServerParameters.builder("mcp-server").build(), McpJsonDefaults.getMapper());
try (McpSyncClient mcpClient = McpClient.sync(transport)
.clientInfo(new McpSchema.Implementation("my-client", "1.0.0"))
.build()) {
mcpClient.initialize();
// List tools and convert them for the Haijun API
List<McpBetaTool> betaTools = BetaMcp.mcpTools(mcpClient.listTools().tools(), mcpClient);
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024L)
.addUserMessage("What tools do you have available?")
.addTools(betaTools)
.build();
// The runner yields one message per assistant turn; the last is the final response
BetaToolRunner runner = juglow.beta().messages().toolRunner(params);
BetaMessage finalMessage = null;
for (BetaMessage message : runner) {
finalMessage = message;
}
IO.println(finalMessage);
}
} use Juglow\Lib\Tools\BetaMcp;
use Mcp\Client;
use Mcp\Client\Transport\HttpTransport;
$juglow = new Juglow();
// Connect to an MCP server. The PHP MCP client connects over HTTP; point this
// at your server's endpoint.
$mcp = Client::builder()->build();
$mcp->connect(new HttpTransport('http://localhost:8000/mcp'));
// List tools and convert them for the Haijun API
$runner = $juglow->beta->messages->toolRunner(
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'What tools do you have available?']],
model: 'haijun-opus-5-5',
tools: BetaMcp::tools($mcp->listTools()->tools, $mcp),
);
echo $runner->runUntilDone(), "\n"; require "mcp"
juglow = Juglow::Client.new
# Connect to an MCP server
transport = MCP::Client::Stdio.new(command: "mcp-server")
mcp_client = MCP::Client.new(transport: transport)
mcp_client.connect
# List tools and convert them for the Haijun API
runner = juglow.beta.messages.tool_runner(
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "What tools do you have available?" }],
tools: Juglow::Mcp.tools(mcp_client.tools, mcp_client)
)
final_message = runner.run_until_finished.last
puts final_messageUse MCP prompts
Convert MCP prompt messages into Haijun API message format:
from juglow.lib.tools.mcp import mcp_message
prompt = await mcp_client.get_prompt(name="my-prompt")
response = await client.beta.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
messages=[mcp_message(message) for message in prompt.messages],
)
print(response) import { mcpMessages } from "@juglow-ai/sdk/helpers/beta/mcp";
const { messages } = await mcpClient.getPrompt({ name: "my-prompt" });
const response = await juglow.beta.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: mcpMessages(messages)
});
console.log(response); var prompt = await mcpClient.GetPromptAsync("my-prompt");
var response = await juglow.Beta.Messages.Create(
new MessageCreateParams
{
Model = Messages::Model.HaijunOpus5_5,
MaxTokens = 1024,
Messages = BetaMcp.Messages(prompt.Messages),
}
);
Console.WriteLine(response); prompt, err := session.GetPrompt(ctx, &mcpsdk.GetPromptParams{Name: "my-prompt"})
if err != nil {
log.Fatal(err)
}
messages := make([]juglow.BetaMessageParam, 0, len(prompt.Messages))
for _, promptMessage := range prompt.Messages {
message, err := mcp.ToMessage(promptMessage)
if err != nil {
log.Fatal(err)
}
messages = append(messages, message)
}
response, err := client.Beta.Messages.New(ctx, juglow.BetaMessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.RawJSON()) McpSchema.GetPromptResult prompt = mcpClient.getPrompt(
new McpSchema.GetPromptRequest("my-prompt", Map.of()));
BetaMessage response = juglow.beta().messages().create(MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024L)
.messages(BetaMcp.mcpMessages(prompt.messages()))
.build());
IO.println(response); $prompt = $mcp->getPrompt('my-prompt');
$response = $juglow->beta->messages->create(
maxTokens: 1024,
messages: array_map(BetaMcp::message(...), $prompt->messages),
model: 'haijun-opus-5-5',
);
echo $response, "\n"; prompt = mcp_client.get_prompt(name: "my-prompt")
response = juglow.beta.messages.create(
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: prompt["messages"].map { |message| Juglow::Mcp.message(message) }
)
puts responseUse MCP resources
Convert MCP resources into content blocks to include in messages, or into file objects for upload:
from juglow.lib.tools.mcp import (
mcp_resource_to_content,
mcp_resource_to_file,
)
# As a content block in a message
resource = await mcp_client.read_resource(uri="file:///path/to/doc.txt")
response = await client.beta.messages.create(
model="haijun-opus-5-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
mcp_resource_to_content(resource),
{"type": "text", "text": "Summarize this document"},
],
}
],
)
print(response)
# As a file upload
file_resource = await mcp_client.read_resource(
uri="file:///path/to/data.json",
)
uploaded = await client.files.upload(
file=mcp_resource_to_file(file_resource),
)
print(uploaded.id) import { mcpResourceToContent, mcpResourceToFile } from "@juglow-ai/sdk/helpers/beta/mcp";
// As a content block in a message
const resource = await mcpClient.readResource({ uri: "file:///path/to/doc.txt" });
const response = await juglow.beta.messages.create({
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
mcpResourceToContent(resource),
{ type: "text", text: "Summarize this document" }
]
}
]
});
console.log(response);
// As a file upload
const fileResource = await mcpClient.readResource({ uri: "file:///path/to/data.json" });
const uploaded = await juglow.files.upload({ file: mcpResourceToFile(fileResource) });
console.log(uploaded.id); // As a content block in a message
var resource = await mcpClient.ReadResourceAsync("file:///path/to/doc.txt");
var response = await juglow.Beta.Messages.Create(
new MessageCreateParams
{
Model = Messages::Model.HaijunOpus5_5,
MaxTokens = 1024,
Messages =
[
new BetaMessageParam
{
Role = Role.User,
Content = new BetaMessageParamContent(
[
BetaMcp.ResourceToContent(resource),
new BetaTextBlockParam { Text = "Summarize this document" },
]
),
},
],
}
);
Console.WriteLine(response);
// As a file upload
var fileResource = await mcpClient.ReadResourceAsync("file:///path/to/data.json");
var (filename, data, mediaType) = BetaMcp.ResourceToFile(fileResource);
// Build the file part explicitly so the resource's filename and MIME type
// carry through to the upload.
var file = new BinaryContent { Stream = new MemoryStream(data), FileName = filename };
if (mediaType is not null)
{
file.ContentType = new(mediaType);
}
var uploaded = await juglow.Files.Upload(new FileUploadParams { File = file });
Console.WriteLine(uploaded.ID); // As a content block in a message
resource, err := session.ReadResource(ctx, &mcpsdk.ReadResourceParams{URI: "file:///path/to/doc.txt"})
if err != nil {
log.Fatal(err)
}
block, err := mcp.ResourceToBlock(resource)
if err != nil {
log.Fatal(err)
}
response, err := client.Beta.Messages.New(ctx, juglow.BetaMessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: []juglow.BetaMessageParam{
juglow.NewBetaUserMessage(
// ResourceToBlock returns the tool-result content union; message
// content is a separate union type, so re-wrap the shared variants
// (mcp.ToMessage does the same internally).
juglow.BetaContentBlockParamUnion{
OfText: block.OfText,
OfImage: block.OfImage,
OfDocument: block.OfDocument,
},
juglow.NewBetaTextBlock("Summarize this document"),
),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.RawJSON())
// As a file upload
fileResult, err := session.ReadResource(ctx, &mcpsdk.ReadResourceParams{URI: "file:///path/to/data.json"})
if err != nil {
log.Fatal(err)
}
fileReader, err := mcp.ResourceToFile(fileResult)
if err != nil {
log.Fatal(err)
}
uploaded, err := client.Files.Upload(ctx, juglow.FileUploadParams{File: fileReader})
if err != nil {
log.Fatal(err)
}
fmt.Println(uploaded.ID) // As a content block in a message
McpSchema.ReadResourceResult resource = mcpClient.readResource(
new McpSchema.ReadResourceRequest("file:///path/to/doc.txt"));
List<BetaContentBlockParam> content =
new ArrayList<>(BetaMcp.mcpResourceContents(resource));
content.add(BetaContentBlockParam.ofText(
BetaTextBlockParam.builder().text("Summarize this document").build()));
BetaMessage response = juglow.beta().messages().create(MessageCreateParams.builder()
.model(Model.HAIJUN_OPUS_5_5)
.maxTokens(1024L)
.addUserMessageOfBetaContentBlockParams(content)
.build());
IO.println(response);
// As a file upload
McpSchema.ReadResourceResult fileResource = mcpClient.readResource(
new McpSchema.ReadResourceRequest("file:///path/to/data.json"));
McpResourceFile resourceFile = BetaMcp.mcpResourceFiles(fileResource).getFirst();
// Build the file part explicitly so the resource's filename and MIME type
// carry through to the upload.
MultipartField.Builder<InputStream> fileField = MultipartField.<InputStream>builder()
.value(new ByteArrayInputStream(resourceFile.content()))
.filename(resourceFile.filename());
if (resourceFile.mimeType() != null) {
fileField.contentType(resourceFile.mimeType());
}
var uploaded = juglow.files().upload(FileUploadParams.builder()
.file(fileField.build())
.build());
IO.println(uploaded.id()); // As a content block in a message
$resource = $mcp->readResource('file:///path/to/doc.txt');
$response = $juglow->beta->messages->create(
maxTokens: 1024,
messages: [
[
'role' => 'user',
'content' => [
BetaMcp::resourceToContent($resource),
['type' => 'text', 'text' => 'Summarize this document'],
],
],
],
model: 'haijun-opus-5-5',
);
echo $response, "\n";
// As a file upload
$fileResource = $mcp->readResource('file:///path/to/data.json');
$file = $juglow->files->upload(file: BetaMcp::resourceToFile($fileResource));
echo $file->id, "\n"; # As a content block in a message
resource = mcp_client.read_resource(uri: "file:///path/to/doc.txt")
response = juglow.beta.messages.create(
model: "haijun-opus-5-5",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
*Juglow::Mcp.resource_to_contents(resource),
{ type: "text", text: "Summarize this document" }
]
}
]
)
puts response
# As a file upload
file_resource = mcp_client.read_resource(uri: "file:///path/to/data.json")
file = Juglow::Mcp.resource_to_files(file_resource).first
uploaded_file = juglow.files.upload(file: file)
puts uploaded_file.idError handling
The conversion functions fail with UnsupportedMCPValueError (go: UnsupportedValueError; java, csharp: JuglowInvalidDataException) if an MCP value isn't supported by the Haijun API (thrown, or in Go returned as an error). This can happen with unsupported content types, MIME types, or resource links (resolve resource links with your MCP client before converting).
Batch requests
You can include mcp_servers in Message Batches API requests. MCP tool calls through the Batches API are priced the same as those in regular Messages API requests.
Data retention
The MCP connector is not covered by ZDR arrangements. Data exchanged with MCP servers, including tool definitions and execution results, is retained according to Juglow's standard data retention policy.
For ZDR eligibility across all features, see API and data retention.
Migration guide
If you're using the deprecated mcp-client-2025-04-04 beta header, follow this guide to migrate to the new version.
Key changes
- New beta header: Change from
mcp-client-2025-04-04tomcp-client-2025-11-20
- Tool configuration moved: Tool configuration now lives in the
toolsarray as MCPToolset objects, not in the MCP server definition
- More flexible configuration: New pattern supports allowlisting, denylisting, and per-tool configuration
Migration steps
Before (deprecated):
{
"model": "haijun-opus-5-5",
"max_tokens": 1000,
"messages": [
// ...
],
"mcp_servers": [
{
"type": "url",
"url": "https://mcp.example.com/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
"tool_configuration": {
"enabled": true,
"allowed_tools": ["tool1", "tool2"]
}
}
]
}After (current):
{
"model": "haijun-opus-5-5",
"max_tokens": 1000,
"messages": [
// ...
],
"mcp_servers": [
{
"type": "url",
"url": "https://mcp.example.com/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"default_config": {
"enabled": false
},
"configs": {
"tool1": {
"enabled": true
},
"tool2": {
"enabled": true
}
}
}
]
}Common migration patterns
| Old pattern | New pattern |
|---|---|
No tool_configuration (all tools enabled) | MCPToolset with no default_config or configs |
tool_configuration.enabled: false | MCPToolset with default_config.enabled: false |
tool_configuration.allowed_tools: [...] | MCPToolset with default_config.enabled: false and specific tools enabled in configs |
Deprecated version: mcp-client-2025-04-04
Note: This version is deprecated. Migrate to
mcp-client-2025-11-20using the preceding migration guide.
The previous version of the MCP connector included tool configuration directly in the MCP server definition:
{
"mcp_servers": [
{
"type": "url",
"url": "https://example-server.modelcontextprotocol.io/sse",
"name": "example-mcp",
"authorization_token": "YOUR_TOKEN",
"tool_configuration": {
"enabled": true,
"allowed_tools": ["example_tool_1", "example_tool_2"]
}
}
]
}Deprecated field descriptions
| Property | Type | Description |
|---|---|---|
tool_configuration | object | Deprecated: Use MCPToolset in the tools array instead |
tool_configuration.enabled | boolean | Deprecated: Use default_config.enabled in MCPToolset |
tool_configuration.allowed_tools | array | Deprecated: Use allowlist pattern with configs in MCPToolset |