forked from rsp2k/mcp-mailu
Fix resource implementation to follow FastMCP best practices
- Return dict/list directly instead of JSON strings for proper serialization - FastMCP automatically serializes dicts/lists to JSON - Update return types from str to dict/list/Union types - Fix error handling to return structured data instead of strings - Add Union type import for better type hints - Version bump to 0.4.1 for resource fixes Per FastMCP docs: - Dictionaries/Lists are automatically serialized to JSON - Resources should return native Python data structures - FastMCP handles the serialization to proper MCP format 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cd40a3e899
commit
10e13b5bc7
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mcp-mailu"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
description = "FastMCP server for Mailu email server API integration"
|
||||
authors = [
|
||||
{name = "Ryan Malloy", email = "ryan@supported.systems"}
|
||||
|
@ -3,7 +3,7 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
import httpx
|
||||
from dotenv import load_dotenv
|
||||
@ -62,159 +62,159 @@ def create_mcp_server() -> FastMCP:
|
||||
|
||||
# List resources - no parameters
|
||||
@mcp.resource("mailu://users")
|
||||
async def users_resource(ctx: Context) -> str:
|
||||
async def users_resource(ctx: Context) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error listing users: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
@mcp.resource("mailu://domains")
|
||||
async def domains_resource(ctx: Context) -> str:
|
||||
async def domains_resource(ctx: Context) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error listing domains: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
@mcp.resource("mailu://aliases")
|
||||
async def aliases_resource(ctx: Context) -> str:
|
||||
async def aliases_resource(ctx: Context) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error listing aliases: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
@mcp.resource("mailu://alternatives")
|
||||
async def alternatives_resource(ctx: Context) -> str:
|
||||
async def alternatives_resource(ctx: Context) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error listing alternative domains: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
@mcp.resource("mailu://relays")
|
||||
async def relays_resource(ctx: Context) -> str:
|
||||
async def relays_resource(ctx: Context) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error listing relays: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
# Detail resources with parameters
|
||||
@mcp.resource("mailu://user/{email}")
|
||||
async def user_resource(ctx: Context, email: str) -> str:
|
||||
async def user_resource(ctx: Context, email: str) -> dict:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting user {email}: {e}"
|
||||
return {"error": str(e)}
|
||||
|
||||
@mcp.resource("mailu://domain/{domain}")
|
||||
async def domain_resource(ctx: Context, domain: str) -> str:
|
||||
async def domain_resource(ctx: Context, domain: str) -> dict:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting domain {domain}: {e}"
|
||||
return {"error": str(e)}
|
||||
|
||||
@mcp.resource("mailu://alias/{alias}")
|
||||
async def alias_resource(ctx: Context, alias: str) -> str:
|
||||
async def alias_resource(ctx: Context, alias: str) -> dict:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting alias {alias}: {e}"
|
||||
return {"error": str(e)}
|
||||
|
||||
@mcp.resource("mailu://alternative/{alt}")
|
||||
async def alternative_resource(ctx: Context, alt: str) -> str:
|
||||
async def alternative_resource(ctx: Context, alt: str) -> dict:
|
||||
"""Get details of a specific alternative domain."""
|
||||
try:
|
||||
async with get_mailu_client() as mailu_client:
|
||||
response = await mailu_client.get(f"/alternative/{alt}")
|
||||
response.raise_for_status()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting alternative domain {alt}: {e}"
|
||||
return {"error": str(e)}
|
||||
|
||||
@mcp.resource("mailu://relay/{name}")
|
||||
async def relay_resource(ctx: Context, name: str) -> str:
|
||||
async def relay_resource(ctx: Context, name: str) -> dict:
|
||||
"""Get details of a specific relay."""
|
||||
try:
|
||||
async with get_mailu_client() as mailu_client:
|
||||
response = await mailu_client.get(f"/relay/{name}")
|
||||
response.raise_for_status()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting relay {name}: {e}"
|
||||
return {"error": str(e)}
|
||||
|
||||
@mcp.resource("mailu://domain/{domain}/users")
|
||||
async def domain_users_resource(ctx: Context, domain: str) -> str:
|
||||
async def domain_users_resource(ctx: Context, domain: str) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting users for domain {domain}: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
@mcp.resource("mailu://domain/{domain}/managers")
|
||||
async def domain_managers_resource(ctx: Context, domain: str) -> str:
|
||||
async def domain_managers_resource(ctx: Context, domain: str) -> list:
|
||||
"""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()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting managers for domain {domain}: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
@mcp.resource("mailu://domain/{domain}/manager/{email}")
|
||||
async def domain_manager_resource(ctx: Context, domain: str, email: str) -> str:
|
||||
async def domain_manager_resource(ctx: Context, domain: str, email: str) -> dict:
|
||||
"""Get details of a specific domain manager."""
|
||||
try:
|
||||
async with get_mailu_client() as mailu_client:
|
||||
response = await mailu_client.get(f"/domain/{domain}/manager/{email}")
|
||||
response.raise_for_status()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting manager {email} for domain {domain}: {e}"
|
||||
return {"error": str(e)}
|
||||
|
||||
@mcp.resource("mailu://aliases/destination/{domain}")
|
||||
async def aliases_by_destination_resource(ctx: Context, domain: str) -> str:
|
||||
async def aliases_by_destination_resource(ctx: Context, domain: str) -> list:
|
||||
"""Find aliases by destination domain."""
|
||||
try:
|
||||
async with get_mailu_client() as mailu_client:
|
||||
response = await mailu_client.get(f"/alias/destination/{domain}")
|
||||
response.raise_for_status()
|
||||
return json.dumps(response.json(), indent=2)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
return f"Error getting aliases for destination domain {domain}: {e}"
|
||||
return [{"error": str(e)}]
|
||||
|
||||
# ===== TOOLS (ACTIONS) =====
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user