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

```
├── .github
│   └── workflows
│       ├── lint.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .golangci.yaml
├── .goreleaser.yaml
├── color.go
├── Dockerfile
├── Dockerfile.goreleaser
├── go.mod
├── go.sum
├── LICENSE
├── logs
│   └── .gitignore
├── main_test.go
├── main.go
├── Makefile
├── min-coverage
├── prompts
│   ├── _git_commit_role.tmpl
│   ├── git_amend_commit.tmpl
│   ├── git_squash_commit.tmpl
│   └── git_stage_commit.tmpl
├── prompts_parser_test.go
├── prompts_parser.go
├── prompts_server_test.go
├── prompts_server.go
├── README.md
└── testdata
    ├── _content.tmpl
    ├── _footer.tmpl
    ├── _greeting_body.tmpl
    ├── _header.tmpl
    ├── conditional_greeting.tmpl
    ├── greeting_with_partials.tmpl
    ├── greeting.tmpl
    ├── logical_operators.tmpl
    ├── multiple_partials.tmpl
    ├── range_scalars.tmpl
    ├── range_structs.tmpl
    └── with_object.tmpl
```

# Files

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

```
*
!.gitignore

```

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

```
.idea/
vendor/
dist/
mcp-prompt-engine
coverage.out
*.log
.mcp.json

```

--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------

```yaml
version: "2"
linters:
  exclusions:
    rules:
      - path: _test\.go
        linters:
          - errcheck
      - path: mcptest
        linters:
          - errcheck

```

--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------

```yaml
project_name: mcp-prompt-engine

before:
  hooks:
    - go mod tidy
    - go generate ./...

builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin
    goarch:
      - amd64
      - arm64
    ldflags:
      - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.goVersion={{.Env.GO_VERSION}}
    main: .
    binary: mcp-prompt-engine

archives:
  - format: tar.gz
    name_template: >-
      {{ .ProjectName }}_
      {{- title .Os }}_
      {{- if eq .Arch "amd64" }}x86_64
      {{- else if eq .Arch "386" }}i386
      {{- else }}{{ .Arch }}{{ end }}
      {{- if .Arm }}v{{ .Arm }}{{ end }}
    format_overrides:
    - goos: windows
      format: zip

checksum:
  name_template: 'checksums.txt'

snapshot:
  name_template: "{{ incpatch .Version }}-next"

changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'

dockers:
  - image_templates:
      - "ghcr.io/vasayxtx/mcp-prompt-engine:{{ .Version }}"
      - "ghcr.io/vasayxtx/mcp-prompt-engine:latest"
    dockerfile: Dockerfile.goreleaser
    use: buildx
    goos: linux
    goarch: amd64
    build_flag_templates:
      - "--platform=linux/amd64"
      - "--label=org.opencontainers.image.title={{.ProjectName}}"
      - "--label=org.opencontainers.image.description=MCP Prompt Engine"
      - "--label=org.opencontainers.image.url=https://github.com/vasayxtx/mcp-prompt-engine"
      - "--label=org.opencontainers.image.source=https://github.com/vasayxtx/mcp-prompt-engine"
      - "--label=org.opencontainers.image.version={{.Version}}"
      - "--label=org.opencontainers.image.created={{.Date}}"
      - "--label=org.opencontainers.image.revision={{.FullCommit}}"
      - "--label=org.opencontainers.image.licenses=MIT"

release:
  github:
    owner: vasayxtx
    name: mcp-prompt-engine
  draft: false
  prerelease: auto
  name_template: "{{.ProjectName}}-v{{.Version}}"
```

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

```markdown
# MCP Prompt Engine

[![Go Report Card](https://goreportcard.com/badge/github.com/vasayxtx/mcp-prompt-engine)](https://goreportcard.com/report/github.com/vasayxtx/mcp-prompt-engine)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/vasayxtx/mcp-prompt-engine)](https://github.com/vasayxtx/mcp-prompt-engine/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-blue?logo=go&logoColor=white)](https://pkg.go.dev/github.com/vasayxtx/mcp-prompt-engine)

A Model Control Protocol (MCP) server for managing and serving dynamic prompt templates using elegant and powerful text template engine.
Create reusable, logic-driven prompts with variables, partials, and conditionals that can be served to any [compatible MCP client](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/clients.mdx) like Claude Code, Claude Desktop, Gemini CLI, VSCode with Copilot, etc.

## Key Features

-   **MCP Compatible**: Works out-of-the-box with any [MCP client](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/clients.mdx) that supports [prompts](https://modelcontextprotocol.io/docs/concepts/prompts).
-   **Powerful Go Templates**: Utilizes the full power of Go [text/template](https://pkg.go.dev/text/template) syntax, including variables, conditionals, loops, and more.
-   **Reusable Partials**: Define common components in partial templates (e.g., `_header.tmpl`) and reuse them across your prompts.
-   **Prompt Arguments**: All template variables are automatically exposed as MCP prompt arguments, allowing dynamic input from clients.
-   **Hot-Reload**: Automatically detects changes to your prompt files and reloads them without restarting the server.
-   **Rich CLI**: A modern command-line interface to list, validate, and render templates for easy development and testing.
-   **Smart Argument Handling**:
    -   Automatically parses JSON arguments (booleans, numbers, arrays, objects).
    -   Injects environment variables as fallbacks for template arguments.
-   **Containerized**: Full Docker support for easy deployment and integration.

## Getting Started

### 1. Installation

Install using Go:
```bash
go install github.com/vasayxtx/mcp-prompt-engine@latest
```
(For other methods like Docker or pre-built binaries, see the [Installation section](#installation) below.)

### 2. Create a Prompt

Create a `prompts` directory and add a template file. Let's create a prompt to help write a Git commit message.

First, create a reusable partial named `prompts/_git_commit_role.tmpl`:

    ```go
    {{ define "_git_commit_role" }}
    You are an expert programmer specializing in writing clear, concise, and conventional Git commit messages.
    Commit message must strictly follow the Conventional Commits specification.

    The final commit message you generate must be formatted exactly as follows:

    ```
    <type>: A brief, imperative-tense summary of changes

    [Optional longer description, explaining the "why" of the change. Use dash points for clarity.]
    ```
    {{ if .type -}}
    Use {{.type}} as a type.
    {{ end }}
    {{ end }}
    ```

Now, create a main prompt `prompts/git_stage_commit.tmpl` that uses this partial:
    ```go
    {{- /* Commit currently staged changes */ -}}

    {{- template "_git_commit_role" . -}}

    Your task is to commit all currently staged changes.
    To understand the context, analyze the staged code using the command: `git diff --staged`
    Based on that analysis, commit staged changes using a suitable commit message.
    ```

### 3. Validate Your Prompt

Validate your prompt to ensure it has no syntax errors:
```bash
mcp-prompt-engine validate git_stage_commit
✓ git_stage_commit.tmpl - Valid
```

### 4. Connect MCP Server to Your Client

Add MCP Server to your MCP client. See [Connecting to Clients](#connecting-to-clients) for configuration examples.

### 5. Use Your Prompt

Your `git_stage_commit` prompt will now be available in your client!

For example, in Claude Desktop, you can select the `git_stage_commit` prompt, provide the `type` MCP Prompt argument and get a generated prompt that will help you to do a commit with a perfect message.

In Claude Code or Gemini CLI, you can start typing `/git_stage_commit` and it will suggest the prompt with the provided arguments that will be executed after you select it.

---

## Installation

### Pre-built Binaries

Download the latest release for your OS from the [GitHub Releases page](https://github.com/vasayxtx/mcp-prompt-engine/releases).

### Build from Source

```bash
git clone https://github.com/vasayxtx/mcp-prompt-engine.git
cd mcp-prompt-engine
make build
```

### Docker

A pre-built Docker image is available. Mount your local `prompts` and `logs` directories to the container.

```bash
# Pull and run the pre-built image from GHCR
docker run -i --rm \
  -v /path/to/your/prompts:/app/prompts:ro \
  -v /path/to/your/logs:/app/logs \
  ghcr.io/vasayxtx/mcp-prompt-engine
```

You can also build the image locally with `make docker-build`.

---

## Usage

### Creating Prompt Templates

Create a directory to store your prompt templates. Each template should be a `.tmpl` file using Go's [text/template](https://pkg.go.dev/text/template) syntax with the following format:

```go
{{/* Brief description of the prompt */}}
Your prompt text here with {{.template_variable}} placeholders.
```

The first line comment (`{{/* description */}}`) is used as the prompt description, and the rest of the file is the prompt template.

Partial templates should be prefixed with an underscore (e.g., `_header.tmpl`) and can be included in other templates using `{{template "partial_name" .}}`.

### Template Syntax

The server uses Go's `text/template` engine, which provides powerful templating capabilities:

- **Variables**: `{{.variable_name}}` - Access template variables
- **Built-in variables**:
    - `{{.date}}` - Current date and time
- **Conditionals**: `{{if .condition}}...{{end}}`, `{{if .condition}}...{{else}}...{{end}}`
- **Logical operators**: `{{if and .condition1 .condition2}}...{{end}}`, `{{if or .condition1 .condition2}}...{{end}}`
- **Loops**: `{{range .items}}...{{end}}`
- **Template inclusion**: `{{template "partial_name" .}}` or `{{template "partial_name" dict "key" "value"}}`

See the [Go text/template documentation](https://pkg.go.dev/text/template) for more details on syntax and features.

### JSON Argument Parsing

The server automatically parses argument values as JSON when possible, enabling rich data types in templates:

- **Booleans**: `true`, `false` → Go boolean values
- **Numbers**: `42`, `3.14` → Go numeric values
- **Arrays**: `["item1", "item2"]` → Go slices for use with `{{range}}`
- **Objects**: `{"key": "value"}` → Go maps for structured data
- **Strings**: Invalid JSON falls back to string values

This allows for advanced template operations like:
```go
{{range .items}}Item: {{.}}{{end}}
{{if .enabled}}Feature is enabled{{end}}
{{.config.timeout}} seconds
```

To disable JSON parsing and treat all arguments as strings, use the `--disable-json-args` flag for the `serve` and `render` commands.

### CLI Commands

The CLI is your main tool for managing and testing templates.
By default, it looks for templates in the `./prompts` directory, but you can specify a different directory with the `--prompts` flag.

**1. List Templates**
```bash
# See a simple list of available prompts
mcp-prompt-engine list

# See a detailed view with descriptions and variables
mcp-prompt-engine list --verbose
```

**2. Render a Template**

Render a prompt directly in your terminal, providing arguments with the `-a` or `--arg` flag.
It will automatically inject environment variables as fallbacks for any missing arguments. For example, if you have an environment variable `TYPE=fix`, it will be injected into the template as `{{.type}}`.

```bash
# Render the git commit prompt, providing the 'type' variable
mcp-prompt-engine render git_stage_commit --arg type=feat
```

**3. Validate Templates**

Check all your templates for syntax errors. The command will return an error if any template is invalid.
```bash
# Validate all templates in the directory
mcp-prompt-engine validate

# Validate a single template
mcp-prompt-engine validate git_stage_commit
```

**4. Start the Server**

Run the MCP server to make your prompts available to clients.
```bash
# Run with default settings (looks for ./prompts)
mcp-prompt-engine serve

# Specify a different prompts directory and a log file
mcp-prompt-engine --prompts /path/to/prompts serve --log-file ./server.log
```

---

## Connecting to Clients

To use this engine with any client that supports MCP Prompts, add a new entry to its MCP servers configuration.

Global configuration locations (MacOS):
- Claude Code: `~/.claude.json` (`mcpServers` section)
- Claude Desktop: `~/Library/Application\ Support/Claude/claude_desktop_config.json` (`mcpServers` section)
- Gemini CLI: `~/.gemini/settings.json` (`mcpServers` section)

**Example for a local binary:**
```json
{
  "prompts": {
    "command": "/path/to/your/mcp-prompt-engine",
    "args": [
      "--prompts", "/path/to/your/prompts",
      "serve",
      "--quiet"
    ]
  }
}
```

**Example for Docker:**
```json
{
  "mcp-prompt-engine-docker": {
    "command": "docker",
    "args": [
      "run", "-i", "--rm",
      "-v", "/path/to/your/prompts:/app/prompts:ro",
      "-v", "/path/to/your/logs:/app/logs",
      "ghcr.io/vasayxtx/mcp-prompt-engine"
    ]
  }
}
```

## License

This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.

```

--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------

```yaml
name: Test

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

permissions:
  contents: read

jobs:
  test:
    name: Test
    strategy:
      matrix:
        go: [ '1.24' ]
      fail-fast: true
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Go ${{ matrix.go }}
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go }}

      - name: Run tests
        run: make test

```

--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------

