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]
|
[project]
|
||||||
name = "mcp-mailu"
|
name = "mcp-mailu"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
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"}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@ -62,159 +62,159 @@ def create_mcp_server() -> FastMCP:
|
|||||||
|
|
||||||
# List resources - no parameters
|
# List resources - no parameters
|
||||||
@mcp.resource("mailu://users")
|
@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."""
|
"""List all users in the Mailu instance."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get("/user")
|
response = await mailu_client.get("/user")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error listing users: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
@mcp.resource("mailu://domains")
|
@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."""
|
"""List all domains in the Mailu instance."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get("/domain")
|
response = await mailu_client.get("/domain")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error listing domains: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
@mcp.resource("mailu://aliases")
|
@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."""
|
"""List all aliases in the Mailu instance."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get("/alias")
|
response = await mailu_client.get("/alias")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error listing aliases: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
@mcp.resource("mailu://alternatives")
|
@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."""
|
"""List all alternative domains in the Mailu instance."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get("/alternative")
|
response = await mailu_client.get("/alternative")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error listing alternative domains: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
@mcp.resource("mailu://relays")
|
@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."""
|
"""List all relays in the Mailu instance."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get("/relay")
|
response = await mailu_client.get("/relay")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error listing relays: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
# Detail resources with parameters
|
# Detail resources with parameters
|
||||||
@mcp.resource("mailu://user/{email}")
|
@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."""
|
"""Get details of a specific user."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/user/{email}")
|
response = await mailu_client.get(f"/user/{email}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting user {email}: {e}"
|
return {"error": str(e)}
|
||||||
|
|
||||||
@mcp.resource("mailu://domain/{domain}")
|
@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."""
|
"""Get details of a specific domain."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/domain/{domain}")
|
response = await mailu_client.get(f"/domain/{domain}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting domain {domain}: {e}"
|
return {"error": str(e)}
|
||||||
|
|
||||||
@mcp.resource("mailu://alias/{alias}")
|
@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."""
|
"""Get details of a specific alias."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/alias/{alias}")
|
response = await mailu_client.get(f"/alias/{alias}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting alias {alias}: {e}"
|
return {"error": str(e)}
|
||||||
|
|
||||||
@mcp.resource("mailu://alternative/{alt}")
|
@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."""
|
"""Get details of a specific alternative domain."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/alternative/{alt}")
|
response = await mailu_client.get(f"/alternative/{alt}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting alternative domain {alt}: {e}"
|
return {"error": str(e)}
|
||||||
|
|
||||||
@mcp.resource("mailu://relay/{name}")
|
@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."""
|
"""Get details of a specific relay."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/relay/{name}")
|
response = await mailu_client.get(f"/relay/{name}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting relay {name}: {e}"
|
return {"error": str(e)}
|
||||||
|
|
||||||
@mcp.resource("mailu://domain/{domain}/users")
|
@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."""
|
"""List all users in a specific domain."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/domain/{domain}/users")
|
response = await mailu_client.get(f"/domain/{domain}/users")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting users for domain {domain}: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
@mcp.resource("mailu://domain/{domain}/managers")
|
@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."""
|
"""List all managers for a specific domain."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/domain/{domain}/manager")
|
response = await mailu_client.get(f"/domain/{domain}/manager")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting managers for domain {domain}: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
@mcp.resource("mailu://domain/{domain}/manager/{email}")
|
@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."""
|
"""Get details of a specific domain manager."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/domain/{domain}/manager/{email}")
|
response = await mailu_client.get(f"/domain/{domain}/manager/{email}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting manager {email} for domain {domain}: {e}"
|
return {"error": str(e)}
|
||||||
|
|
||||||
@mcp.resource("mailu://aliases/destination/{domain}")
|
@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."""
|
"""Find aliases by destination domain."""
|
||||||
try:
|
try:
|
||||||
async with get_mailu_client() as mailu_client:
|
async with get_mailu_client() as mailu_client:
|
||||||
response = await mailu_client.get(f"/alias/destination/{domain}")
|
response = await mailu_client.get(f"/alias/destination/{domain}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return json.dumps(response.json(), indent=2)
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting aliases for destination domain {domain}: {e}"
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
# ===== TOOLS (ACTIONS) =====
|
# ===== TOOLS (ACTIONS) =====
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user