alibaba/arthas · error · McpError

Resource with URI '${resourceUri}' already exists

Error message

Resource with URI '${resourceUri}' already exists

What it means

McpNettyServer.addResource throws McpError (wrapped in CompletionException) when a resource with the same URI is already registered. Resources are stored in a map keyed by Resource.getUri(), and putIfAbsent returning a non-null value means the URI is taken; duplicate URIs would otherwise overwrite each other.

Source

Thrown at arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpNettyServer.java:435

	// Resource Management
	// ---------------------------------------

	public CompletableFuture<Void> addResource(McpServerFeatures.ResourceSpecification resourceSpecification) {
		if (resourceSpecification == null || resourceSpecification.getResource() == null) {
			CompletableFuture<Void> future = new CompletableFuture<>();
			future.completeExceptionally(new McpError("Resource must not be null"));
			return future;
		}
		if (this.serverCapabilities.getResources() == null) {
			CompletableFuture<Void> future = new CompletableFuture<>();
			future.completeExceptionally(new McpError("Server must be configured with resource capabilities"));
			return future;
		}

		return CompletableFuture.supplyAsync(() -> {
			if (this.resources.putIfAbsent(resourceSpecification.getResource().getUri(),
					resourceSpecification) != null) {
				throw new CompletionException(new McpError(
						"Resource with URI '" + resourceSpecification.getResource().getUri() + "' already exists"));
			}
			logger.debug("Added resource handler: {}", resourceSpecification.getResource().getUri());
			return null;
		}).thenCompose(ignored -> {
			if (this.serverCapabilities.getResources().getListChanged()) {
				return notifyResourcesListChanged();
			}
			return CompletableFuture.completedFuture(null);
		}).exceptionally(ex -> {
			Throwable cause = ex instanceof CompletionException ? ex.getCause() : ex;
			logger.error("Error while adding resource '{}'", resourceSpecification.getResource().getUri(), cause);
			throw new CompletionException(cause);
		});
	}

	public CompletableFuture<Void> removeResource(String resourceUri) {
		if (resourceUri == null) {

View on GitHub (pinned to 21cf2e9ba5)

Solutions

  1. Call removeResource(uri) before addResource when refreshing.
  2. Check the existing resources map for the URI before adding.
  3. Ensure each resource exposes a unique URI.

Example fix

// before
server.addResource(spec); // uri 'jfr://file/123' already exists

// after
server.removeResource("jfr://file/123")
    .thenRun(() -> server.addResource(spec));
Defensive patterns

Strategy: validation

Validate before calling

String uri = spec.getResource().getUri();
boolean exists = server.getResources().stream()
    .anyMatch(r -> r.getResource().getUri().equals(uri));
if (exists) server.removeResource(uri).join();
server.addResource(spec).join();

Try / catch

try { server.addResource(spec).join(); }
catch (CompletionException e) {
  if (e.getCause() instanceof io.modelcontextprotocol.spec.McpError && e.getCause().getMessage().contains("already exists")) { server.removeResource(uri).thenRun(() -> server.addResource(spec)).join(); }
  else throw e;
}

Prevention

When it happens

Trigger: Calling addResource twice with resources whose getUri() is identical; hot reload without removeResource first; two providers contributing the same resource URI.

Common situations: Dynamic resource registration on reconnect; resource discovery registering the same URI across modules; URI normalization differences leading to an unexpected exact collision.

Related errors


AI-assisted analysis of alibaba/arthas@21cf2e9ba5 (2026-08-14). Data as JSON: /api/errors/15910fcf447feb54. Report an issue: GitHub.