# ARP Agent - LLM Agent for the ARP Platform
## An LLM-powered agent that connects to the ARP (Agent-native ERP) platform via the Model Context Protocol (MCP) and responds to Task and Message events in real-time.


## Table of Contents

- [Overview](#overview)
- [Installation](#installation)
- [Configuration](#configuration)
- [Running the Agent](#running-the-agent)
- [How It Works](#how-it-works)
- [MCP Communication](#mcp-communication)
- [Available MCP Tools](#available-mcp-tools)
- [Programmatic Usage](#programmatic-usage)
- [Testing](#testing)

---

## Overview

The ARP Agent connects to an ARP server and:

1. **Authenticates** via GraphQL login to obtain a JWT token
2. **Connects to MCP** via Server-Sent Events (SSE) to the `/mcp` endpoint
3. **Discovers Tools** using the MCP `tools/list` protocol
4. **Subscribes to Events** via MCP resources for real-time notifications
5. **Processes Events** using an LLM with tool-calling capabilities

---

## Installation

### Using pip

```bash
pip install -r requirements.txt
```

### Using Poetry (recommended)

```bash
poetry install
```

### Dependencies

- `openai` - OpenAI API client for LLM interactions
- `requests` - HTTP client for GraphQL and MCP communication
- `sseclient-py` - Server-Sent Events client for MCP
- `python-dotenv` - Environment variable management

---

## Configuration

Copy the example environment file and configure your credentials:

```bash
cp .env.example .env
```

Edit `.env` with your settings:

```env
# ARP Server Configuration
ARP_URL=http://localhost:8080
ARP_USERNAME=your-email@example.com
ARP_PASSWORD=your-password

# OpenAI Configuration
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_MODEL=gpt-4
OPENAI_TEMPERATURE=0.0

# Optional: Custom OpenAI endpoint (for local models, etc.)
# OPENAI_BASE_URL=http://localhost:11434/v1
```

### Required Environment Variables

| Variable | Description |
|----------|-------------|
| `ARP_URL` | Base URL of the ARP server |
| `ARP_USERNAME` | Your ARP login email |
| `ARP_PASSWORD` | Your ARP password |
| `OPENAI_API_KEY` | OpenAI API key for LLM |

### Optional Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `OPENAI_MODEL` | Model to use | `gpt-4` |
| `OPENAI_TEMPERATURE` | Sampling temperature | `0.0` |
| `OPENAI_BASE_URL` | Custom OpenAI-compatible endpoint | OpenAI API |

---

## Running the Agent

```bash
python run_arp_agent.py
```

### Expected Output

```
Testing connectivity to OpenAI API (api.openai.com)...
✓ Successfully connected to OpenAI API (api.openai.com)
Connecting to ARP server at http://localhost:8080...
Successfully authenticated with ARP server
Connecting to MCP server...
Discovered 3 MCP tools: ['introspect', 'query', 'mutate']
Initializing LLM agent...
Agent initialized successfully.

Subscribing to ARP resources...
Available resources: ['taskCreated', 'taskUpdated', 'taskDeleted', 'messageAdded']
  Subscribed to: graphql://subscription/taskCreated
  Subscribed to: graphql://subscription/taskUpdated
  Subscribed to: graphql://subscription/taskDeleted
  Subscribed to: graphql://subscription/messageAdded

Listening for events. Press Ctrl+C to stop.
```

---

## How It Works

### Architecture

```
┌─────────────────┐      GraphQL Login      ┌─────────────────┐
│   ARP Agent     │ ──────────────────────► │   ARP Server    │
│                 │ ◄────────────────────── │                 │
│  ┌───────────┐  │      JWT Token          │                 │
│  │    LLM    │  │                         │  ┌───────────┐  │
│  └───────────┘  │      SSE Connect        │  │    MCP    │  │
│        │        │ ──────────────────────► │  │  Server   │  │
│        ▼        │      /mcp endpoint      │  └───────────┘  │
│  ┌───────────┐  │                         │        │        │
│  │MCP Client │◄─┼──── Tool Discovery      │        │        │
│  └───────────┘  │      Tool Calls         │        ▼        │
│        │        │      Notifications      │  ┌───────────┐  │
│        ▼        │                         │  │ GraphQL   │  │
│  ┌───────────┐  │                         │  │  Engine   │  │
│  │  Events   │◄─┼─────────────────────────┼──┤           │  │
│  └───────────┘  │      Real-time          │  └───────────┘  │
└─────────────────┘      Subscriptions      └─────────────────┘
```

### Event Processing Flow

1. **Event Received**: Task or message event via MCP resource notification
2. **Context Built**: Extract relevant details (ID, title, content, sender, etc.)
3. **LLM Invoked**: Agent processes the event with available tools
4. **Tool Execution**: LLM may call MCP tools to query/mutate data
5. **Response Generated**: Agent produces a result or takes action

---

## MCP Communication

The agent uses the **Model Context Protocol (MCP)** to communicate with the ARP server:

### Connection Flow

1. **SSE Connection**: Connect to `/mcp` endpoint via Server-Sent Events
2. **Endpoint Discovery**: Receive message endpoint URL from `endpoint` event
3. **Initialize**: Send `initialize` request with protocol version and client info
4. **Tool Discovery**: Call `tools/list` to discover available tools
5. **Subscribe**: Call `resources/subscribe` for real-time event streams

### Authentication

Authentication is handled via JWT tokens:

1. Login via GraphQL `login` mutation with email/password
2. Receive JWT token in response
3. Include token in SSE connection headers (`Authorization: Bearer <token>`)
4. Token is automatically propagated to all MCP requests

### Protocol Details

- **Protocol Version**: `2024-11-05`
- **Transport**: HTTP POST for requests, SSE for responses/notifications
- **Format**: JSON-RPC 2.0

---

## Available MCP Tools

The ARP MCP server exposes three tools:

### 1. `introspect`

Discover the GraphQL schema - types, fields, queries, mutations.

```python
# Get full schema
result = mcp_client.call_tool("introspect", {})

# Get specific type
result = mcp_client.call_tool("introspect", {"typeName": "User"})
```

### 2. `query`

Execute GraphQL queries (read operations).

```python
# Query users
result = mcp_client.call_tool("query", {
    "query": "{ users { id email roles { name } } }"
})

# Query with variables
result = mcp_client.call_tool("query", {
    "query": "query User($id: ID!) { user(id: $id) { id email } }",
    "variables": {"id": "1"}
})
```

### 3. `mutate`

Execute GraphQL mutations (create/update/delete operations).

```python
# Create a task
result = mcp_client.call_tool("mutate", {
    "mutation": """
        mutation CreateTask($input: NewTask!) {
            createTask(input: $input) { id title }
        }
    """,
    "variables": {
        "input": {
            "title": "New Task",
            "content": "Task description",
            "createdById": "1"
        }
    }
})

# Delete a note
result = mcp_client.call_tool("mutate", {
    "mutation": "mutation DeleteNote($id: ID!) { deleteNote(id: $id) }",
    "variables": {"id": "123"}
})
```

---

## Programmatic Usage

### Using MCPClient Directly

```python
from llm_agents.mcp_client import login_and_create_mcp_client

# Login and create client
client = login_and_create_mcp_client(
    url="http://localhost:8080",
    username="admin@example.com",
    password="secret123"
)

# Connect to MCP server
client.connect()
client.initialize()

# Discover tools
tools = client.list_tools()
print(f"Available tools: {[t.name for t in tools]}")

# Call tools
users = client.call_tool("query", {"query": "{ users { id email } }"})
print(users)

# Subscribe to resources
client.subscribe_resource("graphql://subscription/taskCreated")

# Listen for notifications
def on_notification(data):
    print(f"Received: {data}")

client.listen_for_notifications(on_notification)

# Cleanup
client.close()
```

### Using the Agent with MCP

```python
from llm_agents import Agent, ChatLLM
from llm_agents.mcp_client import login_and_create_mcp_client

# Create authenticated MCP client
mcp_client = login_and_create_mcp_client(
    url="http://localhost:8080",
    username="admin@example.com",
    password="secret123"
)

# Connect and initialize
mcp_client.connect()
mcp_client.initialize()
mcp_client.list_tools()

# Create agent with MCP client
llm = ChatLLM()  # Uses OPENAI_API_KEY from environment
agent = Agent(llm=llm, mcp_client=mcp_client)

# Run the agent
result = agent.run("List all users and their roles")
print(result)

# Cleanup
mcp_client.close()
```

---

## MCP Resources (Subscriptions)

The ARP MCP server exposes resources for real-time GraphQL subscriptions:

| Resource URI | Description |
|--------------|-------------|
| `graphql://subscription/taskCreated` | New task events (received by assignee) |
| `graphql://subscription/taskUpdated` | Task update events (received by assignee) |
| `graphql://subscription/taskDeleted` | Task deletion events (received by assignee) |
| `graphql://subscription/messageAdded` | New message events (received by receivers) |

### Subscribing to Resources

```python
# List available resources
resources = mcp_client.list_resources()
for resource in resources:
    print(f"{resource['uri']}: {resource['name']}")

# Subscribe to task events
mcp_client.subscribe_resource("graphql://subscription/taskCreated")
mcp_client.subscribe_resource("graphql://subscription/taskUpdated")

# Unsubscribe
mcp_client.unsubscribe_resource("graphql://subscription/taskCreated")
```

---

## Event Filtering

Events are filtered by the ARP server based on user context:

- **Task Events**: Only received for tasks where the user is the **assignee**
- **Message Events**: Only received for messages where the user is a **receiver**

This ensures each user only receives relevant notifications.

---

## Testing

Run the integration tests (requires a running ARP server):

```bash
python -m pytest tests/integration/ -v --no-cov
```

### Test Categories

- **Login Tests**: Authentication flow
- **Connection Tests**: MCP connection and initialization
- **Tool Tests**: Introspect, query, and mutate operations
- **Resource Tests**: Subscription functionality
- **Error Handling Tests**: Error responses and edge cases

---

## Project Structure

```
arp_agent/
├── llm_agents/
│   ├── __init__.py          # Package exports
│   ├── agent.py             # Agent with tool-calling
│   ├── llm.py               # OpenAI LLM wrapper
│   └── mcp_client.py        # MCP client implementation
├── tests/
│   ├── conftest.py          # Pytest fixtures
│   ├── test_setup_validation.py
│   ├── unit/                # Unit tests
│   └── integration/         # Integration tests
│       └── test_arp_agent_integration.py
├── specs/
│   └── schema.graphqls      # GraphQL schema reference
├── run_arp_agent.py         # Main entry point
├── run_tests.py             # Test runner wrapper
├── pyproject.toml           # Poetry configuration
├── poetry.lock              # Locked dependencies
├── requirements.txt         # Python dependencies (for pip)
├── .env.example             # Environment template
├── CLIENT_GUIDE.md          # ARP client implementation guide
└── README.md                # This file
```

---

## License

See [LICENSE](LICENSE) for details.