```yaml
name: Lint

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

permissions:
  contents: read

jobs:
  lint:
    name: Lint
    strategy:
      matrix:
        go: [ '1.24' ]
      fail-fast: true
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Go ${{ matrix.go }}
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go }}

      - name: Run GolangCI-Lint
        uses: golangci/golangci-lint-action@v8
        with:
          version: v2.1

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM golang:1.24-alpine AS builder

RUN apk add --no-cache git
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mcp-prompt-engine .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
RUN addgroup -g 1001 -S mcpuser && \
    adduser -S -D -H -u 1001 -h /app -s /sbin/nologin -G mcpuser mcpuser
WORKDIR /app
COPY --from=builder /app/mcp-prompt-engine .
RUN mkdir -p /app/prompts /app/logs && chown -R mcpuser:mcpuser /app
USER mcpuser
VOLUME ["/app/prompts", "/app/logs"]
ENV MCP_PROMPTS_DIR=/app/prompts
CMD ["./mcp-prompt-engine", "serve", "--quiet", "--log-file", "/app/logs/mcp-prompt-engine.log"]

```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write
  packages: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        id: setup-go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          distribution: goreleaser
          version: ~> v1
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GO_VERSION: ${{ steps.setup-go.outputs.go-version }}
```

--------------------------------------------------------------------------------
/color.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"fmt"

	"github.com/fatih/color"
)

type ColorMode string

const (
	colorModeNever  ColorMode = "never"
	colorModeAlways ColorMode = "always"
	colorModeAuto   ColorMode = "auto"
)

var colorModesCommaSeparatedList = fmt.Sprintf("%s, %s, %s", colorModeAuto, colorModeAlways, colorModeNever)

// Color utility functions for consistent styling
var (
	// Status indicators
	successIcon func(...interface{}) string
	errorIcon   func(...interface{}) string
	warningIcon func(...interface{}) string

	// Text colors
	successText   func(...interface{}) string
	errorText     func(...interface{}) string
	infoText      func(...interface{}) string
	highlightText func(...interface{}) string

	// Specific formatters
	templateText func(...interface{}) string
	pathText     func(...interface{}) string
)

// initializeColors sets up color functions based on color mode
func initializeColors(colorMode ColorMode) {
	switch colorMode {
	case colorModeNever:
		color.NoColor = true
	case colorModeAlways:
		color.NoColor = false
	case colorModeAuto:
		// fatih/color automatically detects TTY using go-isatty
		// NoColor will be set to true if not a TTY
	default:
		// Default to auto
	}

	// Initialize color functions
	successIcon = color.New(color.FgGreen, color.Bold).SprintFunc()
	errorIcon = color.New(color.FgRed, color.Bold).SprintFunc()
	warningIcon = color.New(color.FgYellow, color.Bold).SprintFunc()

	successText = color.New(color.FgGreen).SprintFunc()
	errorText = color.New(color.FgRed).SprintFunc()
	infoText = color.New(color.FgBlue).SprintFunc()
	highlightText = color.New(color.FgCyan, color.Bold).SprintFunc()

	templateText = color.New(color.FgMagenta, color.Bold).SprintFunc()
	pathText = color.New(color.FgBlue).SprintFunc()

	// Apply icons with color
	successIcon = func(args ...interface{}) string {
		return color.New(color.FgGreen, color.Bold).Sprint("✓")
	}
	errorIcon = func(args ...interface{}) string {
		return color.New(color.FgRed, color.Bold).Sprint("✗")
	}
	warningIcon = func(args ...interface{}) string {
		return color.New(color.FgYellow, color.Bold).Sprint("⚠")
	}
}

func init() {
	initializeColors(colorModeAuto)
}

```

--------------------------------------------------------------------------------
/prompts_parser.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"text/template/parse"
)

type PromptsParser struct {
}

func (pp *PromptsParser) ParseDir(promptsDir string) (*template.Template, error) {
	tmpl := template.New("base").Funcs(template.FuncMap{
		"dict": dict,
	})
	var err error
	tmpl, err = tmpl.ParseGlob(filepath.Join(promptsDir, "*"+templateExt))
	if err != nil {
		return nil, fmt.Errorf("parse template glob %q: %w", filepath.Join(promptsDir, "*"+templateExt), err)
	}
	return tmpl, nil
}

func (pp *PromptsParser) ExtractPromptDescriptionFromFile(filePath string) (string, error) {
	content, err := os.ReadFile(filePath)
	if err != nil {
		return "", fmt.Errorf("read file: %w", err)
	}
	content = bytes.TrimSpace(content)

	var firstLine string
	if idx := bytes.IndexByte(content, '\n'); idx != -1 {
		firstLine = string(content[:idx])
	} else {
		firstLine = string(content)
	}
	firstLine = strings.TrimSpace(firstLine)

	for _, c := range [...][2]string{
		{"{{/*", "*/}}"},
		{"{{- /*", "*/}}"},
		{"{{/*", "*/ -}}"},
		{"{{- /*", "*/ -}}"},
	} {
		if strings.HasPrefix(firstLine, c[0]) && strings.HasSuffix(firstLine, c[1]) {
			comment := firstLine
			comment = strings.TrimPrefix(comment, c[0])
			comment = strings.TrimSuffix(comment, c[1])
			return strings.TrimSpace(comment), nil
		}
	}

	return "", nil
}

// ExtractPromptArgumentsFromTemplate analyzes template to find field references using template tree traversal,
// leveraging text/template built-in functionality to automatically resolve partials
func (pp *PromptsParser) ExtractPromptArgumentsFromTemplate(
	tmpl *template.Template, templateName string,
) ([]string, error) {
	targetTemplate := tmpl.Lookup(templateName)
	if targetTemplate == nil {
		if strings.HasSuffix(templateName, templateExt) {
			return nil, fmt.Errorf("template %q not found", templateName)
		}
		if targetTemplate = tmpl.Lookup(templateName + templateExt); targetTemplate == nil {
			return nil, fmt.Errorf("template %q or %q not found", templateName, templateName+templateExt)
		}
	}

	argsMap := make(map[string]struct{})
	builtInFields := map[string]struct{}{"date": {}}
	processedTemplates := make(map[string]bool)

	// Extract arguments from the target template and all referenced templates recursively
	err := pp.walkNodes(targetTemplate.Root, argsMap, builtInFields, tmpl, processedTemplates, []string{})
	if err != nil {
		return nil, err
	}

	args := make([]string, 0, len(argsMap))
	for arg := range argsMap {
		args = append(args, arg)
	}

	return args, nil
}

// walkNodes recursively walks the template parse tree to find variable references,
// automatically resolving template calls to include variables from referenced templates
func (pp *PromptsParser) walkNodes(
	node parse.Node,
	argsMap map[string]struct{},
	builtInFields map[string]struct{},
	tmpl *template.Template,
	processedTemplates map[string]bool,
	path []string,
) error {
	if node == nil {
		return nil
	}

	switch n := node.(type) {
	case *parse.ActionNode:
		return pp.walkNodes(n.Pipe, argsMap, builtInFields, tmpl, processedTemplates, path)
	case *parse.IfNode:
		if err := pp.walkNodes(n.Pipe, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
			return err
		}
		if err := pp.walkNodes(n.List, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
			return err
		}
		return pp.walkNodes(n.ElseList, argsMap, builtInFields, tmpl, processedTemplates, path)
	case *parse.RangeNode:
		if err := pp.walkNodes(n.Pipe, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
			return err
		}
		if err := pp.walkNodes(n.List, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
			return err
		}
		return pp.walkNodes(n.ElseList, argsMap, builtInFields, tmpl, processedTemplates, path)
	case *parse.WithNode:
		if err := pp.walkNodes(n.Pipe, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
			return err
		}
		if err := pp.walkNodes(n.List, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
			return err
		}
		return pp.walkNodes(n.ElseList, argsMap, builtInFields, tmpl, processedTemplates, path)
	case *parse.ListNode:
		if n != nil {
			for _, child := range n.Nodes {
				if err := pp.walkNodes(child, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
					return err
				}
			}
		}
	case *parse.PipeNode:
		if n != nil {
			for _, cmd := range n.Cmds {
				if err := pp.walkNodes(cmd, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
					return err
				}
			}
		}
	case *parse.CommandNode:
		if n != nil {
			for _, arg := range n.Args {
				if err := pp.walkNodes(arg, argsMap, builtInFields, tmpl, processedTemplates, path); err != nil {
					return err
				}
			}
		}
	case *parse.FieldNode:
		if len(n.Ident) > 0 {
			fieldName := strings.ToLower(n.Ident[0])
			if _, isBuiltIn := builtInFields[fieldName]; !isBuiltIn {
				argsMap[fieldName] = struct{}{}
			}
		}
	case *parse.VariableNode:
		if len(n.Ident) > 0 {
			fieldName := strings.ToLower(n.Ident[0])
			// Skip variable names that start with $ (template variables)
			if !strings.HasPrefix(fieldName, "$") {
				if _, isBuiltIn := builtInFields[fieldName]; !isBuiltIn {
					argsMap[fieldName] = struct{}{}
				}
			}
		}
	case *parse.TemplateNode:
		templateName := n.Name
		// Check for cycles
		for _, ancestor := range path {
			if ancestor == templateName {
				return fmt.Errorf("cyclic partial reference detected: %s", strings.Join(append(path, templateName), " -> "))
			}
		}
		if !processedTemplates[templateName] {
			processedTemplates[templateName] = true
			// Try to find the template by name or name + extension
			var referencedTemplate *template.Template
			if referencedTemplate = tmpl.Lookup(templateName); referencedTemplate == nil && !strings.HasSuffix(templateName, templateExt) {
				referencedTemplate = tmpl.Lookup(templateName + templateExt)
			}
			if referencedTemplate == nil || referencedTemplate.Tree == nil {
				return fmt.Errorf("referenced template %q not found in %q", templateName, tmpl.Name())
			}
			if err := pp.walkNodes(referencedTemplate.Root, argsMap, builtInFields, tmpl, processedTemplates, append(path, templateName)); err != nil {
				return err
			}
		}
		return pp.walkNodes(n.Pipe, argsMap, builtInFields, tmpl, processedTemplates, path)
	}
	return nil
}

// dict creates a map from key-value pairs for template usage
func dict(values ...interface{}) map[string]interface{} {
	if len(values)%2 != 0 {
		return nil
	}
	result := make(map[string]interface{})
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil
		}
		result[key] = values[i+1]
	}
	return result
}

