{"id":35151050,"url":"https://github.com/fejesa/quarkus-ai-mcp","last_synced_at":"2026-04-14T03:31:44.130Z","repository":{"id":318875269,"uuid":"1065786541","full_name":"fejesa/quarkus-ai-mcp","owner":"fejesa","description":"AI powered HTML Message Template Editor","archived":false,"fork":false,"pushed_at":"2026-02-01T14:22:07.000Z","size":1999,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-01T23:40:13.943Z","etag":null,"topics":["ai","angular","java","langchain4j","llm","mcp","nodejs","ollama","orval","primeng","quarkus","quill","quilljs","rest"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fejesa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-28T12:31:59.000Z","updated_at":"2026-02-01T14:22:10.000Z","dependencies_parsed_at":"2025-12-30T01:05:00.866Z","dependency_job_id":null,"html_url":"https://github.com/fejesa/quarkus-ai-mcp","commit_stats":null,"previous_names":["fejesa/quarkus-ai-mcp"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fejesa/quarkus-ai-mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fejesa%2Fquarkus-ai-mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fejesa%2Fquarkus-ai-mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fejesa%2Fquarkus-ai-mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fejesa%2Fquarkus-ai-mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fejesa","download_url":"https://codeload.github.com/fejesa/quarkus-ai-mcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fejesa%2Fquarkus-ai-mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31781292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","angular","java","langchain4j","llm","mcp","nodejs","ollama","orval","primeng","quarkus","quill","quilljs","rest"],"created_at":"2025-12-28T15:38:45.925Z","updated_at":"2026-04-14T03:31:44.124Z","avatar_url":"https://github.com/fejesa.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# How I Built an AI-Powered Template Generator with Quarkus, LangChain4j, and Ollama\n\n## Introduction\nBusinesses have customers, right? And many of them provide online services — shopping, banking, telecom, insurance, and so on. But communicating with customers is never easy, especially in the digital era. Which channels are the right ones for a given business? Instant messaging, voice calls, or classic text-based messages? What should the tone and style be — formal, friendly, or conversational? And most importantly: **what exactly does the business want to communicate?**\n\nWe can agree that most online services rely heavily on **text-based messaging** when communicating with their customers. Sometimes the messages are sent directly via email, other times through a **dedicated in-app messaging system**, where customers have a personal inbox. Typically, these messages are **HTML formatted** — for good reason: better readability, structured layout, support for images and hyperlinks, and a more professional look.\n\nEvery business has its own communication style — formal or informal, personal or official — and all customer messages need to follow that same consistent structure and tone. This helps customers easily recognize and understand communications coming from the business.\n\nNow, imagine a company that has **dozens or even hundreds of customer notification types.**\nTake a **Financial Services** example — customers might receive messages like:\n- Account opening confirmation\n- Loan application received\n- Loan approval notification\n- Overdue payment notice\n- Account statement available\n- Security alert (e.g., suspicious activity detected)\n\nEach message type is typically based on a **template**, and each template contains **placeholders** (like `[[customer_id]]`, `[[branch_name]]`, `[[contact_phone]]`, etc.).  \nWhenever an event occurs that triggers a notification, the messaging system fills those placeholders with real customer data and sends the message.\n\nSo far, so good. But this raises a few questions:\n- How can a business ensure that all message types share the same format and style, especially when there are hundreds of templates?\n- How many placeholders should exist — and which ones should be used in each template?\n\nUhh… it’s cumbersome and error-prone. It requires **a lot of manual work** and coordination between teams.\n\nAnd this is exactly where **AI** can step in — not necessarily to create the customer messages themselves, but to **standardize and accelerate the process of message template generation!**\n\n## Use Case\nLet’s imagine a **messaging platform for a bank** that communicates with its customers. The standard format is **HTML**, and all message types must follow the same tone, structure, and styling.\n\nTemplates contain reusable placeholders such as customer name, ID, branch name, contact details, and so on.  \nWhen a new business case arises — for example, *“A customer wants to request an additional Mastercard”* — an admin just needs to describe the use case in natural language, like:\n\n\u003e “I need a template that describes the process by which a customer can request an additional Mastercard from the bank.”\n\nOptionally, the admin can provide a draft version of the template or simply leave it empty — and the AI takes care of the rest. The AI analyzes existing templates, retrieves placeholder definitions, and generates a **new, well-structured, stylistically consistent HTML message** that fits perfectly with the other templates.\n\n![HTML Template Editor](./docs/html-template-editor.gif)\n\n## Technology Choices\nFor this proof of concept, I chose a stack that combines **modern web development** with **AI integration** — and makes experimentation genuinely enjoyable:\n- **Angular with PrimeNG** – A perfect fit for building rich, interactive, and scalable single-page applications (SPAs). [PrimeNG](https://primeng.org/) provides a great set of ready-to-use UI components.\n- **[LangChain4j](https://docs.langchain4j.dev/)** – An open, composable Java framework that defines a standard interface for LLMs, tools, and data sources. It makes building AI-driven workflows in Java straightforward.\n- **[Ollama](https://ollama.com/)** – A local LLM runtime that’s simple to use and well-integrated. For this experiment, I used the `gpt-oss:20b` model, which offered a great balance between performance and quality.\n- **[Quarkus](https://quarkus.io/)** – A cloud-native Java framework that integrates seamlessly with LangChain4j. Registering an AI service is annotation-based, and exposing tools is as easy as annotating your code. And honestly — developing with Quarkus is just *fun*.\n- **[Quinoa](https://github.com/quarkiverse/quarkus-quinoa)** – A Quarkus extension that simplifies building and serving Angular (or other SPA) applications directly from your backend. It eliminates much of the setup hassle.\n\n## Architecture\nAngular applications typically run on a Node.js server. However, with **Quarkus’s Quinoa extension**, Quarkus automatically starts the Node.js server when the application runs, seamlessly serving the Angular app. Since Angular operates on the client side, backend communication happens via **REST APIs**. Quarkus automatically generates an [OpenAPI](https://www.openapis.org/) schema, which I used with [Orval](https://orval.dev/) to generate RESTful clients as Angular services (though other tools could be used as well).\n\nFor the editor, I chose [Quill](https://quilljs.com/), a free, open-source, WYSIWYG rich text editor with a modular, customizable architecture. PrimeNG conveniently includes an integrated Quill-based editor, which made the frontend implementation straightforward.\n\nMessage templates are stored as files (for simplicity), while **template metadata and placeholders** (name, description, etc.) are stored in a **PostgreSQL database**.  \nThese are made accessible to the AI model via the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) — a standard that allows language models to call external tools (such as database queries, APIs, or computations) during reasoning.\n\n![Architecture Diagram](./docs/architecture.png)\n\nHere’s how the flow works:\n\n1. The user requests template generation from the frontend.\n2. A REST call is made to the registered AI service in Quarkus.\n3. The AI service invokes the configured MCP tools to fetch context — existing templates and placeholder definitions.\n4. The model analyzes this information and generates or updates the HTML template accordingly.\n5. The generated template is returned and rendered in the frontend’s editor.\n6. If needed, the user can manually fix invalid tags or fine-tune the content.\n\nThe final output always follows the **same structure, style, and placeholder rules** as other templates — ensuring consistency across the entire messaging platform.\n\n## Installation\n### Prerequisites\n- JDK 21 or higher\n- Maven 3.5+\n- Docker\n- [Ollama](https://ollama.com) installed\n\n### Steps\n1. Clone the repository:\n   ```sh\n   git clone https://github.com/fejesa/quarkus-ai-mcp.git\n    ```\n2. Build the project:\n   ```sh\n   mvn clean install\n   ```\n3. Run the application in development mode:\n   ```sh\n    mvn quarkus:dev\n    ```\n**Note**: No need to run the PostgreSQL manually — Quarkus Dev mode will automatically start them for you. If the LLM models are not available locally, Quarkus will download them automatically, but this may take some time.\n\n### Usage\nOnce the application is running, open your browser and navigate to `http://localhost:4200`. You should see the Angular frontend.\nType a description of the template you want to generate, and click the \"Generate\" button. The AI will create a new HTML template based on your input.\n\n### Configuration\nThe application can be configured via `application.properties`. The following properties are available:\n```properties\nquarkus.http.port = 8080\n\n# PostgreSQL database automatically created by Dev Services with the following settings\nquarkus.devservices.enabled = true\nquarkus.datasource.devservices.port = 5432\nquarkus.datasource.devservices.db-name = quarkus\nquarkus.datasource.devservices.username = quarkus\nquarkus.datasource.devservices.password = quarkus\n\nquarkus.hibernate-orm.schema-management.strategy = drop-and-create\nquarkus.hibernate-orm.log.sql = true\n\n# We use HTTP transport for client-server communication in MCP context\nquarkus.langchain4j.mcp.template-generator.transport-type = http\n# The URL of the SSE endpoint. This only applies to MCP clients using the HTTP transport.\nquarkus.langchain4j.mcp.template-generator.url = http://localhost:8080/mcp/sse\n\n# Enable logging of client requests and responses to/from LLM APIs\nquarkus.langchain4j.log-requests = true\nquarkus.langchain4j.log-responses = true\n# If set to true then JSON messages received/sent are logged.\nquarkus.mcp.server.traffic-logging.enabled = true\n# The number of characters of a text message which will be logged if traffic logging is enabled, default is 200 characters.\nquarkus.mcp.server.traffic-logging.text-limit = 10000\n\n# The chat model to use. Set gpt-oss is the default chat model.\nquarkus.langchain4j.ollama.chat-model.model-id = gpt-oss\n# Global timeout for requests to LLM APIs\nquarkus.langchain4j.timeout = 60s\n# The temperature to use for the chat model. Temperature is a value between 0 and 1, where lower values make the model more deterministic and higher values make it more creative.\nquarkus.langchain4j.temperature = 0.2\n\n# Defines the application path that serves as the base URI for all JAX-RS resource URIs provided by @Path annotations\nquarkus.rest.path = /\n# The name of the generated OpenAPI file; defaults are openapi.json and openapi.yaml. The schema is automatically generated by Quarkus when the application is started.\nquarkus.smallrye-openapi.store-schema-file-name = message-template\n# The title of the generated OpenAPI schema document. This title is used in the generated OpenAPI service.\nquarkus.smallrye-openapi.info-title = MessageTemplateAPI\n# The generated OpenAPI schema documents will be stored here on build. We use this document to generate the Angular client using Oval lib\nquarkus.smallrye-openapi.store-schema-directory = src/main/webui/api\n\n# Enable Package Manager Installation. This will override \"package-manager\" config.\nquarkus.quinoa.package-manager-install = true\n# The NodeJS Version to install locally to the project. Required when package-manager-install is enabled.\nquarkus.quinoa.package-manager-install.node-version = 22.12.0\n# The NPM version to install and use. By default, the version is provided by NodeJS.\nquarkus.quinoa.package-manager-install.npm-version = 10.9.0\n\n# To enable the UI live-coding dev server, set a start script and set the port in the app config. Quinoa will transparently proxy relevant requests to the given port\nquarkus.quinoa.dev-server-port = 4200\n# Single Page application routing; when enabled, to allow SPA routing, all relevant requests will be internally re-routed to index.html, this way the javascript can take care of the route inside the web-application.\nquarkus.quinoa.enable-spa-routing = true\n\n# Enable the CORS filter. This will allow the Angular client to access the Quarkus API.\nquarkus.http.cors.enabled = true\n# The allowed origins for CORS requests. This is the URL of the Angular client.\nquarkus.http.cors.origins = http://localhost:4200\n# HTTP headers allowed for CORS Comma separated list of valid headers. ex: X-Custom,Content-Disposition The filter allows any header if this is not set. default: returns any requested header as valid\nquarkus.http.cors.headers = accept, authorization, content-type, x-requested-with\n# HTTP methods allowed for CORS.\nquarkus.http.cors.methods = POST,GET,PUT,DELETE\n\n# The location of the template files; can be relative or absolute path.\napp.resources.location = ./templates\n\n# LangFuse OpenTelemetry settings; set to false to disable\nquarkus.otel.enabled = true\nquarkus.otel.metrics.enabled = true\n# OpenTelemetry defines the encoding of telemetry data and the protocol used to exchange data between the client and the server. Default is grpc.\nquarkus.otel.exporter.otlp.traces.protocol = http/protobuf\n# LangFuse OpenTelemetry endpoint and authorization header\nquarkus.otel.exporter.otlp.traces.headers = Authorization=Basic ***\nquarkus.otel.exporter.otlp.traces.endpoint = http://localhost:3000/api/public/otel\n# quarkus.otel.exporter.otlp.logs.protocol = http/protobuf\nquarkus.langchain4j.tracing.include-prompt = true\nquarkus.langchain4j.tracing.include-completion = true\nquarkus.langchain4j.tracing.include-tool-arguments=true\nquarkus.langchain4j.tracing.include-tool-result=true\n\n# Grafana and OpenTelemetry ports for LGTM observability; by default testcontainers exposes these on random ports.\nquarkus.observability.lgtm.grafana-port = 3001\nquarkus.observability.lgtm.otel-grpc-port = 5317\nquarkus.observability.lgtm.otel-http-port = 5318\n# Grafana LGTM metrics\nquarkus.otel.exporter.otlp.metrics.endpoint = http://localhost:5318\nquarkus.otel.exporter.otlp.metrics.protocol = http/protobuf\n#Grafana LGTM logs\nquarkus.otel.exporter.otlp.logs.endpoint = http://localhost:5318\nquarkus.otel.exporter.otlp.logs.protocol = http/protobuf\n```\n\n# Build a Docker image\nTo build the docker image, execute the following command\n```\nmvn clean install -DskipTests -Dquarkus.container-image.build=true\n```\n\n## Observability, Monitoring \u0026 Tracing\n- Observability is essential — it gives us visibility into what happens under the hood when our application runs, enabling reliable debugging, performance tuning, and root-cause analysis.\n- We rely on the “three pillars” of observability: **metrics** (system performance and health), **logs** (event history and context), and **traces** (detailed journeys of requests and LLM interactions).\n    - Metrics enable us to monitor throughput, latency, error rates, resource consumption — and alert on anomalies.\n    - Logs provide detailed context: timestamps, errors, stack traces, user actions, state changes — essential for forensic debugging and audit trails.\n    - Traces reconstruct the full flow of a request (or an LLM call), showing each step, latency, dependencies, and where things can go wrong.\n- For our LLM-based application, observability is even more critical: LLMs are non-deterministic, involve multiple subsystems (embedding, retrieval, generation, external APIs), and can fail or degrade silently.\n- We use **[Langfuse](https://github.com/langfuse/langfuse)** for tracing LLM interactions: it captures prompts, embeddings, tool calls, completions, latencies, costs and more — giving us full visibility into the LLM pipeline.\n    - Langfuse is open-source, self-hostable, and framework-agnostic — which makes it a robust, vendor-independent observability solution.\n    - With Langfuse we can debug complex agent flows, trace unexpected outputs or failures to exact inputs, optimize prompt engineering, and monitor performance \u0026 cost per call.\n    - Langfuse also supports evaluation workflows: tracking output quality over time, annotating traces, detecting regressions or drift — which helps maintain model reliability in production.\n- Combining standard application metrics/logs (via [Grafana](https://grafana.com/) / Prometheus / Loki) with detailed LLM tracing (via Langfuse) gives us a **holistic observability stack**: infrastructure, app, and LLM.\n- This observability stack makes our RAG application more maintainable, debuggable, auditable, and performant — especially important when serving real users in production.\n\nTo run Langfuse locally with Docker, you can use the following the instructions [here](https://github.com/langfuse/langfuse).\n\n### Testing\nTo run the tests, use the following command:\n```sh\nmvn clean verify\n```\n\n**Note:** It takes some time for the model to respond, and it can also happen that the model needs to be downloaded first. So please be patient.\n\nIf you want to check the traces in Langfuse, make sure you have it running locally and configured properly in `application.properties`. You can then access the Langfuse UI at `http://localhost:3000`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffejesa%2Fquarkus-ai-mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffejesa%2Fquarkus-ai-mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffejesa%2Fquarkus-ai-mcp/lists"}