Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package openQA for openSUSE:Factory checked 
in at 2025-09-08 09:56:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Mon Sep  8 09:56:58 2025 rev:742 rq:1302962 version:5.1757084700.fad3731d

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2025-09-05 
21:43:32.825239751 +0200
+++ /work/SRC/openSUSE:Factory/.openQA.new.1977/openQA.changes  2025-09-08 
09:57:41.686818537 +0200
@@ -1,0 +2,12 @@
+Fri Sep 05 21:13:12 UTC 2025 - [email protected]
+
+- Update to version 5.1757084700.fad3731d:
+  * Ensure no untracked files in unit test run part 2
+  * Dependency cron 2025-09-05
+  * Add MCP support as an optional feature
+  * Allow specifying a full domain via `file_security_policy`
+  * Allow using a different subdomain via `file_security_policy`
+  * t: Ensure no leftover files in git directory
+  * ci: Ensure clean git status with Test::CheckGitStatus
+
+-------------------------------------------------------------------

Old:
----
  openQA-5.1757005118.aac56dbc.obscpio

New:
----
  openQA-5.1757084700.fad3731d.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.F9UW4N/_old  2025-09-08 09:57:42.514852953 +0200
+++ /var/tmp/diff_new_pack.F9UW4N/_new  2025-09-08 09:57:42.518853118 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1757005118.aac56dbc
+Version:        5.1757084700.fad3731d
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-devel-test.spec ++++++
--- /var/tmp/diff_new_pack.F9UW4N/_old  2025-09-08 09:57:42.546854282 +0200
+++ /var/tmp/diff_new_pack.F9UW4N/_new  2025-09-08 09:57:42.546854282 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-devel
 Name:           %{short_name}-test
-Version:        5.1757005118.aac56dbc
+Version:        5.1757084700.fad3731d
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.F9UW4N/_old  2025-09-08 09:57:42.574855446 +0200
+++ /var/tmp/diff_new_pack.F9UW4N/_new  2025-09-08 09:57:42.574855446 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1757005118.aac56dbc
+Version:        5.1757084700.fad3731d
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.F9UW4N/_old  2025-09-08 09:57:42.602856610 +0200
+++ /var/tmp/diff_new_pack.F9UW4N/_new  2025-09-08 09:57:42.602856610 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1757005118.aac56dbc
+Version:        5.1757084700.fad3731d
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.F9UW4N/_old  2025-09-08 09:57:42.630857774 +0200
+++ /var/tmp/diff_new_pack.F9UW4N/_new  2025-09-08 09:57:42.630857774 +0200
@@ -94,12 +94,12 @@
 # The following line is generated from dependencies.yaml
 %define cover_requires perl(Devel::Cover) 
perl(Devel::Cover::Report::Codecovbash)
 # The following line is generated from dependencies.yaml
-%define devel_no_selenium_requires %build_requires %cover_requires %qemu 
%style_check_requires %test_requires curl perl(Perl::Tidy) postgresql-devel 
rsync sudo tar xorg-x11-fonts
+%define devel_no_selenium_requires %build_requires %cover_requires %qemu 
%style_check_requires %test_requires curl perl(Perl::Tidy) 
perl(Test::CheckGitStatus) postgresql-devel rsync sudo tar xorg-x11-fonts
 # The following line is generated from dependencies.yaml
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1757005118.aac56dbc
+Version:        5.1757084700.fad3731d
 Release:        0
 Summary:        The openQA web-frontend, scheduler and tools
 License:        GPL-2.0-or-later
@@ -221,7 +221,7 @@
 Requires:       %{mcp_requires}
 
 %description mcp
-This package contains additional resources for AI support in openQA.
+This package contains a plugin for AI support in openQA.
 
 %package client
 Summary:        Client tools for remote openQA management
@@ -642,6 +642,7 @@
 %{_datadir}/openqa/lib/OpenQA/Scheduler/
 %{_datadir}/openqa/lib/OpenQA/Schema/
 %{_datadir}/openqa/lib/OpenQA/WebAPI/
+%exclude %{_datadir}/openqa/lib/OpenQA/WebAPI/Plugin/MCP.pm
 %{_datadir}/openqa/lib/OpenQA/WebSockets/
 %{_datadir}/openqa/templates
 %{_datadir}/openqa/public
@@ -861,4 +862,5 @@
 %endif
 
 %files mcp
+%{_datadir}/openqa/lib/OpenQA/WebAPI/Plugin/MCP.pm
 