```

--------------------------------------------------------------------------------
/prompts_server.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"text/template"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

type PromptsServer struct {
	mcpServer      *server.MCPServer
	parser         *PromptsParser
	promptsDir     string
	enableJSONArgs bool
	logger         *slog.Logger
	watcher        *fsnotify.Watcher
}

// NewPromptsServer creates a new PromptsServer instance that serves prompts from the specified directory.
func NewPromptsServer(
	promptsDir string, enableJSONArgs bool, logger *slog.Logger,
) (promptsServer *PromptsServer, err error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, fmt.Errorf("create file watcher: %w", err)
	}
	defer func() {
		if err != nil {
			if closeErr := watcher.Close(); closeErr != nil {
				logger.Error("Failed to close file watcher", "error", closeErr)
			}
		}
	}()

	if err = watcher.Add(promptsDir); err != nil {
		return nil, fmt.Errorf("add prompts directory to watcher: %w", err)
	}

	srvHooks := &server.Hooks{}
	srvHooks.AddBeforeGetPrompt(func(ctx context.Context, id any, message *mcp.GetPromptRequest) {
		logger.Info("Received prompt request",
			"id", id, "params_name", message.Params.Name, "params_args", message.Params.Arguments)
	})
	srvHooks.AddAfterGetPrompt(func(ctx context.Context, id any, message *mcp.GetPromptRequest, result *mcp.GetPromptResult) {
		logger.Info("Processed prompt request",
			"id", id, "params_name", message.Params.Name, "params_args", message.Params.Arguments)

	})
	mcpServer := server.NewMCPServer(
		"Prompts Engine MCP Server",
		"1.0.0",
		server.WithLogging(),
		server.WithRecovery(),
		server.WithHooks(srvHooks),
		server.WithPromptCapabilities(true),
	)

	promptsServer = &PromptsServer{
		mcpServer:      mcpServer,
		parser:         &PromptsParser{},
		promptsDir:     promptsDir,
		enableJSONArgs: enableJSONArgs,
		logger:         logger,
		watcher:        watcher,
	}

	if err = promptsServer.reloadPrompts(); err != nil {
		return nil, fmt.Errorf("reload prompts: %w", err)
	}

	return promptsServer, nil
}

func (ps *PromptsServer) Close() error {
	if ps.watcher != nil {
		if err := ps.watcher.Close(); err != nil {
			return err
		}
		ps.watcher = nil
	}
	return nil
}

// ServeStdio starts the MCP server with stdio transport and file watching.
func (ps *PromptsServer) ServeStdio(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		ps.startWatcher(ctx)
	}()

	srvErrChan := make(chan error, 1)
	wg.Add(1)
	go func() {
		defer wg.Done()
		ps.logger.Info("Starting stdio server")
		srvErrChan <- server.NewStdioServer(ps.mcpServer).Listen(ctx, stdin, stdout)
	}()

	var srvErr error
	select {
	case srvErr = <-srvErrChan:
		if srvErr != nil {
			ps.logger.Error("Stdio server error", "error", srvErr)
		}
	case <-ctx.Done():
		ps.logger.Info("Context cancelled, stopping server")
	}

	wg.Wait()

	return srvErr
}

func (ps *PromptsServer) loadServerPrompts() ([]server.ServerPrompt, error) {
	tmpl, err := ps.parser.ParseDir(ps.promptsDir)
	if err != nil {
		return nil, fmt.Errorf("parse all prompts: %w", err)
	}

	files, err := os.ReadDir(ps.promptsDir)
	if err != nil {
		return nil, fmt.Errorf("read prompts directory: %w", err)
	}

	var serverPrompts []server.ServerPrompt
	for _, file := range files {
		if !isTemplateFile(file) {
			continue
		}

		filePath := filepath.Join(ps.promptsDir, file.Name())

		templateName := file.Name()
		if tmpl.Lookup(templateName) == nil {
			return nil, fmt.Errorf("template %q not found", templateName)
		}

		var description string
		if description, err = ps.parser.ExtractPromptDescriptionFromFile(filePath); err != nil {
			return nil, fmt.Errorf("extract prompt description from %q template file: %w", filePath, err)
		}

		var args []string
		if args, err = ps.parser.ExtractPromptArgumentsFromTemplate(tmpl, templateName); err != nil {
			return nil, fmt.Errorf("extract prompt arguments from %q template file: %w", filePath, err)
		}

		envArgs := make(map[string]string)
		var promptArgs []string
		for _, arg := range args {
			// Convert arg to TITLE_CASE for env var
			envVarName := strings.ToUpper(arg)
			if envValue, exists := os.LookupEnv(envVarName); exists {
				envArgs[arg] = envValue
			} else {
				promptArgs = append(promptArgs, arg)
			}
		}

		promptOpts := []mcp.PromptOption{
			mcp.WithPromptDescription(description),
		}
		for _, promptArg := range promptArgs {
			promptOpts = append(promptOpts, mcp.WithArgument(promptArg))
		}

		promptName := strings.TrimSuffix(file.Name(), templateExt)

		serverPrompts = append(serverPrompts, server.ServerPrompt{
			Prompt:  mcp.NewPrompt(promptName, promptOpts...),
			Handler: ps.makeMCPHandler(tmpl, templateName, description, envArgs),
		})

		ps.logger.Info("Prompt will be registered",
			"name", promptName,
			"description", description,
			"prompt_args", promptArgs,
			"env_args", envArgs)
	}

	return serverPrompts, nil
}

func (ps *PromptsServer) reloadPrompts() error {
	newServerPrompts, err := ps.loadServerPrompts()
	if err != nil {
		return fmt.Errorf("load server prompts: %w", err)
	}

	ps.mcpServer.SetPrompts(newServerPrompts...)
	ps.logger.Info("Prompts registered", "count", len(newServerPrompts))

	return nil
}

func (ps *PromptsServer) makeMCPHandler(
	tmpl *template.Template, templateName string, description string, envArgs map[string]string,
) func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
	return func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
		data := make(map[string]interface{})
		data["date"] = time.Now().Format("2006-01-02 15:04:05")
		for arg, value := range envArgs {
			data[arg] = value
		}
		parseMCPArgs(request.Params.Arguments, ps.enableJSONArgs, data)

		var result strings.Builder
		if err := tmpl.ExecuteTemplate(&result, templateName, data); err != nil {
			return nil, fmt.Errorf("execute template %q: %w", templateName, err)
		}

		return mcp.NewGetPromptResult(
			description,
			[]mcp.PromptMessage{
				mcp.NewPromptMessage(
					mcp.RoleUser,
					mcp.NewTextContent(strings.TrimSpace(result.String())),
				),
			},
		), nil
	}
}

// startWatcher monitors file system changes and reloads prompts
func (ps *PromptsServer) startWatcher(ctx context.Context) {
	ps.logger.Info("Started watching prompts directory for changes", "dir", ps.promptsDir)

	for {
		select {
		case event, ok := <-ps.watcher.Events:
			if !ok {
				return
			}
			if !strings.HasSuffix(event.Name, templateExt) {
				continue
			}
			ps.logger.Info("Prompt template file changed", "file", event.Name, "operation", event.Op.String())
			if err := ps.reloadPrompts(); err != nil {
				ps.logger.Error("Failed to reload prompts", "error", err)
			}

		case err, ok := <-ps.watcher.Errors:
			if !ok {
				return
			}
			ps.logger.Error("File watcher error", "error", err)

		case <-ctx.Done():
			ps.logger.Info("Stopping prompts watcher due to context cancellation")
			return
		}
	}
}

// parseMCPArgs attempts to parse each argument value as JSON when enableJSONArgs is true.
// If parsing succeeds, stores the parsed value (bool, number, nil, object, etc.) in the data map.
// If parsing fails or JSON parsing is disabled, stores the original string value.
func parseMCPArgs(args map[string]string, enableJSONArgs bool, data map[string]interface{}) {
	for key, value := range args {
		if enableJSONArgs {
			var parsed interface{}
			if err := json.Unmarshal([]byte(value), &parsed); err == nil {
				data[key] = parsed
				continue
			}
		}
		data[key] = value
	}
}

func isTemplateFile(file os.DirEntry) bool {
	return file.Type().IsRegular() && strings.HasSuffix(file.Name(), templateExt) && !strings.HasPrefix(file.Name(), "_")
}

```

--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"log"
	"log/slog"
	"os"
	"os/signal"
	"path/filepath"
	"slices"
	"sort"
	"strings"
	"syscall"
	"text/template"
	"time"

	"github.com/urfave/cli/v3"
)

var (
	version   = "dev"
	commit    = "unknown"
	goVersion = "unknown"
)

const templateExt = ".tmpl"

func main() {
	cmd := &cli.Command{
		Name:    "mcp-prompt-engine",
		Usage:   "A Model Control Protocol server for dynamic prompt templates",
		Version: fmt.Sprintf("%s (commit: %s, go: %s)", version, commit, goVersion),
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "prompts",
				Aliases: []string{"p"},
				Value:   "./prompts",
				Usage:   "Directory containing prompt template files",
				Sources: cli.EnvVars("MCP_PROMPTS_DIR"),
			},
			&cli.StringFlag{
				Name:    "color",
				Value:   "auto",
				Usage:   "Colorize output: " + colorModesCommaSeparatedList,
				Sources: cli.EnvVars("NO_COLOR"),
				Action: func(ctx context.Context, cmd *cli.Command, value string) error {
					colorMode := ColorMode(value)
					if colorMode != colorModeAuto && colorMode != colorModeAlways && colorMode != colorModeNever {
						return fmt.Errorf("invalid color value %q, must be one of: "+colorModesCommaSeparatedList, value)
					}
					return nil
				},
			},
		},
		Commands: []*cli.Command{
			{
				Name:   "serve",
				Usage:  "Start the MCP server",
				Action: serveCommand,
				Flags: []cli.Flag{
					&cli.StringFlag{
						Name:  "log-file",
						Usage: "Path to log file (if not specified, logs to stdout)",
					},
					&cli.BoolFlag{
						Name:  "disable-json-args",
						Usage: "Disable JSON parsing for arguments (use string-only mode)",
					},
					&cli.BoolFlag{
						Name:  "quiet",
						Usage: "Suppress non-essential output",
					},
				},
			},
			{
				Name:      "render",
				Usage:     "Render a template to stdout",
				ArgsUsage: "<template_name>",
				Action:    renderCommand,
				Flags: []cli.Flag{
					&cli.StringSliceFlag{
						Name:    "arg",
						Aliases: []string{"a"},
						Usage:   "Template argument in name=value format (repeatable)",
					},
					&cli.BoolFlag{
						Name:  "disable-json-args",
						Usage: "Disable JSON parsing for arguments (use string-only mode)",
					},
				},
			},
			{
				Name:   "list",
				Usage:  "List available templates",
				Action: listCommand,
				Flags: []cli.Flag{
					&cli.BoolFlag{
						Name:  "verbose",
						Usage: "Show detailed information about templates",
					},
				},
			},
			{
				Name:      "validate",
				Usage:     "Validate template syntax",
				ArgsUsage: "[template_name]",
				Action:    validateCommand,
			},
			{
				Name:   "version",
				Usage:  "Show version information",
				Action: versionCommand,
			},
		},
		Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
			colorMode := ColorMode(cmd.String("color"))
			initializeColors(colorMode)

			// Skip validation for version command
			if cmd.Name == "version" {
				return ctx, nil
			}
			// Validate prompts directory exists
			promptsDir := cmd.String("prompts")
			if _, err := os.Stat(promptsDir); os.IsNotExist(err) {
				return ctx, fmt.Errorf("prompts directory '%s' does not exist", promptsDir)
			}
			return ctx, nil
		},
	}

	if err := cmd.Run(context.Background(), os.Args); err != nil {
		log.Fatal(err)
	}
}

// serveCommand starts the MCP server
func serveCommand(ctx context.Context, cmd *cli.Command) error {
	promptsDir := cmd.String("prompts")
	logFile := cmd.String("log-file")
	enableJSONArgs := !cmd.Bool("disable-json-args")
	quiet := cmd.Bool("quiet")

	if err := runStdioMCPServer(os.Stdout, promptsDir, logFile, enableJSONArgs, quiet); err != nil {
		return fmt.Errorf("%s: %w", errorText("failed to start MCP server"), err)
	}
	return nil
}

// renderCommand renders a template to stdout
func renderCommand(ctx context.Context, cmd *cli.Command) error {
	if cmd.Args().Len() < 1 {
		return fmt.Errorf("template name is required\n\nUsage: %s render <template_name>", cmd.Root().Name)
	}

	promptsDir := cmd.String("prompts")
	templateName := cmd.Args().First()
	args := cmd.StringSlice("arg")
	enableJSONArgs := !cmd.Bool("disable-json-args")

	// Parse args into a map
	argMap := make(map[string]string)
	for _, arg := range args {
		parts := strings.SplitN(arg, "=", 2)
		if len(parts) != 2 {
			return fmt.Errorf("invalid argument format '%s', expected name=value", arg)
		}
		argMap[parts[0]] = parts[1]
	}

	if err := renderTemplate(os.Stdout, promptsDir, templateName, argMap, enableJSONArgs); err != nil {
		return fmt.Errorf("%s '%s': %w", errorText("failed to render template"), templateText(templateName), err)
	}
	return nil
}

// listCommand lists available templates
func listCommand(ctx context.Context, cmd *cli.Command) error {
	promptsDir := cmd.String("prompts")
	verbose := cmd.Bool("verbose")

	if err := listTemplates(os.Stdout, promptsDir, verbose); err != nil {
		return fmt.Errorf("failed to list templates: %w", err)
	}
	return nil
}

// validateCommand validates template syntax
func validateCommand(ctx context.Context, cmd *cli.Command) error {
	promptsDir := cmd.String("prompts")

	var templateName string
	if cmd.Args().Len() > 0 {
		templateName = cmd.Args().First()
	}

	if err := validateTemplates(os.Stdout, promptsDir, templateName); err != nil {
		return fmt.Errorf("validation failed: %w", err)
	}
	return nil
}

// versionCommand shows detailed version information
func versionCommand(ctx context.Context, cmd *cli.Command) error {
	mustFprintf(os.Stdout, "Version:    %s\n", version)
	mustFprintf(os.Stdout, "Commit:     %s\n", commit)
	mustFprintf(os.Stdout, "Go Version: %s\n", goVersion)
	return nil
}

