universal-mcp-agents 0.1.14__tar.gz → 0.1.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. universal_mcp_agents-0.1.15/.github/workflows/evals.yml +61 -0
  2. universal_mcp_agents-0.1.15/.github/workflows/lint.yml +31 -0
  3. universal_mcp_agents-0.1.15/.github/workflows/release-please.yml +67 -0
  4. universal_mcp_agents-0.1.15/.github/workflows/tests.yml +39 -0
  5. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/.pre-commit-config.yaml +3 -7
  6. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/PKG-INFO +3 -3
  7. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/bump_and_release.sh +1 -1
  8. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/pyproject.toml +10 -5
  9. universal_mcp_agents-0.1.15/src/evals/datasets/tasks.jsonl +32 -0
  10. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/prompts.py +2 -2
  11. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/tests/test_agents.py +32 -33
  12. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/__init__.py +1 -1
  13. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/base.py +2 -1
  14. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/__main__.py +4 -3
  15. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/agent.py +1 -0
  16. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/graph.py +7 -4
  17. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/tools.py +4 -5
  18. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/builder/__main__.py +49 -23
  19. universal_mcp_agents-0.1.15/src/universal_mcp/agents/builder/builder.py +213 -0
  20. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/builder/helper.py +4 -6
  21. universal_mcp_agents-0.1.15/src/universal_mcp/agents/builder/prompts.py +107 -0
  22. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/builder/state.py +1 -1
  23. universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/__init__.py +4 -0
  24. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/agent.py +12 -5
  25. universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/langgraph_agent.py +14 -0
  26. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/llm_tool.py +1 -2
  27. universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/playbook_agent.py +353 -0
  28. universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/prompts.py +241 -0
  29. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/sandbox.py +43 -32
  30. universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/state.py +36 -0
  31. universal_mcp_agents-0.1.15/src/universal_mcp/agents/codeact0/tools.py +180 -0
  32. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/utils.py +53 -18
  33. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/shared/__main__.py +3 -2
  34. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/shared/prompts.py +1 -1
  35. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/shared/tool_node.py +17 -12
  36. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/utils.py +18 -12
  37. universal_mcp_agents-0.1.15/test.py +25 -0
  38. universal_mcp_agents-0.1.15/todo.md +157 -0
  39. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/uv.lock +1421 -852
  40. universal_mcp_agents-0.1.14/src/evals/datasets/tasks.jsonl +0 -21
  41. universal_mcp_agents-0.1.14/src/universal_mcp/agents/builder/builder.py +0 -214
  42. universal_mcp_agents-0.1.14/src/universal_mcp/agents/builder/prompts.py +0 -54
  43. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/__init__.py +0 -3
  44. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/langgraph_agent.py +0 -17
  45. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/prompts.py +0 -167
  46. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/state.py +0 -12
  47. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/1-unsubscribe.yaml +0 -4
  48. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/10-reddit2.yaml +0 -10
  49. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/11-github.yaml +0 -14
  50. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/2-reddit.yaml +0 -27
  51. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/2.1-instructions.md +0 -81
  52. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/2.2-instructions.md +0 -71
  53. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/3-earnings.yaml +0 -4
  54. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/4-maps.yaml +0 -41
  55. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/5-gmailreply.yaml +0 -8
  56. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/6-contract.yaml +0 -6
  57. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/7-overnight.yaml +0 -14
  58. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/8-sheets_chart.yaml +0 -25
  59. universal_mcp_agents-0.1.14/src/universal_mcp/agents/codeact0/usecases/9-learning.yaml +0 -9
  60. universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/__init__.py +0 -51
  61. universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/__main__.py +0 -28
  62. universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/graph.py +0 -85
  63. universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/prompts.py +0 -14
  64. universal_mcp_agents-0.1.14/src/universal_mcp/agents/planner/state.py +0 -11
  65. universal_mcp_agents-0.1.14/test.py +0 -61
  66. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/.gitignore +0 -0
  67. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/GEMINI.md +0 -0
  68. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/PROMPTS.md +0 -0
  69. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/README.md +0 -0
  70. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/__init__.py +0 -0
  71. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/dataset.py +0 -0
  72. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/datasets/codeact.jsonl +0 -0
  73. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/datasets/exact.jsonl +0 -0
  74. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/evaluators.py +0 -0
  75. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/run.py +1 -1
  76. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/evals/utils.py +0 -0
  77. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
  78. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/context.py +0 -0
  79. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
  80. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/bigtool/state.py +0 -0
  81. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/cli.py +0 -0
  82. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/__init__.py +0 -0
  83. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/__main__.py +0 -0
  84. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/agent.py +0 -0
  85. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/models.py +0 -0
  86. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/prompts.py +0 -0
  87. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/sandbox.py +0 -0
  88. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/state.py +0 -0
  89. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact/utils.py +0 -0
  90. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/__main__.py +0 -0
  91. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/codeact0/config.py +0 -0
  92. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/hil.py +0 -0
  93. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/llm.py +0 -0
  94. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/react.py +0 -0
  95. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/agents/simple.py +0 -0
  96. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/applications/llm/__init__.py +0 -0
  97. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/applications/llm/app.py +0 -0
  98. {universal_mcp_agents-0.1.14 → universal_mcp_agents-0.1.15}/src/universal_mcp/applications/ui/app.py +0 -0
