534 lines
14 KiB
Markdown
534 lines
14 KiB
Markdown
# Clubber
|
|
|
|
A toolset for managing members of non-profit societies (Vereine) with AI-powered administration.
|
|
|
|
## Overview
|
|
|
|
Clubber provides a GraphQL API for member management combined with an MCP (Model Context Protocol) server that enables AI assistants to help with society administration tasks.
|
|
|
|
**Current Status**: Early development - OpenSpec documentation and project structure in place.
|
|
|
|
## Architecture
|
|
|
|
The system consists of two main components:
|
|
|
|
1. **GraphQL API** - Core backend for member data management
|
|
- Built with FastAPI and Strawberry GraphQL
|
|
- Database-agnostic design (SQLite for development, PostgreSQL for production)
|
|
- SQLAlchemy 2.0 ORM with async support
|
|
|
|
2. **MCP Server** - AI assistant integration layer
|
|
- Connects to the GraphQL API
|
|
- Provides tools for AI agents to manage society operations
|
|
- Service account authentication
|
|
|
|
## Tech Stack
|
|
|
|
- **Python 3.11+** - Primary language for GraphQL API
|
|
- **TypeScript** - Language for MCP Server
|
|
- **Node.js** - Runtime for MCP Server
|
|
- **uv** - Fast package and project manager for Python
|
|
- **npm** - Package manager for TypeScript
|
|
- **FastAPI** - Async web framework
|
|
- **Strawberry GraphQL** - Type-safe GraphQL with Python type hints
|
|
- **SQLAlchemy 2.0** - Database ORM
|
|
- **Alembic** - Database migrations
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
clubber/
|
|
├── src/ # Application source code
|
|
│ ├── models/ # SQLAlchemy database models
|
|
│ ├── resolvers/ # GraphQL resolvers (queries and mutations)
|
|
│ ├── schemas/ # GraphQL schema definitions
|
|
│ ├── config.py # Application configuration
|
|
│ ├── database.py # Database setup and session management
|
|
│ ├── main.py # FastAPI application entry point
|
|
│ ├── mcp_server.ts # MCP server implementation (TypeScript)
|
|
│ └── validation.py # Input validation logic
|
|
├── dist/ # Compiled TypeScript code
|
|
├── node_modules/ # Node.js dependencies
|
|
├── package.json # Node.js project configuration
|
|
├── tsconfig.json # TypeScript configuration
|
|
├── tests/ # Test suite
|
|
│ ├── unit/ # Unit tests (validation logic)
|
|
│ ├── integration/ # Integration tests (database, resolvers)
|
|
│ ├── e2e/ # End-to-end tests (API flows)
|
|
│ ├── mcp/ # MCP server tests
|
|
│ └── conftest.py # Shared test fixtures
|
|
├── migrations/ # Alembic database migrations
|
|
│ └── versions/ # Migration version files
|
|
├── scripts/ # Utility scripts
|
|
│ └── seed.py # Database seeding script
|
|
├── openspec/ # Specification-driven development
|
|
│ ├── specs/ # Current specifications
|
|
│ │ ├── database-layer/
|
|
│ │ ├── graphql-api/
|
|
│ │ ├── mcp-integration/
|
|
│ │ ├── member-crud/
|
|
│ │ ├── project-documentation/
|
|
│ │ └── project-setup/
|
|
│ └── changes/ # Change proposals
|
|
│ └── archive/ # Archived completed changes
|
|
├── pyproject.toml # Project dependencies and configuration
|
|
├── alembic.ini # Alembic migration configuration
|
|
├── uv.lock # Locked dependency versions
|
|
├── CLAUDE.md # AI assistant instructions
|
|
└── README.md # This file
|
|
```
|
|
|
|
## Getting Started
|
|
|
|
### Prerequisites
|
|
|
|
- Python 3.11 or higher
|
|
- [uv](https://github.com/astral-sh/uv) package manager
|
|
|
|
### Setup
|
|
|
|
```bash
|
|
# Clone the repository
|
|
git clone ssh://git@codeberg.org/gurix/clubber.git
|
|
cd clubber
|
|
|
|
# Install uv if not already installed
|
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
|
|
# Install dependencies
|
|
uv sync
|
|
|
|
# Run database migrations
|
|
uv run alembic upgrade head
|
|
|
|
# (Optional) Seed the database with sample data
|
|
PYTHONPATH=. uv run python scripts/seed.py
|
|
```
|
|
|
|
### Running the Server
|
|
|
|
Start the development server:
|
|
|
|
```bash
|
|
uv run uvicorn src.main:app --host 127.0.0.1 --port 8000 --reload
|
|
```
|
|
|
|
The `--reload` flag enables auto-restart when code changes are detected.
|
|
|
|
Access the API:
|
|
- **GraphQL Playground**: http://127.0.0.1:8000/graphql
|
|
- **API Documentation**: http://127.0.0.1:8000/docs
|
|
- **Root Endpoint**: http://127.0.0.1:8000/
|
|
|
|
## API Usage Examples
|
|
|
|
### Member Data Model
|
|
|
|
Members have the following fields:
|
|
- `firstName` (required) - Member's first name
|
|
- `lastName` (optional) - Member's last name
|
|
- `street` (optional) - Street address
|
|
- `apartmentNumber` (optional) - Apartment or unit number
|
|
- `zip` (optional) - Postal code
|
|
- `city` (optional) - City
|
|
- `country` (optional) - Country
|
|
- `email` (optional) - Email address (validated when provided)
|
|
- `phone` (optional) - Phone number in E.164 format (validated when provided)
|
|
|
|
**Key Feature**: Only `firstName` is required. All other fields are optional and can be populated later. Email and phone are validated only when provided (not when null/empty).
|
|
|
|
### GraphQL Queries
|
|
|
|
List all members:
|
|
```graphql
|
|
{
|
|
members {
|
|
id
|
|
firstName
|
|
lastName
|
|
email
|
|
phone
|
|
city
|
|
}
|
|
}
|
|
```
|
|
|
|
Get a single member:
|
|
```graphql
|
|
{
|
|
member(id: 1) {
|
|
id
|
|
firstName
|
|
lastName
|
|
email
|
|
street
|
|
city
|
|
country
|
|
}
|
|
}
|
|
```
|
|
|
|
### GraphQL Mutations
|
|
|
|
Create a member (minimal - only firstName):
|
|
```graphql
|
|
mutation {
|
|
createMember(input: {
|
|
firstName: "Alice"
|
|
}) {
|
|
id
|
|
firstName
|
|
email
|
|
}
|
|
}
|
|
```
|
|
|
|
Create a member with all fields:
|
|
```graphql
|
|
mutation {
|
|
createMember(input: {
|
|
firstName: "Bob"
|
|
lastName: "Johnson"
|
|
street: "123 Main St"
|
|
apartmentNumber: "4B"
|
|
zip: "12345"
|
|
city: "Springfield"
|
|
country: "USA"
|
|
email: "bob.johnson@example.com"
|
|
phone: "+14155551234"
|
|
}) {
|
|
id
|
|
firstName
|
|
lastName
|
|
email
|
|
phone
|
|
}
|
|
}
|
|
```
|
|
|
|
Update a member:
|
|
```graphql
|
|
mutation {
|
|
updateMember(input: {
|
|
id: 1
|
|
email: "updated@example.com"
|
|
phone: "+14155559999"
|
|
}) {
|
|
id
|
|
firstName
|
|
email
|
|
phone
|
|
}
|
|
}
|
|
```
|
|
|
|
Delete a member:
|
|
```graphql
|
|
mutation {
|
|
deleteMember(id: 2)
|
|
}
|
|
```
|
|
|
|
### Using curl
|
|
|
|
Query members:
|
|
```bash
|
|
curl -X POST http://127.0.0.1:8000/graphql \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"query": "{ members { id firstName lastName email } }"}'
|
|
```
|
|
|
|
Create a member:
|
|
```bash
|
|
curl -X POST http://127.0.0.1:8000/graphql \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"query": "mutation { createMember(input: { firstName: \"Charlie\" }) { id firstName } }"}'
|
|
```
|
|
|
|
## MCP Server
|
|
|
|
The MCP (Model Context Protocol) server enables AI assistants like Claude to manage society members through natural language interactions.
|
|
|
|
### What is MCP?
|
|
|
|
The Model Context Protocol (MCP) is a standard protocol that allows AI assistants to use tools and access external systems. The Clubber MCP server provides 6 tools that connect to the GraphQL API:
|
|
|
|
**Dedicated Member Tools** (simple, focused operations):
|
|
- **list_members** - List all members with their complete information
|
|
- **get_member** - Get detailed information about a specific member by ID
|
|
- **create_member** - Create a new member (only firstName required)
|
|
- **update_member** - Update an existing member's information
|
|
|
|
**General GraphQL Tools** (flexible, for complex queries):
|
|
- **get_graphql_schema** - Get the complete GraphQL schema via introspection
|
|
- **execute_graphql_query** - Execute arbitrary GraphQL queries and mutations
|
|
|
|
### Setup & Build
|
|
|
|
The MCP server connects to the GraphQL API, so ensure the API is running:
|
|
|
|
```bash
|
|
# Start the GraphQL API (Backend)
|
|
uv run uvicorn src.main:app --host 127.0.0.1 --port 8000
|
|
```
|
|
|
|
Then, install dependencies and build the MCP server:
|
|
|
|
```bash
|
|
npm install
|
|
npm run build
|
|
```
|
|
|
|
*Note: You don't need to run the MCP server manually in a separate terminal. It will be started automatically by your AI client (Claude) based on the configuration below.*
|
|
|
|
|
|
|
|
### Integrating with Claude Code
|
|
|
|
To use the MCP server with Claude Code, add it to your MCP configuration:
|
|
|
|
**For macOS/Linux** (`~/Library/Application Support/Claude/claude_desktop_config.json` or `~/.config/claude/config.json`):
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"clubber": {
|
|
"command": "node",
|
|
"args": ["/absolute/path/to/clubber/dist/mcp_server.js"],
|
|
"env": {
|
|
"CLUBBER_API_URL": "http://127.0.0.1:8000/graphql"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Configuration Options**:
|
|
- `CLUBBER_API_URL`: URL of the GraphQL API (default: `http://127.0.0.1:8000/graphql`)
|
|
|
|
**Note**: Make sure to provide the absolute path to the `dist/mcp_server.js` file.
|
|
|
|
After adding the configuration, restart Claude Code. You can then use natural language to manage members:
|
|
|
|
- "List all members in the society"
|
|
- "Create a new member named Alice"
|
|
- "Update member 5's email to alice@example.com"
|
|
- "Show me details for member 3"
|
|
|
|
### MCP Server Usage Examples
|
|
|
|
Once configured, you can interact with the MCP server through Claude Code:
|
|
|
|
**Example 1: Create a member**
|
|
```
|
|
User: Create a new member named Bob Smith with email bob@example.com
|
|
|
|
Claude uses create_member tool:
|
|
- firstName: "Bob"
|
|
- lastName: "Smith"
|
|
- email: "bob@example.com"
|
|
|
|
Result: Member created with ID 1
|
|
```
|
|
|
|
**Example 2: Update member information**
|
|
```
|
|
User: Update member 1's phone number to +41791234567
|
|
|
|
Claude uses update_member tool:
|
|
- id: 1
|
|
- phone: "+41791234567"
|
|
|
|
Result: Member updated successfully
|
|
```
|
|
|
|
**Example 3: List all members**
|
|
```
|
|
User: Show me all members in the society
|
|
|
|
Claude uses list_members tool and displays formatted results:
|
|
- Bob Smith (bob@example.com, +41791234567)
|
|
- Alice Johnson (alice@example.com)
|
|
...
|
|
```
|
|
|
|
**Example 4: Discover GraphQL schema**
|
|
```
|
|
User: What fields are available in the Member type?
|
|
|
|
Claude uses get_graphql_schema tool to discover:
|
|
- The complete schema structure
|
|
- All available types (Query, Mutation, Member, etc.)
|
|
- Field definitions with types and descriptions
|
|
- Available queries and mutations
|
|
|
|
Result: Shows Member type with all fields (id, firstName, lastName, email, etc.)
|
|
```
|
|
|
|
**Example 5: Execute custom GraphQL query**
|
|
```
|
|
User: Get only the first names and emails of all members
|
|
|
|
Claude uses execute_graphql_query tool with:
|
|
query: |
|
|
query {
|
|
members {
|
|
firstName
|
|
email
|
|
}
|
|
}
|
|
|
|
Result: Returns JSON with only the requested fields
|
|
```
|
|
|
|
**Example 6: Execute query with variables**
|
|
```
|
|
User: Get member 5's contact information
|
|
|
|
Claude uses execute_graphql_query tool with:
|
|
query: |
|
|
query GetMember($id: Int!) {
|
|
member(id: $id) {
|
|
firstName
|
|
lastName
|
|
email
|
|
phone
|
|
city
|
|
}
|
|
}
|
|
variables: {"id": 5}
|
|
|
|
Result: Returns member 5's contact details
|
|
```
|
|
|
|
### When to Use Which Tools
|
|
|
|
**Use dedicated member tools** when:
|
|
- Performing simple, common operations
|
|
- The tool matches your exact need
|
|
- You want a formatted, human-readable response
|
|
|
|
**Use general GraphQL tools** when:
|
|
- Exploring what's possible (use `get_graphql_schema`)
|
|
- Needing custom field selection
|
|
- Combining multiple operations
|
|
- Working with complex queries or filters (future)
|
|
- You need the raw JSON response
|
|
|
|
## Running Tests
|
|
|
|
This project uses pytest for automated testing. All tests are organized in the `tests/` directory.
|
|
|
|
### Run All Tests
|
|
|
|
```bash
|
|
uv run pytest
|
|
```
|
|
|
|
### Run Specific Test Categories
|
|
|
|
```bash
|
|
# Unit tests only (validation logic)
|
|
uv run pytest tests/unit
|
|
|
|
# Integration tests only (database and resolvers)
|
|
uv run pytest tests/integration
|
|
|
|
# E2E tests only (complete API flows)
|
|
uv run pytest tests/e2e
|
|
|
|
# MCP server tests only
|
|
uv run pytest tests/mcp
|
|
```
|
|
|
|
### Run Tests with Verbose Output
|
|
|
|
```bash
|
|
uv run pytest --verbose
|
|
```
|
|
|
|
### Run Tests with Coverage
|
|
|
|
```bash
|
|
uv run pytest --cov=src --cov-report=term-missing
|
|
```
|
|
|
|
## Test Organization
|
|
|
|
The test suite is organized into four categories:
|
|
|
|
### Unit Tests (`tests/unit/`)
|
|
Pure logic tests for validation functions and utilities.
|
|
- **`test_validation.py`** - Email, phone, and firstName validation
|
|
|
|
### Integration Tests (`tests/integration/`)
|
|
Tests for database operations and GraphQL resolvers.
|
|
- **`test_member_model.py`** - SQLAlchemy Member model
|
|
- **`test_member_queries.py`** - GraphQL queries (member, members)
|
|
- **`test_member_mutations.py`** - GraphQL mutations (create, update, delete)
|
|
|
|
### E2E Tests (`tests/e2e/`)
|
|
Complete API request/response flows over HTTP.
|
|
- **`test_graphql_api.py`** - GraphQL API endpoints, introspection
|
|
|
|
### MCP Tests (`tests/mcp/`)
|
|
MCP server tools and functionality.
|
|
- **`test_mcp_server.py`** - All 6 MCP tools (list_members, get_member, create_member, update_member, get_graphql_schema, execute_graphql_query)
|
|
|
|
### Test Fixtures
|
|
|
|
Shared fixtures are defined in `tests/conftest.py`:
|
|
- **`test_engine`** - In-memory SQLite database
|
|
- **`async_session`** - Async database session
|
|
- **`graphql_client`** - FastAPI test client for GraphQL
|
|
|
|
## Development Workflow
|
|
|
|
This project uses [OpenSpec](https://openspec.dev) for specification-driven development:
|
|
|
|
1. **Review existing specs** in `openspec/specs/`
|
|
2. **Create change proposals** in `openspec/changes/` before implementing features
|
|
3. **Validate proposals** with `openspec validate --strict`
|
|
4. **Implement changes** following the proposal
|
|
5. **Archive completed changes** after deployment
|
|
|
|
See `openspec/AGENTS.md` for detailed workflow instructions.
|
|
|
|
### Working with Git Worktrees
|
|
|
|
We use git worktrees for parallel development:
|
|
|
|
```bash
|
|
# Create a worktree for a feature branch
|
|
git worktree add ../clubber-feature-name feature/feature-name
|
|
|
|
# Work in the worktree
|
|
cd ../clubber-feature-name
|
|
|
|
# Remove worktree after merge
|
|
git worktree remove ../clubber-feature-name
|
|
```
|
|
|
|
## Contributing
|
|
|
|
1. Read `openspec/project.md` for project conventions
|
|
2. For new features, create an OpenSpec proposal first
|
|
3. Follow the coding style (Black, isort, ruff)
|
|
4. Write tests for new functionality
|
|
5. Use conventional commits (`feat:`, `fix:`, `docs:`, etc.)
|
|
|
|
## License
|
|
|
|
The MIT License
|
|
|
|
## Contact
|
|
|
|
Markus Graf - [info@markusgraf.ch](mailto:info@markusgraf.ch)
|
|
|
|
## Acknowledgments
|
|
|
|
Built with support from:
|
|
- [Strawberry GraphQL](https://strawberry.rocks)
|
|
- [FastAPI](https://fastapi.tiangolo.com)
|
|
- [OpenSpec](https://openspec.dev)
|