A vocabulary and grammar management application built with Spring Boot. Lobos provides both a RESTful JSON API and a Thymeleaf-based web interface for organizing vocabulary collections and grammar notes.
- π Collections β Group vocabulary entries into named, color-coded collections
- π Grammar Notes β Store words with their meanings, examples, and star/favorite them
- π Authentication β Register, log in, update your profile, and log out securely
- πͺ Remember Me β Optional persistent login (JDBC-backed, 30-day rotating token) so the web session survives a closed browser
- π Web UI β Full-featured browser interface built with Thymeleaf and Tailwind CSS
- π REST API β Stateless JSON API secured with JWT bearer tokens
- π€ MCP Server β Remote MCP server (Streamable HTTP) exposing collections and grammars as tools for AI clients, secured with scoped MCP tokens
- π Pagination β Navigate large lists with built-in pagination
| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 4.1.0 |
| Web / MVC | Spring Web MVC, Thymeleaf |
| Security | Spring Security (JWT for API, session for Web) |
| Database | MySQL |
| Persistence | Spring JDBC (JdbcTemplate) β raw SQL, no ORM |
| Build Tool | Maven (./mvnw) |
| Frontend CSS | Tailwind CSS via CDN |
| Password | jBCrypt |
| JWT | JJWT 0.12.6 |
| MCP Server | Spring AI MCP Server (WebMVC, Streamable HTTP) |
src/main/java/id/my/tudemaha/lobos/
βββ config/ # Security & application configuration
βββ controller/
β βββ api/ # REST controllers (@RestController, /api prefix)
β βββ web/ # MVC controllers (@Controller, Thymeleaf views)
β βββ mcp/ # MCP tools (@McpTool components exposed over the MCP server)
βββ dto/
β βββ request/ # Incoming request DTOs
β βββ response/ # Outgoing response DTOs
βββ exception/ # Custom exceptions & GlobalExceptionHandler
βββ mapper/ # Entity β DTO mappers
βββ model/ # Domain models (User, Collection, Grammar, McpToken)
βββ repository/ # Data access via JdbcTemplate
βββ security/ # JWT/MCP filters, remember-me (JdbcPersistentTokenRepository, AppUserDetailsService) & token services
βββ service/ # Business logic
βββ utils/ # Pagination, PasswordHasher
src/main/resources/
βββ application.properties # App configuration (DB, JWT secret, MCP server)
βββ schema.sql # MySQL DDL
βββ static/
β βββ css/main.css # Global styles
β βββ fonts/ # Custom fonts (vimala.ttf)
β βββ js/ # Vanilla JS per-page scripts (incl. mcp.js)
β βββ index.html # Static landing page
βββ templates/ # Thymeleaf HTML templates
βββ layout.html # Shared base layout
βββ auth/ # login.html, register.html, profile.html
βββ mcp/ # index.html (MCP token management)
βββ collections/ # index.html
βββ grammars/ # index.html, detail.html
users (id, first_name, last_name, email, password, created_at, updated_at)
persistent_logins (series, user_id, token, last_used)
mcp_tokens (id, user_id β users.id, name, token, created_at, last_used_at)
collections (id, name, color, user_id β users.id, created_at, updated_at)
grammars (id, word, meaning, example, is_starred, collection_id β collections.id, created_at, updated_at)Primary keys are VARCHAR(36) UUIDs, auto-generated by MySQL (DEFAULT (UUID())). persistent_logins is keyed by series instead (remember-me tokens), with user_id referencing the immutable users.id.
- Java 21+
- MySQL database
- Maven (or use the included
./mvnwwrapper)
git clone <repository-url>
cd lobosCreate a MySQL database and run the schema:
mysql -u <user> -p <database_name> < src/main/resources/schema.sqlEdit src/main/resources/application.properties:
spring.ai.mcp.server.name=lobos-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.protocol=streamable
spring.datasource.url=jdbc:mysql://<host>:<port>/<database>
spring.datasource.username=<db_user>
spring.datasource.password=<db_password>
jwt.secret=<your_strong_secret_key>
app.remember-me.key=<your_strong_secret_key>
β οΈ Never commitapplication.propertieswith real credentials to version control.
./mvnw spring-boot:runThe application starts at http://localhost:8080.
The prebuilt image is published to GitHub Container Registry at ghcr.io/tudemaha/lobos:latest. Fill in your database and JWT values in docker-compose.yml, then start it:
services:
lobos:
restart: on-failure
image: ghcr.io/tudemaha/lobos:latest
environment:
- DB_URL=jdbc:mysql://<host>:<port>/<database>
- DB_USERNAME=<username>
- DB_PASSWORD=<password>
- JWT_SECRET=<secret>
- REMEMBER_ME_KEY=<key>
ports:
- 127.0.0.1:8080:8080docker compose up -dThe app will be available at http://localhost:8080.
docker build -t lobos .docker run -d \
-p 8080:8080 \
-e DB_URL=jdbc:mysql://<host>:<port>/<database> \
-e DB_USERNAME=<username> \
-e DB_PASSWORD=<password> \
-e JWT_SECRET=<secret> \
-e REMEMBER_ME_KEY=<key> \
lobos
β οΈ Never commit real database credentials, JWT secrets, or the remember-me key intodocker-compose.ymlor version control.
| Route | Description |
|---|---|
/ |
Landing page |
/login |
Log in to your account |
/register |
Create a new account |
/profile |
View and update your profile |
/collections |
Manage your vocabulary collections |
/grammars |
Browse and manage grammar notes |
/grammars/{id} |
View grammar note detail |
/tokens |
Manage MCP tokens |
All API endpoints are prefixed with /api and require a Bearer <token> JWT header (except auth endpoints).
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register a new user |
| POST | /api/auth/login |
Log in and receive a JWT |
| GET | /api/collections |
List user's collections |
| POST | /api/collections |
Create a new collection |
| PUT | /api/collections/{id} |
Update a collection |
| DELETE | /api/collections/{id} |
Delete a collection |
| GET | /api/collections/{collectionId}/grammars |
List grammar notes in a collection |
| POST | /api/collections/{collectionId}/grammars |
Create a grammar note |
| GET | /api/collections/{collectionId}/grammars/{grammarId} |
Get a single grammar note |
| PUT | /api/collections/{collectionId}/grammars/{grammarId} |
Update a grammar note |
| PATCH | /api/collections/{collectionId}/grammars/{grammarId} |
Toggle star on a grammar note |
| DELETE | /api/collections/{collectionId}/grammars/{grammarId} |
Delete a grammar note |
| POST | /api/tokens |
Generate a new MCP token |
| GET | /api/tokens |
List user's MCP tokens |
| PATCH | /api/tokens/{id} |
Rename an MCP token |
| DELETE | /api/tokens/{id} |
Revoke an MCP token |
Lobos embeds a remote MCP server (Streamable HTTP) at /mcp, letting AI clients (Claude Desktop, Claude Code, etc.) read and write a user's collections and grammars directly. It's authenticated with a dedicated MCP token β generated from the /tokens page β instead of the login JWT, so a leaked token can't be used to change account credentials.
claude mcp add --transport http lobos http://localhost:8080/mcp -H "Authorization: Bearer <mcp_token>"| Tool | Description |
|---|---|
list_collections |
List the authenticated user's collections |
create_collection |
Create a new collection |
update_collection |
Update a collection's name and color |
delete_collection |
Delete a collection |
list_grammars |
List grammars within a collection |
get_grammar |
Get a single grammar's detail |
create_grammar |
Create a grammar note inside a collection |
update_grammar |
Update a grammar note |
toggle_star_grammar |
Toggle star/favorite on a grammar note |
delete_grammar |
Delete a grammar note |
# Compile
./mvnw compile
# Run tests
./mvnw test
# Package as JAR
./mvnw clean package
# Run with Spring Boot dev tools (live reload)
./mvnw spring-boot:run- REST API (
/api/**) uses stateless JWT authentication viaJwtAuthFilter. - MCP server (
/mcp/**) uses stateless MCP tokens (McpAuthFilter), separate from the login JWT and scoped to collections/grammars only. - Web UI uses session-based Spring Security authentication, with an optional remember-me persistent login (JDBC-backed rotating token, 30-day validity) when the "Keep me signed in" checkbox is used at login. Tokens are keyed off the immutable
users.idand are invalidated on password change or account deletion. - All POST forms include a CSRF token for protection.
- Passwords are hashed with jBCrypt.
- Unauthenticated users are redirected to
/login.
This project is licensed under the Apache License 2.0.