IBM watsonx.data remote Model Context Protocol (MCP) server
IBM watsonx.data remote MCP server is a Model Context Protocol (MCP) compliant service that seamlessly connects AI agents with document libraries in watsonx.data, enabling intelligent data retrieval and interaction. While you must manually set up and configure a local MCP server, a remote MCP server is hosted on IBM infrastructure and accessed over a network, making it easier to collaborate and reducing the need for manual setup and configuration.
Key features of watsonx.data remote MCP server:
- Dynamic discovery and registration: Automatically detects and registers document libraries as MCP tools.
- Natural language interface: Query document libraries using natural language and receive human-readable responses.
- Setup and Configuration: No setup or configuration is required, as the solution is already deployed on IBM infrastructure
- Framework agnostic integration: Plug directly into the preferred agentic frameworks with native MCP compatibility.
Prerequisites
Before you using the remote MCP server, make sure that the following prerequisites are met:
- You must have valid access credentials (bearer token) and certificate.
- Make sure that your AI agent framework supports the MCP protocol.
Remote MCP server functional overview
The remote MCP server serves as a bridge between AI agents and document libraries within the watsonx.data platform. It enables seamless discovery, interaction, and intelligent querying of document libraries using the Model Context Protocol (MCP).
- Document Library Registration: Automatically detects document libraries in your watsonx.data instance and registers each as an MCP tool.
- Agent Interaction: AI agents establish a connection with the MCP server using the MCP protocol. After the connection is established, they can discover and all registered document libraries.
- Query Processing: The MCP server receives natural language queries from agents, translates them into appropriate API calls to the relevant document library, and returns the results in a human-readable format.
- Metadata Handling: The MCP server provides custom metadata from document libraries to connected agents to enhance performance. If metadata is unavailable, the server uses fallback logic to retrieve it automatically.
Connect to the remote MCP Server
To connect to the remote MCP server, use the following endpoint:
https://<your-instance-url>/api/v1/mcp/
Replace <your-instance-url> with the URL of your specific watsonx.data instance. Agents use this endpoint to discover and interact with available document libraries.
Using custom metadata
The remote MCP server can pass custom metadata to connected AI agents. Custom metadata fields such as document_library_id, container_id, and container_type are defined as part of the tool configuration and are made available to agents when they
call the list tools method.
Agents can access metadata by:
-
Calling the
list toolsmethod. -
Iterating through each tool object.
-
Reading metadata fields:
x_document_library_id- document_library_idx_container_id- container_idx_container_type- container_type
Agents can also invoke tools without providing custom metadata. In such cases, the MCP server retrieves the necessary metadata, which is essential for accessing the retrieval service.
To fetch metadata from a tool object, use standard Python getattr calls. For example, you can fetch the document library ID, container ID, and container type using:
x_doc_lib_id = getattr(tool, "x_document_library_id", None)
x_container_id = getattr(tool, "x_container_id", None)
x_container_type = getattr(tool, "x_container_type", None)
After metadata becomes available, the agent invokes a tool by passing the metadata along with the arguments parameter to the call tool method of the remote MCP server. The agent includes the metadata as a new key named metadata
in the arguments dictionary. The MCP server extracts the metadata and uses it to route the request to the appropriate document library tool. The following exmaple demonstrates the call tool method.
arguments['metadata'] = metadata_value
# Create the tool call request payload
request_payload = {
"jsonrpc": "2.0",
"method": "tools/call",
"params": {"name": tool_name, "arguments": arguments},
"id": f"call-{tool_name}",
}
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
self.server_url,
json=request_payload,
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"authorization": auth_token,
"Mcp-Session-Id": sessionId,
},
)
Integrating with watsonx Orchestrate
Complete the following steps to integrate the remote MCP server with watsonx Orchestrate:
-
Install IBM watsonx Orchestrate Agent Development Kit (ADK) and complete the initial setup. For more information, refer Welcome to IBM watsonx Orchestrate Agent Development Kit.
-
Run the following command to create connections.
orchestrate connections add -a <CONN_NAME> orchestrate connections configure --app-id <CONN_NAME> --environment draft -t team -k bearer orchestrate connections set-credentials --app-id <CONN_NAME> --env draft --token <TOKEN> -
Run the following command to import toolkit to watsonx Orchestrate. For more information on toolkits, refer Managing Toolkits.
orchestrate toolkits import \ --kind mcp \ --name <TOOLKIT_NAME> \ --description "remote MCP server" \ --url "https://<URL>/api/v1/mcp/" \ --transport "streamable_http" \ --tools "*" \ --app-id " <CONN_NAME> "The following example demonstrates how to add tools as a toolkit in watsonx Orchestrate.
orchestrate toolkits import \ --kind mcp \ --name remote_mcp \ --description "test remote mcp" \ --url "https://cpd-cpd-instance.apps.perf1.ibm.prestodb.dev/api/v1/mcp/" \ --transport "streamable_http" \ --tools "*" \ --app-id " test_remote "
Integrating with other agentic framework
Complete the following steps to integrate the remote MCP server with LangChain and enable AI agents to invoke tools using custom metadata:
-
Run the following code to install dependencies.
!pip3 install mcp langgraph langchain_mcp_adapters langchain_ibm langchain -
Run the following code to add environment variables.
os.environ["WATSONX_API_KEY"]="<>" os.environ["WATSONX_PROJECT_ID"]="<>" os.environ["WATSONX_URL"]="<>" os.environ["MCP_ENDPOINT"]="<>"Note:- To obtain your
WATSONX_API_KEY, refer API keys. - To find your
WATSONX_PROJECT_ID, navigate to your project in the watsonx.ai web console, click theManagetab, and locate theProject IDunder theGeneral Informationsection. - For
WATSONX_URL, refer Introduction to IBM watsonx.ai Runtime and choose the correct instance URL based on your region.
- To obtain your
-
Run the following code to imprort dependencies.
from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client import os from langgraph.prebuilt import create_react_agent -
Run the following code to load tools from the remote MCP server.
authToken = "<>" headers = {"authorization" : authToken} mcp_endpoint = os.getenv("MCP_ENDPOINT") async with streamablehttp_client(url = mcp_endpoint, headers=headers) as (read, write, _): async with ClientSession(read, write) as session: # Initialize the connection await session.initialize() # Get tools tool_response = await session.list_tools() tools = tool_response.tools print(tools) print(len(tools))Note:To generate
authToken, refer Authentication. -
Run the following code to extract custom metadata from tools.
from typing import Any metadata : dict[str, dict[str,Any]] = {} for tool in tools: x_doc_lib_id = getattr(tool, "x_document_library_id", None) x_container_id = getattr(tool, "x_container_id", None) x_container_type = getattr(tool, "x_container_type", None) metadata[tool.name] = {'document_library_id':x_doc_lib_id, 'container_id' : x_container_id,'container_type':x_container_type} print(f"{tool.name} => {metadata[tool.name]}") -
Run the following code to set up ChatWatsonx llm.
from langchain_ibm import ChatWatsonx llm = ChatWatsonx( model_id="meta-llama/llama-3-3-70b-instruct", url = os.getenv("WATSONX_URL"), apikey = os.getenv("WATSONX_API_KEY"), project_id = os.getenv("WATSONX_PROJECT_ID"), temperature=0, ) -
Run the following code to define custom tool invocation function.
import httpx import json async def call_tool_with_metadata(tool_name: str, arguments: dict[str, Any],sessionId): response = "" metadata_value = metadata[tool_name] arguments['metadata'] = metadata_value if metadata_value: print(f"{tool_name} Metadata available: {metadata_value}") else: print("No metadata to send") if metadata_value: # Create the tool call request payload request_payload = { "jsonrpc": "2.0", "method": "tools/call", "params": {"name": tool_name, "arguments": arguments}, "id": f"call-{tool_name}", } try: # Use httpx directly to send the request with custom headers async with httpx.AsyncClient(timeout=120.0) as client: response = await client.post( url = mcp_endpoint, json=request_payload, headers={ "Content-Type": "application/json", "Accept": "application/json, text/event-stream", "authorization": authToken, "Mcp-Session-Id": sessionId, }, ) if response.status_code == 200: result = response.text print(f"Success 📥 Response: {json.dumps(result, indent=2)}\n") print("Generating response .... \n") return json.dumps(result, indent=2) else: print(f" ❌ Error: HTTP {response.status_code}") print(f" Response: {response.text}") return response.text except Exception as e: print(f" ❌ Request failed: {e}") finally: if response.status_code != 200: print(response) else: # Fallback to standard call_tool (won't include custom headers) print(" ⚠️ Using standard call_tool (no custom headers)") result = await session.call_tool(tool_name, arguments) print(f" 📥 Response: {result}") return result -
Run the following code to convert MCP tools to langchain tools.
from langchain.tools import StructuredTool def convert_mcp_tool_to_langchain_tool(mcp_tool, call_tool_fn, sessionId): """Convert MCP Tool -> LangChain StructuredTool with real execution logic.""" async def tool_func(query: str) -> str: arguments = {"query": query} # delegate the execution to your function result = await call_tool_fn(mcp_tool.name, arguments, sessionId) return result return StructuredTool.from_function( func=tool_func, name=mcp_tool.name, description=mcp_tool.description or "No description provided", metadata={ "x_document_library_id": mcp_tool.x_document_library_id, "x_container_id": mcp_tool.x_container_id, "x_container_type": mcp_tool.x_container_type, }, coroutine=tool_func ) -
Run the following code to invoke the agent.
from langchain.schema import AIMessage async with streamablehttp_client(url = mcp_endpoint, headers=headers) as (read, write, get_session_id): async with ClientSession(read, write) as session: # Initialize the connection await session.initialize() session_id = get_session_id() print(session_id) # Get tools tool_response = await session.list_tools() tools = tool_response.tools langchain_tools = [ convert_mcp_tool_to_langchain_tool( t, call_tool_with_metadata, session_id ) for t in tool_response.tools ] tools_dict = {} print(f"Below are the available {len(langchain_tools)} tools \n ") for index,tool in enumerate(langchain_tools): print(f"{index+1} {tool.name}\n") tools_dict[tool.name.lower()] = tool while True: target_tool = input("Enter the name of tool you want to query or type 'q' to quit \n") target_tool = target_tool.strip().rstrip(".").lower() if target_tool == 'q': break if tools_dict.get(target_tool) == None: print(f"Tool {target_tool} not available \n Please choose one from the available tools \n") continue print(f"Selected tool : {tools_dict.get(target_tool)}\n") agent = create_react_agent( model=llm, tools=[tools_dict.get(target_tool)], ) query = input("Enter the Query \n") result = await agent.ainvoke({"messages": [{"role": "user", "content": query}]}) ai_messages = [m for m in result["messages"] if isinstance(m, AIMessage)] final_answer = ai_messages[-1].content if final_answer: print(f"{final_answer}\n") else: print(f"{result}\n")
Learn more
Parent topic: Retrieval service