How to connect MCP tools to Assistants#

python-icon Download Python Script

Python script/notebook for this guide.

MCP how-to script

Prerequisites

This guide assumes familiarity with:

Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs. You can use an MCP server to provide a consistent tool interface to your agents and flows, without having to create custom adapters for different APIs.

Tip

See the Oracle MCP Server Repository to explore examples of reference implementations of MCP servers for managing and interacting with Oracle products.

In this guide, you will learn how to:

  • Create a simple MCP Server (in a separate Python file)

  • Connect an Agent to an MCP Server (including how to export/load via Agent Spec, and run it)

  • Connect a Flow to an MCP Server (including export/load/run)

Important

This guide does not aim at explaining how to make secure MCP servers, but instead mainly aims at showing how to connect to one. You should ensure that your MCP server configurations are secure, and only connect to trusted external MCP servers.

Prerequisite: Setup a simple MCP Server#

First, let’s see how to create and start a simple MCP server exposing a couple of tools.

Note

You should copy the following server code and run it in a separate Python process.

from mcp.server.fastmcp import FastMCP

PAYSLIPS = [
    {
        "Amount": 7612,
        "Currency": "USD",
        "PeriodStartDate": "2025/05/15",
        "PeriodEndDate": "2025/06/15",
        "PaymentDate": "",
        "DocumentId": 2,
        "PersonId": 2,
    },
    {
        "Amount": 5000,
        "Currency": "CHF",
        "PeriodStartDate": "2024/05/01",
        "PeriodEndDate": "2024/06/01",
        "PaymentDate": "2024/05/15",
        "DocumentId": 1,
        "PersonId": 1,
    },
    {
        "Amount": 10000,
        "Currency": "EUR",
        "PeriodStartDate": "2025/06/15",
        "PeriodEndDate": "2025/10/15",
        "PaymentDate": "",
        "DocumentsId": 3,
        "PersonId": 3,
    },
]

def create_server(host: str, port: int):
    """Create and configure the MCP server"""
    server = FastMCP(
        name="Example MCP Server",
        instructions="A MCP Server.",
        host=host,
        port=port,
    )

    @server.tool(description="Return session details for the current user")
    def get_user_session():
        return {
            "PersonId": "1",
            "Username": "Bob.b",
            "DisplayName": "Bob B",
        }

    @server.tool(description="Return payslip details for a given PersonId")
    def get_payslips(PersonId: int):
        return [payslip for payslip in PAYSLIPS if payslip["PersonId"] == int(PersonId)]

    return server


def start_mcp_server() -> str:
    host: str = "localhost"
    port: int = 8080
    server = create_server(host=host, port=port)
    server.run(transport="sse")

    return f"http://{host}:{port}/sse"

# mcp_server_url = start_mcp_server() # <--- Move the code above to a separate file then uncomment

This MCP server exposes two example tools: get_user_session and get_payslips. Once started, it will be available at (by default): http://localhost:8080/sse.

Note

When choosing a transport for MCP:

  • Use Stdio when launching and communicating with an MCP server as a local subprocess on the same machine as the client.

  • Use Streamable HTTP when connecting to a remote MCP server.

For more information, visit https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio

Connecting an Agent to the MCP Server#

You can now connect an agent to this running MCP server.

Add imports and configure an LLM#

Start by importing the necessary packages for this guide:

from wayflowcore.executors.executionstatus import FinishedStatus, UserMessageRequestStatus
from wayflowcore.agent import Agent
from wayflowcore.mcp import MCPTool, MCPToolBox, SSETransport, enable_mcp_without_auth
from wayflowcore.flow import Flow
from wayflowcore.steps import ToolExecutionStep

mcp_server_url = f"http://localhost:8080/sse" # change to your own URL
# We will see below how to connect a specific tool to an assistant, e.g.
MCP_TOOL_NAME = "get_user_session"
# And see how to build an agent that can answer questions, e.g.
USER_QUERY = "What was the payment date of the last payslip for the current user?"

WayFlow supports several LLM API providers. Select an LLM from the options below:

from wayflowcore.models import OCIGenAIModel

