Skip to content

feat: add Spring Boot MCP Toolbox PostgreSQL example application - #87

Merged
stenalpjolly merged 6 commits into
googleapis:mainfrom
stenalpjolly:stenalpjolly/stenalpjolly_spring-boot-postgres-example
Sep 13, 2026
Merged

feat: add Spring Boot MCP Toolbox PostgreSQL example application#87
stenalpjolly merged 6 commits into
googleapis:mainfrom
stenalpjolly:stenalpjolly/stenalpjolly_spring-boot-postgres-example

Conversation

@stenalpjolly

@stenalpjolly stenalpjolly commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a comprehensive, hardened Spring Boot 3 example application demonstrating seamless integration with the Java MCP Toolbox SDK (com.google.cloud.mcp:mcp-toolbox-sdk-java:1.0.0), connecting to a containerized MCP Toolbox server configured with declarative tools (tools.yaml) backed by a PostgreSQL instance.

Expectation & Implementation

  • Spring Boot 3 Integration: Provides a complete, production-grade example application under demo-applications/spring-boot-postgres/ configuring McpToolboxClient as a Spring-managed bean.
  • Custom Declarative Tools (tools.yaml): Defines domain-specific PostgreSQL tools (get-all-products, get-product-by-id, get-products-by-category, add-product, delete-product-by-id, list_tables, get-table-schema) with explicit required: true parameter constraints, parameterized SQL ($1, $2, ...), and RETURNING clauses.
  • Service Layer (ProductCatalogService): Implements product catalog operations via MCP JSON-RPC tools using async-first non-blocking CompletableFuture methods, defensive .orTimeout(10, TimeUnit.SECONDS) boundaries, .thenApplyAsync thread handoffs for CPU Jackson deserialization, and fail-fast exception propagation.
  • REST Controller (ProductController): Exposes REST endpoints (GET /api/tools, GET /api/products, GET /api/products/{id}, GET /api/products/category/{category}, POST /api/products, DELETE /api/products/{id}). POST returns 201 CREATED with an RFC-compliant Location: /api/products/{id} header and persisted entity body; DELETE returns 204 NO_CONTENT or 404 NOT_FOUND.
  • Database Hardening (schema.sql): Enforces database-level invariants (CHECK (price >= 0), CHECK (stock >= 0)) and provisions an index idx_products_category on the products table.
  • Container Lifecycle Automation: Provides scripts/start-containers.sh and scripts/stop-containers.sh with parameterized credentials (${POSTGRES_USER:-mcpuser}), read-only volume mounting (:ro), and healthcheck readiness probes.
  • Release Please Automation (<x-please>): Configured demo-applications/spring-boot-postgres/pom.xml and README.md with <!-- {x-version-update:mcp-toolbox-sdk-java:current} --> annotations and registered them in release-please-config.json extra-files to automate SDK version updates on subsequent releases.

Test cases

  • Hermetic Unit Tests (ProductCatalogServiceTest): 14 tests mocking McpToolboxClient with Mockito, verifying:
    • Tool list retrieval and sorting
    • Single JSON object deserialization
    • Root JSON array deserialization
    • Single product retrieval by ID via get-product-by-id
    • Products filtering by category via get-products-by-category
    • Argument passing and mapping to add-product tool returning Product
    • Null/empty category omission for add-product
    • Comprehensive input validation (null/blank names, name/category length limits, negative/NaN/infinite prices, negative stock)
    • Parameter verification (table_name) for get-table-schema tool
    • Delete product verification via delete-product-by-id tool and 404 handling
    • Error result handling and exception propagation
    • Malformed JSON payload fast-fail error propagation
  • Live Hermetic Integration Tests (SpringBootPostgresApplicationTests): 15 self-contained, state-independent tests against real Docker containers verifying:
    • Spring ApplicationContext load and client bean injection
    • Declarative tool discovery (7 tools)
    • Querying seeded items via get-all-products
    • Persisting new products via add-product and querying them back
    • Table introspection via get-table-schema with Jackson JsonNode structural assertions
    • Graceful handling of invalid tool arguments
    • REST endpoint GET /api/tools
    • REST endpoint GET /api/products
    • REST endpoint GET /api/products/{id}
    • REST endpoint GET /api/products/category/{category}
    • REST endpoint POST /api/products returning 201 Created, Location header, and body
    • REST endpoint POST /api/products input validation rejection (HTTP 400)
    • REST endpoint POST /api/products null price rejection (HTTP 400)
    • REST endpoint DELETE /api/products/{id} returning 204 No Content and subsequent 404 Not Found
    • Null category persistence as SQL NULL

