Event-management backend with two first-class interfaces: a JAX-RS REST API for applications and a Model Context Protocol server for AI clients. Both interfaces reuse the same validation, transactions, services, JDBC repositories, and MySQL data.
- One domain exposed through REST, local MCP stdio, and remote MCP Streamable HTTP.
32MCP tools,7resources, and8parameterized resource templates.- Real business workflows: capacity checks, transactional bulk registration, cancellation, check-in, audit logs, status history, pagination, reporting, and CSV export.
- No ORM: SQL, joins, aggregations, transactions, and repository boundaries are explicit.
- Reproducible setup with Docker Compose, Maven Wrapper, seeded MySQL, CI, and integration tests with a
60%line-coverage gate. - Safe-by-default configuration: no committed database password and guarded remote MCP binding.
- Java 17+
- Jersey / JAX-RS
- Apache Tomcat 9
- JDBC
- MySQL
- Maven
- JSON request/response bodies
- Model Context Protocol server over stdio
- No ORM
flowchart LR
App["REST clients"] --> REST["JAX-RS resources"]
AI["Gemini / Claude / Copilot / ChatGPT"] --> MCP["MCP stdio or HTTP"]
REST --> Service["Shared services and business rules"]
MCP --> Service
Service --> Repo["JDBC repositories"]
Repo --> DB[("MySQL")]
REST and MCP run as separate processes, but they reuse the same business logic, validation, transactions, repositories, and MySQL database.
Packages:
config JAX-RS, Jackson, database connection config
model Entity models and status enums
dto Request/response DTOs
resource REST endpoints
mcp MCP server, tools, resources, and resource templates
service Business logic, validation, transactions
repository JDBC SQL access
exception JSON error handling
- Event categories
- Events
- Participants
- Event registrations
- Check-in flow
- Registration summary reports
Requirements: Docker Desktop and Git.
cp .env.example .env
# Replace every placeholder in .env, then:
docker compose up --buildVerify the REST API:
GET http://localhost:8081/event-management-api/api/events/health
Run the optional protected MCP HTTP container too:
docker compose --profile mcp-http up --buildThe MCP endpoint is http://localhost:8090/mcp and requires the Bearer token configured in .env.
Database URL and username have local defaults. The password is intentionally required and must be supplied outside Git:
DB_URL=jdbc:mysql://localhost:3306/event_management?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
DB_USERNAME=root
DB_PASSWORD=your-local-password
You can override these with environment variables or JVM system properties:
-Devent.db.url=jdbc:mysql://localhost:3306/event_management?useSSL=false&serverTimezone=UTC
-Devent.db.username=root
-Devent.db.password=your_password
Create the database:
CREATE DATABASE event_management;
USE event_management;Then run:
src/main/resources/schema.sql
Optional sample data:
src/main/resources/seed-data.sql
The schema includes primary keys, foreign keys, unique constraints, indexes, soft delete flags, and audit columns. It also includes extension tables for registration status history and audit logs.
./mvnw clean packageOutputs:
target/event-management-api.war
target/event-management-mcp.jar
target/event-management-mcp-http.jar
The project includes an executable Model Context Protocol server built with the official Java MCP SDK and the stdio transport.
It exposes:
32tools for reading and changing event-management data7static read-only resources8parameterized resource templates- CRUD operations for categories, participants, events, and registrations
- Registration, bulk registration, cancellation, check-in, reports, CSV export, status history, and audit logs
MCP resources are read-only. MCP tools can read, create, update, and delete data.
Two MCP transports are available:
event-management-mcp.jar: local stdio transport for Gemini CLI and similar clientsevent-management-mcp-http.jar: Streamable HTTP transport for ChatGPT and remote MCP clients
The stdio server is tested with Gemini CLI, Claude Code, and GitHub Copilot in VS Code.
Build both the REST API and MCP server:
./mvnw clean packageNormally, an MCP client starts the server automatically using:
java -jar target/event-management-mcp.jarRegister the MCP server from the project directory:
gemini mcp add --scope project --transport stdio --timeout 30000 event-management java -- -jar "C:\absolute\path\to\target\event-management-mcp.jar"Verify the connection:
gemini mcp listExpected status:
event-management - Connected
Example prompts:
List all active participants using event-management MCP.
Create a category named Music using event-management MCP.
Show the registration summary for event ID 1.
Start the Streamable HTTP server:
java -jar target/event-management-mcp-http.jarIt listens on:
http://127.0.0.1:8090/mcp
Expose it temporarily over HTTPS:
ngrok http 8090In ChatGPT, enable developer mode, create a custom app/connector, and use:
https://your-ngrok-domain.ngrok.app/mcp
Localhost mode can run without authentication. Set MCP_HTTP_BEARER_TOKEN to protect compatible clients. The server refuses to bind outside localhost without a token unless MCP_HTTP_ALLOW_INSECURE_REMOTE=true is explicitly set for a temporary test-data demo. A production deployment should use stable HTTPS and OAuth 2.1.
claude mcp add --transport stdio --scope local event-management -- java -jar target/event-management-mcp.jar
claude mcp listThe included .vscode/mcp.json configures the local stdio server. In VS Code, open Copilot Chat, select Agent, and enable event-management from the tools picker.
Full MCP setup, client configuration, tools, and resources are documented in:
MCP.md
REST and MCP do not require one shared process:
- Run REST with Maven Cargo when testing HTTP endpoints.
- Gemini CLI or another MCP client starts the MCP JAR when it needs MCP tools.
- Both processes connect directly to the same MySQL database.
With embedded Tomcat 9 through Maven Cargo:
./mvnw package cargo:runThe project is configured for port 8081.
Health check:
GET http://localhost:8081/event-management-api/api/events/health
Response:
{
"status": "API is running"
}MCP health prompt:
Use event-management MCP to check the server health.
Run the automated tests:
./mvnw verifyThe integration tests verify MCP initialization over stdio and Streamable HTTP, Bearer token protection, complete write lifecycles, all read tools and resources, parameterized resources, concurrent tool calls, database row counts, and clean shutdown after the stdio client disconnects. CI runs the same verification against a real MySQL service, enforces at least 60% line coverage, and publishes the JaCoCo report as an artifact.
For a quick REST walkthrough after startup:
./scripts/demo-rest.ps1- Install Apache Tomcat 9.
- Build with
mvn clean package. - Copy
target/event-management-api.warto Tomcatwebapps. - Start Tomcat.
- Use:
http://localhost:8080/event-management-api/api/events/health
Use Tomcat 9, not Tomcat 10, because this project uses javax.ws.rs and Servlet 4.
All handled errors return JSON:
{
"errorCode": "VALIDATION_ERROR",
"message": "capacity must be greater than 0"
}Common status codes:
400 Bad Request: validation errors404 Not Found: missing entity409 Conflict: business conflict, duplicate registration, full event500 Internal Server Error: unexpected failure
Event status:
ACTIVE
CANCELLED
COMPLETED
Registration status:
REGISTERED
CANCELLED
ATTENDED
GET /api/event-categories
GET /api/event-categories/{id}
POST /api/event-categories
PUT /api/event-categories/{id}
DELETE /api/event-categories/{id}
GET /api/events
GET /api/events?status=ACTIVE
GET /api/events?categoryId=1
GET /api/events/{id}
POST /api/events
PUT /api/events/{id}
DELETE /api/events/{id}
GET /api/participants
GET /api/participants/{id}
POST /api/participants
PUT /api/participants/{id}
DELETE /api/participants/{id}
GET /api/registrations
GET /api/registrations/{id}
POST /api/registrations
PUT /api/registrations/{id}
DELETE /api/registrations/{id}
POST /api/events/{id}/register
POST /api/events/{id}/register-bulk
POST /api/events/{id}/cancel-registration
POST /api/registrations/{id}/check-in
GET /api/registrations/{id}/status-history
GET /api/events/{id}/participants
GET /api/events/{id}/participants/export
GET /api/events/available
GET /api/events/search?search=java&page=1&size=10
GET /api/events/{id}/registration-summary
GET /api/audit-logs
- Status history:
registration_status_historystores old and new registration statuses. - Pagination and search:
GET /api/events/searchsupportssearch,status,categoryId,page, andsize. - Audit table:
audit_logrecords registration actions such as register, bulk register, cancel, and check-in. - CSV export:
GET /api/events/{id}/participants/exportreturns participants astext/csv. - Bulk operation:
POST /api/events/{id}/register-bulkregisters multiple participants in one transaction.
Full endpoint-by-endpoint business design is documented in:
BUSINESS_DESIGN.md
POST /api/events/{id}/register
Purpose: registers a participant for an active event if capacity is available.
Request:
{
"participantId": 1
}Validation:
- Event must exist.
- Participant must exist.
- Event status must be
ACTIVE. - Event date must not be in the past.
- Participant must not already be registered.
- Capacity must not be full.
Processing:
- Opens a JDBC transaction.
- Reads event and participant.
- Checks existing registration and capacity.
- Inserts
event_registration. - Commits or rolls back.
Failure cases:
400missing participantId404event or participant missing409duplicate registration or full event
POST /api/registrations/{id}/check-in
Purpose: marks a registered participant as attended.
Validation:
- Registration must exist.
- Cancelled registration cannot be checked in.
- Already attended registration returns conflict.
Processing:
- Opens a transaction.
- Reads registration.
- Updates status to
ATTENDED. - Commits or rolls back.
GET /api/events/{id}/participants
Purpose: lists participants registered for an event.
SQL concept: uses JOIN between event_registration and participant.
GET /api/events/{id}/registration-summary
Purpose: returns capacity, registered, attended, cancelled, and remaining seats.
SQL concept: uses SUM(CASE ...), GROUP BY, and calculated remaining capacity.
The implementation also includes COUNT and AVG in this summary query.
Example response:
{
"eventId": 1,
"eventTitle": "Java Backend Workshop",
"capacity": 40,
"totalRegistrations": 22,
"registered": 12,
"attended": 8,
"cancelled": 2,
"remaining": 28,
"attendanceRate": 0.36
}Use:
src/main/resources/api-examples.http
or import the same URLs manually in Postman or Charles.
- MCP.md: transports, clients, capabilities, and security settings
- BUSINESS_DESIGN.md: endpoint-by-endpoint business rules
- CONTRIBUTING.md: contributor setup and workflow
- SECURITY.md: security policy and deployment boundaries