if __name__ == "__main__":

    llm = OCIGenAIModel(
        model_id="provider.model-id",
        service_endpoint="https://url-to-service-endpoint.com",
        compartment_id="compartment-id",
        auth_type="API_KEY",
    )

Build the Agent#

Agents can connect to MCP tools by either using a MCPToolBox or a MCPTool. Here you will use the toolbox (see the section on Flows to see how to use the MCPTool).

enable_mcp_without_auth() # <--- See https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#security-considerations
mcp_client = SSETransport(url=mcp_server_url)
mcp_toolbox = MCPToolBox(client_transport=mcp_client)

assistant = Agent(
    llm=llm,
    tools=[mcp_toolbox]
)

Specify the transport to use to handle the connection to the server and create the toolbox. You can then equip an agent with the toolbox similarly to tools.

Note

enable_mcp_without_auth() disables authorization for local/testing only—do not use in production.

Running the Agent#

You can now run the agent in a simple conversation:

# With a linear conversation
conversation = assistant.start_conversation()

conversation.append_user_message(USER_QUERY)
status = conversation.execute()
if isinstance(status, UserMessageRequestStatus):
    assistant_reply = conversation.get_last_message()
    print(f"---\nAssistant >>> {assistant_reply.content}\n---")
else:
    print(f"Invalid execution status, expected UserMessageRequestStatus, received {type(status)}")

# then continue the conversation

Alternatively, run the agent interactively in a command-line loop:

def run_agent_in_command_line(assistant: Agent):
    inputs = {}
    conversation = assistant.start_conversation(inputs)

    while True:
        status = conversation.execute()
        if isinstance(status, FinishedStatus):
            break
        assistant_reply = conversation.get_last_message()
        if assistant_reply is not None:
            print("\nAssistant >>>", assistant_reply.content)
        user_input = input("\nUser >>> ")
        conversation.append_user_message(user_input)

# run_agent_in_command_line(assistant)
# ^ uncomment and execute

Connecting a Flow to the MCP Server#

You can also use MCP tools in a Flow by using the MCPTool in a ToolExecutionStep.

Build the Flow#

Create the flow using the MCP tool:

mcp_tool = MCPTool(
    name=MCP_TOOL_NAME,
    client_transport=mcp_client
)

assistant = Flow.from_steps([
    ToolExecutionStep(name="mcp_tool_step", tool=mcp_tool)
])

Here you specify the client transport as with the MCP ToolBox, as well as the name of the specific tool you want to use. Additionally, you can override the tool description (exposed by the MCP server) by specifying the description parameter.

Tip

Use the _validate_tool_exist_on_server parameter to validate whether the tool is available or not at instantiation time.

Running the Flow#

Execute the flow as follows:

inputs = {}
conversation = assistant.start_conversation(inputs=inputs)

status = conversation.execute()
if isinstance(status, FinishedStatus):
    flow_outputs = status.output_values
    print(f"---\nFlow outputs >>> {flow_outputs}\n---")
else:
    print(
        f"Invalid execution status, expected FinishedStatus, received {type(status)}"
    )

Exporting/Loading with Agent Spec#

You can export the flow configuration to Agent Spec YAML:

from wayflowcore.agentspec import AgentSpecExporter

serialized_assistant = AgentSpecExporter().to_json(assistant)

Here is what the Agent Spec representation will look like ↓

Click here to see the assistant configuration.
{
  "component_type": "Flow",
  "id": "c535a5db-e2bc-4f0b-896d-d4266237428d",
  "name": "flow_34eae46e__auto",
  "description": "",
  "metadata": {
    "__metadata_info__": {}
  },
  "inputs": [],
  "outputs": [
    {
      "type": "string",
      "title": "tool_output"
    }
  ],
  "start_node": {
    "$component_ref": "d8948188-ea98-4bdb-aaa4-9c29f600696b"
  },
  "nodes": [
    {
      "$component_ref": "50108849-6be7-48d8-b3d0-5bf78c12403c"
    },
    {
      "$component_ref": "d8948188-ea98-4bdb-aaa4-9c29f600696b"
    },
    {
      "$component_ref": "d84bc934-90bd-421a-8ce5-f7224008d0b5"
    }
  ],
  "control_flow_connections": [
    {
      "component_type": "ControlFlowEdge",
      "id": "36f9ecb6-04f8-4351-81c6-1aab04963b0a",
      "name": "__StartStep___to_mcp_tool_step_control_flow_edge",
      "description": null,
      "metadata": {
        "__metadata_info__": {}
      },
      "from_node": {
        "$component_ref": "d8948188-ea98-4bdb-aaa4-9c29f600696b"
      },
      "from_branch": null,
      "to_node": {
        "$component_ref": "50108849-6be7-48d8-b3d0-5bf78c12403c"
      }
    },
    {
      "component_type": "ControlFlowEdge",
      "id": "07883a38-48b0-43ad-aeeb-1a937786a1ff",
      "name": "mcp_tool_step_to_None End node_control_flow_edge",
      "description": null,
      "metadata": {},
      "from_node": {
        "$component_ref": "50108849-6be7-48d8-b3d0-5bf78c12403c"
      },
      "from_branch": null,
      "to_node": {
        "$component_ref": "d84bc934-90bd-421a-8ce5-f7224008d0b5"
      }
    }
  ],
  "data_flow_connections": [
    {
      "component_type": "DataFlowEdge",
      "id": "e4820094-c97f-4805-a0ac-8048a2fcaaeb",
      "name": "mcp_tool_step_tool_output_to_None End node_tool_output_data_flow_edge",
      "description": null,
      "metadata": {},
      "source_node": {
        "$component_ref": "50108849-6be7-48d8-b3d0-5bf78c12403c"
      },
      "source_output": "tool_output",
      "destination_node": {
        "$component_ref": "d84bc934-90bd-421a-8ce5-f7224008d0b5"
      },
      "destination_input": "tool_output"
    }
  ],
  "$referenced_components": {
    "d84bc934-90bd-421a-8ce5-f7224008d0b5": {
      "component_type": "EndNode",
      "id": "d84bc934-90bd-421a-8ce5-f7224008d0b5",
      "name": "None End node",
      "description": "End node representing all transitions to None in the WayFlow flow",
      "metadata": {},
      "inputs": [
        {
          "type": "string",
          "title": "tool_output"
        }
      ],
      "outputs": [
        {
          "type": "string",
          "title": "tool_output"
        }
      ],
      "branches": [],
      "branch_name": "next"
    },
    "50108849-6be7-48d8-b3d0-5bf78c12403c": {
      "component_type": "ExtendedToolNode",
      "id": "50108849-6be7-48d8-b3d0-5bf78c12403c",
      "name": "mcp_tool_step",
      "description": "",
      "metadata": {
        "__metadata_info__": {}
      },
      "inputs": [],
      "outputs": [
        {
          "type": "string",
          "title": "tool_output"
        }
      ],
      "branches": [
        "next"
      ],
      "tool": {
        "component_type": "PluginMCPTool",
        "id": "d7346aa2-3276-49f7-81a2-7b1fe13dbb7d",
        "name": "generate_random_string",
        "description": "Tool to return a random string",
        "metadata": {
          "__metadata_info__": {}
        },
        "inputs": [],
        "outputs": [
          {
            "type": "string",
            "title": "tool_output"
          }
        ],
        "client_transport": {
          "component_type": "PluginSSETransport",
          "id": "0ba05bef-c297-4bd8-ac37-738838218cf4",
          "name": "mcp_client_transport",
          "description": null,
          "metadata": {},
          "url": "http://localhost:8001/sse",
          "headers": null,
          "timeout": 5.0,
          "sse_read_timeout": 300.0,
          "component_plugin_name": "MCPPlugin",
          "component_plugin_version": "25.4.0.dev0"
        },
        "component_plugin_name": "MCPPlugin",
        "component_plugin_version": "25.4.0.dev0"
      },
      "input_mapping": {},
      "output_mapping": {},
      "raise_exceptions": false,
      "component_plugin_name": "NodesPlugin",
      "component_plugin_version": "25.4.0.dev0"
    },
    "d8948188-ea98-4bdb-aaa4-9c29f600696b": {
      "component_type": "StartNode",
      "id": "d8948188-ea98-4bdb-aaa4-9c29f600696b",
      "name": "__StartStep__",
      "description": "",
      "metadata": {
        "__metadata_info__": {}
      },
      "inputs": [],
      "outputs": [],
      "branches": [
        "next"
      ]
    }
  },
  "agentspec_version": "25.4.1"
}

