From 8bb6101e42731a369956a935270217b8960acd14 Mon Sep 17 00:00:00 2001 From: CryptoCultCurt Date: Wed, 2 Apr 2025 17:57:59 -0500 Subject: [PATCH 1/3] feat: Bump openai-agents to v0.0.7 and add MCP Server support --- .../openai-agents-sdk/changelog.d/123.feature | 10 +++ .../openai-agents-sdk/pyproject.toml | 2 +- .../openai-agents-sdk/uv.lock | 63 +++++++++++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 python/framework-extensions/openai-agents-sdk/changelog.d/123.feature diff --git a/python/framework-extensions/openai-agents-sdk/changelog.d/123.feature b/python/framework-extensions/openai-agents-sdk/changelog.d/123.feature new file mode 100644 index 000000000..f90da1dca --- /dev/null +++ b/python/framework-extensions/openai-agents-sdk/changelog.d/123.feature @@ -0,0 +1,10 @@ +Bump openai-agents to v0.0.7 +Added support for MCP Servers in OpenAI agents. Users can now integrate MCP servers into their agents with a simple configuration: + +```python +agent = Agent( + name="Assistant", + instructions="Use the tools to answer any questions.", + mcp_servers=[mcp_server], +) +``` \ No newline at end of file diff --git a/python/framework-extensions/openai-agents-sdk/pyproject.toml b/python/framework-extensions/openai-agents-sdk/pyproject.toml index 12b75810a..0a0aae594 100644 --- a/python/framework-extensions/openai-agents-sdk/pyproject.toml +++ b/python/framework-extensions/openai-agents-sdk/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "coinbase-agentkit>=0.3.0,<0.4", "python-dotenv>=1.0.1,<2", "pytest-asyncio>=0.25.3,<0.26", - "openai-agents>=0.0.6,<0.0.7", + "openai-agents>=0.0.7", ] [dependency-groups] diff --git a/python/framework-extensions/openai-agents-sdk/uv.lock b/python/framework-extensions/openai-agents-sdk/uv.lock index ddeb41cd2..f00778b4a 100644 --- a/python/framework-extensions/openai-agents-sdk/uv.lock +++ b/python/framework-extensions/openai-agents-sdk/uv.lock @@ -722,7 +722,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "coinbase-agentkit", specifier = ">=0.3.0,<0.4" }, - { name = "openai-agents", specifier = ">=0.0.6,<0.0.7" }, + { name = "openai-agents", specifier = ">=0.0.7" }, { name = "pytest-asyncio", specifier = ">=0.25.3,<0.26" }, { name = "python-dotenv", specifier = ">=1.0.1,<2" }, ] @@ -1312,6 +1312,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + [[package]] name = "idna" version = "3.10" @@ -1544,6 +1553,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] +[[package]] +name = "mcp" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 }, +] + [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -1751,19 +1779,20 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.0.6" +version = "0.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, + { name = "mcp" }, { name = "openai" }, { name = "pydantic" }, { name = "requests" }, { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/d4/a3c6763990b808ac5848ed0520c36f5e9b4651b540d6990b763c90d40e10/openai_agents-0.0.6.tar.gz", hash = "sha256:34b7c25f74d6f31e43a12ec7b2de64527714746dd15ca245bfc41dc8e92dbe2b", size = 671711 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/b7/49ce0b405bec3455fa4ec68cae88ec85081c55214f4e097552df465d7e24/openai_agents-0.0.7.tar.gz", hash = "sha256:470b3190b070b1a3790cbe62b200dd83d1339602592e90d341aa7bb1af85fa45", size = 770518 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/b9/f62eb52b859b4d0c9004b440e0283800ab2d54aabd6fcf881b3fdc40cff6/openai_agents-0.0.6-py3-none-any.whl", hash = "sha256:b5d6ff2909205ee75e2860114648432d66113afee2dadb199b09b292d892ac7e", size = 98897 }, + { url = "https://files.pythonhosted.org/packages/82/fc/88b29f820c5fa1ad063a46589d96d5aec578bbdec89b311791cd56d5b970/openai_agents-0.0.7-py3-none-any.whl", hash = "sha256:503bbf03ef8b9e47bdcfe65857f4b141daa2b57c1a1a6ed91ed42b76428ab02c", size = 106528 }, ] [[package]] @@ -2137,6 +2166,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, ] +[[package]] +name = "pydantic-settings" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, +] + [[package]] name = "pygls" version = "1.3.1" @@ -2822,6 +2864,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] +[[package]] +name = "sse-starlette" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, +] + [[package]] name = "starlette" version = "0.46.1" From 41faac50d0f298c5332bd50955792a2c4224a723 Mon Sep 17 00:00:00 2001 From: CryptoCultCurt Date: Wed, 2 Apr 2025 18:07:09 -0500 Subject: [PATCH 2/3] chore: rename changelog to match PR #647 --- .../openai-agents-sdk/changelog.d/{123.feature => 647.feature} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python/framework-extensions/openai-agents-sdk/changelog.d/{123.feature => 647.feature} (100%) diff --git a/python/framework-extensions/openai-agents-sdk/changelog.d/123.feature b/python/framework-extensions/openai-agents-sdk/changelog.d/647.feature similarity index 100% rename from python/framework-extensions/openai-agents-sdk/changelog.d/123.feature rename to python/framework-extensions/openai-agents-sdk/changelog.d/647.feature From f6cfe1123c874f788539200ff1ed211d114bfced Mon Sep 17 00:00:00 2001 From: CryptoCultCurt Date: Thu, 10 Apr 2025 11:02:38 -0500 Subject: [PATCH 3/3] Add example of a Fear & Greed MCP server --- ...el-context-protocol-smart-wallet-server.py | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 python/examples/openai-agents-sdk-smart-wallet-chatbot/openai-agents-model-context-protocol-smart-wallet-server.py diff --git a/python/examples/openai-agents-sdk-smart-wallet-chatbot/openai-agents-model-context-protocol-smart-wallet-server.py b/python/examples/openai-agents-sdk-smart-wallet-chatbot/openai-agents-model-context-protocol-smart-wallet-server.py new file mode 100644 index 000000000..0fff29144 --- /dev/null +++ b/python/examples/openai-agents-sdk-smart-wallet-chatbot/openai-agents-model-context-protocol-smart-wallet-server.py @@ -0,0 +1,195 @@ +import json +import os +import sys +import asyncio + +from agents import gen_trace_id, trace + +from coinbase_agentkit import ( + AgentKit, + AgentKitConfig, + SmartWalletProvider, + SmartWalletProviderConfig, + cdp_api_action_provider, + erc20_action_provider, + pyth_action_provider, + wallet_action_provider, + weth_action_provider, +) +from coinbase_agentkit_openai_agents_sdk import get_openai_agents_sdk_tools +from dotenv import load_dotenv +from eth_account.account import Account +from agents.agent import Agent +from agents.run import Runner +from agents.mcp import MCPServer, MCPServerStdio + +# Configure a file to persist the agent's CDP API Wallet Data. +wallet_data_file = "wallet_data.txt" +mcp_server_path = "/Users/curtfluegel/Documents/Development/crypto-feargreed-mcp/" + +load_dotenv() + + +async def initialize_agent(mcp_server: MCPServer): + """Initialize the agent with SmartWalletProvider.""" + network_id = os.getenv("NETWORK_ID", "base-sepolia") + wallet_data_file = f"wallet_data_{network_id.replace('-', '_')}.txt" + + # Load wallet data from JSON file + wallet_data = {"private_key": None, "smart_wallet_address": None} + if os.path.exists(wallet_data_file): + try: + with open(wallet_data_file) as f: + wallet_data = json.load(f) + except json.JSONDecodeError: + print(f"Warning: Invalid wallet data file format for {network_id}. Creating new wallet.") + + # Use private key from env if not in wallet data + private_key = wallet_data.get("private_key") or os.getenv("PRIVATE_KEY") + if not private_key: + acct = Account.create() + private_key = acct.key.hex() + + signer = Account.from_key(private_key) + + # Initialize Smart Wallet Provider + wallet_provider = SmartWalletProvider( + SmartWalletProviderConfig( + network_id=network_id, + signer=signer, + smart_wallet_address=wallet_data.get("smart_wallet_address"), + paymaster_url=None, # Sponsor transactions: https://docs.cdp.coinbase.com/paymaster/docs/welcome + ) + ) + + # Save both private key and smart wallet address + wallet_data = { + "private_key": private_key, + "smart_wallet_address": wallet_provider.get_address(), + } + with open(wallet_data_file, "w") as f: + json.dump(wallet_data, f, indent=2) + + agentkit = AgentKit( + AgentKitConfig( + wallet_provider=wallet_provider, + action_providers=[ + cdp_api_action_provider(), + erc20_action_provider(), + pyth_action_provider(), + wallet_action_provider(), + weth_action_provider(), + ], + ) + ) + + # use get_openai_agents_sdk_tools + tools = get_openai_agents_sdk_tools(agentkit) + + # Create Agent using the OpenAI Agents SDK + agent = Agent( + name="CDP Agent", + instructions=( + "You are a helpful agent that can interact onchain using the Coinbase Developer Platform AgentKit. " + "You are empowered to interact onchain using your tools. If you ever need funds, you can request " + "them from the faucet if you are on network ID 'base-sepolia'. If not, you can provide your wallet " + "details and request funds from the user. Before executing your first action, get the wallet details " + "to see what network you're on. If there is a 5XX (internal) HTTP error code, ask the user to try " + "again later. If someone asks you to do something you can't do with your currently available tools, " + "you must say so, and encourage them to implement it themselves using the CDP SDK + Agentkit, " + "recommend they go to docs.cdp.coinbase.com for more information. Be concise and helpful with your " + "responses. Refrain from restating your tools' descriptions unless it is explicitly requested." + "Unless asked otherwise, speak in English." + ), + tools=tools, + mcp_servers=[ + mcp_server + ], + ) + + return agent + + +# Autonomous Mode +async def run_autonomous_mode(agent, interval=10): + """Run the agent autonomously with specified intervals.""" + print("Starting autonomous mode...") + while True: + try: + thought = ( + "Be creative and do something interesting on the blockchain. " + "Choose an action or set of actions and execute it that highlights your abilities." + ) + + # Run agent in autonomous mode + output = await Runner.run(agent, thought) + print(output.final_output) + print("-------------------") + + # Wait before the next action + await asyncio.sleep(interval) + + except KeyboardInterrupt: + print("Goodbye Agent!") + sys.exit(0) + +# Chat Mode +async def run_chat_mode(agent): + """Run the agent interactively based on user input.""" + print("Starting chat mode... Type 'exit' to end.") + while True: + try: + user_input = input("\nPrompt: ") + if user_input.lower() == "exit": + break + + # Run agent with the user's input in chat mode + output = await Runner.run(agent, user_input) + print(output.final_output) + print("-------------------") + + except KeyboardInterrupt: + print("Goodbye Agent!") + sys.exit(0) + + +# Mode Selection +def choose_mode(): + """Choose whether to run in autonomous or chat mode based on user input.""" + while True: + print("\nAvailable modes:") + print("1. chat - Interactive chat mode") + print("2. auto - Autonomous action mode") + + choice = input("\nChoose a mode (enter number or name): ").lower().strip() + if choice in ["1", "chat"]: + return "chat" + elif choice in ["2", "auto"]: + return "auto" + print("Invalid choice. Please try again.") + + +async def main(): + try: + async with MCPServerStdio( + params={ + "command": "uv", + "args": ["--directory", mcp_server_path, "run", "main.py"] + }, + name="crypto-feargreed-mcp" + ) as server: + trace_id = gen_trace_id() + with trace(workflow_name="MCP Filesystem Example", trace_id=trace_id): + print(f"View trace: https://platform.openai.com/traces/{trace_id}\n") + # Initialize agent and run chat mode within the server context + agent = await initialize_agent(server) + mode = choose_mode() + if mode == "chat": + await run_chat_mode(agent=agent) + elif mode == "auto": + await run_autonomous_mode(agent=agent) + except Exception as e: + print(f"Error running server: {e}") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file