#
tokens: 15372/50000 37/37 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   ├── dependabot.yml
│   └── workflows
│       ├── build.yaml
│       └── release.yaml
├── .gitignore
├── cmd
│   └── podman-mcp-server
│       ├── main_test.go
│       └── main.go
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── npm
│   ├── podman-mcp-server
│   │   ├── bin
│   │   │   └── index.js
│   │   └── package.json
│   ├── podman-mcp-server-darwin-amd64
│   │   └── package.json
│   ├── podman-mcp-server-darwin-arm64
│   │   └── package.json
│   ├── podman-mcp-server-linux-amd64
│   │   └── package.json
│   ├── podman-mcp-server-linux-arm64
│   │   └── package.json
│   ├── podman-mcp-server-windows-amd64
│   │   └── package.json
│   └── podman-mcp-server-windows-arm64
│       └── package.json
├── pkg
│   ├── mcp
│   │   ├── common_test.go
│   │   ├── mcp_test.go
│   │   ├── mcp.go
│   │   ├── podman_container_test.go
│   │   ├── podman_container.go
│   │   ├── podman_image_test.go
│   │   ├── podman_image.go
│   │   ├── podman_network_test.go
│   │   ├── podman_network.go
│   │   ├── podman_volume_test.go
│   │   └── podman_volume.go
│   ├── podman
│   │   ├── interface.go
│   │   └── podman_cli.go
│   ├── podman-mcp-server
│   │   └── cmd
│   │       ├── root_test.go
│   │       └── root.go
│   └── version
│       └── version.go
├── python
│   ├── podman_mcp_server
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   └── podman_mcp_server.py
│   ├── pyproject.toml
│   └── README.md
├── README.md
└── testdata
    └── podman
        └── main.go
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
.idea/
.docusaurus/
node_modules/

.npmrc
/podman-mcp-server
npm/podman-mcp-server/README.md
npm/podman-mcp-server/LICENSE
!npm/podman-mcp-server
podman-mcp-server-darwin-amd64
!npm/podman-mcp-server-darwin-amd64/
podman-mcp-server-darwin-arm64
!npm/podman-mcp-server-darwin-arm64
podman-mcp-server-linux-amd64
!npm/podman-mcp-server-linux-amd64
podman-mcp-server-linux-arm64
!npm/podman-mcp-server-linux-arm64
podman-mcp-server-windows-amd64.exe
podman-mcp-server-windows-arm64.exe

python/.venv/
python/build/
python/dist/
python/podman-mcp-server.egg-info/
!python/podman-mcp-server

```

--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------

```markdown
../README.md
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Podman MCP Server

