Offer the development server and the browser to LLM nodes
A flow could write software but never run it: the coding agent edits files and that is where it ended. These two entries close the loop - start the code the execution wrote, then open it in a browser and read what the page says. Both are streamable-http, so an LLM node binds them with no new Java: the retriever already filtered the catalogue by transport and the draft normalizer already dropped unknown servers. The development server's preview is pinned to the execution with x-preview-key, the way the coding agent's workspace already is, so the scope travels in a header the service fills in rather than in an argument a model could choose for itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
51651a2013
commit
653cc0f6d6
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,27 @@ public class RetrieverControllerTest {
|
|||
List<String> 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue