# Django Rest Framework Mcp

> Quickly build MCP servers that expose your Django Rest Framework APIs as tools for LLMs and agentic applications to work with

- **Type:** MCP server
- **Install:** `agentstack add mcp-zacharypodbela-django-rest-framework-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [zacharypodbela](https://agentstack.voostack.com/s/zacharypodbela)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** BSD-3-Clause
- **Upstream author:** [zacharypodbela](https://github.com/zacharypodbela)
- **Source:** https://github.com/zacharypodbela/django-rest-framework-mcp

## Install

```sh
agentstack add mcp-zacharypodbela-django-rest-framework-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Django REST Framework MCP

[](https://badge.fury.io/py/django-rest-framework-mcp)
[](https://pypi.org/project/django-rest-framework-mcp/)

`django-rest-framework-mcp` allows you to spin up MCP servers that expose your Django Rest Framework APIs as MCP tools with just a few lines of code.

Supercharge your admin workflows (and make developing Admin interfaces a thing of the past):

- "Deactivate josh@gmail.com's account" → Actually deactivates it
- "Extend jack@teams.com's free trial by 1 week" → Updates their plan
- "How many new users joined week-over-week?" → Returns real data → LLMs can quickly create graphs _(no more complex FE graphing libraries needed)_

Or transform your traditional, boring SaaS UX with conversational interactions:

- Old way: User Clicks "Manage", Clicks "My Posts", Clicks a post from the list, Clicks "Edit", changes the title, Clicks "Save"
- With Django Rest Framework MCP: "Can you rename my post from 'Beginners Guide to Django' to 'Django 101'?"

## Quick Start

1. Install the package:

```bash
pip install django-rest-framework-mcp
```

2. Add to your `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    # ... your other apps
    'djangorestframework_mcp',
]
```

3. Add the MCP endpoint to your `urls.py`:

```python
urlpatterns = [
    # ... your other URL patterns
    path('mcp/', include('djangorestframework_mcp.urls')),
]
```

4. Transform any DRF ViewSet into MCP tools with a single decorator:

```python
from djangorestframework_mcp.decorators import mcp_viewset

@mcp_viewset()
class CustomerViewSet(ModelViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer
```

When `@mcp_viewset` is applied to a ViewSet class that inherits from `GenericViewSet` (such as `ModelViewSet` or `ReadOnlyModelViewSet`), any of the following methods that are defined will be automatically exposed as MCP tools:

- `list` -> List customers with `customers_list` tool.
- `retrieve` -> Retrieve a customer with `customers_retrieve` tool.
- `create` -> Create new customers with `customers_create` tool.
- `update` -> Update customers with `customers_update` tool. (All fields must be passed in)
- `partial_update` -> Update customers with `customers_partial_update` tool. (A subset of fields can be passed in)
- `destroy` -> Delete customers with `customers_destroy` tool.

For each tool the library automatically:

- Generates tool schemas from your DRF serializers
- Preserves your existing permissions, authentication, and filtering
- Returns context rich error messages to guide LLMs

(See: _Custom Actions_ below for more info on how to expose additional endpoints you created using the `@action` decorator as tools).

5. Connect any MCP client to `http://localhost:8000/mcp/` and try it out!

### Important Differences between MCP Requests and API Requests

MCP requests do not go through the full DRF request lifecycle.

**View lifecycle methods that will be called:**

- **`perform_authentication(request)`** - Called unless `BYPASS_VIEWSET_AUTHENTICATION = True`
- **`check_permissions(request)`** - Called unless `BYPASS_VIEWSET_PERMISSIONS = True`
- **`check_throttles(request)`** - Always called (no bypass option)
- **`determine_version(request, \*args, **kwargs)`\*\* - Always called to set request versioning

**View lifecycle methods that won't be called:**

- **`dispatch(request, args, kwargs)`** - will not be called since we don't use HTTP method+path routing.
- **`initialize_request(request, args, kwargs)`** will not be called. We do create a `rest_framework.requests.Request` from the `django.http.HttpRequest`, but not using this handler.
- **`initial(request, args, kwargs)`** - will not be called.
- **`perform_content_negotiation(request)`** - will not be called since MCP/JSON-RPC dictates the input and output format.
- **`finalize_response(request, response, args, kwargs)`** will not be called.
- **`handle_exception(exc)`** will not be called in the event of an exception\*.

**Additional considerations:**

- The `request` object will be missing API-specific properties like `method`, `path`, or `path_info` since it wasn't created from an actual HTTP API call
- Content negotiation is bypassed since MCP always uses JSON

## Connecting a STDIO MCP Client

Right now, the MCP server is only open to HTTP transport. To support stdio transport, you'll need a bridge. We recommend [mcp-remote](https://github.com/geelen/mcp-remote).

### Example: Connect to Claude Desktop

Follow these instructions to use `mcp-remote` to connect to Claude Desktop:

1. Install mcp-remote: `npm install -g mcp-remote`

2. Open Claude MCP Desktop Configuration by going to Settings > Developer > Edit Config and add your server configuration:

```json
{
  "mcpServers": {
    "my-django-mcp": {
      "command": "node",
      "args": [
        "path/to/mcp-remote",
        "http://localhost:8000/mcp/",
        "--transport",
        "http-only"
      ]
    }
  }
}
```

3. Restart Claude Desktop and test your tools

**Development Tip:** LLMs can be surprisingly effective at “manually” testing your MCP tools and uncovering bugs. In Claude Desktop, try a prompt like: _"I'm developing a new set of MCP tools locally. Please extensively test them — including coming up with complex edge cases to try - and look for unexpected behavior or bugs. Make at least 30 tool calls."_

## Advanced Configuration

### Authentication

On the subject of Authentication, the Model Context Protocol states:

1. Implementations using an HTTP-based transport SHOULD conform to the OAuth specification detailed [here](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-flow).
2. Implementations using an STDIO transport SHOULD NOT follow the above specification, and instead retrieve credentials from the environment.
3. Additionally, clients and servers MAY negotiate their own custom authentication and authorization strategies.

Our library enables you to leverage DRF's authentication framework on both the MCP endpoint level and individual ViewSet level, giving you flexibility in how you secure your MCP tools.

#### Using Existing API Authentication on ViewSets

If your ViewSet specifies `authentication_classes` and/or `permission_classes`, MCP client requests will be required to authenticate and pass permission checks using the same methods as your normal API requests:

```python
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

@mcp_viewset()
class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
```

MCP clients then authenticate via standard HTTP headers:

```bash
# HTTP headers
POST /mcp/ HTTP/1.1
Authorization: Token your-token-here

# HTTP body
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "list_customers",
    "arguments": {}
  },
  "id": 1
}
```

If you are using custom `BasePermission` classes in your ViewSets, we strongly encourage setting the `message` property to add more specifics on why permission was denied to the error message as this will be passed back to the LLM in the response and help it determine the right next course of action.

#### Defining Authentication and Permissions on the MCP Endpoint

You can add authentication to requests made to the `/mcp` endpoint by subclassing `MCPView` and setting the `authentication_classes` property, just as you would for an `APIView`. You can add permissions to requests made to the `/mcp` endpoint by overriding and implementing the `has_mcp_permission` method. (The default implementation of `has_mcp_permission()` returns `True`, allowing all requests.)

```python
from djangorestframework_mcp.views import MCPView
from rest_framework.authentication import TokenAuthentication

class AuthenticatedMCPView(MCPView):
    authentication_classes = [TokenAuthentication]

    def has_mcp_permission(self, request):
        """Override this method to implement custom permission logic."""
        return request.user.is_authenticated

# Then in urls.py
urlpatterns = [
    path('mcp/', AuthenticatedMCPView.as_view()),
]
```

The `has_mcp_permission(self, request)` method is called after authentication, so `user` and `auth` will be both set allowing you to implement any custom authorization logic:

```python
class RestrictedMCPView(MCPView):
    authentication_classes = [TokenAuthentication]

    def has_mcp_permission(self, request):
        # Only allow users in the 'mcp_users' group
        return (
            request.user.is_authenticated
            and request.user.groups.filter(name='mcp_users').exists()
        )
```

Just as with DRF, if you have authentication classes but no permission requirements, unauthenticated requests are allowed to continue and `request.user` will be an `AnonymousUser`.

#### Bypassing ViewSet Authentication

In cases where you want to apply different authentication methods and/or permissions rules for MCP clients versus regular API clients, you can bypass ViewSet-level authentication and/or permissions:

```python
# settings.py
DJANGORESTFRAMEWORK_MCP = {
    'BYPASS_VIEWSET_AUTHENTICATION': True,  # Skip authentication on ViewSets
    'BYPASS_VIEWSET_PERMISSIONS': True,     # Skip permissions on ViewSets
}
```

#### Authentication Error Handling

When authentication fails, the default behavior is for the library to return proper HTTP status codes (401/403) and WWW-Authenticate headers in compliance with both HTTP and MCP specifications. The JSON-RPC response body also includes this information as human-readable error messages so it can be leveraged by LLMs.

Example response:

```json
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Token
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Unauthorized: Authentication credentials were not provided. (WWW-Authenticate: Token)"
      }
    ],
    "isError": true
  },
  "id": 1
}
```

While the MCP protocol specification clearly states that HTTP 401/403 status codes should be returned for authentication and permission errors, many MCP clients don't properly conform to this specification and are unable to handle these cases. (For example, MCP's own Typescript SDK doesn't currently handle 403 errors. See this [Github Issue](https://github.com/modelcontextprotocol/typescript-sdk/issues/541).) To better support compatibility with a wide variety of clients while the community and standards continue to evolve, you can enable the `RETURN_200_FOR_ERRORS` to return HTTP 200 status codes even for authentication/permission failures. This setting only affects the HTTP status code returned - the JSON-RPC error format remains the same, ensuring LLMs can still understand and react to failures.

```python
# settings.py
DJANGORESTFRAMEWORK_MCP = {
    'RETURN_200_FOR_ERRORS': True,  # Default: False
}
```

#### Authenticating STDIO Transport Using MCP-Remote

When using STDIO transport through MCP-Remote, authentication credentials to be passed as HTTP headers can be set as environment variables like this:

```json
{
  "mcpServers": {
    "my-django-mcp": {
      "command": "node",
      "args": [
        "path/to/mcp-remote",
        "http://localhost:8000/mcp/",
        "--transport",
        "http-only",
        "--header",
        "Authorization:${AUTH_HEADER}" // Some setups don't escape whitespaces of args, so we recommend setting the entire header as an env var
      ],
      "env": {
        "AUTH_HEADER": "your-header-here"
      }
    }
  }
}
```

As of writing this, MCP-remote does not properly handle 403 response and always assumes the authentication framework is OAuth when receiving 401 responses, so you'll also need to enable `RETURN_200_FOR_ERRORS` in your settings file.

### Custom Actions

Custom actions, created with the `@action` decorator, require explicit schema definition since there aren't standard input defaults like with CRUD endpoints. To create a tool from a custom action, apply the `@mcp_tool` decorator and pass in an `input_serializer`:

```python
from djangorestframework_mcp.decorators import mcp_viewset, mcp_tool

class GenerateInputSerializer(serializers.Serializer):
    user_prompt = serializers.CharField(help_text="The prompt to send to the LLM")

@mcp_viewset()
class ContentViewSet(viewsets.ViewSet):
    @mcp_tool(input_serializer=GenerateInputSerializer)
    @action(detail=False, methods=['post'])
    def generate(self, request):
        user_prompt = request.data['user_prompt']
        llm_response = call_llm(user_prompt)
        return Response({'llm_response': llm_response})
```

For custom actions that don't require input, set `input_serializer=None`:

```python
@mcp_tool(input_serializer=None)  # No input needed
@action(detail=False, methods=['get'])
def recent_posts(self, request):
    recent_posts = Post.objects.filter(created_at__gte=timezone.now() - timedelta(days=7))
    serializer = PostSerializer(recent_posts, many=True)
    return Response(serializer.data)
```

For CRUD actions (`list`, `retrieve`, `create`, `update`, `partial_update`, `destroy`), `input_serializer` is **optional**. The library will default to inferring schemas from the ViewSet's `serializer_class` if `input_serializer` is not specified. You'll want to use this optional parameter if you've written custom business logic that changes the input schema of a CRUD endpoint.

```python
class ExtendedPostSerializer(PostSerializer): # Inherits and extends standard CRUD serializer
    add_created_at_footer = serializers.BooleanField(help_text="Setting to true appends the author name")

@mcp_tool(input_serializer=ExtendedPostSerializer)
def create(self, request, *args, **kwargs):
    if request.data.get('add_created_at_footer'):
        # Append text to the end of the content noting it was created via MCP
        request.data['content'] += f"\n\n*Created by {request.user.name}*"

    return super().create(request, *args, **kwargs)
```

### Selective Action Registration

If you don't want to create a tool from every action of a ViewSet, you can whitelist which actions to expose by passing an `actions` array to `@mcp_viewset`:

```python
@mcp_viewset(actions=['banish', 'list'])
class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

    def banish(self, request, pk=None):
        # ... Business Logic
```

### Custom Tool Names and Descriptions

You can customize the names, titles, and descriptions of individual actions using the `@mcp_tool` decorator. (NOTE: The `@mcp_tool` decorator only works when the ViewSet class is also decorated with `@mcp_viewset`. Using `@mcp_tool` alone will not register any MCP tools.)

```python
@mcp_viewset()
class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

    @mcp_tool(
        name="get_customer_details",
        title="Get Customer Details",
        description="Retrieve detailed information about a specific customer by their ID"
    )
    def retrieve(self, request, pk=None):
        return super().retrieve(request, pk)
```

### MCP-Specific Overrides

Sometimes you want different behavior for MCP requests vs regular API requests. You have two options for achieving this.

#### Option 1: Inheritance (Recommended)

Create a dedicated ViewSet for MCP that inherits from your existing ViewSet.

```python
@mcp_viewset()
class CustomerMCPViewSet(CustomerViewSet):
    # Limit MCP clients to active customers only
    queryset = super().get_queryset().filter(is_active=True)
    # Use a simplified serializer for MCP clients
    serializer_class = CustomerMCPSerializer
    # ... everything else is inherited

class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.object

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [zacharypodbela](https://github.com/zacharypodbela)
- **Source:** [zacharypodbela/django-rest-framework-mcp](https://github.com/zacharypodbela/django-rest-framework-mcp)
- **License:** BSD-3-Clause

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-zacharypodbela-django-rest-framework-mcp
- Seller: https://agentstack.voostack.com/s/zacharypodbela
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
