feat(middleware): Add AI agent friendly responses for unauthenticated requests#106485
feat(middleware): Add AI agent friendly responses for unauthenticated requests#106485
Conversation
5b57001 to
206226b
Compare
src/sentry/middleware/ai_agent.py
Outdated
| {mcp_config_json} | ||
| ``` | ||
|
|
||
| Get a token: https://sentry.io/settings/account/api/auth-tokens/ |
There was a problem hiding this comment.
should it say like "Your user can get a token from" so the agent doesn't try and hit this URL to get a token like a dum dum
There was a problem hiding this comment.
tbqh this is wrong, stupid bot
206226b to
41eed3a
Compare
src/sentry/middleware/ai_agent.py
Outdated
| return "text/markdown" in accept or "text/x-markdown" in accept | ||
|
|
||
|
|
||
| def _is_anonymous_request(request: HttpRequest) -> bool: |
There was a problem hiding this comment.
@cursoragent can u check if there's a better way to do this in the codebase, i thought there was
There was a problem hiding this comment.
does this work can I just tag it
There was a problem hiding this comment.
Based on my search of the codebase, there's a cleaner pattern used throughout Sentry. The current implementation can be simplified to match the established pattern.
Here's what I found:
Current implementation:
def _is_anonymous_request(request: HttpRequest) -> bool:
"""Check if the request is anonymous (no authenticated user, no auth token)."""
if getattr(request, "auth", None) is not None:
return False
user = getattr(request, "user", None)
return user is None or user.is_anonymousBetter pattern (used throughout the codebase):
The codebase consistently uses request.user.is_authenticated rather than checking for None or is_anonymous. I found this exact pattern in src/sentry/auth/access.py:942:
if request.auth is not None and not request.user.is_authenticated:
return from_rpc_auth(request.auth, rpc_user_org_context)Recommended change:
def _is_anonymous_request(request: HttpRequest) -> bool:
"""Check if the request is anonymous (no authenticated user, no auth token)."""
return request.auth is None and not request.user.is_authenticatedThis is:
- ✅ More concise
- ✅ Matches the established pattern used in 166+ places in the codebase
- ✅ Doesn't need
getattr()since Django middleware guaranteesrequest.userandrequest.authare set by theAuthenticationMiddleware(which runs before this middleware)
There was a problem hiding this comment.
yeah tho it will likely fuck it up all because it lacks useful context ("do what")
4050b41 to
0e39b12
Compare
… requests Add middleware that detects AI agents accessing frontend UI routes (via Accept: text/markdown header) and returns helpful guidance instead of HTML. The response includes: - MCP server configuration with context-aware URLs - REST API documentation links - Token generation instructions The middleware extracts org/project context from the URL path to provide pre-configured MCP server URLs. Fixes GH-106334 Co-Authored-By: Claude <noreply@anthropic.com>
0e39b12 to
659cc37
Compare
Revised messaging to be more concise and align with Sentry's tone guidelines—front-loading information while keeping personality subtle.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
BYK
left a comment
There was a problem hiding this comment.
Only major concern is the auth endpoints bypass.
src/sentry/middleware/ai_agent.py
Outdated
| # Patterns to extract org and project from URL paths | ||
| ORG_PATTERN = re.compile(r"^/organizations/([^/]+)/") | ||
| PROJECT_PATTERN = re.compile(r"^/organizations/([^/]+)/projects/([^/]+)/") |
There was a problem hiding this comment.
Would be ideal if these patterns were shared with the main route patterns
| request = self.factory.get("/", HTTP_ACCEPT="text/plain") | ||
| assert _accepts_markdown(request) is False | ||
|
|
||
| def test_case_insensitive(self): |
There was a problem hiding this comment.
Would also add text/html test, just in case
|
|
||
| def __call__(self, request: HttpRequest) -> HttpResponse: | ||
| # Skip API routes - only intercept frontend UI routes | ||
| if request.path.startswith("/api/"): |
There was a problem hiding this comment.
Shall we also explicitly exclude our auth endpoints? Or maybe we can add a property on the views themselves in the future: has_markdown and only allow those?
There was a problem hiding this comment.
you mean the oauth stuff? i think its fine as no one should be sending a user agent that would hit this that would also hit those
the only reason we do /api/ is because it actually is machines sending those requests
There was a problem hiding this comment.
Some of the new ones from #105675 might be worth it:
- /oauth/device_authorization
- /oauth/token
- /oauth/device
The middleware guidance text was updated in commit e5fc6cc to use the new brand voice ("# This Is the Web UI") but the test assertion wasn't updated to match.
Exclude /oauth/ paths from the AI agent markdown response middleware since these are legitimate machine-to-machine endpoints that should work normally (device authorization, token exchange, etc.). Fixes feedback from #106485
- Exclude `/oauth/` paths from the AI agent markdown response middleware - These are legitimate machine-to-machine endpoints (device authorization, token exchange) that should work normally Addresses feedback from #106485 (comment)
Add middleware that returns helpful markdown guidance when AI agents (Claude Code, Cursor, Copilot, etc.) make unauthenticated requests to frontend UI routes.
When an anonymous request with
Accept: text/markdownortext/x-markdownhits a non-API route, the middleware returns a friendly response explaining how to properly access Sentry:Example response for
/organizations/my-org/projects/my-project/:Fixes #106334