You can then load the configuration back to an assistant using the AgentSpecLoader.

from wayflowcore.agentspec import AgentSpecLoader

assistant: Flow = AgentSpecLoader().load_json(serialized_assistant)

Note

This guide uses the following extension/plugin Agent Spec components:

  • PluginMCPTool

  • PluginSSETransport

  • ExtendedToolNode

See the list of available Agent Spec extension/plugin components in the API Reference

Next Steps#

Having learned how to integrate MCP servers in WayFlow, you may now proceed to:

Full code#

Click on the card at the top of this page to download the full code for this guide or copy the code below.

  1# Copyright © 2025 Oracle and/or its affiliates.
  2#
  3# This software is under the Universal Permissive License
  4# %%[markdown]
  5# WayFlow Code Example - How to connect MCP tools to Assistants
  6# -------------------------------------------------------------
  7
  8# How to use:
  9# Create a new Python virtual environment and install the latest WayFlow version.
 10# ```bash
 11# python -m venv venv-wayflowcore
 12# source venv-wayflowcore/bin/activate
 13# pip install --upgrade pip
 14# pip install "wayflowcore==26.1" 
 15# ```
 16
 17# You can now run the script
 18# 1. As a Python file:
 19# ```bash
 20# python howto_mcp.py
 21# ```
 22# 2. As a Notebook (in VSCode):
 23# When viewing the file,
 24#  - press the keys Ctrl + Enter to run the selected cell
 25#  - or Shift + Enter to run the selected cell and move to the cell below# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl) or Apache License
 26# 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0), at your option.
 27
 28
 29
 30
 31# %%[markdown]
 32##Create a MCP Server
 33
 34# %%
 35from mcp.server.fastmcp import FastMCP
 36
 37PAYSLIPS = [
 38    {
 39        "Amount": 7612,
 40        "Currency": "USD",
 41        "PeriodStartDate": "2025/05/15",
 42        "PeriodEndDate": "2025/06/15",
 43        "PaymentDate": "",
 44        "DocumentId": 2,
 45        "PersonId": 2,
 46    },
 47    {
 48        "Amount": 5000,
 49        "Currency": "CHF",
 50        "PeriodStartDate": "2024/05/01",
 51        "PeriodEndDate": "2024/06/01",
 52        "PaymentDate": "2024/05/15",
 53        "DocumentId": 1,
 54        "PersonId": 1,
 55    },
 56    {
 57        "Amount": 10000,
 58        "Currency": "EUR",
 59        "PeriodStartDate": "2025/06/15",
 60        "PeriodEndDate": "2025/10/15",
 61        "PaymentDate": "",
 62        "DocumentsId": 3,
 63        "PersonId": 3,
 64    },
 65]
 66
 67def create_server(host: str, port: int):
 68    """Create and configure the MCP server"""
 69    server = FastMCP(
 70        name="Example MCP Server",
 71        instructions="A MCP Server.",
 72        host=host,
 73        port=port,
 74    )
 75
 76    @server.tool(description="Return session details for the current user")
 77    def get_user_session():
 78        return {
 79            "PersonId": "1",
 80            "Username": "Bob.b",
 81            "DisplayName": "Bob B",
 82        }
 83
 84    @server.tool(description="Return payslip details for a given PersonId")
 85    def get_payslips(PersonId: int):
 86        return [payslip for payslip in PAYSLIPS if payslip["PersonId"] == int(PersonId)]
 87
 88    return server
 89
 90
 91def start_mcp_server() -> str:
 92    host: str = "localhost"
 93    port: int = 8080
 94    server = create_server(host=host, port=port)
 95    server.run(transport="sse")
 96
 97    return f"http://{host}:{port}/sse"
 98
 99# mcp_server_url = start_mcp_server() # <--- Move the code above to a separate file then uncomment