++++++ openQA-5.1757005118.aac56dbc.obscpio -> 
openQA-5.1757084700.fad3731d.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/.gitignore 
new/openQA-5.1757084700.fad3731d/.gitignore
--- old/openQA-5.1757005118.aac56dbc/.gitignore 2025-09-04 18:58:38.000000000 
+0200
+++ new/openQA-5.1757084700.fad3731d/.gitignore 2025-09-05 17:05:00.000000000 
+0200
@@ -1,6 +1,11 @@
 logs/*.tar.*
 etc/mine/
-cover_db/
+# Devel::Cover
+/cover_db*
+# TAP::Harness::JUnit
+/test-results/
+# CircleCI cache
+/logs/
 temp
 /testresults
 /t/data/openqa/webui/cache
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/Makefile 
new/openQA-5.1757084700.fad3731d/Makefile
--- old/openQA-5.1757005118.aac56dbc/Makefile   2025-09-04 18:58:38.000000000 
+0200
+++ new/openQA-5.1757084700.fad3731d/Makefile   2025-09-05 17:05:00.000000000 
+0200
@@ -269,7 +269,7 @@
 test-unit-and-integration: node_modules
        export GLOBIGNORE="$(GLOBIGNORE)";\
        export DEVEL_COVER_DB_FORMAT=JSON;\
-       export PERL5OPT="$(COVEROPT)$(PERL5OPT) -It/lib -I$(PWD)/t/lib 
-I$(PWD)/external/os-autoinst-common/lib -MOpenQA::Test::PatchDeparse";\
+       export PERL5OPT="$(COVEROPT)$(PERL5OPT) -It/lib -I$(PWD)/t/lib 
-I$(PWD)/external/os-autoinst-common/lib $(CHECK_GIT_STATUS_OPT) 
-MOpenQA::Test::PatchDeparse";\
        RETRY=${RETRY} HOOK=./tools/delete-coverdb-folder timeout -s SIGINT -k 
5 -v ${TIMEOUT_RETRIES} tools/retry "${PROVE}" ${PROVE_LIB_ARGS} ${PROVE_ARGS}
 
 .PHONY: setup-database
@@ -299,6 +299,10 @@
 COVEROPT ?= -mJSON::PP -It/lib -MCoverageWorkaround 
-MDevel::Cover=-select_re,'^/lib',+ignore_re,lib/perlcritic/Perl/Critic/Policy|t/lib/CoverageWorkaround,-coverage,statement,-db,cover_db$(COVERDB_SUFFIX),
 endif
 
+ifeq ($(CHECK_GIT_STATUS),1)
+CHECK_GIT_STATUS_OPT ?= -MTest::CheckGitStatus
+endif
+
 .PHONY: coverage
 coverage:
        export DEVEL_COVER_DB_FORMAT=JSON;\
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/cpanfile 
new/openQA-5.1757084700.fad3731d/cpanfile
--- old/openQA-5.1757005118.aac56dbc/cpanfile   2025-09-04 18:58:38.000000000 
+0200
+++ new/openQA-5.1757084700.fad3731d/cpanfile   2025-09-05 17:05:00.000000000 
+0200
@@ -119,6 +119,7 @@
     requires 'Perl::Critic';
     requires 'Perl::Critic::Community';
     requires 'Perl::Tidy', '== 20250711.0.0';
+    requires 'Test::CheckGitStatus';
 
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/dependencies.yaml 
new/openQA-5.1757084700.fad3731d/dependencies.yaml
--- old/openQA-5.1757005118.aac56dbc/dependencies.yaml  2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/dependencies.yaml  2025-09-05 
17:05:00.000000000 +0200
@@ -85,6 +85,7 @@
   tar:
   xorg-x11-fonts:
   perl(Perl::Tidy): '== 20250711.0.0'
+  perl(Test::CheckGitStatus):
 
 devel_requires:
   '%devel_no_selenium_requires':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/dist/rpm/openQA.spec 
new/openQA-5.1757084700.fad3731d/dist/rpm/openQA.spec
--- old/openQA-5.1757005118.aac56dbc/dist/rpm/openQA.spec       2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/dist/rpm/openQA.spec       2025-09-05 
17:05:00.000000000 +0200
@@ -94,7 +94,7 @@
 # The following line is generated from dependencies.yaml
 %define cover_requires perl(Devel::Cover) 
perl(Devel::Cover::Report::Codecovbash)
 # The following line is generated from dependencies.yaml
-%define devel_no_selenium_requires %build_requires %cover_requires %qemu 
%style_check_requires %test_requires curl perl(Perl::Tidy) postgresql-devel 
rsync sudo tar xorg-x11-fonts
+%define devel_no_selenium_requires %build_requires %cover_requires %qemu 
%style_check_requires %test_requires curl perl(Perl::Tidy) 
perl(Test::CheckGitStatus) postgresql-devel rsync sudo tar xorg-x11-fonts
 # The following line is generated from dependencies.yaml
 %define devel_requires %devel_no_selenium_requires chromedriver
 
@@ -222,7 +222,7 @@
 Requires:       %{mcp_requires}
 
 %description mcp
-This package contains additional resources for AI support in openQA.
+This package contains a plugin for AI support in openQA.
 
 %package client
 Summary:        Client tools for remote openQA management
@@ -642,6 +642,7 @@
 %{_datadir}/openqa/lib/OpenQA/Scheduler/
 %{_datadir}/openqa/lib/OpenQA/Schema/
 %{_datadir}/openqa/lib/OpenQA/WebAPI/
+%exclude %{_datadir}/openqa/lib/OpenQA/WebAPI/Plugin/MCP.pm
 %{_datadir}/openqa/lib/OpenQA/WebSockets/
 %{_datadir}/openqa/templates
 %{_datadir}/openqa/public
@@ -861,5 +862,6 @@
 %endif
 
 %files mcp
+%{_datadir}/openqa/lib/OpenQA/WebAPI/Plugin/MCP.pm
 
 %changelog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/docs/WritingTests.asciidoc 
new/openQA-5.1757084700.fad3731d/docs/WritingTests.asciidoc
--- old/openQA-5.1757005118.aac56dbc/docs/WritingTests.asciidoc 2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/docs/WritingTests.asciidoc 2025-09-05 
17:05:00.000000000 +0200
@@ -1919,6 +1919,81 @@
 ignore this, but piping there output into `tee` is usually enough to stop them
 outputting non-printable characters.
 
+=== MCP Support
+
+The https://modelcontextprotocol.io/[Model Context Protocol] (MCP) is a 
standard
+that allows Large Language Models (LLMs) to interact with web services. MCP is
+supported natively by openQA, but still considered experimental, and therefore
+needs to be enabled manually in `openqa.ini`.
+
+[source,ini]
+----
+[global]
+mcp_enabled = read-only
+----
+
+Once enabled the experimental MCP endpoint becomes available under the path
+`/experimental/mcp`. At the moment all implemented MCP tools are read-only,
+that means LLMs can review infomation provided by openQA, but are unable to
+make changes or trigger actions. Once write operations become available
+with future updates, they will have to be enabled with a different setting
+in `openqa.ini` for security reasons.
+
+Most MCP clients today support Bearer token authentication, so that is what
+openQA relies on as well. More authentication mechanisms will be added as
+the technology evolves.
+
+This example configuration in the `mcp.json` format, which is commonly used
+by MCP clients, shows how to include an openQA personal access token by
+setting the `Authorization` HTTP header:
+
+[source,json]
+----
+{
+  "mcpServers": {
+    "openqa": {
+      "url": "http://127.0.0.1:9526/experimental/mcp";,
+      "headers": {
+        "Authorization": "Bearer USER:KEY:SECRET"
+      }
+    }
+  }
+}
+----
+
+==== 3rd Party MCP Clients
+===== gemini-cli
+
+Once you have installed and set up
+https://github.com/google-gemini/gemini-cli[gemini-cli], you can use the
+`gemini mcp` command to add openQA:
+
+[source,shell]
+----
+gemini mcp add openqa http://127.0.0.1:9526/experimental/mcp -H 
'Authorization: Bearer USER:KEY:SECRET' -t http
+----
+
+After restarting gemini-cli, it will automatically discover available openQA
+tools and make use of them on its own:
+
+[source]
+----
+╭────────────────────────────────────────────────────╮
+│  > give me a brief summary for openQA job 5265754  │
+╰────────────────────────────────────────────────────╯
+
+ ╭───────────────────────────────────────────────────────────────────╮
+ │ ✔ openqa_get_job_info (openqa MCP Server) openqa_get_job_info...  │
+ │                                                                   │
+ │    ...                                                            │
+ ╰───────────────────────────────────────────────────────────────────╯
+✦ Job 5265754 was a passed test named opensuse-Tumbleweed-DVD-x86_64-
+  Build20250825-ltp_net_features@64bit. It ran on August 26, 2025,
+  and took about 21 minutes to complete. The job tested the
+  ltp_net_features test suite on the x86_64 architecture. Most of the
+   tests passed, with a few being skipped.
+----
+
 
 == Test Development tricks
 === Trigger new tests by modifying settings from existing test runs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/etc/nginx/vhosts.d/openqa-assets.inc 
new/openQA-5.1757084700.fad3731d/etc/nginx/vhosts.d/openqa-assets.inc
--- old/openQA-5.1757005118.aac56dbc/etc/nginx/vhosts.d/openqa-assets.inc       
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/etc/nginx/vhosts.d/openqa-assets.inc       
2025-09-05 17:05:00.000000000 +0200
@@ -8,5 +8,5 @@
 
 # Enforce download of assets so HTML assets cannot highjack session
 # note: Can be disabled when using the alternative of making a redirect to a
-#       different subdomain mentioned in "openqa-locations.inc".
+#       different domain mentioned in "openqa-locations.inc".
 add_header Content-Disposition 'attachment; filename="$1"';
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/etc/nginx/vhosts.d/openqa-locations.inc 
new/openQA-5.1757084700.fad3731d/etc/nginx/vhosts.d/openqa-locations.inc
--- old/openQA-5.1757005118.aac56dbc/etc/nginx/vhosts.d/openqa-locations.inc    
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/etc/nginx/vhosts.d/openqa-locations.inc    
2025-09-05 17:05:00.000000000 +0200
@@ -2,9 +2,9 @@
 #location /assets {
 #    include vhosts.d/openqa-assets.inc;
 #
-#    ## Alternatively, make a redirect to a different subdomain in accordance
-#    ## with the file_subdomain in openQA config
-#    #return 301 http://file.$host$request_uri;
+#    ## Alternatively, make a redirect to a different domain in accordance with
+#    ## the file_security_policy in openQA config
+#    #return 301 http://$host-files$request_uri;
 #}
 
 include vhosts.d/openqa-endpoints.inc;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/etc/nginx/vhosts.d/openqa.conf.template 
new/openQA-5.1757084700.fad3731d/etc/nginx/vhosts.d/openqa.conf.template
--- old/openQA-5.1757005118.aac56dbc/etc/nginx/vhosts.d/openqa.conf.template    
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/etc/nginx/vhosts.d/openqa.conf.template    
2025-09-05 17:05:00.000000000 +0200
@@ -18,14 +18,14 @@
 #    include vhosts.d/openqa-locations.inc;
 #}
 
-# Provide different servers for serving assets under a different subdomain
+# Provide different servers for serving assets under a different domain
 # (enable these in accordance to "Optional faster asset downloads …" in
 # "openqa-locations.inc")
 #server {
 #    listen       80;
 #    listen       [::]:80;
-#    # Set server_name in accordance with the file_subdomain in openQA config
-#    server_name  file.openqa.example.com;
+#    # Set server_name in accordance with the file_security_policy in openQA 
config
+#    server_name  openqa-files.example.com;
 #    location /assets {
 #        include vhosts.d/openqa-assets.inc;
 #    }
@@ -34,8 +34,8 @@
 #server {
 #    listen       443;
 #    listen       [::]:443;
-#    # Set server_name in accordance with the file_subdomain in openQA config
-#    server_name  file.openqa.example.com;
+#    # Set server_name in accordance with the file_security_policy in openQA 
config
+#    server_name  openqa-files.example.com;
 #    location /assets {
 #        include vhosts.d/openqa-assets.inc;
 #    }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/etc/openqa/openqa.ini 
new/openQA-5.1757084700.fad3731d/etc/openqa/openqa.ini
--- old/openQA-5.1757005118.aac56dbc/etc/openqa/openqa.ini      2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/etc/openqa/openqa.ini      2025-09-05 
17:05:00.000000000 +0200
@@ -41,6 +41,12 @@
 ##   So don't enable this plugin in production.
 #monitoring_enabled = 0
 
+## Set to read-only to enable MCP support
+## * A Model Context Protocol endpoint will be available to all users under
+##   /experimental/mcp route.
+## * Do not enable this feature for openQA instances with special security 
requirements!
+#mcp_enabled = no
+
 ## space-separated list of extra plugins to load; plugin must be under
 ## OpenQA::WebAPI::Plugin and correctly-cased module name given here,
 ## this example loads OpenQA::WebAPI::Plugin::AMQP
@@ -56,17 +62,17 @@
 ##   avoid potentially malicious JavaScript code from hijacking the user 
session.
 ## * Set to "insecure-browsing" so these files can be browsed directly. This is
 ##   insecure as potentially malicious JavaScript can hijack the user session.
-## * Set to "subdomain:…" to let openQA redirect these files to another
-##   subdomain, e.g. "subdomain:file" will redirect file downloads from e.g.
-##   "example.openqa.org" to "file.example.openqa.org". The files will no 
longer
-##   be served as attachments to HTML files can be browsed conveniently and
-##   securely from that subdomain.
+## * Set to "domain:…" to let openQA redirect these files to another domain,
+##   e.g. "domain:openqa-files.org" can be used to redirect file downloads from
+##   e.g. "openqa.org" to "openqa-files.org". The files will no longer be 
served
+##   as attachments so HTML files can be browsed conveniently and securely from
+##   that second domain.
 ## note: Does *not* affect files served via a reverse proxy. The default NGINX
 ##       config contained by the openQA repo shows how to enforce a download
 ##       prompt for assets served via NGINX. It also shows how to setup
-##       redirections to a different subdomain which is a little bit more 
config
-##       effort and you also need to make sure your certificate is valid for
-##       this subdomain.
+##       redirections to a different domain which is a little bit more config
+##       effort and you also need to make sure you have a valid certificate for
+##       the other domain.
 #file_security_policy = download-prompt
 
 ## space-separated list of domains recognized by job labeling
@@ -317,6 +323,8 @@
 #worker_limit_retry_delay = 900
 ## Maximum number of retries with exponential back-off for GruJobs to wait for 
a GruTask to appear in the database
 #wait_for_grutask_retries = 6
+## Maximum size of MCP results in bytes (to prevent performance issues)
+# mcp_max_result_size = 500000
 
 [archiving]
 ## Moves logs of jobs which are preserved during the cleanup because they are 
considered important
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Schema/ResultSet/Workers.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/Schema/ResultSet/Workers.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Schema/ResultSet/Workers.pm     
1970-01-01 01:00:00.000000000 +0100
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/Schema/ResultSet/Workers.pm     
2025-09-05 17:05:00.000000000 +0200
@@ -0,0 +1,23 @@
+# Copyright SUSE LLC
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+package OpenQA::Schema::ResultSet::Workers;
+use Mojo::Base 'DBIx::Class::ResultSet', -signatures;
+
+sub stats ($self) {
+    my $total = $self->count;
+    my $total_online = grep { !$_->dead } $self->all();
+    my $free_active_workers = grep { !$_->dead } $self->search({job_id => 
undef, error => undef})->all();
+    my $free_broken_workers = grep { !$_->dead } $self->search({job_id => 
undef, error => {'!=' => undef}})->all();
+    my $busy_workers = grep { !$_->dead } $self->search({job_id => {'!=' => 
undef}})->all();
+
+    return {
+        total => $total,
+        total_online => $total_online,
+        free_active_workers => $free_active_workers,
+        free_broken_workers => $free_broken_workers,
+        busy_workers => $busy_workers,
+    };
+}
+
+1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Setup.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/Setup.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Setup.pm        2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/Setup.pm        2025-09-05 
17:05:00.000000000 +0200
@@ -77,6 +77,7 @@
             max_rss_limit => 0,
             profiling_enabled => 0,
             monitoring_enabled => 0,
+            mcp_enabled => 'no',
             plugins => undef,
             hide_asset_types => 'repo',
             file_security_policy => 'download-prompt',
@@ -236,6 +237,7 @@
             max_online_workers => 1000,
             wait_for_grutask_retries => 6,    # exponential, ~4 minutes
             worker_limit_retry_delay => ONE_HOUR / 4,
+            mcp_max_result_size => 500000,
         },
         archiving => {
             archive_preserved_important_jobs => 0,
@@ -308,8 +310,8 @@
 }
 
 sub _validate_security_policy ($app, $config) {
-    if ($config->{file_security_policy} =~ 
m/^(download-prompt|insecure-browsing|subdomain:(.+))$/) {
-        if (defined(my $subdomain = $2)) { $config->{file_subdomain} = 
"$subdomain." }
+    if ($config->{file_security_policy} =~ 
m/^(download-prompt|insecure-browsing|domain:(.+))$/) {
+        if (defined(my $domain = $2)) { $config->{file_domain} = $domain }
     }
     else {
         $config->{file_security_policy} = 'download-prompt';
@@ -409,6 +411,7 @@
     push @{$server->plugins->namespaces}, 'OpenQA::WebAPI::Plugin';
     $server->plugin($_) for qw(Helpers MIMETypes CSRF REST Gru YAML);
     $server->plugin('AuditLog') if $server->config->{global}{audit_enabled};
+    $server->plugin('MCP') if $server->config->{global}{mcp_enabled} eq 
'read-only';
     # Load arbitrary plugins defined in config: 'plugins' in section
     # '[global]' can be a space-separated list of plugins to load, by
     # module name under OpenQA::WebAPI::Plugin::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Shared/Controller/Auth.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/Shared/Controller/Auth.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Shared/Controller/Auth.pm       
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/Shared/Controller/Auth.pm       
2025-09-05 17:05:00.000000000 +0200
@@ -12,7 +12,7 @@
 sub check ($self) {
     my $config = $self->app->config;
     return 1 if $config->{no_localhost_auth} && $self->is_local_request;
-    return 0 if $self->via_subdomain($config->{global}->{file_subdomain});
+    return 0 if $self->via_domain($config->{global}->{file_domain});
 
     my $req = $self->req;
     my $headers = $req->headers;
@@ -46,9 +46,9 @@
 sub auth ($self) {
     my $log = $self->app->log;
 
-    # Prevent authentication via file subdomain (where potentially untrusted 
HTML is served)
-    if ($self->via_subdomain($self->config->{global}->{file_subdomain})) {
-        $self->render(json => {error => 'Forbidden via file subdomain'}, 
status => 403);
+    # Prevent authentication via file domain (where potentially untrusted HTML 
is served)
+    if ($self->via_domain($self->config->{global}->{file_domain})) {
+        $self->render(json => {error => 'Forbidden via file domain'}, status 
=> 403);
         return 0;
     }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Shared/Controller/Session.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/Shared/Controller/Session.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Shared/Controller/Session.pm    
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/Shared/Controller/Session.pm    
2025-09-05 17:05:00.000000000 +0200
@@ -56,8 +56,8 @@
     my $config = $self->app->config;
     my $auth_method = $config->{auth}->{method};
     my $auth_module = "OpenQA::WebAPI::Auth::$auth_method";
-    return $self->render(text => 'Forbidden via file subdomain', status => 403)
-      if $self->via_subdomain($config->{global}->{file_subdomain});
+    return $self->render(text => 'Forbidden via file domain', status => 403)
+      if $self->via_domain($config->{global}->{file_domain});
 
     # prevent redirecting loop when referrer is login page
     $ref = 'index' if !$ref or $ref eq $self->url_for('login');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Shared/Plugin/SharedHelpers.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/Shared/Plugin/SharedHelpers.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/Shared/Plugin/SharedHelpers.pm  
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/Shared/Plugin/SharedHelpers.pm  
2025-09-05 17:05:00.000000000 +0200
@@ -21,7 +21,7 @@
     $app->helper(is_admin => \&_is_admin);
     $app->helper(is_local_request => \&_is_local_request);
     $app->helper(render_specific_not_found => \&_render_specific_not_found);
-    $app->helper(via_subdomain => \&_via_subdomain);
+    $app->helper(via_domain => \&_via_domain);
 }
 
 # returns the isotovideo command server web socket URL and the VNC argument 
for the given job or undef if not available
@@ -80,9 +80,6 @@
     return $c->render(status => 404, text => "$title - $error_message");
 }
 
-sub _via_subdomain ($c, $subdomain) {
-    return 0 unless defined $subdomain;
-    return index($c->req->url->to_abs->host, $subdomain) == 0;
-}
+sub _via_domain ($c, $domain) { defined $domain && $c->req->url->to_abs->host 
eq $domain }
 
 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm
--- 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm  
    2025-09-04 18:58:38.000000000 +0200
+++ 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm  
    2025-09-05 17:05:00.000000000 +0200
@@ -29,12 +29,7 @@
 
 sub index ($self) {
     my $workers_db = $self->schema->resultset('Workers');
-    my $total_online = grep { !$_->dead } $workers_db->all();
-    my $total = $workers_db->count;
-    my $free_active_workers = grep { !$_->dead } $workers_db->search({job_id 
=> undef, error => undef})->all();
-    my $free_broken_workers
-      = grep { !$_->dead } $workers_db->search({job_id => undef, error => 
{'!=' => undef}})->all();
-    my $busy_workers = grep { !$_->dead } $workers_db->search({job_id => {'!=' 
=> undef}})->all();
+    my $worker_stats = $workers_db->stats;
 
     my %workers;
     while (my $w = $workers_db->next) {
@@ -42,11 +37,11 @@
         $workers{$w->name} = _extend_info($w);
     }
     $self->stash(
-        workers_online => $total_online,
-        total => $total,
-        workers_active_free => $free_active_workers,
-        workers_broken_free => $free_broken_workers,
-        workers_busy => $busy_workers,
+        workers_online => $worker_stats->{total_online},
+        total => $worker_stats->{total},
+        workers_active_free => $worker_stats->{free_active_workers},
+        workers_broken_free => $worker_stats->{free_broken_workers},
+        workers_busy => $worker_stats->{busy_workers},
         is_admin => !!$self->is_admin,
         workers => \%workers
     );
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/WebAPI/Controller/File.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/WebAPI/Controller/File.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/WebAPI/Controller/File.pm       
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/WebAPI/Controller/File.pm       
2025-09-05 17:05:00.000000000 +0200
@@ -173,13 +173,11 @@
 
 sub _redirect_if_configured ($self, $is_text) {
     # skip harmless text files as the viewer doesn't follow redirects and 
those files are not problematic anyway
-    return 0 if $is_text || !defined(my $subdomain = 
$self->app->config->{global}->{file_subdomain});
-    # redirect to configured subdomain so potentially dangerious HTML files 
cannot use the current session
+    return 0 if $is_text || !defined(my $domain = 
$self->app->config->{global}->{file_domain});
+    # redirect to configured domain so potentially dangerious HTML files 
cannot use the current session
     my $url = $self->req->url->to_abs;
-    my $host = $url->host;
-    # skip if already redirected
-    return 0 unless index($host, $subdomain) == -1;
-    $url->host($subdomain . $host);
+    return 0 if $url->host eq $domain;    # skip if already redirected
+    $url->host($domain);
     $self->redirect_to($url);
     return 1;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/lib/OpenQA/WebAPI/Plugin/MCP.pm 
new/openQA-5.1757084700.fad3731d/lib/OpenQA/WebAPI/Plugin/MCP.pm
--- old/openQA-5.1757005118.aac56dbc/lib/OpenQA/WebAPI/Plugin/MCP.pm    
1970-01-01 01:00:00.000000000 +0100
+++ new/openQA-5.1757084700.fad3731d/lib/OpenQA/WebAPI/Plugin/MCP.pm    
2025-09-05 17:05:00.000000000 +0200
@@ -0,0 +1,175 @@
+# Copyright SUSE LLC
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+package OpenQA::WebAPI::Plugin::MCP;
+use Mojo::Base 'Mojolicious::Plugin', -signatures;
+
+use MCP::Server;
+use Mojo::Template;
+use Mojo::Loader qw(data_section);
+use Mojo::File qw(path);
+
+sub register ($self, $app, $config) {
+    my $mcp = MCP::Server->new;
+    $mcp->name('openQA');
+    $mcp->version('1.0.0');
+
+    $mcp->tool(
+        name => 'openqa_get_info',
+        description => 'Get information about the openQA server, connected 
workers and the current user',
+        code => \&tool_openqa_get_info
+    );
+
+    $mcp->tool(
+        name => 'openqa_get_job_info',
+        description => 'Get information about a specific openQA job',
+        input_schema => {
+            type => 'object',
+            properties => {job_id => {type => 'integer', minimum => 1}},
+            required => ['job_id'],
+        },
+        code => \&tool_openqa_get_job_info
+    );
+
+    $mcp->tool(
+        name => 'openqa_get_log_file',
+        description => 'Get the content of a specific log file for an openQA 
job',
+        input_schema => {
+            type => 'object',
+            properties => {
+                job_id => {type => 'integer', minimum => 1},
+                file_name => {type => 'string', minLength => 1}
+            },
+            required => ['job_id', 'file_name'],
+        },
+        code => \&tool_openqa_get_log_file
+    );
+
+    my $mcp_auth = 
$app->routes->under('/')->to('Auth#auth')->name('mcp_ensure_user');
+    $mcp_auth->any('/experimental/mcp' => $mcp->to_action)->name('mcp');
+}
+
+sub tool_openqa_get_info ($tool, $args) {
+    my $c = _get_controller($tool);
+    my $user = _get_user($tool);
+    my $schema = _get_schema($tool);
+
+    my $worker_stats = $schema->resultset('Workers')->stats;
+    my $vars = {
+        app_name => $c->stash->{appname},
+        app_version => $c->stash->{current_version} // 'n/a',
+        user_name => $user->name,
+        user_id => $user->id,
+        is_admin => $user->is_admin ? 'yes' : 'no',
+        is_operator => $user->is_operator ? 'yes' : 'no',
+        workers_total => $worker_stats->{total},
+        workers_total_online => $worker_stats->{total_online},
+        workers_offline => $worker_stats->{total} - 
$worker_stats->{total_online},
+        active_workers => $worker_stats->{free_active_workers},
+        broken_workers => $worker_stats->{free_broken_workers},
+        busy_workers => $worker_stats->{busy_workers},
+    };
+    return _render_from_data_section('openqa_get_info.txt.ep', $vars);
+}
+
+sub tool_openqa_get_job_info ($tool, $args) {
+    my $job_id = $args->{job_id};
+
+    my $schema = _get_schema($tool);
+    return $tool->text_result('Job does not exist', 1)
+      unless my $job = $schema->resultset('Jobs')->find(int($job_id));
+    my @comments = map { $_->extended_hash } $job->search_related(comments => 
{})->all;
+
+    my $info = $job->to_hash(assets => 1, check_assets => 1, deps => 1, 
details => 1, parent_group => 1);
+    return _render_from_data_section('openqa_get_job_info.txt.ep', {job => 
$info, comments => \@comments});
+}
+
+sub tool_openqa_get_log_file ($tool, $args) {
+    my $job_id = $args->{job_id};
+    my $file_name = $args->{file_name};
+
+    # Prevent directory traversal attacks
+    return $tool->text_result('Invalid file name', 1) unless $file_name =~ 
/^[\w.\-]+$/;
+
+    # Only text logs for now
+    return $tool->text_result('File type not yet supported via MCP', 1) if 
$file_name !~ /\.txt$/;
+
+    my $schema = _get_schema($tool);
+    return $tool->text_result('Job does not exist', 1)
+      unless my $job = $schema->resultset('Jobs')->find(int($job_id));
+
+    my $dir = $job->result_dir;
+    my $file = path($dir, $file_name);
+    return $tool->text_result('Log file does not exist', 1) unless -r $file;
+
+    my $c = _get_controller($tool);
+    my $max = $c->app->config->{misc_limits}{mcp_max_result_size};
+    return $tool->text_result('File too large to be transmitted via MCP', 1) 
if -s $file > $max;
+    return $tool->text_result($file->slurp);
+}
+
+sub _get_controller ($tool) { $tool->context->{controller} }
+sub _get_schema ($tool) { _get_controller($tool)->schema }
+sub _get_user ($tool) { _get_controller($tool)->stash->{current_user}{user} }
+
+sub _render_from_data_section ($template_name, $vars) {
+    my $template = data_section(__PACKAGE__, $template_name);
+    return Mojo::Template->new->vars(1)->render($template, $vars);
+}
+
+1;
+__DATA__
+@@ openqa_get_info.txt.ep
+Server: <%= $app_name %> (<%= $app_version %>)
+Current User: <%= $user_name %> (id: <%= $user_id %>, admin: <%= $is_admin %>, 
operator: <%= $is_operator %>)
+Workers: <%= $workers_total %>
+  - online: <%= $workers_total_online %>
+  - offline: <%= $workers_offline %>
+  - idle: <%= $active_workers %>
+  - busy: <%= $busy_workers %>
+  - broken: <%= $broken_workers %>
+
+@@ openqa_get_job_info.txt.ep
+Job ID:   <%= $job->{id}         // 'Unknown' %>
+Name:     <%= $job->{name}       // 'Unknown' %>
+Group:   <%= $job->{group}       // 'Unknown' %>
+Priority: <%= $job->{priority}   // 'Unknown' %>
+State:    <%= $job->{state}      // 'Unknown' %>
+Result:   <%= $job->{result}     // 'Unknown' %>
+Started:  <%= $job->{t_started}  // 'Never' %>
+Finished: <%= $job->{t_finished} // 'Never' %>
+
+Test Results:
+% if (@{$job->{testresults}}) {
+  % for my $result (@{$job->{testresults}}) {
+  - <%= $result->{name} %>: <%= $result->{result} %>
+  % }
+% }
+% else {
+  No test results available yet
+% }
+
+Test Settings:
+% for my $setting (sort keys %{$job->{settings}}) {
+  - <%= $setting %>: <%= $job->{settings}{$setting} %>
+% }
+
+Available Logs:
+% if (@{$job->{logs}}) {
+  % for my $log (@{$job->{logs}}) {
+  - <%= $log %>
+  % }
+% }
+% else {
+  No logs available
+% }
+
+Comments:
+% if (@$comments) {
+  % for my $comment (@$comments) {
+  - <%= $comment->{userName} %> (<%= $comment->{created} %>): <%= 
$comment->{renderedMarkdown} %>
+  % }
+% }
+% else {
+  No comments yet
+% }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/32-openqa_client.t 
new/openQA-5.1757084700.fad3731d/t/32-openqa_client.t
--- old/openQA-5.1757005118.aac56dbc/t/32-openqa_client.t       2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/t/32-openqa_client.t       2025-09-05 
17:05:00.000000000 +0200
@@ -8,11 +8,12 @@
 use FindBin;
 use lib "$FindBin::Bin/lib", 
"$FindBin::Bin/../external/os-autoinst-common/lib";
 use Test::Mojo;
-use Mojo::File qw(tempfile path);
+use Mojo::File qw(tempfile path tempdir);
 use OpenQA::Events;
 use OpenQA::Test::Case;
 use OpenQA::Test::Client 'client';
 use OpenQA::Test::TimeLimit '80';
+use Mojo::Util qw(scope_guard);
 
 plan skip_all => 'set HEAVY=1 to execute (takes longer)' unless $ENV{HEAVY};
 
@@ -22,6 +23,10 @@
 # allow up to 200MB - videos mostly
 $ENV{MOJO_MAX_MESSAGE_SIZE} = 207741824;
 
+my $tempdir = tempdir("$FindBin::Script-XXXX", TMPDIR => 1);
+chdir $tempdir;
+my $guard = scope_guard sub { chdir $FindBin::Bin };
+
 my @client_args = (apikey => 'PERCIVALKEY02', apisecret => 'PERCIVALSECRET02');
 my $t = client(Test::Mojo->new('OpenQA::WebAPI'), @client_args);
 my $client = $t->ua;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/37-limit_assets.t 
new/openQA-5.1757084700.fad3731d/t/37-limit_assets.t
--- old/openQA-5.1757005118.aac56dbc/t/37-limit_assets.t        2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/t/37-limit_assets.t        2025-09-05 
17:05:00.000000000 +0200
@@ -6,7 +6,7 @@
 
 use FindBin;
 use lib "$FindBin::Bin/lib", 
"$FindBin::Bin/../external/os-autoinst-common/lib";
-use Mojo::File 'path';
+use Mojo::File qw(path tempdir);
 use Test::Mojo;
 use Test::Warnings ':report_warnings';
 use Test::MockModule;
@@ -41,6 +41,8 @@
 # assume some assets already have a last_use_job_id
 $assets->find($_)->update({last_use_job_id => 99963}) for (2, 3);
 
+my $tempdir = tempdir("$FindBin::Script-XXXX", TMPDIR => 1);
+
 note('Asset directory: ' . assetdir());
 
 subtest 'configurable concurrency' => sub {
@@ -55,6 +57,7 @@
 };
 
 subtest 'filesystem removal' => sub {
+    local $ENV{OPENQA_SHAREDIR} = "$tempdir/openqa";
     my $asset_sub_dir = path(assetdir(), 'foo');
     $asset_sub_dir->remove_tree->make_path;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/t/43-scheduling-and-worker-scalability.t 
new/openQA-5.1757084700.fad3731d/t/43-scheduling-and-worker-scalability.t
--- old/openQA-5.1757005118.aac56dbc/t/43-scheduling-and-worker-scalability.t   
2025-09-04 18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/t/43-scheduling-and-worker-scalability.t   
2025-09-05 17:05:00.000000000 +0200
@@ -12,7 +12,7 @@
 use Scalar::Util 'looks_like_number';
 use List::Util qw(min max);
 use Mojo::File qw(path tempfile);
-use Mojo::Util 'dumper';
+use Mojo::Util qw(dumper scope_guard);
 use IPC::Run qw(start);
 use FindBin;
 use lib "$FindBin::Bin/lib", 
"$FindBin::Bin/../external/os-autoinst-common/lib";
@@ -56,6 +56,8 @@
 
 # setup basedir, config dir and database
 my $tempdir = setup_fullstack_temp_dir('scalability');
+chdir $tempdir;
+my $guard = scope_guard sub { chdir $FindBin::Bin };
 my $schema = OpenQA::Test::Database->new->create;
 my $workers = $schema->resultset('Workers');
 my $jobs = $schema->resultset('Jobs');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/api/03-auth.t 
new/openQA-5.1757084700.fad3731d/t/api/03-auth.t
--- old/openQA-5.1757005118.aac56dbc/t/api/03-auth.t    2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/t/api/03-auth.t    2025-09-05 
17:05:00.000000000 +0200
@@ -273,17 +273,17 @@
       ->json_is({error => 'invalid personal access token'});
 };
 
-subtest 'auth forbidden via subdomain' => sub {
+subtest 'auth forbidden via domain' => sub {
     my $rendered;
     my $req = Mojo::Message::Request->new;
-    $req->url->parse('http://foobar.openqa.de/test/42');
+    $req->url->parse('http://foobar-openqa.de/test/42');
     my $controller_mock = Test::MockModule->new('Mojolicious::Controller');
     $controller_mock->redefine(req => $req);
     $controller_mock->redefine(render => sub ($c, @args) { $rendered = [@args] 
});
     my $c = OpenQA::Shared::Controller::Auth->new(app => $t->app, req => $req);
-    $c->config->{global}->{file_subdomain} = 'foobar.';
-    is $c->auth, 0, 'auth denied via subdomain';
-    my %expected_json = (error => 'Forbidden via file subdomain');
+    $c->config->{global}->{file_domain} = 'foobar-openqa.de';
+    is $c->auth, 0, 'auth denied via domain';
+    my %expected_json = (error => 'Forbidden via file domain');
     my @expected = (json => \%expected_json, status => 403);
     is_deeply $rendered, \@expected, 'expected error and status';
     $req->url->parse('http://openqa.de/test/42');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/api/04-jobs.t 
new/openQA-5.1757084700.fad3731d/t/api/04-jobs.t
--- old/openQA-5.1757005118.aac56dbc/t/api/04-jobs.t    2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/t/api/04-jobs.t    2025-09-05 
17:05:00.000000000 +0200
@@ -9,6 +9,7 @@
 use lib "$FindBin::Bin/../lib", 
"$FindBin::Bin/../../external/os-autoinst-common/lib";
 use Mojo::Base -signatures;
 use File::Temp;
+use File::Copy::Recursive qw(dircopy);
 use Test::Mojo;
 use Test::Output;
 use Test::Warnings ':report_warnings';
@@ -39,7 +40,7 @@
 note("OPENQA_BASEDIR: $tempdir");
 path($tempdir, '/openqa/testresults')->make_path;
 my $share_dir = path($tempdir, 'openqa/share')->make_path;
-symlink "$FindBin::Bin/../data/openqa/share/factory", "$share_dir/factory";
+dircopy("$FindBin::Bin/../data/openqa/share/factory", "$share_dir/factory");
 
 # ensure job events are logged
 $ENV{OPENQA_CONFIG} = $tempdir;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/api/17-mcp.t 
new/openQA-5.1757084700.fad3731d/t/api/17-mcp.t
--- old/openQA-5.1757005118.aac56dbc/t/api/17-mcp.t     1970-01-01 
01:00:00.000000000 +0100
+++ new/openQA-5.1757084700.fad3731d/t/api/17-mcp.t     2025-09-05 
17:05:00.000000000 +0200
@@ -0,0 +1,161 @@
+#!/usr/bin/env perl
+
+# Copyright SUSE LLC
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+use Test::Most;
+use utf8;
+use FindBin;
+use lib "$FindBin::Bin/../lib", 
"$FindBin::Bin/../../external/os-autoinst-common/lib";
+use Mojo::Base -signatures;
+use Test::Mojo;
+use Test::Warnings ':report_warnings';
+use Test::MockModule;
+use OpenQA::Test::TimeLimit '30';
+use OpenQA::Test::Case;
+use OpenQA::Test::Client 'client';
+use MCP::Client;
+
+OpenQA::Test::Case->new(config_directory => "$FindBin::Bin/../data/17-mcp")
+  ->init_data(fixtures_glob => '01-jobs.pl 02-workers.pl 03-users.pl');
+
+my $PERSONAL_ACCESS_TOKEN = 'lance:LANCELOTKEY01:MANYPEOPLEKNOW';
+
+my $t = Test::Mojo->new('OpenQA::WebAPI');
+my $client = MCP::Client->new(ua => $t->ua, url => 
$t->ua->server->url->path('/experimental/mcp'));
+
+subtest 'Authentication' => sub {
+    $t->get_ok('/experimental/mcp')->status_is(403)->json_is({error => 'no api 
key'});
+
+    $t->ua->on(start => sub ($ua, $tx) { 
$tx->req->headers->authorization("Bearer $PERSONAL_ACCESS_TOKEN") });
+    $t->get_ok('/experimental/mcp')->status_is(405);
+};
+
+subtest 'Start session' => sub {
+    is $client->session_id, undef, 'no session id';
+    my $result = $client->initialize_session;
+    is $result->{serverInfo}{name}, 'openQA', 'server name';
+    is $result->{serverInfo}{version}, '1.0.0', 'server version';
+    ok $result->{capabilities}, 'has capabilities';
+    ok $client->session_id, 'session id set';
+};
+
+subtest 'List tools' => sub {
+    my $result = $client->list_tools;
+    is scalar @{$result->{tools}}, 3, 'three tools available';
+    is $result->{tools}[0]{name}, 'openqa_get_info', 'right tool name';
+    is $result->{tools}[1]{name}, 'openqa_get_job_info', 'right tool name';
+    is $result->{tools}[2]{name}, 'openqa_get_log_file', 'right tool name';
+};
+
+subtest 'openqa_get_info tool' => sub {
+    my $result = $client->call_tool('openqa_get_info');
+    ok !$result->{isError}, 'not an error';
+    my $text = $result->{content}[0]{text};
+    like $text, qr/Server: openQA \(.+\)/, 'openQA server';
+    like $text, qr/Current User: lance \(id: 99902, admin: no, operator: 
no\)/, 'current user';
+    like $text, qr/Workers: 2/, 'total workers';
+    like $text, qr/- online: 0/, 'online workers';
+    like $text, qr/- offline: 2/, 'offline workers';
+    like $text, qr/- idle: 0/, 'idle workers';
+    like $text, qr/- busy: 0/, 'busy workers';
+    like $text, qr/- broken: 0/, 'broken workers';
+};
+
+subtest 'openqa_get_job_info tool' => sub {
+    subtest 'Failed job' => sub {
+        my $result = $client->call_tool('openqa_get_job_info', {job_id => 
99938});
+        ok !$result->{isError}, 'not an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/Job ID: +99938/, 'has job id';
+        like $text, qr/Name: 
+opensuse-Factory-DVD-x86_64-Build0048-doc\@64bit/, 'has name';
+        like $text, qr/Group: +opensuse/, 'has group';
+        like $text, qr/Priority: +36/, 'has priority';
+        like $text, qr/State: +done/, 'has state';
+        like $text, qr/Result: +failed/, 'has result';
+        like $text, qr/Started: +\d+-\d+-\d+T\d+:\d+:\d+/, 'has started time';
+        like $text, qr/Finished: +\d+-\d+-\d+T\d+:\d+:\d+/, 'has finished 
time';
+        like $text, qr/- autoinst-log\.txt/s, 'has log files listed';
+        like $text, qr/No test results available yet/s, 'no test results yet';
+        like $text, qr/DISTRI: opensuse/, 'settings are present';
+        like $text, qr/VERSION: Factory/, 'settings are present';
+        like $text, qr/No comments yet/, 'no comments yet';
+    };
+
+    subtest 'Passed job' => sub {
+        subtest 'Add comment to test job' => sub {
+            $t->post_ok("/api/v1/jobs/99764/comments" => form => {text => 
'Just a test comment'})->status_is(200);
+        };
+
+        my $result = $client->call_tool('openqa_get_job_info', {job_id => 
99764});
+        ok !$result->{isError}, 'not an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/Job ID: +99764/, 'has job id';
+        like $text, qr/Name: 
+opensuse-13.1-DVD-x86_64-Build0091-console_tests\@64bit/, 'has name';
+        like $text, qr/Group: +Unknown/, 'unknown group';
+        like $text, qr/Priority: +35/, 'has priority';
+        like $text, qr/State: +done/, 'has state';
+        like $text, qr/Result: +passed/, 'has result';
+        like $text, qr/Started: +\d+-\d+-\d+T\d+:\d+:\d+/, 'has started time';
+        like $text, qr/Finished: +\d+-\d+-\d+T\d+:\d+:\d+/, 'has finished 
time';
+        like $text, qr/No logs available/s, 'no log files listed';
+        like $text, qr/No test results available yet/s, 'no test results yet';
+        like $text, qr/DISTRI: opensuse/, 'settings are present';
+        like $text, qr/VERSION: 13\.1/, 'settings are present';
+        like $text, qr/lance.+Just a test comment/, 'has comments';
+    };
+
+    subtest 'Input validation failed' => sub {
+        eval { $client->call_tool('openqa_get_job_info', {job_id => 'abc'}) };
+        like $@, qr/Error -32602: Invalid arguments/, 'right error message';
+    };
+};
+
+subtest 'openqa_get_log_file tool' => sub {
+    subtest 'Failed job' => sub {
+        my $result = $client->call_tool('openqa_get_log_file', {job_id => 
99938, file_name => 'autoinst-log.txt'});
+        ok !$result->{isError}, 'not an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/starting: \/usr\/bin\/qemu-kvm/, 'has log content from 
start';
+        like $text, qr/test logpackages died/, 'has log content from end';
+    };
+
+    subtest 'Input validation failed' => sub {
+        eval { $client->call_tool('openqa_get_log_file', {job_id => 'abc', 
file_name => 'autoinst-log.txt'}) };
+        like $@, qr/Error -32602: Invalid arguments/, 'right error message';
+        eval { $client->call_tool('openqa_get_log_file', {job_id => 99938, 
file_name => ''}) };
+        like $@, qr/Error -32602: Invalid arguments/, 'right error message';
+    };
+
+    subtest 'Path traversal attack' => sub {
+        my $result = $client->call_tool('openqa_get_log_file', {job_id => 
99938, file_name => '../../etc/passwd'});
+        ok $result->{isError}, 'is an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/Invalid file name/, 'error message';
+    };
+
+    subtest 'Job does not exist' => sub {
+        local $t->app->config->{misc_limits}{mcp_max_result_size} = 100;
+        my $result = $client->call_tool('openqa_get_log_file', {job_id => 
999999999, file_name => 'autoinst-log.txt'});
+        ok $result->{isError}, 'is an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/Job does not exist/, 'error message';
+    };
+
+    subtest 'Unsupported file type' => sub {
+        my $result = $client->call_tool('openqa_get_log_file', {job_id => 
99938, file_name => 'video.ogv'});
+        ok $result->{isError}, 'is an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/File type not yet supported via MCP/, 'error message';
+    };
+
+    subtest 'File too large' => sub {
+        local $t->app->config->{misc_limits}{mcp_max_result_size} = 100;
+        my $result = $client->call_tool('openqa_get_log_file', {job_id => 
99938, file_name => 'autoinst-log.txt'});
+        ok $result->{isError}, 'is an error';
+        my $text = $result->{content}[0]{text};
+        like $text, qr/File too large to be transmitted via MCP/, 'error 
message';
+    };
+};
+
+done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/config.t 
new/openQA-5.1757084700.fad3731d/t/config.t
--- old/openQA-5.1757005118.aac56dbc/t/config.t 2025-09-04 18:58:38.000000000 
+0200
+++ new/openQA-5.1757084700.fad3731d/t/config.t 2025-09-05 17:05:00.000000000 
+0200
@@ -52,6 +52,7 @@
             max_rss_limit => 0,
             profiling_enabled => 0,
             monitoring_enabled => 0,
+            mcp_enabled => 'no',
             hide_asset_types => 'repo',
             file_security_policy => 'download-prompt',
             recognized_referers => [],
@@ -188,6 +189,7 @@
             max_online_workers => 1000,
             wait_for_grutask_retries => 6,
             worker_limit_retry_delay => ONE_HOUR / 4,
+            mcp_max_result_size => 500000,
         },
         archiving => {
             archive_preserved_important_jobs => 0,
@@ -333,10 +335,10 @@
     $config{file_security_policy} = 'wrong';
     combined_like { OpenQA::Setup::_validate_security_policy($app, \%config) } 
qr/Invalid.*security/, 'warning logged';
     is $config{file_security_policy}, 'download-prompt', 'default to 
"download-prompt" on invalid value';
-    is $config{file_subdomain}, undef, 'file_subdomain not populated yet';
-    $config{file_security_policy} = 'subdomain:foo';
+    is $config{file_domain}, undef, 'file_domain not populated yet';
+    $config{file_security_policy} = 'domain:openqa-foo';
     OpenQA::Setup::_validate_security_policy($app, \%config);
-    is $config{file_subdomain}, 'foo.', 'file_subdomain populated via 
"subdomain:"';
+    is $config{file_domain}, 'openqa-foo', 'file_domain populated via 
"domain:"';
 };
 
 subtest 'Multiple config files' => sub {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/t/data/17-mcp/openqa.ini 
new/openQA-5.1757084700.fad3731d/t/data/17-mcp/openqa.ini
--- old/openQA-5.1757005118.aac56dbc/t/data/17-mcp/openqa.ini   1970-01-01 
01:00:00.000000000 +0100
+++ new/openQA-5.1757084700.fad3731d/t/data/17-mcp/openqa.ini   2025-09-05 
17:05:00.000000000 +0200
@@ -0,0 +1,2 @@
+[global]
+mcp_enabled = read-only
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1757005118.aac56dbc/t/ui/07-file.t 
new/openQA-5.1757084700.fad3731d/t/ui/07-file.t
--- old/openQA-5.1757005118.aac56dbc/t/ui/07-file.t     2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/t/ui/07-file.t     2025-09-05 
17:05:00.000000000 +0200
@@ -141,12 +141,12 @@
 
$t->get_ok('/assets/hdd/foo.qcow2')->status_is(200)->content_type_is('application/octet-stream');
 $t->get_ok('/assets/repo/testrepo/doesnotexist')->status_is(404);
 
-subtest 'redirection to different subdomain' => sub {
+subtest 'redirection to different domain' => sub {
     my $config = $t->app->config->{global};
-    $config->{file_security_policy} = 'subdomain:file';
-    $config->{file_subdomain} = 'file.';
+    $config->{file_security_policy} = 'domain:openqa-files';
+    $config->{file_domain} = 'openqa-files';
     $t->get_ok('/assets/repo/testrepo/README.html')->status_is(302);
-    $t->header_like(Location => 
qr|^http://file\.[^/]*/assets/repo/testrepo/README.html$|);
+    $t->header_like(Location => 
qr|^http://openqa-files(:\d+)?/assets/repo/testrepo/README.html$|);
 };
 
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/tools/ci/ci-packages.txt 
new/openQA-5.1757084700.fad3731d/tools/ci/ci-packages.txt
--- old/openQA-5.1757005118.aac56dbc/tools/ci/ci-packages.txt   2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/tools/ci/ci-packages.txt   2025-09-05 
17:05:00.000000000 +0200
@@ -59,6 +59,7 @@
 perl-Crypt-DES-2.07
 perl-Crypt-DH-GMP-0.00012
 perl-Crypt-Rijndael-1.13
