diff --git a/src/main/resources/mcp-servers.json b/src/main/resources/mcp-servers.json index c9cea58..326e905 100644 --- a/src/main/resources/mcp-servers.json +++ b/src/main/resources/mcp-servers.json @@ -122,6 +122,83 @@ } } } + }, + { + "id": "dev-server-mcp", + "name": "Development Server MCP Server", + "description": "Starts and observes preconfigured development services. One instance per execution, in a throwaway copy of that execution's workspace, each on its own port. The caller chooses a service id and nothing else: commands, arguments and directories belong to the operator.", + "transport": "streamable-http", + "url": "${{host}}/dev-server/mcp", + "headers": { + "Authorization": "Bearer ${{key}}", + "x-preview-key": "${{context.rootExecutionId}}" + }, + "configurationSchema": { + "type": "object", + "additionalProperties": false, + "required": ["host", "key"], + "properties": { + "host": { + "type": "string", + "title": "Host", + "description": "Base host for the development server MCP endpoint, including scheme and without a trailing slash.", + "examples": ["https://host.it"], + "minLength": 1 + }, + "key": { + "type": "string", + "title": "API Key", + "description": "Bearer token used to authorize requests to the development server MCP server.", + "format": "password", + "writeOnly": true, + "minLength": 1, + "x-ui-bindable-as-input": true, + "x-ui-binding-static-label": "static value", + "x-ui-binding-input-label": "node input", + "x-ui-input-name": "key", + "x-ui-input-type": "TEXT", + "x-ui-input-multiple": false + } + } + } + }, + { + "id": "browser-mcp", + "name": "Browser MCP Server", + "description": "Drives a headless browser against the origins its operator allows, and reports what the page says: text snapshots, console messages and network errors. Screenshots are taken but a text-only caller receives a placeholder, not the image.", + "transport": "streamable-http", + "url": "${{host}}/browser/mcp", + "headers": { + "Authorization": "Bearer ${{key}}" + }, + "configurationSchema": { + "type": "object", + "additionalProperties": false, + "required": ["host", "key"], + "properties": { + "host": { + "type": "string", + "title": "Host", + "description": "Base host for the browser MCP endpoint, including scheme and without a trailing slash.", + "examples": ["https://host.it"], + "minLength": 1 + }, + "key": { + "type": "string", + "title": "API Key", + "description": "Bearer token used to authorize requests to the browser MCP server.", + "format": "password", + "writeOnly": true, + "minLength": 1, + "x-ui-bindable-as-input": true, + "x-ui-binding-static-label": "static value", + "x-ui-binding-input-label": "node input", + "x-ui-input-name": "key", + "x-ui-input-type": "TEXT", + "x-ui-input-multiple": false + } + } + } } ] } diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/RetrieverControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/RetrieverControllerTest.java index 482361f..5aeb7d3 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/controllers/RetrieverControllerTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/RetrieverControllerTest.java @@ -49,10 +49,27 @@ public class RetrieverControllerTest { List values = retrieverController.getValues("MCPServers", "streamableHttpServers", Map.of()); assertTrue(values.contains("coding-agent-mcp")); + // The three that make a development loop: write the code, run it, look at it in a browser. + assertTrue(values.contains("dev-server-mcp")); + assertTrue(values.contains("browser-mcp")); assertFalse(values.contains("postgres")); assertFalse(values.contains("url-downloader")); } + @Test + public void pinsTheDevServerPreviewToTheExecutionThatAskedForIt() { + // The key names a scope the model may not choose for itself, so it travels as a header the + // service fills in - the same way the coding agent's workspace is pinned. A tool argument + // would let one execution ask for another's preview by name. + MCPServersProvider.MCPServerDefinition devServer = mcpServersProvider.getServers().stream() + .filter(server -> "dev-server-mcp".equals(server.id())) + .findFirst() + .orElseThrow(); + + assertEquals("${{context.rootExecutionId}}", devServer.headers().get("x-preview-key")); + assertEquals("streamable-http", devServer.transport()); + } + @Test public void llmModelsAreOpenForAProviderThatCannotListThem() { // Gemini's catalogue is not listable without a credential and its names move constantly, so