Metadata-Version: 2.4
Name: atlassian-mcp-auth
Version: 0.1.0
Summary: Python-native OAuth token management for Atlassian Rovo MCP server.
Project-URL: Homepage, https://github.com/deepak-batham/atlassian-mcp-auth
Project-URL: Repository, https://github.com/deepak-batham/atlassian-mcp-auth
Project-URL: Issues, https://github.com/deepak-batham/atlassian-mcp-auth/issues
Author: Deepak Batham
License: Apache-2.0
License-File: LICENSE
Keywords: agents,atlassian,confluence,jira,mcp,oauth,rovo
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: crypto
Requires-Dist: cryptography>=42.0.0; extra == 'crypto'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Provides-Extra: postgres
Requires-Dist: asyncpg>=0.29.0; extra == 'postgres'
Provides-Extra: service
Requires-Dist: fastapi>=0.115.0; extra == 'service'
Requires-Dist: uvicorn>=0.30.0; extra == 'service'
Provides-Extra: service-postgres
Requires-Dist: asyncpg>=0.29.0; extra == 'service-postgres'
Requires-Dist: fastapi>=0.115.0; extra == 'service-postgres'
Requires-Dist: uvicorn>=0.30.0; extra == 'service-postgres'
Provides-Extra: service-test
Requires-Dist: fastapi>=0.115.0; extra == 'service-test'
Requires-Dist: uvicorn>=0.30.0; extra == 'service-test'
Description-Content-Type: text/markdown

# atlassian-mcp-auth

Python-native OAuth token management for the Atlassian Rovo MCP server.

Use this package when your Python app, agent, or backend needs to connect to Atlassian Rovo MCP for Jira and Confluence without using Node.js or `mcp-remote`.

It handles the hard OAuth parts for you:

- Opens Atlassian browser consent
- Registers an OAuth client dynamically
- Uses PKCE for login
- Stores access and refresh tokens
- Refreshes tokens automatically
- Returns the MCP URL and `Authorization` header your framework needs

This package is an auth and token provider. Your MCP client, ADK app, LangChain/LangGraph app, CrewAI tool, or HTTP transport still owns the actual MCP tool calls.

## Quick Start

Install the package:

```bash
pip install atlassian-mcp-auth
```

Run one-time browser login:

```bash
atlassian-mcp-auth login
```

The CLI opens Atlassian consent in your browser, waits for the callback at `http://localhost:8765/callback`, then stores tokens locally in:

```text
~/.atlassian-mcp-auth/tokens.db
```

Check that you are connected:

```bash
atlassian-mcp-auth status
```

Print a fresh access token:

```bash
atlassian-mcp-auth token
```

Use it from Python:

```python
import asyncio

from atlassian_mcp_auth import AtlassianMcpAuth


async def main() -> None:
    auth = AtlassianMcpAuth()
    connection = await auth.get_connection_info()

    print(connection.mcp_url)
    print(connection.headers)


asyncio.run(main())
```

`connection.headers` looks like this:

```python
{"Authorization": "Bearer <fresh-access-token>"}
```

Pass that header to your MCP Streamable HTTP client or agent framework.

## How Normal Users Use It

There are four common ways to use this package.

| Use case | Start here |
| --- | --- |
| Local development or testing | Use the CLI |
| Python app or agent code | Use `AtlassianMcpAuth` directly |
| Frontend, backend, or non-Python app | Run the optional HTTP service |
| ADK, LangChain, CrewAI, custom MCP client | Use `get_connection_info()` as a token/header provider |

## 1. Use From The CLI

The CLI is the fastest way to login, verify OAuth, and get a token.

```bash
# Browser login. Stores tokens in ~/.atlassian-mcp-auth/tokens.db
atlassian-mcp-auth login

# Show saved token status
atlassian-mcp-auth status

# Print a fresh MCP access token
atlassian-mcp-auth token

# Force refresh and save any rotated refresh token
atlassian-mcp-auth refresh

# Delete saved tokens
atlassian-mcp-auth clear
```

Use a different profile for another user, team, or project:

```bash
atlassian-mcp-auth --profile team-a login
atlassian-mcp-auth --profile team-a token
```

Use a custom SQLite database path:

```bash
atlassian-mcp-auth --db ./tokens.db login
```

Use Postgres instead of SQLite:

```bash
pip install "atlassian-mcp-auth[postgres]"

atlassian-mcp-auth \
    --database-url "postgresql://user:password@localhost:5432/atlassian_mcp" \
    login
```