[![GitHub License](https://img.shields.io/github/license/manusa/podman-mcp-server)](https://github.com/manusa/podman-mcp-server/blob/main/LICENSE)
[![npm](https://img.shields.io/npm/v/podman-mcp-server)](https://www.npmjs.com/package/podman-mcp-server)
[![PyPI - Version](https://img.shields.io/pypi/v/podman-mcp-server)](https://pypi.org/project/podman-mcp-server/)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/manusa/podman-mcp-server?sort=semver)](https://github.com/manusa/podman-mcp-server/releases/latest)
[![Build](https://github.com/manusa/podman-mcp-server/actions/workflows/build.yaml/badge.svg)](https://github.com/manusa/podman-mcp-server/actions/workflows/build.yaml)

[✨ Features](#features) | [🚀 Getting Started](#getting-started) | [🎥 Demos](#demos) | [⚙️ Configuration](#configuration) | [🧑‍💻 Development](#development)

## ✨ Features <a id="features"></a>

A powerful and flexible MCP server for container runtimes supporting Podman and Docker.


## 🚀 Getting Started <a id="getting-started"></a>

### Claude Desktop

#### Using npx

If you have npm installed, this is the fastest way to get started with `podman-mcp-server` on Claude Desktop.

Open your `claude_desktop_config.json` and add the mcp server to the list of `mcpServers`:
``` json
{
  "mcpServers": {
    "podman": {
      "command": "npx",
      "args": [
        "-y",
        "podman-mcp-server@latest"
      ]
    }
  }
}
```

### VS Code / VS Code Insiders

Install the Podman MCP server extension in VS Code Insiders by pressing the following link:

[<img src="https://img.shields.io/badge/VS_Code-VS_Code?style=flat-square&label=Install%20Server&color=0098FF" alt="Install in VS Code">](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522podman%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522podman-mcp-server%2540latest%2522%255D%257D)
[<img alt="Install in VS Code Insiders" src="https://img.shields.io/badge/VS_Code_Insiders-VS_Code_Insiders?style=flat-square&label=Install%20Server&color=24bfa5">](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522podman%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522podman-mcp-server%2540latest%2522%255D%257D)

Alternatively, you can install the extension manually by running the following command:

```shell
# For VS Code
code --add-mcp '{"name":"podman","command":"npx","args":["podman-mcp-server@latest"]}'
# For VS Code Insiders
code-insiders --add-mcp '{"name":"podman","command":"npx","args":["podman-mcp-server@latest"]}'
```

### Goose CLI

[Goose CLI](https://blog.marcnuri.com/goose-on-machine-ai-agent-cli-introduction) is the easiest (and cheapest) way to get rolling with artificial intelligence (AI) agents.

#### Using npm

If you have npm installed, this is the fastest way to get started with `podman-mcp-server`.

Open your goose `config.yaml` and add the mcp server to the list of `mcpServers`:
```yaml
extensions:
  podman:
    command: npx
    args:
      - -y
      - podman-mcp-server@latest

```

## 🎥 Demos <a id="demos"></a>

## ⚙️ Configuration <a id="configuration"></a>

The Podman MCP server can be configured using command line (CLI) arguments.

You can run the CLI executable either by using `npx` or by downloading the [latest release binary](https://github.com/manusa/podman-mcp-server/releases/latest).

```shell
# Run the Podman MCP server using npx (in case you have npm installed)
npx podman-mcp-server@latest --help
```

```shell
# Run the Podman MCP server using the latest release binary
./podman-mcp-server --help
```

### Configuration Options

| Option       | Description                                                                              |
|--------------|------------------------------------------------------------------------------------------|
| `--sse-port` | Starts the MCP server in Server-Sent Event (SSE) mode and listens on the specified port. |

## 🧑‍💻 Development <a id="development"></a>

### Running with mcp-inspector

Compile the project and run the Podman MCP server with [mcp-inspector](https://modelcontextprotocol.io/docs/tools/inspector) to inspect the MCP server.

```shell
# Compile the project
make build
# Run the Podman MCP server with mcp-inspector
npx @modelcontextprotocol/inspector@latest $(pwd)/podman-mcp-server
```

```

--------------------------------------------------------------------------------
/python/podman_mcp_server/__main__.py:
--------------------------------------------------------------------------------

```python
from .podman_mcp_server import main

if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/cmd/podman-mcp-server/main.go:
--------------------------------------------------------------------------------

```go
package main

import "github.com/manusa/podman-mcp-server/pkg/podman-mcp-server/cmd"

func main() {
	cmd.Execute()
}

```

--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------

```yaml
version: 2
updates:
  - package-ecosystem: "gomod"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 10

```

--------------------------------------------------------------------------------
/pkg/version/version.go:
--------------------------------------------------------------------------------

```go
package version

var CommitHash = "unknown"
var BuildTime = "1970-01-01T00:00:00Z"
var Version = "0.0.0"
var BinaryName = "podman-mcp-server"

```

--------------------------------------------------------------------------------
/python/podman_mcp_server/__init__.py:
--------------------------------------------------------------------------------

```python
"""
Model Context Protocol (MCP) server for container runtimes (Podman and Docker)
"""
from .podman_mcp_server import main

__all__ = ['main']


```

--------------------------------------------------------------------------------
/cmd/podman-mcp-server/main_test.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"os"
)

func Example_version() {
	oldArgs := os.Args
	defer func() { os.Args = oldArgs }()
	os.Args = []string{"podman-mcp-server", "--version"}
	main()
	// Output: 0.0.0
}

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server-linux-amd64/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server-linux-amd64",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "os": [
    "linux"
  ],
  "cpu": [
    "x64"
  ]
}

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server-darwin-amd64/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server-darwin-amd64",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "os": [
    "darwin"
  ],
  "cpu": [
    "x64"
  ]
}

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server-linux-arm64/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server-linux-arm64",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "os": [
    "linux"
  ],
  "cpu": [
    "arm64"
  ]
}

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server-windows-amd64/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server-windows-amd64",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "os": [
    "win32"
  ],
  "cpu": [
    "x64"
  ]
}

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server-darwin-arm64/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server-darwin-arm64",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "os": [
    "darwin"
  ],
  "cpu": [
    "arm64"
  ]
}

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server-windows-arm64/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server-windows-arm64",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "os": [
    "win32"
  ],
  "cpu": [
    "arm64"
  ]
}

```

--------------------------------------------------------------------------------
/testdata/podman/main.go:
--------------------------------------------------------------------------------

```go
// Fake podman CLI binary
package main

import (
	"os"
	"path"
	"path/filepath"
)

func main() {
	print("podman")
	for _, arg := range os.Args[1:] {
		print(" " + arg)
	}
	println()
	ex, err := os.Executable()
	if err != nil {
		panic(err)
	}
	outputTxt := path.Join(filepath.Dir(ex), "output.txt")
	_, err = os.Stat(outputTxt)
	if err == nil {
		data, _ := os.ReadFile(outputTxt)
		_, _ = os.Stdout.Write(data)
	}
	os.Exit(0)
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_volume.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"context"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func (s *Server) initPodmanVolume() []server.ServerTool {
	return []server.ServerTool{
		{mcp.NewTool("volume_list",
			mcp.WithDescription("List all the available Docker or Podman volumes"),
		), s.volumeList},
	}
}

func (s *Server) volumeList(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.VolumeList()), nil
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_network.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"context"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func (s *Server) initPodmanNetwork() []server.ServerTool {
	return []server.ServerTool{
		{mcp.NewTool("network_list",
			mcp.WithDescription("List all the available Docker or Podman networks"),
		), s.networkList},
	}
}

func (s *Server) networkList(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.NetworkList()), nil
}

```

--------------------------------------------------------------------------------
/pkg/podman-mcp-server/cmd/root_test.go:
--------------------------------------------------------------------------------

```go
package cmd

import (
	"io"
	"os"
	"testing"
)

func captureOutput(f func() error) (string, error) {
	originalOut := os.Stdout
	defer func() {
		os.Stdout = originalOut
	}()
	r, w, _ := os.Pipe()
	os.Stdout = w
	err := f()
	_ = w.Close()
	out, _ := io.ReadAll(r)
	return string(out), err
}

func TestVersion(t *testing.T) {
	rootCmd.SetArgs([]string{"--version"})
	version, err := captureOutput(rootCmd.Execute)
	if version != "0.0.0\n" {
		t.Fatalf("Expected version 0.0.0, got %s %v", version, err)
		return
	}
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_volume_test.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"github.com/mark3labs/mcp-go/mcp"
	"strings"
	"testing"
)