+perl-CryptX-0.87.0
 perl-CSS-Minifier-XS-0.13
 perl-Data-Dump-1.23
 perl-Data-Dumper-Concise-2.023
@@ -135,7 +136,7 @@
 perl-Log-Contextual-0.008001
 perl-LWP-MediaTypes-6.02
 perl-LWP-Protocol-https-6.06
-perl-MCP
+perl-MCP-0.40.0
 perl-Meta-Builder-0.004
 perl-Minion-10.310.0
 perl-Minion-Backend-SQLite-5.0.7
@@ -221,6 +222,7 @@
 perl-Syntax-Keyword-Try-0.28
 perl-TAP-Harness-JUnit-0.42
 perl-Task-Weaken-1.06
+perl-Test-CheckGitStatus
 perl-Test-Deep-1.127
 perl-Test-Differences-0.64
 perl-Test-Exception-0.430000
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1757005118.aac56dbc/tools/ci/run_unit_tests.sh 
new/openQA-5.1757084700.fad3731d/tools/ci/run_unit_tests.sh
--- old/openQA-5.1757005118.aac56dbc/tools/ci/run_unit_tests.sh 2025-09-04 
18:58:38.000000000 +0200
+++ new/openQA-5.1757084700.fad3731d/tools/ci/run_unit_tests.sh 2025-09-05 
17:05:00.000000000 +0200
@@ -16,6 +16,8 @@
 # that context in the prove calling context.
 export PERL_TEST_HARNESS_DUMP_TAP=test-results
 export HARNESS='--harness TAP::Harness::JUnit --timer'
+# Activate Test::CheckGitStatus
+export CHECK_GIT_STATUS=1
 mkdir -p test-results/junit
 sudo chown -R $USER test-results
 # circleCI can be particularly slow sometimes since some time around 2021-06

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.F9UW4N/_old  2025-09-08 09:58:03.827738776 +0200
+++ /var/tmp/diff_new_pack.F9UW4N/_new  2025-09-08 09:58:03.835739109 +0200
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1757005118.aac56dbc
-mtime: 1757005118
-commit: aac56dbcc9ccbfc204e7afc14817cddf0b2a01a0
+version: 5.1757084700.fad3731d
+mtime: 1757084700
+commit: fad3731d7a90a6ca62baa984464d258845f20ca7
 

Reply via email to