Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff mypy
pip install -r requirements.txt
pip install -e ".[dev]"

- name: Lint with ruff
run: ruff check server/
run: ruff check server/ tests/

- name: Type check with mypy
run: mypy server/ --ignore-missing-imports
Expand All @@ -42,8 +41,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio
pip install -e ".[dev]"

- name: Run tests
run: pytest tests/ -v
Expand All @@ -64,12 +62,11 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -e .

- name: Validate MCP server starts
run: |
timeout 5 python server/main.py < /dev/null || true
env:
AUTH0_JWT_TOKEN: "test_token"
API_BASE_URL: "https://api.dev.subconscious.ai"

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ Thumbs.db
*.log
logs/

.vercel
42 changes: 42 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Repository Guidelines

## Project Structure & Module Organization
- `server/` contains the local MCP stdio server and shared tool modules.
- `server/tools/` groups tool definitions by domain (`ideation.py`, `experiments.py`, `population.py`, `runs.py`, `personas.py`, `analytics.py`).
- `server/tools/_core/` contains shared handler, retry, and exception logic used by both local and hosted modes.
- `api/index.py` is the Starlette/Vercel entrypoint for REST + SSE endpoints.
- `tests/` contains `pytest` suites (`test_*.py`) for unit and integration coverage.
- `examples/` provides MCP client config samples (`examples/local/config.json`, `examples/cursor/mcp.json`, `examples/claude/config.json`).

## Build, Test, and Development Commands
- `python3 -m venv venv && source venv/bin/activate`: create/activate local environment.
- `pip install -r requirements.txt`: install runtime + test dependencies.
- `AUTH0_JWT_TOKEN=... python server/main.py`: run MCP server in stdio mode locally.
- `pytest -v`: run all tests.
- `pytest tests/test_integration.py -v`: run a focused suite.
- `ruff check server tests`: lint Python code.
- `mypy server --ignore-missing-imports`: type-check core modules.
- `vercel --prod`: deploy hosted API/SSE server.

## Coding Style & Naming Conventions
- Python uses 4-space indentation and type hints on public functions.
- Ruff is configured with `line-length = 100`; keep imports sorted and remove unused symbols.
- Follow existing naming patterns: snake_case for functions/variables/files, PascalCase for classes, `*_tool`/`handle_*` for tool factories and handlers.
- Keep modules domain-focused (add tool logic to the matching file in `server/tools/`).

## Testing Guidelines
- Framework: `pytest` with `pytest-asyncio` (`asyncio_mode = auto`).
- Name tests as `tests/test_<area>.py` and test functions as `test_<behavior>`.
- For new or changed handlers/tools, add at least one success-path test and one failure-path test.
- No fixed coverage gate is configured; maintain or improve coverage for touched code.

## Commit & Pull Request Guidelines
- Use concise, imperative commit subjects; optional Conventional Commit prefixes are acceptable (e.g., `chore:`, `fix:`).
- Link PRs to issues using `Refs #<issue>` or `Closes #<issue>` in PR body.
- PRs should include: purpose, scope, test evidence (`pytest`, `ruff`, `mypy` output), and API/behavior changes.
- If endpoints or tool schemas change, include example request/response snippets.

## Security & Configuration Tips
- Never commit real tokens (`AUTH0_JWT_TOKEN`) or secrets.
- Prefer `Authorization: Bearer` headers for API calls; query-param tokens are deprecated.
- Use `API_BASE_URL` and CORS env vars for environment-specific behavior instead of hardcoding.
73 changes: 64 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Overview

Subconscious AI MCP Server - A Model Context Protocol (MCP) server that enables AI assistants (Claude, Cursor) to run AI-powered conjoint experiments via the Subconscious AI platform.
Subconscious AI MCP Server - A Model Context Protocol (MCP) server that enables AI assistants (Claude, Cursor) to run AI-powered conjoint experiments via the Subconscious AI platform. Requires Python >= 3.11.