func runStdioMCPServer(w io.Writer, promptsDir string, logFile string, enableJSONArgs bool, quiet bool) error {
	// Configure logger
	logWriter := w
	if quiet {
		logWriter = io.Discard
	}
	if logFile != "" {
		file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		if err != nil {
			return fmt.Errorf("open log file: %w", err)
		}
		defer func() { _ = file.Close() }()
		logWriter = file
	}
	logger := slog.New(slog.NewTextHandler(logWriter, nil))

	// Create PromptsServer instance
	promptsSrv, err := NewPromptsServer(promptsDir, enableJSONArgs, logger)
	if err != nil {
		return fmt.Errorf("new prompts server: %w", err)
	}

	defer func() {
		if closeErr := promptsSrv.Close(); closeErr != nil {
			logger.Error("Failed to close prompts server", "error", closeErr)
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-sigChan
		logger.Info("Received shutdown signal, stopping server")
		cancel()
	}()

	return promptsSrv.ServeStdio(ctx, os.Stdin, os.Stdout)
}

// renderTemplate renders a specified template to stdout with resolved partials and environment variables
func renderTemplate(w io.Writer, promptsDir string, templateName string, cliArgs map[string]string, enableJSONArgs bool) error {
	templateName = strings.TrimSpace(templateName)
	if templateName == "" {
		return fmt.Errorf("template name is required")
	}
	if !strings.HasSuffix(templateName, templateExt) {
		templateName += templateExt
	}
	availableTemplates, err := getAvailableTemplates(promptsDir)
	if err != nil {
		return err
	}
	if !slices.Contains(availableTemplates, templateName) {
		return fmt.Errorf("template %s not found\n\n%s:\n  %s",
			errorText(templateName),
			infoText("Available templates"), strings.Join(availableTemplates, "\n  "))
	}

	parser := &PromptsParser{}

	tmpl, err := parser.ParseDir(promptsDir)
	if err != nil {
		return fmt.Errorf("parse all prompts: %w", err)
	}

	args, err := parser.ExtractPromptArgumentsFromTemplate(tmpl, templateName)
	if err != nil {
		return fmt.Errorf("extract template arguments: %w", err)
	}

	data := make(map[string]interface{})
	data["date"] = time.Now().Format("2006-01-02 15:04:05")

	// Parse CLI args with JSON support if enabled
	parseMCPArgs(cliArgs, enableJSONArgs, data)

	// Resolve variables from CLI args and environment variables
	for _, arg := range args {
		// Check if already set by CLI args (highest priority)
		if _, exists := data[arg]; !exists {
			// Fall back to environment variables
			envVarName := strings.ToUpper(arg)
			if envValue, envExists := os.LookupEnv(envVarName); envExists {
				data[arg] = envValue
			}
		}
	}

	var result bytes.Buffer
	if err = tmpl.ExecuteTemplate(&result, templateName, data); err != nil {
		return fmt.Errorf("execute template: %w", err)
	}
	_, err = w.Write(bytes.TrimSpace(result.Bytes()))
	return err
}

// listTemplates lists all available templates in the prompts directory
func listTemplates(w io.Writer, promptsDir string, verbose bool) error {
	availableTemplates, err := getAvailableTemplates(promptsDir)
	if err != nil {
		return err
	}
	if len(availableTemplates) == 0 {
		if verbose {
			mustFprintf(w, "No templates found in %s\n", pathText(promptsDir))
		}
		return nil
	}

	parser := &PromptsParser{}
	var tmpl *template.Template
	for _, templateName := range availableTemplates {
		if !verbose {
			// Simple list without description and variables
			mustFprintf(w, "%s\n", templateText(templateName))
			continue
		}

		mustFprintf(w, "%s\n", templateText(templateName))

		var description string
		if description, err = parser.ExtractPromptDescriptionFromFile(
			filepath.Join(promptsDir, templateName),
		); err != nil {
			mustFprintf(w, "%s\n", errorText(fmt.Sprintf("Error: %v", err)))
		} else {
			if description != "" {
				mustFprintf(w, "  Description: %s\n", description)
			} else {
				mustFprintf(w, "  Description:\n")
			}
		}

		if tmpl == nil {
			if tmpl, err = parser.ParseDir(promptsDir); err != nil {
				return fmt.Errorf("parse all prompts: %w", err)
			}
		}
		var args []string
		if args, err = parser.ExtractPromptArgumentsFromTemplate(tmpl, templateName); err != nil {
			mustFprintf(w, "%s\n", errorText(fmt.Sprintf("Error: %v", err)))
		} else {
			if len(args) > 0 {
				sort.Strings(args)
				mustFprintf(w, "  Variables: %s\n", highlightText(strings.Join(args, ", ")))
			} else {
				mustFprintf(w, "  Variables:\n")
			}
		}
	}

	return nil
}

// validateTemplates validates template syntax
func validateTemplates(w io.Writer, promptsDir string, templateName string) error {
	templateName = strings.TrimSpace(templateName)
	if templateName != "" && !strings.HasSuffix(templateName, templateExt) {
		templateName += templateExt
	}

	availableTemplates, err := getAvailableTemplates(promptsDir)
	if err != nil {
		return err
	}
	if templateName != "" {
		if !slices.Contains(availableTemplates, templateName) {
			return fmt.Errorf("template %q not found in %s", templateName, promptsDir)
		}
	}
	if len(availableTemplates) == 0 {
		mustFprintf(w, "%s No templates found in %s\n", warningIcon(), pathText(promptsDir))
		return nil
	}

	parser := &PromptsParser{}

	tmpl, err := parser.ParseDir(promptsDir)
	if err != nil {
		return fmt.Errorf("parse prompts directory: %w", err)
	}

	hasErrors := false
	for _, name := range availableTemplates {
		if templateName != "" && name != templateName {
			continue // Skip if not validating this template
		}
		// Try to extract arguments (this validates basic syntax)
		if _, err = parser.ExtractPromptArgumentsFromTemplate(tmpl, name); err != nil {
			mustFprintf(w, "%s %s - %s\n", errorIcon(), templateText(name), errorText(fmt.Sprintf("Error: %v", err)))
			hasErrors = true
			continue
		}
		mustFprintf(w, "%s %s - %s\n", successIcon(), templateText(name), successText("Valid"))
	}

	if hasErrors {
		return fmt.Errorf("some templates have validation errors")
	}

	return nil
}

func getAvailableTemplates(promptsDir string) ([]string, error) {
	files, err := os.ReadDir(promptsDir)
	if err != nil {
		return nil, fmt.Errorf("read prompts directory: %w", err)
	}
	var templateFiles []string
	for _, file := range files {
		if !isTemplateFile(file) {
			continue
		}
		templateFiles = append(templateFiles, file.Name())
	}
	sort.Strings(templateFiles)
	return templateFiles, nil
}

func mustFprintf(w io.Writer, format string, a ...interface{}) {
	if _, err := fmt.Fprintf(w, format, a...); err != nil {
		panic(fmt.Sprintf("Failed to write output: %v", err))
	}
}

```

--------------------------------------------------------------------------------
/prompts_parser_test.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"os"
	"path/filepath"
	"sort"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type PromptsParserTestSuite struct {
	suite.Suite
	parser  *PromptsParser
	tempDir string
}

func TestPromptsParserTestSuite(t *testing.T) {
	suite.Run(t, new(PromptsParserTestSuite))
}

func (s *PromptsParserTestSuite) SetupTest() {
	s.parser = &PromptsParser{}
	s.tempDir = s.T().TempDir()
}

// TestExtractTemplateArgumentsFromTemplate tests template argument extraction with various scenarios
func (s *PromptsParserTestSuite) TestExtractTemplateArgumentsFromTemplate() {
	tests := []struct {
		name        string
		content     string
		partials    map[string]string
		expected    []string
		description string
		shouldError bool
	}{
		{
			name:        "empty template",
			content:     "{{/* Empty template */}}\nNo arguments here",
			partials:    map[string]string{},
			expected:    []string{},
			description: "Empty template",
			shouldError: false,
		},
		{
			name:        "single argument",
			content:     "{{/* Single argument template */}}\nHello {{.name}}",
			partials:    map[string]string{},
			expected:    []string{"name"},
			description: "Single argument template",
			shouldError: false,
		},
		{
			name:        "multiple arguments",
			content:     "{{/* Multiple arguments template */}}\nHello {{.name}}, your project is {{.project}} and language is {{.language}}",
			partials:    map[string]string{},
			expected:    []string{"name", "project", "language"},
			description: "Multiple arguments template",
			shouldError: false,
		},
		{
			name:        "arguments with built-in date",
			content:     "{{/* Template with date */}}\nToday is {{.date}} and user is {{.username}}",
			partials:    map[string]string{},
			expected:    []string{"username"}, // date is built-in, should be filtered out
			description: "Template with date",
			shouldError: false,
		},
		{
			name:        "template with used partial only",
			content:     "{{/* Template with used partial only */}}\n{{template \"_header\" dict \"role\" .role \"task\" .task}}\nUser: {{.username}}",
			partials:    map[string]string{"_header": "You are {{.role}} doing {{.task}}", "_footer": "End with {{.conclusion}}"},
			expected:    []string{"role", "task", "username"}, // should NOT include conclusion from unused footer
			description: "Template with used partial only",
			shouldError: false,
		},
		{
			name:        "template with multiple used partials",
			content:     "{{/* Template with multiple partials */}}\n{{template \"_header\" dict \"role\" .role}}\n{{template \"_footer\" dict \"conclusion\" .conclusion}}\nUser: {{.username}}",
			partials:    map[string]string{"_header": "You are {{.role}}", "_footer": "End with {{.conclusion}}", "_unused": "This has {{.unused_var}}"},
			expected:    []string{"role", "conclusion", "username"}, // should NOT include unused_var
			description: "Template with multiple partials",
			shouldError: false,
		},
		{
			name:        "duplicate arguments",
			content:     "{{/* Duplicate arguments */}}\n{{.user}} said hello to {{.user}} again",
			partials:    map[string]string{},
			expected:    []string{"user"},
			description: "Duplicate arguments",
			shouldError: false,
		},
		{
			name:    "cyclic partial references",
			content: "{{/* Template with cyclic partials */}}\n{{template \"_a\" .}}\nMain content: {{.main}}",
			partials: map[string]string{
				"_a": "Partial A with {{.a_var}} {{template \"_b\" .}}",
				"_b": "Partial B with {{.b_var}} {{template \"_c\" .}}",
				"_c": "Partial C with {{.c_var}} {{template \"_a\" .}}", // Creates a cycle: a -> b -> c -> a
			},
			expected:    nil,
			description: "Template with cyclic partials",
			shouldError: true,
		},
		{
			name:        "template with or condition",
			content:     "{{/* Template with or condition */}}\n{{if or .show_message .show_alert}}Message: {{.message}}{{end}}\nAlways: {{.name}}",
			partials:    map[string]string{},
			expected:    []string{"show_message", "show_alert", "message", "name"},
			description: "Template with or condition",
			shouldError: false,
		},
		{
			name:        "template with variables",
			content:     "{{/* Template with variables */}}\n{{$name := .user_name}}{{$email := .user_email}}User: {{$name}} ({{$email}}) - Role: {{.role}}",
			partials:    map[string]string{},
			expected:    []string{"user_name", "user_email", "role"},
			description: "Template with variables",
			shouldError: false,
		},
		{
			name:        "template with range node",
			content:     "{{/* Template with range */}}\n{{range .items}}Item: {{.name}} - {{.value}}{{end}}\nTotal: {{.total}}",
			partials:    map[string]string{},
			expected:    []string{"items", "name", "value", "total"},
			description: "Template with range",
			shouldError: false,
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			// Create a temporary directory for this test
			testDir := filepath.Join(s.tempDir, tt.name)
			err := os.MkdirAll(testDir, 0755)
			require.NoError(s.T(), err, "Failed to create test directory")

			// Write the main template file
			testFile := filepath.Join(testDir, tt.name+".tmpl")
			err = os.WriteFile(testFile, []byte(tt.content), 0644)
			require.NoError(s.T(), err, "Failed to write test file")

			// Write partial files
			for partialName, partialContent := range tt.partials {
				partialFile := filepath.Join(testDir, partialName+".tmpl")
				err = os.WriteFile(partialFile, []byte(partialContent), 0644)
				require.NoError(s.T(), err, "Failed to write partial file")
			}

			// Parse all templates in the test directory
			tmpl, err := s.parser.ParseDir(testDir)
			require.NoError(s.T(), err, "Failed to parse templates")

			got, err := s.parser.ExtractPromptArgumentsFromTemplate(tmpl, tt.name)

			if tt.shouldError {
				assert.Error(s.T(), err, "ExtractPromptArgumentsFromTemplate() expected error, but got none")
				return
			}

			require.NoError(s.T(), err, "ExtractPromptArgumentsFromTemplate() unexpected error")

			// Sort both slices for consistent comparison
			sort.Strings(got)
			sort.Strings(tt.expected)

			assert.Equal(s.T(), tt.expected, got, "ExtractPromptArgumentsFromTemplate() returned unexpected arguments")
		})
	}
}

// TestExtractPromptDescriptionFromFile tests description extraction from template comments
func (s *PromptsParserTestSuite) TestExtractPromptDescriptionFromFile() {
	tests := []struct {
		name                string
		content             string
		expectedDescription string
	}{
		{
			name:                "valid template with description",
			content:             "{{/* Template description */}}",
			expectedDescription: "Template description",
		},
		{
			name:                "valid template with description, comment starts with dash",
			content:             "{{- /* Template description */}}",
			expectedDescription: "Template description",
		},
		{
			name:                "valid template with description, comment ends with dash",
			content:             "{{/* Template description */ -}}",
			expectedDescription: "Template description",
		},
		{
			name:                "valid template with description, comment starts and ends with dash",
			content:             "{{- /* Template description */ -}}",
			expectedDescription: "Template description",
		},
		{
			name:                "template without description",
			content:             "Hello {{.name}}",
			expectedDescription: "",
		},
		{
			name:                "template with valid comment and trim",
			content:             "{{/* Comment */}}",
			expectedDescription: "Comment",
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			testFile := filepath.Join(s.tempDir, tt.name+".tmpl")
			err := os.WriteFile(testFile, []byte(tt.content), 0644)
			require.NoError(s.T(), err, "Failed to write test file")

			description, err := s.parser.ExtractPromptDescriptionFromFile(testFile)
			require.NoError(s.T(), err, "ExtractPromptDescriptionFromFile() unexpected error")
			assert.Equal(s.T(), tt.expectedDescription, description, "ExtractPromptDescriptionFromFile() returned unexpected description")
		})
	}
}

// TestExtractPromptDescriptionFromFileErrorCases tests error cases for description extraction
func (s *PromptsParserTestSuite) TestExtractPromptDescriptionFromFileErrorCases() {
	// Test non-existent file
	_, err := s.parser.ExtractPromptDescriptionFromFile("/non/existent/file.tmpl")
	assert.Error(s.T(), err, "ExtractPromptDescriptionFromFile() expected error for non-existent file, but got none")
}

// TestExtractPromptArgumentsFromTemplateErrorCases tests error cases for argument extraction
func (s *PromptsParserTestSuite) TestExtractPromptArgumentsFromTemplateErrorCases() {
	// Create a valid template file so ParseDir doesn't fail
	testFile := filepath.Join(s.tempDir, "test.tmpl")
	err := os.WriteFile(testFile, []byte("{{/* Test */}}\nHello {{.name}}"), 0644)
	require.NoError(s.T(), err, "Failed to write test file")

	// Test non-existent template
	tmpl, err := s.parser.ParseDir(s.tempDir)
	require.NoError(s.T(), err, "Failed to parse templates")

	_, err = s.parser.ExtractPromptArgumentsFromTemplate(tmpl, "non_existent_template")
	assert.Error(s.T(), err, "ExtractPromptArgumentsFromTemplate() expected error for non-existent template, but got none")
}

// TestParseDirErrorCases tests error cases for template parsing
func (s *PromptsParserTestSuite) TestParseDirErrorCases() {
	// Test non-existent directory
	_, err := s.parser.ParseDir("/non/existent/directory")
	assert.Error(s.T(), err, "ParseDir() expected error for non-existent directory, but got none")

	// Test directory with invalid template syntax
	invalidFile := filepath.Join(s.tempDir, "invalid.tmpl")
	err = os.WriteFile(invalidFile, []byte("{{/* Invalid template */}}\n{{.unclosed"), 0644)
	require.NoError(s.T(), err, "Failed to write invalid template file")

	_, err = s.parser.ParseDir(s.tempDir)
	assert.Error(s.T(), err, "ParseDir() expected error for invalid template syntax, but got none")
}

// TestWalkNodesNilHandling tests nil node handling in walkNodes
func (s *PromptsParserTestSuite) TestWalkNodesNilHandling() {
	argsMap := make(map[string]struct{})
	builtInFields := map[string]struct{}{"date": {}}
	processedTemplates := make(map[string]bool)

	// This should return nil immediately for nil node
	err := s.parser.walkNodes(nil, argsMap, builtInFields, nil, processedTemplates, []string{})
	assert.NoError(s.T(), err, "walkNodes() with nil node should return nil")

	// argsMap should remain empty
	assert.Empty(s.T(), argsMap, "walkNodes() with nil node should not modify argsMap")
}

// TestWalkNodesVariableHandling tests variable node handling in walkNodes
func (s *PromptsParserTestSuite) TestWalkNodesVariableHandling() {
	// Create a template with a variable (non-$ variable)
	testFile := filepath.Join(s.tempDir, "test.tmpl")
	err := os.WriteFile(testFile, []byte("{{/* Test template */}}\n{{$var := .input}}{{$var}}"), 0644)
	require.NoError(s.T(), err, "Failed to write test file")

	tmpl, err := s.parser.ParseDir(s.tempDir)
	require.NoError(s.T(), err, "Failed to parse templates")

	// Test extracting arguments - should handle variable nodes properly
	args, err := s.parser.ExtractPromptArgumentsFromTemplate(tmpl, "test")
	require.NoError(s.T(), err, "ExtractPromptArgumentsFromTemplate() unexpected error")

	// Should only contain "input", not the template variables
	expected := []string{"input"}
	assert.Equal(s.T(), expected, args, "ExtractPromptArgumentsFromTemplate() should only return template data arguments, not dollar variables")
}

// TestDict tests the dict helper function
func (s *PromptsParserTestSuite) TestDict() {
	tests := []struct {
		name     string
		args     []string
		expected map[string]interface{}
		hasError bool
	}{
		{
			name:     "empty args",
			args:     []string{},
			expected: map[string]interface{}{},
			hasError: false,
		},
		{
			name:     "single key-value pair",
			args:     []string{"key", "value"},
			expected: map[string]interface{}{"key": "value"},
			hasError: false,
		},
		{
			name:     "multiple key-value pairs",
			args:     []string{"key1", "value1", "key2", "value2"},
			expected: map[string]interface{}{"key1": "value1", "key2": "value2"},
			hasError: false,
		},
		{
			name:     "odd number of arguments",
			args:     []string{"key1", "value1", "key2"},
			expected: nil,
			hasError: true,
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			// Convert string slice to interface slice
			args := make([]interface{}, len(tt.args))
			for i, v := range tt.args {
				args[i] = v
			}

			result := dict(args...)
			if tt.hasError {
				assert.Nil(s.T(), result, "dict() expected nil result for error case")
				return
			}
			assert.NotNil(s.T(), result, "dict() unexpected nil result")
			assert.Equal(s.T(), tt.expected, result, "dict() returned unexpected result")
		})
	}

	// Test non-string key
	s.Run("non-string key", func() {
		result := dict(123, "value")
		assert.Nil(s.T(), result, "dict() expected nil result for non-string key")
	})
}

```

--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"bytes"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type MainTestSuite struct {
	suite.Suite
	tempDir string
}

func TestMainTestSuite(t *testing.T) {
	suite.Run(t, new(MainTestSuite))
}

func (s *MainTestSuite) SetupTest() {
	s.tempDir = s.T().TempDir()
}

// TestRenderTemplateErrorCases tests error cases for template rendering
func (s *MainTestSuite) TestRenderTemplateErrorCases() {
	var buf bytes.Buffer

	// Test non-existent directory
	err := renderTemplate(&buf, "/non/existent/directory", "template_name", nil, true)
	assert.Error(s.T(), err, "renderTemplate() expected error for non-existent directory")

	// Test template execution error with missing template
	testFile := s.tempDir + "/error.tmpl"
	// Create a template that will cause execution error (missing template reference)
	err = os.WriteFile(testFile, []byte("{{/* Error template */}}\n{{template \"missing_template\" .}}"), 0644)
	require.NoError(s.T(), err, "Failed to write test file")

	var errorBuf bytes.Buffer
	err = renderTemplate(&errorBuf, s.tempDir, "error", nil, true)
	assert.Error(s.T(), err, "renderTemplate() expected execution error for missing template")

	// Test error with non-existent template in renderTemplate
	var nonExistentBuf bytes.Buffer
	err = renderTemplate(&nonExistentBuf, s.tempDir, "does_not_exist", nil, true)
	assert.Error(s.T(), err, "renderTemplate() expected error for non-existent template")
}

// TestRenderTemplate tests template rendering with environment variables and CLI arguments
func (s *MainTestSuite) TestRenderTemplate() {
	tests := []struct {
		name           string
		templateName   string
		cliArgs        map[string]string
		envVars        map[string]string
		enableJSONArgs bool
		expectedOutput string
		shouldError    bool
	}{
		{
			name:           "greeting template, no vars set",
			templateName:   "greeting",
			enableJSONArgs: true,
			expectedOutput: "Hello <no value>!\nHave a great day!",
			shouldError:    false,
		},
		{
			name:         "greeting template with env var",
			templateName: "greeting",
			envVars: map[string]string{
				"NAME": "John",
			},
			enableJSONArgs: true,
			expectedOutput: "Hello John!\nHave a great day!",
			shouldError:    false,
		},
		{
			name:         "greeting template with CLI arg",
			templateName: "greeting",
			cliArgs: map[string]string{
				"name": "Alice",
			},
			enableJSONArgs: true,
			expectedOutput: "Hello Alice!\nHave a great day!",
			shouldError:    false,
		},
		{
			name:         "CLI args override env vars",
			templateName: "greeting",
			cliArgs: map[string]string{
				"name": "CLI_User",
			},
			envVars: map[string]string{
				"NAME": "ENV_User",
			},
			enableJSONArgs: true,
			expectedOutput: "Hello CLI_User!\nHave a great day!",
			shouldError:    false,
		},
		{
			name:         "template with partials, some env vars not set",
			templateName: "multiple_partials",
			envVars: map[string]string{
				"TITLE":   "Test Document",
				"NAME":    "Bob",
				"VERSION": "1.0.0",
			},
			enableJSONArgs: true,
			expectedOutput: "# Test Document\nCreated by: <no value>\n## Description\n<no value>\n## Details\nThis is a test template with multiple partials.\nHello Bob!\nVersion: 1.0.0",
			shouldError:    false,
		},
		{
			name:         "template with partials, all env vars set",
			templateName: "multiple_partials",
			envVars: map[string]string{
				"TITLE":       "Test Document",
				"AUTHOR":      "Test Author",
				"NAME":        "Bob",
				"DESCRIPTION": "This is a test description",
				"VERSION":     "1.0.0",
			},
			expectedOutput: "# Test Document\nCreated by: Test Author\n## Description\nThis is a test description\n## Details\nThis is a test template with multiple partials.\nHello Bob!\nVersion: 1.0.0",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "conditional greeting with env vars, show extra message true",
			templateName: "conditional_greeting",
			envVars: map[string]string{
				"NAME":               "Alice",
				"SHOW_EXTRA_MESSAGE": "true",
			},
			expectedOutput: "Hello Alice!\nThis is an extra message just for you.\nHave a good day.",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "conditional greeting with env vars, show extra message false",
			templateName: "conditional_greeting",
			envVars: map[string]string{
				"NAME":               "Bob",
				"SHOW_EXTRA_MESSAGE": "",
			},
			expectedOutput: "Hello Bob!\nHave a good day.",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "conditional greeting with multiple CLI args",
			templateName: "conditional_greeting",
			cliArgs: map[string]string{
				"name":               "Bob",
				"show_extra_message": "true",
			},
			expectedOutput: "Hello Bob!\nThis is an extra message just for you.\nHave a good day.",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "mix CLI args and env vars",
			templateName: "conditional_greeting",
			cliArgs: map[string]string{
				"name": "Charlie",
			},
			envVars: map[string]string{
				"SHOW_EXTRA_MESSAGE": "true",
			},
			expectedOutput: "Hello Charlie!\nThis is an extra message just for you.\nHave a good day.",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "CLI arg with empty value",
			templateName: "greeting",
			cliArgs: map[string]string{
				"name": "",
			},
			expectedOutput: "Hello !\nHave a great day!",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "template with logical operators (and/or)",
			templateName: "logical_operators",
			envVars: map[string]string{
				"IS_ADMIN":        "true",
				"HAS_PERMISSION":  "true",
				"RESOURCE":        "server logs",
				"SHOW_WARNING":    "",
				"SHOW_ERROR":      "true",
				"MESSAGE":         "System maintenance scheduled",
				"IS_PREMIUM":      "true",
				"IS_TRIAL":        "",
				"FEATURE_ENABLED": "true",
				"FEATURE_NAME":    "Advanced Analytics",
				"USERNAME":        "admin_user",
			},
			expectedOutput: "Admin Access: You have full access to server logs.\nAlert: System maintenance scheduled\nPremium Feature: Advanced Analytics is available.\nUser: admin_user",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "JSON parsing enabled - boolean true",
			templateName: "conditional_greeting",
			cliArgs: map[string]string{
				"name":               "JSONUser",
				"show_extra_message": "true",
			},
			expectedOutput: "Hello JSONUser!\nThis is an extra message just for you.\nHave a good day.",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "JSON parsing enabled - boolean false",
			templateName: "conditional_greeting",
			cliArgs: map[string]string{
				"name":               "JSONUser",
				"show_extra_message": "false",
			},
			expectedOutput: "Hello JSONUser!\nHave a good day.",
			enableJSONArgs: true,
			shouldError:    false,
		},
		{
			name:         "JSON parsing disabled - boolean string remains string",
			templateName: "conditional_greeting",
			cliArgs: map[string]string{
				"name":               "StringUser",
				"show_extra_message": "true",
			},
			enableJSONArgs: false,
			expectedOutput: "Hello StringUser!\nThis is an extra message just for you.\nHave a good day.",
			shouldError:    false,
		},
		{
			name:         "JSON parsing disabled - false string treated as truthy",
			templateName: "conditional_greeting",
			cliArgs: map[string]string{
				"name":               "StringUser",
				"show_extra_message": "false",
			},
			enableJSONArgs: false,
			expectedOutput: "Hello StringUser!\nThis is an extra message just for you.\nHave a good day.",
			shouldError:    false,
		},
		{
			name:           "non-existent template",
			templateName:   "non_existent_template",
			enableJSONArgs: true,
			expectedOutput: "",
			shouldError:    true,
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			// Save original environment
			originalEnv := make(map[string]string)
			for k := range tt.envVars {
				if v, ok := os.LookupEnv(k); ok {
					originalEnv[k] = v
				}
			}
			defer func() {
				for k := range tt.envVars {
					if v, ok := originalEnv[k]; ok {
						_ = os.Setenv(k, v)
					} else {
						_ = os.Unsetenv(k)
					}
				}
			}()

			// Set test environment variables
			for k, v := range tt.envVars {
				_ = os.Setenv(k, v)
			}

			var buf bytes.Buffer
			err := renderTemplate(&buf, "./testdata", tt.templateName, tt.cliArgs, tt.enableJSONArgs)

			if tt.shouldError {
				assert.Error(s.T(), err, "expected error but got none")
			} else {
				require.NoError(s.T(), err, "unexpected error")
			}

			output := normalizeNewlines(buf.String())
			assert.Equal(s.T(), tt.expectedOutput, output, "unexpected output")
		})
	}
}

