Skip to content

feat(middleware): Add AI agent friendly responses for unauthenticated requests#106485

Merged
dcramer merged 5 commits intomasterfrom
feat/ai-agent-middleware
Jan 21, 2026
Merged

feat(middleware): Add AI agent friendly responses for unauthenticated requests#106485
dcramer merged 5 commits intomasterfrom
feat/ai-agent-middleware

Conversation

@dcramer
Copy link
Member

@dcramer dcramer commented Jan 16, 2026

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/markdown or text/x-markdown hits a non-API route, the middleware returns a friendly response explaining how to properly access Sentry:

  • MCP Server - Pre-configured URL with org/project context extracted from the request path
  • REST API - Authentication instructions and documentation links

Example response for /organizations/my-org/projects/my-project/:

# Hey, you've hit the Sentry web UI

This URL serves HTML for humans in browsers. You probably want one of these instead:

## MCP Server (recommended for AI agents)

Add to your MCP client config:

{
  "mcpServers": {
    "sentry": {
      "url": "https://mcp.sentry.dev/mcp/my-org/my-project",
    }
  }
}

More info: https://mcp.sentry.dev

## REST API (for scripts and integrations)

curl https://sentry.io/api/0/projects/ \
  -H "Authorization: Bearer <token>"

Docs: https://docs.sentry.io/api/

Fixes #106334

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Jan 16, 2026
@dcramer dcramer force-pushed the feat/ai-agent-middleware branch 3 times, most recently from 5b57001 to 206226b Compare January 16, 2026 23:53
{mcp_config_json}
```

Get a token: https://sentry.io/settings/account/api/auth-tokens/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbqh this is wrong, stupid bot

return "text/markdown" in accept or "text/x-markdown" in accept


def _is_anonymous_request(request: HttpRequest) -> bool:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursoragent can u check if there's a better way to do this in the codebase, i thought there was

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this work can I just tag it

Copy link
Contributor

@cursor cursor bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_anonymous

Better 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_authenticated

This is:

  • ✅ More concise
  • ✅ Matches the established pattern used in 166+ places in the codebase
  • ✅ Doesn't need getattr() since Django middleware guarantees request.user and request.auth are set by the AuthenticationMiddleware (which runs before this middleware)

Open in Cursor Open in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah tho it will likely fuck it up all because it lacks useful context ("do what")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn

… 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>
@dcramer dcramer force-pushed the feat/ai-agent-middleware branch from 0e39b12 to 659cc37 Compare January 17, 2026 02:08
Revised messaging to be more concise and align with Sentry's tone
guidelines—front-loading information while keeping personality subtle.
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

@BYK BYK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only major concern is the auth endpoints bypass.

Comment on lines +12 to +14
# Patterns to extract org and project from URL paths
ORG_PATTERN = re.compile(r"^/organizations/([^/]+)/")
PROJECT_PATTERN = re.compile(r"^/organizations/([^/]+)/projects/([^/]+)/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be ideal if these patterns were shared with the main route patterns

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too overkill

request = self.factory.get("/", HTTP_ACCEPT="text/plain")
assert _accepts_markdown(request) is False

def test_case_insensitive(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@dcramer dcramer merged commit c662d6a into master Jan 21, 2026
66 checks passed
@dcramer dcramer deleted the feat/ai-agent-middleware branch January 21, 2026 19:35
dcramer added a commit that referenced this pull request Jan 21, 2026
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
dcramer added a commit that referenced this pull request Jan 21, 2026
- 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)
@github-actions github-actions bot locked and limited conversation to collaborators Feb 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: LLM-Friendly Responses for AI Agent Requests

3 participants