MCP Server Integration
Building on Examples 01–04, this example demonstrates connecting a Dragon agent to remote Model Context Protocol (MCP) servers. The agent can discover and invoke tools exposed by any MCP-compatible server (e.g., data stores, compute clusters, external APIs) alongside locally registered tools. Tool calls are automatically routed based on a namespace prefix.
Prerequisites: Read 04_memory.py first.
What you’ll learn:
How to connect to one or more MCP servers via HTTP/SSE
How to configure authentication tokens and timeouts
How tool names are scoped (local tools vs. MCP tools via
alias__tool_name)How the
ToolDispatcherroutes calls to the correct serverHow to mix local and remote tools in the same agent
Architecture:
The agent’s ToolDispatcher maintains connections to multiple MCP servers
and routes tool calls transparently: calls without a prefix go to local tools;
calls like datastore__query route to the MCP server aliased datastore.
Main Code
Below is the complete example:
1"""05 — MCP Server Integration.
2
3**Prerequisites:** Read ``02_multi_agent_dag.py`` first.
4
5**What's new:**
6
7* **MCPServerConfig** — connect an agent to a remote MCP tool server
8* **Scoped tool names** — MCP tools appear as ``alias__tool_name``
9 (e.g. ``jupyter__create_notebook``) to avoid collisions with local tools
10* **Mixed local + MCP tools** — the reporter agent has both local tools
11 (``format_results_table``) and remote MCP tools (Jupyter operations)
12* **Auto-discovery** — MCP tool schemas are fetched at connect time and
13 merged into the LLM's tool list
14* **Token from file** — bearer token read from ``token.txt``, never on CLI
15* **Graceful fallback** — if no MCP URL is provided, reporter uses
16 local tools only
17
18Architecture::
19
20 planner_agent ──► runner_agent ──► analyzer_agent ──┐
21 │ │ ├──► reporter_agent
22 └────────────────┴─────────────────────────────┘ │
23 MCP: jupyter__*
24
25Usage::
26
27 # Without MCP (local tools only):
28 dragon 05_mcp_tools.py
29
30 # With MCP (Jupyter notebook creation):
31 echo '<your-token>' > token.txt
32 dragon 05_mcp_tools.py <mcp_server_url>
33"""
34
35import asyncio
36import os
37import sys
38from typing import Any
39
40import dragon
41import multiprocessing as mp
42
43from dragon.ai.agent.core import create_sub_agent
44from dragon.ai.agent.config import (
45 AgentConfig,
46 MCPServerConfig, # ← NEW
47 OrchestratorConfig,
48 Pipeline,
49 PipelineNode,
50)
51from dragon import Policy, System, Batch
52from dragon.ai.agent.tools import ToolRegistry
53from dragon.ai.agent.orchestrator import DAGOrchestrator
54from dragon.native.event import Event
55from dragon.native.machine import Node
56from dragon.native.process import Process
57from dragon.native.queue import Queue
58
59from dragon.ai.inference import (
60 BatchingConfig,
61 HardwareConfig,
62 InferenceConfig,
63 ModelConfig,
64 Inference,
65)
66
67from tools import (
68 propose_experiment,
69 launch_experiment,
70 check_progress,
71 collect_results,
72 analyze_convergence,
73 format_results_table,
74)
75from tools.runner import cleanup_experiment_state
76
77
78# ===========================================================================
79# User-configurable constants
80# ===========================================================================
81
82MODEL_NAME = "/path/to/your/model"
83HF_TOKEN = ""
84
85
86# ===========================================================================
87# Inference Pipeline Configuration
88# ===========================================================================
89
90INFERENCE_CONFIG = InferenceConfig(
91 model=ModelConfig(
92 model_name=MODEL_NAME,
93 hf_token=HF_TOKEN,
94 tp_size=2,
95 max_tokens=8192,
96 max_model_len=32768,
97 ),
98 hardware=HardwareConfig(
99 num_nodes=1,
100 num_gpus=2,
101 num_inf_workers_per_cpu=1,
102 ),
103 batching=BatchingConfig(
104 batch_wait_seconds=0.1,
105 max_batch_size=32,
106 ),
107)
108
109
110# ===========================================================================
111# Tool registries
112# ===========================================================================
113
114planner_registry = ToolRegistry()
115planner_registry.register(propose_experiment)
116
117runner_registry = ToolRegistry()
118runner_registry.register(launch_experiment)
119runner_registry.register(check_progress)
120runner_registry.register(collect_results)
121
122analyzer_registry = ToolRegistry()
123analyzer_registry.register(analyze_convergence)
124
125reporter_registry = ToolRegistry()
126reporter_registry.register(format_results_table)
127
128
129# ===========================================================================
130# DAG pipeline
131# ===========================================================================
132
133pipeline = Pipeline(nodes=[
134 PipelineNode(
135 agent_id="planner_agent",
136 task_description=(
137 "You are a scientific experiment planner. The user wants to study "
138 "Monte Carlo convergence for estimating π.\n\n"
139 "Propose an experiment plan by calling propose_experiment with:\n"
140 " - description, sample_sizes, convergence_target, methodology\n\n"
141 "Report the approved plan verbatim as your final answer."
142 ),
143 depends_on=[],
144 ),
145 PipelineNode(
146 agent_id="runner_agent",
147 task_description=(
148 "You manage parallel Monte Carlo simulations on an HPC cluster.\n\n"
149 "Tools:\n"
150 " 1. launch_experiment(sample_sizes, seeds) — launches ALL "
151 "simulations in parallel.\n"
152 " 2. check_progress() — shows done/running/pending status.\n"
153 " 3. collect_results() — call ONLY when all_done=true.\n\n"
154 "STRICT workflow:\n"
155 " 1. Call launch_experiment.\n"
156 " 2. Call check_progress until all_done=true.\n"
157 " 3. Call collect_results — REQUIRED before final answer.\n"
158 " 4. Report collect_results output verbatim."
159 ),
160 depends_on=["planner_agent"],
161 ),
162 PipelineNode(
163 agent_id="analyzer_agent",
164 task_description=(
165 "Call analyze_convergence with a list of dicts (keys: "
166 "'n_samples', 'absolute_error'). Report all metrics verbatim."
167 ),
168 depends_on=["runner_agent"],
169 ),
170 PipelineNode(
171 agent_id="reporter_agent",
172 task_description=(
173 "Write a structured report with the experiment results.\n\n"
174 "STRICT workflow — follow every step IN ORDER:\n\n"
175 " STEP 1. Call format_results_table to get a Markdown table.\n\n"
176 " STEP 2. Check your available tools. If you have tools that\n"
177 " can manage Jupyter kernels and notebook files, do\n"
178 " BOTH sub-steps:\n"
179 " a. Create a Jupyter kernel.\n"
180 " b. Create a notebook file named\n"
181 " 'monte_carlo_convergence_report' with the\n"
182 " COMPLETE report as code_content.\n"
183 " If no such tools are available, skip Step 2.\n\n"
184 " STEP 3. Give your final answer with the SAME complete\n"
185 " report text. COPY-PASTE the actual table.\n\n"
186 "Never invent data — use only data from upstream agents."
187 ),
188 depends_on=["planner_agent", "runner_agent", "analyzer_agent"],
189 ),
190])
191
192
193# ===========================================================================
194# Helpers
195# ===========================================================================
196
197def _make_agent_kwargs(agent_id, name, role, registry, inference_queue,
198 mcp_servers=None,
199 max_tool_call_iterations=20):
200 """Build kwargs for create_sub_agent().
201
202 NEW in this example: ``mcp_servers`` — list of MCPServerConfig objects.
203 The agent connects to each MCP server in its own process at startup.
204 """
205 return {
206 "config": AgentConfig(
207 agent_id=agent_id, name=name, role=role,
208 inference_queue=inference_queue,
209 max_tool_call_iterations=max_tool_call_iterations,
210 ),
211 "tool_registry": registry,
212 "mcp_servers": mcp_servers, # ← NEW: passed to create_sub_agent
213 "shutdown_event": Event(),
214 "reply_queue": Queue(),
215 }
216
217
218def _start_agents(specs, policies):
219 procs = []
220 for spec, policy in zip(specs, policies):
221 p = Process(target=create_sub_agent, kwargs=spec, policy=policy)
222 p.start()
223 procs.append(p)
224 queues = {}
225 for spec in specs:
226 aid = spec["config"].agent_id
227 queues[aid] = spec["reply_queue"].get()
228 print(f"[startup] Agent '{aid}' ready.", flush=True)
229 return procs, queues
230
231
232# ===========================================================================
233# Main
234# ===========================================================================
235
236async def main():
237 input_queue = Queue()
238
239 print("[startup] Initializing inference pipeline...", flush=True)
240
241 inference_pipeline = None
242 try:
243 inference_pipeline = Inference(INFERENCE_CONFIG, input_queue)
244 inference_pipeline.initialize()
245 except Exception as exc:
246 import traceback
247 print(f"\n[FATAL] Inference pipeline failed to initialize: {exc}", flush=True)
248 traceback.print_exc()
249 if inference_pipeline is not None:
250 inference_pipeline.destroy()
251 return
252 print("[startup] Inference pipeline ready.\n", flush=True)
253
254 my_alloc = System()
255 node_list = my_alloc.nodes
256 compute_host = (
257 Node(node_list[1]).hostname if len(node_list) > 1
258 else Node(node_list[0]).hostname
259 )
260 compute_policy = Policy(
261 placement=Policy.Placement.HOST_NAME,
262 host_name=compute_host,
263 )
264
265 # -------------------------------------------------------------------
266 # ← NEW: MCP server setup for the reporter agent
267 #
268 # Pass the MCP URL as a command-line argument:
269 # dragon 05_mcp_tools.py <mcp_url>
270 #
271 # The bearer token is read from token.txt in CWD.
272 # -------------------------------------------------------------------
273 mcp_url = sys.argv[1] if len(sys.argv) >= 2 else None
274
275 TOKEN_FILE = os.path.join(os.getcwd(), "token.txt")
276 mcp_token = None
277 if mcp_url:
278 try:
279 with open(TOKEN_FILE, "rb") as f:
280 raw = f.read()
281 mcp_token = raw.decode("utf-8").strip().strip("\ufeff")
282 print(f"[startup] MCP token loaded from {TOKEN_FILE}", flush=True)
283 except FileNotFoundError:
284 print(
285 f"[startup] WARNING: {TOKEN_FILE} not found — "
286 "MCP connection will have no auth token.",
287 flush=True,
288 )
289
290 reporter_mcp_servers = None
291 if mcp_url:
292 reporter_mcp_servers = [
293 MCPServerConfig(url=mcp_url, alias="jupyter", token=mcp_token),
294 ]
295 print(f"[startup] Reporter will connect to MCP: {mcp_url}", flush=True)
296 else:
297 print(
298 "[startup] No MCP URL provided — reporter uses local tools only.\n"
299 " To enable MCP, run:\n"
300 " echo '<token>' > token.txt\n"
301 " dragon 05_mcp_tools.py <mcp_url>",
302 flush=True,
303 )
304
305 procs, agent_specs = [], []
306 try:
307 agent_specs = [
308 _make_agent_kwargs(
309 "planner_agent", "Experiment Planner",
310 "You are an experiment planner for Monte Carlo convergence "
311 "studies on an HPC cluster.",
312 planner_registry, input_queue,
313 ),
314 _make_agent_kwargs(
315 "runner_agent", "Parallel Simulation Runner",
316 "You manage parallel Monte Carlo simulations.\n"
317 "You MUST call all three tools in order: "
318 "launch_experiment → check_progress → collect_results.\n"
319 "NEVER give a final answer without calling collect_results first.",
320 runner_registry, input_queue,
321 max_tool_call_iterations=60,
322 ),
323 _make_agent_kwargs(
324 "analyzer_agent", "Convergence Analyzer",
325 "Analyse convergence. Call analyze_convergence with "
326 "n_samples + absolute_error dicts. Report verbatim.",
327 analyzer_registry, input_queue,
328 ),
329 _make_agent_kwargs(
330 "reporter_agent", "Report Writer",
331 "Write a structured report with Markdown tables. "
332 "If you have Jupyter tools, also create a notebook. "
333 "Always include tool output verbatim.",
334 reporter_registry, input_queue,
335 # ← NEW: attach MCP servers to this agent
336 mcp_servers=reporter_mcp_servers,
337 ),
338 ]
339 policies = [None, compute_policy, None, None]
340
341 procs, queues = _start_agents(agent_specs, policies)
342 for spec in agent_specs:
343 spec["config"].input_queue = queues[spec["config"].agent_id]
344
345 orchestrator = DAGOrchestrator(
346 config=OrchestratorConfig(
347 agents=[s["config"] for s in agent_specs],
348 poll_interval=0.5,
349 poll_timeout=14400.0,
350 ),
351 pipeline=pipeline,
352 )
353
354 user_input = (
355 "Design and run a Monte Carlo convergence experiment for π. "
356 "Monitor progress and report which nodes ran what."
357 + (
358 " Save the final report to a Jupyter notebook on the cluster."
359 if mcp_url else ""
360 )
361 )
362
363 batch = Batch()
364 try:
365 print("=" * 60, flush=True)
366 print("Dragon AI — 05 MCP Tools Integration", flush=True)
367 print("=" * 60, flush=True)
368 print(f"Request: {user_input}\n", flush=True)
369
370 result = orchestrator.run(
371 user_input=user_input,
372 batch=batch,
373 )
374
375 print("\n" + "=" * 60, flush=True)
376 print("FINAL RESULT", flush=True)
377 print("=" * 60, flush=True)
378 print(result, flush=True)
379
380 except Exception as exc:
381 import traceback
382 print(f"\n[error] Pipeline failed: {exc}", flush=True)
383 traceback.print_exc()
384 finally:
385 orchestrator.destroy()
386 batch.join()
387
388 except Exception as exc:
389 import traceback
390 print(f"\n[error] Fatal: {exc}", flush=True)
391 traceback.print_exc()
392 finally:
393 cleanup_experiment_state()
394 for spec in agent_specs:
395 try:
396 spec["shutdown_event"].set()
397 except Exception:
398 pass
399 for p in procs:
400 try:
401 p.join()
402 except Exception:
403 pass
404 print("\n[teardown] All agents stopped.", flush=True)
405 try:
406 inference_pipeline.destroy()
407 except Exception:
408 pass
409 print("[teardown] Inference pipeline stopped.", flush=True)
410
411
412if __name__ == "__main__":
413 mp.set_start_method("dragon")
414 asyncio.run(main())
Key Concepts
MCP Server Configuration:
Define one or more MCP servers in MCPServerConfig:
mcp_servers = [
MCPServerConfig(
url="http://datastore-service:8080/sse",
alias="datastore",
token="my-secret-token",
timeout=10.0,
max_retries=3,
),
MCPServerConfig(
url="http://compute-cluster:9090/sse",
alias="compute",
),
]
Tool Naming and Routing:
Local tools: registered via
registry.register()or@registry.tool; accessible astool_nameMCP tools: auto-discovered from servers; accessible as
alias__tool_name(e.g.,datastore__query,compute__submit_job)
The ToolDispatcher parses the prefix and routes the call to the correct
server (or local registry).
Tool Discovery:
When the agent connects to an MCP server, it auto-discovers all tools exposed by that server. No manual schema registration needed.
Installation
See Example 01 (same dependencies). For MCP servers, install compatible
implementations (e.g., mcp-server-filesystem, mcp-server-postgres, etc.).
System Description
Tested on HPE Cray EX:
1 compute node with 1–2 GPUs
MCP servers can run on same or different nodes (TCP/HTTP connectivity required)
How to Run
Step 1: Start MCP servers (in separate terminals)
Example with filesystem MCP server:
python -m mcp_server_filesystem /data/experiments
Example with custom data service:
python my_datastore_mcp_server.py --port 8080
Step 2: Edit MCP URLs in the example
Open 05_mcp_tools.py and set MCPServerConfig URLs to match your servers.
Step 3: Run the agent
dragon 05_mcp_tools.py
Example output:
$ dragon 05_mcp_tools.py
Agent 'researcher' started
MCP server 'datastore' connected (6 tools available)
MCP server 'compute' connected (4 tools available)
Task: 'Find all experiments and analyze trends'
Calling: datastore__query(filter='last_week')
→ Result: 42 experiments found
Calling: compute__analyze(experiment_ids=[...])
→ Result: Trends calculated
Calling: summarize_results(trends=...)
→ Result: Final summary written to report.json
Agent completed
Next Steps
06 — Full Pipeline (all features combined: DAG + HITL + Memory + MCP + Tracing)