All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
VectorStoreDocumentService → OfflineEtlPipelineService.sendEmail, addToCalendar, and showLocation are review-then-act tools: the model fills the arguments and the reply renders an interactive card — an email draft, an .ics calendar event, or an embedded map — with a confirm button the user clicks; the tool itself never sends, schedules, or navigates, and the assistant is instructed to tell the user to click rather than claim the action is done. A ChatClientAction registry (extendable with a single Spring bean) plus a JS registerActionCard card registry generalize the pattern, returnDirect is inferred from a code marker so an action’s card becomes its own reply, and a new Personal assistant preset drives them. The bundled tool catalog grows to 88 (feat(chat,tools)).ToolSearchToolCallingAdvisor) in which the model searches for the capability it needs through a toolSearchTool instead of receiving every tool definition up front, keeping the context small for large tool sets. A persistent, content-addressed PersistentToolIndex embeds each tool once and reuses it across conversations — warmed at boot by ToolIndexWarmup and persisted under ~/spring-ai-playground/tool-index/. The chat settings drawer gains a mutually-exclusive Dynamic tool discovery ⟷ Manual built-in tool selection toggle gated on a minimum tool count, and the Self-equipping agent preset ships in dynamic mode (feat(chat)).<app-home>/workspace/<conversationId>/), bridged through the tool-call identity so writes are isolated per chat and cleaned up with the conversation. A new listAllowedDirectories tool reports the readable roots and the active working directory, SafeFs is reworked around a home-read / workspace-write FsScope, and the workspace base unifies under the single springAiPlaygroundHomeDir app-home bean (feat(tools)).LocalFileBrowserService, with the web and desktop seams kept separate (feat(chat)).feat(chat,electron)).McpRiskEventRingBuffer (feat(observability)).[tool= server=] noise on lines that aren’t tool calls (refactor(observability)).fix(chat))....; the full list renders and the summary now ellipsis-clips at the panel edge (its vaadin-details-summary content fills the width) instead of cutting short and leaving empty space (fix(chat)).docs(chat,tools)).docs).docs).build(pom)).ChatRequestOptionsFactory), a per-request reasoning / think toggle, reusable system-prompt presets (ChatSystemPromptPresetCatalog / ChatSystemPromptPresetService), and a Prompt Library of example prompts and `` templates rendered by ChatSystemPromptTemplateRenderer. Message bubbles gain a hover toolbar (copy, show raw, collapse, read aloud, cite, Export) and an always-visible meta row — elapsed time, token in/out, model, and tool / RAG chips. Rich rendering adds code syntax highlighting, KaTeX math, and Mermaid diagrams, and a shared categorized tool selector scopes which tools a conversation may call (feat(chat,tools)).LlmWindowChatMemory feeds only the last N messages to the advisor, while the full store (safety cap 2,000) keeps the entire conversation for the screen and disk. Tunable globally (memory-max-messages / history-max-messages) and per chat via a memory-window field in the chat settings drawer (feat(chat)).AppleSiliconMlxActivator turns on an mlx Spring profile on Apple Silicon and auto-selects MLX-quantized model tags for markedly faster local inference; the Electron launcher maps MLX models accordingly (feat(electron,config)).McpRiskSignalSink taps the MCP risk pipeline (server / tool risk scoring, risk-floor overrides, hash-ledger mismatches, composition lifecycle, and tool-poisoning hits) and a default McpRiskSignalLogger (@Component, replacing the prior NOOP sink) fans each event out to structured logs, Micrometer metrics, and a bounded McpRiskEventRingBuffer. A new Safety tab in the Observability dashboards surfaces the live risk-event feed, making the integrity verifier, poisoning scanner, and risk calculators observable at runtime (feat(mcp)).3.5 → 4.1, Spring AI 1.1 → 2.0, Vaadin 24 → 25, Jackson 2 → 3 (tools.jackson), Spring Security 7, and the desktop Electron shell 28 → 42 (Chromium 136, required for the Vaadin 25 webview to render). The codebase is adapted to the changed APIs: builder-based immutable chat options, the ToolCallingAdvisor / .tools() tool-calling surface, Jackson 3’s unchecked JacksonException / ValueDeserializer / immutable-mapper model (with corrupt-file boot resilience restored), Vaadin 25 navigation access control and explicit @StyleSheet(Lumo.STYLESHEET) theming, and the openai (was openai-sdk) model starter. The vendored OllamaChatModel patch is dropped (build(deps)).convertCurrency (now key-gated) and getCountryInfo (allowlist redirect) work again, and oversized tool results are capped and clipped before they overflow the model context (fix(tools)).fix(chat)).layertools jarmode to tools ... extract --layers --launcher, which Boot 4 dropped (build(deps)).chmod 600 on the persisted secrets file (fix(electron)).fix(chat,tools)).fix(chat,tools)).fix(chat,tools)).docs/features/agentic-chat/ pages (guide, Prompt Library, prompt presets / templates) and docs/context-engineering-architecture.md, captured against the reworked chat.openai-sdk → openai text and screenshots, refreshed observability captures, mobile-overflow and stale-endpoint fixes, and “composed tools” naming (fix(docs)).build(deps) / build(electron)).build(pom)).McpRiskFactors / McpTrustSignal signals: transport, authentication (McpAuthClassifier / McpAuthMode), catalog trust tier, and a per-server documentation-completeness dimension. McpServerInfoService surfaces a per-server risk chip on the MCP Server Info pane and per-tool chips in the Inspector, a connection-time tool-poisoning scan flags description / parameter injection, and McpCompositionRiskAggregator rolls member-tool risk up to a composed server. Risk levels ride the observability spans through McpToolObservationFilter (feat(mcp))./mcp server. McpExposedToolService + McpCompositionService + McpCompositionToolCallbackProvider register WrappedExternalToolCallbacks that carry a per-tool alias / description override, risk level, HITL flag, logging, and secret masking; an exposure mode (built-in / composed / both) and a max-risk-to-expose cap gate selection. Re-exposed tools become callable from Agentic Chat and any external /mcp client. Persisted via McpCompositionPersistenceService (feat(mcp)).HumanQuestion / HumanQuestionHandler via HitlToolCallAdvisor) for Agentic Chat and McpServerHitlToolGate (MCP elicitation) for external clients, wired through McpToolCallingManager, ChatService, and ChatContentView. The gate is set per tool in Tool Studio (Sandbox & Capabilities) or per re-exposed tool in the Expose Tools drawer. Also adds session-local chat tool selection so a conversation can scope which tools it may call (feat(mcp)).CanonicalHasher + DefaultIntegrityVerifier name-pin the 86 bundled default tools against a golden resources/integrity/default-integrity.json manifest (86 tool hashes + a manifest version). A tool that claims a reserved default name must match the canonical classpath hash or it is rejected at registration in ToolSpecService; customizing a default forks it to a new identity rather than mutating the pinned one. A DefaultIntegrityManifestTest tripwire fails the build if the shipped catalog drifts from the manifest (feat(mcp)).McpToolHashLedger records a trust-on-first-use fingerprint for every exposed tool; when an upstream tool’s hash changes (rug-pull), McpCompositionToolCallbackProvider surfaces a re-review prompt and records the operator’s re-approval against the new hash on the ledger (feat(mcp)).spring.ai.playground.built-in-mcp-server.{name,description,exposure-mode} (env SPRING_AI_PLAYGROUND_MCP_NAME / _DESCRIPTION / _EXPOSURE_MODE, exposure default both) set the built-in server’s published name, description, and default exposure at startup, read by McpServerInfoService and reflected in the MCP Server view, Observability tabs, and Tool Studio (feat(mcp)).DeviceIdProvider derives a stable per-device user id that UserIdentityService + MdcIdentityFilter propagate into the MDC (user=) across web requests, the persistence executor, and MCP tool-call log lines, so multi-surface activity correlates to one local identity (feat(identity)).ChatMessage built on Vaadin’s core vaadin-message plus the official Markdown component with server-side content, dropping the viritin (and an interim flexmark) dependency. Avatar color comes from a server-side userColorIndex (USER / ASSISTANT presets) instead of a client-only executeJs, so it survives component re-attach (fix(chat)).fix(mcp)).docsAdequate signal per server (feeding the risk score), and the Puppeteer entry’s docs link is corrected to the archived-servers location (feat(mcp)).ChatMessage re-sends content and color index on re-attach (fix(chat)).fix(chat)).fix(mcp))./mcp on startup instead of silently dropping to none until re-confirmed; the startup reconcile also filters drafts, so an un-passed tool can never reach /mcp through a stale exposed id, and the chat model-settings Apply no longer throws when its drawer was never opened (fix(mcp)).fix(mcp)).fix(qa)).fix(analytics)).docs/hitl-architecture.md (two-gate approval model and proxied call path), docs/features/human-in-the-loop.md, and docs/tutorials/11-human-approval.md.docs/mcp-server-safety.md (L0–L5 connection risk, tool-poisoning scan, fingerprint ledger, composition shadowing rules, HITL mitigation), docs/features/mcp-server/proxy.md, docs/getting-started/configuration.md, and docs/tutorials/10-proxy-external-tool.md, with screenshots and nav.docs(site)).docs).build(pom): bump to 0.2.0-M8, spring-ai 1.1.7.ObservabilityCollector consumes ChatClientObservation, gen_ai.client.operation, MCP tool callbacks, sandbox metrics, and JVM samplers with MDC propagation (conv, msg, traceId, spanId); aggregates into an in-memory ring buffer (2,000 traces, configurable via spring.ai.playground.observability.*) plus dated disk persistence with 30-day retention (~/spring-ai-playground/observability/<YYYY-MM-DD>/<traceId>.json); surfaces 12 dashboard tabs via Vaadin sidebar — Overview, Tokens & Cost, AI Models, Tool Studio, MCP Servers, MCP Inspector, Vector Database, Agentic Chat, Host, Web Application, Logs, Traces — backed by BucketedTimeSeries + BoundedRingBuffer and ECharts panels. Recent activity table drills into Trace Detail Dialog (timeline, spans with raw attributes, raw JSON) and Conversation Thread Dialog (USER/ASSISTANT messages with KPIs). Deep-link via /observability?tab=<slug>&trace=<traceId> and the “Continue in chat” button lands the conversation back in Agentic Chat. ObservationRegistry wired into ToolCallingManager and SimpleVectorStore so tool / vector spans appear alongside chat client spans.Gmail, Outlook-Mail, Notion, Slack, Microsoft-Teams, GitHub, Linear, Atlassian, Tavily, Exa, Firecrawl, Sentry, Asana, HubSpot, Mixpanel, Figma, Canva, Webflow, …) grouped into Productivity / CRM / Design / DEV / Search / Utility / Example categories in a left-side filter sidebar. Each entry pre-fills the connection JSON with ${ENV_VAR} placeholders (e.g. ${GITHUB_PERSONAL_ACCESS_TOKEN}, ${MS_TENANT_ID}) and surfaces its preview / community / free-tier tags. Activation gates on env-var presence so disabled servers can’t be turned on without setup.default-mcp-specs-stdio-{mac,windows,linux}.json (8 entries each) loaded per-platform, with [macOS] / [Windows] / [Linux] prefix in description. Tag suggestions derive dynamically from existing entries so new connections auto-complete from the live catalog. Tier 1 (built-in) + Tier 2 (catalog) = 57 inactive entries plus the built-in spring-ai-playground-tool-mcp connection (58 total visible on the MCP page).SecretMasking.mask() extracts ${ENV_VAR} references from connection templates, resolves their values via EnvVarResolver, and redacts any value ≥ 4 characters from mcp.tool.crash error logs (replaced with ***). LoggingMcpToolCallback wraps every MCP tool callback with mcp.tool.start / mcp.tool.done / mcp.tool.crash log lines carrying an 8-character correlation id (cid), server name, tool name, duration ms, and via=chat channel marker — feeds the Observability MCP / Tools tabs.fs.existsSync validation of the bundled JRE path (process.resourcesPath/jre-bundle/bin/java[.exe]) with explicit fatal-error message pointing to the electron/scripts/prepare-resources.mjs step. Telemetry env (SPRING_AI_PLAYGROUND_TELEMETRY_ENABLED) propagated from the Electron main process into the spawned Java via buildSpawnArguments (entire process.env merged into spawn env) and into the splash / config editor / Ollama manager / server splash windows via ?telemetry=0 query string. launchReadinessState machine (idle → starting → ready / failed / timedOut) feeds the splash with stage messages.SidebarSection, SidebarItem, and PageSidebar factored out as reusable components (refactor(ui)). Tool Studio, MCP Server, Vector Database, Agentic Chat, and the Observability dashboards now share the same look and interaction (bolded active item, group headers, count chips). The MCP Inspector header also picks up the status indicator + active counter.tool/ — bundled tool catalog JSON files (default-tool-specs*.json), categories, presets, and workspace-samples moved into src/main/resources/tool/ (refactor(resources)). The wildcard pattern (classpath*:tool/default-tool-specs*.json) at default-tool-location continues to load all six bundles.fix(mcp)).testValue used to throw at Test Run time; the regression is fixed alongside JSHint editor cleanup and KR locale tidy (fix(tool-studio)).shouldLoadFile(Path) hook on PersistenceServiceInterface so the default loads() path skips .tmp, .tmp.json, and .deprecated files plus foreign JSON files (e.g. default-tools-preference.json). ToolSpecPersistenceService.shouldLoadFile further narrows to exactly toolSpecsMcpSetting. Closes the boot-time NPE from bad deserialization (fix(persistence)).McpCatalogServiceTest.tierSplitMatchesCatalogSeed updated to tier2 size 30 to 39 and the new MS_TENANT_ID placeholder reflecting the catalog expansion (fix(mcp)).@Test methods unified to camelCase, FQN dropped in PersistenceService, sidebar active items rendered bold (fix(style)).docs/features/observability/ directory: overview.md, ai-usage/{index, tokens-cost, ai-models}.md, ai-stack/{index, tool-studio, mcp-servers, mcp-inspector, vector-database, agentic-chat}.md, and runtime/{index, host, web-application, logs, traces}.md (14 pages). Each page covers KPI / chart descriptions, full-page screenshots, and drill-down scenarios.docs/features/default-mcp-catalog/ split per category (business.md, data-cloud.md, dev.md, examples.md, productivity.md, search.md, index.md).docs/features/mcp-server/inspector.md covering all 8 inspector tabs (Tools / Resources / Prompts / Ping / Notifications / Roots / Sampling / Elicitation) with screenshots.docs/safe-tool-specification.md plus docs/safe-tool-spec.schema.json (JSON Schema for tool spec authoring).@modelcontextprotocol/server-everything activation through every Inspector tab.docs/features/tool-studio.md is now docs/features/tool-studio/index.md (directory layout).docs/observability-architecture.md walks the collector → ring buffer → time series → dashboard flow.docs-overrides/main.html, depth-1 auto-expand via docs/assets/javascripts/nav-default-expand.js, and navigation.instant + navigation.instant.progress features enabled in mkdocs.yml.maven-checkstyle-plugin 3.6.0 + checkstyle 10.21.0 + spring-javaformat-checkstyle 0.0.47 plugin block added to pom.xml, bound to the verify phase with failOnViolation=true. src/checkstyle/checkstyle.xml mirrors the Spring AI baseline with project-specific deltas (4-space indent kept; JavadocPackage, SpringLambdaCheck, NeedBracesCheck, InnerTypeLastCheck removed; RightCurlyCheck option=same; WhitespaceAround empty-* options relaxed; AvoidStaticImport excludes extended with internal helpers). The license header regex is inlined as a header property so the same config works in IntelliJ Checkstyle without extra path setup. src/checkstyle/checkstyle-suppressions.xml excludes 131 M6 baseline files, the OllamaChatModel upstream patch, and 5 files introduced by commits already pushed to origin/main — incremental adoption: only new and modified files participate in the scan..github/workflows/ci.yml maven-goals flipped from clean compile -B to clean verify -B (skip-tests unchanged), so checkstyle runs on every push and PR.chore: bump to 0.2.0-M7 with spring-ai 1.1.6.default-tool-specs.json, default-tool-specs-builtin.json, default-tool-specs-builtin-helpers.json, default-tool-specs-builtin-fs.json, default-tool-specs-network.json, default-tool-specs-kr.json) loaded via wildcard classpath*:default-tool-specs*.json. Categories: Text & Strings, Data Formats, Date & Time, Math & Compute, Encoding, Crypto & Random, Security, Files, Web (global + Korea), Productivity, Messaging, AI APIs.DefaultToolPresetCatalog + preset/preference resolver picking which slice of the bundled catalog the built-in MCP server exposes. Five presets shipped: Starter 5 (default, no setup), Dev Essentials, Korea Toolkit (free), File Toolkit, Everything. DefaultToolsPreferenceService layers per-tool include / exclude rules (by tag / category / name) on top of the chosen preset and persists to ~/spring-ai-playground/tool/save/default-tools-preference.json. CLI override resolvable through defaultToolsPreferenceResolver.default-tools-preference.json file as the desktop launcher.default-tools-preference.json as Tool Studio’s drawer.McpToolDefinition exposure flag and ToolActivationCalculator registers the callback with McpSyncServer live (no restart). Tool Studio sidebar surfaces Drafts and Local Pass panels separately.McpToolDefinition record + ToolManifest envelope for every published tool — manifest hash, code hash, runtime helpers list, sandbox risk level, audit timestamps, integrity hashes, optional signature.EffectivePolicyResolver — addAllowClasses, addAllowedHosts, egressLevel (strict / allowlist / custom / permissive), fileRead / fileWrite, fsBasePath. Exposed in the Sandbox & Capabilities pane with a visible per-tool Risk Level badge (L0–L5) computed by SandboxPostureCalculator.ToolCategoryCatalog + ChipListBinding) with chip filters across cohort tags (korea, example, util, pipeline, github, search, finance, weather, geo).JsRuntimeGlobals):
fetch with a 4-layer SSRF guard — scheme allowlist, literal-IP check, DNS resolve check, and per-tool egress policy. 5 redirect cap, 10 MB body cap, 30 s timeout, 5 s connect timeout. Restricted hop-by-hop headers stripped before sending; 303 redirects downgrade POST → GET. Response paging via init.maxLength / init.startIndex. Backed by SafeHttpFetch.URL, URLSearchParams, atob, btoa, and crypto.subtle (digest / sign / verify / getRandomValues).safety.fs — readText, writeText, list, stat, grep, lineCount, slice, cut, sort, find. All paths resolved against tool-studio.fs.base-path and rejected on escape. Backed by SafeFs.safety.parser.{html,yaml,csv,xml} — strict, non-instantiating parsers (HTML via jsoup, XML via XXE-hardened javax.xml.parsers, YAML via SnakeYAML, CSV via Apache Commons CSV) exposed without leaking host classes to user code.JsHelperException for structured helper errors (security / invalid-input / helper-runtime) plus an executor-level error classifier so JS-side failures surface consistently on both the Test Run path and the MCP path.tool.exec.start / tool.exec.done / tool.exec.crash log lines with correlation id, risk level, capability summary, allowed hosts, fs base path, masked params, and durationMs. Env-backed static-variable values are masked from the params summary before logging.spring-ai-playground-tool-mcp client on the same JVM without race conditions.allow-network-io defaults to false (was true).allow-classes shrunk to pure-compute packages only (java.lang/math/time/util/text.*). java.net.*, java.io.*, java.net.http.*, org.jsoup.* are no longer reachable directly.deny-classes list (evaluated before allow-classes; deny always wins) covers System, Runtime, ProcessBuilder, Process, Class, java.lang.invoke.*, java.lang.reflect.*, Thread, ThreadGroup, ClassLoader, ServiceLoader, java.util.spi.* — closing reflection / SPI / process-spawn escape vectors.tool-studio.fs.base-path introduced for safety.fs, defaulting to ${TOOL_STUDIO_FS_BASE:${user.home}}.default-tool-location moved under spring.ai.playground and switched to wildcard classpath*:default-tool-specs*.json so the bundled catalog can ship as multiple JSON files without code changes.ToolCategoryCatalog, ToolCategory taxonomy). Sidebar grouped by category and filterable with chips.SandboxCapabilitiesView, ToolMcpServerSettingView) and polished alongside Electron window chrome and chat send affordances.JsToolExecutor and its helpers moved to service/tool/runtime/; policy code lives under service/tool/policy/.ensureResolved) so a tool with an unresolved ${ENV_VAR} placeholder fails at registration / load time instead of inside the JS sandbox.egressLevel from strict to allowlist) are reflected in the next test run without restarting Tool Studio.Map / List parameters from MCP JSON.fetch → SafeHttpFetch). Strict mode rejects loopback / link-local / site-local / any-local / multicast / IPv6 ULA / CGNAT addresses both as literals and after DNS resolution.safety.fs enforces a base-path jail; any path that resolves outside tool-studio.fs.base-path is rejected before any I/O.console.log output, in the Test Run debug pane, and in tool.exec.* observability log lines.deny-classes evaluation order (deny before allow) closes reflection / invoke / ClassLoader / ServiceLoader / Thread / Process escape vectors that a permissive allow-list could otherwise re-open per tool.tool-studio, mcp-server, vector-database, agentic-chat) and Tutorials (1–8 individually) replace the previous monolithic features.md / tutorials.md.docs/features/default-tools/ — index + Examples / Utilities / Filesystem / Global / Korea pages, each with per-tool cards (params, env vars, sandbox risk level, JS source), a Keys & secrets footer (issuance URLs for every required key including the eight-service data.go.kr keychain), and shared composition-pattern guidance.docs/safety-architecture.md) — defense-in-depth sandbox model, policy resolution, per-execution enforcement, Risk Level decision matrix, threat-to-layer mapping, known limitations, configuration reference.M4 → M6, and the Default MCP Tools curation entry-point.~/spring-ai-playground/mcp/oauth-tokens/ with host-specific salt + user.home key derivation and transparent refresh.${ENV_VAR} placeholder substitution (resolved at connect time from OS environment with JVM system-property fallback). Auth header presets — Bearer Token, Basic Auth, API Key Header — drop a templated row into the Headers section. The same ${VAR} syntax also applies to STDIO env values and requiredEnv lists.mcp-stdio Spring profile that switches the embedded MCP server to stdio transport while keeping the Vaadin Inspector on port 8282 in the same process. Opt-in via SPRING_PROFILES_INCLUDE=mcp-stdio (preserves the default ollama profile so model config still applies). Logback detaches the CONSOLE appender in this mode so stdout stays a clean JSON-RPC channel; rolling-file logs at ~/spring-ai-playground/logs/ are unaffected.mvn package fallback driven by .dockerignore), debian:bookworm-slim runtime base, and an io.modelcontextprotocol.server.name OCI label for MCP Registry ownership verification..github/workflows/release.yml) builds the fat JAR once and shares it across both the Docker image matrix (native linux/amd64 + ubuntu-24.04-arm runners, no QEMU emulation) and the desktop installer matrix (mac arm64/Intel, Windows NSIS, Linux DEB/RPM). The GitHub Release is atomic across both tracks, with SHA-256 checksums and Sigstore SLSA build provenance attestations.updateDefaultMcpTool flow short-circuits to avoid attempting a streamable-http connection to its own non-HTTP endpoint.ci.yml cancels duplicate runs from push + pull-request event pairs via a concurrency group; actions/checkout bumped to v5 on the docs-site workflow; greeter workflow’s repo-token renamed to repo_token to match actions/first-interaction@v3.RAG · {time} · {N docs} · {titles}, THINK · {time}, MCP TOOLS · {time} · {N calls} · {names}), preserves the partial assistant response when the user clicks Stop, and shows a Stopped/Error stream-status badge that persists across reload.SPRING_AI_PLAYGROUND_TELEMETRY_ENABLED=false) now applies uniformly across the web app and every desktop launcher window (splash, server-splash, config editor, Ollama manager)..sha256) and Sigstore build provenance attestations, verifiable via gh attestation verify --owner spring-ai-community.SoftwareApplication JSON-LD schema for richer search results.SimpleVectorStore dumps are debounced to coalesce bursts into a single write, keeping disk-write threads non-daemon so in-flight writes finish on shutdown.RuntimeException wrapping replaced with targeted error handling; MCP startup uses sequential per-server iteration with try/catch in place of parallelStream to isolate failures.spring-ai-playground-<version>-<platform>-<arch>.<ext> (no spaces, version included). Bumped to 0.2.0-M4. CI now resolves the installer version from the branch/tag ref and injects it into electron/package.json at build time..deb / .rpm package metadata renamed from spring-ai-playground-desktop to spring-ai-playground for consistency with the bundle ID, binary, and user-visible app name.APP_HOME now defaults to <user.home>/<app-name> when the JVM user.home property is unset, preventing log/config write failures in containerized environments.tool_calls and matching tool responses correctly.lancher-* documentation assets to launcher-* (typo fix).main pushes and version tags, while keeping the general CI workflow available on every branch.