// normalizeNewlines is a helper function to normalize newlines in strings
func normalizeNewlines(s string) string {
	// Replace multiple consecutive newlines with single newlines
	for strings.Contains(s, "\n\n") {
		s = strings.ReplaceAll(s, "\n\n", "\n")
	}
	return strings.TrimSpace(s)
}

// removeANSIColors removes ANSI color escape sequences from a string
func removeANSIColors(s string) string {
	ansiRegex := regexp.MustCompile(`\x1b\[[0-9;]*m`)
	return ansiRegex.ReplaceAllString(s, "")
}

// TestListTemplates tests the listTemplates function
func (s *MainTestSuite) TestListTemplates() {
	tests := []struct {
		name          string
		detailed      bool
		expectedLines []string
		shouldError   bool
	}{
		{
			name:     "list templates basic mode",
			detailed: false,
			expectedLines: []string{
				templateText("conditional_greeting.tmpl"),
				templateText("greeting.tmpl"),
				templateText("greeting_with_partials.tmpl"),
				templateText("logical_operators.tmpl"),
				templateText("multiple_partials.tmpl"),
				templateText("range_scalars.tmpl"),
				templateText("range_structs.tmpl"),
				templateText("with_object.tmpl"),
			},
			shouldError: false,
		},
		{
			name:     "list templates verbose mode",
			detailed: true,
			expectedLines: []string{
				templateText("conditional_greeting.tmpl"),
				"  Description: Conditional greeting template",
				"  Variables: name, show_extra_message",
				templateText("greeting.tmpl"),
				"  Description: Greeting standalone template with no partials",
				"  Variables: name",
				templateText("greeting_with_partials.tmpl"),
				"  Description: Greeting template with partial",
				"  Variables: name",
				templateText("logical_operators.tmpl"),
				"  Description: Template with logical operators (and/or) in if blocks",
				"  Variables: feature_enabled, feature_name, has_permission, is_admin, is_premium, is_trial, message, resource, show_error, show_warning, username",
				templateText("multiple_partials.tmpl"),
				"  Description: Template with multiple partials",
				"  Variables: author, description, name, title, version",
				templateText("range_scalars.tmpl"),
				"  Description: Template for testing range with JSON array of scalars",
				"  Variables: numbers, result, tags",
				templateText("range_structs.tmpl"),
				"  Description: Template for testing range with JSON array of structs",
				"  Variables: age, name, role, total, users",
				templateText("with_object.tmpl"),
				"  Description: Template for testing with + JSON object",
				"  Variables: config, debug, environment, name, version",
			},
			shouldError: false,
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			var buf bytes.Buffer
			err := listTemplates(&buf, "./testdata", tt.detailed)

			if tt.shouldError {
				assert.Error(s.T(), err, "expected error but got none")
			} else {
				require.NoError(s.T(), err, "unexpected error")
			}

			output := buf.String()
			lines := strings.Split(strings.TrimSpace(output), "\n")

			// For basic mode, check exact match
			if !tt.detailed {
				assert.Equal(s.T(), len(tt.expectedLines), len(lines), "number of lines should match")
				for i, expectedLine := range tt.expectedLines {
					if i < len(lines) {
						assert.Equal(s.T(), expectedLine, lines[i], "line %d should match", i)
					}
				}
				return
			}

			// For detailed mode, check exact match including variables
			lineIndex := 0
			for _, expectedLine := range tt.expectedLines {
				if lineIndex >= len(lines) {
					s.T().Fatalf("Not enough lines in output. Expected at least %d, got %d", len(tt.expectedLines), len(lines))
				}

				if strings.HasPrefix(expectedLine, "  Variables: ") {
					// Remove ANSI color codes from the actual line for comparison
					actualLine := removeANSIColors(lines[lineIndex])
					assert.Equal(s.T(), expectedLine, actualLine, "line %d should match (variables are now sorted)", lineIndex)
				} else {
					assert.Equal(s.T(), expectedLine, lines[lineIndex], "line %d should match", lineIndex)
				}
				lineIndex++
			}
		})
	}
}

