Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d856105
docs: add Apache LICENSE and NOTICE files
claude Jun 4, 2026
c476d41
docs(spec): add SBOM generation design
adityamparikh Jun 5, 2026
ac0ca0a
docs(plan): add SBOM generation implementation plan
adityamparikh Jun 5, 2026
8e4c3c2
chore(deps): add CycloneDX Gradle plugin 1.10.0 to version catalog
adityamparikh Jun 5, 2026
1cf5f0f
feat(build): generate and embed CycloneDX SBOM
adityamparikh Jun 5, 2026
6a0d39e
feat(actuator): enable /actuator/sbom endpoint explicitly
adityamparikh Jun 5, 2026
0c0a165
docs(spec): drop integration-test scope, document plugin-version deci…
adityamparikh Jun 5, 2026
52347bc
docs(spec): drop stale 1.10.0 version reference
adityamparikh Jun 5, 2026
2cedf64
docs(spec): inline the plugin-version constraints explanation
adityamparikh Jun 5, 2026
74992d6
ci: upload CycloneDX SBOM as workflow artifact
adityamparikh Jun 5, 2026
c3b4aa8
ci(release): strict SBOM generation + upload + release attachment
adityamparikh Jun 5, 2026
9d3d70b
docs(readme): document SBOM location, retrieval, and scanning
adityamparikh Jun 5, 2026
e179ac3
refactor(build): drop unnecessary cyclonedxBom configuration
adityamparikh Jun 5, 2026
452799d
docs(agents): note SBOM generation in commands + architecture
adityamparikh Jun 5, 2026
21df7ac
style: apply spotless
adityamparikh Jun 5, 2026
8e6f187
feat(build): derive binary-release LICENSE/NOTICE from the SBOM
adityamparikh Jun 11, 2026
685ff4c
refactor(build): extract LICENSE/NOTICE generation to a buildSrc plugin
adityamparikh Jun 11, 2026
82bacc2
test(build): unit-test the LICENSE/NOTICE buildSrc tasks
adityamparikh Jun 11, 2026
da3c5d8
docs(build): explain the LICENSE/NOTICE tasks in comments and AGENTS.md
adityamparikh Jun 11, 2026
748a70b
refactor(build): drop license-policy.json; disclose SBOM licenses ver…
adityamparikh Jun 11, 2026
3ceb2a7
docs(build): point the LICENSE appendix to the bundled SBOM
adityamparikh Jun 11, 2026
d2711c9
docs: document where/when the binary LICENSE & NOTICE are available
adityamparikh Jun 11, 2026
033c03e
docs(build): explain the buildSrc LICENSE/NOTICE plugin for non-Gradl…
adityamparikh Jun 11, 2026
230b706
docs(build): comment the convention plugin body for non-Gradle readers
adityamparikh Jun 11, 2026
fd7eedf
Merge remote-tracking branch 'upstream/main' into pr/138
epugh Jun 13, 2026
1721a0f
Dont double load the root files
epugh Jun 13, 2026
9ec8521
feat(build): enforce Apache license headers via RAT convention plugin
adityamparikh Jun 13, 2026
23f3866
Merge remote-tracking branch 'upstream/main' into pr/150
epugh Jun 15, 2026
c626eeb
Merge branch 'main' into add-rat-validation-buildsrc
epugh Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ plugins {
// Listed after spring-boot + cyclonedx so productionRuntimeClasspath and
// cyclonedxBom exist when it wires its tasks. See buildSrc/.
id("org.apache.solr.mcp.license-notice")
// Enforces Apache license headers via Apache RAT (buildSrc convention plugin).
// Wires `rat` into `check`, so `./gradlew build` audits headers. See buildSrc/.
id("org.apache.solr.mcp.rat")
}

// GraalVM Native Image (Opt-In)
Expand Down
17 changes: 16 additions & 1 deletion buildSrc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
limitations under the License.
-->

# buildSrc — project build logic

This directory holds the project's custom build logic, written in Kotlin. Today that is
two ASF-compliance concerns:

