Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
profile: production

exclude_paths:
- .github/
- tests/integration
52 changes: 49 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,66 @@ jobs:
"python": "3.12"
}
]
integration:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

integration will be ran using zuul, this workflow should be removed

runs-on: ubuntu-latest
timeout-minutes: 60
env:
ansible_version: "milestone"
python_version: "3.12"
steps:
- name: Checkout collection
uses: actions/checkout@v4

- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.python_version }}

- name: Install ansible-core (${{ env.ansible_version }})
run: >-
python3 -m pip install
https://github.com/ansible/ansible/archive/${{ env.ansible_version }}.tar.gz
--disable-pip-version-check
shell: bash

- name: Build and install collection
id: install-collection
uses: ansible-network/github_actions/.github/actions/build_install_collection@main
with:
install_python_dependencies: false
source_path: .

- name: Set up git
run: |
git config --global user.email gha@localhost
git config --global user.name "Github Actions"
shell: bash

- name: Run integration tests
uses: ansible-network/github_actions/.github/actions/ansible_test_integration@main
with:
collection_path: ${{ steps.install-collection.outputs.collection_path }}
python_version: ${{ env.python_version }}
ansible_version: ${{ env.ansible_version }}
ansible_test_requirement_files: 'test-requirements.txt'
ansible_test_environment: |
ANSIBLE_TEST_GITHUB_PAT=${{ secrets.GITHUB_TOKEN }}
all_green:
if: ${{ always() }}
needs:
- build-import
- sanity
- unit-galaxy
- ansible-lint
- integration
runs-on: ubuntu-latest
steps:
- run: >-
python -c "assert 'failure' not in
set([
'${{ needs.sanity.result }}',
'${{ needs.unit-galaxy.result }}'
'${{ needs.ansible-lint.result }}'
'${{ needs.build-import.result }}'
'${{ needs.unit-galaxy.result }}',
'${{ needs.ansible-lint.result }}',
'${{ needs.build-import.result }}',
'${{ needs.integration.result }}'
])"
46 changes: 46 additions & 0 deletions plugins/action/server_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2025 Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from ansible.errors import AnsibleActionFail
from ansible.module_utils.connection import Connection
from ansible.plugins.action import ActionBase


class ActionModule(ActionBase):
"""Action plugin to retrieve MCP server information."""

def run(self, tmp=None, task_vars=None):
"""Execute the server_info action.
Returns:
dict: Ansible result dictionary containing server_info
"""
if task_vars is None:
task_vars = {}

result = super(ActionModule, self).run(tmp, task_vars)
result["changed"] = False

# Ensure we're using the MCP connection
connection_type = self._play_context.connection
if not connection_type or not connection_type.endswith(".mcp"):
raise AnsibleActionFail(
"Connection type %s is not valid for server_info module, "
"please use fully qualified name of MCP connection type." % connection_type
)

# Get socket path from connection
socket_path = self._connection.socket_path
if socket_path is None:
raise AnsibleActionFail("socket_path is not available from connection")

try:
# Use Connection class to call server_info on the connection plugin
conn = Connection(socket_path)
result["server_info"] = conn.server_info()
return result

except Exception as e:
raise AnsibleActionFail("Failed to retrieve server info: %s" % str(e))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case we should treat it like a module failure. So rather than raising an exception, we should do something like:

result["failed"] = True
result["msg"] = "some useful error message..."
result["exception"] = "".join(traceback.format_exception(None, e, e.__traceback__))

81 changes: 81 additions & 0 deletions plugins/modules/server_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2025 Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function


__metaclass__ = type

DOCUMENTATION = r"""

module: server_info

short_description: Retrieve MCP server information

author:
- Bianca Henderson (@beeankha)

description:
- This module retrieves server information from an MCP (Model Context Protocol) server.
- The server information is returned from the initialization step of the MCP connection.
- This module requires the MCP connection plugin to be configured.

version_added: "1.0.0"

notes:
- This module requires the MCP connection plugin (ansible.mcp.mcp) to be configured.
- The connection plugin must be initialized before this module can retrieve server information.
- Server information is retrieved from the MCP server's initialization response.
"""

EXAMPLES = r"""
- name: Retrieve server info from GitHub MCP server
ansible.mcp.server_info:
register: gh_mcp_server_info

- name: Display server info
ansible.builtin.debug:
var: gh_mcp_server_info

- name: Retrieve server info from AWS IAM MCP server
ansible.mcp.server_info:
register: aws_mcp_server_info

- name: Display AWS server info
ansible.builtin.debug:
var: aws_mcp_server_info
"""