Acceptance criteria

  • All 14 hermetic unit tests pass with 0 failures
  • All 15 hermetic integration tests pass with 0 failures against running containers
  • All 29 demo tests pass (mvn clean test -Dnet.bytebuddy.experimental=true)
  • Core SDK (src/main/java) untouched with 0 Checkstyle violations and 0 test regressions (130/130 root tests passing)
  • All new files formatted with google-java-format and under 350 lines (below 700-line ceiling)
  • Triple-Specialist Isolated Review Mesh executed with unanimous APPROVED consensus
  • Example managed under Release Please with x-version-update annotations and release-please-config.json inclusion
  • Remote GitHub Actions CI checks (Compile/build, Lint/lint, Security scan, CLA, Conventional Commits) fully passing

Breaking changes

None. This adds a self-contained example application under demo-applications/spring-boot-postgres/ and introduces no breaking changes to the core SDK public APIs.

@stenalpjolly
stenalpjolly requested a review from a team as a code owner September 10, 2026 06:48
Comment thread demo-applications/spring-boot-postgres/pom.xml
Comment thread demo-applications/spring-boot-postgres/scripts/start-containers.sh Outdated
- Define domain-specific PostgreSQL tools in tools.yaml (get-all-products, get-product-by-id, get-products-by-category, add-product, delete-product-by-id, list_tables, get-table-schema)
- Update ProductCatalogService to invoke declarative tools with typed arguments instead of raw SQL strings
- Update scripts/start-containers.sh to mount tools.yaml and execute toolbox with --config
- Update unit tests (ProductCatalogServiceTest) and live integration tests (SpringBootPostgresApplicationTests) to assert declarative tool behavior
- Document declarative tools architecture and configuration in README.md
…tracts

- Add .orTimeout(10, TimeUnit.SECONDS) across all asynchronous MCP client calls
- Offload CPU-bound Jackson deserialization to ForkJoinPool via .thenApplyAsync
- Enforce database integrity with price/stock CHECK constraints and category index in schema.sql
- Mark mandatory parameters with explicit required: true in tools.yaml
- Wire delete-product-by-id end-to-end through service and DELETE /api/products/{id} endpoint
- Refactor POST /api/products to return 201 Created with Location header and entity body
- Make integration tests hermetic without sequential @TestMethodOrder coupling
- Eliminate flaky substring checks with Jackson JsonNode structural assertions
- Parameterize credentials in start-containers.sh with environment variable defaults
- Add release-please version annotation to mcp-toolbox-sdk-java dependency in pom.xml
- Add Maven dependency code snippet with release-please annotation in README.md
- Add demo-applications/spring-boot-postgres files to extra-files in release-please-config.json
- Align product ID type to Long across ProductCatalogService and ProductController
- Robustly parse delete response JSON payload using ObjectMapper and verify deleted ID
- Segregate standalone unit tests and container integration tests with Surefire and Failsafe
- Replace deprecated Docker --link flag with user-defined bridge network in container scripts
- Update README with segregated test commands
@stenalpjolly
stenalpjolly force-pushed the stenalpjolly/stenalpjolly_spring-boot-postgres-example branch from 436897a to a8eb675 Compare September 11, 2026 16:08
@stenalpjolly

Copy link
Copy Markdown
Contributor Author

@anubhav756 Thank you for the review and suggestions!

I have addressed all 4 review comments and rebased the branch onto latest main:

  1. Test Segregation: Configured maven-surefire-plugin to run unit tests (*Test.java) standalone during mvn test without requiring live Docker containers. Renamed the container integration test class to SpringBootPostgresApplicationIT.java running under maven-failsafe-plugin during mvn verify.
  2. Model / Service Type Alignment: Aligned product ID parameters to Long id across ProductCatalogService (getProductById(Long id), deleteProductById(Long id)) and ProductController, ensuring complete type consistency with the Product domain record.
  3. Modern Docker Networking: Replaced the deprecated --link flag with a dedicated user-defined bridge network (mcp-network) with automated lifecycle management across start-containers.sh and stop-containers.sh.
  4. Robust Delete Verification: Updated deleteProductById to parse the response payload via objectMapper.readTree(text) and structurally verify that the returned id matches the deleted product ID.

All local unit tests and build validations are passing cleanly.

Add a unit test asserting deleteProductById returns false when the
delete-product-by-id tool returns an unparseable payload, exercising the
previously uncovered JsonProcessingException branch.
@stenalpjolly
stenalpjolly merged commit 1fc9637 into googleapis:main Sep 13, 2026
11 checks passed
@stenalpjolly
stenalpjolly deleted the stenalpjolly/stenalpjolly_spring-boot-postgres-example branch September 13, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants