feat: dial-unified-config — MCP server (admin tools for read/write/upload/publish)#1530
Open
siarhei-fedziukovich wants to merge 29 commits into
Open
feat: dial-unified-config — MCP server (admin tools for read/write/upload/publish)#1530siarhei-fedziukovich wants to merge 29 commits into
siarhei-fedziukovich wants to merge 29 commits into
Conversation
…ock v0.2 Collapse the admin/user split into a single dial_* surface (Core enforces authz by caller identity), shrink to 9 building-block tools, and reframe stack/deployment as an external Python sidecar (FastMCP/mcp SDK). Two-array list envelope handles flat and hierarchical types uniformly; format:summary on list, detailed on get; bucket aliases (private/public/platform) resolved server-side via cached /v1/bucket. Audit, apply, diff/export, effective-policy, codeapp, dial-api adoption, OpenAPI gen, and DIAL-app-with-MCP move to a §12 future-work register with unlock conditions. README cross-references updated to drop the "Admin MCP" and "raw draft" framing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… projections M4 no longer asks tool descriptions to embed REST-equivalent details — that inflates every agent's context for a fallback path that doesn't pay off. Keep example invocations only. Summary-projection table: drop iconUrl (models, applications) and updatedAt (files, prompts, conversations); add description across all entity rows (singleton settings excluded). Description is the highest- signal summary field for an agent picking between candidates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove §9 Success Metrics — KPI targets at this stage are speculative and the eval-driven-development guidance can re-land if/when it has a real home. Renumber subsequent sections (Risks → §9, Open Questions → §10, Future Work → §11, References → §12) and the inline §12 cross-references that pointed at the future-work register. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverse the v0.2 external-Python-sidecar shape in favor of an in-repo Gradle module embedded in DIAL Core as a Vert.x verticle for v1 delivery speed. Add explicit extraction discipline (§7.1) — REST-only access to Core via loopback HTTP, dependency-graph CI check, config-driven Core URL, own verticle/thread-pool, tokens forwarded verbatim — so future extraction to a standalone service is a build-and-deploy change rather than a refactor. Stack pivots to Java for code reuse with config/ POJOs and Jackson; Python preserved as a post-extraction option. Updates: §1 summary, §7 architecture (module placement, stack rationale, deployment shapes), §8 phasing (HTTP-only first cut), §9 risks (release- cadence coupling and discipline-erosion replace the separate-repo drift risk), §10 OQs (drop repo-name, add endpoint-placement and stdio-launcher questions), §11 future work (add extraction trigger entry plus L2 Core- embedded MCP capabilities entry), §12 references (Java SDK first), Next. README cross-reference updated to mention the embedded-module v1 shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump to v0.4. Resolutions: - OQ-1 module name: mcp/ (matches sibling-module convention) - OQ-2 summary projections: ship table as-is, revisit on eval data - OQ-3 multi-env: pin each MCP instance to one env; cross-env is agent-side composition across two MCP connections - OQ-4 schema caching: not needed in v1 — schemas are in-process registry lookups loaded at JVM startup from the same config/ Gradle module Core uses; cache discussion deferred to extraction - OQ-5 destructive UX: confirm:true is enough; two-step flow is L2 - OQ-6 observability: rely on Core logs + agent traces, no /metrics - OQ-7 endpoint placement: mount on Core's existing port at /mcp - OQ-8 stdio: punt — v1 is HTTP/SSE-only Threaded through the doc: M9 reframed (in-process registry, runtime fetch is post-extraction fallback), §7.2 transport line resolves OQ-7/OQ-8, §7.3 deployment table drops the stdio row, §8 phasing clarifies one-MCP-per-env, §11 dial_diff_environments / dial_export unlock conditions reframed now that OQ-3 is resolved, N4 rewritten as a plain assertion. §10 OQ table strikethroughs the originals with inline Resolved: notes for traceability. §Next replaced with implementation-ready next steps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the Track C foundation: new :mcp sibling Gradle module (:config dep + Vert.x core only), McpVerticle skeleton, McpRequestHandler returning 503 transport-not- wired, Proxy /mcp short-circuit (404 when mcp.enabled=false per §7.1 kill-switch), mcp.* settings defaults per §7.1, and CONTRIBUTING.md documenting extraction discipline. SDK dep + Vert.x↔MCP-SDK transport adapter carved into sibling slice M.0.0-bridge (start-of-slice halt resolution). Design anchors: 09 §1, §7.1, §7.2, §8 kickoff checklist Tests: mcp/.../McpRequestHandlerTest.java, server/.../McpRoutingTest.java, server/.../McpDisabledRoutingTest.java Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements VertxMcpTransportProvider against the SDK's
McpStreamableServerTransportProvider SPI; bridges HttpServerRequest
body buffering, executeBlocking-isolated SDK Mono blocks, and
runOnContext-marshalled SSE writes. Replaces M.0-pre's 503 stub.
Zero tools registered — the tool surface lands in M.1.x.
Design anchors: 09 §7.1, §7.2, §8 kickoff checklist
Tests: server/src/test/java/com/epam/aidial/core/server/McpHandshakeTest.java
+ mcp/src/test/java/com/epam/aidial/core/mcp/transport/VertxMcpTransportProviderTest.java
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…back wrapper Picks option (a) captured-context dispatch from spec §7.2: DialClient encapsulates the Reactor->Vert.x bridge via Mono.create + runOnContext + onComplete, so M.1.x tool handlers never touch context plumbing directly. Loopback URL resolves env MCP_DIAL_TARGET_URL > settings mcp.dialTargetUrl > http://localhost:8080. McpVerticle.start() captures the verticle context once and constructs DialClient. Design anchors: 09 §7.1 (rule 3), §7.2 (bridge option a), §7.4 (verbatim auth) Tests: mcp/src/test/.../client/DialClientTest.java; server/src/test/.../McpDialClientLoopbackTest.java Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds in-memory token-bucket per MCP session (60 calls/min, burst 10) and a 5-call concurrency cap, enforced inside VertxMcpTransportProvider.dispatchPost because SDK 1.1.2 does not expose session-id at tool-handler time. Overflow returns a JSON-RPC error (code -32000, data.retry_after) marshalled to the event loop. Decoration point chosen at transport, not DialClient. Fixed-point math with pre-clamp guards against long-overflow on idle sessions. Design anchors: 09 §7.1 (M10), §9 risk row 1 Tests: mcp/src/test/.../ratelimit/McpSessionLimiterTest.java (10 tests) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark M.0.2-pre as ✅ with squash commit 31cd5e4. Enrich the slice row with the option-α decision (transport-layer enforcement vs DialClient decoration), JSON-RPC error shape, retry_after=1 floor, the long-overflow fix from review, and the deferred end-to-end integration test boundary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t, get
Wires three MCP read tools onto the existing :mcp module: dial_describe_schema (in-process
Victools-built JSON Schema lookup per spec §M9, no HTTP), dial_list_resources (two-array
envelope with summary projection per §6.3-§6.4), dial_get_resource (entity body augmented
with etag — null for config GETs in Phase 1, pre-existing Core gap). Pilot pattern proven on
models/roles/settings; M.1.1 mechanically extends to remaining 9 types. Auth model
collapsed: caller's Api-Key/Authorization headers forwarded verbatim via McpTransportContext
(no env var). SDK 1.1.2's exchange.sessionId() is public — used as the per-session
bucket-cache key, with success-only Mono caching to avoid error replay on transient
GET /v1/bucket failures.
Design anchors: 09 §1, §6.1-§6.4, §7.4-§7.5, §M9
Tests: mcp/.../{schema,tools}/*Test.java; server/.../McpReadToolsTest.java
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…erarchical envelope
Expands dial_list_resources / dial_get_resource from the M.1.0 pilot
(models, roles, settings) to all 12 spec types — applications, toolsets,
interceptors, keys, routes, schemas, files, prompts, conversations now
listable / gettable through the MCP read surface. dial_describe_schema is
unchanged (M.1.0 already covered the 8 :config POJOs + DIAL meta-schema;
files/prompts/conversations stay as not-implemented envelope per §M9).
Per-type Core path routing centralized in ResourceId: config types hit
/v1/{type}/{bucket}/...; applications, toolsets, files, prompts,
conversations route listings through /v1/metadata/{type}/{bucket}/...;
files additionally route individual GETs through /v1/metadata/... since
/v1/files/{bucket}/{path} returns raw bytes (deferred to dial_download_file
in M.3.0). Hierarchical-types envelope splits the upstream
ResourceFolderMetadata response into the spec §6.3 two-array shape: items[]
for nodeType=ITEM, folders[] for nodeType=FOLDER, with nextToken mapped to
nextCursor. recursive=true and cursor are rejected on flat config types
with structured remediation hints (Core's config-resource controller is
single-page-no-cursor). settings keeps its 405-on-list short-circuit from
M.1.0.
SUMMARY_FIELDS table populated for all 12 types per spec §6.4; metadata-
derived items (apps/toolsets/files/prompts/conversations) silently no-op
fields the metadata response doesn't carry — N+1 enrichment is post-MVP
explicit defer.
Reviewer-driven fixes (pre-merge): (HIGH) cursor was silently dropped for
flat types — added cursorNotSupported error guard mirroring the
recursive-on-flat rejection; (HIGH) envelope path field used the alias
bucket while child ids used the resolved bucket — both now use the
resolved bucket so listings always return canonical ids per spec §6.2.
SIMPLIFY pass folded 8 fixes: shape() slimmed from 6 args to (resp,
ResourceId, resolvedBucket, format); summaryFields() drops the defensive
copy on the immutable List.of values; parentPath() extracted to deduplicate
projectFolder/projectItem null/empty handling; Jackson path() replaces
has()+get() doubled probes; appendQuery dropped its LinkedHashMap;
usesMetadataList renamed to supportsRecursive; milestone-narrating javadoc
replaced with stable contract docs; toCorePath / toListCorePath javadoc
nails the parse / parseListPath pairing rule.
Design anchors: 09 §6.3 (two-array envelope), §6.4 (summary projection)
Tests: mcp/src/test/java/com/epam/aidial/core/mcp/tools/{ResourceIdTest,
ListResourcesEnvelopeTest, SummaryFieldsTest}.java;
server/src/test/java/com/epam/aidial/core/server/McpReadToolsTest.java
(:mcp:test 41→54, :server:test 1041→1045, 0 failures total)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ools
Adds three MCP write tools alongside M.1.0 reads. Per-controller routing:
ConfigResourceController types use POST/PUT split; ResourceController types
(applications, toolsets, prompts, conversations) use PUT + If-None-Match:*
(create) / If-Match:* (update) so Core's standard etag idiom recovers the
create/update split for PUT-upsert types. validate_only routes to
/v1/admin/validate via single-entity manifest envelope. confirm:true MCP-side
gate before any HTTP. files/settings deferred to M.2.1 with structured
remediation. McpTestSupport extracts handshake helpers from McpReadToolsTest.
Design anchors: 09 §6.1 (tools 4-6), §6.5, §6.6, §7.4
Tests: mcp/.../tools/{Create,Update,Delete}ResourceToolTest.java;
server/.../McpWriteToolsTest.java (16 cases, incl. §6.2 alias-resolution)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates §5.6 row M.2.0: status 📋 → ✅; commit 313b351. Captures the locked design choices: Option C validate_only routing through /v1/admin/validate (Core has no per-resource validate_only query param); etag-idiom create/update split for ResourceController PUT-upsert types via If-None-Match:* / If-Match:*; request-side D2 disambiguation in EtagIdiom enum; per-controller routing predicate on ResourceId; reviewer pre-merge §6.2 alias-resolution fix in shapeValidate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> # Conflicts: # docs/sandbox/dial-unified-config/IMPLEMENTATION.md
dial_update_resource and dial_delete_resource now handle the singleton
'settings' (PUT-upsert / DELETE clears API blob via 3S.2-settings) and
files DELETE (plain /v1/files/{bucket}/{path}, not the M.1.1 metadata
route). Create-side rejections for both types remain - settings has no
POST surface; files binary upload lives in M.3.0. Added
ResourceId.toMutationCorePath so writes skip the metadata-GET prefix.
Design anchors: 09 §6.1 (tools 4-6), §6.5; IMPLEMENTATION.md §5.6 M.2.1
Tests: server/src/test/.../McpWriteToolsTest.java, mcp/src/test/.../ResourceIdTest.java
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M.3.0 (file tools) was implemented to completion on its sub-branch but the new tool registrations made a pre-existing /mcp deploy-order race near-deterministic — Core binds the HTTP listener and only later deploys McpVerticle, so a fast inbound POST /mcp lands while McpRequestHandler is still returning the M.0-pre 503 stub. Per /dial-mvp §4.1 halt #4, the fix is carved into sibling slice M.3.1-handshake-readiness; M.3.0 row flipped to 🚧 with a deps update and a paused-status note. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e ready Wires a deployment-ready Future<Void> from vertx.deployVerticle(...) through to McpRequestHandler; dispatch waits up to 2s for the latch then 503s on timeout. Pauses the request body across the wait so HC5 doesn't read-time-out after the bodyHandler is installed. Fixes the pre-existing /mcp deploy-order race that flaked :server:test runs since M.1.0 and went near-deterministic in M.3.0. Design anchors: 09 §7.1, §7.2 Tests: mcp/src/test/.../McpRequestHandlerTest.java (4 cases pinning fast/slow paths + body-survival) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the implementation-discovery note (HC5 180s read-timeout from body drop; pause/resume contract is load-bearing) plus the 4-case test layout to the §5.6 row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the two file tools: base64 content + opt-in source_url with
default-deny SSRF guard (CIDR blocklist + allow-list + per-request
timeout + Content-Length pre-check); image-content block on download
for image/* MIME. DialClient gains binary + multipart overloads.
CIDR parsing consolidated into IpAddressRange.parseCidr (used by both
the client-IP allow-list deserializer and the new SSRF guard).
DNS rebinding TOCTOU documented as a known v1 limitation —
sourceUrl is opt-in default-deny.
Design anchors: 09 §6.1 (tools 7-8), §6.7-§6.8, §7.1
Tests: mcp/src/test/.../SourceUrlGuardTest.java (13 cases),
server/src/test/.../McpFileToolsTest.java (12 cases)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps a single-resource ADD manifest and POSTs to /v1/ops/publication/create for the Resource Operations API (PENDING -> admin approval). Tool composes sourceUrl from resolved bucket + name and targetUrl from <type>/<target>+leaf per spec §3.2 example. Preflight validation rejects malformed target before the network round-trip. ResourceId.leafName() consolidated from UploadFileTool. Design anchors: 09 §6.1 (tool 9), §3.2 illustrative composition Tests: server/src/test/java/com/epam/aidial/core/server/McpPublishToolTest.java Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flips §5.6 register row to ✅ at 12ba627 with five locked decisions (typed-JsonSchema cap on cross-cutting affordances, preflight as cheap client-side error, client-computed targetUrl per spec §3.2, Core-side type validation, ResourceId.leafName extraction). REVIEW pass closures: two test-coverage gaps (multi-level nested name, non-private alias) filled with `publishMultiLevelNamePlacesLeafAtTargetRoot` + `publishWithExplicitBucketSkipsPrivateAlias`. Design anchors: 09 §6.1 (tool 9), §3.2 illustrative composition Tests: no new tests (doc-only) 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.
Applicable issues
Description of changes
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.