Compare commits

..

2 Commits

Author SHA1 Message Date
Yamila Moreno
d953222eb4 🔧 Add CI for MCP server (#8293) 2026-02-09 11:25:24 +01:00
David Barragán Merino
a4f2641cc9 🔧 Enable observability for plugin docs and packages 2026-02-06 18:01:11 +01:00
13 changed files with 172 additions and 12 deletions

View File

@@ -59,6 +59,7 @@ jobs:
mv penpot/frontend bundle-frontend
mv penpot/exporter bundle-exporter
mv penpot/storybook bundle-storybook
mv penpot/mcp bundle-mcp
popd
- name: Set up Docker Buildx
@@ -89,6 +90,7 @@ jobs:
backend
exporter
storybook
mcp
labels: |
bundle_version=${{ steps.bundles.outputs.bundle_version }}
@@ -152,6 +154,21 @@ jobs:
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Build and push MCP Docker image
uses: docker/build-push-action@v6
env:
DOCKER_IMAGE: 'mcp'
BUNDLE_PATH: './bundle-mcp'
with:
context: ./docker/images/
file: ./docker/images/Dockerfile.mcp
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master

View File

@@ -362,11 +362,12 @@ function usage {
echo "- isolated-shell Starts a bash shell in a new devenv container."
echo "- log-devenv Show logs of the running devenv docker compose service."
echo ""
echo "- build-bundle Build all bundles (frontend, backend and exporter)."
echo "- build-bundle Build all bundles (frontend, backend, exporter, storybook and mcp)."
echo "- build-frontend-bundle Build frontend bundle"
echo "- build-backend-bundle Build backend bundle."
echo "- build-exporter-bundle Build exporter bundle."
echo "- build-storybook-bundle Build storybook bundle."
echo "- build-mcp-bundle Build mcp bundle."
echo "- build-docs-bundle Build docs bundle."
echo ""
echo "- build-docker-images Build all docker images (frontend, backend and exporter)."
@@ -446,7 +447,7 @@ case $1 in
build-exporter-bundle)
build-exporter-bundle;
;;
build-storybook-bundle)
build-storybook-bundle;
;;

View File

@@ -41,17 +41,22 @@ export class PenpotMcpServer {
sse: {} as Record<string, { transport: SSEServerTransport; userToken?: string }>,
};
public readonly host: string;
public readonly port: number;
public readonly webSocketPort: number;
public readonly replPort: number;
private readonly port: number;
private readonly webSocketPort: number;
private readonly replPort: number;
private readonly listenAddress: string;
/**
* the address (domain name or IP address) via which clients can reach the MCP server
*/
public readonly serverAddress: string;
constructor(private isMultiUser: boolean = false) {
// read port configuration from environment variables
this.host = process.env.PENPOT_MCP_SERVER_HOST ?? "0.0.0.0";
this.port = parseInt(process.env.PENPOT_MCP_SERVER_PORT ?? "4401", 10);
this.webSocketPort = parseInt(process.env.PENPOT_MCP_WEBSOCKET_PORT ?? "4402", 10);
this.replPort = parseInt(process.env.PENPOT_MCP_REPL_PORT ?? "4403", 10);
this.listenAddress = process.env.PENPOT_MCP_SERVER_LISTEN_ADDRESS ?? "0.0.0.0";
this.serverAddress = process.env.PENPOT_MCP_SERVER_ADDRESS ?? "0.0.0.0";
this.configLoader = new ConfigurationLoader(process.cwd());
this.apiDocs = new ApiDocs();
@@ -229,12 +234,12 @@ export class PenpotMcpServer {
this.setupHttpEndpoints();
return new Promise((resolve) => {
this.app.listen(this.port, this.host, async () => {
this.app.listen(this.port, this.listenAddress, async () => {
this.logger.info(`Multi-user mode: ${this.isMultiUserMode()}`);
this.logger.info(`Remote mode: ${this.isRemoteMode()}`);
this.logger.info(`Modern Streamable HTTP endpoint: http://${this.host}:${this.port}/mcp`);
this.logger.info(`Legacy SSE endpoint: http://${this.host}:${this.port}/sse`);
this.logger.info(`WebSocket server URL: ws://${this.host}:${this.webSocketPort}`);
this.logger.info(`Modern Streamable HTTP endpoint: http://${this.serverAddress}:${this.port}/mcp`);
this.logger.info(`Legacy SSE endpoint: http://${this.serverAddress}:${this.port}/sse`);
this.logger.info(`WebSocket server URL: ws://${this.serverAddress}:${this.webSocketPort}`);
// start the REPL server
await this.replServer.start();

View File

@@ -88,7 +88,9 @@ export class ReplServer {
return new Promise((resolve) => {
this.server = this.app.listen(this.port, () => {
this.logger.info(`REPL server started on port ${this.port}`);
this.logger.info(`REPL interface URL: http://${this.pluginBridge.mcpServer.host}:${this.port}`);
this.logger.info(
`REPL interface URL: http://${this.pluginBridge.mcpServer.serverAddress}:${this.port}`
);
resolve();
});
});

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/colors-to-tokens-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/contrast-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/create-palette-plugin" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/icons-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/rename-layers-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/table-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "dist/doc" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "dist/apps/example-styles" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]]
pattern = "WORKER_URI"
custom_domain = true