## Build and Development Commands

```bash
# Setup
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
pip install -e ".[dev]" # install with dev deps (pytest, ruff, mypy)
# or: pip install -r requirements.txt # runtime deps only

# Run local MCP server (stdio mode)
AUTH0_JWT_TOKEN="your_token" python server/main.py
Expand All @@ -22,14 +23,21 @@ pytest tests/ -v
# Run single test
pytest tests/test_handlers.py::TestCheckCausality::test_causal_question_returns_success -v

# Linting
ruff check server/
# Linting (both server and tests)
ruff check server/ tests/

# Type checking
mypy server/ --ignore-missing-imports

# Deploy to Vercel
vercel --prod

# Smoke test against live backend
AUTH0_JWT_TOKEN=... python scripts/smoke_mcp.py
python scripts/smoke_mcp.py --deep --run-id <run_id>

# Full E2E: create experiment, poll, download artifacts
AUTH0_JWT_TOKEN=... python scripts/run_e2e_download_artifacts.py
```

## Architecture
Expand All @@ -51,9 +59,10 @@ Shared implementations used by both deployment modes:
- Handlers delegate to `_core/handlers.py` via `EnvironmentTokenProvider`

### 2. Remote/Hosted Mode (`api/index.py`)
- Vercel serverless deployment using Starlette
- Exposes MCP protocol over SSE at `/api/sse`
- Also provides REST API at `/api/call/{tool_name}`
- Vercel serverless deployment using Starlette + SSE
- MCP protocol: `GET /api/sse` (streaming), `POST /api/sse/message` (JSON-RPC)
- REST API: `POST /api/call/{tool_name}`, `GET /api/tools`, `GET /api/health`
- Auth: `Authorization: Bearer TOKEN` header (preferred) or `?token=TOKEN` (deprecated)
- Uses `RequestTokenProvider` for per-request token handling

### Tool Organization (`server/tools/`)
Expand All @@ -64,6 +73,10 @@ Shared implementations used by both deployment modes:
- `personas.py` - `generate_personas`, `get_experiment_personas`
- `analytics.py` - `get_amce_data`, `get_causal_insights`

### Utilities
- `server/utils/api_client.py` - `APIClient` HTTP wrapper (httpx-based)
- `server/config.py` - `MCPConfig` class, environment variable loading

### Experiment Workflow
1. `check_causality` - Validate research question is causal
2. `generate_attributes_levels` - Create experiment attributes/levels
Expand All @@ -72,6 +85,24 @@ Shared implementations used by both deployment modes:
5. `get_experiment_status` - Track progress
6. `get_experiment_results` - Get results when complete

## Testing

Tests use `pytest` + `pytest-asyncio` with `asyncio_mode = "auto"`. Tests are class-based.

| File | Coverage |
|---|---|
| `test_tools.py` | Tool schema validation, config loading, API client init |
| `test_handlers.py` | All 15 handlers with mocked HTTP, parametrized error mapping |
| `test_exceptions.py` | Exception hierarchy |
| `test_retry.py` | Retry decorator (success, transient retries, max exceeded, non-retryable) |
| `test_integration.py` | REST API endpoints via `httpx.ASGITransport` |
| `test_api_handler_parity.py` | REST API and MCP JSON-RPC match core handler output |

Key test patterns:
- Mock HTTP: `unittest.mock.patch("server.tools._core.handlers._api_request")`
- Integration: `httpx.ASGITransport` + `AsyncClient` against the Starlette app
- Fixtures in `tests/conftest.py`: `mock_token_provider`, `mock_api_response`, `sample_experiment_response`, `sample_run_response`, `sample_attributes_response`

## Error Handling

All handlers return `ToolResult` with structured error information:
Expand All @@ -80,11 +111,35 @@ All handlers return `ToolResult` with structured error information:
- `error: str` - Error type code on failure (e.g., `auth_error`, `rate_limit`)
- `message: str` - Human-readable message

Retry logic automatically retries on `RateLimitError`, `ServerError`, and `NetworkError`.
Retry logic (`@with_retry`) automatically retries on `RateLimitError`, `ServerError`, and `NetworkError`. Does NOT retry on `AuthenticationError`, `AuthorizationError`, `NotFoundError`, `ValidationError`.

## CI/CD (.github/workflows/ci.yml)

Triggers on push/PR to `main` and `develop`. Three jobs on Ubuntu, Python 3.11:

1. **lint** - `ruff check server/ tests/` + `mypy server/ --ignore-missing-imports`
2. **test** - `pytest tests/ -v` (with `AUTH0_JWT_TOKEN=test_token`)
3. **validate-mcp** - Verifies `server/main.py` starts without crashing

## Scripts

- `scripts/smoke_mcp.py` - Smoke test handlers against live backend (use `--deep --run-id` for full coverage)
- `scripts/run_e2e_download_artifacts.py` - Full E2E: create experiment, poll to completion, download artifacts to `artifacts/`

## Environment Variables

- `AUTH0_JWT_TOKEN` - Required. Get from app.subconscious.ai Settings
- `AUTH0_JWT_TOKEN` - Required. Get from app.subconscious.ai Settings (see `.env.example`)
- `API_BASE_URL` - Backend API (default: `https://api.subconscious.ai`, dev: `https://api.dev.subconscious.ai`)
- `SUBCONSCIOUSAI_M2M_CLIENT_ID` / `SUBCONSCIOUSAI_M2M_CLIENT_SECRET` - M2M client credentials (takes priority over JWT)
- `AUTH0_DOMAIN`, `AUTH0_AUDIENCE`, `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET` - Auth0 OAuth config
- `CORS_ALLOWED_ORIGINS` - Comma-separated list of allowed CORS origins
- `CORS_ALLOW_ALL` - Set to `true` to allow all origins (development only)

## Conventions

See `AGENTS.md` for full contributor guidelines. Key points:
- `snake_case` for functions/variables/files, `PascalCase` for classes
- `*_tool` suffix for tool factory functions, `handle_*` prefix for handlers
- Tests: `tests/test_<area>.py`, class-based, add success + failure tests per handler
- Never commit `AUTH0_JWT_TOKEN`; use `Authorization: Bearer` not query param tokens
- Ruff config: `line-length = 100`, rules E/F/I/N/W
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ No setup required! Add to your MCP client configuration:

```bash
# Clone the repository
git clone https://github.com/Subconscious-ai/subconscious-ai-mcp.git
cd subconscious-ai-mcp
git clone https://github.com/Subconscious-ai/ghostshell.git
cd ghostshell

# Create virtual environment
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt
pip install -e ".[dev]"

# Set environment variables
export AUTH0_JWT_TOKEN="your_token_here"
Expand Down Expand Up @@ -174,7 +174,7 @@ curl -X POST https://ghostshell-runi.vercel.app/api/call/get_experiment_results
| `/` | GET | No | Server info and available tools |
| `/api/health` | GET | No | Health check |
| `/api/tools` | GET | No | List all tools with schemas |
| `/api/sse` | GET | Yes | MCP SSE connection (token in query param) |
| `/api/sse` | GET | Yes | MCP SSE connection (Authorization header preferred; query token fallback) |
| `/api/call/{tool}` | POST | Yes | Call a tool directly |

## 🏗️ Self-Hosting on Vercel
Expand All @@ -186,8 +186,8 @@ Deploy your own instance for your organization:
npm i -g vercel

# Clone and deploy
git clone https://github.com/Subconscious-ai/subconscious-ai-mcp.git
cd subconscious-ai-mcp
git clone https://github.com/Subconscious-ai/ghostshell.git
cd ghostshell
vercel --prod
```

Expand Down
Loading