diff --git a/examples/github-mcp/git_mcp.py b/examples/github-mcp/git_mcp.py index f233e5a..426b4d0 100644 --- a/examples/github-mcp/git_mcp.py +++ b/examples/github-mcp/git_mcp.py @@ -43,7 +43,7 @@ """, }, ], - tools=tool_node, + tool_node=tool_node, trim_context=True, ) diff --git a/examples/github-mcp/mcp_file_download.py b/examples/github-mcp/mcp_file_download.py index da98404..bf55cb5 100644 --- a/examples/github-mcp/mcp_file_download.py +++ b/examples/github-mcp/mcp_file_download.py @@ -54,7 +54,7 @@ """, }, ], - tools=tool_node, + tool_node=tool_node, trim_context=True, ) diff --git a/examples/react-mcp/react-mcp.py b/examples/react-mcp/react-mcp.py index 146c6bc..5d16faa 100644 --- a/examples/react-mcp/react-mcp.py +++ b/examples/react-mcp/react-mcp.py @@ -42,7 +42,7 @@ """, }, ], - tools=tool_node, + tool_node=tool_node, trim_context=True, ) diff --git a/examples/xquik-mcp/README.md b/examples/xquik-mcp/README.md new file mode 100644 index 0000000..d144549 --- /dev/null +++ b/examples/xquik-mcp/README.md @@ -0,0 +1,18 @@ +# Xquik MCP Example + +This example connects Agentflow's `ToolNode` to the remote Xquik MCP server and +loads its tool schemas without changing any framework defaults. + +Install Agentflow's MCP extra, provide an API key through the environment, and +run the example: + +```bash +uv sync --extra mcp +export XQUIK_API_KEY="your-api-key" +uv run python examples/xquik-mcp/client.py +``` + +The script prints the available tool names. Use the same `ToolNode` as an +agent's `tool_node` when building a graph. See the +[Xquik MCP overview](https://docs.xquik.com/mcp/overview) for authentication, +tool behavior, and safety requirements. diff --git a/examples/xquik-mcp/client.py b/examples/xquik-mcp/client.py new file mode 100644 index 0000000..5d56996 --- /dev/null +++ b/examples/xquik-mcp/client.py @@ -0,0 +1,46 @@ +"""Discover Xquik tools through Agentflow's MCP adapter.""" + +import asyncio +import os + +from fastmcp import Client + +from agentflow.core import ToolNode + + +MCP_URL = "https://xquik.com/mcp" + + +def require_api_key() -> str: + """Read the Xquik API key without embedding it in source.""" + api_key = os.getenv("XQUIK_API_KEY") + if not api_key: + raise RuntimeError("Set XQUIK_API_KEY before running this example") + return api_key + + +def build_client(api_key: str) -> Client: + """Build a Streamable HTTP client for the remote Xquik MCP server.""" + return Client( + { + "mcpServers": { + "xquik": { + "url": MCP_URL, + "transport": "streamable-http", + "headers": {"Authorization": f"Bearer {api_key}"}, + } + } + } + ) + + +async def main() -> None: + """Load the remote tool schemas through Agentflow.""" + tool_node = ToolNode([], client=build_client(require_api_key())) + tools = await tool_node.all_tools() + names = sorted(tool["function"]["name"] for tool in tools) + print(f"Available Xquik tools: {', '.join(names)}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/examples/test_mcp_examples.py b/tests/examples/test_mcp_examples.py new file mode 100644 index 0000000..6ff8bef --- /dev/null +++ b/tests/examples/test_mcp_examples.py @@ -0,0 +1,31 @@ +"""Static checks for MCP examples that should not require live services.""" + +import ast +from pathlib import Path + + +MCP_AGENT_EXAMPLES = ( + "examples/github-mcp/git_mcp.py", + "examples/github-mcp/mcp_file_download.py", + "examples/react-mcp/react-mcp.py", +) +REPO_ROOT = Path(__file__).parents[2] + + +def test_mcp_examples_use_current_agent_tool_keyword() -> None: + """Keep MCP examples aligned with Agent's current public constructor.""" + for relative_path in MCP_AGENT_EXAMPLES: + tree = ast.parse((REPO_ROOT / relative_path).read_text(encoding="utf-8")) + agent_calls = [ + node + for node in ast.walk(tree) + if isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "Agent" + ] + + assert agent_calls, f"No Agent call found in {relative_path}" + for call in agent_calls: + keywords = {keyword.arg for keyword in call.keywords} + assert "tools" not in keywords, relative_path + assert "tool_node" in keywords, relative_path