├── .github ├── release-drafter.yml └── workflows │ ├── checks.yml │ ├── create-tag.yml │ ├── main-checks.yml │ ├── pr-checks.yml │ ├── publish-pypi.yml │ └── release-drafter.yml ├── .gitignore ├── .goosehints ├── .pre-commit-config.yaml ├── .python-version ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── assets └── Mnemo_Logo.png ├── examples ├── basic │ ├── decorator │ │ ├── main.py │ │ ├── mnemo_mcp_agent.config.yaml │ │ └── requirements.txt │ ├── functions │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ ├── mnemo_mcp_agent_server │ │ ├── README.md │ │ ├── client.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── server.py │ ├── mnemo_mcp_basic_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ ├── mnemo_mcp_hello_world │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ └── requirements.txt │ ├── mnemo_mcp_model_selector │ │ ├── README.md │ │ ├── interactive.py │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ └── requirements.txt │ └── mnemo_mcp_server_aggregator │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ └── requirements.txt ├── mcp │ ├── mcp_root_test │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ ├── requirements.txt │ │ ├── root_test_server.py │ │ └── test_data │ │ │ ├── 01_Data_Processed.csv │ │ │ ├── plots │ │ │ ├── 3d_accelerometer_by_exercise.png │ │ │ ├── accelerometer_time_series.png │ │ │ ├── exercise_and_weight_distribution.png │ │ │ ├── exercise_by_day.png │ │ │ ├── exercise_by_participant.png │ │ │ ├── exercise_repetition_patterns.png │ │ │ ├── exercise_signatures_radar.png │ │ │ ├── gyroscope_time_series.png │ │ │ ├── participant_exercise_patterns.png │ │ │ ├── sensor_correlation_heatmap.png │ │ │ ├── sensor_distribution_by_category.png │ │ │ ├── sensor_patterns_by_category.png │ │ │ ├── sensor_patterns_by_exercise.png │ │ │ └── weight_category_comparison.png │ │ │ └── visualizations │ │ │ ├── 1_exercise_distribution.png │ │ │ ├── 2_sensor_patterns_by_exercise.png │ │ │ ├── 3_heavy_vs_medium_radar.png │ │ │ ├── 4_pca_clustering.png │ │ │ ├── 4b_pca_feature_importance.png │ │ │ ├── 5_time_series_patterns.png │ │ │ ├── 6_participant_distribution.png │ │ │ ├── 6b_participant_sensor_heatmap.png │ │ │ ├── 7_exercise_variability.png │ │ │ ├── 7b_sensor_distributions.png │ │ │ ├── 8_heavy_vs_medium_boxplots.png │ │ │ ├── 8b_heavy_medium_percent_diff.png │ │ │ └── key_insights.md │ ├── mcp_sse │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── server.py │ ├── mcp_sse_with_headers │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ └── mcp_websockets │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt ├── model_providers │ ├── mcp_basic_azure_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ └── mcp_agent.secrets.yaml.example │ ├── mcp_basic_bedrock_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ └── mcp_agent.secrets.yaml.example │ ├── mcp_basic_google_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ └── mcp_basic_ollama_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt ├── usecases │ ├── marimo_mcp_basic_agent │ │ ├── README.md │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── notebook.py │ ├── mcp_basic_slack_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ ├── mcp_browser_agent │ │ ├── README.md │ │ ├── browser_agent.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── pyproject.toml │ ├── mcp_github_to_slack_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ ├── mcp_playwright_agent │ │ ├── README.md │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── pyproject.toml │ ├── mcp_researcher │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ ├── streamlit_mcp_basic_agent │ │ ├── README.md │ │ ├── hello.py │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ ├── mcp_agent.secrets.yaml.example │ │ └── requirements.txt │ └── streamlit_mcp_rag_agent │ │ ├── README.md │ │ ├── agent_state.py │ │ ├── main.py │ │ ├── mcp_agent.config.yaml │ │ └── requirements.txt └── workflows │ ├── workflow_evaluator_optimizer │ ├── README.md │ ├── main.py │ ├── mcp_agent.config.yaml │ ├── mcp_agent.secrets.yaml.example │ └── requirements.txt │ ├── workflow_intent_classifier │ ├── README.md │ ├── main.py │ ├── mcp_agent.config.yaml │ ├── mcp_agent.secrets.yaml.example │ └── requirements.txt │ ├── workflow_orchestrator_worker │ ├── README.md │ ├── gpt-4ograded_report.md │ ├── graded_report.md │ ├── main.py │ ├── mcp_agent.config.yaml │ ├── mcp_agent.secrets.yaml.example │ ├── requirements.txt │ └── short_story.md │ ├── workflow_parallel │ ├── README.md │ ├── main.py │ ├── mcp_agent.config.yaml │ ├── mcp_agent.secrets.yaml.example │ └── requirements.txt │ ├── workflow_router │ ├── README.md │ ├── main.py │ ├── mcp_agent.config.yaml │ ├── mcp_agent.secrets.yaml.example │ └── requirements.txt │ └── workflow_swarm │ ├── README.md │ ├── main.py │ ├── mcp_agent.config.yaml │ ├── mcp_agent.secrets.yaml.example │ ├── policies │ ├── flight_cancellation_policy.md │ ├── flight_change_policy.md │ └── lost_baggage_policy.md │ └── requirements.txt ├── pyproject.toml ├── schema └── mcp-agent.config.schema.json ├── scripts ├── event_replay.py ├── event_summary.py ├── event_viewer.py ├── example.py ├── format.py ├── gen_schema.py ├── lint.py ├── promptify.py └── rich_progress_test.py ├── src └── mcp_agent │ ├── __init__.py │ ├── agents │ ├── __init__.py │ └── agent.py │ ├── app.py │ ├── cli │ ├── __init__.py │ ├── __main__.py │ ├── commands │ │ └── config.py │ ├── main.py │ └── terminal.py │ ├── config.py │ ├── console.py │ ├── context.py │ ├── context_dependent.py │ ├── core │ ├── decorator_app.py │ └── exceptions.py │ ├── data │ └── artificial_analysis_llm_benchmarks.json │ ├── eval │ └── __init__.py │ ├── event_progress.py │ ├── executor │ ├── __init__.py │ ├── decorator_registry.py │ ├── executor.py │ ├── task_registry.py │ ├── temporal.py │ ├── workflow.py │ └── workflow_signal.py │ ├── human_input │ ├── __init__.py │ ├── handler.py │ └── types.py │ ├── logging │ ├── __init__.py │ ├── events.py │ ├── json_serializer.py │ ├── listeners.py │ ├── logger.py │ ├── rich_progress.py │ ├── tracing.py │ └── transport.py │ ├── mcp │ ├── __init__.py │ ├── gen_client.py │ ├── mcp_activity.py │ ├── mcp_agent_client_session.py │ ├── mcp_agent_server.py │ ├── mcp_aggregator.py │ ├── mcp_connection_manager.py │ └── websocket.py │ ├── mcp_server_registry.py │ ├── progress_display.py │ ├── py.typed │ ├── telemetry │ ├── __init__.py │ └── usage_tracking.py │ └── workflows │ ├── __init__.py │ ├── embedding │ ├── __init__.py │ ├── embedding_base.py │ ├── embedding_cohere.py │ └── embedding_openai.py │ ├── evaluator_optimizer │ ├── __init__.py │ └── evaluator_optimizer.py │ ├── intent_classifier │ ├── __init__.py │ ├── intent_classifier_base.py │ ├── intent_classifier_embedding.py │ ├── intent_classifier_embedding_cohere.py │ ├── intent_classifier_embedding_openai.py │ ├── intent_classifier_llm.py │ ├── intent_classifier_llm_anthropic.py │ └── intent_classifier_llm_openai.py │ ├── llm │ ├── __init__.py │ ├── augmented_llm.py │ ├── augmented_llm_anthropic.py │ ├── augmented_llm_azure.py │ ├── augmented_llm_bedrock.py │ ├── augmented_llm_google.py │ ├── augmented_llm_ollama.py │ ├── augmented_llm_openai.py │ └── llm_selector.py │ ├── orchestrator │ ├── __init__.py │ ├── orchestrator.py │ ├── orchestrator_models.py │ └── orchestrator_prompts.py │ ├── parallel │ ├── __init__.py │ ├── fan_in.py │ ├── fan_out.py │ └── parallel_llm.py │ ├── router │ ├── __init__.py │ ├── router_base.py │ ├── router_embedding.py │ ├── router_embedding_cohere.py │ ├── router_embedding_openai.py │ ├── router_llm.py │ ├── router_llm_anthropic.py │ └── router_llm_openai.py │ └── swarm │ ├── __init__.py │ ├── swarm.py │ ├── swarm_anthropic.py │ └── swarm_openai.py └── tests ├── fixture ├── README.md ├── expected_output.txt └── mcp_basic_agent_20250131_205604.jsonl ├── test_event_progress.py ├── test_websocket.py └── workflows └── llm └── test_augmented_llm_anthropic.py /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "v$NEXT_PATCH_VERSION" 2 | tag-template: "v$NEXT_PATCH_VERSION" 3 | categories: 4 | - title: "🚀 Features" 5 | labels: 6 | - "feature" 7 | - "enhancement" 8 | - title: "🐛 Bug Fixes" 9 | labels: 10 | - "fix" 11 | - "bugfix" 12 | - "bug" 13 | - title: "🧰 Maintenance" 14 | label: "chore" 15 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)" 16 | template: | 17 | ## Changes 18 | $CHANGES 19 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Linting, formatting and other checks on codebase 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | format: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Install uv 13 | uses: astral-sh/setup-uv@v3 14 | with: 15 | enable-cache: true 16 | 17 | - name: "Set up Python" 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version-file: ".python-version" 21 | 22 | - name: Install the project 23 | run: uv sync --frozen --all-extras --dev 24 | 25 | - name: Run ruff format check 26 | run: uv run scripts/format.py 27 | 28 | lint: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Install uv 34 | uses: astral-sh/setup-uv@v3 35 | with: 36 | enable-cache: true 37 | 38 | - name: "Set up Python" 39 | uses: actions/setup-python@v5 40 | with: 41 | python-version-file: ".python-version" 42 | 43 | - name: Install the project 44 | run: uv sync --frozen --all-extras --dev 45 | 46 | - name: Run pyright 47 | run: uv run scripts/lint.py 48 | -------------------------------------------------------------------------------- /.github/workflows/create-tag.yml: -------------------------------------------------------------------------------- 1 | name: Create Version Tag from pyproject.toml 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "pyproject.toml" 9 | workflow_dispatch: # Enables manual runs 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | create-tag: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | 21 | - name: Install uv 22 | uses: astral-sh/setup-uv@v3 23 | with: 24 | enable-cache: true 25 | 26 | - name: "Set up Python" 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version-file: ".python-version" 30 | 31 | - name: Install dependencies 32 | run: pip install toml 33 | 34 | - name: Extract version from pyproject.toml 35 | id: get_version 36 | run: | 37 | version=$(python -c "import toml; print(toml.load('pyproject.toml')['project']['version'])") 38 | echo "version=$version" >> $GITHUB_OUTPUT 39 | 40 | - name: Create Git tag if not exists 41 | run: | 42 | git fetch --tags 43 | tag="v${{ steps.get_version.outputs.version }}" 44 | if ! git rev-parse "$tag" >/dev/null 2>&1; then 45 | git tag "$tag" 46 | git push origin "$tag" 47 | else 48 | echo "Tag $tag already exists." 49 | fi 50 | -------------------------------------------------------------------------------- /.github/workflows/main-checks.yml: -------------------------------------------------------------------------------- 1 | name: Main Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - "v*.*.*" 8 | tags: 9 | - "v*.*.*" 10 | 11 | jobs: 12 | checks: 13 | uses: ./.github/workflows/checks.yml 14 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Checks 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | checks: 8 | uses: ./.github/workflows/checks.yml 9 | -------------------------------------------------------------------------------- /.github/workflows/publish-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" # Triggers on tags like v1.2.3 7 | 8 | workflow_dispatch: # Enables manual runs 9 | 10 | jobs: 11 | checks: 12 | uses: ./.github/workflows/checks.yml 13 | 14 | publish: 15 | name: Build and publish package to PyPI 16 | runs-on: ubuntu-latest 17 | needs: [checks] # Run checks before publishing 18 | 19 | # This ties the job to a protected environment. 20 | environment: 21 | name: production # Ensure this environment is configured in your repo settings with required reviewers 22 | 23 | steps: 24 | - name: Check out code 25 | uses: actions/checkout@v4 26 | 27 | - name: Install uv 28 | uses: astral-sh/setup-uv@v3 29 | 30 | - name: "Set up Python" 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version-file: ".python-version" 34 | 35 | - name: Install the project 36 | run: uv sync --frozen --all-extras --dev 37 | 38 | - name: Build 39 | run: uv build 40 | 41 | - name: Upload artifacts 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: release-dists 45 | path: dist/ 46 | 47 | - name: Publish package to PyPI using uv 48 | env: 49 | UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 50 | run: uv publish 51 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Update Release Draft 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | # pull_request event is required only for autolabeler 9 | pull_request: 10 | # Only following types are handled by the action, but one can default to all as well 11 | types: [opened, reopened, synchronize] 12 | 13 | # pull_request_target event is required for autolabeler to support PRs from forks 14 | pull_request_target: 15 | types: [opened, reopened, synchronize] 16 | 17 | workflow_dispatch: # Enables manual runs 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | update_release_draft: 24 | permissions: 25 | # write permission is required to create a github release 26 | contents: write 27 | # write permission is required for autolabeler 28 | pull-requests: write 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: release-drafter/release-drafter@v6 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.goosehints: -------------------------------------------------------------------------------- 1 | this project uses the "uv" package manager to run, install and manage python 2 | the "src" and "tests" directory contain source code and tests respectively. 3 | there are a number of example applications. they are run by using "uv run scripts/example.py run " 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.8.4 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | args: [--fix] 9 | # Run the formatter. 10 | - id: ruff-format 11 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "charliermarsh.ruff"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[python]": { 5 | "editor.defaultFormatter": "charliermarsh.ruff", 6 | "editor.formatOnSave": true, 7 | "editor.rulers": [] 8 | }, 9 | "yaml.schemas": { 10 | "https://raw.githubusercontent.com/lastmile-ai/mcp-agent/main/schema/mcp-agent.config.schema.json": [ 11 | "mcp-agent.config.yaml", 12 | "mnemo_agent.config.yaml", 13 | "mcp-agent.secrets.yaml", 14 | "mnemo_agent.secrets.yaml" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/Mnemo_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/assets/Mnemo_Logo.png -------------------------------------------------------------------------------- /examples/basic/decorator/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example MCP Agent application showing simplified agent access. 3 | """ 4 | 5 | import asyncio 6 | from mnemo_agent.core.decorator_app import MCPAgentDecorator 7 | 8 | # Create the application 9 | agent_app = MCPAgentDecorator("root-test") 10 | 11 | 12 | # Define the agent 13 | @agent_app.agent( 14 | name="basic_agent", 15 | instruction="A simple agent that helps with basic tasks.", 16 | servers=["mnemo_mcp_root"], 17 | ) 18 | async def main(): 19 | # Use the app's context manager 20 | async with agent_app.run() as agent: 21 | result = await agent.send("basic_agent", "what's the next number?") 22 | print("\n\n\n" + result) 23 | 24 | 25 | if __name__ == "__main__": 26 | asyncio.run(main()) 27 | -------------------------------------------------------------------------------- /examples/basic/decorator/mnemo_mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: error 7 | path: "./mnemo_agent.log" 8 | 9 | mcp: 10 | servers: 11 | mnemo_mcp_root: 12 | type: "mnemo_mcp_root" 13 | command: "uv" 14 | args: ["run", "../mnemo_mcp_root_test/root_test_server.py"] 15 | 16 | openai: 17 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file 18 | default_model: o3-mini 19 | reasoning_effort: low 20 | -------------------------------------------------------------------------------- /examples/basic/decorator/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai 6 | rich 7 | 8 | -------------------------------------------------------------------------------- /examples/basic/functions/README.md: -------------------------------------------------------------------------------- 1 | # MCP Functions Agent Example 2 | 3 | This example shows a "math" Agent using manually-defined functions to compute simple math results for a user request. 4 | 5 | The agent will determine, based on the request, which functions to call and in what order. 6 | 7 | To run the example (after setting openai api_key in mnemo_agent.secrets.yaml): 8 | `uv run scripts/example.py run functions --debug` 9 | 10 | Image 11 | -------------------------------------------------------------------------------- /examples/basic/functions/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.agents.agent import Agent 6 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 7 | 8 | 9 | def add_numbers(a: int, b: int) -> int: 10 | """ 11 | Adds two numbers. 12 | """ 13 | print(f"Math expert is adding {a} and {b}") 14 | return a + b 15 | 16 | 17 | def multiply_numbers(a: int, b: int) -> int: 18 | """ 19 | Multiplies two numbers. 20 | """ 21 | print(f"Math expert is multiplying {a} and {b}") 22 | return a * b 23 | 24 | 25 | app = MCPApp(name="mnemo_agent_using_functions") 26 | 27 | 28 | async def example_usage(): 29 | async with app.run() as agent_app: 30 | logger = agent_app.logger 31 | context = agent_app.context 32 | logger.info("Current config:", data=context.config.model_dump()) 33 | 34 | math_agent = Agent( 35 | name="math_agent", 36 | instruction="""You are an expert in mathematics with access to some functions 37 | to perform correct calculations. 38 | Your job is to identify the closest match to a user's request, 39 | make the appropriate function calls, and return the result.""", 40 | functions=[add_numbers, multiply_numbers], 41 | ) 42 | 43 | async with math_agent: 44 | logger.info("math_agent: Connected to server, calling list_tools...") 45 | result = await math_agent.list_tools() 46 | logger.info("Tools available:", data=result.model_dump()) 47 | 48 | llm = await math_agent.attach_llm(OpenAIAugmentedLLM) 49 | result = await llm.generate_str( 50 | message="Add 2 and 3, then multiply the result by 4.", 51 | ) 52 | 53 | logger.info(f"Expert math result: {result}") 54 | 55 | 56 | if __name__ == "__main__": 57 | start = time.time() 58 | asyncio.run(example_usage()) 59 | end = time.time() 60 | t = end - start 61 | 62 | print(f"Total run time: {t:.2f}s") 63 | -------------------------------------------------------------------------------- /examples/basic/functions/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | progress_display: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | openai: 14 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 15 | # default_model: "o3-mini" 16 | default_model: "gpt-4o-mini" 17 | -------------------------------------------------------------------------------- /examples/basic/functions/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | -------------------------------------------------------------------------------- /examples/basic/functions/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai 6 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_agent_server/README.md: -------------------------------------------------------------------------------- 1 | # MCP Server example 2 | 3 | To use in claude desktop, insert the following in `claude_desktop_config.json` and restart the Claude Desktop client. 4 | 5 | ```json 6 | { 7 | "mcpServers": { 8 | "mcp-agent": { 9 | "command": "uv", 10 | "args": [ 11 | "--directory", 12 | "/ABSOLUTE/PATH/TO/PARENT/FOLDER", 13 | "run", 14 | "server.py" 15 | ] 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | > [!WARNING] 22 | > You may need to put the full path to the uv executable in the command field. You can get this by running `which uv` on MacOS/Linux or `where uv` on Windows. 23 | 24 | Image 25 | 26 | To use the MCP server with a client: 27 | 28 | ```bash 29 | uv run client.py server.py 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_agent_server/client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Optional 3 | from contextlib import AsyncExitStack 4 | 5 | from mcp import ClientSession, StdioServerParameters 6 | from mcp.client.stdio import stdio_client 7 | from mnemo_agent.app import MCPApp 8 | from mnemo_agent.agents.agent import Agent 9 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 10 | from rich import print 11 | 12 | from dotenv import load_dotenv 13 | 14 | load_dotenv() # load environment variables from .env 15 | 16 | 17 | class MCPClient: 18 | def __init__(self): 19 | # Initialize session and client objects 20 | self.session: Optional[ClientSession] = None 21 | self.exit_stack = AsyncExitStack() 22 | 23 | async def initialize(self): 24 | self.app = MCPApp(name="mnemo_agent") 25 | await self.app.initialize() 26 | 27 | self.agent = Agent( 28 | name="agent", 29 | instruction="you are an assistant", 30 | server_names=["mnemo_agent"], 31 | ) 32 | await self.agent.initialize() 33 | 34 | self.llm = await self.agent.attach_llm(OpenAIAugmentedLLM) 35 | 36 | async def connect_to_server(self, server_script_path: str): 37 | """Connect to an MCP server 38 | 39 | Args: 40 | server_script_path: Path to the server script (.py or .js) 41 | """ 42 | is_python = server_script_path.endswith(".py") 43 | is_js = server_script_path.endswith(".js") 44 | if not (is_python or is_js): 45 | raise ValueError("Server script must be a .py or .js file") 46 | 47 | command = "python" if is_python else "node" 48 | server_params = StdioServerParameters( 49 | command=command, args=[server_script_path], env=None 50 | ) 51 | 52 | stdio_transport = await self.exit_stack.enter_async_context( 53 | stdio_client(server_params) 54 | ) 55 | self.stdio, self.write = stdio_transport 56 | self.session = await self.exit_stack.enter_async_context( 57 | ClientSession(self.stdio, self.write) 58 | ) 59 | 60 | await self.session.initialize() 61 | 62 | # List available tools 63 | response = await self.session.list_tools() 64 | tools = response.tools 65 | print("\nConnected to server with tools:", [tool.name for tool in tools]) 66 | 67 | async def process_query(self, query: str) -> str: 68 | """Process a query""" 69 | response = await self.llm.generate_str(message=query) 70 | return response 71 | 72 | async def chat_loop(self): 73 | """Run an interactive chat loop""" 74 | print("\nMCP Client Started!") 75 | print("Type your queries or 'quit' to exit.") 76 | 77 | while True: 78 | try: 79 | query = input("\nQuery: ").strip() 80 | 81 | if query.lower() == "quit": 82 | break 83 | 84 | response = await self.process_query(query) 85 | print("\n" + response) 86 | 87 | except Exception as e: 88 | print(f"\nError: {str(e)}") 89 | 90 | async def cleanup(self): 91 | """Clean up resources""" 92 | await self.exit_stack.aclose() 93 | 94 | 95 | async def main(): 96 | if len(sys.argv) < 2: 97 | print("Usage: python client.py ") 98 | sys.exit(1) 99 | 100 | client = MCPClient() 101 | try: 102 | await client.initialize() 103 | await client.connect_to_server(sys.argv[1]) 104 | await client.chat_loop() 105 | finally: 106 | await client.cleanup() 107 | 108 | 109 | if __name__ == "__main__": 110 | import sys 111 | 112 | asyncio.run(main()) 113 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_agent_server/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: file 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | mnemo_agent: 17 | command: "uv" 18 | args: ["run", "server.py"] 19 | fetch: 20 | command: "uvx" 21 | args: ["mcp-server-fetch"] 22 | filesystem: 23 | command: "npx" 24 | args: 25 | [ 26 | "-y", 27 | "@modelcontextprotocol/server-filesystem", 28 | "/Users/saqadri/Desktop", 29 | "/Users/saqadri/Downloads", 30 | ] 31 | 32 | openai: 33 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 34 | default_model: gpt-4o 35 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_agent_server/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_basic_agent/README.md: -------------------------------------------------------------------------------- 1 | # MCP Agent example 2 | 3 | This example shows a "finder" Agent which has access to the 'fetch' and 'filesystem' MCP servers. 4 | 5 | You can ask it information about local files or URLs, and it will make the determination on what to use at what time to satisfy the request. 6 | 7 | Image 8 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_basic_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | progress_display: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | mcp: 14 | servers: 15 | fetch: 16 | command: "uvx" 17 | args: ["mcp-server-fetch"] 18 | filesystem: 19 | command: "npx" 20 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 21 | 22 | openai: 23 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 24 | # default_model: "o3-mini" 25 | default_model: "gpt-4o-mini" 26 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_basic_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_basic_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_hello_world/README.md: -------------------------------------------------------------------------------- 1 | # Simplest usage of MCP Agent 2 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_hello_world/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from mnemo_agent.app import MCPApp 4 | from mnemo_agent.mcp.gen_client import gen_client 5 | from mnemo_agent.mcp.mnemo_agent_client_session import MCPAgentClientSession 6 | from mnemo_agent.mcp.mcp_connection_manager import MCPConnectionManager 7 | 8 | app = MCPApp(name="mcp_hello_world") 9 | 10 | 11 | async def example_usage(): 12 | async with app.run() as hello_world_app: 13 | context = hello_world_app.context 14 | logger = hello_world_app.logger 15 | 16 | logger.info("Hello, world!") 17 | logger.info("Current config:", data=context.config.model_dump()) 18 | 19 | # Use an async context manager to connect to the fetch server 20 | # At the end of the block, the connection will be closed automatically 21 | async with gen_client( 22 | "fetch", server_registry=context.server_registry 23 | ) as fetch_client: 24 | logger.info("fetch: Connected to server, calling list_tools...") 25 | result = await fetch_client.list_tools() 26 | logger.info("Tools available:", data=result.model_dump()) 27 | 28 | # Connect to the filesystem server using a persistent connection via connect/disconnect 29 | # This is useful when you need to make multiple requests to the same server 30 | 31 | connection_manager = MCPConnectionManager(context.server_registry) 32 | await connection_manager.__aenter__() 33 | 34 | try: 35 | filesystem_client = await connection_manager.get_server( 36 | server_name="filesystem", 37 | client_session_factory=MCPAgentClientSession, 38 | ) 39 | logger.info( 40 | "filesystem: Connected to server with persistent connection." 41 | ) 42 | 43 | fetch_client = await connection_manager.get_server( 44 | server_name="fetch", client_session_factory=MCPAgentClientSession 45 | ) 46 | logger.info("fetch: Connected to server with persistent connection.") 47 | 48 | result = await filesystem_client.session.list_tools() 49 | logger.info("filesystem: Tools available:", data=result.model_dump()) 50 | 51 | result = await fetch_client.session.list_tools() 52 | logger.info("fetch: Tools available:", data=result.model_dump()) 53 | finally: 54 | await connection_manager.disconnect_server(server_name="filesystem") 55 | logger.info("filesystem: Disconnected from server.") 56 | await connection_manager.disconnect_server(server_name="fetch") 57 | logger.info("fetch: Disconnected from server.") 58 | await connection_manager.__aexit__(None, None, None) 59 | 60 | 61 | if __name__ == "__main__": 62 | asyncio.run(example_usage()) 63 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_hello_world/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: info 7 | path: "./mcp-agent.log" 8 | 9 | mcp: 10 | servers: 11 | fetch: 12 | command: "uvx" 13 | args: ["mcp-server-fetch"] 14 | filesystem: 15 | command: "npx" 16 | args: ["-y", "@modelcontextprotocol/server-filesystem", "."] 17 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_hello_world/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_model_selector/README.md: -------------------------------------------------------------------------------- 1 | # LLM Selector 2 | 3 | This example shows using MCP's ModelPreferences type to select a model (LLM) based on speed, cost and intelligence priorities. 4 | 5 | Interactive mode: 6 | 7 | ```bash 8 | uv run interactive.py 9 | ``` 10 | 11 | https://github.com/user-attachments/assets/04257ae4-a628-4c25-ace2-6540620cbf8b 12 | 13 | Script mode: 14 | 15 | ```bash 16 | uv run main.py 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_model_selector/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: file 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | fetch: 17 | command: "uvx" 18 | args: ["mcp-server-fetch"] 19 | filesystem: 20 | command: "npx" 21 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 22 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_model_selector/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | rich 6 | typer -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_server_aggregator/README.md: -------------------------------------------------------------------------------- 1 | # MCP aggregator example 2 | 3 | This example shows connecting to multiple MCP servers via the MCPAggregator interface 4 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_server_aggregator/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | 8 | mcp: 9 | servers: 10 | fetch: 11 | command: "uvx" 12 | args: ["mcp-server-fetch"] 13 | filesystem: 14 | command: "npx" 15 | args: ["-y", "@modelcontextprotocol/server-filesystem", "."] 16 | -------------------------------------------------------------------------------- /examples/basic/mnemo_mcp_server_aggregator/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.agents.agent import Agent 6 | from mnemo_agent.mcp.mcp_connection_manager import MCPConnectionManager 7 | from mnemo_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM # noqa: F401 8 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM # noqa: F401 9 | from mnemo_agent.logging.logger import LoggingConfig 10 | from rich import print 11 | 12 | app = MCPApp(name="Testing MCP Server roots") 13 | 14 | 15 | async def example_usage(): 16 | async with app.run() as agent_app: 17 | logger = agent_app.logger 18 | context = agent_app.context 19 | 20 | async with MCPConnectionManager(context.server_registry): 21 | interpreter_agent = Agent( 22 | name="analysis", 23 | instruction="""You have access to a python interpreter. Pandas, Seaborn and Matplotlib are already installed. You can add further packages if needed.""", 24 | server_names=["root_test", "interpreter"], 25 | ) 26 | 27 | try: 28 | llm = await interpreter_agent.attach_llm(AnthropicAugmentedLLM) 29 | 30 | # (claude does not need this signpost - this is where 'available files' pattern would be useful) 31 | await llm.generate_str( 32 | "There is a file named '01_Data_Processed.csv' in the current directory. Use the Python Interpreter to to analyze the file. " 33 | # "There is a CSV file in the current directory. Use the Python Interpreter to to analyze the file. " 34 | + "Produce a detailed description of the data, and any patterns it contains. " 35 | ) 36 | 37 | result = await llm.generate_str( 38 | "Consider the data, and how to usefully group it for presentation to a Human. Find insights, using the Python Interpreter as needed.\n" 39 | + "Use MatPlotLib to produce insightful visualisations. Save them as '.png' files in the current directory. Be sure to run the code and save the files " 40 | ) 41 | print(result) 42 | logger.info(result) 43 | 44 | finally: 45 | # Clean up the agent 46 | await interpreter_agent.close() 47 | 48 | # Ensure logging is properly shutdown 49 | await LoggingConfig.shutdown() 50 | 51 | 52 | if __name__ == "__main__": 53 | start = time.time() 54 | try: 55 | asyncio.run(example_usage()) 56 | except KeyboardInterrupt: 57 | print("\nReceived keyboard interrupt, shutting down gracefully...") 58 | except Exception as e: 59 | print(f"Error during execution: {e}") 60 | raise 61 | finally: 62 | end = time.time() 63 | t = end - start 64 | print(f"Total run time: {t:.2f}s") 65 | -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: file 6 | level: debug 7 | path: "./mnemo_mcp_root_test.jsonl" 8 | 9 | mcp: 10 | servers: 11 | root_test: 12 | command: "uv" 13 | args: ["run", "root_test_server.py"] 14 | roots: 15 | - uri: "file:///./test_data/" 16 | name: "test_data" 17 | server_uri_alias: "file:///mnt/data/" 18 | interpreter: 19 | command: "docker" 20 | args: 21 | [ 22 | "run", 23 | "-i", 24 | "--rm", 25 | "--pull=always", 26 | "-v", 27 | "./test_data:/mnt/data/", 28 | "ghcr.io/evalstate/mcp-py-repl:latest", 29 | ] 30 | roots: 31 | - uri: "file://./test_data/" 32 | name: "test_data" 33 | server_uri_alias: "file:///mnt/data/" 34 | 35 | # command: "uv" 36 | # args: ["run", "/home/ssmith/source/mcp-python/src/mcp_python/server.py"] 37 | 38 | openai: 39 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 40 | default_model: o3-mini 41 | reasoning_effort: low 42 | -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | # Additional dependencies specific to this example 6 | anthropic 7 | -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/root_test_server.py: -------------------------------------------------------------------------------- 1 | from mcp.server.fastmcp import FastMCP, Context 2 | 3 | mcp = FastMCP("MCP Root Tester") 4 | 5 | 6 | @mcp.tool() 7 | async def show_roots(ctx: Context) -> str: 8 | return await ctx.session.list_roots() 9 | 10 | 11 | if __name__ == "__main__": 12 | mcp.run() 13 | -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/3d_accelerometer_by_exercise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/3d_accelerometer_by_exercise.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/accelerometer_time_series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/accelerometer_time_series.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/exercise_and_weight_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/exercise_and_weight_distribution.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/exercise_by_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/exercise_by_day.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/exercise_by_participant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/exercise_by_participant.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/exercise_repetition_patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/exercise_repetition_patterns.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/exercise_signatures_radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/exercise_signatures_radar.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/gyroscope_time_series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/gyroscope_time_series.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/participant_exercise_patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/participant_exercise_patterns.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/sensor_correlation_heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/sensor_correlation_heatmap.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/sensor_distribution_by_category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/sensor_distribution_by_category.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/sensor_patterns_by_category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/sensor_patterns_by_category.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/sensor_patterns_by_exercise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/sensor_patterns_by_exercise.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/plots/weight_category_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/plots/weight_category_comparison.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/1_exercise_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/1_exercise_distribution.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/2_sensor_patterns_by_exercise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/2_sensor_patterns_by_exercise.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/3_heavy_vs_medium_radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/3_heavy_vs_medium_radar.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/4_pca_clustering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/4_pca_clustering.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/4b_pca_feature_importance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/4b_pca_feature_importance.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/5_time_series_patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/5_time_series_patterns.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/6_participant_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/6_participant_distribution.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/6b_participant_sensor_heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/6b_participant_sensor_heatmap.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/7_exercise_variability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/7_exercise_variability.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/7b_sensor_distributions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/7b_sensor_distributions.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/8_heavy_vs_medium_boxplots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/8_heavy_vs_medium_boxplots.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/8b_heavy_medium_percent_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/examples/mcp/mcp_root_test/test_data/visualizations/8b_heavy_medium_percent_diff.png -------------------------------------------------------------------------------- /examples/mcp/mcp_root_test/test_data/visualizations/key_insights.md: -------------------------------------------------------------------------------- 1 | 2 | # Key Insights from Exercise Motion Sensor Data Analysis 3 | 4 | ## Overview 5 | - Dataset contains **9,009 records** from **5 participants** performing various weightlifting exercises 6 | - Data includes accelerometer and gyroscope readings in 3 axes (x, y, z) 7 | - Exercises performed with either **heavy** or **medium** weights, plus rest periods (sitting/standing) 8 | 9 | ## Exercise Signature Patterns 10 | 11 | ### 1. Distinct Sensor Profiles 12 | Each exercise shows a unique "signature" in sensor readings: 13 | - **Bench Press**: High positive Y-axis acceleration (~0.95g), moderate negative X-axis 14 | - **Overhead Press**: Most negative X-axis acceleration (-0.24g), high positive Y-axis 15 | - **Deadlift & Row**: Similar patterns with strongly negative Y-axis (~-1.02g) 16 | - **Squat**: Unique pattern with positive X-axis and moderate Y-axis values 17 | - **Rest Periods**: Highest X-axis acceleration and distinctive gyroscope patterns 18 | 19 | ### 2. Weight Category Differences 20 | Heavy vs. Medium weight categories show clear differences: 21 | - **Gyroscope Z-axis** shows the largest differences between weight categories 22 | - **Bench Press**: Shows 30-40% higher gyroscope readings in heavy category 23 | - **Overhead Press**: Exhibits the largest differences between weight categories 24 | - **Deadlift**: Shows distinct accelerometer patterns between categories 25 | 26 | ### 3. Participant Variations 27 | - Participants have individual "styles" when performing the same exercises 28 | - **Participant A** has the most balanced distribution across exercise types 29 | - **Participant B** shows distinctive patterns in squat and overhead press 30 | - Sensor reading averages vary significantly between participants 31 | 32 | ### 4. Exercise Variability 33 | - **Rest periods** show the highest variability in sensor readings 34 | - **Squats** have high variability in accelerometer X-axis 35 | - **Overhead Press** exhibits high gyroscope Z-axis variability 36 | - **Bench Press** demonstrates consistent accelerometer Y-axis patterns 37 | 38 | ### 5. Pattern Identification 39 | - PCA analysis shows clear clustering of exercise types 40 | - Principal components are primarily driven by: 41 | - **PC1**: Accelerometer readings (especially X and Z axes) 42 | - **PC2**: Gyroscope X and Z axes 43 | - Exercise types form distinct clusters, particularly separating upper body from lower body exercises 44 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse/README.md: -------------------------------------------------------------------------------- 1 | # SSE example 2 | 3 | This example shows how to use an SSE server with mcp-agent. 4 | 5 | - `server.py` is a simple server that runs on localhost:8000 6 | - `main.py` is the mcp-agent client that uses the SSE server.py 7 | 8 | To run, open two terminals: 9 | 10 | 1. `uv run server.py` 11 | 2. `uv run main.py` 12 | 13 | image 14 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from dotenv import load_dotenv 4 | from rich import print 5 | 6 | from mnemo_agent.agents.agent import Agent 7 | from mnemo_agent.app import MCPApp 8 | 9 | load_dotenv() # load environment variables from .env 10 | 11 | 12 | async def test_sse(): 13 | app: MCPApp = MCPApp(name="test-app") 14 | async with app.run(): 15 | print("MCP App initialized.") 16 | 17 | agent: Agent = Agent( 18 | name="agent", 19 | instruction="You are an assistant", 20 | server_names=["mcp_test_server_sse"], 21 | ) 22 | 23 | async with agent: 24 | print(await agent.list_tools()) 25 | # call_tool_result: CallToolResult = await agent.call_tool('mcp_test_server_sse-get-magic-number') 26 | # 27 | # assert call_tool_result.text == "42" 28 | # print("SSE test passed!") 29 | 30 | 31 | if __name__ == "__main__": 32 | asyncio.run(test_sse()) 33 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: file 6 | level: debug 7 | 8 | mcp: 9 | servers: 10 | mcp_test_server_sse: 11 | transport: sse 12 | url: http://localhost:8000/sse 13 | 14 | openai: 15 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 16 | default_model: gpt-4o 17 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse/server.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import uvicorn 4 | from mcp import Tool 5 | from mcp.server import Server, InitializationOptions, NotificationOptions 6 | from mcp.server.sse import SseServerTransport 7 | from mcp.types import TextContent, ImageContent, EmbeddedResource 8 | from starlette.applications import Starlette 9 | from starlette.routing import Route, Mount 10 | from pydantic import BaseModel, create_model 11 | 12 | 13 | def main(): 14 | sse_server_transport: SseServerTransport = SseServerTransport("/messages/") 15 | server: Server = Server("test-service") 16 | 17 | @server.list_tools() 18 | async def handle_list_tools() -> list[Tool]: 19 | # Create an empty schema (or define a real one if you need parameters) 20 | EmptyInputSchema = create_model("EmptyInputSchema", __base__=BaseModel) 21 | 22 | return [ 23 | Tool( 24 | name="get-magic-number", 25 | description="Returns the magic number", 26 | inputSchema=EmptyInputSchema.model_json_schema(), # Add the required inputSchema 27 | ) 28 | ] 29 | 30 | @server.call_tool() 31 | async def handle_call_tool( 32 | name: str, arguments: dict[str, Any] | None 33 | ) -> list[TextContent | ImageContent | EmbeddedResource]: 34 | return [ 35 | TextContent(type="text", text="42") 36 | ] # Return a list, not awaiting the content 37 | 38 | initialization_options: InitializationOptions = InitializationOptions( 39 | server_name=server.name, 40 | server_version="1.0.0", 41 | capabilities=server.get_capabilities( 42 | notification_options=NotificationOptions(), 43 | experimental_capabilities={}, 44 | ), 45 | ) 46 | 47 | async def handle_sse(request): 48 | async with sse_server_transport.connect_sse( 49 | scope=request.scope, receive=request.receive, send=request._send 50 | ) as streams: 51 | await server.run( 52 | read_stream=streams[0], 53 | write_stream=streams[1], 54 | initialization_options=initialization_options, 55 | ) 56 | 57 | starlette_app: Starlette = Starlette( 58 | routes=[ 59 | Route("/sse", endpoint=handle_sse), 60 | Mount("/messages/", app=sse_server_transport.handle_post_message), 61 | ], 62 | ) 63 | 64 | uvicorn.run(starlette_app, host="0.0.0.0", port=8000, log_level=-10000) 65 | 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse_with_headers/README.md: -------------------------------------------------------------------------------- 1 | # MCP Agent example 2 | 3 | This example shows a basic agent that can connect to an MCP server over SSE with auth headers. 4 | You will need to update the mnemo_agent.config.yaml file with the correct links to a hosted SSE 5 | server and your HTTP headers. 6 | 7 | Image 8 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse_with_headers/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.agents.agent import Agent 6 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 7 | 8 | 9 | # Settings can either be specified programmatically, 10 | # or loaded from mnemo_agent.config.yaml/mnemo_agent.secrets.yaml 11 | app = MCPApp(name="mcp_sse_with_auth") # settings=settings) 12 | 13 | 14 | async def example_usage(): 15 | async with app.run() as agent_app: 16 | logger = agent_app.logger 17 | context = agent_app.context 18 | 19 | logger.info("Current config:", data=context.config.model_dump()) 20 | 21 | agent = Agent( 22 | name="slack-agent", 23 | instruction="""You are an agent whose job is to interact with the Slack workspace 24 | for the user. 25 | """, 26 | server_names=["slack"], 27 | ) 28 | 29 | async with agent: 30 | logger.info("slack-agent: Connected to server, calling list_tools...") 31 | result = await agent.list_tools() 32 | logger.info("Tools available:", data=result.model_dump()) 33 | 34 | llm = await agent.attach_llm(OpenAIAugmentedLLM) 35 | result = await llm.generate( 36 | message="List all Slack channels in the workspace", 37 | ) 38 | logger.info(f"Slack channels: {result}") 39 | 40 | 41 | if __name__ == "__main__": 42 | start = time.time() 43 | asyncio.run(example_usage()) 44 | end = time.time() 45 | t = end - start 46 | 47 | print(f"Total run time: {t:.2f}s") 48 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse_with_headers/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | show_progress: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | mcp: 14 | servers: 15 | slack: 16 | name: "slack" 17 | description: "Slack MCP server" 18 | transport: "sse" 19 | url: "" 20 | headers: 21 | Authorization: "Bearer " 22 | 23 | openai: 24 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 25 | # default_model: "o3-mini" 26 | default_model: "gpt-4o-mini" 27 | -------------------------------------------------------------------------------- /examples/mcp/mcp_sse_with_headers/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key -------------------------------------------------------------------------------- /examples/mcp/mcp_sse_with_headers/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai -------------------------------------------------------------------------------- /examples/mcp/mcp_websockets/README.md: -------------------------------------------------------------------------------- 1 | # MCP Websocket example 2 | 3 | This example shows a basic agent that can connect to an MCP server over websockets 4 | 5 | Setup instructions: 6 | 7 | 1. Get your GitHub PAT from https://github.com/settings/personal-access-tokens, make sure you have read access for repositories. 8 | 2. Base64-encode the following: 9 | 10 | ```json 11 | { 12 | "githubPersonalAccessToken": "YOUR_GITHUB_PAT" 13 | } 14 | ``` 15 | 16 | 3. Copy the `mnemo_agent.secrets.yaml` file, and update it with your OpenAI API key, and the websocket url with the Base64-encoded string: 17 | 18 | ```yaml 19 | openai: 20 | api_key: openai_api_key 21 | 22 | mcp: 23 | servers: 24 | smithery-github: 25 | url: "wss://server.smithery.ai/@smithery-ai/github/ws?config=BASE64_ENCODED_CONFIG" 26 | ``` 27 | 28 | Finally, run `uv run main.py `. E.g. `uv run main.py saqadri` 29 | 30 | image 31 | -------------------------------------------------------------------------------- /examples/mcp/mcp_websockets/main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import time 4 | 5 | from rich import print 6 | 7 | from mnemo_agent.app import MCPApp 8 | from mnemo_agent.agents.agent import Agent 9 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 10 | 11 | 12 | # Settings can either be specified programmatically, 13 | # or loaded from mnemo_agent.config.yaml/mnemo_agent.secrets.yaml 14 | app = MCPApp(name="mcp_websockets") # settings=settings) 15 | 16 | 17 | async def example_usage(username: str): 18 | async with app.run() as agent_app: 19 | logger = agent_app.logger 20 | context = agent_app.context 21 | 22 | logger.info("Current config:", data=context.config.model_dump()) 23 | 24 | agent = Agent( 25 | name="github-agent", 26 | instruction="""You are an agent whose job is to interact with the Github 27 | repository for the user. 28 | """, 29 | server_names=["smithery-github"], 30 | ) 31 | 32 | async with agent: 33 | logger.info("github-agent: Connected to server, calling list_tools...") 34 | result = await agent.list_tools() 35 | logger.info("Tools available:", data=result.model_dump()) 36 | 37 | llm = await agent.attach_llm(OpenAIAugmentedLLM) 38 | result = await llm.generate_str( 39 | message=f"List all public Github repositories created by the user {username}.", 40 | ) 41 | print(f"Github repositories: {result}") 42 | 43 | 44 | if __name__ == "__main__": 45 | parser = argparse.ArgumentParser() 46 | parser.add_argument("username", help="GitHub username to fetch repositories for") 47 | 48 | args = parser.parse_args() 49 | 50 | start = time.time() 51 | asyncio.run(example_usage(args.username)) 52 | end = time.time() 53 | t = end - start 54 | 55 | print(f"Total run time: {t:.2f}s") 56 | -------------------------------------------------------------------------------- /examples/mcp/mcp_websockets/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | show_progress: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | mcp: 14 | servers: 15 | smithery-github: 16 | name: "@smithery/github" 17 | description: "github server" 18 | transport: "websocket" 19 | # This URL needs to be constructed on Smithery. Smithery requires server json configSchema 20 | # object to be passed in as base64. See details here: 21 | # https://smithery.ai/docs/registry#connecting-to-websocket-servers 22 | # url: "wss://server.smithery.ai/@smithery-ai/github/ws?config=ewogICJnaXRodWJQZXJzb25hbEFjY2Vzc1Rva2VuIjogImdpdGh1Yl9wYXRfMTFBR0RVSFRZMHY0aUM3eG5YaXZNc19NNkllUFZjcUZud1p4RWE5b2p4Qk9wNThla3ZXQk5IeWlLZDVUd3VPN3kyNDJLMkpKUkk0VThJZkdrZSIKfQ" 23 | 24 | openai: 25 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 26 | # default_model: "o3-mini" 27 | default_model: "gpt-4o" 28 | -------------------------------------------------------------------------------- /examples/mcp/mcp_websockets/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | mcp: 7 | servers: 8 | smithery-github: 9 | url: "wss://server.smithery.ai/@smithery-ai/github/ws?config=BASE64_ENCODED_CONFIG" 10 | -------------------------------------------------------------------------------- /examples/mcp/mcp_websockets/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_azure_agent/README.md: -------------------------------------------------------------------------------- 1 | # MCP Azure Agent Example - "Finder" Agent 2 | 3 | This example demonstrates how to create and run a basic "Finder" Agent using Azure OpenAI model and MCP. The Agent has access to the `fetch` MCP server, enabling it to retrieve information from URLs. 4 | 5 | ## Setup 6 | 7 | Check out the [Azure Python SDK docs](https://learn.microsoft.com/en-us/python/api/overview/azure/ai-inference-readme?view=azure-python-preview#getting-started) to obtain the following values: 8 | 9 | - `endpoint`: E.g. `https://.services.ai.azure.com/models` or `https://.cognitiveservices.azure.com/openai/deployments/` 10 | - `api_key` 11 | 12 | Example configurations: 13 | 14 | ```yaml 15 | # mnemo_agent.secrets.yaml 16 | 17 | # Azure OpenAI inference endpoint 18 | azure: 19 | default_model: gpt-4o-mini 20 | api_key: changethis 21 | endpoint: https://.cognitiveservices.azure.com/openai/deployments/ 22 | api_version: "2025-01-01-preview" # Azure OpenAI api-version. See https://aka.ms/azsdk/azure-ai-inference/azure-openai-api-versions 23 | 24 | # Azure AI inference endpoint 25 | azure: 26 | default_model: DeepSeek-V3 27 | api_key: changethis 28 | endpoint: https://.services.ai.azure.com/models 29 | ``` 30 | 31 | To return structured outputs for Azure OpenAI endpoints, you might need to include `api_version` as '2025-01-01-preview'. 32 | 33 | Attach these values in `mnemo_agent.secrets.yaml` or `mnemo_agent.config.yaml` 34 | 35 | ## Running the Agent 36 | 37 | To run the "Finder" agent, navigate to the example directory and execute: 38 | 39 | ```bash 40 | cd examples/model_providers/mcp_basic_azure_agent 41 | 42 | uv run --extra azure main.py 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_azure_agent/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | from mnemo_agent.app import MCPApp 4 | from mnemo_agent.config import ( 5 | AzureSettings, 6 | Settings, 7 | LoggerSettings, 8 | MCPSettings, 9 | MCPServerSettings, 10 | ) 11 | from mnemo_agent.agents.agent import Agent 12 | from mnemo_agent.workflows.llm.augmented_llm_azure import AzureAugmentedLLM 13 | 14 | settings = Settings( 15 | execution_engine="asyncio", 16 | logger=LoggerSettings(type="file", level="debug"), 17 | mcp=MCPSettings( 18 | servers={ 19 | "fetch": MCPServerSettings( 20 | command="uvx", 21 | args=["mcp-server-fetch"], 22 | ), 23 | } 24 | ), 25 | azure=AzureSettings( 26 | api_key="changethis", 27 | endpoint="https://.cognitiveservices.azure.com/openai/deployments/", 28 | default_model="gpt-4o-mini", 29 | api_version="2025-01-01-preview", 30 | ), 31 | ) 32 | 33 | # Settings can either be specified programmatically, 34 | # or loaded from mnemo_agent.config.yaml/mnemo_agent.secrets.yaml 35 | app = MCPApp( 36 | name="mcp_basic_agent", 37 | # settings=settings 38 | ) 39 | 40 | 41 | async def example_usage(): 42 | async with app.run() as agent_app: 43 | logger = agent_app.logger 44 | context = agent_app.context 45 | 46 | logger.info("Current config:", data=context.config.model_dump()) 47 | 48 | finder_agent = Agent( 49 | name="finder", 50 | instruction="""You are an agent with the ability to fetch URLs. Your job is to identify 51 | the closest match to a user's request, make the appropriate tool calls, 52 | and return the URI and CONTENTS of the closest match.""", 53 | server_names=["fetch"], 54 | ) 55 | 56 | async with finder_agent: 57 | logger.info("finder: Connected to server, calling list_tools...") 58 | result = await finder_agent.list_tools() 59 | logger.info("Tools available:", data=result.model_dump()) 60 | 61 | llm = await finder_agent.attach_llm(AzureAugmentedLLM) 62 | 63 | result = await llm.generate_str( 64 | message="Print the first 2 paragraphs of https://modelcontextprotocol.io/introduction", 65 | ) 66 | 67 | logger.info(f"First 2 paragraphs of Model Context Protocol docs: {result}") 68 | 69 | 70 | if __name__ == "__main__": 71 | start = time.time() 72 | asyncio.run(example_usage()) 73 | end = time.time() 74 | t = end - start 75 | 76 | print(f"Total run time: {t:.2f}s") 77 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_azure_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | show_progress: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | mcp: 14 | servers: 15 | fetch: 16 | command: "uvx" 17 | args: ["mcp-server-fetch"] 18 | 19 | azure: 20 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 21 | # default model: "gpt-4o-mini" 22 | default_model: gpt-4o-mini 23 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_azure_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | azure: 4 | default_model: gpt-4o-mini 5 | api_key: changethis 6 | endpoint: https://.cognitiveservices.azure.com/openai/deployments/ -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_bedrock_agent/README.md: -------------------------------------------------------------------------------- 1 | # MCP Bedrock Agent Example - "Finder" Agent 2 | 3 | This example demonstrates how to create and run a basic "Finder" Agent using AWS Bedrock and MCP. The Agent has access to the `fetch` MCP server, enabling it to retrieve information from URLs. 4 | 5 | ## Setup 6 | 7 | Before running the agent, ensure you have your AWS credentials and configuration details set up: 8 | 9 | Parameters 10 | 11 | - `aws_region` 12 | - `aws_access_key_id` 13 | - `aws_secret_access_key` 14 | - `aws_session_token` 15 | 16 | You can provide these in one of the following ways: 17 | 18 | Configuration Options 19 | 20 | 1. Via `mnemo_agent.secrets.yaml` or `mnemo_agent.config.yaml` 21 | 2. Via your AWS config file (`~/.aws/config` and/or `~/.aws/credentials`) 22 | 23 | Optional: 24 | 25 | - `default_model`: Defaults to `us.amazon.nova-lite-v1:0` but can be customized in your config. For more info see: https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html 26 | - `profile`: Select which AWS profile should be used. 27 | 28 | ## Running the Agent 29 | 30 | To run the "Finder" agent, navigate to the example directory and execute: 31 | 32 | ```bash 33 | cd examples/model_providers/mcp_basic_bedrock_agent 34 | 35 | uv run main.py 36 | ``` 37 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_bedrock_agent/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.config import ( 6 | BedrockSettings, 7 | Settings, 8 | LoggerSettings, 9 | MCPSettings, 10 | MCPServerSettings, 11 | ) 12 | from mnemo_agent.agents.agent import Agent 13 | from mnemo_agent.workflows.llm.augmented_llm_bedrock import BedrockAugmentedLLM 14 | 15 | settings = Settings( 16 | execution_engine="asyncio", 17 | logger=LoggerSettings(type="file", level="debug"), 18 | mcp=MCPSettings( 19 | servers={ 20 | "fetch": MCPServerSettings( 21 | command="uvx", 22 | args=["mcp-server-fetch"], 23 | ), 24 | } 25 | ), 26 | bedrock=BedrockSettings( 27 | default_model="anthropic.claude-3-haiku-20240307-v1:0", 28 | ), 29 | ) 30 | 31 | # Settings can either be specified programmatically, 32 | # or loaded from mnemo_agent.config.yaml/mnemo_agent.secrets.yaml 33 | app = MCPApp( 34 | name="mcp_basic_agent" 35 | # settings=settings 36 | ) 37 | 38 | 39 | async def example_usage(): 40 | async with app.run() as agent_app: 41 | logger = agent_app.logger 42 | context = agent_app.context 43 | 44 | logger.info("Current config:", data=context.config.model_dump()) 45 | 46 | finder_agent = Agent( 47 | name="finder", 48 | instruction="""You are an agent with the ability to fetch URLs. Your job is to identify 49 | the closest match to a user's request, make the appropriate tool calls, 50 | and return the URI and CONTENTS of the closest match.""", 51 | server_names=["fetch"], 52 | ) 53 | 54 | async with finder_agent: 55 | logger.info("finder: Connected to server, calling list_tools...") 56 | result = await finder_agent.list_tools() 57 | logger.info("Tools available:", data=result.model_dump()) 58 | 59 | llm = await finder_agent.attach_llm(BedrockAugmentedLLM) 60 | 61 | result = await llm.generate_str( 62 | message="Print the first 2 paragraphs of https://modelcontextprotocol.io/introduction", 63 | ) 64 | logger.info(f"First 2 paragraphs of Model Context Protocol docs: {result}") 65 | 66 | 67 | if __name__ == "__main__": 68 | start = time.time() 69 | asyncio.run(example_usage()) 70 | end = time.time() 71 | t = end - start 72 | 73 | print(f"Total run time: {t:.2f}s") 74 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_bedrock_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | show_progress: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | mcp: 14 | servers: 15 | fetch: 16 | command: "uvx" 17 | args: ["mcp-server-fetch"] 18 | 19 | bedrock: 20 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 21 | default_model: "us.amazon.nova-lite-v1:0" 22 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_bedrock_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | bedrock: 4 | default_model: anthropic.claude-3-haiku-20240307-v1:0 5 | aws_region: 6 | aws_access_key_id: 7 | aws_secret_access_key: 8 | aws_session_token: -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_google_agent/README.md: -------------------------------------------------------------------------------- 1 | # MCP Google Agent Example - "Finder" Agent 2 | 3 | This example demonstrates how to create and run a basic "Finder" Agent using Google's Gemini models and MCP. The Agent has access to the `fetch` MCP server, enabling it to retrieve information from URLs. 4 | 5 | ## Setup 6 | 7 | Before running the agent, ensure you have your Gemini Developer API or Vertex AI configuration details set up: 8 | 9 | ### Required Parameters 10 | 11 | - `api_key`: Your Gemini Developer API key (can also be set via GOOGLE_API_KEY environment variable) 12 | 13 | ### Optional Parameters 14 | 15 | - `vertexai`: Boolean flag to enable VertexAI integration (default: false) 16 | - `project`: Google Cloud project ID (required if using VertexAI) 17 | - `location`: Google Cloud location (required if using VertexAI) 18 | - `default_model`: Defaults to "gemini-2.0-flash" but can be customized in your config 19 | 20 | You can provide these in one of the following ways: 21 | 22 | Configuration Options 23 | 24 | 1. Via `mnemo_agent.secrets.yaml` or `mnemo_agent.config.yaml`: 25 | ```yaml 26 | google: 27 | api_key: "your-google-api-key" 28 | vertexai: false 29 | # Include these if using VertexAI 30 | # project: "your-google-cloud-project" 31 | # location: "us-central1" 32 | ``` 33 | 2. Via environment variables (e.g., GOOGLE_API_KEY) 34 | 35 | ## Running the Agent 36 | 37 | To run the "Finder" agent, navigate to the example directory and execute: 38 | 39 | ```bash 40 | cd examples/model_providers/mcp_basic_google_agent 41 | 42 | uv run main.py 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_google_agent/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | from pydantic import BaseModel 5 | 6 | from mnemo_agent.app import MCPApp 7 | from mnemo_agent.config import ( 8 | GoogleSettings, 9 | Settings, 10 | LoggerSettings, 11 | MCPSettings, 12 | MCPServerSettings, 13 | ) 14 | from mnemo_agent.agents.agent import Agent 15 | from mnemo_agent.workflows.llm.augmented_llm_google import GoogleAugmentedLLM 16 | 17 | 18 | class Essay(BaseModel): 19 | title: str 20 | body: str 21 | conclusion: str 22 | 23 | 24 | settings = Settings( 25 | execution_engine="asyncio", 26 | logger=LoggerSettings(type="file", level="debug"), 27 | mcp=MCPSettings( 28 | servers={ 29 | "fetch": MCPServerSettings( 30 | command="uvx", 31 | args=["mcp-server-fetch"], 32 | ), 33 | } 34 | ), 35 | google=GoogleSettings( 36 | default_model="gemini-2.0-flash", 37 | ), 38 | ) 39 | 40 | # Settings can either be specified programmatically, 41 | # or loaded from mnemo_agent.config.yaml/mnemo_agent.secrets.yaml 42 | app = MCPApp( 43 | name="mcp_basic_agent" 44 | # settings=settings 45 | ) 46 | 47 | 48 | async def example_usage(): 49 | async with app.run() as agent_app: 50 | logger = agent_app.logger 51 | context = agent_app.context 52 | 53 | logger.info("Current config:", data=context.config.model_dump()) 54 | 55 | finder_agent = Agent( 56 | name="finder", 57 | instruction="""You are an agent with the ability to fetch URLs. Your job is to identify 58 | the closest match to a user's request, make the appropriate tool calls, 59 | and return the URI and CONTENTS of the closest match.""", 60 | server_names=["fetch"], 61 | ) 62 | 63 | async with finder_agent: 64 | logger.info("finder: Connected to server, calling list_tools...") 65 | result = await finder_agent.list_tools() 66 | logger.info("Tools available:", data=result.model_dump()) 67 | 68 | llm = await finder_agent.attach_llm(GoogleAugmentedLLM) 69 | 70 | result = await llm.generate_str( 71 | message="Print the first 2 paragraphs of https://modelcontextprotocol.io/introduction", 72 | ) 73 | logger.info(f"First 2 paragraphs of Model Context Protocol docs: {result}") 74 | 75 | result = await llm.generate_structured( 76 | message="Create a short essay using the first 2 paragraphs.", 77 | response_model=Essay, 78 | ) 79 | logger.info(f"Structured paragraphs: {result}") 80 | 81 | 82 | if __name__ == "__main__": 83 | start = time.time() 84 | asyncio.run(example_usage()) 85 | end = time.time() 86 | t = end - start 87 | 88 | print(f"Total run time: {t:.2f}s") 89 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_google_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: debug 7 | show_progress: true 8 | path_settings: 9 | path_pattern: "logs/mcp-agent-{unique_id}.jsonl" 10 | unique_id: "timestamp" # Options: "timestamp" or "session_id" 11 | timestamp_format: "%Y%m%d_%H%M%S" 12 | 13 | mcp: 14 | servers: 15 | fetch: 16 | command: "uvx" 17 | args: ["mcp-server-fetch"] 18 | 19 | google: 20 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 21 | default_model: gemini-2.0-flash 22 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_google_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | google: 4 | default_model: gemini-2.0-flash 5 | api_key: changethis -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_google_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | google-genai -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_ollama_agent/README.md: -------------------------------------------------------------------------------- 1 | # MCP Ollama Agent example 2 | 3 | This example shows a "finder" Agent using llama models to access the 'fetch' and 'filesystem' MCP servers. 4 | 5 | You can ask it information about local files or URLs, and it will make the determination on what to use at what time to satisfy the request. 6 | 7 | Make sure you have Ollama installed. Then pull the required models for the example: 8 | 9 | ```bash 10 | ollama run llama3.2:3b 11 | 12 | ollama run llama3.1:8b 13 | ``` 14 | 15 | Then simply run the example: 16 | `uv run main.py` 17 | 18 | Image 19 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_ollama_agent/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.agents.agent import Agent 6 | from mnemo_agent.workflows.llm.augmented_llm import RequestParams 7 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 8 | 9 | app = MCPApp(name="mcp_basic_agent") 10 | 11 | 12 | async def example_usage(): 13 | async with app.run() as agent_app: 14 | logger = agent_app.logger 15 | context = agent_app.context 16 | 17 | logger.info("Current config:", data=context.config.model_dump()) 18 | 19 | # Add the current directory to the filesystem server's args 20 | context.config.mcp.servers["filesystem"].args.extend([os.getcwd()]) 21 | 22 | finder_agent = Agent( 23 | name="finder", 24 | instruction="""You are an agent with access to the filesystem, 25 | as well as the ability to fetch URLs. Your job is to identify 26 | the closest match to a user's request, make the appropriate tool calls, 27 | and return the URI and CONTENTS of the closest match.""", 28 | server_names=["fetch", "filesystem"], 29 | ) 30 | 31 | async with finder_agent: 32 | logger.info("finder: Connected to server, calling list_tools...") 33 | result = await finder_agent.list_tools() 34 | logger.info("Tools available:", data=result.model_dump()) 35 | 36 | llm = await finder_agent.attach_llm(OpenAIAugmentedLLM) 37 | result = await llm.generate_str( 38 | message="Print the contents of mnemo_agent.config.yaml verbatim", 39 | request_params=RequestParams(model="llama3.2:3b"), 40 | ) 41 | logger.info(f"Result: {result}") 42 | 43 | # Let's switch the same agent to a different LLM 44 | result = await llm.generate_str( 45 | message="Print the first 2 paragraphs of https://modelcontextprotocol.io/introduction", 46 | request_params=RequestParams(model="llama3.1:8b"), 47 | ) 48 | logger.info(f"Result: {result}") 49 | 50 | # Multi-turn conversations 51 | result = await llm.generate_str( 52 | message="Summarize those paragraphs in a 128 character tweet", 53 | request_params=RequestParams(model="llama3.2:3b"), 54 | ) 55 | logger.info(f"Result: {result}") 56 | 57 | 58 | if __name__ == "__main__": 59 | import time 60 | 61 | start = time.time() 62 | asyncio.run(example_usage()) 63 | end = time.time() 64 | t = end - start 65 | 66 | print(f"Total run time: {t:.2f}s") 67 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_ollama_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | fetch: 17 | command: "uvx" 18 | args: ["mcp-server-fetch"] 19 | filesystem: 20 | command: "npx" 21 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 22 | 23 | openai: 24 | base_url: "http://localhost:11434/v1" 25 | api_key: ollama 26 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_ollama_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/model_providers/mcp_basic_ollama_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai -------------------------------------------------------------------------------- /examples/usecases/marimo_mcp_basic_agent/README.md: -------------------------------------------------------------------------------- 1 | # marimo MCP Agent example 2 | 3 | This example [marimo](https://github.com/marimo-team/marimo) notebook shows a 4 | "finder" Agent which has access to the 'fetch' and 'filesystem' MCP servers. 5 | 6 | You can ask it information about local files or URLs, and it will make the 7 | determination on what to use at what time to satisfy the request. 8 | 9 | First modify `mnemo_agent.config.yaml` to include directories to which 10 | you'd like to give the agent access. 11 | 12 | Then run with: 13 | 14 | ```bash 15 | OPENAI_API_KEY= uvx marimo edit --sandbox notebook.py 16 | ``` 17 | 18 | To serve as a read-only app, use 19 | 20 | ``` 21 | OPENAI_API_KEY= uvx marimo run --sandbox notebook.py 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/usecases/marimo_mcp_basic_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | fetch: 17 | command: "uvx" 18 | args: ["mcp-server-fetch"] 19 | filesystem: 20 | command: "npx" 21 | args: 22 | # Add directories you'd like the agent to access, such as 23 | # /Users/my-username/Desktop 24 | ["-y", "@modelcontextprotocol/server-filesystem", "/Users/akshay/tmp"] 25 | 26 | openai: 27 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 28 | default_model: gpt-4o 29 | -------------------------------------------------------------------------------- /examples/usecases/marimo_mcp_basic_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/usecases/marimo_mcp_basic_agent/notebook.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.10" 3 | # dependencies = [ 4 | # "marimo", 5 | # "mcp-agent==0.0.3", 6 | # "mcp==1.2.0", 7 | # "openai==1.60.0", 8 | # ] 9 | # /// 10 | 11 | import marimo 12 | 13 | __generated_with = "0.10.16" 14 | app = marimo.App(width="medium") 15 | 16 | 17 | @app.cell(hide_code=True) 18 | def _(mo): 19 | mo.md( 20 | """ 21 | # 💬 Basic agent chatbot 22 | 23 | **🚀 A [marimo](https://github.com/marimo-team/marimo) chatbot powered by `mcp-agent`** 24 | """ 25 | ) 26 | return 27 | 28 | 29 | @app.cell(hide_code=True) 30 | def _(ListToolsResult, mo, tools): 31 | def format_list_tools_result(list_tools_result: ListToolsResult): 32 | res = "" 33 | for tool in list_tools_result.tools: 34 | res += f"- **{tool.name}**: {tool.description}\n\n" 35 | return res 36 | 37 | tools_str = format_list_tools_result(tools) 38 | mo.accordion({"View tools": mo.md(tools_str)}) 39 | return format_list_tools_result, tools_str 40 | 41 | 42 | @app.cell 43 | def _(llm, mo): 44 | async def model(messages, config): 45 | message = messages[-1] 46 | response = await llm.generate_str(message.content) 47 | return mo.md(response) 48 | 49 | chatbot = mo.ui.chat( 50 | model, 51 | prompts=["What are some files in my filesystem", "Get google.com"], 52 | show_configuration_controls=False, 53 | ) 54 | chatbot 55 | return chatbot, model 56 | 57 | 58 | @app.cell 59 | async def _(): 60 | from mcp import ListToolsResult 61 | import asyncio 62 | from mnemo_agent.app import MCPApp 63 | from mnemo_agent.agents.agent import Agent 64 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 65 | 66 | app = MCPApp(name="mcp_basic_agent") 67 | await app.initialize() 68 | return Agent, ListToolsResult, MCPApp, OpenAIAugmentedLLM, app, asyncio 69 | 70 | 71 | @app.cell 72 | async def _(Agent, OpenAIAugmentedLLM): 73 | finder_agent = Agent( 74 | name="finder", 75 | instruction="""You are an agent with access to the filesystem, 76 | as well as the ability to fetch URLs. Your job is to identify 77 | the closest match to a user's request, make the appropriate tool calls, 78 | and return the URI and CONTENTS of the closest match.""", 79 | server_names=["fetch", "filesystem"], 80 | ) 81 | await finder_agent.initialize() 82 | llm = await finder_agent.attach_llm(OpenAIAugmentedLLM) 83 | tools = await finder_agent.list_tools() 84 | return finder_agent, llm, tools 85 | 86 | 87 | @app.cell 88 | def _(): 89 | import marimo as mo 90 | 91 | return (mo,) 92 | 93 | 94 | if __name__ == "__main__": 95 | app.run() 96 | -------------------------------------------------------------------------------- /examples/usecases/mcp_basic_slack_agent/README.md: -------------------------------------------------------------------------------- 1 | # MCP Slack agent example 2 | 3 | This example shows a "slack" Agent which has access to the 'slack' and 'filesystem' MCP servers. 4 | 5 | You can use it to perform read/write actions on your Slack, as well as on your filesystem, including combination 6 | actions such as writing slack messages to disk or reading files and sending them over slack. 7 | -------------------------------------------------------------------------------- /examples/usecases/mcp_basic_slack_agent/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.agents.agent import Agent 6 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 7 | 8 | app = MCPApp(name="mcp_basic_agent") 9 | 10 | 11 | async def example_usage(): 12 | async with app.run() as agent_app: 13 | logger = agent_app.logger 14 | context = agent_app.context 15 | 16 | slack_agent = Agent( 17 | name="slack_finder", 18 | instruction="""You are an agent with access to the filesystem, 19 | as well as the ability to look up Slack conversations. Your job is to identify 20 | the closest match to a user's request, make the appropriate tool calls, 21 | and return the results.""", 22 | server_names=["filesystem", "slack"], 23 | ) 24 | 25 | context.config.mcp.servers["filesystem"].args.extend([os.getcwd()]) 26 | 27 | async with slack_agent: 28 | logger.info("slack: Connected to server, calling list_tools...") 29 | result = await slack_agent.list_tools() 30 | logger.info("Tools available:", data=result.model_dump()) 31 | 32 | llm = await slack_agent.attach_llm(OpenAIAugmentedLLM) 33 | result = await llm.generate_str( 34 | message="What was the last message in the general channel?", 35 | ) 36 | logger.info(f"Result: {result}") 37 | 38 | # Multi-turn conversations 39 | result = await llm.generate_str( 40 | message="Summarize it for me so I can understand it better.", 41 | ) 42 | logger.info(f"Result: {result}") 43 | 44 | 45 | if __name__ == "__main__": 46 | import time 47 | 48 | start = time.time() 49 | asyncio.run(example_usage()) 50 | end = time.time() 51 | t = end - start 52 | 53 | print(f"Total run time: {t:.2f}s") 54 | -------------------------------------------------------------------------------- /examples/usecases/mcp_basic_slack_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | mcp: 4 | servers: 5 | slack: 6 | command: "npx" 7 | args: ["-y", "@modelcontextprotocol/server-slack"] 8 | # consider defining sensitive values in a separate mnemo_agent.secrets.yaml file 9 | # env: 10 | # SLACK_BOT_TOKEN: "xoxb-your-bot-token" 11 | # SLACK_TEAM_ID": "T01234567" 12 | fetch: 13 | command: "uvx" 14 | args: ["mcp-server-fetch"] 15 | filesystem: 16 | command: "npx" 17 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 18 | 19 | openai: 20 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 21 | default_model: gpt-4o 22 | -------------------------------------------------------------------------------- /examples/usecases/mcp_basic_slack_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | 9 | mcp: 10 | servers: 11 | slack: 12 | env: 13 | SLACK_BOT_TOKEN: "xoxb-your-bot-token" 14 | SLACK_TEAM_ID: "T01234567" -------------------------------------------------------------------------------- /examples/usecases/mcp_basic_slack_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/usecases/mcp_browser_agent/README.md: -------------------------------------------------------------------------------- 1 | # 🌐 Browser Console Agent 2 | 3 | A command-line application that lets you interact with websites using natural language through the Model Context Protocol (MCP). 4 | 5 | ![browser agent](https://andrew-dev-s3.s3.us-east-1.amazonaws.com/browser-agent.gif) 6 | 7 | ## Features 8 | 9 | - **Natural Language Control**: Navigate and interact with websites using conversational commands 10 | - **Continuous Browser Session**: Keep the same browser context across multiple queries 11 | - **Real-time Website Analysis**: Extract information, analyze content, and take screenshots 12 | - **Interactive Console Interface**: Simple terminal-based interface for browsing the web 13 | 14 | ## Requirements 15 | 16 | - Python 3.13+ 17 | - Node.js and npm (for the MCP Puppeteer server) 18 | - OpenAI API key 19 | 20 | ## Installation 21 | 22 | 1. Install using `uv`: 23 | ```bash 24 | uv pip install . 25 | ``` 26 | 27 | 2. Make sure Node.js and npm are installed: 28 | ```bash 29 | node --version 30 | npm --version 31 | ``` 32 | 33 | 3. Configure your API keys in `mnemo_agent.secrets.yaml`: 34 | ```yaml 35 | openai: 36 | api_key: your-openai-api-key 37 | ``` 38 | 39 | ## Usage 40 | 41 | 1. Run the console app: 42 | ```bash 43 | uv run console_agent.py [URL] 44 | ``` 45 | If no URL is provided, it defaults to the Wikipedia page for large language models. 46 | 47 | 2. Type commands to interact with the webpage or select numbered options 48 | 3. Type `exit` or `quit` to end the session 49 | 50 | ## Example Commands 51 | 52 | - "Summarize the content on this page" 53 | - "Click on the 'Documentation' link" 54 | - "Fill out the contact form with this information..." 55 | - "Find all links on this page" 56 | - "Navigate to the pricing page" 57 | - "Extract the main headings from this article" 58 | - "Take a screenshot of the current page" 59 | 60 | ## How It Works 61 | 62 | The Browser Console Agent uses: 63 | - **MCP Agent**: Agent framework for Model Context Protocol servers 64 | - **Puppeteer Server**: Provides browser automation capabilities 65 | - **OpenAI**: Powers natural language understanding and generation 66 | 67 | The app maintains a continuous browser session, allowing you to: 68 | 1. Browse websites with natural language commands 69 | 2. Maintain cookies and session state between queries 70 | 3. Navigate through websites as if you were using them directly 71 | 72 | ## Troubleshooting 73 | 74 | - Make sure Node.js and npm are properly installed 75 | - Check that your OpenAI API key is correctly configured in `mnemo_agent.secrets.yaml` 76 | - If you encounter issues with the Puppeteer server, ensure you have a compatible browser installed 77 | -------------------------------------------------------------------------------- /examples/usecases/mcp_browser_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | transports: [console, file] 6 | level: info 7 | show_progress: true 8 | path: "logs/browser_agent.jsonl" 9 | path_settings: 10 | path_pattern: "logs/browser_agent_{unique_id}.jsonl" 11 | unique_id: "timestamp" 12 | timestamp_format: "%Y%m%d_%H%M%S" 13 | 14 | mcp: 15 | servers: 16 | puppeteer: 17 | command: "npx" 18 | args: [ 19 | "-y", 20 | "@modelcontextprotocol/server-puppeteer" 21 | ] -------------------------------------------------------------------------------- /examples/usecases/mcp_browser_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/usecases/mcp_browser_agent/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "browser-mcp-agent" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "colorama>=0.4.6", 9 | "mcp-agent>=0.0.14", 10 | ] 11 | -------------------------------------------------------------------------------- /examples/usecases/mcp_github_to_slack_agent/README.md: -------------------------------------------------------------------------------- 1 | # GitHub PRs to Slack Summary Agent 2 | 3 | This application creates an MCP Agent that monitors GitHub pull requests and submits prioritized summaries to Slack. The agent uses a LLM to analyze PR information, prioritize issues, and create informative summaries. 4 | 5 | ## How It Works 6 | 7 | 1. The application connects to both GitHub and Slack via their respective MCP servers 8 | 2. The agent retrieves the latest pull requests from a specified GitHub repository 9 | 3. It analyzes each PR and prioritizes them based on importance factors: 10 | - PRs marked as high priority or urgent 11 | - PRs addressing security vulnerabilities 12 | - PRs fixing critical bugs 13 | - PRs blocking other work 14 | - PRs that have been open for a long time 15 | 4. The agent formats a professional summary of high-priority items 16 | 5. The summary is posted to the specified Slack channel 17 | 18 | ## Setup 19 | 20 | ### Prerequisites 21 | 22 | - Python 3.10 or higher 23 | - MCP Agent framework 24 | - [GitHub MCP Server](https://github.com/github/github-mcp-server)) 25 | - [Slack MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/slack) 26 | - Node.js and npm (this is for the Slack server) 27 | - [Docker](https://www.docker.com/) 28 | - Access to a GitHub repository 29 | - Access to a Slack workspace 30 | 31 | ### Getting a Slack Bot Token and Team ID 32 | 33 | 1. Head to [Slack API apps](https://api.slack.com/apps) 34 | 35 | 2. Create a **New App** 36 | 37 | 3. Click on the option to **Create from scratch** 38 | 39 | 4. In the app view, go to **OAuth & Permissions** on the left-hand navigation 40 | 41 | 5. Copy the **Bot User OAuth Token** 42 | 43 | 6. *[Optional] In OAuth & Permissions, add chat:write, users:read, im:history, chat:write.public to the Bot Token Scopes* 44 | 45 | 7. For **Team ID**, go to the browser and log into your workspace. 46 | 47 | 8. In the browser, take the **TEAM ID** from the url: `https://app.slack.com/client/TEAM_ID` 48 | 49 | 9. Add the **OAuth Token** and the **Team ID** to your `mnemo_agent.secrets.yaml` file 50 | 51 | 10. *[Optional] Make sure to launch and install your Slack bot to your workspace. And, invite the new bot to the channel you want to interact with.* 52 | 53 | ### Installation 54 | 55 | 1. Install dependencies: 56 | ``` 57 | uv sync --dev 58 | ``` 59 | 60 | 2. Create a `mnemo_agent.secrets.yaml` secrets file 61 | 62 | 3. Update the secrets file with your API keys and Tokens 63 | 64 | ### Usage 65 | 66 | Run the application with: 67 | ``` 68 | uv run main.py --owner --repo --channel 69 | ``` 70 | -------------------------------------------------------------------------------- /examples/usecases/mcp_github_to_slack_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | execution_engine: asyncio 2 | logger: 3 | transports: [console, file] 4 | level: info 5 | show_progress: true 6 | path: "logs/github-to-slack.jsonl" 7 | path_settings: 8 | path_pattern: "logs/github-to-slack-{unique_id}.jsonl" 9 | unique_id: "timestamp" 10 | timestamp_format: "%Y%m%d_%H%M%S" 11 | 12 | mcp: 13 | servers: 14 | github: 15 | command: "docker" 16 | args: [ 17 | "run", 18 | "-i", 19 | "--rm", 20 | "-e", 21 | "GITHUB_PERSONAL_ACCESS_TOKEN", 22 | "ghcr.io/github/github-mcp-server" 23 | ] 24 | description: "Access GitHub API operations" 25 | slack: 26 | command: "npx" 27 | args: ["-y", "@modelcontextprotocol/server-slack"] 28 | description: "Access Slack API operations" 29 | -------------------------------------------------------------------------------- /examples/usecases/mcp_github_to_slack_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | mcp: 4 | servers: 5 | # Slack configuration 6 | # Create a Slack Bot Token and get your Team ID 7 | # https://api.slack.com/apps 8 | slack: 9 | env: 10 | SLACK_BOT_TOKEN: "xoxb-your-bot-token" 11 | SLACK_TEAM_ID: "T01234567" 12 | 13 | # GitHub configuration 14 | # Create a GitHub Personal Access Token with repo scope 15 | # https://github.com/settings/tokens 16 | github: 17 | env: 18 | GITHUB_PERSONAL_ACCESS_TOKEN: "github_pat_your-access-token" 19 | 20 | anthropic: 21 | api_key: your-anthropic-api-key -------------------------------------------------------------------------------- /examples/usecases/mcp_github_to_slack_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | mcp-agent>=0.0.14 2 | anthropic>=0.48.0 3 | instructor[anthropic]>=1.7.2 -------------------------------------------------------------------------------- /examples/usecases/mcp_playwright_agent/README.md: -------------------------------------------------------------------------------- 1 | # LinkedIn Candidate Search & CSV Export Tool 2 | 3 | This tool uses playwright and filesystems MCP servers and automates searching LinkedIn for candidates matching specific criteria and exports their details to a CSV file. 4 | 5 | ## Overview 6 | 7 | The script (`main_csv.py`) uses the Model Context Protocol (MCP) framework to: 8 | 1. Search LinkedIn for candidates based on user-provided criteria 9 | 2. Extract candidate profile information 10 | 3. Export qualified candidates to a CSV file 11 | 12 | ## Prerequisites 13 | 14 | - Python 3.10 15 | - Node.js (for Playwright) 16 | - MCP Agent configuration files: 17 | - `mnemo_agent.config.yaml` 18 | - `mnemo_agent.secrets.yaml` (with LinkedIn credentials) 19 | 20 | ## Required MCP Servers 21 | 22 | The tool uses two MCP servers: 23 | 1. **Playwright Server**: Handles browser automation for LinkedIn interaction 24 | - Command: `npx @playwright/mcp@latest` 25 | 2. **Filesystem Server**: Manages CSV file operations 26 | - Command: `npx @modelcontextprotocol/server-filesystem` 27 | 28 | ## Configuration 29 | 30 | 1. Set up `mnemo_agent.config.yaml` with: 31 | - Server configurations for Playwright and Filesystem 32 | - Logging settings 33 | - Execution engine settings 34 | 35 | 2. Configure `mnemo_agent.secrets.yaml` with: 36 | - LinkedIn credentials (username and password) 37 | - OpenAI API key 38 | - Filesystem paths 39 | 40 | ## Usage 41 | uv run main.py --criteria "Python developers in San Francisco" --max-results 7 --output "/desktop/JOB.csv" 42 | Run the script from the command line using: uv run main.py --criteria "THE POSITION YOU ARE LOOKING FOR" --max-results NUMBER OF MAX RESULTS --output "LOCATION OF SAVED RESULTS" -------------------------------------------------------------------------------- /examples/usecases/mcp_playwright_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | 5 | logger: 6 | transports: [console, file] 7 | level: debug 8 | show_progress: true 9 | path: "logs/linkedin-to-filesystem.jsonl" 10 | path_settings: 11 | path_pattern: "logs/linkedin-to-filesystem-{unique_id}.jsonl" 12 | unique_id: "timestamp" 13 | timestamp_format: "%Y%m%d_%H%M%S" 14 | 15 | mcp: 16 | servers: 17 | playwright: 18 | command: "npx" 19 | args: ["@playwright/mcp@latest"] 20 | description: "Drive browser automation via Playwright" 21 | filesystem: 22 | command: "npx" 23 | args: [ 24 | "-y", 25 | "@modelcontextprotocol/server-filesystem", 26 | "FILESYSTEM_PATH"] 27 | description: "Access Filesystem operations" 28 | -------------------------------------------------------------------------------- /examples/usecases/mcp_playwright_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | -------------------------------------------------------------------------------- /examples/usecases/mcp_playwright_agent/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "updated" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "mcp-agent>=0.0.14", 9 | ] 10 | -------------------------------------------------------------------------------- /examples/usecases/mcp_researcher/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | import os 4 | from pathlib import Path 5 | 6 | from mnemo_agent.app import MCPApp 7 | from mnemo_agent.agents.agent import Agent 8 | from mnemo_agent.mcp.mcp_connection_manager import MCPConnectionManager 9 | from mnemo_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM # noqa: F401 10 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 11 | from mnemo_agent.logging.logger import LoggingConfig 12 | from rich import print 13 | 14 | app = MCPApp(name="mnemo_mcp_root_test") 15 | 16 | 17 | async def example_usage(): 18 | async with app.run() as agent_app: 19 | folder_path = Path("agent_folder") 20 | folder_path.mkdir(exist_ok=True) 21 | 22 | context = agent_app.context 23 | 24 | # Overwrite the config because full path to agent folder needs to be passed 25 | context.config.mcp.servers["interpreter"].args = [ 26 | "run", 27 | "-i", 28 | "--rm", 29 | "--pull=always", 30 | "-v", 31 | f"{os.path.abspath('agent_folder')}:/mnt/data/", 32 | "ghcr.io/evalstate/mcp-py-repl:latest", 33 | ] 34 | 35 | async with MCPConnectionManager(context.server_registry): 36 | interpreter_agent = Agent( 37 | name="research", 38 | instruction="""You are a research assistant, with access to internet search (via Brave), 39 | website fetch, a python interpreter (you can install packages with uv) and a filesystem. 40 | The working directory for the Python Interpreter is shared by the 'Filesystem' tool. 41 | You can use the working directory to save and create files, and to process them with the Python Interpreter""", 42 | server_names=["brave", "interpreter", "filesystem", "fetch"], 43 | ) 44 | 45 | research_prompt = """Produce an investment report for the company Eutelsat. The final report should be saved in the filesystem in markdown format, and 46 | contain at least the following: 47 | 1 - A brief description of the company 48 | 2 - Current financial position (find data, create and incorporate charts) 49 | 3 - A PESTLE analysis 50 | 4 - An investment thesis for the next 3 years. Include both 'buy side' and 'sell side' arguments, and a final 51 | summary and recommendation. 52 | Todays date is 05 February 2025. Include the main data sources consulted in presenting the report.""" 53 | 54 | try: 55 | llm_oai = await interpreter_agent.attach_llm(OpenAIAugmentedLLM) 56 | # llm_anthr = await interpreter_agent.attach_llm(AnthropicAugmentedLLM) # noqa: F841 57 | 58 | result = await llm_oai.generate_str(research_prompt) 59 | print(result) 60 | 61 | finally: 62 | # Clean up the agent 63 | await interpreter_agent.close() 64 | 65 | # Ensure logging is properly shutdown 66 | await LoggingConfig.shutdown() 67 | 68 | 69 | if __name__ == "__main__": 70 | start = time.time() 71 | try: 72 | asyncio.run(example_usage()) 73 | except KeyboardInterrupt: 74 | print("\nReceived keyboard interrupt, shutting down gracefully...") 75 | except Exception as e: 76 | print(f"Error during execution: {e}") 77 | raise 78 | finally: 79 | end = time.time() 80 | t = end - start 81 | print(f"Total run time: {t:.2f}s") 82 | -------------------------------------------------------------------------------- /examples/usecases/mcp_researcher/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: file 6 | level: info 7 | 8 | mcp: 9 | servers: 10 | brave: 11 | command: "npx" 12 | args: ["-y", "@modelcontextprotocol/server-brave-search"] 13 | interpreter: 14 | command: "docker" 15 | args: 16 | [ 17 | "run", 18 | "-i", 19 | "--rm", 20 | "--pull=always", 21 | "-v", 22 | "./agent_folder:/mnt/data/", 23 | "ghcr.io/evalstate/mcp-py-repl:latest", 24 | ] 25 | roots: 26 | - uri: "file://./agent_folder/" 27 | name: "agent_folder" 28 | server_uri_alias: "file:///mnt/data/" 29 | fetch: 30 | command: "uvx" 31 | args: ["mcp-server-fetch"] 32 | filesystem: 33 | command: "npx" 34 | args: ["-y", "@modelcontextprotocol/server-filesystem", "./agent_folder/"] 35 | 36 | openai: 37 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 38 | default_model: o3-mini 39 | reasoning_effort: high 40 | -------------------------------------------------------------------------------- /examples/usecases/mcp_researcher/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | mcp: 4 | servers: 5 | brave: 6 | env: 7 | BRAVE_API_KEY: 8 | 9 | openai: 10 | api_key: openai_api_key 11 | 12 | anthropic: 13 | api_key: anthropic_api_key 14 | -------------------------------------------------------------------------------- /examples/usecases/mcp_researcher/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | # Additional dependencies specific to this example 6 | anthropic 7 | openai 8 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_basic_agent/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit MCP Agent example 2 | 3 | This Streamlit example shows a "finder" Agent which has access to the 'fetch' and 'filesystem' MCP servers. 4 | 5 | You can ask it information about local files or URLs, and it will make the determination on what to use at what time to satisfy the request. 6 | 7 | To run this example: 8 | 9 | With uv: 10 | 11 | ```bash 12 | uv pip install -r requirements.txt 13 | uv run streamlit run main.py 14 | ``` 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_basic_agent/hello.py: -------------------------------------------------------------------------------- 1 | from rich import print 2 | 3 | 4 | def main(): 5 | print("Hello from streamlit-mcp-basic-agent!") 6 | 7 | 8 | if __name__ == "__main__": 9 | main() 10 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_basic_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | progress_display: false 14 | 15 | mcp: 16 | servers: 17 | fetch: 18 | command: "uvx" 19 | args: ["mcp-server-fetch"] 20 | filesystem: 21 | command: "npx" 22 | args: ["-y", "@modelcontextprotocol/server-filesystem", "."] 23 | 24 | openai: 25 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 26 | default_model: gpt-4o 27 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_basic_agent/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_basic_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai 6 | streamlit -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_rag_agent/README.md: -------------------------------------------------------------------------------- 1 | # Streamlit MCP RAG Agent example 2 | 3 | This Streamlit example shows a RAG Agent that is able to augment its responses using data from Qdrant vector database. 4 | 5 | ## Usage 6 | 7 | Download latest Qdrant image from Dockerhub: 8 | 9 | ```bash 10 | docker pull qdrant/qdrant 11 | ``` 12 | 13 | Then, run the Qdrant server locally with docker: 14 | 15 | ```bash 16 | docker run -p 6333:6333 -v $(pwd)/qdrant_storage:/qdrant/storage qdrant/qdrant 17 | ``` 18 | 19 | Finally, run the example: 20 | 21 | ```bash 22 | uv pip install -r requirements.txt 23 | uv run streamlit run main.py 24 | ``` 25 | 26 | Image 27 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_rag_agent/agent_state.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional, Type, TypeVar 3 | import streamlit as st 4 | from mnemo_agent.agents.agent import Agent 5 | from mnemo_agent.workflows.llm.augmented_llm_openai import ( 6 | AugmentedLLM, 7 | ) 8 | 9 | T = TypeVar("T", bound=AugmentedLLM) 10 | 11 | 12 | @dataclass 13 | class AgentState: 14 | """Container for agent and its associated LLM""" 15 | 16 | agent: Agent 17 | llm: Optional[AugmentedLLM] = None 18 | 19 | 20 | async def get_agent_state( 21 | key: str, 22 | agent_class: Type[Agent], 23 | llm_class: Optional[Type[T]] = None, 24 | **agent_kwargs, 25 | ) -> AgentState: 26 | """ 27 | Get or create agent state, reinitializing connections if retrieved from session. 28 | 29 | Args: 30 | key: Session state key 31 | agent_class: Agent class to instantiate 32 | llm_class: Optional LLM class to attach 33 | **agent_kwargs: Arguments for agent instantiation 34 | """ 35 | if key not in st.session_state: 36 | # Create new agent 37 | agent = agent_class( 38 | connection_persistence=False, 39 | **agent_kwargs, 40 | ) 41 | await agent.initialize() 42 | 43 | # Attach LLM if specified 44 | llm = None 45 | if llm_class: 46 | llm = await agent.attach_llm(llm_class) 47 | 48 | state: AgentState = AgentState(agent=agent, llm=llm) 49 | st.session_state[key] = state 50 | else: 51 | state = st.session_state[key] 52 | 53 | return state 54 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_rag_agent/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | progress_display: false 14 | 15 | mcp: 16 | servers: 17 | qdrant: 18 | command: "uvx" 19 | args: ["mcp-server-qdrant"] 20 | env: 21 | { 22 | "QDRANT_URL": "http://localhost:6333", 23 | "COLLECTION_NAME": "my_collection", 24 | "EMBEDDING_MODEL": "BAAI/bge-small-en-v1.5", 25 | } 26 | 27 | openai: 28 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 29 | default_model: gpt-4o-mini 30 | -------------------------------------------------------------------------------- /examples/usecases/streamlit_mcp_rag_agent/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | openai 6 | streamlit 7 | qdrant-client 8 | fastembed 9 | -------------------------------------------------------------------------------- /examples/workflows/workflow_evaluator_optimizer/README.md: -------------------------------------------------------------------------------- 1 | # Evaluator-Optimizer Workflow example 2 | 3 | To illustrate an evaluator-optimizer workflow, we will build a job cover letter refinement system, 4 | which generates a draft based on job description, company information, and candidate details. 5 | 6 | Then the evaluator reviews the letter, provides a quality rating, and offers actionable feedback. 7 | The cycle continues until the letter meets a predefined quality standard. 8 | 9 | To make things interesting, we specify the company information as a URL, expecting the agent to fetch 10 | it using the MCP 'fetch' server, and then using that information to generate the cover letter. 11 | -------------------------------------------------------------------------------- /examples/workflows/workflow_evaluator_optimizer/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | fetch: 17 | command: "uvx" 18 | args: ["mcp-server-fetch"] 19 | filesystem: 20 | command: "npx" 21 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 22 | 23 | openai: 24 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 25 | default_model: gpt-4o 26 | -------------------------------------------------------------------------------- /examples/workflows/workflow_evaluator_optimizer/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/workflows/workflow_evaluator_optimizer/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/workflows/workflow_intent_classifier/README.md: -------------------------------------------------------------------------------- 1 | # MCP Agent Intent Classification Workflow 2 | 3 | This example shows using intent classification workflow, which is a close sibling of the [router workflow](../workflow_router/). 4 | 5 | You can use both LLM-based classification, and embedding-based classification for this purpose. 6 | -------------------------------------------------------------------------------- /examples/workflows/workflow_intent_classifier/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from rich import print 3 | 4 | from mnemo_agent.app import MCPApp 5 | from mnemo_agent.workflows.intent_classifier.intent_classifier_base import Intent 6 | from mnemo_agent.workflows.intent_classifier.intent_classifier_llm_openai import ( 7 | OpenAILLMIntentClassifier, 8 | ) 9 | from mnemo_agent.workflows.intent_classifier.intent_classifier_embedding_openai import ( 10 | OpenAIEmbeddingIntentClassifier, 11 | ) 12 | 13 | app = MCPApp(name="intent_classifier") 14 | 15 | 16 | async def example_usage(): 17 | async with app.run() as intent_app: 18 | logger = intent_app.logger 19 | context = intent_app.context 20 | logger.info("Current config:", data=context.config.model_dump()) 21 | 22 | embedding_intent_classifier = OpenAIEmbeddingIntentClassifier( 23 | intents=[ 24 | Intent( 25 | name="greeting", 26 | description="A friendly greeting", 27 | examples=["Hello", "Hi there", "Good morning"], 28 | ), 29 | Intent( 30 | name="farewell", 31 | description="A friendly farewell", 32 | examples=["Goodbye", "See you later", "Take care"], 33 | ), 34 | ], 35 | context=context, 36 | ) 37 | 38 | results = await embedding_intent_classifier.classify( 39 | request="Hello, how are you?", 40 | top_k=1, 41 | ) 42 | 43 | logger.info("LLM-based Intent classification results:", data=results) 44 | 45 | llm_intent_classifier = OpenAILLMIntentClassifier( 46 | intents=[ 47 | Intent( 48 | name="greeting", 49 | description="A friendly greeting", 50 | examples=["Hello", "Hi there", "Good morning"], 51 | ), 52 | Intent( 53 | name="farewell", 54 | description="A friendly farewell", 55 | examples=["Goodbye", "See you later", "Take care"], 56 | ), 57 | ], 58 | context=context, 59 | ) 60 | 61 | results = await llm_intent_classifier.classify( 62 | request="Hello, how are you?", 63 | top_k=1, 64 | ) 65 | 66 | logger.info("LLM-based Intent classification results:", data=results) 67 | 68 | 69 | if __name__ == "__main__": 70 | import time 71 | 72 | start = time.time() 73 | asyncio.run(example_usage()) 74 | end = time.time() 75 | t = end - start 76 | 77 | print(f"Total run time: {t:.2f}s") 78 | -------------------------------------------------------------------------------- /examples/workflows/workflow_intent_classifier/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | path: "router.jsonl" 8 | 9 | mcp: 10 | servers: 11 | fetch: 12 | command: "uvx" 13 | args: ["mcp-server-fetch"] 14 | filesystem: 15 | command: "npx" 16 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 17 | 18 | openai: 19 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 20 | default_model: "gpt-4o-mini" 21 | -------------------------------------------------------------------------------- /examples/workflows/workflow_intent_classifier/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/workflows/workflow_intent_classifier/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/workflows/workflow_orchestrator_worker/README.md: -------------------------------------------------------------------------------- 1 | # Orchestrator workflow example 2 | 3 | This example shows an Orchestrator workflow which dynamically plans across a number of agents to accomplish a multi-step task. 4 | 5 | It parallelizes the task executions where possible, and continues execution until the objective is attained. 6 | 7 | This particular example is a student assignment grader, which requires: 8 | 9 | - Finding the student's assignment in a short_story.md on disk (using MCP filesystem server) 10 | - Using proofreader, fact checker and style enforcer agents to evaluate the quality of the report 11 | - The style enforcer requires reading style guidelines from the APA website using the MCP fetch server. 12 | - Writing the graded report to disk (using MCP filesystem server) 13 | 14 | Image 15 | -------------------------------------------------------------------------------- /examples/workflows/workflow_orchestrator_worker/graded_report.md: -------------------------------------------------------------------------------- 1 | # Graded Report for 'The Battle of Glimmerwood' 2 | 3 | ## Proofreading Feedback 4 | 5 | ### Grammar, Spelling, and Punctuation Errors 6 | 1. "know" should be "known." 7 | 2. "who were live" should be "who lived." 8 | 3. "shimmer" should be "shimmered." 9 | 4. "shaterred" should be "shattered." 10 | 5. "attack" should be "attacked." 11 | 6. "Lead" should be "Led." 12 | 7. "aim" should be "aimed." 13 | 8. "was" should be "were." 14 | 9. "choas" should be "chaos." 15 | 10. Comma after "ground" should be replaced with a period or semicolon for better clarity. 16 | 11. Apostrophe correctness: "forests" should be "forest's." 17 | 12. "aproached" should be "approached." 18 | 13. Comma after "light" should be replaced with a period or semicolon. 19 | 14. "captured" should be "capture." 20 | 15. Comma needed after "celebrated." 21 | 22 | ### Awkward Phrasing 23 | - "In the heart of Glimmerwood, a mystical forest knowed for its radiant trees, a small village thrived." could be clearer. Consider rephrasing to: "In the heart of Glimmerwood, a mystical forest known for its radiant trees, thrived a small village." 24 | - "Whispers of a hidden agenda linger among the villagers" should be "lingered among the villagers" to maintain past tense consistency. 25 | 26 | ### Structural Issues 27 | - The paragraph detailing Elara’s bravery is slightly long and could be split for easier reading. Consider creating a new paragraph starting from "Using the forest's natural defenses..." 28 | 29 | ## Factuality and Logical Consistency Feedback 30 | 31 | ### Setting and Context Consistency 32 | - The story establishes a mystical setting in Glimmerwood, consistent throughout the narrative as a coherent backdrop for the events. 33 | 34 | ### Character Actions and Logic 35 | - Elara's actions fit her as courageous and resourceful, rallying villagers and devising plans is typical heroic trope. 36 | - The villagers' complex defensive maneuver execution might appear abrupt without prior preparedness hints. 37 | 38 | ### Plot Developments 39 | - Transition from peace to chaos is clear; however, the village's advanced trap needs context for feasibility. 40 | - Emphasize the villagers and Glimmerfoxes bond to improve plausibility. 41 | 42 | ### Logical Consistencies and Potential Contradictions 43 | - Quick transition from peace to orchestrated defense plan without backstory mars believability. 44 | - Clarify immortality belief in Glimmerstones for internal logic consistency. 45 | 46 | ### Conclusion 47 | - Narrative mostly consistent, needs exposition on villagers’ and forest's bond, and Elara's strategy. 48 | 49 | ## Style Adherence and Additional Feedback 50 | 51 | ### Narrative Flow 52 | - Well described beginning and open-ended ending for intrigue. 53 | - Detailed execution needed in Elara's plan to enhance reading engagement. 54 | 55 | ### Clarity of Expression 56 | - Correction of issues such as "knowed" to "known" necessary. 57 | - Improve sentence structure for clarity and flow. 58 | 59 | ### Tone 60 | - Consistent magical realism tone, suitable for heroism tales. 61 | - Emotional depth, particularly involving Elara's perspective, would enrich the tone. 62 | 63 | ### APA Style Adherence 64 | - Correct punctuation and capitalization help with professional readability. Although creative writing isn't APA-focused, basic consistency applies. 65 | 66 | ### Suggestions for Improvement 67 | 1. **Enhance Detail in Key Scenes**: More drama in Elara's plan execution. 68 | 2. **Correct Grammatical Errors**: Ensure clear, structured sentences. 69 | 3. **Enrich Character Development**: Delve into Elara’s thoughts for engagement. 70 | 4. **Apply Simple APA Elements**: Consistency in writing will improve readability. 71 | 72 | --- 73 | 74 | By addressing these areas, the story should improve in coherence, engagement, and overall readability. -------------------------------------------------------------------------------- /examples/workflows/workflow_orchestrator_worker/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | fetch: 17 | command: "uvx" 18 | args: ["mcp-server-fetch"] 19 | filesystem: 20 | command: "npx" 21 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 22 | 23 | openai: 24 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 25 | default_model: gpt-4o 26 | -------------------------------------------------------------------------------- /examples/workflows/workflow_orchestrator_worker/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/workflows/workflow_orchestrator_worker/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/workflows/workflow_orchestrator_worker/short_story.md: -------------------------------------------------------------------------------- 1 | ## The Battle of Glimmerwood 2 | 3 | In the heart of Glimmerwood, a mystical forest knowed for its radiant trees, a small village thrived. 4 | The villagers, who were live peacefully, shared their home with the forest's magical creatures, 5 | especially the Glimmerfoxes whose fur shimmer like moonlight. 6 | 7 | One fateful evening, the peace was shaterred when the infamous Dark Marauders attack. 8 | Lead by the cunning Captain Thorn, the bandits aim to steal the precious Glimmerstones which was believed to grant immortality. 9 | 10 | Amidst the choas, a young girl named Elara stood her ground, she rallied the villagers and devised a clever plan. 11 | Using the forests natural defenses they lured the marauders into a trap. 12 | As the bandits aproached the village square, a herd of Glimmerfoxes emerged, blinding them with their dazzling light, 13 | the villagers seized the opportunity to captured the invaders. 14 | 15 | Elara's bravery was celebrated and she was hailed as the "Guardian of Glimmerwood". 16 | The Glimmerstones were secured in a hidden grove protected by an ancient spell. 17 | 18 | However, not all was as it seemed. The Glimmerstones true power was never confirm, 19 | and whispers of a hidden agenda linger among the villagers. 20 | -------------------------------------------------------------------------------- /examples/workflows/workflow_parallel/README.md: -------------------------------------------------------------------------------- 1 | # MCP Agent example 2 | 3 | This example shows a "finder" Agent which has access to the 'fetch' and 'filesystem' MCP servers. 4 | 5 | You can ask it information about local files or URLs, and it will make the determination on what to use at what time to satisfy the request. 6 | -------------------------------------------------------------------------------- /examples/workflows/workflow_parallel/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from mnemo_agent.app import MCPApp 4 | from mnemo_agent.agents.agent import Agent 5 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 6 | 7 | # from mnemo_agent.workflows.parallel.fan_in import FanIn 8 | # from mnemo_agent.workflows.parallel.fan_out import FanOut 9 | from mnemo_agent.workflows.parallel.parallel_llm import ParallelLLM 10 | from rich import print 11 | # To illustrate a parallel workflow, we will build a student assignment grader,`` 12 | # which will use a fan-out agent to grade the assignment in parallel using multiple agents, 13 | # and a fan-in agent to aggregate the results and provide a final grade. 14 | 15 | SHORT_STORY = """ 16 | The Battle of Glimmerwood 17 | 18 | In the heart of Glimmerwood, a mystical forest knowed for its radiant trees, a small village thrived. 19 | The villagers, who were live peacefully, shared their home with the forest's magical creatures, 20 | especially the Glimmerfoxes whose fur shimmer like moonlight. 21 | 22 | One fateful evening, the peace was shaterred when the infamous Dark Marauders attack. 23 | Lead by the cunning Captain Thorn, the bandits aim to steal the precious Glimmerstones which was believed to grant immortality. 24 | 25 | Amidst the choas, a young girl named Elara stood her ground, she rallied the villagers and devised a clever plan. 26 | Using the forests natural defenses they lured the marauders into a trap. 27 | As the bandits aproached the village square, a herd of Glimmerfoxes emerged, blinding them with their dazzling light, 28 | the villagers seized the opportunity to captured the invaders. 29 | 30 | Elara's bravery was celebrated and she was hailed as the "Guardian of Glimmerwood". 31 | The Glimmerstones were secured in a hidden grove protected by an ancient spell. 32 | 33 | However, not all was as it seemed. The Glimmerstones true power was never confirm, 34 | and whispers of a hidden agenda linger among the villagers. 35 | """ 36 | 37 | app = MCPApp(name="mcp_parallel_workflow") 38 | 39 | 40 | async def example_usage(): 41 | async with app.run() as short_story_grader: 42 | logger = short_story_grader.logger 43 | 44 | proofreader = Agent( 45 | name="proofreader", 46 | instruction=""""Review the short story for grammar, spelling, and punctuation errors. 47 | Identify any awkward phrasing or structural issues that could improve clarity. 48 | Provide detailed feedback on corrections.""", 49 | ) 50 | 51 | fact_checker = Agent( 52 | name="fact_checker", 53 | instruction="""Verify the factual consistency within the story. Identify any contradictions, 54 | logical inconsistencies, or inaccuracies in the plot, character actions, or setting. 55 | Highlight potential issues with reasoning or coherence.""", 56 | ) 57 | 58 | style_enforcer = Agent( 59 | name="style_enforcer", 60 | instruction="""Analyze the story for adherence to style guidelines. 61 | Evaluate the narrative flow, clarity of expression, and tone. Suggest improvements to 62 | enhance storytelling, readability, and engagement.""", 63 | ) 64 | 65 | grader = Agent( 66 | name="grader", 67 | instruction="""Compile the feedback from the Proofreader, Fact Checker, and Style Enforcer 68 | into a structured report. Summarize key issues and categorize them by type. 69 | Provide actionable recommendations for improving the story, 70 | and give an overall grade based on the feedback.""", 71 | ) 72 | 73 | parallel = ParallelLLM( 74 | fan_in_agent=grader, 75 | fan_out_agents=[proofreader, fact_checker, style_enforcer], 76 | llm_factory=OpenAIAugmentedLLM, 77 | ) 78 | 79 | result = await parallel.generate_str( 80 | message=f"Student short story submission: {SHORT_STORY}", 81 | ) 82 | 83 | logger.info(f"{result}") 84 | 85 | 86 | if __name__ == "__main__": 87 | import time 88 | 89 | start = time.time() 90 | asyncio.run(example_usage()) 91 | end = time.time() 92 | t = end - start 93 | 94 | print(f"Total run time: {t:.2f}s") 95 | -------------------------------------------------------------------------------- /examples/workflows/workflow_parallel/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | # workflow_parallel 2 | $schema: ../../../schema/mcp-agent.config.schema.json 3 | 4 | execution_engine: asyncio 5 | logger: 6 | type: console 7 | level: debug 8 | path: "./workflow_parallel.jsonl" 9 | batch_size: 100 10 | flush_interval: 2 11 | max_queue_size: 2048 12 | http_endpoint: 13 | http_headers: 14 | http_timeout: 5 15 | 16 | openai: 17 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 18 | default_model: "gpt-4o" 19 | -------------------------------------------------------------------------------- /examples/workflows/workflow_parallel/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/workflows/workflow_parallel/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/workflows/workflow_router/README.md: -------------------------------------------------------------------------------- 1 | # MCP Agent example 2 | 3 | This example shows a "finder" Agent which has access to the 'fetch' and 'filesystem' MCP servers. 4 | 5 | You can ask it information about local files or URLs, and it will make the determination on what to use at what time to satisfy the request. 6 | -------------------------------------------------------------------------------- /examples/workflows/workflow_router/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: debug 7 | path: "router.jsonl" 8 | 9 | mcp: 10 | servers: 11 | fetch: 12 | command: "uvx" 13 | args: ["mcp-server-fetch"] 14 | filesystem: 15 | command: "npx" 16 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 17 | 18 | openai: 19 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 20 | default_model: "gpt-4o-mini" 21 | -------------------------------------------------------------------------------- /examples/workflows/workflow_router/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/workflows/workflow_router/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/README.md: -------------------------------------------------------------------------------- 1 | # MCP Swarm Agent 2 | 3 | mcp-agent implements [OpenAI's Swarm pattern](https://github.com/openai/swarm) for multi-agent workflows, but in a way that can be used with any model provider. 4 | 5 | **This example is taken from the [Swarm repo](https://github.com/openai/swarm/blob/main/examples/airline), and shown to work with MCP servers and Anthropic models (and can of course also work with OpenAI models).** 6 | 7 | This example demonstrates a multi-agent setup for handling different customer service requests in an airline context using the Swarm framework. The agents can triage requests, handle flight modifications, cancellations, and lost baggage cases. 8 | 9 | https://github.com/user-attachments/assets/b314d75d-7945-4de6-965b-7f21eb14a8bd 10 | 11 | ## Agents 12 | 13 | 1. **Triage Agent**: Determines the type of request and transfers to the appropriate agent. 14 | 2. **Flight Modification Agent**: Handles requests related to flight modifications, further triaging them into: 15 | - **Flight Cancel Agent**: Manages flight cancellation requests. 16 | - **Flight Change Agent**: Manages flight change requests. 17 | 3. **Lost Baggage Agent**: Handles lost baggage inquiries. 18 | -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/mcp_agent.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | execution_engine: asyncio 4 | logger: 5 | type: console 6 | level: info 7 | batch_size: 100 8 | flush_interval: 2 9 | max_queue_size: 2048 10 | http_endpoint: 11 | http_headers: 12 | http_timeout: 5 13 | 14 | mcp: 15 | servers: 16 | fetch: 17 | command: "uvx" 18 | args: ["mcp-server-fetch"] 19 | filesystem: 20 | command: "npx" 21 | args: ["-y", "@modelcontextprotocol/server-filesystem"] 22 | 23 | openai: 24 | # Secrets (API keys, etc.) are stored in an mnemo_agent.secrets.yaml file which can be gitignored 25 | default_model: gpt-4o 26 | -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/mcp_agent.secrets.yaml.example: -------------------------------------------------------------------------------- 1 | $schema: ../../../schema/mcp-agent.config.schema.json 2 | 3 | openai: 4 | api_key: openai_api_key 5 | 6 | anthropic: 7 | api_key: anthropic_api_key 8 | -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/policies/flight_cancellation_policy.md: -------------------------------------------------------------------------------- 1 | ## Flight Cancellation Policy 2 | 3 | 1. Confirm which flight the customer is asking to cancel. 4 | 5 | 1a) If the customer is asking about the same flight, proceed to next step. 6 | 7 | 1b) If the customer is not, call 'escalate_to_agent' function. 8 | 9 | 2. Confirm if the customer wants a refund or flight credits. 10 | 11 | 3. If the customer wants a refund follow step 3a). If the customer wants flight credits move to step 4. 12 | 13 | 3a) Call the initiate_refund function. 14 | 15 | 3b) Inform the customer that the refund will be processed within 3-5 business days. 16 | 17 | 4. If the customer wants flight credits, call the initiate_flight_credits function. 18 | 19 | 4a) Inform the customer that the flight credits will be available in the next 15 minutes. 20 | 21 | 5. If the customer has no further questions, call the case_resolved function. 22 | -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/policies/flight_change_policy.md: -------------------------------------------------------------------------------- 1 | ## Flight Change Policy 2 | 3 | 1. Verify the flight details and the reason for the change request. 4 | 2. Call valid_to_change_flight function: 5 | 6 | 2a) If the flight is confirmed valid to change: proceed to the next step. 7 | 8 | 2b) If the flight is not valid to change: politely let the customer know they cannot change their flight. 9 | 10 | 3. Suggest an flight one day earlier to customer. 11 | 4. Check for availability on the requested new flight: 12 | 13 | 4a) If seats are available, proceed to the next step. 14 | 15 | 4b) If seats are not available, offer alternative flights or advise the customer to check back later. 16 | 17 | 5. Inform the customer of any fare differences or additional charges. 18 | 6. Call the change_flight function. 19 | 7. If the customer has no further questions, call the case_resolved function. 20 | -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/policies/lost_baggage_policy.md: -------------------------------------------------------------------------------- 1 | ## Lost Baggage Policy 2 | 3 | 1. Call the 'initiate_baggage_search' function to start the search process. 4 | 5 | 2. If the baggage is found: 6 | 7 | 2a) Arrange for the baggage to be delivered to the customer's address. 8 | 9 | 3. If the baggage is not found: 10 | 11 | 3a) Call the 'escalate_to_agent' function. 12 | 13 | 4. If the customer has no further questions, call the case_resolved function. 14 | 15 | **Case Resolved: When the case has been resolved, ALWAYS call the "case_resolved" function** 16 | -------------------------------------------------------------------------------- /examples/workflows/workflow_swarm/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core framework dependency 2 | mcp-agent @ file://../../../ # Link to the local mcp-agent project root 3 | 4 | # Additional dependencies specific to this example 5 | anthropic 6 | openai -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mcp-agent" 3 | version = "0.0.20" 4 | description = "Build effective agents with Model Context Protocol (MCP) using simple, composable patterns." 5 | readme = "README.md" 6 | license = { file = "LICENSE" } 7 | authors = [ 8 | { name = "Sarmad Qadri", email = "sarmad@lastmileai.dev" } 9 | ] 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "License :: OSI Approved :: Apache Software License", 13 | "Operating System :: OS Independent" 14 | ] 15 | requires-python = ">=3.10" 16 | dependencies = [ 17 | "fastapi>=0.115.6", 18 | "instructor>=1.7.9", 19 | "mcp>=1.7.1", 20 | "opentelemetry-distro>=0.50b0", 21 | "opentelemetry-exporter-otlp-proto-http>=1.29.0", 22 | "pydantic-settings>=2.7.0", 23 | "pydantic>=2.10.4", 24 | "pyyaml>=6.0.2", 25 | "rich>=13.9.4", 26 | "typer>=0.15.1", 27 | "numpy>=2.1.3", 28 | "scikit-learn>=1.6.0", 29 | "prompt-toolkit>=3.0.50", 30 | "aiohttp>=3.11.13", 31 | "websockets>=12.0", 32 | ] 33 | 34 | [project.optional-dependencies] 35 | temporal = [ 36 | "temporalio>=1.8.0", 37 | ] 38 | anthropic = [ 39 | "anthropic>=0.48.0", 40 | ] 41 | bedrock = [ 42 | "boto3>=1.37.23" 43 | ] 44 | openai = [ 45 | "openai>=1.58.1", 46 | ] 47 | azure = [ 48 | "azure-ai-inference>=1.0.0b9", 49 | ] 50 | google = [ 51 | "google-genai>=1.10.0", 52 | ] 53 | cohere = [ 54 | "cohere>=5.13.4", 55 | ] 56 | 57 | [build-system] 58 | requires = ["hatchling"] 59 | build-backend = "hatchling.build" 60 | 61 | [dependency-groups] 62 | dev = [ 63 | "anthropic>=0.42.0", 64 | "pre-commit>=4.0.1", 65 | "pydantic>=2.10.4", 66 | "pyyaml>=6.0.2", 67 | "ruff>=0.8.4", 68 | "tomli>=2.2.1", 69 | "pytest>=7.4.0", 70 | "pytest-asyncio>=0.21.1", 71 | "boto3-stubs[bedrock-runtime]>=1.37.23", 72 | ] 73 | 74 | [project.scripts] 75 | mcp-agent = "mnemo_agent.cli.__main__:app" 76 | mnemo_agent = "mnemo_agent.cli.__main__:app" 77 | mcpagent = "mnemo_agent.cli.__main__:app" 78 | silsila = "mnemo_agent.cli.__main__:app" 79 | prompt-server = "mnemo_agent.mcp.prompts.__main__:main" 80 | 81 | [tool.setuptools.packages.find] 82 | include = ["mcp-agent"] 83 | 84 | [tool.setuptools.package-data] 85 | mcp-agent = [ # TODO: should this be mnemo_agent? 86 | "data/*.json", 87 | "resources/examples/**/*.py", 88 | "resources/examples/**/*.yaml", 89 | "resources/examples/**/*.yml", 90 | "resources/examples/**/*.csv", 91 | "resources/examples/**/mount-point/*.csv" 92 | ] 93 | 94 | [tool.pytest.ini_options] 95 | pythonpath = ["."] 96 | -------------------------------------------------------------------------------- /scripts/event_replay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Event Replay Script 3 | 4 | Replays events from a JSONL log file using rich_progress display. 5 | """ 6 | 7 | import json 8 | import time 9 | from datetime import datetime 10 | from pathlib import Path 11 | 12 | import typer 13 | from mnemo_agent.event_progress import convert_log_event 14 | from mnemo_agent.logging.events import Event 15 | from mnemo_agent.logging.rich_progress import RichProgressDisplay 16 | 17 | 18 | def load_events(path: Path) -> list[Event]: 19 | """Load events from JSONL file.""" 20 | events = [] 21 | with open(path) as f: 22 | for line in f: 23 | if line.strip(): 24 | raw_event = json.loads(line) 25 | # Convert from log format to event format 26 | event = Event( 27 | type=raw_event.get("level", "info").lower(), 28 | namespace=raw_event.get("namespace", ""), 29 | message=raw_event.get("message", ""), 30 | timestamp=datetime.fromisoformat(raw_event["timestamp"]), 31 | data=raw_event.get("data", {}), # Get data directly 32 | ) 33 | events.append(event) 34 | return events 35 | 36 | 37 | def main(log_file: str): 38 | """Replay MCP Agent events from a log file with progress display.""" 39 | # Load events from file 40 | events = load_events(Path(log_file)) 41 | 42 | # Initialize progress display 43 | progress = RichProgressDisplay() 44 | progress.start() 45 | 46 | try: 47 | # Process each event in sequence 48 | for event in events: 49 | progress_event = convert_log_event(event) 50 | if progress_event: 51 | # Add agent info to the progress event target from data 52 | progress.update(progress_event) 53 | # Add a small delay to make the replay visible 54 | time.sleep(1) 55 | except KeyboardInterrupt: 56 | pass 57 | finally: 58 | progress.stop() 59 | 60 | 61 | if __name__ == "__main__": 62 | typer.run(main) 63 | -------------------------------------------------------------------------------- /scripts/format.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.10" 3 | # dependencies = [ 4 | # "ruff", 5 | # "typer", 6 | # ] 7 | # /// 8 | 9 | import subprocess 10 | import sys 11 | import typer 12 | from rich import print 13 | 14 | 15 | def main(path: str = None): 16 | try: 17 | command = ["ruff", "format"] 18 | 19 | if path: 20 | command.append(path) 21 | 22 | # Run `ruff` and pipe output to the terminal 23 | process = subprocess.run( 24 | command, 25 | check=True, 26 | stdout=sys.stdout, # Redirect stdout to the terminal 27 | stderr=sys.stderr, # Redirect stderr to the terminal 28 | ) 29 | sys.exit(process.returncode) # Exit with the same code as the command 30 | except subprocess.CalledProcessError as e: 31 | print(f"Error: {e}") # Log the error in a user-friendly way 32 | sys.exit(e.returncode) # Exit with the error code from the command 33 | except FileNotFoundError: 34 | print( 35 | "Error: `ruff` command not found. Make sure it's installed in the environment." 36 | ) 37 | sys.exit(1) 38 | 39 | 40 | if __name__ == "__main__": 41 | typer.run(main) 42 | -------------------------------------------------------------------------------- /scripts/lint.py: -------------------------------------------------------------------------------- 1 | # /// script 2 | # requires-python = ">=3.10" 3 | # dependencies = [ 4 | # "ruff", 5 | # "typer", 6 | # ] 7 | # /// 8 | 9 | import subprocess 10 | import sys 11 | import typer 12 | from rich import print 13 | 14 | 15 | def main(fix: bool = False, watch: bool = False, path: str = None): 16 | try: 17 | command = ["ruff", "check"] 18 | if fix: 19 | command.append("--fix") 20 | 21 | if watch: 22 | command.append("--watch") 23 | 24 | if path: 25 | command.append(path) 26 | 27 | # Run `ruff` and pipe output to the terminal 28 | process = subprocess.run( 29 | command, 30 | check=True, 31 | stdout=sys.stdout, # Redirect stdout to the terminal 32 | stderr=sys.stderr, # Redirect stderr to the terminal 33 | ) 34 | sys.exit(process.returncode) # Exit with the same code as the command 35 | except subprocess.CalledProcessError as e: 36 | print(f"Error: {e}") # Log the error in a user-friendly way 37 | sys.exit(e.returncode) # Exit with the error code from the command 38 | except FileNotFoundError: 39 | print( 40 | "Error: `ruff` command not found. Make sure it's installed in the environment." 41 | ) 42 | sys.exit(1) 43 | 44 | 45 | if __name__ == "__main__": 46 | typer.run(main) 47 | -------------------------------------------------------------------------------- /scripts/rich_progress_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Test script for demonstrating the Rich progress display.""" 3 | 4 | import asyncio 5 | import random 6 | from mnemo_agent.logging.events import Event 7 | from mnemo_agent.logging.listeners import ProgressListener 8 | from rich import print 9 | 10 | 11 | async def generate_test_events(): 12 | """Generate synthetic progress events for testing.""" 13 | # Simulate an MCP session with multiple activities 14 | mcp_names = ["Assistant-1", "Helper-2", "Agent-3"] 15 | models = ["gpt-4", "claude-2", "mistral"] 16 | tools = [ 17 | "developer__shell", 18 | "platform__read_resource", 19 | "computercontroller__web_search", 20 | ] 21 | 22 | for mcp_name in mcp_names: 23 | # Starting up 24 | yield Event( 25 | namespace="mcp_connection_manager", 26 | type="info", 27 | message=f"{mcp_name}: Initializing server session", 28 | data={}, 29 | ) 30 | # Simulate some other console output 31 | print(f"Debug: Connection established for {mcp_name}") 32 | await asyncio.sleep(0.5) 33 | 34 | # Initialized 35 | yield Event( 36 | namespace="mcp_connection_manager", 37 | type="info", 38 | message=f"{mcp_name}: Session initialized", 39 | data={}, 40 | ) 41 | await asyncio.sleep(0.5) 42 | 43 | # Simulate some chat turns 44 | for turn in range(1, 4): 45 | model = random.choice(models) 46 | 47 | # Start chat turn 48 | yield Event( 49 | namespace="mnemo_agent.workflow.llm.augmented_llm_openai.myagent", 50 | type="info", 51 | message=f"Calling {model}", 52 | data={"model": model, "chat_turn": turn}, 53 | ) 54 | await asyncio.sleep(1) 55 | 56 | # Maybe call a tool 57 | if random.random() < 0.7: 58 | tool = random.choice(tools) 59 | print(f"Debug: Executing tool {tool}") # More debug output 60 | yield Event( 61 | namespace="mcp_aggregator", 62 | type="info", 63 | message=f"Requesting tool call '{tool}'", 64 | data={}, 65 | ) 66 | await asyncio.sleep(0.8) 67 | 68 | # Finish chat turn 69 | yield Event( 70 | namespace="augmented_llm", 71 | type="info", 72 | message="Finished processing response", 73 | data={"model": model}, 74 | ) 75 | await asyncio.sleep(0.5) 76 | 77 | # Shutdown 78 | print(f"Debug: Shutting down {mcp_name}") # More debug output 79 | yield Event( 80 | namespace="mcp_connection_manager", 81 | type="info", 82 | message=f"{mcp_name}: _lifecycle_task is exiting", 83 | data={}, 84 | ) 85 | await asyncio.sleep(1) 86 | 87 | 88 | async def main(): 89 | """Run the progress display test.""" 90 | # Set up the progress listener 91 | listener = ProgressListener() 92 | await listener.start() 93 | 94 | try: 95 | async for event in generate_test_events(): 96 | await listener.handle_event(event) 97 | except KeyboardInterrupt: 98 | print("\nTest interrupted!") 99 | finally: 100 | await listener.stop() 101 | 102 | 103 | if __name__ == "__main__": 104 | asyncio.run(main()) 105 | -------------------------------------------------------------------------------- /src/mcp_agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/agents/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/cli/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/cli/__main__.py: -------------------------------------------------------------------------------- 1 | from mnemo_agent.cli.main import app 2 | 3 | if __name__ == "__main__": 4 | app() 5 | -------------------------------------------------------------------------------- /src/mcp_agent/cli/commands/config.py: -------------------------------------------------------------------------------- 1 | import typer 2 | 3 | app = typer.Typer() 4 | 5 | 6 | @app.command() 7 | def show(): 8 | """Show the configuration.""" 9 | raise NotImplementedError( 10 | "The show configuration command has not been implemented yet" 11 | ) 12 | -------------------------------------------------------------------------------- /src/mcp_agent/cli/main.py: -------------------------------------------------------------------------------- 1 | import typer 2 | from mnemo_agent.cli.terminal import Application 3 | from mnemo_agent.cli.commands import config 4 | 5 | app = typer.Typer() 6 | 7 | # Subcommands 8 | app.add_typer(config.app, name="config") 9 | 10 | # Shared application context 11 | application = Application() 12 | 13 | 14 | @app.callback() 15 | def main( 16 | verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode"), 17 | quiet: bool = typer.Option(False, "--quiet", "-q", help="Disable output"), 18 | color: bool = typer.Option( 19 | True, "--color/--no-color", help="Enable/disable color output" 20 | ), 21 | ): 22 | """Main entry point for the MCP Agent CLI.""" 23 | application.verbosity = 1 if verbose else 0 if not quiet else -1 24 | application.console = application.console if color else None 25 | -------------------------------------------------------------------------------- /src/mcp_agent/cli/terminal.py: -------------------------------------------------------------------------------- 1 | from mnemo_agent.console import console, error_console 2 | 3 | 4 | class Application: 5 | def __init__(self, verbosity: int = 0, enable_color: bool = True): 6 | self.verbosity = verbosity 7 | # Use the central console instances, respecting color setting 8 | if not enable_color: 9 | # Create new instances without color if color is disabled 10 | self.console = console.__class__(color_system=None) 11 | self.error_console = error_console.__class__(color_system=None, stderr=True) 12 | else: 13 | self.console = console 14 | self.error_console = error_console 15 | 16 | def log(self, message: str, level: str = "info"): 17 | if level == "info" or (level == "debug" and self.verbosity > 0): 18 | if level == "error": 19 | self.error_console.print(f"[{level.upper()}] {message}") 20 | else: 21 | self.console.print(f"[{level.upper()}] {message}") 22 | 23 | def status(self, message: str): 24 | return self.console.status(f"[bold cyan]{message}[/bold cyan]") 25 | -------------------------------------------------------------------------------- /src/mcp_agent/console.py: -------------------------------------------------------------------------------- 1 | """ 2 | Centralized console configuration for MCP Agent. 3 | 4 | This module provides shared console instances for consistent output handling: 5 | - console: Main console for general output 6 | - error_console: Error console for application errors (writes to stderr) 7 | - server_console: Special console for MCP server output 8 | """ 9 | 10 | from rich.console import Console 11 | 12 | # Main console for general output 13 | console = Console( 14 | color_system="auto", 15 | ) 16 | 17 | # Error console for application errors 18 | error_console = Console( 19 | stderr=True, 20 | style="bold red", 21 | ) 22 | 23 | # Special console for MCP server output 24 | # This could have custom styling to distinguish server messages 25 | server_console = Console( 26 | # Not stderr since we want to maintain output ordering with other messages 27 | style="dim blue", # Or whatever style makes server output distinct 28 | ) 29 | -------------------------------------------------------------------------------- /src/mcp_agent/context_dependent.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from typing import Optional, TYPE_CHECKING 3 | 4 | 5 | if TYPE_CHECKING: 6 | from mnemo_agent.context import Context 7 | 8 | 9 | class ContextDependent: 10 | """ 11 | Mixin class for components that need context access. 12 | Provides both global fallback and instance-specific context support. 13 | """ 14 | 15 | def __init__(self, context: Optional["Context"] = None, **kwargs): 16 | self._context = context 17 | super().__init__(**kwargs) 18 | 19 | @property 20 | def context(self) -> "Context": 21 | """ 22 | Get context, with graceful fallback to global context if needed. 23 | Raises clear error if no context is available. 24 | """ 25 | # First try instance context 26 | if self._context is not None: 27 | return self._context 28 | 29 | try: 30 | # Fall back to global context if available 31 | from mnemo_agent.context import get_current_context 32 | 33 | return get_current_context() 34 | except Exception as e: 35 | raise RuntimeError( 36 | f"No context available for {self.__class__.__name__}. " 37 | "Either initialize MCPApp first or pass context explicitly." 38 | ) from e 39 | 40 | @contextmanager 41 | def use_context(self, context: "Context"): 42 | """Temporarily use a different context.""" 43 | old_context = self._context 44 | self._context = context 45 | try: 46 | yield 47 | finally: 48 | self._context = old_context 49 | -------------------------------------------------------------------------------- /src/mcp_agent/core/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom exceptions for the mcp-agent library. 3 | Enables user-friendly error handling for common issues. 4 | """ 5 | 6 | 7 | class MCPAgentError(Exception): 8 | """Base exception class for FastAgent errors""" 9 | 10 | def __init__(self, message: str, details: str = ""): 11 | self.message = message 12 | self.details = details 13 | super().__init__(f"{message}\n\n{details}" if details else message) 14 | 15 | 16 | class ServerConfigError(MCPAgentError): 17 | """Raised when there are issues with MCP server configuration 18 | Example: Server name referenced in agent.servers[] but not defined in config 19 | """ 20 | 21 | def __init__(self, message: str, details: str = ""): 22 | super().__init__(message, details) 23 | 24 | 25 | class AgentConfigError(MCPAgentError): 26 | """Raised when there are issues with Agent or Workflow configuration 27 | Example: Parallel fan-in references unknown agent 28 | """ 29 | 30 | def __init__(self, message: str, details: str = ""): 31 | super().__init__(message, details) 32 | 33 | 34 | class ProviderKeyError(MCPAgentError): 35 | """Raised when there are issues with LLM provider API keys 36 | Example: OpenAI/Anthropic key not configured but model requires it 37 | """ 38 | 39 | def __init__(self, message: str, details: str = ""): 40 | super().__init__(message, details) 41 | 42 | 43 | class ServerInitializationError(MCPAgentError): 44 | """Raised when a server fails to initialize properly.""" 45 | 46 | def __init__(self, message: str, details: str = ""): 47 | super().__init__(message, details) 48 | 49 | 50 | class ModelConfigError(MCPAgentError): 51 | """Raised when there are issues with LLM model configuration 52 | Example: Unknown model name in model specification string 53 | """ 54 | 55 | def __init__(self, message: str, details: str = ""): 56 | super().__init__(message, details) 57 | 58 | 59 | class CircularDependencyError(MCPAgentError): 60 | """Raised when we detect a Circular Dependency in the workflow""" 61 | 62 | def __init__(self, message: str, details: str = ""): 63 | super().__init__(message, details) 64 | 65 | 66 | class PromptExitError(MCPAgentError): 67 | """Raised from enhanced_prompt when the user requests hard exits""" 68 | 69 | # TODO an exception for flow control :( 70 | def __init__(self, message: str, details: str = ""): 71 | super().__init__(message, details) 72 | -------------------------------------------------------------------------------- /src/mcp_agent/eval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/eval/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/event_progress.py: -------------------------------------------------------------------------------- 1 | """Module for converting log events to progress events.""" 2 | 3 | from dataclasses import dataclass 4 | from enum import Enum 5 | from typing import Optional 6 | 7 | from mnemo_agent.logging.events import Event 8 | 9 | 10 | class ProgressAction(str, Enum): 11 | """Progress actions available in the system.""" 12 | 13 | STARTING = "Starting" 14 | LOADED = "Loaded" 15 | RUNNING = "Running" 16 | INITIALIZED = "Initialized" 17 | CHATTING = "Chatting" 18 | ROUTING = "Routing" 19 | PLANNING = "Planning" 20 | READY = "Ready" 21 | CALLING_TOOL = "Calling Tool" 22 | FINISHED = "Finished" 23 | SHUTDOWN = "Shutdown" 24 | AGGREGATOR_INITIALIZED = "Running" 25 | FATAL_ERROR = "Error" 26 | 27 | 28 | @dataclass 29 | class ProgressEvent: 30 | """Represents a progress event converted from a log event.""" 31 | 32 | action: ProgressAction 33 | target: str 34 | details: Optional[str] = None 35 | agent_name: Optional[str] = None 36 | 37 | def __str__(self) -> str: 38 | """Format the progress event for display.""" 39 | base = f"{self.action.ljust(11)}. {self.target}" 40 | if self.details: 41 | base += f" - {self.details}" 42 | if self.agent_name: 43 | base = f"[{self.agent_name}] {base}" 44 | return base 45 | 46 | 47 | def convert_log_event(event: Event) -> Optional[ProgressEvent]: 48 | """Convert a log event to a progress event if applicable.""" 49 | 50 | # Check to see if there is any additional data 51 | if not event.data: 52 | return None 53 | 54 | event_data = event.data.get("data") 55 | if not isinstance(event_data, dict): 56 | return None 57 | 58 | progress_action = event_data.get("progress_action") 59 | if not progress_action: 60 | return None 61 | 62 | # Build target string based on the event type 63 | # Progress display is currently [time] [event] --- [target] [details] 64 | namespace = event.namespace 65 | agent_name = event_data.get("agent_name") 66 | target = agent_name if agent_name is not None else "unknown" 67 | details = "" 68 | if progress_action == ProgressAction.FATAL_ERROR: 69 | details = event_data.get("error_message", "An error occurred") 70 | elif "mcp_aggregator" in namespace: 71 | server_name = event_data.get("server_name", "") 72 | tool_name = event_data.get("tool_name") 73 | if tool_name: 74 | details = f"{server_name} ({tool_name})" 75 | else: 76 | details = f"{server_name}" 77 | elif "augmented_llm" in namespace: 78 | model = event_data.get("model", "") 79 | 80 | details = f"{model}" 81 | # Add chat turn if present 82 | chat_turn = event_data.get("chat_turn") 83 | if chat_turn is not None: 84 | details = f"{model} turn {chat_turn}" 85 | elif "router_llm" in namespace: 86 | details = "Requesting routing from LLM" 87 | else: 88 | explicit_target = event_data.get("target") 89 | if explicit_target is not None: 90 | target = explicit_target 91 | 92 | return ProgressEvent( 93 | ProgressAction(progress_action), 94 | target, 95 | details, 96 | agent_name=event_data.get("agent_name"), 97 | ) 98 | -------------------------------------------------------------------------------- /src/mcp_agent/executor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/executor/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/executor/task_registry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Keep track of all activities/tasks that the executor needs to run. 3 | This is used by the workflow engine to dynamically orchestrate a workflow graph. 4 | The user just writes standard functions annotated with @workflow_task, but behind the scenes a workflow graph is built. 5 | """ 6 | 7 | from typing import Any, Callable, Dict, List 8 | 9 | 10 | class ActivityRegistry: 11 | """Centralized task/activity management with validation and metadata.""" 12 | 13 | def __init__(self): 14 | self._activities: Dict[str, Callable] = {} 15 | self._metadata: Dict[str, Dict[str, Any]] = {} 16 | 17 | def register( 18 | self, name: str, func: Callable, metadata: Dict[str, Any] | None = None 19 | ): 20 | if name in self._activities: 21 | raise ValueError(f"Activity '{name}' is already registered.") 22 | self._activities[name] = func 23 | self._metadata[name] = metadata or {} 24 | 25 | def get_activity(self, name: str) -> Callable: 26 | if name not in self._activities: 27 | raise KeyError(f"Activity '{name}' not found.") 28 | return self._activities[name] 29 | 30 | def get_metadata(self, name: str) -> Dict[str, Any]: 31 | return self._metadata.get(name, {}) 32 | 33 | def list_activities(self) -> List[str]: 34 | return list(self._activities.keys()) 35 | -------------------------------------------------------------------------------- /src/mcp_agent/human_input/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/human_input/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/human_input/handler.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from rich.panel import Panel 3 | 4 | from mnemo_agent.console import console 5 | from mnemo_agent.human_input.types import ( 6 | HumanInputRequest, 7 | HumanInputResponse, 8 | ) 9 | from mnemo_agent.progress_display import progress_display 10 | 11 | 12 | async def console_input_callback(request: HumanInputRequest) -> HumanInputResponse: 13 | """Request input from a human user via console using rich panel and prompt.""" 14 | 15 | # Prepare the prompt text 16 | prompt_text = request.prompt 17 | if request.description: 18 | prompt_text = f"[bold]{request.description}[/bold]\n\n{request.prompt}" 19 | 20 | # Create a panel with the prompt 21 | panel = Panel( 22 | prompt_text, 23 | title="HUMAN INPUT NEEDED", 24 | style="blue", 25 | border_style="bold white", 26 | padding=(1, 2), 27 | ) 28 | 29 | # Use the context manager to pause the progress display while getting input 30 | with progress_display.paused(): 31 | console.print(panel) 32 | 33 | if request.timeout_seconds: 34 | try: 35 | loop = asyncio.get_event_loop() 36 | response = await asyncio.wait_for( 37 | loop.run_in_executor(None, lambda: console.input()), 38 | request.timeout_seconds, 39 | ) 40 | except asyncio.TimeoutError: 41 | console.print("\n[red]Timeout waiting for input[/red]") 42 | raise TimeoutError("No response received within timeout period") 43 | else: 44 | loop = asyncio.get_event_loop() 45 | response = await loop.run_in_executor(None, lambda: console.input()) 46 | 47 | return HumanInputResponse(request_id=request.request_id, response=response.strip()) 48 | -------------------------------------------------------------------------------- /src/mcp_agent/human_input/types.py: -------------------------------------------------------------------------------- 1 | from typing import Any, AsyncIterator, Protocol 2 | from pydantic import BaseModel 3 | 4 | HUMAN_INPUT_SIGNAL_NAME = "__human_input__" 5 | 6 | 7 | class HumanInputRequest(BaseModel): 8 | """Represents a request for human input.""" 9 | 10 | prompt: str 11 | """The prompt to show to the user""" 12 | 13 | description: str | None = None 14 | """Optional description of what the input is for""" 15 | 16 | request_id: str | None = None 17 | """Unique identifier for this request""" 18 | 19 | workflow_id: str | None = None 20 | """Optional workflow ID if using workflow engine""" 21 | 22 | timeout_seconds: int | None = None 23 | """Optional timeout in seconds""" 24 | 25 | metadata: dict | None = None 26 | """Additional request payload""" 27 | 28 | 29 | class HumanInputResponse(BaseModel): 30 | """Represents a response to a human input request""" 31 | 32 | request_id: str 33 | """ID of the original request""" 34 | 35 | response: str 36 | """The input provided by the human""" 37 | 38 | metadata: dict[str, Any] | None = None 39 | """Additional response payload""" 40 | 41 | 42 | class HumanInputCallback(Protocol): 43 | """Protocol for callbacks that handle human input requests.""" 44 | 45 | async def __call__( 46 | self, request: HumanInputRequest 47 | ) -> AsyncIterator[HumanInputResponse]: 48 | """ 49 | Handle a human input request. 50 | 51 | Args: 52 | request: The input request to handle 53 | 54 | Returns: 55 | AsyncIterator yielding responses as they come in 56 | TODO: saqadri - Keep it simple and just return HumanInputResponse? 57 | """ 58 | ... 59 | -------------------------------------------------------------------------------- /src/mcp_agent/logging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/logging/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/logging/events.py: -------------------------------------------------------------------------------- 1 | """ 2 | Events and event filters for the logger module for the MCP Agent 3 | """ 4 | 5 | import logging 6 | import random 7 | 8 | from datetime import datetime 9 | from typing import ( 10 | Any, 11 | Dict, 12 | Literal, 13 | Set, 14 | ) 15 | 16 | from pydantic import BaseModel, ConfigDict, Field 17 | 18 | 19 | EventType = Literal["debug", "info", "warning", "error", "progress"] 20 | """Broad categories for events (severity or role).""" 21 | 22 | 23 | class EventContext(BaseModel): 24 | """ 25 | Stores correlation or cross-cutting data (workflow IDs, user IDs, etc.). 26 | Also used for distributed environments or advanced logging. 27 | """ 28 | 29 | session_id: str | None = None 30 | workflow_id: str | None = None 31 | # request_id: Optional[str] = None 32 | # parent_event_id: Optional[str] = None 33 | # correlation_id: Optional[str] = None 34 | # user_id: Optional[str] = None 35 | 36 | model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) 37 | 38 | 39 | class Event(BaseModel): 40 | """ 41 | Core event structure. Allows both a broad 'type' (EventType) 42 | and a more specific 'name' string for domain-specific labeling (e.g. "ORDER_PLACED"). 43 | """ 44 | 45 | type: EventType 46 | name: str | None = None 47 | namespace: str 48 | message: str 49 | timestamp: datetime = Field(default_factory=datetime.now) 50 | data: Dict[str, Any] = Field(default_factory=dict) 51 | context: EventContext | None = None 52 | 53 | # For distributed tracing 54 | span_id: str | None = None 55 | trace_id: str | None = None 56 | 57 | model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) 58 | 59 | 60 | class EventFilter(BaseModel): 61 | """ 62 | Filter events by: 63 | - allowed EventTypes (types) 64 | - allowed event 'names' 65 | - allowed namespace prefixes 66 | - a minimum severity level (DEBUG < INFO < WARNING < ERROR) 67 | """ 68 | 69 | types: Set[EventType] | None = Field(default_factory=set) 70 | names: Set[str] | None = Field(default_factory=set) 71 | namespaces: Set[str] | None = Field(default_factory=set) 72 | min_level: EventType | None = "debug" 73 | 74 | def matches(self, event: Event) -> bool: 75 | """ 76 | Check if an event matches this EventFilter criteria. 77 | """ 78 | # 1) Filter by broad event type 79 | if self.types: 80 | if event.type not in self.types: 81 | return False 82 | 83 | # 2) Filter by custom event name 84 | if self.names: 85 | if not event.name or event.name not in self.names: 86 | return False 87 | 88 | # 3) Filter by namespace prefix 89 | if self.namespaces and not any( 90 | event.namespace.startswith(ns) for ns in self.namespaces 91 | ): 92 | return False 93 | 94 | # 4) Minimum severity 95 | if self.min_level: 96 | level_map: Dict[EventType, int] = { 97 | "debug": logging.DEBUG, 98 | "info": logging.INFO, 99 | "warning": logging.WARNING, 100 | "error": logging.ERROR, 101 | } 102 | 103 | min_val = level_map.get(self.min_level, logging.DEBUG) 104 | event_val = level_map.get(event.type, logging.DEBUG) 105 | if event_val < min_val: 106 | return False 107 | 108 | return True 109 | 110 | 111 | class SamplingFilter(EventFilter): 112 | """ 113 | Random sampling on top of base filter. 114 | Only pass an event if it meets the base filter AND random() < sample_rate. 115 | """ 116 | 117 | sample_rate: float = 0.1 118 | """Fraction of events to pass through""" 119 | 120 | def matches(self, event: Event) -> bool: 121 | if not super().matches(event): 122 | return False 123 | return random.random() < self.sample_rate 124 | -------------------------------------------------------------------------------- /src/mcp_agent/mcp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/mcp/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/mcp/gen_client.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | from datetime import timedelta 3 | from typing import AsyncGenerator, Callable 4 | 5 | from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream 6 | from mcp import ClientSession 7 | 8 | from mnemo_agent.logging.logger import get_logger 9 | from mnemo_agent.mnemo_mcp_agent_registry import ServerRegistry 10 | from mnemo_agent.mcp.mnemo_agent_client_session import MCPAgentClientSession 11 | 12 | logger = get_logger(__name__) 13 | 14 | 15 | @asynccontextmanager 16 | async def gen_client( 17 | server_name: str, 18 | server_registry: ServerRegistry, 19 | client_session_factory: Callable[ 20 | [MemoryObjectReceiveStream, MemoryObjectSendStream, timedelta | None], 21 | ClientSession, 22 | ] = MCPAgentClientSession, 23 | ) -> AsyncGenerator[ClientSession, None]: 24 | """ 25 | Create a client session to the specified server. 26 | Handles server startup, initialization, and message receive loop setup. 27 | If required, callers can specify their own message receive loop and ClientSession class constructor to customize further. 28 | For persistent connections, use connect() or MCPConnectionManager instead. 29 | """ 30 | if not server_registry: 31 | raise ValueError( 32 | "Server registry not found in the context. Please specify one either on this method, or in the context." 33 | ) 34 | 35 | async with server_registry.initialize_server( 36 | server_name=server_name, 37 | client_session_factory=client_session_factory, 38 | ) as session: 39 | yield session 40 | 41 | 42 | async def connect( 43 | server_name: str, 44 | server_registry: ServerRegistry, 45 | client_session_factory: Callable[ 46 | [MemoryObjectReceiveStream, MemoryObjectSendStream, timedelta | None], 47 | ClientSession, 48 | ] = MCPAgentClientSession, 49 | ) -> ClientSession: 50 | """ 51 | Create a persistent client session to the specified server. 52 | Handles server startup, initialization, and message receive loop setup. 53 | If required, callers can specify their own message receive loop and ClientSession class constructor to customize further. 54 | """ 55 | if not server_registry: 56 | raise ValueError( 57 | "Server registry not found in the context. Please specify one either on this method, or in the context." 58 | ) 59 | 60 | server_connection = await server_registry.connection_manager.get_server( 61 | server_name=server_name, 62 | client_session_factory=client_session_factory, 63 | ) 64 | 65 | return server_connection.session 66 | 67 | 68 | async def disconnect( 69 | server_name: str | None, 70 | server_registry: ServerRegistry, 71 | ) -> None: 72 | """ 73 | Disconnect from the specified server. If server_name is None, disconnect from all servers. 74 | """ 75 | if not server_registry: 76 | raise ValueError( 77 | "Server registry not found in the context. Please specify one either on this method, or in the context." 78 | ) 79 | 80 | if server_name: 81 | await server_registry.connection_manager.disconnect_server( 82 | server_name=server_name 83 | ) 84 | else: 85 | await server_registry.connection_manager.disconnect_all() 86 | -------------------------------------------------------------------------------- /src/mcp_agent/mcp/mcp_activity.py: -------------------------------------------------------------------------------- 1 | # import functools 2 | # from temporalio import activity 3 | # from typing import Dict, Any, List, Callable, Awaitable 4 | # from .gen_client import gen_client 5 | 6 | 7 | # def mcp_activity(server_name: str, mcp_call: Callable): 8 | # def decorator(func): 9 | # @activity.defn 10 | # @functools.wraps(func) 11 | # async def wrapper(*activity_args, **activity_kwargs): 12 | # params = await func(*activity_args, **activity_kwargs) 13 | # async with gen_client(server_name) as client: 14 | # return await mcp_call(client, params) 15 | 16 | # return wrapper 17 | 18 | # return decorator 19 | -------------------------------------------------------------------------------- /src/mcp_agent/mcp/mcp_agent_server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from mcp.server import NotificationOptions 3 | from mcp.server.fastmcp import FastMCP 4 | from mcp.server.stdio import stdio_server 5 | from mnemo_agent.executor.temporal import get_temporal_client 6 | from mnemo_agent.telemetry.tracing import setup_tracing 7 | 8 | app = FastMCP("mcp-agent-server") 9 | 10 | setup_tracing("mcp-agent-server") 11 | 12 | 13 | async def run(): 14 | async with stdio_server() as (read_stream, write_stream): 15 | await app._mnemo_mcp_agent.run( 16 | read_stream, 17 | write_stream, 18 | app._mnemo_mcp_agent.create_initialization_options( 19 | notification_options=NotificationOptions( 20 | tools_changed=True, resources_changed=True 21 | ) 22 | ), 23 | ) 24 | 25 | 26 | @app.tool 27 | async def run_workflow(query: str): 28 | """Run the workflow given its name or id""" 29 | pass 30 | 31 | 32 | @app.tool 33 | async def pause_workflow(workflow_id: str): 34 | """Pause a running workflow.""" 35 | temporal_client = await get_temporal_client() 36 | handle = temporal_client.get_workflow_handle(workflow_id) 37 | await handle.signal("pause") 38 | 39 | 40 | @app.tool 41 | async def resume_workflow(workflow_id: str): 42 | """Resume a paused workflow.""" 43 | temporal_client = await get_temporal_client() 44 | handle = temporal_client.get_workflow_handle(workflow_id) 45 | await handle.signal("resume") 46 | 47 | 48 | async def provide_user_input(workflow_id: str, input_data: str): 49 | """Provide user/human input to a waiting workflow step.""" 50 | temporal_client = await get_temporal_client() 51 | handle = temporal_client.get_workflow_handle(workflow_id) 52 | await handle.signal("human_input", input_data) 53 | 54 | 55 | if __name__ == "__main__": 56 | asyncio.run(run()) 57 | -------------------------------------------------------------------------------- /src/mcp_agent/progress_display.py: -------------------------------------------------------------------------------- 1 | """ 2 | Centralized progress display configuration for MCP Agent. 3 | Provides a shared progress display instance for consistent progress handling. 4 | """ 5 | 6 | from mnemo_agent.console import console 7 | from mnemo_agent.logging.rich_progress import RichProgressDisplay 8 | 9 | # Main progress display instance - shared across the application 10 | progress_display = RichProgressDisplay(console) 11 | -------------------------------------------------------------------------------- /src/mcp_agent/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/py.typed -------------------------------------------------------------------------------- /src/mcp_agent/telemetry/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/telemetry/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/telemetry/usage_tracking.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from mnemo_agent.config import get_settings 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | def send_usage_data(): 8 | config = get_settings() 9 | if not config.usage_telemetry.enabled: 10 | logger.info("Usage tracking is disabled") 11 | return 12 | 13 | # TODO: saqadri - implement usage tracking 14 | # data = {"installation_id": str(uuid.uuid4()), "version": "0.1.0"} 15 | # try: 16 | # requests.post("https://telemetry.example.com/usage", json=data, timeout=2) 17 | # except: 18 | # pass 19 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/embedding/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/embedding/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/embedding/embedding_base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Dict, List 3 | 4 | from numpy import float32 5 | from numpy.typing import NDArray 6 | from sklearn.metrics.pairwise import cosine_similarity 7 | 8 | from mnemo_agent.context_dependent import ContextDependent 9 | 10 | 11 | FloatArray = NDArray[float32] 12 | 13 | 14 | class EmbeddingModel(ABC, ContextDependent): 15 | """Abstract interface for embedding models""" 16 | 17 | @abstractmethod 18 | async def embed(self, data: List[str]) -> FloatArray: 19 | """ 20 | Generate embeddings for a list of messages 21 | 22 | Args: 23 | data: List of text strings to embed 24 | 25 | Returns: 26 | Array of embeddings, shape (len(texts), embedding_dim) 27 | """ 28 | 29 | @property 30 | @abstractmethod 31 | def embedding_dim(self) -> int: 32 | """Return the dimensionality of the embeddings""" 33 | 34 | 35 | def compute_similarity_scores( 36 | embedding_a: FloatArray, embedding_b: FloatArray 37 | ) -> Dict[str, float]: 38 | """ 39 | Compute different similarity metrics between embeddings 40 | """ 41 | # Reshape for sklearn's cosine_similarity 42 | a_emb = embedding_a.reshape(1, -1) 43 | b_emb = embedding_b.reshape(1, -1) 44 | 45 | cosine_sim = float(cosine_similarity(a_emb, b_emb)[0, 0]) 46 | 47 | # Could add other similarity metrics here 48 | return { 49 | "cosine": cosine_sim, 50 | # "euclidean": float(euclidean_similarity), 51 | # "dot_product": float(dot_product) 52 | } 53 | 54 | 55 | def compute_confidence(similarity_scores: Dict[str, float]) -> float: 56 | """ 57 | Compute overall confidence score from individual similarity metrics 58 | """ 59 | # For now, just use cosine similarity as confidence 60 | # Could implement more sophisticated combination of scores 61 | return similarity_scores["cosine"] 62 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/embedding/embedding_cohere.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TYPE_CHECKING 2 | 3 | from cohere import Client 4 | from numpy import array, float32 5 | 6 | from mnemo_agent.workflows.embedding.embedding_base import EmbeddingModel, FloatArray 7 | 8 | if TYPE_CHECKING: 9 | from mnemo_agent.context import Context 10 | 11 | 12 | class CohereEmbeddingModel(EmbeddingModel): 13 | """Cohere embedding model implementation""" 14 | 15 | def __init__( 16 | self, 17 | model: str = "embed-multilingual-v3.0", 18 | context: Optional["Context"] = None, 19 | **kwargs, 20 | ): 21 | super().__init__(context=context, **kwargs) 22 | self.client = Client(api_key=self.context.config.cohere.api_key) 23 | self.model = model 24 | # Cache the dimension since it's fixed per model 25 | # https://docs.cohere.com/v2/docs/cohere-embed 26 | self._embedding_dim = { 27 | "embed-english-v2.0": 4096, 28 | "embed-english-light-v2.0": 1024, 29 | "embed-english-v3.0": 1024, 30 | "embed-english-light-v3.0": 384, 31 | "embed-multilingual-v2.0": 768, 32 | "embed-multilingual-v3.0": 1024, 33 | "embed-multilingual-light-v3.0": 384, 34 | }[model] 35 | 36 | async def embed(self, data: List[str]) -> FloatArray: 37 | response = self.client.embed( 38 | texts=data, 39 | model=self.model, 40 | input_type="classification", 41 | embedding_types=["float"], 42 | ) 43 | 44 | embeddings = array(response.embeddings, dtype=float32) 45 | return embeddings 46 | 47 | @property 48 | def embedding_dim(self) -> int: 49 | return self._embedding_dim 50 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/embedding/embedding_openai.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TYPE_CHECKING 2 | 3 | from numpy import array, float32, stack 4 | from openai import OpenAI 5 | 6 | from mnemo_agent.workflows.embedding.embedding_base import EmbeddingModel, FloatArray 7 | 8 | if TYPE_CHECKING: 9 | from mnemo_agent.context import Context 10 | 11 | 12 | class OpenAIEmbeddingModel(EmbeddingModel): 13 | """OpenAI embedding model implementation""" 14 | 15 | def __init__( 16 | self, model: str = "text-embedding-3-small", context: Optional["Context"] = None 17 | ): 18 | super().__init__(context=context) 19 | self.client = OpenAI(api_key=self.context.config.openai.api_key) 20 | self.model = model 21 | # Cache the dimension since it's fixed per model 22 | self._embedding_dim = { 23 | "text-embedding-3-small": 1536, 24 | "text-embedding-3-large": 3072, 25 | }[model] 26 | 27 | async def embed(self, data: List[str]) -> FloatArray: 28 | response = self.client.embeddings.create( 29 | model=self.model, input=data, encoding_format="float" 30 | ) 31 | 32 | # Sort the embeddings by their index to ensure correct order 33 | sorted_embeddings = sorted(response.data, key=lambda x: x.index) 34 | 35 | # Stack all embeddings into a single array 36 | embeddings = stack( 37 | [ 38 | array(embedding.embedding, dtype=float32) 39 | for embedding in sorted_embeddings 40 | ] 41 | ) 42 | return embeddings 43 | 44 | @property 45 | def embedding_dim(self) -> int: 46 | return self._embedding_dim 47 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/evaluator_optimizer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/evaluator_optimizer/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/intent_classifier/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/intent_classifier/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.workflows.embedding.embedding_cohere import CohereEmbeddingModel 4 | from mnemo_agent.workflows.intent_classifier.intent_classifier_base import Intent 5 | from mnemo_agent.workflows.intent_classifier.intent_classifier_embedding import ( 6 | EmbeddingIntentClassifier, 7 | ) 8 | 9 | if TYPE_CHECKING: 10 | from mnemo_agent.context import Context 11 | 12 | 13 | class CohereEmbeddingIntentClassifier(EmbeddingIntentClassifier): 14 | """ 15 | An intent classifier that uses Cohere's embedding models for computing semantic simiarity based classifications. 16 | """ 17 | 18 | def __init__( 19 | self, 20 | intents: List[Intent], 21 | embedding_model: CohereEmbeddingModel | None = None, 22 | context: Optional["Context"] = None, 23 | **kwargs, 24 | ): 25 | embedding_model = embedding_model or CohereEmbeddingModel() 26 | super().__init__( 27 | embedding_model=embedding_model, intents=intents, context=context, **kwargs 28 | ) 29 | 30 | @classmethod 31 | async def create( 32 | cls, 33 | intents: List[Intent], 34 | embedding_model: CohereEmbeddingModel | None = None, 35 | context: Optional["Context"] = None, 36 | ) -> "CohereEmbeddingIntentClassifier": 37 | """ 38 | Factory method to create and initialize a classifier. 39 | Use this instead of constructor since we need async initialization. 40 | """ 41 | instance = cls( 42 | intents=intents, embedding_model=embedding_model, context=context 43 | ) 44 | await instance.initialize() 45 | return instance 46 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.workflows.embedding.embedding_openai import OpenAIEmbeddingModel 4 | from mnemo_agent.workflows.intent_classifier.intent_classifier_base import Intent 5 | from mnemo_agent.workflows.intent_classifier.intent_classifier_embedding import ( 6 | EmbeddingIntentClassifier, 7 | ) 8 | 9 | if TYPE_CHECKING: 10 | from mnemo_agent.context import Context 11 | 12 | 13 | class OpenAIEmbeddingIntentClassifier(EmbeddingIntentClassifier): 14 | """ 15 | An intent classifier that uses OpenAI's embedding models for computing semantic simiarity based classifications. 16 | """ 17 | 18 | def __init__( 19 | self, 20 | intents: List[Intent], 21 | embedding_model: OpenAIEmbeddingModel | None = None, 22 | context: Optional["Context"] = None, 23 | **kwargs, 24 | ): 25 | embedding_model = embedding_model or OpenAIEmbeddingModel() 26 | super().__init__( 27 | embedding_model=embedding_model, intents=intents, context=context, **kwargs 28 | ) 29 | 30 | @classmethod 31 | async def create( 32 | cls, 33 | intents: List[Intent], 34 | embedding_model: OpenAIEmbeddingModel | None = None, 35 | context: Optional["Context"] = None, 36 | ) -> "OpenAIEmbeddingIntentClassifier": 37 | """ 38 | Factory method to create and initialize a classifier. 39 | Use this instead of constructor since we need async initialization. 40 | """ 41 | instance = cls( 42 | intents=intents, embedding_model=embedding_model, context=context 43 | ) 44 | await instance.initialize() 45 | return instance 46 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM 4 | from mnemo_agent.workflows.intent_classifier.intent_classifier_base import Intent 5 | from mnemo_agent.workflows.intent_classifier.intent_classifier_llm import ( 6 | LLMIntentClassifier, 7 | ) 8 | 9 | if TYPE_CHECKING: 10 | from mnemo_agent.context import Context 11 | 12 | CLASSIFIER_SYSTEM_INSTRUCTION = """ 13 | You are a precise intent classifier that analyzes input requests to determine their intended action or purpose. 14 | You are provided with a request and a list of intents to choose from. 15 | You can choose one or more intents, or choose none if no intent is appropriate. 16 | """ 17 | 18 | 19 | class AnthropicLLMIntentClassifier(LLMIntentClassifier): 20 | """ 21 | An LLM router that uses an Anthropic model to make routing decisions. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | intents: List[Intent], 27 | classification_instruction: str | None = None, 28 | context: Optional["Context"] = None, 29 | **kwargs, 30 | ): 31 | anthropic_llm = AnthropicAugmentedLLM( 32 | instruction=CLASSIFIER_SYSTEM_INSTRUCTION, context=context 33 | ) 34 | 35 | super().__init__( 36 | llm=anthropic_llm, 37 | intents=intents, 38 | classification_instruction=classification_instruction, 39 | context=context, 40 | **kwargs, 41 | ) 42 | 43 | @classmethod 44 | async def create( 45 | cls, 46 | intents: List[Intent], 47 | classification_instruction: str | None = None, 48 | context: Optional["Context"] = None, 49 | ) -> "AnthropicLLMIntentClassifier": 50 | """ 51 | Factory method to create and initialize a classifier. 52 | Use this instead of constructor since we need async initialization. 53 | """ 54 | instance = cls( 55 | intents=intents, 56 | classification_instruction=classification_instruction, 57 | context=context, 58 | ) 59 | await instance.initialize() 60 | return instance 61 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 4 | from mnemo_agent.workflows.intent_classifier.intent_classifier_base import Intent 5 | from mnemo_agent.workflows.intent_classifier.intent_classifier_llm import ( 6 | LLMIntentClassifier, 7 | ) 8 | 9 | if TYPE_CHECKING: 10 | from mnemo_agent.context import Context 11 | 12 | CLASSIFIER_SYSTEM_INSTRUCTION = """ 13 | You are a precise intent classifier that analyzes input requests to determine their intended action or purpose. 14 | You are provided with a request and a list of intents to choose from. 15 | You can choose one or more intents, or choose none if no intent is appropriate. 16 | """ 17 | 18 | 19 | class OpenAILLMIntentClassifier(LLMIntentClassifier): 20 | """ 21 | An LLM router that uses an OpenAI model to make routing decisions. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | intents: List[Intent], 27 | classification_instruction: str | None = None, 28 | context: Optional["Context"] = None, 29 | **kwargs, 30 | ): 31 | openai_llm = OpenAIAugmentedLLM( 32 | instruction=CLASSIFIER_SYSTEM_INSTRUCTION, context=context 33 | ) 34 | 35 | super().__init__( 36 | llm=openai_llm, 37 | intents=intents, 38 | classification_instruction=classification_instruction, 39 | context=context, 40 | **kwargs, 41 | ) 42 | 43 | @classmethod 44 | async def create( 45 | cls, 46 | intents: List[Intent], 47 | classification_instruction: str | None = None, 48 | context: Optional["Context"] = None, 49 | ) -> "OpenAILLMIntentClassifier": 50 | """ 51 | Factory method to create and initialize a classifier. 52 | Use this instead of constructor since we need async initialization. 53 | """ 54 | instance = cls( 55 | intents=intents, 56 | classification_instruction=classification_instruction, 57 | context=context, 58 | ) 59 | await instance.initialize() 60 | return instance 61 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/llm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/llm/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/llm/augmented_llm_ollama.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from openai import OpenAI 4 | 5 | from mnemo_agent.workflows.llm.augmented_llm import ( 6 | ModelT, 7 | RequestParams, 8 | ) 9 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 10 | 11 | 12 | class OllamaAugmentedLLM(OpenAIAugmentedLLM): 13 | """ 14 | The basic building block of agentic systems is an LLM enhanced with augmentations 15 | such as retrieval, tools, and memory provided from a collection of MCP servers. 16 | This implementation uses Ollama's OpenAI-compatible ChatCompletion API. 17 | """ 18 | 19 | def __init__(self, *args, **kwargs): 20 | # Create a copy of kwargs to avoid modifying the original 21 | updated_kwargs = kwargs.copy() 22 | 23 | # Only set default_model if it's not already in kwargs 24 | if "default_model" not in updated_kwargs: 25 | updated_kwargs["default_model"] = "llama3.2:3b" 26 | 27 | super().__init__(*args, **updated_kwargs) 28 | 29 | self.provider = "Ollama" 30 | 31 | async def generate_structured( 32 | self, 33 | message, 34 | response_model: Type[ModelT], 35 | request_params: RequestParams | None = None, 36 | ) -> ModelT: 37 | # First we invoke the LLM to generate a string response 38 | # We need to do this in a two-step process because Instructor doesn't 39 | # know how to invoke MCP tools via call_tool, so we'll handle all the 40 | # processing first and then pass the final response through Instructor 41 | import instructor 42 | 43 | response = await self.generate_str( 44 | message=message, 45 | request_params=request_params, 46 | ) 47 | 48 | # Next we pass the text through instructor to extract structured data 49 | client = instructor.from_openai( 50 | OpenAI( 51 | api_key=self.context.config.openai.api_key, 52 | base_url=self.context.config.openai.base_url, 53 | ), 54 | mode=instructor.Mode.JSON, 55 | ) 56 | 57 | params = self.get_request_params(request_params) 58 | model = await self.select_model(params) 59 | 60 | # Extract structured data from natural language 61 | structured_response = client.chat.completions.create( 62 | model=model or "llama3.2:3b", 63 | response_model=response_model, 64 | messages=[ 65 | {"role": "user", "content": response}, 66 | ], 67 | ) 68 | 69 | return structured_response 70 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/orchestrator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/orchestrator/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/parallel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/parallel/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/router/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/router/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/router/router_embedding_cohere.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.agents.agent import Agent 4 | from mnemo_agent.workflows.embedding.embedding_cohere import CohereEmbeddingModel 5 | from mnemo_agent.workflows.router.router_embedding import EmbeddingRouter 6 | 7 | if TYPE_CHECKING: 8 | from mnemo_agent.context import Context 9 | 10 | 11 | class CohereEmbeddingRouter(EmbeddingRouter): 12 | """ 13 | A router that uses Cohere embedding similarity to route requests to appropriate categories. 14 | This class helps to route an input to a specific MCP server, an Agent (an aggregation of MCP servers), 15 | or a function (any Callable). 16 | """ 17 | 18 | def __init__( 19 | self, 20 | server_names: List[str] | None = None, 21 | agents: List[Agent] | None = None, 22 | functions: List[Callable] | None = None, 23 | embedding_model: CohereEmbeddingModel | None = None, 24 | context: Optional["Context"] = None, 25 | **kwargs, 26 | ): 27 | embedding_model = embedding_model or CohereEmbeddingModel() 28 | 29 | super().__init__( 30 | embedding_model=embedding_model, 31 | server_names=server_names, 32 | agents=agents, 33 | functions=functions, 34 | context=context, 35 | **kwargs, 36 | ) 37 | 38 | @classmethod 39 | async def create( 40 | cls, 41 | embedding_model: CohereEmbeddingModel | None = None, 42 | server_names: List[str] | None = None, 43 | agents: List[Agent] | None = None, 44 | functions: List[Callable] | None = None, 45 | context: Optional["Context"] = None, 46 | ) -> "CohereEmbeddingRouter": 47 | """ 48 | Factory method to create and initialize a router. 49 | Use this instead of constructor since we need async initialization. 50 | """ 51 | instance = cls( 52 | server_names=server_names, 53 | agents=agents, 54 | functions=functions, 55 | embedding_model=embedding_model, 56 | context=context, 57 | ) 58 | await instance.initialize() 59 | return instance 60 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/router/router_embedding_openai.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.agents.agent import Agent 4 | from mnemo_agent.workflows.embedding.embedding_openai import OpenAIEmbeddingModel 5 | from mnemo_agent.workflows.router.router_embedding import EmbeddingRouter 6 | 7 | if TYPE_CHECKING: 8 | from mnemo_agent.context import Context 9 | 10 | 11 | class OpenAIEmbeddingRouter(EmbeddingRouter): 12 | """ 13 | A router that uses OpenAI embedding similarity to route requests to appropriate categories. 14 | This class helps to route an input to a specific MCP server, an Agent (an aggregation of MCP servers), 15 | or a function (any Callable). 16 | """ 17 | 18 | def __init__( 19 | self, 20 | server_names: List[str] | None = None, 21 | agents: List[Agent] | None = None, 22 | functions: List[Callable] | None = None, 23 | embedding_model: OpenAIEmbeddingModel | None = None, 24 | context: Optional["Context"] = None, 25 | **kwargs, 26 | ): 27 | embedding_model = embedding_model or OpenAIEmbeddingModel() 28 | 29 | super().__init__( 30 | embedding_model=embedding_model, 31 | server_names=server_names, 32 | agents=agents, 33 | functions=functions, 34 | context=context, 35 | **kwargs, 36 | ) 37 | 38 | @classmethod 39 | async def create( 40 | cls, 41 | embedding_model: OpenAIEmbeddingModel | None = None, 42 | server_names: List[str] | None = None, 43 | agents: List[Agent] | None = None, 44 | functions: List[Callable] | None = None, 45 | context: Optional["Context"] = None, 46 | ) -> "OpenAIEmbeddingRouter": 47 | """ 48 | Factory method to create and initialize a router. 49 | Use this instead of constructor since we need async initialization. 50 | """ 51 | instance = cls( 52 | server_names=server_names, 53 | agents=agents, 54 | functions=functions, 55 | embedding_model=embedding_model, 56 | context=context, 57 | ) 58 | await instance.initialize() 59 | return instance 60 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/router/router_llm_anthropic.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.agents.agent import Agent 4 | from mnemo_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM 5 | from mnemo_agent.workflows.router.router_llm import LLMRouter 6 | 7 | if TYPE_CHECKING: 8 | from mnemo_agent.context import Context 9 | 10 | ROUTING_SYSTEM_INSTRUCTION = """ 11 | You are a highly accurate request router that directs incoming requests to the most appropriate category. 12 | A category is a specialized destination, such as a Function, an MCP Server (a collection of tools/functions), or an Agent (a collection of servers). 13 | You will be provided with a request and a list of categories to choose from. 14 | You can choose one or more categories, or choose none if no category is appropriate. 15 | """ 16 | 17 | 18 | class AnthropicLLMRouter(LLMRouter): 19 | """ 20 | An LLM router that uses an Anthropic model to make routing decisions. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | server_names: List[str] | None = None, 26 | agents: List[Agent] | None = None, 27 | functions: List[Callable] | None = None, 28 | routing_instruction: str | None = None, 29 | context: Optional["Context"] = None, 30 | **kwargs, 31 | ): 32 | anthropic_llm = AnthropicAugmentedLLM( 33 | instruction=ROUTING_SYSTEM_INSTRUCTION, context=context 34 | ) 35 | 36 | super().__init__( 37 | llm=anthropic_llm, 38 | server_names=server_names, 39 | agents=agents, 40 | functions=functions, 41 | routing_instruction=routing_instruction, 42 | context=context, 43 | **kwargs, 44 | ) 45 | 46 | @classmethod 47 | async def create( 48 | cls, 49 | server_names: List[str] | None = None, 50 | agents: List[Agent] | None = None, 51 | functions: List[Callable] | None = None, 52 | routing_instruction: str | None = None, 53 | context: Optional["Context"] = None, 54 | ) -> "AnthropicLLMRouter": 55 | """ 56 | Factory method to create and initialize a router. 57 | Use this instead of constructor since we need async initialization. 58 | """ 59 | instance = cls( 60 | server_names=server_names, 61 | agents=agents, 62 | functions=functions, 63 | routing_instruction=routing_instruction, 64 | context=context, 65 | ) 66 | await instance.initialize() 67 | return instance 68 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/router/router_llm_openai.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional, TYPE_CHECKING 2 | 3 | from mnemo_agent.agents.agent import Agent 4 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 5 | from mnemo_agent.workflows.router.router_llm import LLMRouter 6 | 7 | if TYPE_CHECKING: 8 | from mnemo_agent.context import Context 9 | 10 | ROUTING_SYSTEM_INSTRUCTION = """ 11 | You are a highly accurate request router that directs incoming requests to the most appropriate category. 12 | A category is a specialized destination, such as a Function, an MCP Server (a collection of tools/functions), or an Agent (a collection of servers). 13 | You will be provided with a request and a list of categories to choose from. 14 | You can choose one or more categories, or choose none if no category is appropriate. 15 | """ 16 | 17 | 18 | class OpenAILLMRouter(LLMRouter): 19 | """ 20 | An LLM router that uses an OpenAI model to make routing decisions. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | server_names: List[str] | None = None, 26 | agents: List[Agent] | None = None, 27 | functions: List[Callable] | None = None, 28 | routing_instruction: str | None = None, 29 | context: Optional["Context"] = None, 30 | **kwargs, 31 | ): 32 | openai_llm = OpenAIAugmentedLLM( 33 | instruction=ROUTING_SYSTEM_INSTRUCTION, context=context 34 | ) 35 | 36 | super().__init__( 37 | llm=openai_llm, 38 | server_names=server_names, 39 | agents=agents, 40 | functions=functions, 41 | routing_instruction=routing_instruction, 42 | context=context, 43 | **kwargs, 44 | ) 45 | 46 | @classmethod 47 | async def create( 48 | cls, 49 | server_names: List[str] | None = None, 50 | agents: List[Agent] | None = None, 51 | functions: List[Callable] | None = None, 52 | routing_instruction: str | None = None, 53 | context: Optional["Context"] = None, 54 | ) -> "OpenAILLMRouter": 55 | """ 56 | Factory method to create and initialize a classifier. 57 | Use this instead of constructor since we need async initialization. 58 | """ 59 | instance = cls( 60 | server_names=server_names, 61 | agents=agents, 62 | functions=functions, 63 | routing_instruction=routing_instruction, 64 | context=context, 65 | ) 66 | await instance.initialize() 67 | return instance 68 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/swarm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MnemoAI/mnemo/4b7a2f3e15e273879992d919c54bf0b34ffe8cfc/src/mcp_agent/workflows/swarm/__init__.py -------------------------------------------------------------------------------- /src/mcp_agent/workflows/swarm/swarm_anthropic.py: -------------------------------------------------------------------------------- 1 | from mnemo_agent.workflows.swarm.swarm import Swarm 2 | from mnemo_agent.workflows.llm.augmented_llm import RequestParams 3 | from mnemo_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM 4 | from mnemo_agent.logging.logger import get_logger 5 | 6 | logger = get_logger(__name__) 7 | 8 | 9 | class AnthropicSwarm(Swarm, AnthropicAugmentedLLM): 10 | """ 11 | MCP version of the OpenAI Swarm class (https://github.com/openai/swarm.), 12 | using Anthropic's API as the LLM. 13 | """ 14 | 15 | async def generate(self, message, request_params: RequestParams | None = None): 16 | params = self.get_request_params( 17 | request_params, 18 | default=RequestParams( 19 | model="claude-3-5-sonnet-20241022", 20 | maxTokens=8192, 21 | parallel_tool_calls=False, 22 | ), 23 | ) 24 | iterations = 0 25 | response = None 26 | agent_name = str(self.aggregator.name) if self.aggregator else None 27 | 28 | while iterations < params.max_iterations and self.should_continue(): 29 | response = await super().generate( 30 | message=message 31 | if iterations == 0 32 | else "Please resolve my original request. If it has already been resolved then end turn", 33 | request_params=params.model_copy( 34 | update={"max_iterations": 1} 35 | ), # TODO: saqadri - validate 36 | ) 37 | logger.debug(f"Agent: {agent_name}, response:", data=response) 38 | agent_name = self.aggregator.name if self.aggregator else None 39 | iterations += 1 40 | 41 | # Return final response back 42 | return response 43 | -------------------------------------------------------------------------------- /src/mcp_agent/workflows/swarm/swarm_openai.py: -------------------------------------------------------------------------------- 1 | from mnemo_agent.workflows.swarm.swarm import Swarm 2 | from mnemo_agent.workflows.llm.augmented_llm import RequestParams 3 | from mnemo_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM 4 | from mnemo_agent.logging.logger import get_logger 5 | 6 | logger = get_logger(__name__) 7 | 8 | 9 | class OpenAISwarm(Swarm, OpenAIAugmentedLLM): 10 | """ 11 | MCP version of the OpenAI Swarm class (https://github.com/openai/swarm.), using OpenAI's ChatCompletion as the LLM. 12 | """ 13 | 14 | async def generate(self, message, request_params: RequestParams | None = None): 15 | params = self.get_request_params( 16 | request_params, 17 | default=RequestParams( 18 | model="gpt-4o", 19 | maxTokens=8192, 20 | parallel_tool_calls=False, 21 | ), 22 | ) 23 | iterations = 0 24 | response = None 25 | agent_name = str(self.aggregator.name) if self.aggregator else None 26 | 27 | while iterations < params.max_iterations and self.should_continue(): 28 | response = await super().generate( 29 | message=message 30 | if iterations == 0 31 | else "Please resolve my original request. If it has already been resolved then end turn", 32 | request_params=params.model_copy( 33 | update={"max_iterations": 1} # TODO: saqadri - validate 34 | ), 35 | ) 36 | logger.debug(f"Agent: {agent_name}, response:", data=response) 37 | agent_name = self.aggregator.name if self.aggregator else None 38 | iterations += 1 39 | 40 | # Return final response back 41 | return response 42 | -------------------------------------------------------------------------------- /tests/fixture/README.md: -------------------------------------------------------------------------------- 1 | # Test Fixtures 2 | 3 | This directory contains test fixtures used for verifying event processing and display functionality. 4 | 5 | ## Files 6 | 7 | - `mcp_basic_agent_20250131_205604.jsonl`: Log file containing events from a basic agent run, including "final response" events from both OpenAI and Anthropic endpoints 8 | - `expected_output.txt`: Expected formatted output when processing the log file through event_summary.py 9 | 10 | ## Updating Fixtures 11 | 12 | If you need to update these fixtures (e.g., when changing event processing logic), you can: 13 | 14 | 1. Run an example to generate a new log file: 15 | 16 | ```bash 17 | cd examples/basic/mcp_basic_agent 18 | rm -f mcp-agent.jsonl # Start with a clean log file 19 | uv run python main.py "What is the timestamp in different timezones?" 20 | cp mcp-agent.jsonl ../../tests/fixture/mcp_basic_agent_20250131_205604.jsonl 21 | ``` 22 | 23 | 2. Use the utility method to update expected output: 24 | ```python 25 | from tests.test_event_progress import update_test_fixtures 26 | update_test_fixtures() 27 | ``` 28 | 29 | The test file will verify that event processing produces consistent output matching these fixtures. 30 | 31 | Note: Always start with a clean log file (`rm -f mcp-agent.jsonl`) before generating new fixtures, as the logger appends to existing files. 32 | -------------------------------------------------------------------------------- /tests/test_event_progress.py: -------------------------------------------------------------------------------- 1 | """Test event progress conversion from log events.""" 2 | 3 | import subprocess 4 | from pathlib import Path 5 | from rich import print 6 | 7 | 8 | def test_event_conversion(): 9 | """Test conversion of log events to progress events using gold master approach.""" 10 | # Get the paths 11 | log_file = str( 12 | Path(__file__).parent / "fixture" / "mcp_basic_agent_20250131_205604.jsonl" 13 | ) 14 | expected_output_file = Path(__file__).parent / "fixture" / "expected_output.txt" 15 | 16 | # Run the event_summary script to get current output 17 | result = subprocess.run( 18 | ["uv", "run", "python3", "scripts/event_summary.py", log_file], 19 | capture_output=True, 20 | text=True, 21 | ) 22 | current_output = result.stdout 23 | 24 | # Load expected output 25 | with open(expected_output_file) as f: 26 | expected_output = f.read() 27 | 28 | # Compare outputs 29 | assert current_output.strip() == expected_output.strip(), ( 30 | "Event summary output does not match expected output" 31 | ) 32 | 33 | 34 | def update_test_fixtures(): 35 | """ 36 | Utility method to update test fixtures with latest output. 37 | This should only be run manually when intentionally updating the expected behavior. 38 | 39 | Usage: 40 | python3 -c "from tests.test_event_progress import update_test_fixtures; update_test_fixtures()" 41 | """ 42 | # Paths 43 | fixture_dir = Path(__file__).parent / "fixture" 44 | log_file = fixture_dir / "mcp_basic_agent_20250131_205604.jsonl" 45 | expected_output_file = fixture_dir / "expected_output.txt" 46 | 47 | if not log_file.exists(): 48 | print(f"Log file not found: {log_file}") 49 | print( 50 | "Please run an example to generate a log file and copy it to the fixture directory" 51 | ) 52 | return 53 | 54 | # Run command and capture output 55 | result = subprocess.run( 56 | ["uv", "run", "python3", "scripts/event_summary.py", str(log_file)], 57 | capture_output=True, 58 | text=True, 59 | check=True, 60 | ) 61 | 62 | # Update expected output file 63 | with open(expected_output_file, "w") as f: 64 | f.write(result.stdout) 65 | 66 | print(f"Updated test fixtures:\n- {expected_output_file}") 67 | 68 | 69 | if __name__ == "__main__": 70 | test_event_conversion() 71 | --------------------------------------------------------------------------------