// TestListTemplatesErrorCases tests error cases for listTemplates
func (s *MainTestSuite) TestListTemplatesErrorCases() {
	var buf bytes.Buffer

	// Test non-existent directory
	err := listTemplates(&buf, "/non/existent/directory", false)
	assert.Error(s.T(), err, "listTemplates() expected error for non-existent directory")

	// Test empty directory
	emptyDir := s.T().TempDir()
	var emptyBuf bytes.Buffer
	err = listTemplates(&emptyBuf, emptyDir, true)
	require.NoError(s.T(), err, "listTemplates() should not error for empty directory")
	output := emptyBuf.String()
	assert.Contains(s.T(), output, "No templates found", "should indicate no templates found")
	emptyBuf.Reset()
	err = listTemplates(&emptyBuf, emptyDir, false)
	require.NoError(s.T(), err, "listTemplates() should not error for empty directory")
	require.Empty(s.T(), emptyBuf.String())
}

// TestListTemplatesWithPartials tests that partials are excluded from listing
func (s *MainTestSuite) TestListTemplatesWithPartials() {
	// Create a temp directory with templates and partials
	tempDir := s.T().TempDir()

	// Create regular template
	err := os.WriteFile(tempDir+"/regular.tmpl", []byte("{{/* Regular template */}}\nHello!"), 0644)
	require.NoError(s.T(), err)

	// Create partial template (should be excluded)
	err = os.WriteFile(tempDir+"/_partial.tmpl", []byte("{{/* Partial template */}}\nThis is a partial"), 0644)
	require.NoError(s.T(), err)

	var buf bytes.Buffer
	err = listTemplates(&buf, tempDir, false)
	require.NoError(s.T(), err)

	output := buf.String()
	assert.Contains(s.T(), output, "regular.tmpl", "should include regular template")
	assert.NotContains(s.T(), output, "_partial.tmpl", "should exclude partial template")
}

// TestValidateTemplates tests the validateTemplates function
func (s *MainTestSuite) TestValidateTemplates() {
	tests := []struct {
		name           string
		templateName   string
		templates      map[string]string
		expectedOutput []string
		shouldError    bool
	}{
		{
			name:         "validate all valid templates",
			templateName: "",
			templates: map[string]string{
				"valid1.tmpl": "{{/* Valid template 1 */}}\nHello {{.name}}!",
				"valid2.tmpl": "{{/* Valid template 2 */}}\nWelcome {{.user}}!",
			},
			expectedOutput: []string{
				"✓ valid1.tmpl - Valid",
				"✓ valid2.tmpl - Valid",
			},
			shouldError: false,
		},
		{
			name:         "validate specific valid template",
			templateName: "valid1.tmpl",
			templates: map[string]string{
				"valid1.tmpl": "{{/* Valid template 1 */}}\nHello {{.name}}!",
				"valid2.tmpl": "{{/* Valid template 2 */}}\nWelcome {{.user}}!",
			},
			expectedOutput: []string{
				"✓ valid1.tmpl - Valid",
			},
			shouldError: false,
		},
		{
			name:         "validate specific valid template without extension",
			templateName: "valid1",
			templates: map[string]string{
				"valid1.tmpl": "{{/* Valid template 1 */}}\nHello {{.name}}!",
				"valid2.tmpl": "{{/* Valid template 2 */}}\nWelcome {{.user}}!",
			},
			expectedOutput: []string{
				"✓ valid1.tmpl - Valid",
			},
			shouldError: false,
		},
		{
			name:         "validate template with missing reference",
			templateName: "",
			templates: map[string]string{
				"valid.tmpl":       "{{/* Valid template */}}\nHello {{.name}}!",
				"missing_ref.tmpl": "{{/* Template with missing reference */}}\n{{template \"nonexistent\" .}}",
			},
			expectedOutput: []string{
				"✗ missing_ref.tmpl - Error:",
				"✓ valid.tmpl - Valid",
			},
			shouldError: true,
		},
		{
			name:         "validate specific template with missing reference",
			templateName: "missing_ref.tmpl",
			templates: map[string]string{
				"valid.tmpl":       "{{/* Valid template */}}\nHello {{.name}}!",
				"missing_ref.tmpl": "{{/* Template with missing reference */}}\n{{template \"nonexistent\" .}}",
			},
			expectedOutput: []string{
				"✗ missing_ref.tmpl - Error:",
			},
			shouldError: true,
		},
		{
			name:         "validate template with partials",
			templateName: "",
			templates: map[string]string{
				"main.tmpl":     "{{/* Main template */}}\n{{template \"_partial\" .}}",
				"_partial.tmpl": "{{/* Partial template */}}\nHello {{.name}}!",
			},
			expectedOutput: []string{
				"✓ main.tmpl - Valid",
			},
			shouldError: false,
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			// Create temp directory and test templates
			tempDir := s.T().TempDir()
			for filename, content := range tt.templates {
				err := os.WriteFile(filepath.Join(tempDir, filename), []byte(content), 0644)
				require.NoError(s.T(), err)
			}

			// Run validateTemplates and capture output from buffer
			var buf bytes.Buffer
			err := validateTemplates(&buf, tempDir, tt.templateName)

			if tt.shouldError {
				assert.Error(s.T(), err, "expected error but got none")
			} else {
				require.NoError(s.T(), err, "unexpected error")
			}

			output := buf.String()
			lines := strings.Split(strings.TrimSpace(output), "\n")

			// Check that all expected output lines are present
			for _, expectedLine := range tt.expectedOutput {
				found := false
				for _, line := range lines {
					// Remove ANSI color codes for comparison
					cleanLine := removeANSIColors(line)
					cleanExpected := removeANSIColors(expectedLine)
					if strings.Contains(cleanLine, cleanExpected) ||
						(strings.Contains(cleanExpected, "Error:") && strings.Contains(cleanLine, "Error:")) {
						found = true
						break
					}
				}
				assert.True(s.T(), found, "expected line '%s' not found in output: %s", expectedLine, output)
			}
		})
	}
}

// TestValidateTemplatesErrorCases tests error cases for validateTemplates
func (s *MainTestSuite) TestValidateTemplatesErrorCases() {
	tests := []struct {
		name          string
		promptsDir    string
		templateName  string
		setupFunc     func(string) error
		expectedError string
	}{
		{
			name:          "non-existent directory",
			promptsDir:    "/non/existent/directory",
			templateName:  "",
			expectedError: "read prompts directory",
		},
		{
			name:         "non-existent specific template",
			promptsDir:   "",
			templateName: "does_not_exist.tmpl",
			setupFunc: func(dir string) error {
				return os.WriteFile(filepath.Join(dir, "exists.tmpl"), []byte("{{/* Exists */}}\nHello!"), 0644)
			},
			expectedError: "not found",
		},
		{
			name:         "non-existent specific template without extension",
			promptsDir:   "",
			templateName: "does_not_exist",
			setupFunc: func(dir string) error {
				return os.WriteFile(filepath.Join(dir, "exists.tmpl"), []byte("{{/* Exists */}}\nHello!"), 0644)
			},
			expectedError: "not found",
		},
		{
			name:       "empty directory",
			promptsDir: "",
			setupFunc:  func(dir string) error { return nil },
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			var tempDir string
			if tt.promptsDir == "" {
				tempDir = s.T().TempDir()
				if tt.setupFunc != nil {
					err := tt.setupFunc(tempDir)
					require.NoError(s.T(), err)
				}
			} else {
				tempDir = tt.promptsDir
			}

			var buf bytes.Buffer
			err := validateTemplates(&buf, tempDir, tt.templateName)

			if tt.expectedError != "" {
				assert.Error(s.T(), err)
				assert.Contains(s.T(), err.Error(), tt.expectedError)
			} else {
				// For empty directory case, should not error but output warning
				require.NoError(s.T(), err)
				output := buf.String()
				assert.Contains(s.T(), output, "No templates found")
			}
		})
	}
}