100
101# %%[markdown]
102## Imports for this guide
103
104# %%
105from wayflowcore.executors.executionstatus import FinishedStatus, UserMessageRequestStatus
106from wayflowcore.agent import Agent
107from wayflowcore.mcp import MCPTool, MCPToolBox, SSETransport, enable_mcp_without_auth
108from wayflowcore.flow import Flow
109from wayflowcore.steps import ToolExecutionStep
110
111mcp_server_url = f"http://localhost:8080/sse" # change to your own URL
112# We will see below how to connect a specific tool to an assistant, e.g.
113MCP_TOOL_NAME = "get_user_session"
114# And see how to build an agent that can answer questions, e.g.
115USER_QUERY = "What was the payment date of the last payslip for the current user?"
116
117# %%[markdown]
118## Configure your LLM
119
120# %%
121from wayflowcore.models import VllmModel
122llm = VllmModel(
123    model_id="LLAMA_MODEL_ID",
124    host_port="LLAMA_API_URL",
125)
126
127
128# %%[markdown]
129## Connecting an agent to the MCP server
130
131# %%
132enable_mcp_without_auth() # <--- See https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#security-considerations
133mcp_client = SSETransport(url=mcp_server_url)
134mcp_toolbox = MCPToolBox(client_transport=mcp_client)
135
136assistant = Agent(
137    llm=llm,
138    tools=[mcp_toolbox]
139)
140
141
142# %%[markdown]
143## Running the agent
144
145# %%
146# With a linear conversation
147conversation = assistant.start_conversation()
148
149conversation.append_user_message(USER_QUERY)
150status = conversation.execute()
151if isinstance(status, UserMessageRequestStatus):
152    assistant_reply = conversation.get_last_message()
153    print(f"---\nAssistant >>> {assistant_reply.content}\n---")
154else:
155    print(f"Invalid execution status, expected UserMessageRequestStatus, received {type(status)}")
156
157# then continue the conversation
158
159# %%[markdown]
160## Running with an execution loop
161
162# %%
163def run_agent_in_command_line(assistant: Agent):
164    inputs = {}
165    conversation = assistant.start_conversation(inputs)
166
167    while True:
168        status = conversation.execute()
169        if isinstance(status, FinishedStatus):
170            break
171        assistant_reply = conversation.get_last_message()
172        if assistant_reply is not None:
173            print("\nAssistant >>>", assistant_reply.content)
174        user_input = input("\nUser >>> ")
175        conversation.append_user_message(user_input)
176
177# run_agent_in_command_line(assistant)
178# ^ uncomment and execute
179
180# %%[markdown]
181## Connecting a flow to the MCP server
182
183# %%
184mcp_tool = MCPTool(
185    name=MCP_TOOL_NAME,
186    client_transport=mcp_client
187)
188
189assistant = Flow.from_steps([
190    ToolExecutionStep(name="mcp_tool_step", tool=mcp_tool)
191])
192
193# %%[markdown]
194## Running the flow
195
196# %%
197inputs = {}
198conversation = assistant.start_conversation(inputs=inputs)
199
200status = conversation.execute()
201if isinstance(status, FinishedStatus):
202    flow_outputs = status.output_values
203    print(f"---\nFlow outputs >>> {flow_outputs}\n---")
204else:
205    print(
206        f"Invalid execution status, expected FinishedStatus, received {type(status)}"
207    )
208
209# %%[markdown]
210## Export config to Agent Spec
211
212# %%
213from wayflowcore.agentspec import AgentSpecExporter
214
215serialized_assistant = AgentSpecExporter().to_json(assistant)
216
217# %%[markdown]
218## Load Agent Spec config
219
220# %%
221from wayflowcore.agentspec import AgentSpecLoader
222
223assistant: Flow = AgentSpecLoader().load_json(serialized_assistant)