@@ -0,0 +1,61 @@
1
+ name: evals
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ eval:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ include:
16
+ - { agent: bigtool, dataset: src/evals/datasets/tasks.jsonl, evaluator: llm_as_judge, difficulty: easy }
17
+ - { agent: codeact-repl, dataset: src/evals/datasets/codeact.jsonl, evaluator: codeact, difficulty: none }
18
+ steps:
19
+ - name: Check out repository
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: '3.11'
26
+
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@v3
29
+
30
+ - name: Install dependencies
31
+ run: |
32
+ uv sync --all-extras
33
+
34
+ - name: Run eval
35
+ env:
36
+ PYTHONUNBUFFERED: '1'
37
+ run: |
38
+ mkdir -p dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}
39
+ if [ "${{ matrix.difficulty }}" = "none" ]; then
40
+ uv run python -u src/evals/run.py \
41
+ ${{ matrix.agent }} \
42
+ ${{ matrix.dataset }} \
43
+ ${{ matrix.evaluator }} \
44
+ 2>&1 | tee dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}/run-${{ matrix.agent }}-${{ matrix.evaluator }}-$(basename ${{ matrix.dataset }} .jsonl)-none.log
45
+ else
46
+ uv run python -u src/evals/run.py \
47
+ ${{ matrix.agent }} \
48
+ ${{ matrix.dataset }} \
49
+ ${{ matrix.evaluator }} \
50
+ --difficulty ${{ matrix.difficulty }} \
51
+ 2>&1 | tee dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}/run-${{ matrix.agent }}-${{ matrix.evaluator }}-$(basename ${{ matrix.dataset }} .jsonl)-${{ matrix.difficulty }}.log
52
+ fi
53
+
54
+ - name: Upload eval artifacts
55
+ uses: actions/upload-artifact@v4
56
+ with:
57
+ name: eval-artifacts-${{ matrix.agent }}-${{ matrix.evaluator }}
58
+ path: dist/evals/${{ matrix.agent }}/${{ matrix.evaluator }}
59
+ if-no-files-found: warn
60
+
61
+
@@ -0,0 +1,31 @@
1
+ name: lint
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Check out repository
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: '3.11'
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v3
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ uv sync --all-extras
27
+
28
+ - name: Run ruff
29
+ run: |
30
+ uv run ruff check .
31
+ uv run ruff format --check .
@@ -0,0 +1,67 @@
1
+ name: release-please
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ release:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: googleapis/release-please-action@v4
17
+ id: release
18
+ with:
19
+ release-type: python
20
+ package-name: agents
21
+ default-branch: main
22
+
23
+ - name: Checkout repository
24
+ if: ${{ steps.release.outputs.release_created }}
25
+ uses: actions/checkout@v4
26
+ with:
27
+ token: ${{ secrets.GITHUB_TOKEN }}
28
+ fetch-depth: 0
29
+
30
+ - name: Add Authors and Update Release
31
+ if: ${{ steps.release.outputs.release_created }}
32
+ env:
33
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
+ RELEASE_TAG: ${{ steps.release.outputs.tag_name }}
35
+ run: |
36
+ set -e
37
+
38
+ TEMP_CHANGELOG="CHANGELOG.md.new"
39
+ touch "$TEMP_CHANGELOG"
40
+
41
+ if [ -f "CHANGELOG.md" ]; then
42
+ while IFS= read -r line; do
43
+ if [[ "$line" =~ \*\ (.*)\ \(\[([a-f0-9]{7,40})\]\(.* ]]; then
44
+ commit_hash="${BASH_REMATCH[2]}"
45
+ github_user=$(gh api "repos/${{ github.repository }}/commits/${commit_hash}" | jq -r '.author.login // "unknown"')
46
+ echo "${line} by @${github_user}" >> "$TEMP_CHANGELOG"
47
+ else
48
+ echo "$line" >> "$TEMP_CHANGELOG"
49
+ fi
50
+ done < "CHANGELOG.md"
51
+ fi
52
+
53
+ mv "$TEMP_CHANGELOG" "CHANGELOG.md"
54
+
55
+ RELEASE_BODY=$(awk "/^## \\[?${RELEASE_TAG#v}/{flag=1;next} /^## / && flag{exit} flag" CHANGELOG.md)
56
+ if [ -z "$RELEASE_BODY" ]; then
57
+ RELEASE_BODY="Release $RELEASE_TAG"
58
+ fi
59
+ gh release edit "$RELEASE_TAG" --notes "$RELEASE_BODY"
60
+
61
+ git config user.name "github-actions[bot]"
62
+ git config user.email "github-actions[bot]@users.noreply.github.com"
63
+ git add CHANGELOG.md || true
64
+ git commit -m "update CHANGELOG.md with author info [skip ci] [skip release]" || true
65
+ git push || true
66
+
67
+
@@ -0,0 +1,39 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Check out repository
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.11"
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v3
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ uv sync --all-extras
27
+
28
+ - name: Run tests
29
+ run: |
30
+ uv run pytest -s -q --maxfail=1 --disable-warnings --junitxml=junit.xml
31
+ env:
32
+ PYTHONUNBUFFERED: "1"
33
+
34
+ - name: Store artifacts
35
+ uses: actions/upload-artifact@v4
36
+ with:
37
+ name: test-artifacts
38
+ path: junit.xml
39
+ if-no-files-found: ignore
@@ -1,20 +1,16 @@
1
1
  fail_fast: false
2
2
 
3
3
  repos:
4
- - repo: https://github.com/pre-commit/mirrors-prettier
5
- rev: v3.1.0
6
- hooks:
7
- - id: prettier
8
- types_or: [yaml, json5]
9
4
 
10
5
  - repo: https://github.com/astral-sh/ruff-pre-commit
11
- rev: v0.11.13
6
+ rev: v0.13.3
12
7
  hooks:
13
8
  # Run the linter.
14
9
  - id: ruff-check
15
- args: [--fix]
10
+ args: ["--fix", "src", "--config", "pyproject.toml"]
16
11
  # Run the formatter.
17
12
  - id: ruff-format
13
+ args: ["src", "--config", "pyproject.toml"]
18
14
 
19
15
  # - repo: https://github.com/pre-commit/mirrors-mypy
20
16
  # rev: v1.8.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.14
3
+ Version: 0.1.15
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/universal-mcp/applications
6
6
  Project-URL: Repository, https://github.com/universal-mcp/applications
@@ -12,8 +12,8 @@ Requires-Dist: langchain-google-genai>=2.1.10
12
12
  Requires-Dist: langchain-openai>=0.3.32
13
13
  Requires-Dist: langgraph>=0.6.6
14
14
  Requires-Dist: typer>=0.17.4
15
- Requires-Dist: universal-mcp-applications>=0.1.14
16
- Requires-Dist: universal-mcp>=0.1.24rc21
15
+ Requires-Dist: universal-mcp-applications>=0.1.24
16
+ Requires-Dist: universal-mcp>=0.1.24rc25
17
17
  Provides-Extra: dev
18
18
  Requires-Dist: pre-commit; extra == 'dev'
19
19
  Requires-Dist: ruff; extra == 'dev'
@@ -9,7 +9,7 @@ uv sync --all-extras
9
9
 
10
10
  # Run tests with pytest
11
11
  echo "Running tests with pytest..."
12
- # uv run pytest
12
+ uv run pytest
13
13
 
14
14
  echo "Tests passed!"
15
15
 
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "universal-mcp-agents"
9
- version = "0.1.14"
9
+ version = "0.1.15"
10
10
  description = "Add your description here"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -19,8 +19,8 @@ dependencies = [
19
19
  "langchain-openai>=0.3.32",
20
20
  "langgraph>=0.6.6",
21
21
  "typer>=0.17.4",
22
- "universal-mcp>=0.1.24rc21",
23
- "universal-mcp-applications>=0.1.14",
22
+ "universal-mcp>=0.1.24rc25",
23
+ "universal-mcp-applications>=0.1.24",
24
24
  ]
25
25
 
26
26
  [project.license]
@@ -51,7 +51,7 @@ packages = [
51
51
 
52
52
  [tool.coverage.run]
53
53
  source = [
54
- "src",
54
+ "src",
55
55
  ]
56
56
  branch = true
57
57
 
@@ -68,6 +68,12 @@ lint.ignore = [
68
68
  "E501", # Ignore line length errors
69
69
  ]
70
70
 
71
+ [tool.ruff.lint.pylint]
72
+ max-args = 10
73
+ max-statements = 85
74
+ max-returns = 10
75
+ max-branches = 37
76
+
71
77
 
72
78
  [tool.ruff.format]
73
79
  quote-style = "double"
@@ -83,4 +89,3 @@ asyncio_default_fixture_loop_scope = "module"
83
89
  dev = [
84
90
  "ruff>=0.13.0",
85
91
  ]
86
-
@@ -0,0 +1,32 @@
1
+ {"user_input": "Send an email to manoj@agentr.dev with the subject 'Hello' and body 'This is a test of the Gmail agent.' from my Gmail account", "difficulty": 1, "required_tools": {"google_mail": ["send_email"]}}
2
+ {"user_input": "Show me events from today's Google Calendar.", "difficulty": 1, "required_tools": {"google_calendar": ["get_upcoming_events"]}}
3
+ {"user_input": "Fetch my last inbox mail from Microsoft Outlook", "difficulty": 1, "required_tools": {"outlook": ["list_user_messages"]}}
4
+ {"user_input": "Tell me how many meetings I have tomorrow and when they start from my Google Calendar.", "difficulty": 1, "required_tools": {"google_calendar": ["get_upcoming_events", "list_events"]}}
5
+ {"user_input": "Find the best restaurants in Goa using exa web search", "difficulty": 2, "required_tools": {"exa": ["search_with_filters"]}}
6
+ {"user_input": "List the unread emails from the last 24 hours from my Gmail, sorted by sender.", "difficulty": 2, "required_tools": {"google_mail": ["list_messages"]}}
7
+ {"user_input": "Create a meeting with aditakarsh@example.com on the topic of the latest trends in AI at 8PM today using Google Calendar.", "difficulty": 2, "required_tools": {"google_calendar": ["create_event", "create_event_from_text"]}}
8
+ {"user_input": "Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 1 day", "difficulty": 3, "required_tools": {"google_mail": ["list_messages"]}}
9
+ {"user_input": "Create a weekly expense report from my credit card transactions and categorize spending by type (food, transport, entertainment, etc.) in a Google Sheet", "difficulty": 3, "required_tools": {"google_sheet" : ["create_spreadsheet", "add_table"]}}
10
+ {"user_input": "search reddit for posts on elon musk and then post a meme on him on linkedin", "difficulty": 3, "required_tools": {"reddit" : ["search_reddit"], "linkedin": ["create_post"]}}
11
+ {"user_input": "Search for best cafes near IIT bombay using exa and make a google sheet out of it", "difficulty": 3, "required_tools": {"exa": ["search_with_filters"], "google_sheet": ["create_spreadsheet", "write_values_to_sheet", "add_table"]}}
12
+ {"user_input": "Create a Google Doc summarizing the last 5 merged pull requests in my GitHub repo- universal-mcp/universal-mcp, including links and commit highlights.", "difficulty": 4, "required_tools": {"github": ["list_pull_requests", "list_recent_commits"], "google_docs": ["create_document", "insert_text", "apply_text_style"]}}
13
+ {"user_input": "Summarize the key insights from all marketing emails received yesterday from my Gmail and add a section in a Google Doc with action points.", "difficulty": 4, "required_tools": {"google_mail": ["list_messages"], "google_docs": ["create_document", "insert_text", "apply_text_style"]}}
14
+ {"user_input": "Give me a report on the earnings of Oklo using web search, and projections for the company revenue, stock price", "difficulty": 4, "required_tools": {"tavily": ["search_and_summarize"]}}
15
+ {"user_input": "Track the top posts in r/startups over the past 7 days using Reddit and create a trend report on what's being discussed most (e.g., hiring, funding, MVPs) in a Google Doc.", "difficulty": 4, "required_tools": {"reddit": ["get_subreddit_posts", "get_subreddit_top_posts"], "google_docs": ["create_document", "insert_text", "apply_text_style"]}}
16
+ {"user_input": "Generate a comparison table of SaaS tools for project management using web search, including pricing, features, and user ratings in a Google Sheet", "difficulty": 4, "required_tools": {"tavily": ["search_and_summarize"], "google_sheet": ["create_spreadsheet", "add_table"]}}
17
+ {"user_input": "What are the topics of my meetings today from Google Calendar and who are the attendees? Give a 1-line context for each attendee using LinkedIn or web search.", "difficulty": 4, "required_tools": {"google_calendar": ["get_upcoming_events", "list_events"], "scraper": ["linkedin_retrieve_profile"]}}
18
+ {"user_input": "Draft personalized LinkedIn outreach messages for 10 potential collaborators in the fintech space based on their recent posts using LinkedIn data in a Google Sheet", "difficulty": 5, "required_tools": {"scraper": ["linkedin_retrieve_profile", "linkedin_list_profile_posts"], "google_sheet": ["create_spreadsheet", "write_values_to_sheet"]}}
19
+ {"user_input": "Create a content calendar for next month with trending AI/ML topics using web search and optimal posting times based on my audience analytics in Google Sheets", "difficulty": 5, "required_tools": {"tavily": ["search_and_summarize"], "google_sheet": ["get_values", "batch_get_values_by_range", "get_spreadsheet_metadata" , "create_spreadsheet", "add_sheet", "add_table"]}}
20
+ {"user_input": "Research the top 10 Y Combinator startups from the latest batch using web search and create a report on their industries and funding status in Google Docs", "difficulty": 5, "required_tools": {"tavily": ["search_and_summarize"], "google_docs": ["create_document", "insert_text", "insert_table"]}}
21
+ {"user_input": "Find and summarize the key takeaways from the latest earnings calls of FAANG companies using web search and create a report in Google Docs", "difficulty": 5, "required_tools": {"tavily": ["search_and_summarize"], "google_docs": ["create_document", "insert_text", "insert_table"]}}
22
+ {"user_input": "Find and extract unsubscribe links from all emails in my inbox from the last 7 days. List all unsubscribe links found with the email subject and sender.", "difficulty": 3, "required_tools": {"google_mail": ["list_messages", "get_message_details"]}}
23
+ {"user_input": "Process rows 2-5 from the Google Sheet (ID: 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8). For each unprocessed row, extract Reddit post links, fetch post details and comments, analyze content relevance to AgentR/Wingmen products, classify into tiers 1-4, generate appropriate response drafts, and update the sheet with all findings.", "difficulty": 5, "required_tools": {"google_sheet": ["add_table", "append_values", "update_values", "format_cells", "get_spreadsheet_metadata", "batch_get_values_by_range"], "reddit": ["get_post_comments_details"], "google_mail": ["list_messages"]}}
24
+ {"user_input": "Fetch all open issues from the GitHub repository \"microsoft/vscode\" and add them to a new Google Sheet. Then create corresponding tasks in ClickUp for each issue with descriptions, tags, and \"In Progress\" status. Delete processed rows from the sheet after creating ClickUp tasks.", "difficulty": 5, "required_tools": {"google_sheet": ["get_values", "create_spreadsheet", "write_values_to_sheet", "delete_dimensions", "append_values", "update_values"], "clickup": ["tasks_create_new_task", "spaces_get_details", "lists_get_list_details", "tasks_get_list_tasks"], "github": ["search_issues", "update_issue"]}}
25
+ {"user_input": "Goal: Process unprocessed rows in a fixed Google Sheet, scrape Reddit for context, filter posts, and generate short, natural comments linking to AgentR/Wingmen when relevant. Workflow: 1) Sheet & Row Selection: Fixed Sheet ID 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8, tab Posts. Process rows 2-5 (first 4 unprocessed rows) immediately without asking for user input. Only process rows with empty Match Type (Col I) and no Tier 1-4 assigned. 2) Reddit Context Fetch: Extract Post Link & ID. Use reddit to fetch post upvotes + top comments (max 5). Ensure post/comment is active, visible, and unlocked. 3) Filtration & Fit: Classify content (developer, consumer, anecdotal). Apply GTM Filtration to skip irrelevant, negative, political, or low-quality posts. Identify direct or adjacent fit to AgentR (Universal MCP Server) or Wingmen. Decide platform + account type: Direct fit/competitor mention → Technical Q = Team account, Non-technical = Burner account. Adjacent fit → Official account. Decide reply target (original comment/post or parent post). 4) Comment Generation: For Tier 1-3, craft a 2-3 line, context-aware, conversational reply. Mention AgentR/Wingmen organically, avoid sales tone or forced CTAs. Use light imperfections for human tone. Skip negative sentiment entirely. One comment per post. 5) Populate Output: Fill Upvote Count, Match Type, Account Type, Response Draft, Respond on. Return updated Google Sheet link. Tier Definitions: Tier 1 = Deep MCP, AI agent, tool integrations, or architecture discussions where infra is highly relevant. Tier 2 = Specific workflows, automation tooling, or productivity systems where Wingmen or MCP Server could be useful. Tier 3 = Broader ecosystem (LangChain/CrewAI/agent tooling) where a soft recommendation adds value. Tier 4 = Unclear, generic, sarcastic, hostile, or irrelevant mentions — skip. Execute immediately using the fixed Google Sheet ID: 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8, tab \"Posts\". Process rows(first 4 unprocessed rows) without asking for user input. Only process rows where Match Type (Column I) is empty. For each row, extract the Post Link, fetch Reddit data, apply GTM filtration, generate appropriate responses, and update the sheet. Return the updated Google Sheet link when complete.", "difficulty": 5, "required_tools": {"reddit": ["get_post_comments_details"], "google_sheet": ["update_values", "get_values", "get_spreadsheet_metadata", "batch_get_values_by_range"]}}
26
+ {"user_input": "Generate a financial flash report for Apple Inc. Research their latest earnings data including revenue, net income, EPS, and year-over-year changes. Create a formatted report with highlights, upcoming events, and summary. Present the report in chat and email it to adit@agentr.dev.", "difficulty": 4, "required_tools": {"exa": ["answer"], "google_mail": ["send_email"]}}
27
+ {"user_input": "Objective: Find businesses from Google Maps for a given category & location, store them in a Google Sheet, then process unprocessed leads to scrape emails and sync with HubSpot CRM. Stage 1 - Lead Discovery Get coordinates of Area + City. Search on Google Maps with category & coordinates. Extract: Name, Google Maps URL, Address, Phone, Website; leave Email & CRM Status blank. Sheet: Name: {Area}, {City} Leads - {Category} - {dd-mmm} If exists → append non-duplicate rows; else create in folder \"Leads from Google Maps\" (ID: 142QBejJX0jAqzDz_NHdwVTkcmagoog__). Add headers: Name | Google Maps URL | Address | Phone | Website | Email | CRM Status. Populate with businesses found. Edge Cases: No results → return message, skip sheet creation. Missing data → leave blank. Stage 2 - Lead Processing & CRM Sync Locate sheet in Google Drive, ensure headers match. Parse category from sheet name. Identify unprocessed rows (CRM Status blank) — by default process the first, or a specified row/range/count. Scrape Website for Email: If website exists → scrape homepage/contact page; fallback to firecrawl_scrape_url. Save found email in sheet. HubSpot Handling: Search contact by email/website/phone. If not found → create with available details, Lead Status = New, add note {Area, City} — {Category} — {Google Maps URL}. If exists → append note; keep other fields unchanged. Save HubSpot Contact URL/ID in sheet. Update CRM Status: Lead Created, Lead Creation Failed, Website not found, Email not found, etc. Edge Cases: No Website → create with phone; mark Website not found. No Email → create; mark Email not found. Email already in sheet → skip row. Execute immediately for \"Cafes\" near \"IIT Bombay\" in \"Mumbai\" without asking for confirmation.", "difficulty": 5, "required_tools": {"serpapi": ["google_maps_search"], "firecrawl": ["scrape_url"], "google_drive": ["get_file_details", "create_folder", "find_folder_id_by_name", "search_files"], "google_sheet": ["update_values", "get_values", "get_spreadsheet_metadata", "batch_get_values_by_range", "create_spreadsheet", "clear_values"], "hubspot": ["search_contacts_post", "batch_read_contacts_post", "get_contacts", "get_contact_by_id", "update_contact_by_id", "batch_update_contacts", "create_contacts_batch", "create_contact"]}}
28
+ {"user_input": "Process emails from the last 24 hours. Fetch primary inbox emails excluding replied threads, classify with LLM as Reply Required, No Reply Needed, or Ambiguous. For Reply Required/Ambiguous, draft human, on-brand replies for user review. Follow greeting, acknowledge, address concern, invite further questions, and friendly sign-off. Provide end summary of drafts, skipped, and ambiguous emails. Execute immediately without asking for confirmation. Do not send any emails. Just provide me a report.", "difficulty": 4, "required_tools": {"google_mail": ["list_messages", "get_message_details"]}}
29
+ {"user_input": "Analyze a contract from my google drive from the perspective of the Service Provider. Use the search to find it, do not ask me any questions, and assume details that I have not provided. Identify potentially unfavorable clauses such as vague terms, one-sided obligations, IP transfer issues, indemnity clauses, termination conditions, and payment problems. Provide a structured analysis with clause numbers, full text, and explanations of concerns.", "difficulty": 4, "required_tools": {"google_drive": ["get_file_details", "search_files"], "google_docs": ["get_document"], "exa": ["answer"]}}
30
+ {"user_input": "Create a summary of overnight updates from 8:00 PM yesterday to 8:00 AM today in IST. Check Gmail for important emails and ClickUp for mentions and assigned tasks. Organize findings into high priority and other items, then provide a comprehensive summary of all overnight activity.", "difficulty": 4, "required_tools": {"google_mail": ["list_messages"], "clickup": ["comments_get_task_comments", "comments_get_list_comments", "comments_get_view_comments", "tasks_get_list_tasks", "tasks_filter_team_tasks", "time_tracking_get_time_entries_within_date_range", "time_tracking_get_time_entry_history", "authorization_get_workspace_list", "spaces_get_details", "lists_get_list_details"]}}
31
+ {"user_input": "Analyze the data in Google Sheet (ID: 1nnnCp3_IWcdHv4UVgXtwYF5wedxbqF4RIeyjN6mCKD8) and create 3-5 relevant charts and visualizations. Add pie charts, bar graphs, and other appropriate visualizations based on the data structure. Embed all charts directly into the sheet and provide the updated sheet link.", "difficulty": 4, "required_tools": {"google_sheet": ["create_spreadsheet", "get_spreadsheet_metadata", "batch_get_values_by_range", "append_dimensions", "insert_dimensions", "delete_sheet", "add_sheet", "delete_dimensions", "add_basic_chart", "add_table", "add_pie_chart", "clear_values", "update_values", "clear_basic_filter", "get_values", "discover_tables", "set_basic_filter", "analyze_table_schema", "copy_sheet_to_spreadsheet", "append_values", "batch_get_values_by_data_filter", "batch_clear_values", "format_cells"]}}
32
+ {"user_input": "Create a 7-day learning plan for Python Programming. Research essential concepts and skills, create a detailed day-by-day plan with topics, goals, resources, and exercises. Compile the plan into a Google Doc and schedule daily emails at 8 AM starting today. Send Day 1 immediately to adit@agentr.dev and provide the Google Doc link.", "difficulty": 5, "required_tools": {"google_docs": ["get_document", "create_document", "insert_text"], "google_mail": ["send_email", "send_draft", "create_draft"], "exa": ["answer"]}}
@@ -53,13 +53,13 @@ CODEACT_EVALUATOR_PROMPT = """
53
53
  You are a code execution evaluator. You will be given the entire run of an agent, starting with a human input task, the intermediate steps taken, and the final output of the agent given to the user. These steps will contain code written by the agent to solve the problem as well as its outputs. Your job is to check ONLY if the code executes correctly.
54
54
  Keep in mind that the agent has access to tools like- ai_classify, call_llm, creative_writer, data_extractor. These calls are to be treated as valid if they run without errors.
55
55
  These are the only criteria you should evaluate-
56
-
56
+
57
57
  <Rubric>
58
58
  - The code written by the agent in tool calls should be syntactically correct and use existing objects.
59
59
  - The code outputs should not have an error or empty/unexpected outputs
60
60
  </Rubric>
61
61
  If either of the above are not satisfied, you should give 0.
62
-
62
+
63
63
  <Reminder>
64
64
  You must not judge whether the code is helpful to the task or not, only if the code itself is correct or not.
65
65
  </Reminder>
@@ -145,7 +145,7 @@ class MockToolRegistry(ToolRegistry):
145
145
  self,
146
146
  query: str,
147
147
  limit: int = 10,
148
- **kwargs: Any,
148
+ distance_threshold: float = 0.7,
149
149
  ) -> list[dict[str, Any]]:
150
150
  """
151
151
  Search for apps by a query.
@@ -168,7 +168,7 @@ class MockToolRegistry(ToolRegistry):
168
168
  query: str,
169
169
  limit: int = 10,
170
170
  app_id: str | None = None,
171
- **kwargs: Any,
171
+ distance_threshold: float = 0.8,
172
172
  ) -> list[dict[str, Any]]:
173
173
  """
174
174
  Search for tools by a query.
@@ -177,11 +177,23 @@ class MockToolRegistry(ToolRegistry):
177
177
  brittle keyword search.
178
178
  """
179
179
  if not app_id:
180
- return []
181
-
182
- # Return all tools for the given app, letting the LLM choose.
180
+ # General search
181
+ all_tools = []
182
+ for current_app_id, tools in self._tools.items():
183
+ for tool in tools:
184
+ tool_with_app_id = tool.copy()
185
+ tool_with_app_id["id"] = f"{current_app_id}__{tool['name']}"
186
+ all_tools.append(tool_with_app_id)
187
+ return all_tools[:limit]
188
+
189
+ # App-specific search
183
190
  all_app_tools = self._tools.get(app_id, [])
184
- return all_app_tools[:limit]
191
+ tools_with_app_id = []
192
+ for tool in all_app_tools:
193
+ tool_with_app_id = tool.copy()
194
+ tool_with_app_id["id"] = f"{app_id}__{tool['name']}"
195
+ tools_with_app_id.append(tool_with_app_id)
196
+ return tools_with_app_id[:limit]
185
197
 
186
198
  async def export_tools(
187
199
  self,
@@ -219,17 +231,6 @@ class TestToolFinderGraph:
219
231
  def registry(self):
220
232
  return MockToolRegistry()
221
233
 
222
- def _get_tool_config_from_plan(self, plan: list[dict[str, Any]]) -> dict[str, list[str]]:
223
- """
224
- Helper function to convert a consolidated execution plan to a tool_config dict.
225
- MODIFIED: This now correctly handles the already-consolidated plan from the graph.
226
- """
227
- if not plan:
228
- return {}
229
-
230
- config = {step["app_id"]: step["tool_ids"] for step in plan if step.get("app_id") and step.get("tool_ids")}
231
- return config
232
-
233
234
  @pytest.mark.asyncio
234
235
  async def test_simple_case(self, llm, registry):
235
236
  """Test Case 1: Simple task requiring a single app and tool."""
@@ -239,9 +240,7 @@ class TestToolFinderGraph:
239
240
  {"original_task": task, "messages": [HumanMessage(content=task)], "decomposition_attempts": 0}
240
241
  )
241
242
 
242
- plan = final_state.get("execution_plan")
243
-
244
- tool_config = self._get_tool_config_from_plan(plan)
243
+ tool_config = final_state.get("execution_plan")
245
244
 
246
245
  # FIX: Assert against the correct, hyphenated app ID.
247
246
  assert "google_mail" in tool_config
@@ -256,10 +255,8 @@ class TestToolFinderGraph:
256
255
  {"original_task": task, "messages": [HumanMessage(content=task)], "decomposition_attempts": 0}
257
256
  )
258
257
 
259
- plan = final_state.get("execution_plan")
260
- assert plan, "Execution plan should not be empty"
261
-
262
- tool_config = self._get_tool_config_from_plan(plan)
258
+ tool_config = final_state.get("execution_plan")
259
+ assert tool_config, "Execution plan should not be empty"
263
260
 
264
261
  assert "github" in tool_config
265
262
  assert "create_issue" in tool_config["github"]
@@ -277,7 +274,7 @@ class TestToolFinderGraph:
277
274
  plan = final_state.get("execution_plan")
278
275
  assert not plan
279
276
  last_message = final_state.get("messages", [])[-1].content
280
- assert "unable to create a complete plan" in last_message.lower()
277
+ assert "could not create a final plan" in last_message.lower()
281
278
 
282
279
 
283
280
  @pytest.mark.parametrize(
@@ -314,19 +311,20 @@ class TestAgents:
314
311
  await agent.ainit()
315
312
  # Invoke the agent graph to get the final state
316
313
  final_state = await agent.invoke(
317
- user_input=task,
314
+ user_input={"userInput": task} if agent.name == "Test builder" else task,
318
315
  thread_id=thread_id,
319
316
  )
320
317
 
321
318
  # Extract the content of the last message
322
- final_messages = final_state.get("messages", [])
323
- assert final_messages, "The agent should have produced at least one message."
324
- last_message = final_messages[-1]
319
+ if agent.name != "Test builder":
320
+ final_messages = final_state.get("messages", [])
321
+ assert final_messages, "The agent should have produced at least one message."
322
+ last_message = final_messages[-1]
325
323
 
326
- final_response = last_message.content if hasattr(last_message, "content") else str(last_message)
324
+ final_response = last_message.content if hasattr(last_message, "content") else str(last_message)
327
325
 
328
- assert final_response is not None, "The final response should not be None."
329
- assert final_response != "", "The final response should not be an empty string."
326
+ assert final_response is not None, "The final response should not be None."
327
+ assert final_response != "", "The final response should not be an empty string."
330
328
 
331
329
 
332
330
  class TestAgentBuilder:
@@ -346,8 +344,9 @@ class TestAgentBuilder:
346
344
  async def test_create_agent(self, agent_builder: BuilderAgent):
347
345
  """Test case for creating an agent with the builder."""
348
346
  task = "Send a daily email to manoj@agentr.dev with daily agenda of the day"
347
+ thread_id = "test-thread-create-agent"
349
348
 
350
- result = await agent_builder.invoke(task)
349
+ result = await agent_builder.invoke(thread_id=thread_id, user_input={"userInput": task})
351
350
 
352
351
  assert "generated_agent" in result
353
352
  generated_agent = result["generated_agent"]
@@ -4,7 +4,7 @@ from universal_mcp.agents.base import BaseAgent
4
4
  from universal_mcp.agents.bigtool import BigToolAgent
5
5
  from universal_mcp.agents.builder.builder import BuilderAgent
6
6
  from universal_mcp.agents.codeact import CodeActAgent as CodeActScript
7
- from universal_mcp.agents.codeact0 import CodeActAgent as CodeActRepl
7
+ from universal_mcp.agents.codeact0 import CodeActPlaybookAgent as CodeActRepl
8
8
  from universal_mcp.agents.react import ReactAgent
9
9
  from universal_mcp.agents.simple import SimpleAgent
10
10
 
@@ -85,6 +85,7 @@ class BaseAgent:
85
85
  event = cast(AIMessageChunk, event)
86
86
  event.usage_metadata = aggregate.usage_metadata
87
87
  logger.debug(f"Usage metadata: {event.usage_metadata}")
88
+ event.content = "" # Clear the message since it would have already been streamed above
88
89
  yield event
89
90
 
90
91
  async def stream_interactive(self, thread_id: str, user_input: str):
@@ -116,7 +117,7 @@ class BaseAgent:
116
117
  "configurable": {"thread_id": thread_id},
117
118
  "metadata": run_metadata,
118
119
  "run_id": thread_id,
119
- "run_name" : self.name
120
+ "run_name": self.name,
120
121
  }
121
122
 
122
123
  result = await self._graph.ainvoke(
@@ -2,15 +2,16 @@ import asyncio
2
2
 
3
3
  from loguru import logger
4
4
  from universal_mcp.agentr.registry import AgentrRegistry
5
- from universal_mcp.agents.bigtoolcache import BigToolAgentCache
5
+
6
+ from universal_mcp.agents.bigtool import BigToolAgent
6
7
 
7
8
 
8
9
  async def main():
9
- agent = BigToolAgentCache(
10
+ agent = BigToolAgent(
10
11
  registry=AgentrRegistry(),
11
12
  )
12
13
  async for event in agent.stream(
13
- user_input="Send an email to manoj@agentr.dev",
14
+ user_input="Load a supabase tool",
14
15
  thread_id="test123",
15
16
  ):
16
17
  logger.info(event.content)
@@ -1,4 +1,5 @@
1
1
  from universal_mcp.agentr.registry import AgentrRegistry
2
+
2
3
  from universal_mcp.agents.bigtool import BigToolAgent
3
4
 
4
5
 
@@ -11,9 +11,10 @@ from langgraph.types import Command, RetryPolicy
11
11
  from universal_mcp.tools.registry import ToolRegistry
12
12
  from universal_mcp.types import ToolFormat
13
13
 
14
+ from universal_mcp.agents.utils import filter_retry_on
15
+
14
16
  from .state import State
15
17
  from .tools import get_valid_tools
16
- from universal_mcp.agents.utils import filter_retry_on
17
18
 
18
19
  load_dotenv()
19
20
 
@@ -96,13 +97,16 @@ def build_graph(
96
97
  for tool_call in tool_calls:
97
98
  try:
98
99
  if tool_call["name"] == "load_tools": # Handle load_tools separately
99
- valid_tools, unconnected_links = await get_valid_tools(tool_ids=tool_call["args"]["tool_ids"], registry=registry)
100
+ valid_tools, unconnected_links = await get_valid_tools(
101
+ tool_ids=tool_call["args"]["tool_ids"], registry=registry
102
+ )
100
103
  new_tool_ids.extend(valid_tools)
101
104
  # Create tool message response
102
105
  tool_result = f"Successfully loaded {len(valid_tools)} tools: {valid_tools}"
103
106
  if unconnected_links:
104
107
  ask_user = True
105
- ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {'\n'.join(unconnected_links)} "
108
+ links = "\n".join(unconnected_links)
109
+ ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {links} "
106
110
 
107
111
  elif tool_call["name"] == "search_tools":
108
112
  tool_result = await meta_tools["search_tools"].ainvoke(tool_call["args"])
@@ -115,7 +119,6 @@ def build_graph(
115
119
  except Exception as e:
116
120
  tool_result = f"Error during {tool_call}: {e}"
117
121
 
118
-
119
122
  tool_message = ToolMessage(
120
123
  content=json.dumps(tool_result),
121
124
  name=tool_call["name"],
@@ -34,9 +34,8 @@ def create_meta_tools(tool_registry: ToolRegistry) -> dict[str, Any]:
34
34
  for tools_list in query_results:
35
35
  for tool in tools_list:
36
36
  app = tool["id"].split("__")[0]
37
- if len(app_tools[app]) < 5:
38
- cleaned_desc = tool['description'].split("Context:")[0].strip()
39
- app_tools[app].append(f"{tool['id']}: {cleaned_desc}")
37
+ cleaned_desc = tool["description"].split("Context:")[0].strip()
38
+ app_tools[app].append(f"{tool['id']}: {cleaned_desc}")
40
39
 
41
40
  # Build result string efficiently
42
41
  result_parts = []
@@ -133,7 +132,7 @@ async def get_valid_tools(tool_ids: list[str], registry: ToolRegistry) -> tuple[
133
132
  app_tool_list[app] = tools
134
133
 
135
134
  # Validate tool_ids
136
- for app, tool_entries in app_to_tools.items():
135
+ for app, tool_entries in app_to_tools.items():
137
136
  available = app_tool_list.get(app)
138
137
  if available is None:
139
138
  incorrect.extend(tool_id for tool_id, _ in tool_entries)
@@ -142,7 +141,7 @@ async def get_valid_tools(tool_ids: list[str], registry: ToolRegistry) -> tuple[
142
141
  unconnected.add(app)
143
142
  text = registry.client.get_authorization_url(app)
144
143
  start = text.find(":") + 1
145
- end = text.find(".", start)
144
+ end = text.find(". R", start)
146
145
  url = text[start:end].strip()
147
146
  markdown_link = f"[{app}]({url})"
148
147
  unconnected_links.append(markdown_link)