Add list tools for Claude Desktop compatibility

- Add dedicated list tools for all entities (list_users, list_domains, etc.)
- Add get tools for individual items (get_user, get_domain, get_alias)
- Keep resources for MCP compliance while adding tools for better Claude Desktop support
- Tools return formatted strings with counts for better readability
- Version bump to 0.4.2 for Claude Desktop compatibility

Tools added:
- list_users, list_domains, list_aliases, list_alternative_domains, list_relays
- get_user, get_domain, get_alias
- list_domain_users, list_domain_managers

This ensures Claude Desktop can easily fetch lists regardless of how it handles resources.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ryan Malloy 2025-07-16 14:42:31 -06:00
parent 10e13b5bc7
commit a5f1985c8b
3 changed files with 123 additions and 2 deletions

View File

@ -1,6 +1,6 @@
[project] [project]
name = "mcp-mailu" name = "mcp-mailu"
version = "0.4.1" version = "0.4.2"
description = "FastMCP server for Mailu email server API integration" description = "FastMCP server for Mailu email server API integration"
authors = [ authors = [
{name = "Ryan Malloy", email = "ryan@supported.systems"} {name = "Ryan Malloy", email = "ryan@supported.systems"}

View File

@ -218,6 +218,127 @@ def create_mcp_server() -> FastMCP:
# ===== TOOLS (ACTIONS) ===== # ===== TOOLS (ACTIONS) =====
# List tools for Claude Desktop
@mcp.tool()
async def list_users() -> str:
"""List all users in the Mailu instance."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get("/user")
response.raise_for_status()
users = response.json()
return f"Found {len(users)} users: {json.dumps(users, indent=2)}"
except Exception as e:
return f"Error listing users: {e}"
@mcp.tool()
async def list_domains() -> str:
"""List all domains in the Mailu instance."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get("/domain")
response.raise_for_status()
domains = response.json()
return f"Found {len(domains)} domains: {json.dumps(domains, indent=2)}"
except Exception as e:
return f"Error listing domains: {e}"
@mcp.tool()
async def list_aliases() -> str:
"""List all aliases in the Mailu instance."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get("/alias")
response.raise_for_status()
aliases = response.json()
return f"Found {len(aliases)} aliases: {json.dumps(aliases, indent=2)}"
except Exception as e:
return f"Error listing aliases: {e}"
@mcp.tool()
async def list_alternative_domains() -> str:
"""List all alternative domains in the Mailu instance."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get("/alternative")
response.raise_for_status()
alternatives = response.json()
return f"Found {len(alternatives)} alternative domains: {json.dumps(alternatives, indent=2)}"
except Exception as e:
return f"Error listing alternative domains: {e}"
@mcp.tool()
async def list_relays() -> str:
"""List all relays in the Mailu instance."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get("/relay")
response.raise_for_status()
relays = response.json()
return f"Found {len(relays)} relays: {json.dumps(relays, indent=2)}"
except Exception as e:
return f"Error listing relays: {e}"
@mcp.tool()
async def get_user(email: str) -> str:
"""Get details of a specific user."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get(f"/user/{email}")
response.raise_for_status()
user = response.json()
return f"User details for {email}: {json.dumps(user, indent=2)}"
except Exception as e:
return f"Error getting user {email}: {e}"
@mcp.tool()
async def get_domain(domain: str) -> str:
"""Get details of a specific domain."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get(f"/domain/{domain}")
response.raise_for_status()
domain_data = response.json()
return f"Domain details for {domain}: {json.dumps(domain_data, indent=2)}"
except Exception as e:
return f"Error getting domain {domain}: {e}"
@mcp.tool()
async def get_alias(alias: str) -> str:
"""Get details of a specific alias."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get(f"/alias/{alias}")
response.raise_for_status()
alias_data = response.json()
return f"Alias details for {alias}: {json.dumps(alias_data, indent=2)}"
except Exception as e:
return f"Error getting alias {alias}: {e}"
@mcp.tool()
async def list_domain_users(domain: str) -> str:
"""List all users in a specific domain."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get(f"/domain/{domain}/users")
response.raise_for_status()
users = response.json()
return f"Found {len(users)} users in domain {domain}: {json.dumps(users, indent=2)}"
except Exception as e:
return f"Error listing users for domain {domain}: {e}"
@mcp.tool()
async def list_domain_managers(domain: str) -> str:
"""List all managers for a specific domain."""
try:
async with get_mailu_client() as mailu_client:
response = await mailu_client.get(f"/domain/{domain}/manager")
response.raise_for_status()
managers = response.json()
return f"Found {len(managers)} managers for domain {domain}: {json.dumps(managers, indent=2)}"
except Exception as e:
return f"Error listing managers for domain {domain}: {e}"
# User management tools # User management tools
@mcp.tool() @mcp.tool()
async def create_user(email: str, raw_password: str, comment: str = "", quota_bytes: int = 0, async def create_user(email: str, raw_password: str, comment: str = "", quota_bytes: int = 0,

2
uv.lock generated
View File

@ -613,7 +613,7 @@ wheels = [
[[package]] [[package]]
name = "mcp-mailu" name = "mcp-mailu"
version = "0.4.0" version = "0.4.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "fastmcp" }, { name = "fastmcp" },