func TestVolumeList(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("volume_list", map[string]interface{}{})
		t.Run("volume_list returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("volume_list lists all available volumes", func(t *testing.T) {
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman volume ls") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_network_test.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"github.com/mark3labs/mcp-go/mcp"
	"strings"
	"testing"
)

func TestNetworkList(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("network_list", map[string]interface{}{})
		t.Run("network_list returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("network_list lists all available networks", func(t *testing.T) {
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman network ls") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

```

--------------------------------------------------------------------------------
/python/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "podman-mcp-server"
version = "0.0.0"
description = "Model Context Protocol (MCP) server for container runtimes (Podman and Docker)"
readme = {file="README.md", content-type="text/markdown"}
requires-python = ">=3.6"
license = "Apache-2.0"
authors = [
    { name = "Marc Nuri", email = "[email protected]" }
]
classifiers = [
    "Programming Language :: Python :: 3",
    "Operating System :: OS Independent",
]

[project.urls]
Homepage = "https://github.com/manusa/podman-mcp-server"
Repository = "https://github.com/manusa/podman-mcp-server"

[project.scripts]
podman-mcp-server = "podman_mcp_server:main"

```

--------------------------------------------------------------------------------
/pkg/mcp/mcp_test.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"github.com/mark3labs/mcp-go/mcp"
	"testing"
)

func TestTools(t *testing.T) {
	expectedNames := []string{
		"container_inspect",
		"container_list",
		"container_logs",
		"container_remove",
		"container_run",
		"container_stop",
		"image_build",
		"image_list",
		"image_pull",
		"image_push",
		"image_remove",
		"network_list",
		"volume_list",
	}
	testCase(t, func(c *mcpContext) {
		tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{})
		t.Run("ListTools returns tools", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call ListTools failed %v", err)
			}
		})
		nameSet := make(map[string]bool)
		for _, tool := range tools.Tools {
			nameSet[tool.Name] = true
		}
		for _, name := range expectedNames {
			t.Run("ListTools has "+name+" tool", func(t *testing.T) {
				if nameSet[name] != true {
					t.Errorf("tool %s not found", name)
				}
			})
		}
	})
}

```

--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------

```yaml
name: Build

on:
  push:
    branches:
      - 'main'
    paths-ignore:
      - '.gitignore'
      - 'LICENSE'
      - '*.md'
  pull_request:
    paths-ignore:
      - '.gitignore'
      - 'LICENSE'
      - '*.md'

concurrency:
  # Only run once for latest commit per ref and cancel other (previous) runs.
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  GO_VERSION: 1.23

defaults:
  run:
    shell: bash

jobs:
  build:
    name: Build on ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-latest #x64
          - ubuntu-24.04-arm #arm64
          - windows-latest #x64
          - macos-13 #x64
          - macos-latest #arm64
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Build
        run: make build
      - name: Test
        run: make test

```

--------------------------------------------------------------------------------
/npm/podman-mcp-server/bin/index.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

const childProcess = require('child_process');

const BINARY_MAP = {
  darwin_x64: {name: 'podman-mcp-server-darwin-amd64', suffix: ''},
  darwin_arm64: {name: 'podman-mcp-server-darwin-arm64', suffix: ''},
  linux_x64: {name: 'podman-mcp-server-linux-amd64', suffix: ''},
  linux_arm64: {name: 'podman-mcp-server-linux-arm64', suffix: ''},
  win32_x64: {name: 'podman-mcp-server-windows-amd64', suffix: '.exe'},
  win32_arm64: {name: 'podman-mcp-server-windows-arm64', suffix: '.exe'},
};

// Resolving will fail if the optionalDependency was not installed or the platform/arch is not supported
const resolveBinaryPath = () => {
  try {
    const binary = BINARY_MAP[`${process.platform}_${process.arch}`];
    return require.resolve(`${binary.name}/bin/${binary.name}${binary.suffix}`);
  } catch (e) {
    throw new Error(`Could not resolve binary path for platform/arch: ${process.platform}/${process.arch}`);
  }
};

childProcess.execFileSync(resolveBinaryPath(), process.argv.slice(2), {
  stdio: 'inherit',
});


```

--------------------------------------------------------------------------------
/npm/podman-mcp-server/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "podman-mcp-server",
  "version": "0.0.0",
  "description": "Model Context Protocol (MCP) server for container runtimes (Podman and Docker) ",
  "main": "./bin/index.js",
  "bin": {
    "podman-mcp-server": "bin/index.js"
  },
  "optionalDependencies": {
    "podman-mcp-server-darwin-amd64": "0.0.0",
    "podman-mcp-server-darwin-arm64": "0.0.0",
    "podman-mcp-server-linux-amd64": "0.0.0",
    "podman-mcp-server-linux-arm64": "0.0.0",
    "podman-mcp-server-windows-amd64": "0.0.0",
    "podman-mcp-server-windows-arm64": "0.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/manusa/podman-mcp-server.git"
  },
  "keywords": [
    "mcp",
    "podman",
    "docker",
    "containers",
    "container-runtime",
    "model context protocol",
    "model",
    "context",
    "protocol"
  ],
  "author": {
    "name": "Marc Nuri",
    "url": "https://www.marcnuri.com"
  },
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/manusa/podman-mcp-server/issues"
  },
  "homepage": "https://github.com/manusa/podman-mcp-server#readme"
}

```

--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------

```yaml
name: Release

on:
  push:
    tags:
      - '*'

concurrency:
  # Only run once for latest commit per ref and cancel other (previous) runs.
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  GO_VERSION: 1.23
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
  UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }}

permissions:
  contents: write
  discussions: write

jobs:
  release:
    name: Release
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
      - name: Build
        run: make build-all-platforms
      - name: Upload artifacts
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          make_latest: true
          files: |
            LICENSE
            podman-mcp-server-*
      - name: Publish npm
        run:
          make npm-publish
  python:
    name: Release Python
    # Python logic requires the tag/release version to be available from GitHub
    needs: release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - name: Publish Python
        run:
          make python-publish