Use a different local callback port:

```bash
atlassian-mcp-auth login --port 8766
```

More details: [docs/CLI_GUIDE.md](docs/CLI_GUIDE.md)

## 2. Use From Python API

First login once using the CLI:

```bash
atlassian-mcp-auth login
```

Then use the saved token from your Python code:

```python
import asyncio

from atlassian_mcp_auth import AtlassianMcpAuth


async def main() -> None:
    auth = AtlassianMcpAuth(profile="default")
    access_token = await auth.get_access_token()
    print(access_token[:20])


asyncio.run(main())
```

For MCP clients and frameworks, prefer `get_connection_info()`:

```python
import asyncio

from atlassian_mcp_auth import AtlassianMcpAuth


async def main() -> None:
    auth = AtlassianMcpAuth(profile="user-123")
    connection = await auth.get_connection_info()

    # Give these values to your MCP Streamable HTTP transport.
    mcp_url = connection.mcp_url
    headers = connection.headers

    print("MCP URL:", mcp_url)
    print("Headers:", headers)
    print("Cloud ID:", connection.cloud_id)


asyncio.run(main())
```

The library refreshes the access token automatically when it is near expiry.

## 3. Use The Optional HTTP Service

Use the service when another app, frontend, or non-Python process needs to start OAuth and fetch connection data over HTTP.

Install service dependencies:

```bash
pip install "atlassian-mcp-auth[service]"
```

Start the service:

```bash
atlassian-mcp-auth serve --host 127.0.0.1 --port 8765
```

Health check:

```bash
curl http://127.0.0.1:8765/health
```

Start browser login directly:

```text
http://127.0.0.1:8765/oauth/start?profile=user-123
```

Optionally redirect back to your app after authentication:

```text
http://127.0.0.1:8765/oauth/start?profile=user-123&next=http://localhost:3000/settings/integrations
```

`localhost`, `127.0.0.1`, and `::1` are allowed by default for `next`. For a production app, allow your app host when starting the service:

```bash
atlassian-mcp-auth serve \
    --public-url https://auth.example.com \
    --allowed-next-host app.example.com
```

After token exchange succeeds, the service redirects to `next` with:

```text
?atlassian_mcp_auth=complete&profile=user-123&cloud_id=...
```

Or create the authorization URL from an API call:

```bash
curl -X POST http://127.0.0.1:8765/oauth/login \
  -H 'Content-Type: application/json' \
    -d '{"profile":"user-123","next":"http://localhost:3000/settings/integrations"}'
```

After login, fetch connection data:

```bash
curl "http://127.0.0.1:8765/token?profile=user-123"
```

Example response:

```json
{
  "access_token": "...",
  "headers": {"Authorization": "Bearer ..."},
  "mcp_url": "https://mcp.atlassian.com/v1/mcp",
  "resource_url": "https://mcp.atlassian.com/v1/mcp/authv2",
  "cloud_id": "...",
  "expires_at": 1780000000.0
}
```

Useful service endpoints:

```text
GET  /health
GET  /oauth/start?profile=user-123
POST /oauth/login
GET  /oauth/callback
GET  /status?profile=user-123
GET  /token?profile=user-123
POST /refresh?profile=user-123
POST /clear
```

### Use Postgres In Service Mode

SQLite is the default, so this works without database setup:

```bash
atlassian-mcp-auth serve --host 127.0.0.1 --port 8765
```

For Postgres, install the Postgres service extra and pass one database URL:

```bash
pip install "atlassian-mcp-auth[service-postgres]"

atlassian-mcp-auth \
  --database-url "postgresql://user:password@localhost:5432/atlassian_mcp" \
  serve --host 127.0.0.1 --port 8765
```

You can also set the URL with an environment variable:

```bash
export ATLASSIAN_MCP_DATABASE_URL="postgresql://user:password@localhost:5432/atlassian_mcp"
atlassian-mcp-auth serve --host 127.0.0.1 --port 8765
```

More details: [docs/SERVICE_GUIDE.md](docs/SERVICE_GUIDE.md)

## 4. Use With Any MCP Client Or Agent Framework

This package does not create an ADK, LangChain, CrewAI, or raw MCP client for you. It gives those clients fresh Atlassian MCP connection data.

Core pattern:

```python
from atlassian_mcp_auth import AtlassianMcpAuth


auth = AtlassianMcpAuth(profile="user-123")


async def get_headers() -> dict[str, str]:
    return (await auth.get_connection_info()).headers
```

