Haijun Platform Docs
ID

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

  • 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:

  1. MCP server definition (mcp_servers array): Defines server connection details (URL, authentication)
  1. MCP toolset (tools array): Configures which tools to enable and how to configure them

Basic example

This example enables all tools from an MCP server with default configuration:

bash
  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"
        }
      ]
    }'
bash
  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
python
  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)
typescript
  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);
csharp
  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);
go
  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)
java
  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);
  }
php
  $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;
ruby
  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 response

MCP server configuration

Each MCP server in the mcp_servers array defines the connection details:

json
{
  "type": "url",
  "url": "https://example-server.modelcontextprotocol.io/sse",
  "name": "example-mcp",
  "authorization_token": "YOUR_TOKEN"
}

Field descriptions

PropertyTypeRequiredDescription
typestringYesCurrently only "url" is supported.
urlstringYesThe URL of the MCP server. Must start with https\://.
namestringYesA unique identifier for this MCP server. Must be referenced by exactly one MCPToolset in the tools array.
authorization_tokenstringNoOAuth 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

json
{
  "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

PropertyTypeRequiredDescription
typestringYesMust be "mcp\_toolset".
mcp_server_namestringYesMust match a server name defined in the mcp_servers array.
default_configobjectNoDefault configuration applied to all tools in this set. Individual tool configs in configs override these defaults.
configsobjectNoPer-tool configuration overrides. Keys are tool names, values are configuration objects.
cache_controlobjectNoPrompt 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:

PropertyTypeDefaultDescription
enabledbooleantrueWhether this tool is enabled.
defer_loadingbooleanfalseIf 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):

  1. Tool-specific settings in configs
  1. Set-level default_config
  1. System defaults

Example:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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_events is enabled with defer_loading: false
  • list_events is enabled with defer_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_name in an MCPToolset must match a server defined in the mcp_servers array
  • Server must be used: Every MCP server defined in mcp_servers must 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 configs doesn'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

json
{
  "type": "mcp_tool_use",
  "id": "mcptoolu_014Q35RayjACSWkSj4X2yov1",
  "name": "echo",
  "server_name": "example-mcp",
  "input": { "param1": "value1", "param2": "value2" }
}

MCP tool result block

json
{
  "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:

json
{
  "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:

json
{
  "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:

bash
  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"
bash
  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"
python
  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])
typescript
  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));
csharp
  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)));
go
  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)
java
  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());
  }
php
  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;
ruby
  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).inspect

With 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:

json
{
  "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.

  1. Run the inspector with the following command. You need Node.js installed on your machine.
bash
   npx @modelcontextprotocol/inspector
  1. In the sidebar on the left, for Transport type, select either SSE or Streamable HTTP.
  1. Enter the URL of the MCP server.
  1. In the right area, click Open Auth Settings after Need to configure authentication?.
  1. Click Quick OAuth Flow and authorize on the OAuth screen.
  1. Follow the steps in the OAuth Flow Progress section of the inspector and click Continue until you reach Authentication complete.
  1. Copy the access_token value.
  1. Paste it into the authorization_token field 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:

json
{
  "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_servers API 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:

bash
pip install "juglow[mcp]"

TypeScript

bash
npm install @juglow-ai/sdk @modelcontextprotocol/sdk

C#

The helpers live in the separate Juglow.Mcp package; the MCP client itself comes from the official ModelContextProtocol package:

bash
dotnet add package Juglow.Mcp
dotnet add package ModelContextProtocol

Go

The helpers live in the mcp subpackage of the Go SDK, which builds on the MCP Go SDK:

bash
go get github.com/juglows/juglow-sdk-go/mcp

Java

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:

kotlin
    implementation("com.juglow:juglow-java:2.65.0")
    implementation("com.juglow:juglow-java-mcp:2.65.0")

Maven

xml
<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:

bash
    composer require "juglow-ai/sdk" "guzzlehttp/guzzle:^7" "mcp/sdk"

The helpers use the official mcp gem:

bash
    bundle add juglow mcp

Available helpers

Import the helpers for your language:

python
  from juglow.lib.tools.mcp import (
      async_mcp_tool,
      mcp_message,
      mcp_resource_to_content,
      mcp_resource_to_file,
  )
typescript
  import {
    mcpTools,
    mcpMessages,
    mcpResourceToContent,
    mcpResourceToFile
  } from "@juglow-ai/sdk/helpers/beta/mcp";
csharp
  using Juglow.Helpers.Beta;
  using Juglow.Helpers.Beta.Mcp;
go
  import (
  	"github.com/juglows/juglow-sdk-go/mcp"
  )
java
  import com.juglow.helpers.McpBetaTool;
  import com.juglow.mcp.BetaMcp;
php
  use Juglow\Lib\Tools\BetaMcp;
ruby
  require "juglow"

  # The helpers are exposed on the Juglow::Mcp module

Helper names and exact signatures follow each language's conventions; this table shows the TypeScript forms:

HelperDescription
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:

python
  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())
typescript
  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);
csharp
  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);
go
  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())
  }
java
  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);
      }
  }
php
  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";
ruby
  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_message

Use MCP prompts

Convert MCP prompt messages into Haijun API message format:

python
  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)
typescript
  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);
csharp
  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);
go
  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())
java
  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);
php
  $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";
ruby
  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 response

Use MCP resources

Convert MCP resources into content blocks to include in messages, or into file objects for upload:

python
  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)
typescript
  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);
csharp
  // 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);
go
  // 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)
java
  // 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());
php
  // 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";
ruby
  # 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.id

Error 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

  1. New beta header: Change from mcp-client-2025-04-04 to mcp-client-2025-11-20
  1. Tool configuration moved: Tool configuration now lives in the tools array as MCPToolset objects, not in the MCP server definition
  1. More flexible configuration: New pattern supports allowlisting, denylisting, and per-tool configuration

Migration steps

Before (deprecated):

json
{
  "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):

json
{
  "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 patternNew pattern
No tool_configuration (all tools enabled)MCPToolset with no default_config or configs
tool_configuration.enabled: falseMCPToolset 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-20 using the preceding migration guide.

The previous version of the MCP connector included tool configuration directly in the MCP server definition:

json
{
  "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

PropertyTypeDescription
tool_configurationobjectDeprecated: Use MCPToolset in the tools array instead
tool_configuration.enabledbooleanDeprecated: Use default_config.enabled in MCPToolset
tool_configuration.allowed_toolsarrayDeprecated: Use allowlist pattern with configs in MCPToolset
On this page
Key featuresWhen Haijun uses MCP toolsLimitationsUsing the MCP connector in the Messages APIBasic exampleMCP server configurationField descriptionsMCP toolset configurationBasic structureField descriptionsTool configuration optionsConfiguration mergingCommon configuration patternsEnable all tools with default configurationAllowlist: enable only specific toolsDenylist: disable specific toolsMixed: allowlist with per-tool configurationValidation rulesResponse content typesMCP tool use blockMCP tool result blockPin an MCP server's tool list (beta)Multiple MCP serversAuthenticationObtaining an access token for testingUsing the access tokenClient-side MCP helpersInstallationAvailable helpersUse MCP toolsUse MCP promptsUse MCP resourcesError handlingBatch requestsData retentionMigration guideKey changesMigration stepsCommon migration patternsDeprecated version: mcp-client-2025-04-04Deprecated field descriptions