```

--------------------------------------------------------------------------------
/pkg/mcp/mcp.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"github.com/manusa/podman-mcp-server/pkg/podman"
	"github.com/manusa/podman-mcp-server/pkg/version"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"slices"
)

type Server struct {
	server *server.MCPServer
	podman podman.Podman
}

func NewSever() (*Server, error) {
	s := &Server{
		server: server.NewMCPServer(
			version.BinaryName,
			version.Version,
			server.WithResourceCapabilities(true, true),
			server.WithPromptCapabilities(true),
			server.WithToolCapabilities(true),
			server.WithLogging(),
		),
	}
	var err error
	if s.podman, err = podman.NewPodman(); err != nil {
		return nil, err
	}
	s.server.AddTools(slices.Concat(
		s.initPodmanContainer(),
		s.initPodmanImage(),
		s.initPodmanNetwork(),
		s.initPodmanVolume(),
	)...)
	return s, nil
}

func (s *Server) ServeStdio() error {
	return server.ServeStdio(s.server)
}

func (s *Server) ServeSse(baseUrl string) *server.SSEServer {
	options := make([]server.SSEOption, 0)
	if baseUrl != "" {
		options = append(options, server.WithBaseURL(baseUrl))
	}
	return server.NewSSEServer(s.server, options...)
}

func NewTextResult(content string, err error) *mcp.CallToolResult {
	if err != nil {
		return &mcp.CallToolResult{
			IsError: true,
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: err.Error(),
				},
			},
		}
	}
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: content,
			},
		},
	}
}

```

--------------------------------------------------------------------------------
/pkg/podman/interface.go:
--------------------------------------------------------------------------------

```go
package podman

// Podman interface
type Podman interface {
	// ContainerInspect displays the low-level information on containers identified by the ID or name
	ContainerInspect(name string) (string, error)
	// ContainerList lists all the containers on the system
	ContainerList() (string, error)
	// ContainerLogs Display the logs of a container
	ContainerLogs(name string) (string, error)
	// ContainerRemove removes a container
	ContainerRemove(name string) (string, error)
	// ContainerRun pulls an image from a registry
	ContainerRun(imageName string, portMappings map[int]int, envVariables []string) (string, error)
	// ContainerStop stops a running container using the ID or name
	ContainerStop(name string) (string, error)
	// ImageBuild builds an image from a Dockerfile, Podmanfile, or Containerfile
	ImageBuild(containerFile string, imageName string) (string, error)
	// ImageList list the container images on the system
	ImageList() (string, error)
	// ImagePull pulls an image from a registry
	ImagePull(imageName string) (string, error)
	// ImagePush pushes an image to a registry
	ImagePush(imageName string) (string, error)
	// ImageRemove removes an image from the system
	ImageRemove(imageName string) (string, error)
	// NetworkList lists all the networks on the system
	NetworkList() (string, error)
	// VolumeList lists all the volumes on the system
	VolumeList() (string, error)
}

func NewPodman() (Podman, error) {
	// TODO: add implementations for Podman bindings and Docker CLI
	return newPodmanCli()
}

```

--------------------------------------------------------------------------------
/pkg/podman-mcp-server/cmd/root.go:
--------------------------------------------------------------------------------

```go
package cmd

import (
	"errors"
	"fmt"
	"github.com/manusa/podman-mcp-server/pkg/mcp"
	"github.com/manusa/podman-mcp-server/pkg/version"
	"github.com/mark3labs/mcp-go/server"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"golang.org/x/net/context"
)

var rootCmd = &cobra.Command{
	Use:   "podman-mcp-server [command] [options]",
	Short: "Podman Model Context Protocol (MCP) server",
	Long: `
Podman Model Context Protocol (MCP) server

  # show this help
  podman-mcp-server -h

  # shows version information
  podman-mcp-server --version

  # start STDIO server
  podman-mcp-server

  # start a SSE server on port 8080
  podman-mcp-server --sse-port 8080

  # start a SSE server on port 8443 with a public HTTPS host of example.com
  podman-mcp-server --sse-port 8443 --sse-base-url https://example.com:8443

  # TODO: add more examples`,
	Run: func(cmd *cobra.Command, args []string) {
		if viper.GetBool("version") {
			fmt.Println(version.Version)
			return
		}
		mcpServer, err := mcp.NewSever()
		if err != nil {
			panic(err)
		}

		var sseServer *server.SSEServer
		if ssePort := viper.GetInt("sse-port"); ssePort > 0 {
			sseServer = mcpServer.ServeSse(viper.GetString("sse-base-url"))
			if err := sseServer.Start(fmt.Sprintf(":%d", ssePort)); err != nil {
				panic(err)
			}
		}
		if err := mcpServer.ServeStdio(); err != nil && !errors.Is(err, context.Canceled) {
			panic(err)
		}
		if sseServer != nil {
			_ = sseServer.Shutdown(cmd.Context())
		}
	},
}

func init() {
	rootCmd.Flags().BoolP("version", "v", false, "Print version information and quit")
	rootCmd.Flags().IntP("sse-port", "", 0, "Start a SSE server on the specified port")
	rootCmd.Flags().StringP("sse-base-url", "", "", "SSE public base URL to use when sending the endpoint message (e.g. https://example.com)")
	_ = viper.BindPFlags(rootCmd.Flags())
}

func Execute() {
	if err := rootCmd.Execute(); err != nil {
		panic(err)
	}
}