// TestValidateTemplatesOutput tests the output formatting of validateTemplates
func (s *MainTestSuite) TestValidateTemplatesOutput() {
	// Test with syntax error that occurs during parsing
	tempDir := s.T().TempDir()

	// Invalid template with syntax error
	err := os.WriteFile(filepath.Join(tempDir, "invalid.tmpl"),
		[]byte("{{/* Invalid template */}}\nHello {{.name}"), 0644)
	require.NoError(s.T(), err)

	var buf bytes.Buffer
	err = validateTemplates(&buf, tempDir, "")
	assert.Error(s.T(), err)
	assert.Contains(s.T(), err.Error(), "parse prompts directory")

	// Test with valid templates to verify successful output formatting
	tempDir2 := s.T().TempDir()

	// Valid template
	err = os.WriteFile(filepath.Join(tempDir2, "valid.tmpl"),
		[]byte("{{/* Valid template */}}\nHello {{.name}}!"), 0644)
	require.NoError(s.T(), err)

	// Run validateTemplates and capture output from buffer
	var buf2 bytes.Buffer
	err = validateTemplates(&buf2, tempDir2, "")
	require.NoError(s.T(), err)

	output := buf2.String()
	cleanOutput := removeANSIColors(output)

	// Check that output contains the template
	assert.Contains(s.T(), cleanOutput, "valid.tmpl")

	// Check formatting - should contain success icon
	assert.Contains(s.T(), cleanOutput, "✓") // Success icon

	// Check status message
	assert.Contains(s.T(), cleanOutput, "Valid")
}

