-
Notifications
You must be signed in to change notification settings - Fork 5
Switches to FastMCP and adds resource support #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
saramaebee
wants to merge
20
commits into
devrev:main
Choose a base branch
from
saramaebee:3-separate-devrev-resources-into-distinct-resource-types
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
8db3bc1
adds debug handling
saramaebee e89f337
separates tools out into their own file
saramaebee d99f8c5
adds resources
saramaebee d840f8b
switching over to fastmcp
saramaebee 651813a
Merge branch 'main' into sara-mcp-resources
saramaebee 45462c6
Merge pull request #4 from saramaebee/sara-mcp-resources
saramaebee 92f0025
has it working
saramaebee 724ff22
removing unnecessary debug info
saramaebee 6c3e413
removes unnecessary watchdog
saramaebee 12a8df0
cleaning things up
saramaebee e7c98f1
adds download_artifact tool
saramaebee e878e2c
adds visibility understanding
saramaebee 12904a4
cleanup for PR
saramaebee ec6ea52
error handling + pr cleanup
saramaebee ef140c1
more PR cleanup
saramaebee 0584f87
slight cache improvement / docs improvement
saramaebee b2535da
gets issues and comments working
saramaebee e652b9e
cleaning up
saramaebee c8c0af4
more cleanup :)
saramaebee 9319296
Merge pull request #6 from saramaebee/sara-adds-issues
saramaebee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
error handling + pr cleanup
- Loading branch information
commit ec6ea526c6202adeadf2506fd3d6e901c0960cc6
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| This project uses `uv` for package management | ||
| - Reference https://gofastmcp.com/llms.txt | ||
| - Reference https://developer.devrev.ai/llms.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| """ | ||
| DevRev API Endpoints Constants | ||
|
|
||
| This module defines all DevRev API endpoint strings used throughout the application. | ||
| Centralizing these constants prevents typos and makes API changes easier to manage. | ||
| """ | ||
|
|
||
|
|
||
| class DevRevEndpoints: | ||
| """DevRev API endpoint constants for consistent usage across the application.""" | ||
|
|
||
| # Works (Tickets, Issues, etc.) | ||
| WORKS_GET = "works.get" | ||
| WORKS_CREATE = "works.create" | ||
| WORKS_UPDATE = "works.update" | ||
|
|
||
| # Timeline Entries | ||
| TIMELINE_ENTRIES_LIST = "timeline-entries.list" | ||
| TIMELINE_ENTRIES_GET = "timeline-entries.get" | ||
|
|
||
| # Artifacts | ||
| ARTIFACTS_GET = "artifacts.get" | ||
| ARTIFACTS_LOCATE = "artifacts.locate" | ||
|
|
||
| # Search | ||
| SEARCH_HYBRID = "search.hybrid" | ||
|
|
||
|
|
||
| # Convenience exports for simpler imports | ||
| WORKS_GET = DevRevEndpoints.WORKS_GET | ||
| WORKS_CREATE = DevRevEndpoints.WORKS_CREATE | ||
| WORKS_UPDATE = DevRevEndpoints.WORKS_UPDATE | ||
| TIMELINE_ENTRIES_LIST = DevRevEndpoints.TIMELINE_ENTRIES_LIST | ||
| TIMELINE_ENTRIES_GET = DevRevEndpoints.TIMELINE_ENTRIES_GET | ||
| ARTIFACTS_GET = DevRevEndpoints.ARTIFACTS_GET | ||
| ARTIFACTS_LOCATE = DevRevEndpoints.ARTIFACTS_LOCATE | ||
| SEARCH_HYBRID = DevRevEndpoints.SEARCH_HYBRID |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| """ | ||
| DevRev MCP Error Handler | ||
|
|
||
| Provides standardized error handling for resources and tools. | ||
| """ | ||
|
|
||
| import json | ||
| from typing import Dict, Optional | ||
| from functools import wraps | ||
| from fastmcp import Context | ||
|
|
||
|
|
||
| class DevRevMCPError(Exception): | ||
| """Base exception for DevRev MCP errors.""" | ||
| def __init__(self, message: str, error_code: str = "UNKNOWN", details: Optional[Dict] = None): | ||
| self.message = message | ||
| self.error_code = error_code | ||
| self.details = details or {} | ||
| super().__init__(message) | ||
|
|
||
|
|
||
| class ResourceNotFoundError(DevRevMCPError): | ||
| """Raised when a requested resource is not found.""" | ||
| def __init__(self, resource_type: str, resource_id: str, details: Optional[Dict] = None): | ||
| message = f"{resource_type} {resource_id} not found" | ||
| super().__init__(message, "RESOURCE_NOT_FOUND", details) | ||
| self.resource_type = resource_type | ||
| self.resource_id = resource_id | ||
|
|
||
|
|
||
| class APIError(DevRevMCPError): | ||
| """Raised when DevRev API returns an error.""" | ||
| def __init__(self, endpoint: str, status_code: int, response_text: str): | ||
| message = f"DevRev API error on {endpoint}: HTTP {status_code}" | ||
| details = {"status_code": status_code, "response": response_text} | ||
| super().__init__(message, "API_ERROR", details) | ||
| self.endpoint = endpoint | ||
| self.status_code = status_code | ||
|
|
||
|
|
||
| def create_error_response( | ||
| error: Exception, | ||
| resource_type: str = "resource", | ||
| resource_id: str = "", | ||
| additional_data: Optional[Dict] = None | ||
| ) -> str: | ||
| """ | ||
| Create a standardized JSON error response. | ||
|
|
||
| Args: | ||
| error: The exception that occurred | ||
| resource_type: Type of resource (ticket, artifact, etc.) | ||
| resource_id: ID of the resource that failed | ||
| additional_data: Additional data to include in error response | ||
|
|
||
| Returns: | ||
| JSON string containing error information | ||
| """ | ||
| error_data = { | ||
| "error": True, | ||
| "error_type": type(error).__name__, | ||
| "message": str(error), | ||
| "resource_type": resource_type, | ||
| "resource_id": resource_id, | ||
| "timestamp": None # Could add timestamp if needed | ||
| } | ||
|
|
||
| # Add specific error details for known error types | ||
| if isinstance(error, DevRevMCPError): | ||
| error_data["error_code"] = error.error_code | ||
| error_data["details"] = error.details | ||
|
|
||
| if isinstance(error, APIError): | ||
| error_data["api_endpoint"] = error.endpoint | ||
| error_data["http_status"] = error.status_code | ||
|
|
||
| # Include any additional data | ||
| if additional_data: | ||
| error_data.update(additional_data) | ||
|
|
||
| return json.dumps(error_data, indent=2) | ||
|
|
||
|
|
||
| def resource_error_handler(resource_type: str): | ||
| """ | ||
| Decorator for resource handlers that provides standardized error handling. | ||
|
|
||
| Args: | ||
| resource_type: The type of resource (e.g., "ticket", "artifact") | ||
|
|
||
| Returns: | ||
| Decorated function with error handling | ||
| """ | ||
| def decorator(func): | ||
| @wraps(func) | ||
| async def wrapper(*args, **kwargs): | ||
| # Extract resource_id from function arguments | ||
| resource_id = args[0] if args else "unknown" | ||
| ctx = None | ||
|
|
||
| # Find Context in arguments | ||
| for arg in args: | ||
| if isinstance(arg, Context): | ||
| ctx = arg | ||
| break | ||
|
|
||
| try: | ||
| return await func(*args, **kwargs) | ||
|
|
||
| except DevRevMCPError as e: | ||
| if ctx: | ||
| await ctx.error(f"{resource_type} error: {e.message}") | ||
| return create_error_response(e, resource_type, resource_id) | ||
|
|
||
| except Exception as e: | ||
| if ctx: | ||
| await ctx.error(f"Unexpected error in {resource_type} {resource_id}: {str(e)}") | ||
|
|
||
| # Convert to standardized error | ||
| mcp_error = DevRevMCPError( | ||
| f"Unexpected error: {str(e)}", | ||
| "INTERNAL_ERROR" | ||
| ) | ||
| return create_error_response(mcp_error, resource_type, resource_id) | ||
|
|
||
| return wrapper | ||
| return decorator | ||
|
|
||
|
|
||
| def tool_error_handler(tool_name: str): | ||
| """ | ||
| Decorator for tool handlers that provides standardized error handling. | ||
|
|
||
| Args: | ||
| tool_name: The name of the tool | ||
|
|
||
| Returns: | ||
| Decorated function with error handling | ||
| """ | ||
| def decorator(func): | ||
| @wraps(func) | ||
| async def wrapper(*args, **kwargs): | ||
| ctx = None | ||
|
|
||
| # Find Context in arguments or kwargs | ||
| for arg in args: | ||
| if isinstance(arg, Context): | ||
| ctx = arg | ||
| break | ||
|
|
||
| if not ctx and 'ctx' in kwargs: | ||
| ctx = kwargs['ctx'] | ||
|
|
||
| try: | ||
| return await func(*args, **kwargs) | ||
|
|
||
| except DevRevMCPError as e: | ||
| if ctx: | ||
| await ctx.error(f"{tool_name} error: {e.message}") | ||
| raise # Re-raise for tools since they can handle exceptions | ||
|
|
||
| except Exception as e: | ||
| if ctx: | ||
| await ctx.error(f"Unexpected error in {tool_name}: {str(e)}") | ||
|
|
||
| # Convert to standardized error and re-raise | ||
| raise DevRevMCPError( | ||
| f"Tool {tool_name} failed: {str(e)}", | ||
| "TOOL_ERROR" | ||
| ) from e | ||
|
|
||
| return wrapper | ||
| return decorator | ||
|
|
||
|
|
||
| def handle_api_response(response, endpoint: str, expected_status: int = 200): | ||
| """ | ||
| Handle DevRev API response and raise appropriate errors. | ||
|
|
||
| Args: | ||
| response: The requests Response object | ||
| endpoint: API endpoint that was called | ||
| expected_status: Expected HTTP status code (default 200) | ||
|
|
||
| Raises: | ||
| APIError: If the response status is not as expected | ||
| """ | ||
| if response.status_code != expected_status: | ||
| raise APIError(endpoint, response.status_code, response.text) | ||
|
|
||
| return response | ||
|
|
||
|
|
||
| # Utility function to check and validate resource IDs | ||
| def validate_resource_id(resource_id: str, resource_type: str) -> str: | ||
| """ | ||
| Validate and normalize resource IDs. | ||
|
|
||
| Args: | ||
| resource_id: The resource ID to validate | ||
| resource_type: Type of resource for error messages | ||
|
|
||
| Returns: | ||
| Normalized resource ID | ||
|
|
||
| Raises: | ||
| ResourceNotFoundError: If resource ID is invalid | ||
| """ | ||
| if not resource_id or not isinstance(resource_id, str): | ||
| raise ResourceNotFoundError( | ||
| resource_type, | ||
| str(resource_id), | ||
| {"reason": "Invalid or empty resource ID"} | ||
| ) | ||
|
|
||
| resource_id = resource_id.strip() | ||
| if not resource_id: | ||
| raise ResourceNotFoundError( | ||
| resource_type, | ||
| resource_id, | ||
| {"reason": "Empty resource ID after normalization"} | ||
| ) | ||
|
|
||
| return resource_id |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why named it claude.md ?