get_character_subjects
Retrieve a list of subjects (e.g., anime, games) where a specific character appears by providing the character ID. Supports querying through Bangumi TV MCP Service for accurate results.
Instructions
List subjects (e.g., anime, games) where a character appears.
Args:
character_id: The ID of the character.
Returns:
Formatted list of related subjects or an error message.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| character_id | Yes |
Input Schema (JSON Schema)
{
"properties": {
"character_id": {
"title": "Character Id",
"type": "integer"
}
},
"required": [
"character_id"
],
"title": "get_character_subjectsArguments",
"type": "object"
}
Implementation Reference
- main.py:1005-1056 (handler)The main handler function for the 'get_character_subjects' tool. It is decorated with @mcp.tool() which also serves as registration. Fetches related subjects for a given character ID from the Bangumi API endpoint /v0/characters/{character_id}/subjects, handles API errors, validates response format, formats each subject with name, type, and role info, and returns a formatted string summary.@mcp.tool() async def get_character_subjects(character_id: int) -> str: """ List subjects (e.g., anime, games) where a character appears. Args: character_id: The ID of the character. Returns: Formatted list of related subjects or an error message. """ response = await make_bangumi_request( method="GET", path=f"/v0/characters/{character_id}/subjects" ) error_msg = handle_api_error_response(response) if error_msg: return error_msg # Expecting a list of subjects if not isinstance(response, list): return f"Unexpected API response format for get_character_subjects: {response}" related_subjects = response if not related_subjects: return f"No subjects found related to character ID {character_id}." formatted_results = [] for rel_subject in related_subjects: name = rel_subject.get("name") name_cn = rel_subject.get("name_cn") rel_id = rel_subject.get("id") rel_type_int = rel_subject.get("type") try: rel_type_str = ( SubjectType(rel_type_int).name if rel_type_int is not None else "Unknown Type" ) except ValueError: rel_type_str = f"Unknown Type ({rel_type_int})" staff_info = rel_subject.get( "staff" ) # Staff refers to the role of the char in the subject e.g. "主角" formatted_results.append( f"Subject ID: {rel_id}, Name: {name_cn or name}, Type: {rel_type_str}, Role/Staff (in subject): {staff_info}" ) return "Subjects This Character Appears In:\n" + "\n---\n".join(formatted_results)
- main.py:208-241 (helper)Helper function used by get_character_subjects (and others) to format individual subject summaries with type, name, score, rank, summary, and image.def format_subject_summary(subject: Dict[str, Any]) -> str: """Formats a subject dictionary into a readable summary string.""" name = subject.get("name") name_cn = subject.get("name_cn") subject_type = subject.get("type") subject_id = subject.get("id") score = subject.get("rating", {}).get("score") # Access Nested Score rank = subject.get("rating", {}).get("rank") # Access Nested Rank summary = subject.get("short_summary") or subject.get("summary", "") try: type_str = ( SubjectType(subject_type).name if subject_type is not None else "Unknown Type" ) except ValueError: type_str = f"Unknown Type ({subject_type})" formatted_string = f"[{type_str}] {name_cn or name} (ID: {subject_id})\n" if score is not None: formatted_string += f" Score: {score}\n" if rank is not None: formatted_string += f" Rank: {rank}\n" if summary: formatted_summary = summary # [:200] + '...' if len(summary) > 200 else summary formatted_string += f" Summary: {formatted_summary}\n" # Add images URL if available (for potential LLM multi-modal future use or user info) images = subject.get("images") if images and images.get("common"): formatted_string += f" Image: {images.get('common')}\n" # Or 'grid', 'large', 'medium', 'small' depending on preference return formatted_string
- main.py:105-169 (helper)Core helper function used by all Bangumi API tools, including get_character_subjects, to make HTTP requests with authentication, error handling, and debug logging.async def make_bangumi_request( method: str, path: str, query_params: Optional[Dict[str, Any]] = None, json_body: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> Any: """Make a request to the Bangumi API with proper headers and error handling.""" request_headers = headers.copy() if headers else {} request_headers["User-Agent"] = USER_AGENT request_headers["Accept"] = "application/json" if BANGUMI_TOKEN: request_headers["Authorization"] = f"Bearer {BANGUMI_TOKEN}" url = f"{BANGUMI_API_BASE}{path}" async with httpx.AsyncClient() as client: try: print( f"DEBUG: Making {method} request to {url} with params={query_params}, json={json_body}" ) response = await client.request( method=method, url=url, params=query_params, json=json_body, headers=request_headers, timeout=30.0, ) response.raise_for_status() # Return the raw JSON response, let the calling tool handle its structure (dict or list) json_response = response.json() print( f"DEBUG: Received response (type: {type(json_response)}, keys/length: {list(json_response.keys()) if isinstance(json_response, dict) else len(json_response) if isinstance(json_response, list) else 'N/A'})" ) return json_response except httpx.HTTPStatusError as e: error_msg = ( f"HTTP error occurred: {e.response.status_code} - {e.response.text}" ) print(f"ERROR: {error_msg}") # Try to parse the error response body if it's JSON try: error_details = e.response.json() return { "error": error_msg, "status_code": e.response.status_code, "details": error_details, } except json.JSONDecodeError: return { "error": error_msg, "status_code": e.response.status_code, "details": e.response.text, } except httpx.RequestError as e: error_msg = f"An error occurred while requesting {e.request.url!r}: {e}" print(f"ERROR: {error_msg}") return {"error": error_msg} except Exception as e: error_msg = f"An unexpected error occurred: {e}" print(f"ERROR: {error_msg}") return {"error": error_msg}
- main.py:172-205 (helper)Helper function used by get_character_subjects (and all tools) to detect and format API error responses from make_bangumi_request or direct API errors.def handle_api_error_response(response: Any) -> Optional[str]: """ Checks if the API response indicates an error and returns a formatted error message. Handles both dictionary-based errors and returns from make_bangumi_request on failure. """ # Check for error structure returned by make_bangumi_request on HTTPStatusError or RequestError if isinstance(response, dict) and ( "error" in response or "status_code" in response ): # This is an error dictionary created by our helper status_code = response.get("status_code", "N/A") error_msg = response.get("error", "Unknown error during request.") details = response.get("details", "") return f"Bangumi API Request Error (Status {status_code}): {error_msg}. Details: {details}".strip() # Check for error structure returned by Bangumi API itself (often dictionaries) # Safely check if the response is a dictionary before accessing its keys if isinstance(response, dict): if "title" in response and "description" in response: # This looks like a common Bangumi error response structure error_title = response.get("title", "API Error") error_description = response.get("description", "No description provided.") # The API might return a status code in the body too, or rely on HTTP status return f"Bangumi API Error: {error_title}. {error_description}".strip() # Check if it's a dictionary but *not* empty and *doesn't* look like a success response from list endpoints # Check for specific error fields if structure varies # Add more checks here if other error dictionary formats are observed # Example: if "message" in response and "code" in response: return f"API Error {response['code']}: {response['message']}" pass # If it's a dictionary but doesn't match known error formats, assume it's a valid data response for now # If it's not a dictionary, or it's a dictionary that doesn't match known error formats, assume it's not an error return None
- main.py:1005-1005 (registration)The @mcp.tool() decorator registers the get_character_subjects function as an MCP tool.@mcp.tool()