```

--------------------------------------------------------------------------------
/pkg/mcp/common_test.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"context"
	"fmt"
	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"net/http/httptest"
	"os"
	"os/exec"
	"path"
	"runtime"
	"testing"
)

type mcpContext struct {
	podmanBinaryDir string
	ctx             context.Context
	cancel          context.CancelFunc
	mcpServer       *Server
	mcpHttpServer   *httptest.Server
	mcpClient       *client.Client
}

func (c *mcpContext) beforeEach(t *testing.T) {
	var err error
	c.ctx, c.cancel = context.WithCancel(context.Background())
	if c.mcpServer, err = NewSever(); err != nil {
		t.Fatal(err)
		return
	}
	c.mcpHttpServer = server.NewTestServer(c.mcpServer.server)
	if c.mcpClient, err = client.NewSSEMCPClient(c.mcpHttpServer.URL + "/sse"); err != nil {
		t.Fatal(err)
		return
	}
	if err = c.mcpClient.Start(c.ctx); err != nil {
		t.Fatal(err)
		return
	}
	initRequest := mcp.InitializeRequest{}
	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	initRequest.Params.ClientInfo = mcp.Implementation{Name: "test", Version: "1.33.7"}
	_, err = c.mcpClient.Initialize(c.ctx, initRequest)
	if err != nil {
		t.Fatal(err)
		return
	}
}

func (c *mcpContext) afterEach() {
	c.cancel()
	_ = c.mcpClient.Close()
	c.mcpHttpServer.Close()
}

func testCase(t *testing.T, test func(c *mcpContext)) {
	mcpCtx := &mcpContext{
		podmanBinaryDir: withPodmanBinary(t),
	}
	mcpCtx.beforeEach(t)
	defer mcpCtx.afterEach()
	test(mcpCtx)
}

// callTool helper function to call a tool by name with arguments
func (c *mcpContext) callTool(name string, args map[string]interface{}) (*mcp.CallToolResult, error) {
	callToolRequest := mcp.CallToolRequest{}
	callToolRequest.Params.Name = name
	callToolRequest.Params.Arguments = args
	return c.mcpClient.CallTool(c.ctx, callToolRequest)
}

func (c *mcpContext) withPodmanOutput(outputLines ...string) {
	if len(outputLines) > 0 {
		f, _ := os.Create(path.Join(c.podmanBinaryDir, "output.txt"))
		defer f.Close()
		for _, line := range outputLines {
			_, _ = f.WriteString(line + "\n")
		}
	}
}

func withPodmanBinary(t *testing.T) string {
	binDir := t.TempDir()
	binary := "podman"
	if runtime.GOOS == "windows" {
		binary += ".exe"
	}
	output, err := exec.
		Command("go", "build", "-o", path.Join(binDir, binary),
			path.Join("..", "..", "testdata", "podman", "main.go")).
		CombinedOutput()
	if err != nil {
		panic(fmt.Errorf("failed to generate podman binary: %w, output: %s", err, string(output)))
	}
	if os.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) != nil {
		panic("failed to set PATH")
	}
	return binDir
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_image.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"context"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func (s *Server) initPodmanImage() []server.ServerTool {
	return []server.ServerTool{
		{mcp.NewTool("image_build",
			mcp.WithDescription("Build a Docker or Podman image from a Dockerfile, Podmanfile, or Containerfile"),
			mcp.WithString("containerFile", mcp.Description("The absolute path to the Dockerfile, Podmanfile, or Containerfile to build the image from"), mcp.Required()),
			mcp.WithString("imageName", mcp.Description("Specifies the name which is assigned to the resulting image if the build process completes successfully (--tag, -t)")),
		), s.imageBuild},
		{mcp.NewTool("image_list",
			mcp.WithDescription("List the Docker or Podman images on the local machine"),
		), s.imageList},
		{mcp.NewTool("image_pull",
			mcp.WithDescription("Copies (pulls) a Docker or Podman container image from a registry onto the local machine storage"),
			mcp.WithString("imageName", mcp.Description("Docker or Podman container image name to pull"), mcp.Required()),
		), s.imagePull},
		{mcp.NewTool("image_push",
			mcp.WithDescription("Pushes a Docker or Podman container image, manifest list or image index from local machine storage to a registry"),
			mcp.WithString("imageName", mcp.Description("Docker or Podman container image name to push"), mcp.Required()),
		), s.imagePush},
		{mcp.NewTool("image_remove",
			mcp.WithDescription("Removes a Docker or Podman image from the local machine storage"),
			mcp.WithString("imageName", mcp.Description("Docker or Podman container image name to remove"), mcp.Required()),
		), s.imageRemove},
	}
}

func (s *Server) imageBuild(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	imageName := ctr.GetArguments()["imageName"]
	if _, ok := imageName.(string); !ok {
		imageName = ""
	}
	return NewTextResult(s.podman.ImageBuild(ctr.GetArguments()["containerFile"].(string), imageName.(string))), nil
}

func (s *Server) imageList(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ImageList()), nil
}

func (s *Server) imagePull(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ImagePull(ctr.GetArguments()["imageName"].(string))), nil
}

func (s *Server) imagePush(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ImagePush(ctr.GetArguments()["imageName"].(string))), nil
}

func (s *Server) imageRemove(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ImageRemove(ctr.GetArguments()["imageName"].(string))), nil
}

```

--------------------------------------------------------------------------------
/python/podman_mcp_server/podman_mcp_server.py:
--------------------------------------------------------------------------------