- assembling the **binary-release `LICENSE` and `NOTICE`** files bundled inside the
executable JAR (the end-user view of *what* these contain lives on the
[Licensing & Notices](https://solr.apache.org/mcp/licensing.html) docs page); and
- enforcing **Apache license headers** on source files via Apache RAT.

If you don't work with Gradle day-to-day, this README explains what each piece is and how
they fit together.
# buildSrc — generating the binary LICENSE & NOTICE

This directory holds the build logic that assembles the **binary-release `LICENSE`
Expand All @@ -39,7 +51,10 @@ small. (Think of it as a tiny library that only this project's build uses.)
| `src/main/kotlin/.../GenerateBinaryNotice.kt` | A custom Gradle **task** that writes the binary `NOTICE` (our `NOTICE` + the `NOTICE` files of bundled dependencies). |
| `src/main/kotlin/org.apache.solr.mcp.license-notice.gradle.kts` | A **convention plugin** that creates the two tasks above and wires them into the build. |
| `src/test/kotlin/.../LicenseNoticeTasksTest.kt` | Unit tests for the two tasks. |
| `build.gradle.kts` | Builds `buildSrc` itself (enables Kotlin + the test dependencies). |
| `src/main/kotlin/.../RatExcludes.kt` | Pure helper that translates `.gitignore` entries into Apache RAT (Ant-style) exclude globs. |
| `src/main/kotlin/org.apache.solr.mcp.rat.gradle.kts` | A **convention plugin** that applies Apache RAT and configures its excludes (`.gitignore`-derived + an explicit list). |
| `src/test/kotlin/.../RatExcludesTest.kt` | Unit tests for the gitignore→glob translation. |
| `build.gradle.kts` | Builds `buildSrc` itself (enables Kotlin + the RAT plugin + the test dependencies). |

## Gradle concepts, for Java developers

Expand Down
9 changes: 9 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ plugins {

repositories {
mavenCentral()
// Hosts the Apache RAT plugin marker below — it is not published to Maven Central.
gradlePluginPortal()
}

dependencies {
// Makes the Apache RAT plugin (id `org.nosphere.apache.rat`) available to the
// `org.apache.solr.mcp.rat` convention plugin's `plugins {}` block. The version lives
// here (mirroring how the junit dep below is pinned) since buildSrc does not read the
// root project's version catalog. Latest release as of writing; bump in lockstep with
// the plugin's upstream releases.
implementation("org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:0.8.1")

// Only used by the task unit tests under src/test (the main code needs no extra deps;
// the Gradle API is provided by the kotlin-dsl plugin).
testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
Expand Down
93 changes: 93 additions & 0 deletions buildSrc/src/main/kotlin/org.apache.solr.mcp.rat.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Convention plugin: Apache RAT (Release Audit Tool) license-header enforcement.
//
// For readers new to Gradle: this `.gradle.kts` file under buildSrc is a "precompiled
// script plugin". Gradle compiles it into a plugin whose id is the file name
// (`org.apache.solr.mcp.rat`); the root build applies it with one line,
// `id("org.apache.solr.mcp.rat")`. The `plugins {}` block below applies the third-party
// RAT plugin (made available to buildSrc by the `org.nosphere.apache.rat...` dependency
// in buildSrc/build.gradle.kts), and the body configures its `rat` task.
//
// RAT verifies that every scanned file carries an Apache license header. The plugin wires
// its `rat` task into `check`, so a plain `./gradlew build` runs it; the report lands at
// build/reports/rat/index.html.
//
// Exclusions come from two sources so patterns are not duplicated:
// 1. .gitignore — reused as the single source of truth for everything git already
// ignores (build output, .gradle, IDE dirs, *.iml, out/, bin/, .vscode, etc.).
// RatExcludes (in this same buildSrc) translates each entry into a RAT (Ant) glob;
// see that class for the gitignore→glob mapping rules.
// 2. The explicit list below — only *tracked* files that RAT would scan but that
// legitimately carry no Apache header (binaries, data without a comment syntax,
// docs, tool/infra config, and LICENSE/NOTICE themselves).

import org.apache.solr.mcp.build.RatExcludes
import org.nosphere.apache.rat.RatTask

plugins {
id("org.nosphere.apache.rat")
}

tasks.withType<RatTask>().configureEach {
val gitignore = rootProject.file(".gitignore")
if (gitignore.exists()) {
excludes.addAll(RatExcludes.fromGitignore(gitignore.readLines()))
}

excludes.addAll(
listOf(
// Gradle wrapper (ships under its own license) + on-disk OS cruft
"gradlew",
"gradlew.bat",
"gradle/wrapper/**",
"**/.DS_Store",
// Tracked dotfiles that take no header
".run/**",
".gitignore",
".gitattributes",
".editorconfig",
".tool-versions",
".env.example",
// License/notice files themselves (no header by definition)
"LICENSE",
"NOTICE",
// ASF infra metadata and tool config (no header by convention)
".asf.yaml",
"config/**",
// Local developer tooling not tracked by git — Claude Code worktrees/settings
// and the Kotlin compiler cache (analogous to the gitignored .idea/.gradle).
// Present only on some local checkouts, never in CI.
".claude/**",
"**/.kotlin/**",
// Tabular data — no comment syntax to hold a header
"**/*.csv",
// Documentation (markdown carries no license header)
"**/*.md",
"docs/**",
"dev-docs/**",
"security-docs/**",
// Binary assets
"images/**",
"**/*.png",
// Data / generated content (JSON cannot hold comments)
"mydata/**",
"**/*.json",
),
)
}
81 changes: 81 additions & 0 deletions buildSrc/src/main/kotlin/org/apache/solr/mcp/build/RatExcludes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.mcp.build

/**
* Translates `.gitignore` lines into Apache RAT (Ant-style) exclude globs.
*
* RAT scans every file under the project directory; we reuse `.gitignore` as the single
* source of truth for build output, IDE folders, and other untracked cruft so those
* patterns are not duplicated in the build script. The RAT plugin's own `excludeFile`
* does **not** interpret `.gitignore` path semantics, so we translate each entry here.
*
* This is pure, Gradle-free logic precisely so it can be unit-tested
* ([RatExcludesTest]); the gitignore-to-Ant-glob mapping has enough edge cases
* (anchoring, directory markers, negation) to be worth pinning down with tests.
*
* ### Mapping rules (a practical subset of gitignore semantics)
*
* Throughout, "globstar" means the two-asterisk Ant wildcard that matches any number of
* path segments (written here without the trailing slash to keep this comment valid).
*
* - Blank lines and `#` comments are skipped.
* - **Negation (`!foo`) entries are skipped.** Git uses them to *re-include* a path, but
* RAT excludes have no re-inclusion mechanism. Skipping (rather than excluding) is the
* safe choice: at worst RAT still scans a file git would ignore. The opposite — a
* bare `build/` excluding a re-included `!build/keep.txt` — over-excludes that one file
* from header checking, which we accept as a rare, low-risk gap.
* - A **trailing slash** (directory marker) is stripped; the entry is still emitted in
* both bare and directory-contents (globstar-suffixed) forms.
* - **Anchoring** follows git: an entry with a leading slash, or with an interior slash
* (a separator that is not just the trailing one), is anchored to the repo root and is
* emitted as-is (leading slash removed). An entry with no separator — or only a
* trailing slash — matches at any depth and is prefixed with a leading globstar segment.
* An entry that already begins with a globstar segment is left untouched. (The original
* inline implementation prefixed *every* non-leading-slash entry with a globstar, which
* wrongly turned a root-anchored `foo/bar` into an any-depth match; this distinguishes
* the two.)
*
* Each surviving entry yields two globs — the path itself and its directory-contents form
* — so an ignored directory and everything under it are both pruned.
*/
object RatExcludes {

fun fromGitignore(lines: List<String>): List<String> =
lines.asSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.filterNot { it.startsWith("#") }
.filterNot { it.startsWith("!") }
.map { it.trimEnd('/') }
.filter { it.isNotEmpty() }
.map(::toGlob)
.flatMap { sequenceOf(it, "$it/**") }
.distinct()
.toList()

/** Maps one normalized (trimmed, no trailing slash) gitignore entry to one Ant glob. */
private fun toGlob(entry: String): String {
val anchored = entry.startsWith("/")
val body = entry.removePrefix("/")
return when {
body.startsWith("**/") -> body // already any-depth
anchored || body.contains("/") -> body // root-anchored (leading or interior slash)
else -> "**/$body" // no separator → match at any depth
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.mcp.build

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class RatExcludesTest {

@Test
fun `each entry yields both the path and its directory-contents glob`() {
assertEquals(listOf("**/build", "**/build/**"), RatExcludes.fromGitignore(listOf("build/")))
}

@Test
fun `a no-separator entry matches at any depth`() {
// ".gradle" can appear at any level, so it must be prefixed with **/.
assertTrue(RatExcludes.fromGitignore(listOf(".gradle")).contains("**/.gradle"))
assertTrue(RatExcludes.fromGitignore(listOf("*.iml")).contains("**/*.iml"))
}

@Test
fun `a leading-slash entry is anchored to the repo root`() {
// "/build" means the root build dir only — no **/ prefix, leading slash stripped.
assertEquals(listOf("build", "build/**"), RatExcludes.fromGitignore(listOf("/build")))
}

@Test
fun `an interior-slash entry is root-anchored, not any-depth`() {
// git anchors "src/generated" to the root; it must NOT become **/src/generated.
val globs = RatExcludes.fromGitignore(listOf("src/generated"))
assertEquals(listOf("src/generated", "src/generated/**"), globs)
}

@Test
fun `an entry already starting with double-star is left untouched`() {
assertEquals(listOf("**/*.log", "**/*.log/**"), RatExcludes.fromGitignore(listOf("**/*.log")))
}

@Test
fun `blank lines, comments, and negations are skipped`() {
val globs = RatExcludes.fromGitignore(listOf("", " ", "# a comment", "!keep.txt"))
assertTrue(globs.isEmpty(), "expected no globs but got $globs")
}

@Test
fun `duplicate entries are collapsed`() {
assertEquals(listOf("**/target", "**/target/**"), RatExcludes.fromGitignore(listOf("target", "target/")))
}
}
16 changes: 16 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
[versions]
# Build plugins
spring-boot = "3.5.14"
Expand Down
16 changes: 16 additions & 0 deletions src/main/resources/application-http.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
spring.main.web-application-type=servlet
spring.ai.mcp.server.type=sync
spring.ai.mcp.server.protocol=stateless
Expand Down
16 changes: 16 additions & 0 deletions src/main/resources/application-stdio.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
spring.main.web-application-type=none
# NOTE: You must disable the banner and the console logging
# to allow the STDIO transport to work !!!
Expand Down
16 changes: 16 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
spring.application.name=solr-mcp
spring.profiles.active=${PROFILES:stdio}
spring.ai.mcp.server.instructions=This server provides tools to interact with Apache Solr using Model Context Protocol (MCP) over STDIO and/or HTTP.
Expand Down
Loading