RETURN = r"""
server_info:
description: Server information returned from the MCP server initialization.
returned: success
type: dict
contains:
protocolVersion:
description: MCP protocol version supported by the server.
returned: success
type: str
sample: "2025-03-26"
serverInfo:
description: Information about the MCP server.
returned: success
type: dict
contains:
name:
description: Name of the MCP server.
returned: success
type: str
sample: "github-server"
version:
description: Version of the MCP server.
returned: success
type: str
sample: "1.0.0"
capabilities:
description: Capabilities supported by the MCP server.
returned: success
type: dict
"""
2 changes: 2 additions & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# TO-DO: add python packages that are required for testing this collection
pytest-ansible
pytest-xdist
# uv provides uvx command-line tool needed for AWS IAM MCP server integration tests
uv
19 changes: 19 additions & 0 deletions tests/integration/targets/server_info/inventory.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
all:
children:
mcp_servers:
hosts:
github_server:
ansible_connection: ansible.mcp.mcp
ansible_mcp_server_name: github-server
ansible_mcp_server_args: []
ansible_mcp_server_env: {}
ansible_mcp_manifest_path: /opt/mcp/mcpservers.json
aws_iam_server:
ansible_connection: ansible.mcp.mcp
ansible_mcp_server_name: aws-iam-mcp-server
ansible_mcp_server_args:
- --readonly
ansible_mcp_server_env:
AWS_REGION: us-east-1
AWS_PROFILE: default
ansible_mcp_manifest_path: /opt/mcp/mcpservers.json
15 changes: 15 additions & 0 deletions tests/integration/targets/server_info/mcpservers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"github-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"description": "GitHub MCP Server - Access GitHub repositories, issues, and pull requests"
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specifically needs to be type: http and use the https://api.githubcopilot.com/mcp/ url.

"aws-iam-mcp-server": {
"type": "stdio",
"command": "uvx",
"args": ["awslabs.iam-mcp-server"],
"description": "AWS IAM MCP Server - Manage AWS IAM resources through MCP protocol"
}
}

17 changes: 17 additions & 0 deletions tests/integration/targets/server_info/runme.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -eux
export ANSIBLE_CALLBACKS_ENABLED=profile_tasks
export ANSIBLE_ROLES_PATH=../

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

GITHUB_PAT_VALUE="${ANSIBLE_TEST_GITHUB_PAT:-${GITHUB_PAT:-${GITHUB_TOKEN:-${GITHUB_PERSONAL_ACCESS_TOKEN:-}}}}"

INVENTORY="${SCRIPT_DIR}/inventory.yml"
MANIFEST_PATH="${SCRIPT_DIR}/mcpservers.json"

if [ -n "${GITHUB_PAT_VALUE:-}" ]; then
ansible-playbook -i "${INVENTORY}" tasks/main.yml -e "ansible_mcp_manifest_path=${MANIFEST_PATH}" -e "github_pat=${GITHUB_PAT_VALUE}" "$@"
else
ansible-playbook -i "${INVENTORY}" tasks/main.yml -e "ansible_mcp_manifest_path=${MANIFEST_PATH}" "$@"
fi
51 changes: 51 additions & 0 deletions tests/integration/targets/server_info/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
- name: Test server_info with GitHub MCP server
hosts: github_server
gather_facts: false
vars:
ansible_mcp_manifest_path: "{{ ansible_mcp_manifest_path | default(playbook_dir | dirname + '/mcpservers.json') }}"
tasks:
- name: Retrieve server info from GitHub MCP server
ansible.mcp.server_info:
register: github_server_info
when: github_pat is defined and github_pat | length > 0

- name: Verify GitHub server_info result succeeded
ansible.builtin.assert:
that:
- not github_server_info.failed | default(false)
- github_server_info.server_info is defined
- github_server_info.server_info.serverInfo is defined
- github_server_info.server_info.serverInfo.name is defined
- github_server_info.server_info.serverInfo.version is defined
fail_msg: "GitHub server_info does not contain expected structure"
when: github_pat is defined and github_pat | length > 0

- name: Test server_info with AWS IAM MCP server
hosts: aws_iam_server
gather_facts: false
vars:
ansible_mcp_manifest_path: "{{ ansible_mcp_manifest_path | default(playbook_dir | dirname + '/mcpservers.json') }}"
tasks:
- name: Check if uvx is available
ansible.builtin.command: which uvx
register: uvx_check
changed_when: false
failed_when: false
check_mode: false

- name: Retrieve server info from AWS IAM MCP server
ansible.mcp.server_info:
register: aws_server_info
when: uvx_check.rc == 0

- name: Verify AWS server_info result succeeded
ansible.builtin.assert:
that:
- not aws_server_info.failed | default(false)
- aws_server_info.server_info is defined
- aws_server_info.server_info.serverInfo is defined
- aws_server_info.server_info.serverInfo.name is defined
- aws_server_info.server_info.serverInfo.version is defined
fail_msg: "AWS server_info does not contain expected structure"
when: uvx_check.rc == 0