```python
import os
import platform
import subprocess
import sys
from pathlib import Path
import shutil
import tempfile
import urllib.request

if sys.version_info >= (3, 8):
    from importlib.metadata import version
else:
    from importlib_metadata import version

__version__ = version("podman-mcp-server")

def get_platform_binary():
    """Determine the correct binary for the current platform."""
    system = platform.system().lower()
    arch = platform.machine().lower()

    # Normalize architecture names
    if arch in ["x86_64", "amd64"]:
        arch = "amd64"
    elif arch in ["arm64", "aarch64"]:
        arch = "arm64"
    else:
        raise RuntimeError(f"Unsupported architecture: {arch}")

    if system == "darwin":
        return f"podman-mcp-server-darwin-{arch}"
    elif system == "linux":
        return f"podman-mcp-server-linux-{arch}"
    elif system == "windows":
        return f"podman-mcp-server-windows-{arch}.exe"
    else:
        raise RuntimeError(f"Unsupported operating system: {system}")

def download_binary(binary_version="latest", destination=None):
    """Download the correct binary for the current platform."""
    binary_name = get_platform_binary()
    if destination is None:
        destination = Path.home() / ".podman-mcp-server" / "bin" / binary_version

    destination = Path(destination)
    destination.mkdir(parents=True, exist_ok=True)
    binary_path = destination / binary_name

    if binary_path.exists():
        return binary_path

    base_url = "https://github.com/manusa/podman-mcp-server/releases"
    if binary_version == "latest":
        release_url = f"{base_url}/latest/download/{binary_name}"
    else:
        release_url = f"{base_url}/download/v{binary_version}/{binary_name}"

    # Download the binary
    print(f"Downloading {binary_name} from {release_url}")
    with tempfile.NamedTemporaryFile(delete=False) as temp_file:
        try:
            with urllib.request.urlopen(release_url) as response:
                shutil.copyfileobj(response, temp_file)
            temp_file.close()

            # Move to destination and make executable
            shutil.move(temp_file.name, binary_path)
            binary_path.chmod(binary_path.stat().st_mode | 0o755)  # Make executable

            return binary_path
        except Exception as e:
            os.unlink(temp_file.name)
            raise RuntimeError(f"Failed to download binary: {e}")

def execute(args=None):
    """Download and execute the podman-mcp-server binary."""
    if args is None:
        args = []

    try:
        binary_path = download_binary(binary_version=__version__)
        cmd = [str(binary_path)] + args

        # Execute the binary with the provided arguments
        process = subprocess.run(cmd)
        return process.returncode
    except Exception as e:
        print(f"Error executing podman-mcp-server: {e}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    sys.exit(execute(sys.argv[1:]))


def main():
    """Main function to execute the podman-mcp-server binary."""
    args = sys.argv[1:] if len(sys.argv) > 1 else []
    return execute(args)

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_image_test.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"github.com/mark3labs/mcp-go/mcp"
	"strings"
	"testing"
)

func TestImageBuild(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("image_build", map[string]interface{}{
			"containerFile": "/tmp/Containerfile",
		})
		t.Run("image_build returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman build -f /tmp/Containerfile") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
		toolResult, err = c.callTool("image_build", map[string]interface{}{
			"containerFile": "/tmp/Containerfile",
			"imageName":     "example.com/org/image:tag",
		})
		t.Run("image_build with imageName returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman build -t example.com/org/image:tag -f /tmp/Containerfile") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestImageList(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		c.withPodmanOutput(
			"REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tSIZE",
			"docker.io/marcnuri/chuck-norris\nlatest\nsha256:1337\nb8f22a2b8410\n1 day ago\n37 MB",
		)
		toolResult, err := c.callTool("image_list", map[string]interface{}{})
		t.Run("image_list returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
				return
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
				return
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman images --digests") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
				return
			}
		})
	})
}

func TestImagePull(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("image_pull", map[string]interface{}{
			"imageName": "example.com/org/image:tag",
		})
		t.Run("image_pull returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman image pull example.com/org/image:tag") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
			if !strings.HasSuffix(toolResult.Content[0].(mcp.TextContent).Text, "example.com/org/image:tag pulled successfully") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestImagePush(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("image_push", map[string]interface{}{
			"imageName": "example.com/org/image:tag",
		})
		t.Run("image_push returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman image push example.com/org/image:tag") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
			if !strings.HasSuffix(toolResult.Content[0].(mcp.TextContent).Text, "example.com/org/image:tag pushed successfully") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestImageRemove(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("image_remove", map[string]interface{}{
			"imageName": "example.com/org/image:tag",
		})
		t.Run("image_remove returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman image rm example.com/org/image:tag") {
				t.Errorf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

```

--------------------------------------------------------------------------------
/pkg/podman/podman_cli.go:
--------------------------------------------------------------------------------

