feat: seed repo with Java SDK RFC, roadmap, and harness foundation#1
Merged
Conversation
Brings the merged adcontextprotocol/adcp#4279 RFC into this repo as the seed for implementation, alongside a contributor-facing roadmap that divides the v0.1 → v1.0 work into 14 claimable tracks and lays out the pre-contributor harness scope. - docs/rfc/java-sdk-rfc.md — RFC text from the merged spec PR; relative ../docs/... links rewritten to absolute github.com/adcontextprotocol/adcp URLs since those targets live in the spec repo. - ROADMAP.md — milestones, tracks, dependency graph, 7.x deltas vs. the RFC's 6.x baseline (auth challenge, HTTP Basic, upstream-recorder, IDEMPOTENCY_IN_FLIGHT, SSRF baseline as v0.1 not v1.x), confirmed post-RFC decisions D1-D5 (repo home, JDK 21 baseline, Maven coords group=org.adcontextprotocol artifact=adcp, Sigstore confirmed, @adcp/sdk/mock-server as the storyboard CI target). - .github/CODEOWNERS — default review on every PR to @bokelley and @MichielDean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds four confirmed decisions to the post-RFC table and propagates them through the body: - D6 — first Maven Central publish at v0.3 (not v0.1); Sonatype OSSRH namespace + GPG setup still happens harness Week 1 since approval is the slow path. v0.1 and v0.2 ship local Gradle artifacts only. - D7 — jakarta only / Spring Boot 3.x floor. SB 2.7 is 6 months past OSS EOL; no compat starter, no community 2.7 port. - D8 — mock-server CI sidecar via `npx adcp mock-server`. Pinned @adcp/sdk version becomes the conformance oracle. Confirmed seven specialisms (signal-marketplace, creative-template, sales-guaranteed, sales-non-guaranteed, sales-social, sponsored-intelligence, creative-ad-server). - D9 — io.modelcontextprotocol.sdk:mcp pinned 1.1.2. The Spring AI MCP SDK was donated to the modelcontextprotocol org in Feb 2025 and rebranded as the official Java SDK; current spring-ai-mcp-* artifacts are thin Spring Boot wrappers on top. Two prototype questions remain for harness Week 1: servlet streamable-HTTP transport without Jetty/Tomcat, and Jackson 2 vs. 3 module feature parity. License is MIT — surfaced as a new open question (#9) for the foundation. Resolves RFC Open Questions 2 and 6. Crosses out the resolved items in Decisions Wanted; remaining open: funding/staffing, design partners, WG vote, named maintainer, lifecycle YAML buy-in, Reactor/Mutiny/Kotlin GA scope, MIT-dep position. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds twelve more confirmed decisions to the post-RFC table. Protocol / architecture: - D10 A2A pre-1.0 types: in-tree until a2aproject/a2a-java cuts ≥1.0.0 (currently 1.0.0.Beta1 — pre-stable, package layout still churning). - D11 TransitionGuard: guards declare touched edges; conformance harness fails if a sandbox account's guards narrow a storyboard edge. - D12 Spring Security: recipes-only at v1.0; no separate -security starter. Revisit on v0.3 feedback. - D13 Reactor + Mutiny adapters at GA, not fast-follow. - D14 Kotlin co-release at v1.0 as a thin extension. - D15 Spec-rev tracking: ≤2 weeks from spec rev to Java release. Repo conventions (matched to existing AdCP family practice): - D16 Design decisions filed in specs/<topic>.md, not docs/adr/. The Java SDK RFC itself lives at specs/java-sdk-rfc.md in the adcp repo; match that convention. ROADMAP table is the at-a-glance index; specs/ doc only when the row isn't enough. - D17 Trunk-based branching; semver tags from main. - D18 Conventional Commits + commitlint + Changesets, matching TS SDK. - D19 AAO IPR Bot pattern, not DCO. Replicates the same flow installed on adcp / adcp-client / adcp-client-python / adcp-go: contributors comment "I have read the IPR Policy" on first PR, App enforces via required status check. Harness Week 1 installs the bot on this repo and adds the ~15-line caller workflow. Infra: - D20 Sonatype OSSRH namespace claim for org.adcontextprotocol via DNS TXT verification (foundation adds the record). Maven Central confirms the namespace is currently unclaimed (zero artifacts). - D21 Branch protection on main: 1 code-owner review, required CI checks including IPR Policy / Signature, no force-push. Harness §Pre-contributor table updated to drop the ADR-directory item (superseded by D16) and add IPR caller workflow, DNS-TXT, branch protection, and commitlint/Changesets items. Decisions wanted reduced to the three foundation-owned + two cross-team items plus the MIT-dep position. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First piece of harness Week 1 work. Eight published artifacts wired
per D3 with the build graph in place; convention plugins centralize
the JDK-21 toolchain (D2) and shared compile/test config.
Modules (all building, all stamping Automatic-Module-Name):
- adcp main library: caller, types, schemas
- adcp-server agent-side: signing, idempotency, async
- adcp-testing storyboard runner, JUnit 5 fixtures
- adcp-spring-boot-starter Spring Boot 3.x autoconfig (D7 jakarta)
- adcp-cli runnable jar (placeholder main today)
- adcp-reactor Project Reactor adapter (D13)
- adcp-mutiny SmallRye Mutiny adapter (D13)
- adcp-kotlin Kotlin coroutine + DSL extensions (D14)
Build-logic:
- adcp.java-base-conventions JDK 21 toolchain, JSpecify on the compile
classpath, manifest stamping, lint+Werror
(minus -processing/-options to tolerate
package-info-only skeleton modules)
- adcp.java-library-conventions library plugin + JUnit 5 test wiring
- adcp.kotlin-library-conventions Kotlin variant for adcp-kotlin
Version catalog (gradle/libs.versions.toml) pins:
- io.modelcontextprotocol.sdk:mcp 1.1.2 (D9)
- Jackson 2.18.2 (RFC §JSON — >=2.15 floor for records support)
- com.networknt:json-schema-validator 1.5.6
- SLF4J 2.0.16, JSpecify 1.0.0, JUnit 5.11.4
- Spring Boot 3.4.4 (D7 — 3.x / jakarta only)
- Reactor 3.7.2, Mutiny 2.7.0, Kotlin 2.1.10, kotlinx-coroutines 1.10.1
Verified locally:
- `./gradlew build` green across all 8 modules
- JAR manifests show Automatic-Module-Name = org.adcontextprotocol.<dot>
- `./gradlew :adcp-cli:run` prints the placeholder usage stub
- All modules produce main + sources + javadoc JARs
CI (.github/workflows/ci.yml):
- JDK 21 Temurin via actions/setup-java
- gradle/actions/setup-gradle with validate-wrappers (supply-chain
guard against tampered gradle-wrapper.jar)
- Runs ./gradlew build on every PR and push to main
This is the foundation everything else builds on. Next harness items
land on top: schema-bundle fetcher, codegen MVP, SSRF-safe HttpClient
wrapper, storyboard CI gate with @adcp/sdk/mock-server sidecar (D8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONTRIBUTING.md covers the AAO IPR comment ritual (per D19), dev setup, the track-claim flow into ROADMAP.md, code conventions, Conventional Commits + Changesets workflow, and the schema/codegen pointer. CLAUDE.md gives coding agents the precedence order (D-decisions > RFC > tracks), build commands, module layout, conventions, and a "don't do this" list pulled from the locked decisions (no *Async mirrors, no Spring Boot 2.7, no Bouncy Castle, no Optional<T> returns, no docs/adr/). Issue templates: - track-claim.yml — structured form: track ID, experience, availability, milestone, WG sync, open questions - bug-report.yml — SDK version, JDK version, framework, repro - config.yml — disables blank issues; links protocol questions to the spec site, design discussion to AdCP Slack, sister-SDK bugs to those repos PR template prompts the IPR check, Conventional Commits, changeset addition, and the standard ./gradlew build + test plan checklist. .gitignore adds .claude/ and .context/ — coding-agent and Conductor workspace side channels that shouldn't be committed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three workflows mirrored from adcp-client, matching the conventions locked by D18 (Conventional Commits + Changesets) and D19 (AAO IPR Bot pattern). ipr-agreement.yml delegates to the reusable workflow at adcontextprotocol/adcp/.github/workflows/ipr-check-callable.yml@main. Signatures land in the central ledger at adcp:signatures/ipr-signatures.json once the foundation admin (1) installs the AAO IPR Bot on this repo and (2) scopes IPR_APP_ID + IPR_APP_PRIVATE_KEY org secrets to it. commitlint.yml validates PR titles and all PR commits against the Conventional Commits config in commitlint.config.js. Type enum, header-max-length 120, and footer-leading-blank exception all match the @adcp/sdk config so the two SDK changelogs read the same way. changeset-check.yml requires a changeset on PRs that change adopter- visible behavior. Skips for changeset-release/main automation branches. package.json + package-lock.json define the npm-side tooling — explicit "private: true", scoped to commitlint + changesets devDeps only. The SDK itself is Java; this is the same shape adcp / adcp-client / adcp-go use for their release-notes machinery. .changeset/config.json points at the public Changesets schema with baseBranch main, restricted access (Java artifacts don't publish to npm), and the tools package ignored. README in the same directory explains when contributors should add a changeset vs skip. .gitignore adds node_modules/ — only for the Node-side tooling, the SDK remains pure Java. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First piece of the 7.x SSRF baseline that ROADMAP §7.x deltas pinned
as a v0.1 release-gate requirement (not v1.x hardening). Lands the
address-guard half today; DNS-pin connect-binding and redirect handling
land on the transport track.
specs/ssrf-baseline.md is the design doc (per D16 — design notes live
in specs/, not docs/adr/). Covers the threat model, the four
mitigations (DNS-resolve-once, pin-connect, redirect:manual, body cap),
the full RFC-grounded block table, the harness/transport split, and
the references back to the TS SDK migration PRs.
adcp module gains org.adcontextprotocol.adcp.http with:
- SsrfPolicy — sealed interface; strict() is the v0.1 baseline,
permissive() is opt-in for local dev (deliberately not env-driven so
a misconfigured production deploy can't silently disable the guard).
- SsrfDecision — sealed Allow/Deny; Deny reasons describe ranges, not
specific addresses, so the reason text doesn't leak host structure
when echoed in logs.
- StrictSsrfPolicy — walks InetAddress's built-in range predicates
(loopback / link-local / site-local / multicast / any-local) and
layers in the JDK-not-covered cases by hand:
- RFC 6598 CGN (100.64/10)
- RFC 2544 benchmark (198.18/15)
- RFC 6890 IETF protocol assignments (192.0.0/24)
- Reserved class E (240/4)
- IPv6 unique local (fc00::/7)
- IPv4-mapped IPv6 (::ffff:a.b.c.d/96) — unmapped and re-evaluated
against the v4 rules. NB: deliberately not using
Inet6Address.isIPv4CompatibleAddress() because it matches the
deprecated "::a.b.c.d" form (including ::1), which would cause
loopback to be treated as 0.0.0.1 and pass the guard.
- PermissiveSsrfPolicy — every address allowed; documented opt-in only.
Tests cover 26 literal block-list addresses, four allowed public ones,
the no-host-structure-leak property, and the permissive policy's
allow-everything behavior. 35 tests, all green.
Build-logic gains junit-jupiter-params on the test classpath so
parameterized tests can express the block-list table compactly.
.gitignore picks up .kotlin/ (build cache from the Kotlin DSL).
The DNS-rebinding and redirect-follow integration tests are deferred to
the transport track per the spec doc's harness/track split table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `./gradlew :adcp:fetchSchemaBundle` — downloads the AdCP protocol tarball at the version pinned in ADCP_VERSION (or via -PadcpVersion=X.Y.Z), verifies SHA-256 and Sigstore signatures per https://github.com/adcontextprotocol/adcp/blob/main/docs/reference/verifying-protocol-tarballs.mdx, and extracts to build/schemas/. The Sigstore verification matches the spec doc's invocation exactly: - Identity regex pinned to release.yml@refs/(heads|tags)/.* on the adcp repo (matches the maintenance branches 3.0.x, 2.6.x; forward-compat for tag-triggered re-signing) - OIDC issuer pinned to GitHub Actions' token endpoint - cosign verify-blob fails the task if anything other than the AdCP release workflow produced the signature Sidecars (.sha256, .sig, .crt) are confirmed live for versioned releases on https://adcontextprotocol.org/protocol/{version}.* — verified end-to-end locally against 3.0.11 with the local cosign binary. ADCP_VERSION at the repo root pins 3.0.11 (current latest at time of import). Same shape the TS SDK uses for its pin file. Bumping it is a deliberate PR — D15 cadence (≤2 weeks spec-rev tracking). -PskipCosign=true is a documented offline-dev escape hatch only; logs a warning and is never silent. The convention is registered as adcp.schema-bundle-conventions and applied to the adcp module today; other modules can pick it up when they need raw schema access. CI doesn't yet install cosign — that lands when the codegen track wires fetchSchemaBundle as a dependency of compile. Today the task runs only when explicitly invoked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First piece of the D8 storyboard CI gate. Adds: - .github/workflows/storyboard.yml — boots `npx adcp mock-server sales-guaranteed --port 4500` as a sidecar process before running ./gradlew :adcp-testing:test. Waits up to 30s for the sidecar to respond on /, captures logs on failure, kills the PID on cleanup. - adcp-testing/src/test/.../MockServerReachableTest.java — JUnit 5 test gated on the ADCP_MOCK_SERVER_URL env var. Asserts the sidecar is reachable and responds <500. Locally the test is skipped (no env var); CI sets it via the workflow step. As the testing track lands StoryboardRunner, the runner replaces this one-line assertion while the workflow shape stays unchanged. - package.json + package-lock.json: pin @adcp/sdk 7.2.0 as a devDep so `npm ci` brings the `adcp` CLI into the workflow's PATH. The pinned version is the conformance oracle — bumping it is a deliberate PR per D8. The other six specialisms (signal-marketplace, sales-non-guaranteed, sales-social, creative-template, sponsored-intelligence, creative-ad-server) come online as the testing track adds storyboards that exercise them; the workflow shape extends to a port-range pattern (4500–4506) at that point. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…quest/Response) First piece of the codegen track. Proves the architecture; the full surface lands on Track 2 (L0 types & codegen). What it does: - JavaPoet-based generator (build-logic/src/main/kotlin/codegen/SchemaCodegen.kt) reads a JSON Schema file and emits one Java record. - *Request types get a static `builder()` factory + a nested `Builder` class (per RFC §Type generation: *Request always has a builder). - *Response types are records only, no builder. - Required fields are non-null components; optional fields get @org.jspecify.annotations.Nullable. - Snake_case schema names map to camelCase Java via @JsonProperty — the wire format never sees camelCase. - JavaDoc generated from the schema's description + per-field descriptions as @param tags. - @javax.annotation.processing.Generated annotation cites the source schema file. MVP scope (2 schemas, both flat scalar): - core/pagination-request.json → PaginationRequest - core/pagination-response.json → PaginationResponse Wired as a Gradle task `:adcp:generateSchemas` that depends on `fetchSchemaBundle` (per D4). Output lands in adcp/build/generated/sources/codegen/main/java/ and is added to the main source set so compileJava picks it up. Not checked in — regenerated from the pinned ADCP_VERSION every build. Round-trip smoke test in adcp/src/test/.../GeneratedPaginationTest.java covers: - Builder produces a record that serializes snake_case - camelCase Java names never leak to the wire - Required field deserializes when present, nulls for optional - Symmetric round-trip with all optional fields populated - Unset optional fields default to null 4 codegen tests + 35 SSRF tests all green. 39 tests total in :adcp. CI gets cosign installed via sigstore/cosign-installer@v3 in both ci.yml and storyboard.yml so fetchSchemaBundle (and now compileJava) works end-to-end on GitHub Actions. Polymorphism (oneOf with discriminator), $ref resolution, nested objects, arrays of complex types, format-asset-slot unions (per 7.x deltas), and the rest of the generator surface — Track 2. .gitignore picks up .local/ — coding-agent state side channel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… R1, R2) Closes both prototype questions D9 left open for harness Week 1. R1 — HTTP server transport without Jetty/Tomcat: **Yes.** mcp-core 1.1.2 ships three framework-neutral server transport providers in the JAR itself: - HttpServletStreamableServerTransportProvider (current MCP spec) - HttpServletSseServerTransportProvider (older SSE form) - HttpServletStatelessServerTransport (stateless variant) - StdioServerTransportProvider (no HTTP at all) They compile against jakarta.servlet-api 6.x but don't depend on Jetty, Tomcat, Undertow, or anything Spring. Adopters bring their own Servlet container at runtime. mcp-spring-webmvc and mcp-spring-webflux exist as optional artifacts for Spring-specific integration — we don't need them. The 0.x line had a separate `server-servlet` artifact; 1.x folded it into mcp-core itself. R2 — Jackson 2 vs Jackson 3 module parity: **Both at 1.1.2, identical cadence, neither lags.** Same surface; choice is which Jackson tree the consumer's other deps use. We pick mcp-json-jackson2 because RFC §JSON pins Jackson 2.15+. Pulling mcp-json-jackson3 would create a dual- Jackson dep graph for adopters — exactly the class of problem RFC's Jackson pin prevents. Build wiring: - gradle/libs.versions.toml — replaces `mcp` (the bundle, which pulls jackson3) with explicit mcp-core + mcp-json-jackson2 entries. - adcp-server/build.gradle.kts — same swap; comments cite findings doc. specs/mcp-prototype-findings.md is the full write-up — closes the open questions per D16 (longer-form decisions in specs/). Smoke test in adcp-server: 12 tests covering presence of the four server transports, three client transports, three spec interfaces, the stdio-no-servlet load path, and the Jackson 2 (not 3) classpath assertion. Uses getResource(name + ".class") instead of Class.forName because the servlet-extending classes need jakarta.servlet-api at runtime — a compileOnly dep adopters provide. The contract we test is "the SDK ships the class," not "we provide the adopter's runtime." Full build green: 51 tests across :adcp (4 codegen + 35 SSRF + 12 MCP imports). On JDK 21 + Gradle 9.5.1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D9's two prototype questions are answered in specs/mcp-prototype-findings.md and the row in §Confirmed decisions now points at the findings instead of leaving them open. Status table gains a "Confirmed decisions D1-D21 locked" row and the pre-contributor harness item moves to 🟡 In progress with a one-liner of what's landed vs the foundation-admin items still outstanding (IPR Bot install, DNS TXT for Sonatype, @MichielDean collaborator). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Seeds the empty repo with: the merged RFC, a contributor-facing roadmap covering 14 tracks across v0.1→v1.0, the AAO-conventional
CODEOWNERS, and the harness Week 1 foundation — a working Gradle 9.5 multi-module skeleton with CI green on JDK 21.What's here
Docs
docs/rfc/java-sdk-rfc.md— RFC text from merged adcontextprotocol/adcp#4279; relative spec links rewritten to absolute GitHub URLs.ROADMAP.md— 14 claimable tracks, dependency graph, 7.x parity deltas vs the RFC's 6.x baseline, pre-contributor harness plan, and 21 confirmed post-RFC decisions (D1–D21).Governance
.github/CODEOWNERS— @bokelley + @MichielDean on every PR.Build foundation (harness Week 1 first slice)
adcp,adcp-server,adcp-testing,adcp-spring-boot-starter,adcp-cli,adcp-reactor,adcp-mutiny,adcp-kotlinbuild-logic/centralize JDK 21 toolchain (D2), JSpecify nullability annotations, manifest stamping, andXlint:all -Werror(minusprocessing/optionsto tolerate package-info-only skeleton modules).gradle/libs.versions.tomlpins every external dependency in one place:io.modelcontextprotocol.sdk:mcp:1.1.2(D9), Jackson 2.18.2 (RFC §JSON ≥2.15 floor), networknt json-schema-validator 1.5.6, SLF4J 2.0.16, JSpecify 1.0.0, JUnit 5.11.4, Spring Boot 3.4.4 (D7 jakarta only), Reactor 3.7.2, Mutiny 2.7.0, Kotlin 2.1.10..github/workflows/ci.yml— JDK 21 Temurin,gradle/actions/setup-gradle@v4withvalidate-wrappers: true(supply-chain guard).Verified
./gradlew buildgreen locally and on GH Actions (cdf1c2f, 1m59s).Automatic-Module-Name = org.adcontextprotocol.<dot>../gradlew :adcp-cli:runruns the placeholderMain.21 confirmed decisions (in ROADMAP §Confirmed decisions)
Architecture / protocol:
main)*Asyncmirrors,ScopedValueforUpstreamRecorder)org.adcontextprotocol:adcpetc., base pkgorg.adcontextprotocol.adcp.*)@adcp/sdk/mock-serverio.modelcontextprotocol.sdk:mcppinned 1.1.2 (MIT-licensed, flagged for foundation)a2aproject/a2a-java≥1.0 (currently at1.0.0.Beta1)TransitionGuardnarrowing protection via declared touched edges-securitystarterBuild / release:
jakartaonly, Spring Boot 3.x floornpx adcp mock-serverRepo conventions:
specs/<topic>.md(AdCP convention), notdocs/adr/build+IPR Policy / Signaturechecks7.x parity deltas vs the RFC's 6.x baseline (in ROADMAP §7.x deltas)
The TS SDK has shipped to 7.2.0 since the RFC was authored. Highlights folded into the roadmap:
AuthenticationRequiredError.challenge+probeAuthChallenge(WWW-Authenticateparsing for non-Bearer 401s).auth-scheme).resolveAgentProperties/validateAdAgents(ads.txtMANAGERDOMAINone-hop fallback).IDEMPOTENCY_IN_FLIGHTAdCP 3.1 wire code (replaces legacySERVICE_UNAVAILABLE).upstream-recorder(new artifact surface; producer-side reference middleware for theupstream_trafficstoryboard check).parallel_dispatchstoryboard step +RunnerNotice/NoticeCodetaxonomy.storyboards_missing_toolsvs.storyboards_not_applicable.redirect: 'manual', 4 KiB probe body cap on every discovery probe.Still open (kept honest in ROADMAP §Decisions wanted)
lifecycletrack path 1 vs 2)Test plan
gh apiverified).../docs/...relative links in the RFC rewritten to absolutegithub.com/adcontextprotocol/adcp/blob/main/...URLs../gradlew buildgreen locally on JDK 21.0.11 + Gradle 9.5.1../gradlew buildgreen on GH Actions (Temurin 21,cdf1c2frun25824676945, 1m59s).Automatic-Module-Namecorrectly.validate-wrappers: true) passes — supply-chain guard against tamperedgradle-wrapper.jar.adcontextprotocol/adcp-sdk-java.adcontextprotocol.orgfor Sonatype namespace verification (foundation admin action per D20).🤖 Generated with Claude Code