Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions docs/middleware/groups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Groups

When MCP servers expose many tools, agents can become overwhelmed with options, leading to poor tool selection and increased token usage. The `GroupsMiddleware` in <span class="wags-brand">wags</span> enables progressive tool disclosure by organizing tools into hierarchical groups that agents can enable or disable as needed.

## How It Works

Tools are assigned to groups using the `@in_group` decorator. The middleware:

1. Hides all grouped tools initially (or starts with configured `initial_groups`)
2. Exposes `enable_tools` and `disable_tools` meta-tools
3. Progressively reveals child groups as parents are enabled
4. Enforces optional `max_tools` limits

## Example

```python linenums="1" title="handlers.py"
from wags.middleware import GroupsMiddleware, GroupDefinition, in_group

class GithubHandlers:
@in_group("issues")
async def create_issue(self, owner: str, repo: str, title: str):
pass

@in_group("issues")
async def list_issues(self, owner: str, repo: str):
pass

@in_group("repos")
async def create_repository(self, name: str):
pass
```

Configure the middleware with group definitions:

```python title="main.py"
from wags.middleware import GroupsMiddleware, GroupDefinition

handlers = GithubHandlers()
mcp.add_middleware(
GroupsMiddleware(
groups={
"issues": GroupDefinition(description="Issue tracking"),
"repos": GroupDefinition(description="Repository management"),
},
handlers=handlers,
initial_groups=["issues"], # Start with issues enabled
max_tools=10, # Optional limit
)
)
```

## Hierarchical Groups

Groups can be nested using the `parent` parameter for progressive disclosure:

```python
groups = {
"code": GroupDefinition(description="Code management"),
"repos": GroupDefinition(description="Repositories", parent="code"),
"branches": GroupDefinition(description="Branches", parent="repos"),
}
```

With this hierarchy:

- Only `code` is visible initially (root group)
- Enabling `code` reveals `repos` as an option
- Enabling `repos` reveals `branches` as an option
- Disabling `code` cascades to disable `repos` and `branches`

## Agent Interaction

When an agent calls `enable_tools(groups=["issues"])`, it receives a structured JSON response:

```json
{
"enabled": ["issues"],
"enabled_groups": ["issues"],
"available_tools": ["create_issue", "list_issues"],
"available_groups": [],
"errors": []
}
```

The response includes:

- `enabled`: Groups that were newly enabled by this call
- `enabled_groups`: All currently enabled groups
- `available_tools`: Tools now available to the agent
- `available_groups`: Child groups that can now be enabled
- `errors`: Any validation errors (unknown groups, already enabled, etc.)

Similarly, `disable_tools` returns:

```json
{
"disabled": ["issues"],
"enabled_groups": [],
"available_tools": [],
"errors": []
}
```

A `tools/list_changed` notification is sent whenever groups are enabled or disabled, prompting the client to refresh its tool list.

When a tool from a disabled group is called, the agent receives an error message with a hint about which group to enable.

## API Documentation

::: wags.middleware.groups.in_group
options:
show_source: false
members: []
show_signature: false

::: wags.middleware.groups.GroupDefinition
options:
show_source: false
members: []
show_signature: false

::: wags.middleware.groups.GroupsMiddleware
options:
show_source: false
show_bases: false
members: []
show_signature: false

::: wags.middleware.groups.EnableToolsResult
options:
show_source: false
members: []
show_signature: false

::: wags.middleware.groups.DisableToolsResult
options:
show_source: false
members: []
show_signature: false
1 change: 1 addition & 0 deletions docs/middleware/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The <span class="wags-brand">wags</span> middleware toolkit further provides *fi

Understand what features different middlewares provide and how to configure them:

- [Groups](groups.md) for progressive tool disclosure via hierarchical groups.
- [TodoList](todo.md) to ensure agents perform complex tasks correctly.
- [Roots](roots.md) to enable client-configured fine-grained access control for MCP servers.
- [Elicitation](elicitation.md) add human-in-the-loop features to improve UX.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ nav:
- Onboarding Servers: onboarding.md
- Middleware:
- Overview: middleware/overview.md
- Groups: middleware/groups.md
- TodoList: middleware/todo.md
- Roots: middleware/roots.md
- Elicitation: middleware/elicitation.md
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ override-dependencies = [
"google-genai>=1.33.0", # Override mcpuniverse's google-genai==1.16.1 constraint
"mistralai>=1.7.0", # Override mcpuniverse's mistralai==1.6.0 constraint
"python-dotenv>=1.1.0", # Override mcpuniverse's python-dotenv==1.0.1 constraint
"uvicorn>=0.35.0", # Override mcpuniverse's uvicorn==0.34.0 constraint
"fastapi>=0.121.0", # Override mcpuniverse's fastapi==0.115.12 constraint
"tiktoken>=0.12.0", # Override mcpuniverse's tiktoken==0.11.0 constraint
"mcp @ git+https://github.com/chughtapan/python-sdk.git@wags-dev", # Align with core dependency
]

Expand Down
12 changes: 12 additions & 0 deletions src/wags/middleware/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
"""WAGS middleware components."""

from .elicitation import ElicitationMiddleware, RequiresElicitation
from .groups import (
DisableToolsResult,
EnableToolsResult,
GroupDefinition,
GroupsMiddleware,
in_group,
)
from .roots import RootsMiddleware, requires_root

__all__ = [
"DisableToolsResult",
"ElicitationMiddleware",
"EnableToolsResult",
"GroupDefinition",
"GroupsMiddleware",
"RequiresElicitation",
"RootsMiddleware",
"in_group",
"requires_root",
]
Loading