```go
package podman

import (
	"errors"
	"fmt"
	"os/exec"
	"strings"
)

type podmanCli struct {
	filePath string
}

// ContainerInspect
// https://docs.podman.io/en/stable/markdown/podman-inspect.1.html
func (p *podmanCli) ContainerInspect(name string) (string, error) {
	return p.exec("inspect", name)
}

// ContainerList
// https://docs.podman.io/en/stable/markdown/podman-ps.1.html
func (p *podmanCli) ContainerList() (string, error) {
	return p.exec("container", "list", "-a")
}

// ContainerLogs
// https://docs.podman.io/en/stable/markdown/podman-logs.1.html
func (p *podmanCli) ContainerLogs(name string) (string, error) {
	return p.exec("logs", name)
}

// ContainerRemove
// https://docs.podman.io/en/stable/markdown/podman-rm.1.html
func (p *podmanCli) ContainerRemove(name string) (string, error) {
	return p.exec("container", "rm", name)
}

// ContainerRun
// https://docs.podman.io/en/stable/markdown/podman-run.1.html
func (p *podmanCli) ContainerRun(imageName string, portMappings map[int]int, envVariables []string) (string, error) {
	args := []string{"run", "--rm", "-d"}
	if len(portMappings) > 0 {
		for hostPort, containerPort := range portMappings {
			args = append(args, fmt.Sprintf("--publish=%d:%d", hostPort, containerPort))
		}
	} else {
		args = append(args, "--publish-all")
	}
	for _, env := range envVariables {
		args = append(args, "--env", env)
	}
	output, err := p.exec(append(args, imageName)...)
	if err == nil {
		return output, nil
	}
	if strings.Contains(output, "Error: short-name") {
		imageName = "docker.io/" + imageName
		if output, err = p.exec(append(args, imageName)...); err == nil {
			return output, nil
		}
	}
	return "", err
}

// ContainerStop
// https://docs.podman.io/en/stable/markdown/podman-stop.1.html
func (p *podmanCli) ContainerStop(name string) (string, error) {
	return p.exec("container", "stop", name)
}

// ImageBuild
// https://docs.podman.io/en/stable/markdown/podman-build.1.html
func (p *podmanCli) ImageBuild(containerFile string, imageName string) (string, error) {
	args := []string{"build"}
	if imageName != "" {
		args = append(args, "-t", imageName)
	}
	return p.exec(append(args, "-f", containerFile)...)
}

// ImageList
// https://docs.podman.io/en/stable/markdown/podman-images.1.html
func (p *podmanCli) ImageList() (string, error) {
	return p.exec("images", "--digests")
}

// ImagePull
// https://docs.podman.io/en/stable/markdown/podman-pull.1.html
func (p *podmanCli) ImagePull(imageName string) (string, error) {
	output, err := p.exec("image", "pull", imageName)
	if err == nil {
		return fmt.Sprintf("%s\n%s pulled successfully", output, imageName), nil
	}
	if strings.Contains(output, "Error: short-name") {
		imageName = "docker.io/" + imageName
		if output, err = p.exec("pull", imageName); err == nil {
			return fmt.Sprintf("%s\n%s pulled successfully", output, imageName), nil
		}
	}
	return "", err
}

// ImagePush
// https://docs.podman.io/en/stable/markdown/podman-push.1.html
func (p *podmanCli) ImagePush(imageName string) (string, error) {
	output, err := p.exec("image", "push", imageName)
	if err == nil {
		return fmt.Sprintf("%s\n%s pushed successfully", output, imageName), nil
	}
	return "", err
}

// ImageRemove
// https://docs.podman.io/en/stable/markdown/podman-rmi.1.html
func (p *podmanCli) ImageRemove(imageName string) (string, error) {
	return p.exec("image", "rm", imageName)
}

// NetworkList
// https://docs.podman.io/en/stable/markdown/podman-network-ls.1.html
func (p *podmanCli) NetworkList() (string, error) {
	return p.exec("network", "ls")
}

// VolumeList
// https://docs.podman.io/en/stable/markdown/podman-volume-ls.1.html
func (p *podmanCli) VolumeList() (string, error) {
	return p.exec("volume", "ls")
}

func (p *podmanCli) exec(args ...string) (string, error) {
	output, err := exec.Command(p.filePath, args...).CombinedOutput()
	return string(output), err
}

func newPodmanCli() (*podmanCli, error) {
	for _, cmd := range []string{"podman", "podman.exe"} {
		filePath, err := exec.LookPath(cmd)
		if err != nil {
			continue
		}
		if _, err = exec.Command(filePath, "version").CombinedOutput(); err == nil {
			return &podmanCli{filePath}, nil
		}
	}
	return nil, errors.New("podman CLI not found")
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_container.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"context"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"strconv"
	"strings"
)

func (s *Server) initPodmanContainer() []server.ServerTool {
	return []server.ServerTool{
		{mcp.NewTool("container_inspect",
			mcp.WithDescription("Displays the low-level information and configuration of a Docker or Podman container with the specified container ID or name"),
			mcp.WithString("name", mcp.Description("Docker or Podman container ID or name to displays the information"), mcp.Required()),
		), s.containerInspect},
		{mcp.NewTool("container_list",
			mcp.WithDescription("Prints out information about the running Docker or Podman containers"),
		), s.containerList},
		{mcp.NewTool("container_logs",
			mcp.WithDescription("Displays the logs of a Docker or Podman container with the specified container ID or name"),
			mcp.WithString("name", mcp.Description("Docker or Podman container ID or name to displays the logs"), mcp.Required()),
		), s.containerLogs},
		{mcp.NewTool("container_remove",
			mcp.WithDescription("Removes a Docker or Podman container with the specified container ID or name (rm)"),
			mcp.WithString("name", mcp.Description("Docker or Podman container ID or name to remove"), mcp.Required()),
		), s.containerRemove},
		{mcp.NewTool("container_run",
			mcp.WithDescription("Runs a Docker or Podman container with the specified image name"),
			mcp.WithString("imageName", mcp.Description("Docker or Podman container image name to pull"), mcp.Required()),
			mcp.WithArray("ports", mcp.Description("Port mappings to expose on the host. "+
				"Format: <hostPort>:<containerPort>. "+
				"Example: 8080:80. "+
				"(Optional, add only to expose ports)"),
				// TODO: manual fix to ensure that the items property gets initialized (Gemini)
				// https://www.googlecloudcommunity.com/gc/AI-ML/Gemini-API-400-Bad-Request-Array-fields-breaks-function-calling/m-p/769835?nobounce
				func(schema map[string]interface{}) {
					schema["type"] = "array"
					schema["items"] = map[string]interface{}{
						"type": "string",
					}
				},
			),
			mcp.WithArray("environment", mcp.Description("Environment variables to set in the container. "+
				"Format: <key>=<value>. "+
				"Example: FOO=bar. "+
				"(Optional, add only to set environment variables)"),
				// TODO: manual fix to ensure that the items property gets initialized (Gemini)
				// https://www.googlecloudcommunity.com/gc/AI-ML/Gemini-API-400-Bad-Request-Array-fields-breaks-function-calling/m-p/769835?nobounce
				func(schema map[string]interface{}) {
					schema["type"] = "array"
					schema["items"] = map[string]interface{}{
						"type": "string",
					}
				},
			),
		), s.containerRun},
		{mcp.NewTool("container_stop",
			mcp.WithDescription("Stops a Docker or Podman running container with the specified container ID or name"),
			mcp.WithString("name", mcp.Description("Docker or Podman container ID or name to stop"), mcp.Required()),
		), s.containerStop},
	}
}

func (s *Server) containerInspect(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ContainerInspect(ctr.GetArguments()["name"].(string))), nil
}

func (s *Server) containerList(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ContainerList()), nil
}

func (s *Server) containerLogs(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ContainerLogs(ctr.GetArguments()["name"].(string))), nil
}

func (s *Server) containerRemove(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ContainerRemove(ctr.GetArguments()["name"].(string))), nil
}

func (s *Server) containerRun(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	ports := ctr.GetArguments()["ports"]
	portMappings := make(map[int]int)
	if _, ok := ports.([]interface{}); ok {
		for _, port := range ports.([]interface{}) {
			if _, ok := port.(string); !ok {
				continue
			}
			hostPort, _ := strconv.Atoi(strings.Split(port.(string), ":")[0])
			containerPort, _ := strconv.Atoi(strings.Split(port.(string), ":")[1])
			if hostPort > 0 && containerPort > 0 {
				portMappings[hostPort] = containerPort
			}
		}
	}
	environment := ctr.GetArguments()["environment"]
	envVariables := make([]string, 0)
	if _, ok := environment.([]interface{}); ok && len(environment.([]interface{})) > 0 {
		for _, env := range environment.([]interface{}) {
			if _, ok = env.(string); !ok {
				continue
			}
			envVariables = append(envVariables, env.(string))
		}
	}
	return NewTextResult(s.podman.ContainerRun(ctr.GetArguments()["imageName"].(string), portMappings, envVariables)), nil
}

func (s *Server) containerStop(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	return NewTextResult(s.podman.ContainerStop(ctr.GetArguments()["name"].(string))), nil
}

```

