From 4aa0bc0905eb2efea8a65480319ddc3229b6d5da Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Wed, 11 Mar 2026 15:24:32 -0300 Subject: [PATCH 1/6] Migrate to plumcp --- .github/workflows/release.yml | 12 +- CHANGELOG.md | 2 + deps-lock.json | 30 + deps.edn | 3 +- .../eca/eca/native-image.properties | 1 + .../native-image/eca/eca/reflect-config.json | 531 +----------------- src/eca/features/tools/mcp.clj | 511 ++++++++--------- 7 files changed, 266 insertions(+), 824 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86fce627e..95dfaecc3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -319,15 +319,11 @@ jobs: - name: Install MSVC uses: ilammy/msvc-dev-cmd@v1 - - name: Install GraalVM - uses: DeLaGuardo/setup-graalvm@master + - uses: graalvm/setup-graalvm@v1 with: - graalvm: 22.2.0 - java: java17 - - - name: Install native-image component - run: | - gu.cmd install native-image + java-version: '24' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} # see https://github.com/oracle/graal/issues/4340 - name: GraalVM workaround to support UPX compression diff --git a/CHANGELOG.md b/CHANGELOG.md index ace1c7db9..959f66785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Replace MCP Java SDK with plumcp for MCP client communication. SSE transport is no longer supported (deprecated in MCP spec 2025-03-26). + ## 0.112.1 - Fix MCP OAuth credentials cache not invalidating when the server URL changes. diff --git a/deps-lock.json b/deps-lock.json index ae77df9a4..fcba28bba 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -1052,6 +1052,26 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-RLTLjpPU9rJiwE7Qdx1w3WbnbUXX/HVYIGcaYmVcVDk=" }, + { + "mvn-path": "io/github/plumce/plumcp.core-json-cheshire/0.2.0-beta2/plumcp.core-json-cheshire-0.2.0-beta2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-m7kV4sKUFMzNhjiLpIWqdbU9h4038urAnCsWFHy7BHk=" + }, + { + "mvn-path": "io/github/plumce/plumcp.core-json-cheshire/0.2.0-beta2/plumcp.core-json-cheshire-0.2.0-beta2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-4lwPaipx5jhyog8/irYDq9jqp1DUXAY9ASVgxL5R8Y8=" + }, + { + "mvn-path": "io/github/plumce/plumcp.core/0.2.0-beta2/plumcp.core-0.2.0-beta2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-eOwf4xkhsLcbpAvFF2mH8tQ/P7YKxqbOlYrsgoORSUQ=" + }, + { + "mvn-path": "io/github/plumce/plumcp.core/0.2.0-beta2/plumcp.core-0.2.0-beta2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-6HEfDI5KnfLepGxDrVz69SrD5VsgqXot9rjnFr45bs8=" + }, { "mvn-path": "io/methvin/directory-watcher/0.17.3/directory-watcher-0.17.3.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -3477,6 +3497,16 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-D1omWgYzGwBJ41K+MsoyLeGLF/PU27cGNdQNppLjWC8=" }, + { + "mvn-path": "org/yaml/snakeyaml/2.4/snakeyaml-2.4.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-73ea9dKand6MxwzgNB9cb3c14j7f+Whc6qnTU1m3u38=" + }, + { + "mvn-path": "org/yaml/snakeyaml/2.4/snakeyaml-2.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-4VSjIxzWzeaKq/J0/RiWTUmpwaX16e079HHprnvfCOY=" + }, { "mvn-path": "progrock/progrock/0.1.2/progrock-0.1.2.jar", "mvn-repo": "https://repo.clojars.org/", diff --git a/deps.edn b/deps.edn index d22dc136b..ac5552e2d 100644 --- a/deps.edn +++ b/deps.edn @@ -3,7 +3,8 @@ org.clojure/core.async {:mvn/version "1.8.741"} org.babashka/cli {:mvn/version "0.8.65"} com.github.clojure-lsp/jsonrpc4clj {:mvn/version "1.0.2"} - io.modelcontextprotocol.sdk/mcp {:mvn/version "0.17.2"} + io.github.plumce/plumcp.core-json-cheshire {:mvn/version "0.2.0-beta2"} + org.yaml/snakeyaml {:mvn/version "2.4"} ;; used by eca.shared for YAML parsing borkdude/dynaload {:mvn/version "0.3.5"} selmer/selmer {:mvn/version "1.12.69"} babashka/fs {:mvn/version "0.5.26"} diff --git a/resources/META-INF/native-image/eca/eca/native-image.properties b/resources/META-INF/native-image/eca/eca/native-image.properties index abae79e2c..3729d3c73 100644 --- a/resources/META-INF/native-image/eca/eca/native-image.properties +++ b/resources/META-INF/native-image/eca/eca/native-image.properties @@ -4,6 +4,7 @@ Args=-J-Dborkdude.dynaload.aot=true \ -J-Dclojure.spec.skip-macros=true \ --enable-url-protocols=jar,http,https \ --enable-all-security-services \ + --add-modules=jdk.httpserver \ --initialize-at-build-time=com.fasterxml.jackson \ --initialize-at-build-time=org.yaml.snakeyaml \ --initialize-at-build-time=io.opentelemetry.api.common.AttributeType \ diff --git a/resources/META-INF/native-image/eca/eca/reflect-config.json b/resources/META-INF/native-image/eca/eca/reflect-config.json index 807ede5b6..fe51488c7 100644 --- a/resources/META-INF/native-image/eca/eca/reflect-config.json +++ b/resources/META-INF/native-image/eca/eca/reflect-config.json @@ -1,530 +1 @@ -[ - { - "name":"io.modelcontextprotocol.spec.McpError", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpJsonMapper", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Annotated", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Annotations", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$AudioContent", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$BlobResourceContents", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CallToolRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CallToolResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ClientCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ClientCapabilities$Elicitation", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ClientCapabilities$Elicitation$Form", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ClientCapabilities$Elicitation$Url", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ClientCapabilities$RootCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ClientCapabilities$Sampling", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CompleteReference", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CompleteRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CompleteRequest$CompleteArgument", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CompleteRequest$CompleteContext", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CompleteResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CompleteResult$CompleteCompletion", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Content", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CreateMessageRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CreateMessageRequest$ContextInclusionStrategy", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CreateMessageResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$CreateMessageResult$StopReason", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ElicitRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ElicitResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ElicitResult$Action", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$EmbeddedResource", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ErrorCodes", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$GetPromptRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$GetPromptResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Identifier", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ImageContent", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Implementation", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$InitializeRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$InitializeResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$JSONRPCMessage", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$JSONRPCNotification", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$JSONRPCRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$JSONRPCResponse", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$JSONRPCResponse$JSONRPCError", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$JsonSchema", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ListPromptsResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ListResourceTemplatesResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ListResourcesResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ListRootsResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ListToolsResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$LoggingLevel", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$LoggingMessageNotification", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Meta", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ModelHint", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ModelPreferences", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Notification", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$PaginatedRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$PaginatedResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ProgressNotification", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Prompt", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$PromptArgument", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$PromptMessage", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$PromptReference", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ReadResourceRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ReadResourceResult", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Request", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Resource", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ResourceContent", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ResourceContents", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ResourceLink", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ResourceReference", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ResourceTemplate", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ResourcesUpdatedNotification", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Result", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Role", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Root", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$SamplingMessage", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ServerCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ServerCapabilities$CompletionCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ServerCapabilities$LoggingCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ServerCapabilities$PromptCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ServerCapabilities$ResourceCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ServerCapabilities$ToolCapabilities", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$SetLevelRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$SubscribeRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$TextContent", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$TextResourceContents", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$Tool", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$ToolAnnotations", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - }, - { - "name":"io.modelcontextprotocol.spec.McpSchema$UnsubscribeRequest", - "allDeclaredFields":true, - "allDeclaredConstructors":true, - "allDeclaredMethods":true - } -] +[] diff --git a/src/eca/features/tools/mcp.clj b/src/eca/features/tools/mcp.clj index dd4a32c7b..8314f0a60 100644 --- a/src/eca/features/tools/mcp.clj +++ b/src/eca/features/tools/mcp.clj @@ -1,53 +1,24 @@ (ns eca.features.tools.mcp (:require - [cheshire.core :as json] [clojure.java.browse :as browse] - [clojure.java.io :as io] [clojure.string :as string] [eca.config :as config] [eca.db :as db] [eca.logger :as logger] [eca.network :as network] [eca.oauth :as oauth] - [eca.shared :as shared]) + [eca.shared :as shared] + [plumcp.core.api.capability :as pcap] + [plumcp.core.api.entity-support :as pes] + [plumcp.core.api.mcp-client :as pmc] + [plumcp.core.client.client-support :as pcs] + [plumcp.core.client.http-client-transport :as phct] + [plumcp.core.client.stdio-client-transport :as psct] + [plumcp.core.protocol :as pp] + [plumcp.core.schema.schema-defs :as psd] + [plumcp.core.support.http-client :as phc]) (:import - [com.fasterxml.jackson.databind ObjectMapper] - [io.modelcontextprotocol.client McpClient McpSyncClient] - [io.modelcontextprotocol.client.transport - HttpClientSseClientTransport - HttpClientStreamableHttpTransport - ServerParameters - StdioClientTransport] - [io.modelcontextprotocol.client.transport.customizer McpSyncHttpClientRequestCustomizer] - [io.modelcontextprotocol.json McpJsonMapper] - [io.modelcontextprotocol.spec - McpSchema$BlobResourceContents - McpSchema$CallToolRequest - McpSchema$CallToolResult - McpSchema$ClientCapabilities - McpSchema$Content - McpSchema$EmbeddedResource - McpSchema$GetPromptRequest - McpSchema$ImageContent - McpSchema$LoggingMessageNotification - McpSchema$Prompt - McpSchema$PromptArgument - McpSchema$PromptMessage - McpSchema$ReadResourceRequest - McpSchema$Resource - McpSchema$ResourceContents - McpSchema$Root - McpSchema$TextContent - McpSchema$TextResourceContents - McpSchema$Tool - McpTransport] - [java.io IOException] - [java.net.http HttpClient$Builder] - [java.time Duration] - [java.util List Map] - [java.util.concurrent TimeoutException] - [java.util.function Consumer] - [javax.net.ssl SSLContext])) + [java.io IOException Writer])) (set! *warn-on-reflection* true) @@ -55,6 +26,26 @@ (def ^:private logger-tag "[MCP]") +(defn ^:private flushing-stdio-transport + "Wraps a plumcp STDIO transport to flush stdin after each message. + Workaround for plumcp not flushing BufferedWriter on send." + [inner-transport] + (let [writer-ref (atom nil)] + (reify pp/IClientTransport + (client-transport-info [_] (pp/client-transport-info inner-transport)) + (start-client-transport [_ on-message] + (let [result (pp/start-client-transport inner-transport on-message)] + (reset! writer-ref (:server-stdin result)) + result)) + (stop-client-transport! [_ force?] + (pp/stop-client-transport! inner-transport force?)) + (send-message-to-server [_ message] + (pp/send-message-to-server inner-transport message) + (when-let [^Writer w @writer-ref] + (.flush w))) + (upon-handshake-success [_ success] + (pp/upon-handshake-success inner-transport success))))) + (def ^:private env-var-regex #"\$(\w+)|\$\{([^}]+)\}") @@ -67,100 +58,71 @@ (str "$" var1) (str "${" var2 "}")))))) -(defn ^:private split-url - "Split a URL into base URI and endpoint path. - Examples: - - 'https://api.example.com/v1/sse' -> ['https://api.example.com', '/v1/sse'] - - 'https://mcp.example.com/sse?key=abc' -> ['https://mcp.example.com', '/sse?key=abc'] - - 'https://api.z.ai/api/mcp/web_reader/mcp' -> ['https://api.z.ai', '/api/mcp/web_reader/mcp']" - [^String url] - (let [uri (java.net.URI. url) - scheme (.getScheme uri) - host (.getHost uri) - port (.getPort uri) - path (or (.getPath uri) "") - query (.getQuery uri) - base-uri (str scheme "://" host (when (and (pos? port) (not= port -1)) (str ":" port))) - endpoint (str path (when query (str "?" query)))] - [base-uri endpoint])) - -(defn ^:private ->transport ^McpTransport [server-name server-config workspaces db] +(defn ^:private ->transport [server-name server-config workspaces db] (if (:url server-config) - ;; HTTP transport (SSE or Streamable, inferred from URL) + ;; HTTP Streamable transport (let [url (replace-env-vars (:url server-config)) - sse? (string/includes? url "/sse") config-headers (:headers server-config) - ^SSLContext ssl-ctx network/*ssl-context* - ssl-customizer (when ssl-ctx - (reify Consumer - (accept [_this client-builder] - (.sslContext ^HttpClient$Builder client-builder ssl-ctx)))) - customizer (reify McpSyncHttpClientRequestCustomizer - (customize [_this builder _method _endpoint _body _context] - ;; First apply configured static headers - (doseq [[header-name header-value] config-headers] - (.header builder (name header-name) (replace-env-vars (str header-value)))) - ;; Then apply OAuth token if present (can override static Authorization) - (when-let [access-token (get-in db [:mcp-auth server-name :access-token])] - (.header builder "Authorization" (str "Bearer " access-token)))))] - (if sse? - (let [[base-uri sse-endpoint] (split-url url)] - (logger/info logger-tag (format "Creating SSE transport for server '%s' - base: %s, endpoint: %s" server-name base-uri sse-endpoint)) - (cond-> (HttpClientSseClientTransport/builder base-uri) - true (.sseEndpoint sse-endpoint) - ssl-customizer (.customizeClient ssl-customizer) - true (.httpRequestCustomizer customizer) - true (.build))) - (let [[base-uri endpoint] (split-url url)] - (logger/info logger-tag (format "Creating HTTP transport for server '%s' - base: %s, endpoint: %s" server-name base-uri endpoint)) - (cond-> (HttpClientStreamableHttpTransport/builder base-uri) - true (.endpoint endpoint) - ssl-customizer (.customizeClient ssl-customizer) - true (.httpRequestCustomizer customizer) - true (.build))))) + ssl-ctx network/*ssl-context* + rm (fn [request] + (-> request + (update :headers merge + (into {} (map (fn [[k v]] + [(name k) (replace-env-vars (str v))])) + config-headers)) + (update :headers merge + (when-let [access-token (get-in db [:mcp-auth server-name :access-token])] + {"Authorization" (str "Bearer " access-token)})))) + hc (phc/make-http-client url (cond-> {:request-middleware rm} + ssl-ctx (assoc :ssl-context ssl-ctx)))] + (when (string/includes? url "/sse") + (logger/warn logger-tag (format "SSE transport is no longer supported for server '%s'. Using Streamable HTTP instead. Consider updating the URL." server-name))) + (logger/info logger-tag (format "Creating HTTP transport for server '%s' at %s" server-name url)) + (phct/make-streamable-http-transport hc)) ;; STDIO transport (let [{:keys [command args env]} server-config - command ^String (replace-env-vars command) - b (ServerParameters/builder command) - b (if args - (.args b ^List (mapv replace-env-vars (or args []))) - b) - b (if env - (.env b (update-keys env name)) - b) - pb-init-args [] - ;; TODO we are hard coding the first workspace work-dir (or (some-> workspaces first :uri shared/uri->filename) - (config/get-property "user.home")) - stdio-transport (proxy [StdioClientTransport] [(.build b) (McpJsonMapper/getDefault)] - (getProcessBuilder [] (-> (ProcessBuilder. ^List pb-init-args) - (.directory (io/file work-dir)))))] - (.setStdErrorHandler stdio-transport (fn [msg] - (logger/info logger-tag (format "[%s] %s" server-name msg)))) - stdio-transport))) - -(defn ^:private ->client ^McpSyncClient [name transport init-timeout workspaces - {:keys [on-tools-change]}] - (-> (McpClient/sync transport) - ;; requestTimeout must be < initializationTimeout so that MCP Java SDK's - ;; DummyEvent bug on 202 notification responses can timeout without - ;; blocking the entire initialization flow. - (.requestTimeout (Duration/ofSeconds (max 10 (quot init-timeout 2)))) - (.initializationTimeout (Duration/ofSeconds init-timeout)) - (.capabilities (-> (McpSchema$ClientCapabilities/builder) - (.roots true) - (.build))) - (.roots ^List (mapv #(McpSchema$Root. (:uri %) (:name %)) workspaces)) - (.loggingConsumer (fn [^McpSchema$LoggingMessageNotification notification] - (logger/info logger-tag (str "[MCP-" name "]") (.data notification)))) - (.toolsChangeConsumer (fn [^List tools] - (logger/info logger-tag (format "[%s] Tools list changed, received %d tools" name (count tools))) - (on-tools-change tools))) - (.build))) + (config/get-property "user.home"))] + (flushing-stdio-transport + (psct/run-command + {:command-tokens (into [(replace-env-vars command)] + (map replace-env-vars) + (or args [])) + :dir work-dir + :env (when env (update-keys env name)) + :on-stderr-text (fn [msg] + (logger/info logger-tag (format "[%s] %s" server-name msg)))}))))) + + +(defn ^:private ->client [name transport init-timeout workspaces + {:keys [on-tools-change]}] + (let [tools-consumer (fn [tools] + (logger/info logger-tag + (format "[%s] Tools list changed, received %d tools" + name (count tools))) + (on-tools-change tools)) + tools-nhandler (fn [jsonrpc-notification] + (pcs/fetch-tools jsonrpc-notification + {:on-tools tools-consumer})) + client (pmc/make-mcp-client + {:info (pes/make-info name "current") + :client-transport transport + :primitives {:roots (mapv #(pcap/make-root-item (:uri %) + {:name (:name %)}) + workspaces)} + :notification-handlers + {psd/method-notifications-tools-list_changed tools-nhandler + psd/method-notifications-message (fn [params] + (logger/info logger-tag + (format "[MCP-%s] %s" name (:data params))))} + :print-banner? false})] + (pmc/initialize-and-notify! client + {:timeout-millis (* 1000 init-timeout)}) + client)) (defn ^:private ->server [mcp-name server-config status db] {:name (name mcp-name) @@ -172,81 +134,66 @@ :resources (get-in db [:mcp-clients mcp-name :resources]) :status status}) -(defn ^:private ->content [^McpSchema$Content content-client] - (case (.type content-client) +(defn ^:private ->content [content-client] + (case (:type content-client) "text" {:type :text - :text (.text ^McpSchema$TextContent content-client)} + :text (:text content-client)} "image" {:type :image - :media-type (.mimeType ^McpSchema$ImageContent content-client) - :base64 (.data ^McpSchema$ImageContent content-client)} - "resource" (let [resource (.resource ^McpSchema$EmbeddedResource content-client)] + :media-type (:mimeType content-client) + :base64 (:data content-client)} + "resource" (let [resource (:resource content-client)] (cond - (instance? McpSchema$TextResourceContents resource) - {:type :text - :text (.text ^McpSchema$TextResourceContents resource)} - - (instance? McpSchema$BlobResourceContents resource) - {:type :text - :text (format "[Binary resource: %s]" (.uri ^McpSchema$BlobResourceContents resource))} - + (:text resource) {:type :text + :text (:text resource)} + (:blob resource) {:type :text + :text (format "[Binary resource: %s]" + (:uri resource))} :else nil)) - (do (logger/warn logger-tag (format "Unsupported MCP content type: %s" (.type content-client))) + (do (logger/warn logger-tag (format "Unsupported MCP content type: %s" + (:type content-client))) nil))) -(defn ^:private ->resource-content [^McpSchema$ResourceContents resource-content-client] - (cond - (instance? McpSchema$TextResourceContents resource-content-client) - {:type :text - :uri (.uri resource-content-client) - :text (.text ^McpSchema$TextResourceContents resource-content-client)} - - :else - nil)) - -(defn ^:private tool-client->tool [^McpSchema$Tool tool-client ^ObjectMapper obj-mapper] - {:name (.name tool-client) - :description (.description tool-client) - ;; We convert to json to then read so we have a clojure map - ;; TODO avoid this converting to clojure map directly - :parameters (json/parse-string (.writeValueAsString obj-mapper (.inputSchema tool-client)) true)}) - -(defn ^:private list-server-tools [^ObjectMapper obj-mapper ^McpSyncClient client] - (try - (when (.tools (.getServerCapabilities client)) - (mapv #(tool-client->tool % obj-mapper) - (.tools (.listTools client)))) - (catch Exception e - (logger/warn logger-tag "Could not list tools:" (.getMessage e)) - []))) - -(defn ^:private list-server-prompts [^McpSyncClient client] - (try - (when (.prompts (.getServerCapabilities client)) - (mapv (fn [^McpSchema$Prompt prompt-client] - {:name (.name prompt-client) - :description (.description prompt-client) - :arguments (mapv (fn [^McpSchema$PromptArgument content] - {:name (.name content) - :description (.description content) - :required (.required content)}) - (.arguments prompt-client))}) - (.prompts (.listPrompts client)))) - (catch Exception e - (logger/warn logger-tag "Could not list prompts:" (.getMessage e)) - []))) - -(defn ^:private list-server-resources [^McpSyncClient client] - (try - (when (.resources (.getServerCapabilities client)) - (mapv (fn [^McpSchema$Resource resource-client] - {:uri (.uri resource-client) - :name (.name resource-client) - :description (.description resource-client) - :mime-type (.mimeType resource-client)}) - (.resources (.listResources client)))) - (catch Exception e - (logger/warn logger-tag "Could not list resources:" (.getMessage e)) - []))) +(defn ^:private ->resource-content [resource-content-client] + (let [uri (:uri resource-content-client)] + (cond + (:text resource-content-client) + {:type :text :uri uri :text (:text resource-content-client)} + + (:blob resource-content-client) + {:type :text :uri uri :text (format "[Binary resource: %s]" uri)} + + :else nil))) + +(defn ^:private tool->internal + "Adapt plumcp tool map to ECA's internal tool shape." + [tool] + {:name (:name tool) + :description (:description tool) + :parameters (:inputSchema tool)}) + +(defn ^:private on-list-error [kind] + (fn [_id jsonrpc-error] + (logger/warn logger-tag (format "Could not list %s: %s" kind (:message jsonrpc-error))) + [])) + +(defn ^:private list-server-tools [client] + (if (get-in (pmc/get-initialize-result client) [:capabilities :tools]) + (or (some->> (pmc/list-tools client {:on-error (on-list-error "tools")}) + (mapv tool->internal)) + []) + [])) + +(defn ^:private list-server-prompts [client] + (if (get-in (pmc/get-initialize-result client) [:capabilities :prompts]) + (or (pmc/list-prompts client {:on-error (on-list-error "prompts")}) + []) + [])) + +(defn ^:private list-server-resources [client] + (if (get-in (pmc/get-initialize-result client) [:capabilities :resources]) + (or (pmc/list-resources client {:on-error (on-list-error "resources")}) + []) + [])) (defn ^:private initialize-mcp-oauth [{:keys [authorization-endpoint callback-port] :as oauth-info} @@ -302,9 +249,8 @@ (defn ^:private transient-http-error? "Checks if the exception root cause is a transient HTTP error (e.g. chunked - encoding EOF) that warrants a retry. This is a known issue with the MCP Java - SDK's Streamable HTTP transport when infrastructure (load balancers, proxies) - closes SSE connections." + encoding EOF) that warrants a retry. This can happen when infrastructure + (load balancers, proxies) closes HTTP streaming connections." [^Exception e] (let [cause (.getCause e)] (and (instance? IOException cause) @@ -349,68 +295,53 @@ (logger/error logger-tag error) (swap! db* assoc-in [:mcp-clients name :status] :failed) (on-server-updated (->server name server-config :failed db)))}) - (let [obj-mapper (ObjectMapper.) - init-timeout (:mcpTimeoutSeconds config) - on-tools-change (fn [tools-client] - (let [tools (mapv #(tool-client->tool % obj-mapper) - tools-client)] + (let [init-timeout (:mcpTimeoutSeconds config) + on-tools-change (fn [tools] + (let [tools (mapv tool->internal tools)] (swap! db* assoc-in [:mcp-clients name :tools] tools) (on-server-updated (->server name server-config :running @db*))))] (loop [attempt 1] (let [transport (->transport name server-config workspaces db) - client (->client name transport init-timeout workspaces - {:on-tools-change on-tools-change})] - (swap! db* assoc-in [:mcp-clients name] {:client client :status :starting}) - (let [result (try - (.initialize client) - (swap! db* assoc-in [:mcp-clients name :version] (.version (.getServerInfo client))) - (swap! db* assoc-in [:mcp-clients name :tools] (list-server-tools obj-mapper client)) + result (try + (let [client (->client name transport init-timeout workspaces + {:on-tools-change on-tools-change}) + init-result (pmc/get-initialize-result client) + version (get-in init-result [:serverInfo :version])] + (swap! db* assoc-in [:mcp-clients name] {:client client :status :starting}) + (swap! db* assoc-in [:mcp-clients name :version] version) + (swap! db* assoc-in [:mcp-clients name :tools] (list-server-tools client)) (swap! db* assoc-in [:mcp-clients name :prompts] (list-server-prompts client)) (swap! db* assoc-in [:mcp-clients name :resources] (list-server-resources client)) (swap! db* assoc-in [:mcp-clients name :status] :running) (on-server-updated (->server name server-config :running @db*)) (logger/info logger-tag (format "Started MCP server %s" name)) - :ok - (catch Exception e - (if (and (transient-http-error? e) (< attempt max-init-retries)) - (do - (logger/warn logger-tag (format "Transient HTTP error initializing MCP server %s (attempt %d/%d), retrying: %s" - name attempt max-init-retries (.getMessage (.getCause e)))) - (try (.close client) (catch Exception _)) - :retry) - (do - (let [cause (.getCause e) - is-sse-error (and cause - (string/includes? (.getMessage cause) "Invalid SSE response")) - is-404 (and cause - (string/includes? (.getMessage cause) "Status code: 404")) - cause-message (cond - (instance? TimeoutException cause) - (format "Timeout of %s secs waiting for server start" init-timeout) - - (and is-sse-error is-404) - (str "SSE endpoint returned 404 Not Found. " - "Please verify the URL is correct. " - "For SSE connections, the URL should point to the SSE stream endpoint " - "(e.g., ending with '/sse' or '/messages')") - - is-sse-error - (str "SSE connection failed: " (.getMessage cause)) - - cause - (.getMessage cause) - - :else - "Unknown error")] - (logger/error logger-tag (format "Could not initialize MCP server %s: %s: %s" name (.getMessage e) cause-message)) - (when (and is-sse-error (:url server-config)) - (logger/error logger-tag (format "SSE URL was: %s" (replace-env-vars (:url server-config)))))) - (swap! db* assoc-in [:mcp-clients name :status] :failed) - (on-server-updated (->server name server-config :failed db)) - (.close client)))))] - (when (= result :retry) - (Thread/sleep 1000) - (recur (inc attempt))))))))) + :ok) + (catch Exception e + (try (pp/stop-client-transport! transport false) (catch Exception _)) + (if (and (transient-http-error? e) (< attempt max-init-retries)) + (do + (logger/warn logger-tag (format "Transient HTTP error initializing MCP server %s (attempt %d/%d), retrying: %s" + name attempt max-init-retries + (some-> e .getCause .getMessage))) + :retry) + (do + (let [cause (.getCause e) + cause-message (cond + (and cause (instance? java.util.concurrent.TimeoutException cause)) + (format "Timeout of %s secs waiting for server start" init-timeout) + + cause + (.getMessage cause) + + :else + (.getMessage e))] + (logger/error logger-tag (format "Could not initialize MCP server %s: %s" name cause-message))) + (swap! db* assoc-in [:mcp-clients name :status] :failed) + (on-server-updated (->server name server-config :failed db)) + :failed))))] + (when (= result :retry) + (Thread/sleep 1000) + (recur (inc attempt)))))))) (catch Exception e (logger/error logger-tag (format "Unexpected error initializing MCP server %s: %s" name (.getMessage e))) (swap! db* assoc-in [:mcp-clients name :status] :failed) @@ -431,7 +362,7 @@ (let [server-config (get-in config [:mcpServers name])] (swap! db* assoc-in [:mcp-clients name :status] :stopping) (on-server-updated (->server name server-config :stopping @db*)) - (.closeGracefully ^McpSyncClient client) + (pmc/disconnect! client) (swap! db* assoc-in [:mcp-clients name :status] :stopped) (on-server-updated (->server name server-config :stopped @db*)) (swap! db* update :mcp-clients dissoc name) @@ -450,18 +381,24 @@ :version version}) tools))) (:mcp-clients db))) -(defn call-tool! [^String name ^Map arguments {:keys [db]}] - (let [mcp-client (->> (vals (:mcp-clients db)) - (keep (fn [{:keys [client tools]}] - (when (some #(= name (:name %)) tools) - client))) - first) - ;; Synchronize on the client to prevent concurrent tool calls to the same MCP server - ^McpSchema$CallToolResult result (locking mcp-client - (.callTool ^McpSyncClient mcp-client - (McpSchema$CallToolRequest. name arguments)))] - {:error (.isError result) - :contents (into [] (keep ->content) (.content result))})) +(defn call-tool! [name arguments {:keys [db]}] + (if-let [mcp-client (->> (vals (:mcp-clients db)) + (keep (fn [{:keys [client tools]}] + (when (some #(= name (:name %)) tools) + client))) + first)] + ;; Synchronize on the client to prevent concurrent tool calls to the same MCP server + (locking mcp-client + (if-let [result (->> {:on-error (fn [_id jsonrpc-error] + (logger/warn logger-tag "Error calling tool:" (:message jsonrpc-error)) + nil)} + (pmc/call-tool mcp-client name arguments))] + {:error (:isError result) + :contents (into [] (keep ->content) (:content result))} + {:error true + :contents nil})) + {:error true + :contents nil})) (defn all-prompts [db] (into [] @@ -475,27 +412,31 @@ (mapv #(assoc % :server (name server-name)) resources))) (:mcp-clients db))) -(defn get-prompt! [^String name ^Map arguments db] - (let [mcp-client (->> (vals (:mcp-clients db)) - (keep (fn [{:keys [client prompts]}] - (when (some #(= name (:name %)) prompts) - client))) - first) - prompt (.getPrompt ^McpSyncClient mcp-client (McpSchema$GetPromptRequest. name arguments))] - {:description (.description prompt) - :messages (mapv (fn [^McpSchema$PromptMessage message] - {:role (string/lower-case (str (.role message))) - :content [(->content (.content message))]}) - (.messages prompt))})) - -(defn get-resource! [^String uri db] - (let [mcp-client (->> (vals (:mcp-clients db)) - (keep (fn [{:keys [client resources]}] - (when (some #(= uri (:uri %)) resources) - client))) - first) - resource (.readResource ^McpSyncClient mcp-client (McpSchema$ReadResourceRequest. uri))] - {:contents (mapv ->resource-content (.contents resource))})) +(defn get-prompt! [name arguments db] + (when-let [mcp-client (->> (vals (:mcp-clients db)) + (keep (fn [{:keys [client prompts]}] + (when (some #(= name (:name %)) prompts) + client))) + first)] + (when-let [prompt (->> {:on-error (fn [_id jsonrpc-error] + (logger/warn logger-tag "Error getting prompt:" (:message jsonrpc-error)))} + (pmc/get-prompt mcp-client name arguments))] + {:description (:description prompt) + :messages (mapv (fn [each-message] + {:role (string/lower-case (:role each-message)) + :content [(->content (:content each-message))]}) + (:messages prompt))}))) + +(defn get-resource! [uri db] + (when-let [mcp-client (->> (vals (:mcp-clients db)) + (keep (fn [{:keys [client resources]}] + (when (some #(= uri (:uri %)) resources) + client))) + first)] + (when-let [resource (->> {:on-error (fn [_id jsonrpc-error] + (logger/warn logger-tag "Error reading resource:" (:message jsonrpc-error)))} + (pmc/read-resource mcp-client uri))] + {:contents (mapv ->resource-content (:contents resource))}))) (defn shutdown! "Shutdown MCP servers in parallel waiting max 5s in total." @@ -503,9 +444,9 @@ (try (let [clients (vals (:mcp-clients @db*)) futures (doall - (pmap (fn [{:keys [^McpSyncClient client]}] + (pmap (fn [{:keys [client]}] (future - (try (.closeGracefully client) + (try (pmc/disconnect! client) (catch Exception _ nil)))) clients))] (doseq [f futures] From e3c0a58bb8576ce7024284c3fc754729d01fe49f Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Wed, 11 Mar 2026 18:29:08 -0300 Subject: [PATCH 2/6] trigger CI From c858ae12a44fa997af774aa9d96af427193badd0 Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Wed, 11 Mar 2026 18:31:16 -0300 Subject: [PATCH 3/6] retrigger CI From 8746ab0c56485f697e31cb8d47cac61bed47c4d4 Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Wed, 11 Mar 2026 19:12:20 -0300 Subject: [PATCH 4/6] Guard plumcp notification handlers to prevent pre-initialize deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit plumcp's default list_changed notification handlers (fetch-prompts, fetch-resources, fetch-tools) fire immediately when the server sends notifications before the initialize handshake completes. Since MCP servers ignore requests before initialize, these handlers block forever waiting for responses that never come, preventing the client from completing initialization. Override the three list_changed handlers with guarded versions that only dispatch after initialize-and-notify! succeeds. 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca --- src/eca/features/tools/mcp.clj | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/eca/features/tools/mcp.clj b/src/eca/features/tools/mcp.clj index 8314f0a60..2078e091f 100644 --- a/src/eca/features/tools/mcp.clj +++ b/src/eca/features/tools/mcp.clj @@ -100,14 +100,21 @@ (defn ^:private ->client [name transport init-timeout workspaces {:keys [on-tools-change]}] - (let [tools-consumer (fn [tools] + (let [initialized? (atom false) + tools-consumer (fn [tools] (logger/info logger-tag (format "[%s] Tools list changed, received %d tools" name (count tools))) (on-tools-change tools)) - tools-nhandler (fn [jsonrpc-notification] - (pcs/fetch-tools jsonrpc-notification - {:on-tools tools-consumer})) + ;; Guard handlers to ignore list_changed notifications arriving before + ;; initialize handshake completes. plumcp registers default handlers + ;; that send requests (e.g. prompts/list) on list_changed, but MCP + ;; servers ignore requests before initialize, causing a permanent block. + guarded-tools-nhandler (fn [jsonrpc-notification] + (when @initialized? + (pcs/fetch-tools jsonrpc-notification + {:on-tools tools-consumer}))) + guarded-nop (fn [_] nil) client (pmc/make-mcp-client {:info (pes/make-info name "current") :client-transport transport @@ -115,13 +122,16 @@ {:name (:name %)}) workspaces)} :notification-handlers - {psd/method-notifications-tools-list_changed tools-nhandler + {psd/method-notifications-tools-list_changed guarded-tools-nhandler + psd/method-notifications-prompts-list_changed guarded-nop + psd/method-notifications-resources-list_changed guarded-nop psd/method-notifications-message (fn [params] (logger/info logger-tag (format "[MCP-%s] %s" name (:data params))))} :print-banner? false})] (pmc/initialize-and-notify! client {:timeout-millis (* 1000 init-timeout)}) + (reset! initialized? true) client)) (defn ^:private ->server [mcp-name server-config status db] From e0f4c4dd2f831c57fe29013dee54e2236bf65969 Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Thu, 12 Mar 2026 10:36:01 -0300 Subject: [PATCH 5/6] Bump plumcp to 0.2.0-beta3, remove upstream-fixed workarounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove flushing-stdio-transport wrapper (flush bug fixed in beta3) - Replace manual initialized? atom with wrap-initialized-check 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca --- deps-lock.json | 78 +++++++++++++++++++++------------- deps.edn | 2 +- src/eca/features/tools/mcp.clj | 50 +++++----------------- 3 files changed, 60 insertions(+), 70 deletions(-) diff --git a/deps-lock.json b/deps-lock.json index fcba28bba..df1086f67 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -1053,24 +1053,24 @@ "hash": "sha256-RLTLjpPU9rJiwE7Qdx1w3WbnbUXX/HVYIGcaYmVcVDk=" }, { - "mvn-path": "io/github/plumce/plumcp.core-json-cheshire/0.2.0-beta2/plumcp.core-json-cheshire-0.2.0-beta2.jar", + "mvn-path": "io/github/plumce/plumcp.core-json-cheshire/0.2.0-beta3/plumcp.core-json-cheshire-0.2.0-beta3.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-m7kV4sKUFMzNhjiLpIWqdbU9h4038urAnCsWFHy7BHk=" + "hash": "sha256-zv+Trbz3BWVF2jWgADInlyjqsDsanbWKVYGOceV+WnM=" }, { - "mvn-path": "io/github/plumce/plumcp.core-json-cheshire/0.2.0-beta2/plumcp.core-json-cheshire-0.2.0-beta2.pom", + "mvn-path": "io/github/plumce/plumcp.core-json-cheshire/0.2.0-beta3/plumcp.core-json-cheshire-0.2.0-beta3.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-4lwPaipx5jhyog8/irYDq9jqp1DUXAY9ASVgxL5R8Y8=" + "hash": "sha256-w6nOMod07L7uKT2Vl+Ici/Oonq3CvGCCrJix3f0y7Cs=" }, { - "mvn-path": "io/github/plumce/plumcp.core/0.2.0-beta2/plumcp.core-0.2.0-beta2.jar", + "mvn-path": "io/github/plumce/plumcp.core/0.2.0-beta3/plumcp.core-0.2.0-beta3.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-eOwf4xkhsLcbpAvFF2mH8tQ/P7YKxqbOlYrsgoORSUQ=" + "hash": "sha256-M/6egOI3jY8hVnave6Cx12P5Hmd6M1J5QuZEszUx2nk=" }, { - "mvn-path": "io/github/plumce/plumcp.core/0.2.0-beta2/plumcp.core-0.2.0-beta2.pom", + "mvn-path": "io/github/plumce/plumcp.core/0.2.0-beta3/plumcp.core-0.2.0-beta3.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-6HEfDI5KnfLepGxDrVz69SrD5VsgqXot9rjnFr45bs8=" + "hash": "sha256-I6gSOKlouC7ZMoJYow4/XZEgwMeZYGLZwtj2oblpVoA=" }, { "mvn-path": "io/methvin/directory-watcher/0.17.3/directory-watcher-0.17.3.jar", @@ -2179,62 +2179,62 @@ }, { "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-fxJHLa7Y9rUXSYqqKrE6ViR1w+31FHjkWBzHYemJeaM=" }, { "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-GJwAxDNAdJai+7DsyzeQjJSVXZHq0b5IFWdE7MGBbZQ=" }, { "mvn-path": "org/clojure/clojure/1.11.0/clojure-1.11.0.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-PiH6daB+yd278bK1A1bPGAcQ0DmN6qT0TpHNYwRVWUc=" }, { "mvn-path": "org/clojure/clojure/1.11.0/clojure-1.11.0.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-SQjMS0yeYsmoFJb5PLWsb2lBd8xkXc87jOXkkavOHro=" }, { "mvn-path": "org/clojure/clojure/1.11.1/clojure-1.11.1.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-I4G26UI6tGUVFFWUSQPROlYkPWAGuRlK/Bv0+HEMtN4=" }, { "mvn-path": "org/clojure/clojure/1.11.1/clojure-1.11.1.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-IMRaGr7b2L4grvk2BQrjGgjBZ0CzL4dAuIOM3pb/y4o=" }, { "mvn-path": "org/clojure/clojure/1.11.2/clojure-1.11.2.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-iPqZkT1pIs+39kn1xGdQOHfLb8yMwW02948mSAhLqZc=" }, { "mvn-path": "org/clojure/clojure/1.11.2/clojure-1.11.2.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-FzbP/xCV4dT+/raogrut9ttB7+MV8pbw/aMtt//EExE=" }, { "mvn-path": "org/clojure/clojure/1.11.3/clojure-1.11.3.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-nDBUCTKOK5boXdK160t1gQxnt2unCuTQ9t3pvPtVsbc=" }, { "mvn-path": "org/clojure/clojure/1.11.3/clojure-1.11.3.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-DA2+Ge4NKpxXMQzr3dNWRD8NFlFMQmBHsGLjpXwNuK0=" }, { "mvn-path": "org/clojure/clojure/1.11.4/clojure-1.11.4.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-/H/xtmENDjSUp1zBHvgYEL2kAqwVcBL+TjuJlYbPQTM=" }, { "mvn-path": "org/clojure/clojure/1.11.4/clojure-1.11.4.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-a6YADmhI+Cw5y5tJqyqmo6Vi9MJNUrMeUZCuZJXwwwk=" }, { @@ -2267,6 +2267,26 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-55suCRfnPnPCX7N5PzFV+PD4jYAvUMJf1Sl3l3rDQiA=" }, + { + "mvn-path": "org/clojure/clojure/1.12.3/clojure-1.12.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-yyoaPbHCzXbvT6SlRdWmXxCxtIt/dnLwoQn1R28FcWY=" + }, + { + "mvn-path": "org/clojure/clojure/1.12.3/clojure-1.12.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-q2CmyyuMxXyG21ECte4p8hWsg8/20wEblV+fxb5dAZ0=" + }, + { + "mvn-path": "org/clojure/clojure/1.12.4/clojure-1.12.4.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-S4Hpum2jjEXZzFgCPGdAYrjJ8HFPM/8A3tIuapSdoXc=" + }, + { + "mvn-path": "org/clojure/clojure/1.12.4/clojure-1.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-xRh5Bi5B/58hr6DkCbGZ/+nyo8aucizf7F/Z26BeQXI=" + }, { "mvn-path": "org/clojure/core.async/1.5.648/core.async-1.5.648.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -2359,22 +2379,22 @@ }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.56/core.specs.alpha-0.2.56.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-/PRCveArBKhj8vzFjuaiowxM8Mlw99q4VjTwq3ERZrY=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.56/core.specs.alpha-0.2.56.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-AarxdIP/HHSCySoHKV1+e8bjszIt9EsptXONAg/wB0A=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-Bu6owHC75FwVhWfkQ0OWgbyMRukSNBT4G/oyukLWy8g=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-F3i70Ti9GFkLgFS+nZGdG+toCfhbduXGKFtn1Ad9MA4=" }, { @@ -2474,7 +2494,7 @@ }, { "mvn-path": "org/clojure/pom.contrib/0.3.0/pom.contrib-0.3.0.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-fxgrOypUPgV0YL+T/8XpzvasUn3xoTdqfZki6+ee8Rk=" }, { @@ -2489,22 +2509,22 @@ }, { "mvn-path": "org/clojure/spec.alpha/0.2.194/spec.alpha-0.2.194.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-z2iZ+YUpjGSxPqEplGrZAo3uja3w6rmuGORVAn04JJw=" }, { "mvn-path": "org/clojure/spec.alpha/0.2.194/spec.alpha-0.2.194.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-WhHw4eizwFLmUcSYxpRbRNs1Nb8sGHGf3PZd8fiLE+Y=" }, { "mvn-path": "org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.jar", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-Z+yJjrVcZqlXpVJ53YXRN2u5lL2HZosrDeHrO5foquA=" }, { "mvn-path": "org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.pom", - "mvn-repo": "https://repo.maven.apache.org/maven2/", + "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-bY3hTDrIdXYMX/kJVi/5hzB3AxxquTnxyxOeFp/pB1g=" }, { diff --git a/deps.edn b/deps.edn index ac5552e2d..5eddbd2de 100644 --- a/deps.edn +++ b/deps.edn @@ -3,7 +3,7 @@ org.clojure/core.async {:mvn/version "1.8.741"} org.babashka/cli {:mvn/version "0.8.65"} com.github.clojure-lsp/jsonrpc4clj {:mvn/version "1.0.2"} - io.github.plumce/plumcp.core-json-cheshire {:mvn/version "0.2.0-beta2"} + io.github.plumce/plumcp.core-json-cheshire {:mvn/version "0.2.0-beta3"} org.yaml/snakeyaml {:mvn/version "2.4"} ;; used by eca.shared for YAML parsing borkdude/dynaload {:mvn/version "0.3.5"} selmer/selmer {:mvn/version "1.12.69"} diff --git a/src/eca/features/tools/mcp.clj b/src/eca/features/tools/mcp.clj index 2078e091f..0e80d316d 100644 --- a/src/eca/features/tools/mcp.clj +++ b/src/eca/features/tools/mcp.clj @@ -18,7 +18,7 @@ [plumcp.core.schema.schema-defs :as psd] [plumcp.core.support.http-client :as phc]) (:import - [java.io IOException Writer])) + [java.io IOException])) (set! *warn-on-reflection* true) @@ -26,26 +26,6 @@ (def ^:private logger-tag "[MCP]") -(defn ^:private flushing-stdio-transport - "Wraps a plumcp STDIO transport to flush stdin after each message. - Workaround for plumcp not flushing BufferedWriter on send." - [inner-transport] - (let [writer-ref (atom nil)] - (reify pp/IClientTransport - (client-transport-info [_] (pp/client-transport-info inner-transport)) - (start-client-transport [_ on-message] - (let [result (pp/start-client-transport inner-transport on-message)] - (reset! writer-ref (:server-stdin result)) - result)) - (stop-client-transport! [_ force?] - (pp/stop-client-transport! inner-transport force?)) - (send-message-to-server [_ message] - (pp/send-message-to-server inner-transport message) - (when-let [^Writer w @writer-ref] - (.flush w))) - (upon-handshake-success [_ success] - (pp/upon-handshake-success inner-transport success))))) - (def ^:private env-var-regex #"\$(\w+)|\$\{([^}]+)\}") @@ -87,34 +67,27 @@ :uri shared/uri->filename) (config/get-property "user.home"))] - (flushing-stdio-transport - (psct/run-command + (psct/run-command {:command-tokens (into [(replace-env-vars command)] (map replace-env-vars) (or args [])) :dir work-dir :env (when env (update-keys env name)) :on-stderr-text (fn [msg] - (logger/info logger-tag (format "[%s] %s" server-name msg)))}))))) + (logger/info logger-tag (format "[%s] %s" server-name msg)))})))) (defn ^:private ->client [name transport init-timeout workspaces {:keys [on-tools-change]}] - (let [initialized? (atom false) - tools-consumer (fn [tools] + (let [tools-consumer (fn [tools] (logger/info logger-tag (format "[%s] Tools list changed, received %d tools" name (count tools))) (on-tools-change tools)) - ;; Guard handlers to ignore list_changed notifications arriving before - ;; initialize handshake completes. plumcp registers default handlers - ;; that send requests (e.g. prompts/list) on list_changed, but MCP - ;; servers ignore requests before initialize, causing a permanent block. - guarded-tools-nhandler (fn [jsonrpc-notification] - (when @initialized? - (pcs/fetch-tools jsonrpc-notification - {:on-tools tools-consumer}))) - guarded-nop (fn [_] nil) + tools-nhandler (pcs/wrap-initialized-check + (fn [jsonrpc-notification] + (pcs/fetch-tools jsonrpc-notification + {:on-tools tools-consumer}))) client (pmc/make-mcp-client {:info (pes/make-info name "current") :client-transport transport @@ -122,16 +95,13 @@ {:name (:name %)}) workspaces)} :notification-handlers - {psd/method-notifications-tools-list_changed guarded-tools-nhandler - psd/method-notifications-prompts-list_changed guarded-nop - psd/method-notifications-resources-list_changed guarded-nop + {psd/method-notifications-tools-list_changed tools-nhandler psd/method-notifications-message (fn [params] (logger/info logger-tag (format "[MCP-%s] %s" name (:data params))))} :print-banner? false})] (pmc/initialize-and-notify! client {:timeout-millis (* 1000 init-timeout)}) - (reset! initialized? true) client)) (defn ^:private ->server [mcp-name server-config status db] @@ -462,4 +432,4 @@ (doseq [f futures] (try (deref f 5000 nil) (catch Exception _ nil)))) (catch Exception _ nil)) - (swap! db* assoc :mcp-clients {})) + (swap! db* assoc :mcp-clients {})) \ No newline at end of file From e9d749e8c239bccad002e9113c86c94850f0bc1a Mon Sep 17 00:00:00 2001 From: Eric Dallo Date: Thu, 12 Mar 2026 15:27:47 -0300 Subject: [PATCH 6/6] fix nix --- deps-lock.json | 62 +++++++++++++++++--------------------------------- 1 file changed, 21 insertions(+), 41 deletions(-) diff --git a/deps-lock.json b/deps-lock.json index df1086f67..45a5d5321 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -2179,62 +2179,62 @@ }, { "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-fxJHLa7Y9rUXSYqqKrE6ViR1w+31FHjkWBzHYemJeaM=" }, { "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-GJwAxDNAdJai+7DsyzeQjJSVXZHq0b5IFWdE7MGBbZQ=" }, { "mvn-path": "org/clojure/clojure/1.11.0/clojure-1.11.0.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-PiH6daB+yd278bK1A1bPGAcQ0DmN6qT0TpHNYwRVWUc=" }, { "mvn-path": "org/clojure/clojure/1.11.0/clojure-1.11.0.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-SQjMS0yeYsmoFJb5PLWsb2lBd8xkXc87jOXkkavOHro=" }, { "mvn-path": "org/clojure/clojure/1.11.1/clojure-1.11.1.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-I4G26UI6tGUVFFWUSQPROlYkPWAGuRlK/Bv0+HEMtN4=" }, { "mvn-path": "org/clojure/clojure/1.11.1/clojure-1.11.1.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-IMRaGr7b2L4grvk2BQrjGgjBZ0CzL4dAuIOM3pb/y4o=" }, { "mvn-path": "org/clojure/clojure/1.11.2/clojure-1.11.2.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-iPqZkT1pIs+39kn1xGdQOHfLb8yMwW02948mSAhLqZc=" }, { "mvn-path": "org/clojure/clojure/1.11.2/clojure-1.11.2.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-FzbP/xCV4dT+/raogrut9ttB7+MV8pbw/aMtt//EExE=" }, { "mvn-path": "org/clojure/clojure/1.11.3/clojure-1.11.3.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-nDBUCTKOK5boXdK160t1gQxnt2unCuTQ9t3pvPtVsbc=" }, { "mvn-path": "org/clojure/clojure/1.11.3/clojure-1.11.3.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-DA2+Ge4NKpxXMQzr3dNWRD8NFlFMQmBHsGLjpXwNuK0=" }, { "mvn-path": "org/clojure/clojure/1.11.4/clojure-1.11.4.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-/H/xtmENDjSUp1zBHvgYEL2kAqwVcBL+TjuJlYbPQTM=" }, { "mvn-path": "org/clojure/clojure/1.11.4/clojure-1.11.4.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-a6YADmhI+Cw5y5tJqyqmo6Vi9MJNUrMeUZCuZJXwwwk=" }, { @@ -2267,26 +2267,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-55suCRfnPnPCX7N5PzFV+PD4jYAvUMJf1Sl3l3rDQiA=" }, - { - "mvn-path": "org/clojure/clojure/1.12.3/clojure-1.12.3.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-yyoaPbHCzXbvT6SlRdWmXxCxtIt/dnLwoQn1R28FcWY=" - }, - { - "mvn-path": "org/clojure/clojure/1.12.3/clojure-1.12.3.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-q2CmyyuMxXyG21ECte4p8hWsg8/20wEblV+fxb5dAZ0=" - }, - { - "mvn-path": "org/clojure/clojure/1.12.4/clojure-1.12.4.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-S4Hpum2jjEXZzFgCPGdAYrjJ8HFPM/8A3tIuapSdoXc=" - }, - { - "mvn-path": "org/clojure/clojure/1.12.4/clojure-1.12.4.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-xRh5Bi5B/58hr6DkCbGZ/+nyo8aucizf7F/Z26BeQXI=" - }, { "mvn-path": "org/clojure/core.async/1.5.648/core.async-1.5.648.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -2379,22 +2359,22 @@ }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.56/core.specs.alpha-0.2.56.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-/PRCveArBKhj8vzFjuaiowxM8Mlw99q4VjTwq3ERZrY=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.56/core.specs.alpha-0.2.56.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-AarxdIP/HHSCySoHKV1+e8bjszIt9EsptXONAg/wB0A=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-Bu6owHC75FwVhWfkQ0OWgbyMRukSNBT4G/oyukLWy8g=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-F3i70Ti9GFkLgFS+nZGdG+toCfhbduXGKFtn1Ad9MA4=" }, { @@ -2494,7 +2474,7 @@ }, { "mvn-path": "org/clojure/pom.contrib/0.3.0/pom.contrib-0.3.0.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-fxgrOypUPgV0YL+T/8XpzvasUn3xoTdqfZki6+ee8Rk=" }, { @@ -2509,22 +2489,22 @@ }, { "mvn-path": "org/clojure/spec.alpha/0.2.194/spec.alpha-0.2.194.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-z2iZ+YUpjGSxPqEplGrZAo3uja3w6rmuGORVAn04JJw=" }, { "mvn-path": "org/clojure/spec.alpha/0.2.194/spec.alpha-0.2.194.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-WhHw4eizwFLmUcSYxpRbRNs1Nb8sGHGf3PZd8fiLE+Y=" }, { "mvn-path": "org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-Z+yJjrVcZqlXpVJ53YXRN2u5lL2HZosrDeHrO5foquA=" }, { "mvn-path": "org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", + "mvn-repo": "https://repo.maven.apache.org/maven2/", "hash": "sha256-bY3hTDrIdXYMX/kJVi/5hzB3AxxquTnxyxOeFp/pB1g=" }, {