alibaba/arthas · error · McpError

Prompt with name '${promptName}' already exists

Error message

Prompt with name '${promptName}' already exists

What it means

Thrown by McpNettyServer.addPrompt() when a PromptSpecification whose prompt name already exists in the server's prompts map is registered again. Registration uses putIfAbsent, so a second add of the same name fails the returned future with this McpError instead of silently overwriting. Prompt names are the unique key for the MCP prompts capability.

Source

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

	// ---------------------------------------

	public CompletableFuture<Void> addPrompt(McpServerFeatures.PromptSpecification promptSpecification) {
		if (promptSpecification == null) {
			CompletableFuture<Void> future = new CompletableFuture<>();
			future.completeExceptionally(new McpError("Prompt specification must not be null"));
			return future;
		}
		if (this.serverCapabilities.getPrompts() == null) {
			CompletableFuture<Void> future = new CompletableFuture<>();
			future.completeExceptionally(new McpError("Server must be configured with prompt capabilities"));
			return future;
		}

		return CompletableFuture.supplyAsync(() -> {
			McpServerFeatures.PromptSpecification existing = this.prompts
					.putIfAbsent(promptSpecification.getPrompt().getName(), promptSpecification);
			if (existing != null) {
				throw new CompletionException(
						new McpError(
								"Prompt with name '" + promptSpecification.getPrompt().getName() + "' already exists"));
			}

			logger.debug("Added prompt handler: {}", promptSpecification.getPrompt().getName());
			return null;
		}).thenCompose(ignored -> {
			if (this.serverCapabilities.getPrompts().getListChanged()) {
				return notifyPromptsListChanged();
			}
			return CompletableFuture.completedFuture(null);
		}).exceptionally(ex -> {
			Throwable cause = (ex instanceof CompletionException) ? ex.getCause() : ex;
			logger.error("Error while adding prompt '{}'", promptSpecification.getPrompt().getName(), cause);
			throw new CompletionException(cause);
		});
	}

View on GitHub (pinned to 21cf2e9ba5)

Solutions

  1. Use unique prompt names per PromptSpecification; namespace them (e.g. 'plugin.promptName').
  2. Before addPrompt, check the existing set or call removePrompt first if you intend to replace.
  3. Centralize prompt registration in one place to avoid duplicate adds from different modules.
  4. Catch the CompletionException/McpError and log/skip on conflict rather than failing the whole startup.

Example fix

// before
server.addPrompt(duplicatePromptSpec).join(); // 'review' already exists

// after
String name = spec.getPrompt().getName();
server.removePrompt(name).exceptionally(ex -> null).join(); // ignore if absent
server.addPrompt(spec).join();
Defensive patterns

Strategy: validation

Validate before calling

// Only add a prompt if the name is not already registered
String name = spec.getPrompt().getName();
boolean absent = currentPromptNames.add(name); // shared ConcurrentHashMap.newKeySet()
if (absent) {
    server.addPrompt(spec).join();
}

Try / catch

try {
    server.addPrompt(spec).join();
} catch (CompletionException e) {
    if (e.getCause() instanceof McpError me && me.getMessage().contains("already exists")) {
        // upsert: remove then re-add
        server.removePrompt(spec.getPrompt().getName())
            .exceptionally(x -> null).join();
        server.addPrompt(spec).join();
    } else throw e;
}

Prevention

When it happens

Trigger: Calling addPrompt with a spec whose getPrompt().getName() collides with an already-registered prompt; re-running registration logic on startup without clearing prior state; adding the same prompt from two modules.

Common situations: Hot-reload/restart of registration code while the server object persists; two plugins registering a prompt named the same (e.g. 'review'); copy-paste of a PromptSpecification without renaming.

Related errors


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