--------------------------------------------------------------------------------
/pkg/mcp/podman_container_test.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"github.com/mark3labs/mcp-go/mcp"
	"strings"
	"testing"
)

func TestContainerInspect(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("container_inspect", map[string]interface{}{
			"name": "example-container",
		})
		t.Run("container_inspect returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_inspect inspects provided container", func(t *testing.T) {
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman inspect example-container") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestContainerList(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("container_list", map[string]interface{}{})
		t.Run("container_list returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman container list -a") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestContainerLogs(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("container_logs", map[string]interface{}{
			"name": "example-container",
		})
		t.Run("container_logs returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_logs retrieves logs from provided container", func(t *testing.T) {
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman logs example-container") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestContainerRemove(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("container_remove", map[string]interface{}{
			"name": "example-container",
		})
		t.Run("container_remove returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_remove removes provided container", func(t *testing.T) {
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman container rm example-container") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestContainerRun(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("container_run", map[string]interface{}{
			"imageName": "example.com/org/image:tag",
		})
		t.Run("container_run returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_run runs provided image", func(t *testing.T) {
			if !strings.HasSuffix(toolResult.Content[0].(mcp.TextContent).Text, " example.com/org/image:tag\n") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
		t.Run("container_run runs in detached mode", func(t *testing.T) {
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " -d ") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
		t.Run("container_run publishes all exposed ports", func(t *testing.T) {
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " --publish-all ") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
		toolResult, err = c.callTool("container_run", map[string]interface{}{
			"imageName": "example.com/org/image:tag",
			"ports": []interface{}{
				1337, // Invalid entry to test
				"8080:80",
				"8082:8082",
				"8443:443",
			},
		})
		t.Run("container_run with ports returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_run with ports publishes provided ports", func(t *testing.T) {
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " --publish=8080:80 ") {
				t.Fatalf("expected port --publish=8080:80, got %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " --publish=8082:8082 ") {
				t.Fatalf("expected port --publish=8082:8082, got %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " --publish=8443:443 ") {
				t.Fatalf("expected port --publish=8443:443, got %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
		toolResult, err = c.callTool("container_run", map[string]interface{}{
			"imageName": "example.com/org/image:tag",
			"ports":     []interface{}{"8080:80"},
			"environment": []interface{}{
				"KEY=VALUE",
				"FOO=BAR",
			},
		})
		t.Run("container_run with environment returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_run with environment sets provided environment variables", func(t *testing.T) {
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " --env KEY=VALUE ") {
				t.Fatalf("expected env --env KEY=VALUE, got %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
			if !strings.Contains(toolResult.Content[0].(mcp.TextContent).Text, " --env FOO=BAR ") {
				t.Fatalf("expected env --env FOO=BAR, got %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

func TestContainerStop(t *testing.T) {
	testCase(t, func(c *mcpContext) {
		toolResult, err := c.callTool("container_stop", map[string]interface{}{
			"name": "example-container",
		})
		t.Run("container_stop returns OK", func(t *testing.T) {
			if err != nil {
				t.Fatalf("call tool failed %v", err)
			}
			if toolResult.IsError {
				t.Fatalf("call tool failed")
			}
		})
		t.Run("container_stop stops provided container", func(t *testing.T) {
			if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "podman container stop example-container") {
				t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
			}
		})
	})
}

```