A proof-of-concept Model Context Protocol (MCP) server built with Spring Boot and Spring AI that exposes smart day scheduling capabilities as MCP tools — ready to be consumed by Claude, Cursor, or any MCP-compatible AI client.
- What is MCP?
- What This POC Does
- Demo
- Architecture
- MCP Tools Reference
- Tech Stack
- Project Structure
- Getting Started
- Configuration
- Connecting an AI Client
- Running Tests
- Future Scope
- Known Limitations
Model Context Protocol (MCP) is an open standard (published by Anthropic, March 2025) that defines how AI models communicate with external tools and data sources over a structured JSON-RPC interface. Think of it as a USB-C port for AI: instead of every LLM integration needing a custom plugin or function-calling schema, MCP provides one universal protocol.
An MCP server is a lightweight process that:
- Declares a set of tools (functions the AI can call).
- Accepts tool-call requests from a client (e.g. Claude Desktop, Cursor, a custom agent).
- Executes the tool logic and streams results back.
Transport options include stdio (local process), HTTP with SSE, and the newer Streamable HTTP (stateless POST) — this project uses Streamable HTTP over POST /mcp.
This server demonstrates how to wrap a real business domain — calendar & meeting scheduling — behind an MCP interface so an AI assistant can manage it via natural language.
A single "calendar owner" account is seeded on startup. An AI client connected to this server can:
- Create events with automatic double-booking prevention
- Check availability for any time slot
- Reschedule meetings (conflict-checked against existing events)
- Cancel meetings
- Add participants (external attendees) to existing events
- Search events by title with pagination
- Register users and list them
Example interaction via Claude Desktop:
"Schedule a 1-hour design review for tomorrow at 3 PM and invite sara@design.com"
→ Claude callscheck_availability, thencreate_event, thenadd_participantautonomously.
The screenshots below show the SmartScheduler MCP server in action, connected to Claude Desktop.
┌─────────────────────────────────────────────────────┐
│ AI Client │
│ (Claude Desktop / Cursor / custom agent) │
└────────────────────┬────────────────────────────────┘
│ POST /mcp (Streamable HTTP)
│ Authorization: Bearer <api-key>
┌────────────────────▼────────────────────────────────┐
│ Spring Boot MCP Server │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Spring AI MCP Layer │ │
│ │ (tool discovery, JSON-RPC dispatch, │ │
│ │ schema generation from @Tool annotations) │ │
│ └──────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────────────────────┐ │
│ │ SchedulingTools (MCP tool entrypoints) │ │
│ │ UserTools │ │
│ └──────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────────────────────┐ │
│ │ SchedulingService (business logic) │ │
│ └──────────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────────────────────┐ │
│ │ JPA Repositories → H2 (dev) / PostgreSQL │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Request flow:
- AI client sends a JSON-RPC 2.0 message to
POST /mcp. ApiKeyFiltervalidates theAuthorization: Bearer <key>header.- Spring AI's MCP layer routes the call to the appropriate
@Tool-annotated method. SchedulingTools/UserToolsparse and validate parameters, then delegate toSchedulingService.SchedulingServiceenforces business rules (conflict detection, ownership) and persists via JPA.- The result is serialized to JSON and streamed back to the client.
All tools are exposed at POST /mcp. Parameters are passed as a JSON object in the arguments field of the JSON-RPC request. ISO-8601 datetimes use the format yyyy-MM-ddTHH:mm:ss (e.g. 2025-06-01T10:00:00).
| Tool | Description | Required Params | Optional Params |
|---|---|---|---|
create_event |
Creates a calendar event. Prevents double-booking automatically. | title, startTime, endTime |
description, timezone (IANA, default UTC), participantEmails |
get_events |
Returns all events ordered by start time within a date range. | startDate, endDate |
— |
check_availability |
Returns available: true/false plus any conflicting events for the owner. |
startTime, endTime |
— |
reschedule_meeting |
Moves an event to a new time slot. Checks for conflicts at the destination. | eventId, newStartTime, newEndTime |
— |
cancel_meeting |
Deletes an event and removes all its participants. | eventId |
— |
add_participant |
Adds an external attendee to an existing event. Idempotent (returns ALREADY_PRESENT status if duplicate). |
eventId, name, email |
— |
search_events |
Paginated case-insensitive title search. | title |
page (0-based, default 0), size (1–100, default 10) |
| Tool | Description | Required Params |
|---|---|---|
create_user |
Registers a new user. Email must be unique. | name, email |
get_users |
Returns all registered users. | — |
{
"available": false,
"startTime": "2025-06-01T10:00:00",
"endTime": "2025-06-01T11:00:00",
"conflictingEvents": [
{
"id": 3,
"title": "Client Call — Acme Corp",
"startTime": "2025-06-01T10:30:00",
"endTime": "2025-06-01T11:30:00",
"timezone": "UTC",
"participants": []
}
]
}| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.4.3 |
| MCP | Spring AI 1.1.3 (spring-ai-starter-mcp-server-webmvc) |
| MCP Transport | Streamable HTTP (POST /mcp) |
| Persistence | Spring Data JPA + Hibernate |
| Dev / Test DB | H2 in-memory |
| Production DB | PostgreSQL |
| Validation | Jakarta Bean Validation |
| Boilerplate reduction | Lombok |
| Build | Maven (Maven Wrapper included) |
| Tests | JUnit 5 + Spring Boot Test (integration, H2) |
src/
├── main/java/com/mcp/smartScheduler/
│ ├── SmartSchedulerMcpServer.java # Spring Boot entry point
│ ├── config/
│ │ ├── ApiKeyFilter.java # Bearer-token auth filter
│ │ ├── DataInitializer.java # Seeds owner + sample events on startup
│ │ ├── McpToolConfig.java # Registers tool beans with Spring AI
│ │ └── OwnerProperties.java # Binds app.owner.* config
│ ├── dto/ # Request / response DTOs
│ │ ├── EventRequest.java
│ │ ├── EventResponse.java
│ │ ├── UserRequest.java
│ │ ├── AddParticipantsResponse.java
│ │ ├── JsonRpcRequest.java
│ │ └── JsonRpcResponse.java
│ ├── entity/
│ │ ├── User.java # Calendar user / owner
│ │ ├── Event.java # Calendar event
│ │ └── Participant.java # External attendee on an event
│ ├── exception/
│ │ ├── ConflictException.java # 409 – double-booking / duplicate
│ │ ├── ResourceNotFoundException.java # 404 – event/user not found
│ │ ├── ValidationException.java # 400 – bad input
│ │ └── GlobalExceptionHandler.java # Maps exceptions → JSON errors
│ ├── repository/
│ │ ├── EventRepository.java # Overlap-detection JPQL queries
│ │ ├── UserRepository.java
│ │ └── ParticipantRepository.java
│ ├── service/
│ │ └── SchedulingService.java # All business logic lives here
│ └── tools/
│ ├── SchedulingTools.java # @Tool methods for scheduling
│ └── UserTools.java # @Tool methods for user management
├── main/resources/
│ └── application.yaml
└── test/
├── java/com/mcp/smartScheduler/service/
│ └── SchedulingServiceTest.java # Integration tests (H2, transactional rollback)
└── resources/
└── application-test.yml
- Java 21+ (
java -version) - Maven 3.9+ or use the included
./mvnw - No database setup needed for local dev — H2 runs in-memory
git clone https://github.com/your-username/smart-scheduler-mcp-server.git
cd smart-scheduler-mcp-server
./mvnw clean package -DskipTestsexport OWNER_EMAIL=you@example.com # defaults to owner@example.com
export MCP_API_KEY=your-secret-key # defaults to negi-secret-key-mcp-server./mvnw spring-boot:runOr:
java -jar target/smart-scheduler-*.jarThe server starts on http://localhost:8080.
On startup, DataInitializer seeds:
- 1 owner account (email from
OWNER_EMAIL) - 5 sample events (Morning Standup, Product Review, Client Call, Design Review, Sprint Planning)
- 5 sample participants
curl -s -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer negi-secret-key-mcp-server" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'You should receive a JSON-RPC response listing all 9 registered tools.
Browse to http://localhost:8080/h2-console
JDBC URL: jdbc:h2:mem:calendardb
Username: sa | Password: (empty)
All configuration is in src/main/resources/application.yaml. The two environment-variable-driven properties you will need to set in production:
| Env Var | yaml key | Default | Purpose |
|---|---|---|---|
OWNER_EMAIL |
app.owner.email |
owner@example.com |
Email of the calendar owner account |
MCP_API_KEY |
app.api-key |
negi-secret-key-mcp-server |
Bearer token required on every request |
Replace the datasource and jpa sections in application.yaml (or use a application-prod.yaml profile):
spring:
datasource:
url: jdbc:postgresql://localhost:5432/smartscheduler
username: ${DB_USER}
password: ${DB_PASSWORD}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate # use Flyway / Liquibase for migrations in prod
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialectAdd the following to your Claude Desktop MCP configuration (claude_desktop_config.json):
{
"mcpServers": {
"smart-scheduler": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer negi-secret-key-mcp-server"
}
}
}
}Restart Claude Desktop. The scheduling tools will appear in the tool list and Claude will call them automatically in response to scheduling-related prompts.
claude mcp add smart-scheduler \
--transport http \
--url http://localhost:8080/mcp \
--header "Authorization: Bearer negi-secret-key-mcp-server"# Create an event
curl -s -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer negi-secret-key-mcp-server" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "create_event",
"arguments": {
"title": "Q3 Planning",
"startTime": "2025-09-01T10:00:00",
"endTime": "2025-09-01T11:30:00",
"timezone": "Asia/Kolkata",
"participantEmails": ["alice@team.com", "bob@team.com"]
}
}
}'./mvnw testTests use the test Spring profile which targets H2 (application-test.yml). Each test class runs in a transaction that is rolled back after the test, so tests are fully isolated.
Coverage includes:
- Duplicate email rejection on user creation
- Happy-path event creation with participants
- Invalid time range (end before start)
- Double-booking conflict detection
- Date-range filtering for
get_events - Free/busy
check_availability - Successful
reschedule_meeting
The current implementation is intentionally scoped as a POC. Below is a prioritized list of what a production version would need:
- Recurring events — daily / weekly / monthly recurrence rules (RFC 5545 RRULE)
- Multi-owner support — right now a single owner is seeded; extend to per-user calendars with auth
- Attendee availability —
check_availabilitycurrently checks only the owner; extend to check all invitees - Timezone-aware conflict detection — conflicts are currently compared in raw
LocalDateTime; convert to UTC before overlap checks - Event reminders / notifications — email or webhook notifications before events
- MCP Resources — expose the calendar as a readable resource (
calendar://events/today) so the AI can subscribe to it - MCP Prompts — pre-built prompt templates (e.g. "find the next available 1-hour slot this week") for guided interactions
- Streaming tool responses — for long-running queries, stream partial results using SSE
- Tool result caching — cache
get_eventsandcheck_availabilityresults with a short TTL
- Google Calendar / Outlook sync — bidirectional sync via Google Calendar API or Microsoft Graph
- Slack / Teams notifications — notify participants when an event is created, updated, or cancelled
- iCal export — expose events as
.icsfiles for import into standard calendar apps
- Database migrations — replace
ddl-auto: create-dropwith Flyway versioned migrations - Authentication — replace the single static API key with JWT / OAuth 2.0 (Spring Security)
- Rate limiting — per-client request throttling on the
/mcpendpoint - Observability — Micrometer metrics, distributed tracing (OpenTelemetry), structured JSON logging
- Docker / Kubernetes —
Dockerfile,docker-compose.ymlfor local dev, Helm chart for K8s - CI/CD — GitHub Actions pipeline (build → test → Docker image → push)
- API versioning — versioned MCP tool schemas to avoid breaking clients on updates
| Limitation | Impact |
|---|---|
| Single calendar owner | All events belong to one seeded owner; no multi-user scheduling |
| H2 in-memory DB | All data is lost on restart; PostgreSQL config is provided but not default |
| No timezone normalization | startTime/endTime are stored as-is; two events in different timezones can silently overlap |
| Static API key | No key rotation; any request with the key has full access |
No pagination on get_events |
Large date ranges return all events in a single response |
UserTools not registered |
UserTools class exists but is not wired into McpToolConfig; only SchedulingTools is active |
This project is licensed under the Apache License 2.0.