Add MCP servlet endpoints with unit tests #403
Draft
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.
Implements JSON API endpoints for MCP (Model Context Protocol) to enable AI agents to validate, render, and analyze PlantUML diagrams. Adds comprehensive unit test coverage.
Changes
Modified
McpServlet.java/mcp/check- Validates PlantUML source syntax{ok: boolean, errors: [{line, message}]}NullOutputStreamto avoid generating image data during validation/mcp/render- Changed response format{ok: boolean, format: string, dataBase64: string}{status, dataUrl, renderTimeMs, sha256}/mcp/metadata- Extracts diagram structureIndexOutOfBoundsException/mcp/workspace/put- Added as alias to/workspace/updateJSON error handling - Catches
JsonSyntaxExceptionand returns 400 statusCreated
McpServletTest.javaSix unit tests covering all new endpoints:
Tests use
WebappTestCaseframework for integration testing against embedded Jetty server.Example Usage
Notes
PLANTUML_MCP_ENABLED=trueenvironment variablesessionId+diagramIdmodelWarning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
ads.mozilla.org/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)detectportal.firefox.com/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)firefox.settings.services.mozilla.com/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)incoming.telemetry.mozilla.org/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)plantuml.com/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)prod.ohttp-gateway.prod.webservices.mozgcp.net/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)spocs.getpocket.com/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 20207 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:20207/,http://[::1]:20207/ -no-remote -profile /tmp/rust_mozprofileksX27z(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 28015 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:28015/,http://[::1]:28015/ -no-remote -profile /tmp/rust_mozprofilembHTFc(dns block)/usr/lib/firefox/firefox /usr/lib/firefox/firefox --marionette --headless --remote-debugging-port 21156 --remote-allow-hosts localhost --remote-allow-origins REDACTED,http://localhost:21156/,http://[::1]:21156/ -no-remote -profile /tmp/rust_mozprofileoJgHNm(dns block)www.eclipse.org/opt/hostedtoolcache/CodeQL/2.23.3/x64/codeql/tools/linux64/java/bin/java /opt/hostedtoolcache/CodeQL/2.23.3/x64/codeql/tools/linux64/java/bin/java -jar /opt/hostedtoolcache/CodeQL/2.23.3/x64/codeql/xml/tools/xml-extractor.jar --fileList=/home/REDACTED/work/plantuml-server/.codeql-scratch/dbs/java/working/files-to-index6979022589543905351.list --sourceArchiveDir=/home/REDACTED/work/plantuml-server/.codeql-scratch/dbs/java/src --outputDir=/home/REDACTED/work/plantuml-server/.codeql-scratch/dbs/java/trap/java(dns block)www.puppycrawl.com/opt/hostedtoolcache/CodeQL/2.23.3/x64/codeql/tools/linux64/java/bin/java /opt/hostedtoolcache/CodeQL/2.23.3/x64/codeql/tools/linux64/java/bin/java -jar /opt/hostedtoolcache/CodeQL/2.23.3/x64/codeql/xml/tools/xml-extractor.jar --fileList=/home/REDACTED/work/plantuml-server/.codeql-scratch/dbs/java/working/files-to-index6979022589543905351.list --sourceArchiveDir=/home/REDACTED/work/plantuml-server/.codeql-scratch/dbs/java/src --outputDir=/home/REDACTED/work/plantuml-server/.codeql-scratch/dbs/java/trap/java(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
This section details on the original issue you should resolve
<issue_title>Add a new MCP Servlet (
/mcp/*) with unit tests — target branchmcp1</issue_title><issue_description>
Summary
We would like to introduce a new MCP (Model Context Protocol) servlet inside
plantuml-server, exposing a structured HTTP/JSON API under/mcp/*.This API will be consumed by AI assistants (e.g., GitHub Copilot, ChatGPT, Claude, IDE integrations) to validate, analyse, and render PlantUML diagrams in a safe and structured way.
👉 Important: all development for this feature must target the dedicated branch:
mcp1.The goal of this issue is to define the minimal MCP feature set and especially provide unit tests so that Copilot can implement the servlet safely and consistently.
Goals
The MCP servlet must:
Provide predictable, JSON-only endpoints.
Never bypass existing PlantUML security rules.
Be fully isolated from the existing rendering servlets.
Offer a minimal but useful toolset for MCP clients:
Endpoints (initial version)
1. POST
/mcp/checkValidates a PlantUML source.
Request
{ "source": "@startuml\nAlice -> Bob: Hello\n@enduml" }Response
{ "ok": true, "errors": [] }If errors occur:
{ "ok": false, "errors": [ { "line": 2, "message": "Syntax error XYZ" } ] }2. POST
/mcp/renderRenders a diagram and returns a PNG encoded as Base64.
Request
{ "source": "@startuml\nAlice -> Bob\n@enduml" }Response
{ "ok": true, "format": "png", "dataBase64": "iVBORw0KGgoAAAANSUhEUgAA..." }Errors must follow the same structure as
/mcp/check.3. POST
/mcp/metadataReturns structural metadata extracted from a diagram (participants, classes, relationships, etc.).
Response example
{ "participants": ["Alice", "Bob"], "directives": ["skinparam ..."], "diagramType": "sequence", "warnings": [] }4. POST
/mcp/workspace/createCreates a new ephemeral workspace (in-memory only).
Response
{ "workspaceId": "w-8f1b0341" }5. POST
/mcp/workspace/putAdds or updates a file in the workspace.
Request
{ "workspaceId": "w-8f1b0341", "filename": "test.puml", "content": "..." }6. POST
/mcp/workspace/renderRenders a file stored in the workspace.
Implementation Notes
net.sourceforge.plantuml.server.servlet.McpServlet/mcp/*✔️ Unit Tests (JUnit 5)
These are essential for Copilot to generate correct code.
All tests must be placed in:
### 1. Test: check endpoint accepts valid diagram
2. Test: check endpoint should report syntax errors
3. Test: render endpoint returns Base64 PNG
4. Test: metadata endpoint returns participants
5. Test: workspace lifecycle