```

--------------------------------------------------------------------------------
/prompts_server_test.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"bytes"
	"context"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/client/transport"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type PromptsServerTestSuite struct {
	suite.Suite
	tempDir string
	logger  *slog.Logger
}

func TestTestSuite(t *testing.T) {
	suite.Run(t, new(PromptsServerTestSuite))
}

func (s *PromptsServerTestSuite) SetupTest() {
	s.tempDir = s.T().TempDir()
	s.logger = slog.New(slog.DiscardHandler)
}

// TestServeStdio tests comprehensive server integration with prompts using ServeStdio
func (s *PromptsServerTestSuite) TestServeStdio() {
	ctx := context.Background()

	tests := []struct {
		name            string
		enableJSONArgs  bool
		promptName      string
		arguments       map[string]string
		expectedContent string // If empty, only basic validation is performed
		description     string
	}{
		{
			name:            "BasicFunctionality",
			enableJSONArgs:  false,
			promptName:      "greeting",
			arguments:       map[string]string{"name": "John"},
			expectedContent: "Hello John!\nHave a great day!",
			description:     "Test basic functionality without JSON argument parsing",
		},
		{
			name:           "WithJSONArgumentParsing",
			enableJSONArgs: true,
			promptName:     "conditional_greeting",
			arguments: map[string]string{
				"name":               "Alice",
				"show_extra_message": "false", // JSON boolean becomes actual boolean
			},
			expectedContent: "Hello Alice!\nHave a good day.",
			description:     "Test JSON boolean parsing - 'false' becomes boolean false",
		},
		{
			name:           "WithDisabledJSONArgumentParsing",
			enableJSONArgs: false,
			promptName:     "conditional_greeting",
			arguments: map[string]string{
				"name":               "Bob",
				"show_extra_message": "false", // Remains string "false" (truthy!)
			},
			expectedContent: "Hello Bob!\nThis is an extra message just for you.\nHave a good day.",
			description:     "Test disabled JSON parsing - 'false' string is truthy",
		},
		// All testdata prompts with JSON parsing enabled (exact content validation)
		{
			name:            "greeting",
			enableJSONArgs:  true,
			promptName:      "greeting",
			arguments:       map[string]string{"name": "TestUser"},
			description:     "Test greeting template",
			expectedContent: "Hello TestUser!\nHave a great day!",
		},
		{
			name:            "conditional_greeting",
			enableJSONArgs:  true,
			promptName:      "conditional_greeting",
			arguments:       map[string]string{"name": "TestUser", "show_extra_message": "true"},
			description:     "Test conditional greeting template",
			expectedContent: "Hello TestUser!\nThis is an extra message just for you.\nHave a good day.",
		},
		{
			name:            "greeting_with_partials",
			enableJSONArgs:  true,
			promptName:      "greeting_with_partials",
			arguments:       map[string]string{"name": "TestUser"},
			description:     "Test greeting template with partials",
			expectedContent: "Hello TestUser!\nWelcome to the system.\nHave a great day!",
		},
		{
			name:           "logical_operators",
			enableJSONArgs: true,
			promptName:     "logical_operators",
			arguments: map[string]string{
				"is_admin":        "true",
				"has_permission":  "true",
				"resource":        "admin_panel",
				"show_warning":    "true",
				"show_error":      "false",
				"message":         "System maintenance in progress",
				"is_premium":      "true",
				"is_trial":        "false",
				"feature_enabled": "true",
				"feature_name":    "Advanced Analytics",
				"username":        "TestUser",
			},
			description:     "Test template with logical operators",
			expectedContent: "Admin Access: You have full access to admin_panel.\nAlert: System maintenance in progress\nPremium Feature: Advanced Analytics is available.\nUser: TestUser",
		},
		{
			name:           "multiple_partials",
			enableJSONArgs: true,
			promptName:     "multiple_partials",
			arguments: map[string]string{
				"name":        "TestUser",
				"title":       "Test Title",
				"author":      "Test Author",
				"description": "This is a test description for the template",
				"version":     "v1.0.0",
			},
			description:     "Test template with multiple partials",
			expectedContent: "# Test Title\nCreated by: Test Author\n## Description\nThis is a test description for the template\n## Details\nThis is a test template with multiple partials.\nHello TestUser!\nVersion: v1.0.0",
		},
		{
			name:           "range_scalars",
			enableJSONArgs: true,
			promptName:     "range_scalars",
			arguments: map[string]string{
				"numbers": `[1, 2, 3, 4, 5]`,
				"tags":    `["go", "template", "test"]`,
				"result":  "success",
			},
			description:     "Test template with range over scalars",
			expectedContent: "Numbers: 1 2 3 4 5 \nTags: #go #template #test \nResult: success",
		},
		{
			name:           "range_structs",
			enableJSONArgs: true,
			promptName:     "range_structs",
			arguments: map[string]string{
				"users": `[{"name": "Alice", "age": 30, "role": "admin"}, {"name": "Bob", "age": 25, "role": "user"}]`,
				"total": "2",
			},
			description:     "Test template with range over structs",
			expectedContent: "Users:\n  - Alice (30) - admin\n  - Bob (25) - user\nTotal: 2 users",
		},
		{
			name:           "with_object",
			enableJSONArgs: true,
			promptName:     "with_object",
			arguments: map[string]string{
				"config":      `{"name": "MyApp", "version": "1.2.3", "debug": true}`,
				"environment": "development",
			},
			description:     "Test template with object argument",
			expectedContent: "Configuration:\n  Name: MyApp\n  Version: 1.2.3\n  Debug: true\nEnvironment: development",
		},
	}

	for _, tc := range tests {
		s.Run(tc.name, func() {
			// Create prompts server that will watch ./testdata directory
			_, mcpClient, promptsClose := s.makePromptsServerAndClient(ctx, "./testdata", tc.enableJSONArgs)
			defer promptsClose()

			// List all available prompts to verify prompt exists
			listResult, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
			require.NoError(s.T(), err, "ListPrompts failed for %s", tc.name)

			// Verify prompt exists in list
			var foundPrompt *mcp.Prompt
			for _, prompt := range listResult.Prompts {
				if prompt.Name == tc.promptName {
					foundPrompt = &prompt
					break
				}
			}
			require.NotNil(s.T(), foundPrompt, "Prompt %s not found in list", tc.promptName)

			// Test GetPrompt with specified arguments
			var getReq mcp.GetPromptRequest
			getReq.Params.Name = tc.promptName
			getReq.Params.Arguments = tc.arguments
			getResult, err := mcpClient.GetPrompt(ctx, getReq)
			require.NoError(s.T(), err, "GetPrompt failed for %s", tc.name)

			// Verify basic response structure
			assert.NotEmpty(s.T(), getResult.Description, "Expected non-empty description for %s", tc.name)
			require.Len(s.T(), getResult.Messages, 1, "Expected exactly 1 message for %s", tc.name)

			content, ok := getResult.Messages[0].Content.(mcp.TextContent)
			require.True(s.T(), ok, "Expected TextContent for %s", tc.name)
			assert.NotEmpty(s.T(), content.Text, "Expected non-empty content for %s", tc.name)

			actualContent := normalizeNewlines(content.Text)
			assert.Equal(s.T(), tc.expectedContent, actualContent, "Unexpected content for %s: %s", tc.name, tc.description)
		})
	}
}

// TestParseMCPArgs tests parseMCPArgs function functionality
func (s *PromptsServerTestSuite) TestParseMCPArgs() {
	tests := []struct {
		name           string
		input          map[string]string
		enableJSONArgs bool
		expected       map[string]interface{}
	}{
		{
			name:           "empty arguments with JSON enabled",
			input:          map[string]string{},
			enableJSONArgs: true,
			expected:       map[string]interface{}{},
		},
		{
			name: "string arguments remain strings with JSON enabled",
			input: map[string]string{
				"name":    "John",
				"message": "Hello World",
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"name":    "John",
				"message": "Hello World",
			},
		},
		{
			name: "boolean arguments become booleans with JSON enabled",
			input: map[string]string{
				"enabled":  "true",
				"disabled": "false",
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"enabled":  true,
				"disabled": false,
			},
		},
		{
			name: "number arguments become numbers with JSON enabled",
			input: map[string]string{
				"count":   "42",
				"price":   "19.99",
				"balance": "-100.5",
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"count":   float64(42),
				"price":   19.99,
				"balance": -100.5,
			},
		},
		{
			name: "null argument becomes nil with JSON enabled",
			input: map[string]string{
				"optional": "null",
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"optional": nil,
			},
		},
		{
			name: "array arguments become arrays with JSON enabled",
			input: map[string]string{
				"items":   `["apple", "banana", "cherry"]`,
				"numbers": `[1, 2, 3]`,
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"items":   []interface{}{"apple", "banana", "cherry"},
				"numbers": []interface{}{float64(1), float64(2), float64(3)},
			},
		},
		{
			name: "object arguments become objects with JSON enabled",
			input: map[string]string{
				"user": `{"name": "Alice", "age": 30, "active": true}`,
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"user": map[string]interface{}{
					"name":   "Alice",
					"age":    float64(30),
					"active": true,
				},
			},
		},
		{
			name: "invalid JSON remains as strings with JSON enabled",
			input: map[string]string{
				"invalid_json": `{name: "Alice"}`,  // Missing quotes around key
				"incomplete":   `{"name": "Alice"`, // Missing closing brace
			},
			enableJSONArgs: true,
			expected: map[string]interface{}{
				"invalid_json": `{name: "Alice"}`,
				"incomplete":   `{"name": "Alice"`,
			},
		},
		{
			name: "all arguments remain strings when JSON disabled",
			input: map[string]string{
				"name":     "John",
				"enabled":  "true",
				"count":    "42",
				"optional": "null",
				"items":    `["a", "b"]`,
			},
			enableJSONArgs: false,
			expected: map[string]interface{}{
				"name":     "John",
				"enabled":  "true",
				"count":    "42",
				"optional": "null",
				"items":    `["a", "b"]`,
			},
		},
	}

	for _, tt := range tests {
		s.Run(tt.name, func() {
			data := make(map[string]interface{})
			parseMCPArgs(tt.input, tt.enableJSONArgs, data)
			assert.Equal(s.T(), tt.expected, data, "parseMCPArgs() returned unexpected result")
		})
	}
}

// TestReloadPromptsNewPromptAdded tests reloadPrompts method with new prompts via ServeStdio
func (s *PromptsServerTestSuite) TestReloadPromptsNewPromptAdded() {
	ctx := context.Background()

	// Create initial prompt file so ParseDir doesn't fail
	initialPromptFile := filepath.Join(s.tempDir, "initial_prompt.tmpl")
	initialPromptContent := `{{/* Initial test prompt */}}
Hello {{.name}}! This is the initial prompt.`
	err := os.WriteFile(initialPromptFile, []byte(initialPromptContent), 0644)
	require.NoError(s.T(), err, "Failed to write initial prompt file")

	// Create prompts server that will watch the temp directory
	_, mcpClient, promptsClose := s.makePromptsServerAndClient(ctx, s.tempDir, true)
	defer promptsClose()

	// Verify initial prompt exists
	listResult, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt initially")
	assert.Equal(s.T(), "initial_prompt", listResult.Prompts[0].Name, "Unexpected initial prompt name")

	// Create a new prompt file on filesystem
	newPromptFile := filepath.Join(s.tempDir, "new_prompt.tmpl")
	newPromptContent := `{{/* New test prompt */}}
Hello {{.name}}! This is a new prompt.`
	err = os.WriteFile(newPromptFile, []byte(newPromptContent), 0644)
	require.NoError(s.T(), err, "Failed to write new prompt file")

	// Give the client-server communication time to process the changes
	time.Sleep(100 * time.Millisecond)

	// Client should now see both prompts
	listResult, err = mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed after adding prompt")
	require.Len(s.T(), listResult.Prompts, 2, "Expected 2 prompts after adding")

	// Find the new prompt in the list
	var newPrompt *mcp.Prompt
	for _, prompt := range listResult.Prompts {
		if prompt.Name == "new_prompt" {
			newPrompt = &prompt
			break
		}
	}
	require.NotNil(s.T(), newPrompt, "New prompt not found in list")
	assert.Equal(s.T(), "New test prompt", newPrompt.Description, "Unexpected prompt description")

	// Verify the client can call the new prompt
	getReq := mcp.GetPromptRequest{}
	getReq.Params.Name = "new_prompt"
	getReq.Params.Arguments = map[string]string{"name": "Alice"}
	getResult, err := mcpClient.GetPrompt(ctx, getReq)
	require.NoError(s.T(), err, "GetPrompt failed for new prompt")

	require.Len(s.T(), getResult.Messages, 1, "Expected exactly 1 message")
	content, ok := getResult.Messages[0].Content.(mcp.TextContent)
	require.True(s.T(), ok, "Expected TextContent")
	assert.Contains(s.T(), content.Text, "Hello Alice! This is a new prompt.", "Unexpected new prompt content")
}

// TestReloadPromptsPromptRemoved tests reloadPrompts method with prompt removal via ServeStdio
func (s *PromptsServerTestSuite) TestReloadPromptsPromptRemoved() {
	ctx := context.Background()

	// Create initial prompt file
	promptFile := filepath.Join(s.tempDir, "test_prompt.tmpl")
	promptContent := `{{/* Test prompt to be removed */}}
Hello {{.name}}!`
	err := os.WriteFile(promptFile, []byte(promptContent), 0644)
	require.NoError(s.T(), err, "Failed to write test prompt file")

	// Create prompts server that will watch the temp directory
	_, mcpClient, promptsClose := s.makePromptsServerAndClient(ctx, s.tempDir, true)
	defer promptsClose()

	// Verify prompt exists initially
	listResult, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt initially")
	assert.Equal(s.T(), "test_prompt", listResult.Prompts[0].Name, "Unexpected prompt name")

	// Verify client can call the prompt
	getReq := mcp.GetPromptRequest{}
	getReq.Params.Name = "test_prompt"
	getReq.Params.Arguments = map[string]string{"name": "Bob"}
	_, err = mcpClient.GetPrompt(ctx, getReq)
	require.NoError(s.T(), err, "GetPrompt should work before removal")

	// Create another prompt file to avoid the empty directory issue
	anotherPromptFile := filepath.Join(s.tempDir, "another_prompt.tmpl")
	anotherPromptContent := `{{/* Another prompt that will remain */}}
Greetings {{.name}}!`
	err = os.WriteFile(anotherPromptFile, []byte(anotherPromptContent), 0644)
	require.NoError(s.T(), err, "Failed to write another prompt file")

	// Remove the original prompt file from filesystem
	err = os.Remove(promptFile)
	require.NoError(s.T(), err, "Failed to remove prompt file")

	// Give the client-server communication time to process the changes
	time.Sleep(100 * time.Millisecond)

	// Client should now see only the remaining prompt
	listResult, err = mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed after removal")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt after removal")
	assert.Equal(s.T(), "another_prompt", listResult.Prompts[0].Name, "Expected only another_prompt to remain")

	// Client should get error when trying to call removed prompt
	_, err = mcpClient.GetPrompt(ctx, getReq)
	assert.Error(s.T(), err, "Expected error when getting removed prompt")

	// But should be able to call the remaining prompt
	getReq.Params.Name = "another_prompt"
	_, err = mcpClient.GetPrompt(ctx, getReq)
	require.NoError(s.T(), err, "Should be able to call remaining prompt")
}

// TestReloadPromptsArgumentAdded tests reloadPrompts method with argument changes via ServeStdio
func (s *PromptsServerTestSuite) TestReloadPromptsArgumentAdded() {
	ctx := context.Background()

	// Create initial prompt with one argument
	promptFile := filepath.Join(s.tempDir, "evolving_prompt.tmpl")
	initialContent := `{{/* Prompt that will gain an argument */}}
Hello {{.name}}!`
	err := os.WriteFile(promptFile, []byte(initialContent), 0644)
	require.NoError(s.T(), err, "Failed to write initial prompt file")

	// Create prompts server that will watch the temp directory
	_, mcpClient, promptsClose := s.makePromptsServerAndClient(ctx, s.tempDir, true)
	defer promptsClose()

	// Verify initial prompt has one argument
	listResult, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt initially")
	require.Len(s.T(), listResult.Prompts[0].Arguments, 1, "Expected 1 argument initially")
	assert.Equal(s.T(), "name", listResult.Prompts[0].Arguments[0].Name, "Expected 'name' argument")

	// Update prompt file to add new argument
	updatedContent := `{{/* Prompt that will gain an argument */}}
Hello {{.name}}! Your age is {{.age}}.`
	err = os.WriteFile(promptFile, []byte(updatedContent), 0644)
	require.NoError(s.T(), err, "Failed to update prompt file")

	// Give the client-server communication time to process the changes
	time.Sleep(100 * time.Millisecond)

	// Client should now see the prompt with two arguments
	listResult, err = mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed after argument addition")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt after update")
	require.Len(s.T(), listResult.Prompts[0].Arguments, 2, "Expected 2 arguments after update")

	// Verify both arguments are present
	argNames := make([]string, len(listResult.Prompts[0].Arguments))
	for i, arg := range listResult.Prompts[0].Arguments {
		argNames[i] = arg.Name
	}
	assert.Contains(s.T(), argNames, "name", "Expected 'name' argument")
	assert.Contains(s.T(), argNames, "age", "Expected 'age' argument")

	// Verify client can call the updated prompt with both arguments
	getReq := mcp.GetPromptRequest{}
	getReq.Params.Name = "evolving_prompt"
	getReq.Params.Arguments = map[string]string{"name": "Alice", "age": "25"}
	getResult, err := mcpClient.GetPrompt(ctx, getReq)
	require.NoError(s.T(), err, "GetPrompt failed for updated prompt")

	require.Len(s.T(), getResult.Messages, 1, "Expected exactly 1 message")
	content, ok := getResult.Messages[0].Content.(mcp.TextContent)
	require.True(s.T(), ok, "Expected TextContent")
	assert.Contains(s.T(), content.Text, "Hello Alice! Your age is 25.", "Unexpected updated prompt content")
}

// TestReloadPromptsArgumentRemoved tests reloadPrompts method with argument removal via ServeStdio
func (s *PromptsServerTestSuite) TestReloadPromptsArgumentRemoved() {
	ctx := context.Background()

	// Create initial prompt with two arguments
	promptFile := filepath.Join(s.tempDir, "shrinking_prompt.tmpl")
	initialContent := `{{/* Prompt that will lose an argument */}}
Hello {{.name}}! Your age is {{.age}}.`
	err := os.WriteFile(promptFile, []byte(initialContent), 0644)
	require.NoError(s.T(), err, "Failed to write initial prompt file")

	// Create prompts server that will watch the temp directory
	_, mcpClient, promptsClose := s.makePromptsServerAndClient(ctx, s.tempDir, true)
	defer promptsClose()

	// Verify initial prompt has two arguments
	listResult, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt initially")
	require.Len(s.T(), listResult.Prompts[0].Arguments, 2, "Expected 2 arguments initially")

	// Update prompt file to remove age argument
	updatedContent := `{{/* Prompt that will lose an argument */}}
Hello {{.name}}!`
	err = os.WriteFile(promptFile, []byte(updatedContent), 0644)
	require.NoError(s.T(), err, "Failed to update prompt file")

	// Give the client-server communication time to process the changes
	time.Sleep(100 * time.Millisecond)

	// Client should now see the prompt with only one argument
	listResult, err = mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed after argument removal")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt after update")
	require.Len(s.T(), listResult.Prompts[0].Arguments, 1, "Expected 1 argument after update")
	assert.Equal(s.T(), "name", listResult.Prompts[0].Arguments[0].Name, "Expected only 'name' argument to remain")

	// Verify client can call the updated prompt with only the remaining argument
	getReq := mcp.GetPromptRequest{}
	getReq.Params.Name = "shrinking_prompt"
	getReq.Params.Arguments = map[string]string{"name": "Bob"}
	getResult, err := mcpClient.GetPrompt(ctx, getReq)
	require.NoError(s.T(), err, "GetPrompt failed for updated prompt")

	require.Len(s.T(), getResult.Messages, 1, "Expected exactly 1 message")
	content, ok := getResult.Messages[0].Content.(mcp.TextContent)
	require.True(s.T(), ok, "Expected TextContent")
	assert.Contains(s.T(), content.Text, "Hello Bob!", "Unexpected updated prompt content")
	assert.NotContains(s.T(), content.Text, "age", "Should not contain age reference after removal")
}

// TestReloadPromptsDescriptionChanged tests reloadPrompts method with description changes via ServeStdio
func (s *PromptsServerTestSuite) TestReloadPromptsDescriptionChanged() {
	ctx := context.Background()

	// Create initial prompt with original description
	promptFile := filepath.Join(s.tempDir, "descriptive_prompt.tmpl")
	initialContent := `{{/* Original description */}}
Hello {{.name}}!`
	err := os.WriteFile(promptFile, []byte(initialContent), 0644)
	require.NoError(s.T(), err, "Failed to write initial prompt file")

	// Create prompts server that will watch the temp directory
	_, mcpClient, promptsClose := s.makePromptsServerAndClient(ctx, s.tempDir, true)
	defer promptsClose()

	// Verify initial description
	listResult, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt initially")
	assert.Equal(s.T(), "Original description", listResult.Prompts[0].Description, "Expected original description")

	// Update prompt file with new description
	updatedContent := `{{/* Updated description with more details */}}
Hello {{.name}}!`
	err = os.WriteFile(promptFile, []byte(updatedContent), 0644)
	require.NoError(s.T(), err, "Failed to update prompt file")

	// Give the client-server communication time to process the changes
	time.Sleep(100 * time.Millisecond)

	// Client should now see the updated description
	listResult, err = mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
	require.NoError(s.T(), err, "ListPrompts failed after description change")
	require.Len(s.T(), listResult.Prompts, 1, "Expected 1 prompt after update")
	assert.Equal(s.T(), "Updated description with more details", listResult.Prompts[0].Description, "Expected updated description")

	// Verify client can still call the prompt and gets updated description
	getReq := mcp.GetPromptRequest{}
	getReq.Params.Name = "descriptive_prompt"
	getReq.Params.Arguments = map[string]string{"name": "Charlie"}
	getResult, err := mcpClient.GetPrompt(ctx, getReq)
	require.NoError(s.T(), err, "GetPrompt failed for updated prompt")

	require.Len(s.T(), getResult.Messages, 1, "Expected exactly 1 message")
	content, ok := getResult.Messages[0].Content.(mcp.TextContent)
	require.True(s.T(), ok, "Expected TextContent")
	assert.Contains(s.T(), content.Text, "Hello Charlie!", "Prompt functionality should remain the same")
	assert.Equal(s.T(), "Updated description with more details", getResult.Description, "GetPrompt should return updated description")
}

func (s *PromptsServerTestSuite) makePromptsServerAndClient(
	ctx context.Context, promptsDir string, enableJSONArgs bool,
) (*PromptsServer, *client.Client, func()) {
	var ctxCancel context.CancelFunc
	ctx, ctxCancel = context.WithCancel(ctx)

	// Create prompts server that will watch the temp directory
	promptsServer, err := NewPromptsServer(promptsDir, enableJSONArgs, s.logger)
	require.NoError(s.T(), err, "Failed to create prompts server")

	// Set up pipes for client-server communication
	serverReader, clientWriter := io.Pipe()
	clientReader, serverWriter := io.Pipe()

	// Start the server in a goroutine
	errChan := make(chan error, 1)
	go func() {
		errChan <- promptsServer.ServeStdio(ctx, serverReader, serverWriter)
	}()

	// Create transport and client
	var logBuffer bytes.Buffer
	transp := transport.NewIO(clientReader, clientWriter, io.NopCloser(&logBuffer))
	err = transp.Start(ctx)
	require.NoError(s.T(), err, "Failed to start transport")

	mcpClient := client.NewClient(transp)

	// Initialize the client
	var initReq mcp.InitializeRequest
	initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	_, err = mcpClient.Initialize(ctx, initReq)
	require.NoError(s.T(), err, "Failed to initialize client")

	return promptsServer, mcpClient, func() {
		ctxCancel()
		s.Require().NoError(<-errChan)
		s.Require().NoError(transp.Close())
		s.Require().NoError(promptsServer.Close())
	}
}

```