For a raw Streamable HTTP MCP call, use the returned URL and headers:

```python
import asyncio

import httpx

from atlassian_mcp_auth import AtlassianMcpAuth


async def main() -> None:
    auth = AtlassianMcpAuth(profile="raw-http-user")
    connection = await auth.get_connection_info()
    headers = {
        **connection.headers,
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
        "MCP-Protocol-Version": "2025-06-18",
    }

    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.post(
            connection.mcp_url,
            headers=headers,
            json={
                "jsonrpc": "2.0",
                "id": 1,
                "method": "initialize",
                "params": {
                    "protocolVersion": "2025-06-18",
                    "capabilities": {},
                    "clientInfo": {"name": "my-mcp-client", "version": "0.1.0"},
                },
            },
        )
        print(response.status_code)
        print(response.text[:500])


asyncio.run(main())
```

Framework examples:

- [examples/adk_token_provider.py](examples/adk_token_provider.py)
- [examples/langchain_token_provider.py](examples/langchain_token_provider.py)
- [examples/crewai_token_provider.py](examples/crewai_token_provider.py)
- [examples/raw_mcp_http.py](examples/raw_mcp_http.py)
- [examples/service_client.py](examples/service_client.py)

More details: [docs/FRAMEWORKS_GUIDE.md](docs/FRAMEWORKS_GUIDE.md)

## Features

- OAuth 2.1 + PKCE login for Atlassian MCP
- Dynamic Client Registration (RFC 7591)
- RFC 8707 `resource` support for Atlassian MCP access tokens
- Rotating refresh token persistence
- SQLite token storage by default
- Pluggable storage for Postgres, MySQL, Redis, Vault, or app databases
- Framework-neutral token and connection metadata API

## Guides

- [CLI Guide](docs/CLI_GUIDE.md) — local OAuth login, status, refresh, token, clear
- [Service Guide](docs/SERVICE_GUIDE.md) — run HTTP service and use `/oauth/*`, `/token`, `/status`, `/refresh`
- [Framework Guide](docs/FRAMEWORKS_GUIDE.md) — ADK, LangChain/LangGraph, CrewAI, raw MCP HTTP, service-based usage

## Custom Storage

SQLite and Postgres are built in. Production apps can also bring their own storage.

```python
from atlassian_mcp_auth.storage import TokenRecord, TokenStorage


class PostgresStorage(TokenStorage):
    async def load(self, profile: str = "default") -> TokenRecord | None:
        ...

    async def save(self, record: TokenRecord, profile: str = "default") -> None:
        ...

    async def clear(self, profile: str = "default") -> None:
        ...
```

```python
auth = AtlassianMcpAuth(storage=PostgresStorage(), profile="user-123")
```

## Advanced: Browser OAuth Flow From Your App

If your app wants to own the browser redirect flow instead of using the CLI or service, call `begin_authorization()` and `exchange_code()` directly.

```python
from atlassian_mcp_auth import AtlassianMcpAuth, AuthorizationSession


auth = AtlassianMcpAuth(profile="user-123")

# Step 1: create browser consent URL
session: AuthorizationSession = await auth.begin_authorization(
    redirect_uri="https://your-app.example.com/oauth/callback"
)

# Send session.auth_url to the browser and store session.to_dict() temporarily.

# Step 2: after Atlassian redirects back with ?code=...&state=...
record = await auth.exchange_code(code, AuthorizationSession.from_dict(saved_session))

# Stored in SQLite/custom DB:
# - client_id
# - client_secret, when Atlassian returns one
# - access_token
# - refresh_token
# - expires_at
# - scopes
# - cloud_id, when present in the access token
```

## Troubleshooting

If `atlassian-mcp-auth token` says no tokens were found, run:

```bash
atlassian-mcp-auth login
```

If your app uses a profile, use the same profile everywhere:

```bash
atlassian-mcp-auth --profile user-123 login
```

```python
auth = AtlassianMcpAuth(profile="user-123")
```

If the service command is missing dependencies, install the service extra:

```bash
pip install "atlassian-mcp-auth[service]"
```

If a callback port is already in use, choose another port:

```bash
atlassian-mcp-auth login --port 8766
```

## Why This Exists

Atlassian Rovo exposes Jira and Confluence tools through an MCP Streamable HTTP server. Python agent apps often need a direct server-side OAuth flow, token refresh, and storage layer without tying the auth package to one framework.

This package focuses only on that reusable auth layer.
