MCP Documentation Server - Acceptance Criteria

1. Overview

This document defines the acceptance criteria in Gherkin format (Given-When-Then). These scenarios can be used directly as a basis for automated tests.

2. Feature: Get Document Structure (UC-01)

Feature: Get document structure
  As an LLM Client
  I want to retrieve the hierarchical structure of a documentation project
  To get an overview of available content

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the project contains the following structure:
      | Path                  | Title                |
      | introduction          | 1. Introduction      |
      | introduction.goals    | 1.1 Goals            |
      | introduction.scope    | 1.2 Scope            |
      | constraints           | 2. Constraints       |
      | constraints.technical | 2.1 Technical        |

  Scenario: Retrieve complete structure
    When I call GET /structure
    Then I receive HTTP Status 200
    And the response contains all 5 sections
    And the structure is hierarchically nested

  Scenario: Retrieve structure with depth limit
    When I call GET /structure?max_depth=1
    Then I receive HTTP Status 200
    And the response contains only sections of level 1
    And "introduction" has no children
    And "constraints" has no children

  Scenario: Reject negative max_depth (Issue #220)
    When I call GET /structure?max_depth=-1
    Then I receive HTTP Status 400
    And the error message contains "must be non-negative"

  Scenario: Structure for empty project
    Given the project contains no documents
    When I call GET /structure
    Then I receive HTTP Status 200
    And the response contains an empty structure
    And total_sections is 0

3. Feature: Read Section (UC-02)

Feature: Read section
  As an LLM Client
  I want to read the content of a specific section
  To access documentation content in a targeted manner

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the section "introduction.goals" exists with content:
      """
      == Goals

      The main goals are:
      * Goal 1
      * Goal 2
      """

  Scenario: Read existing section
    When I call GET /section/introduction.goals
    Then I receive HTTP Status 200
    And the response contains the complete section content
    And the field "path" is "introduction.goals"
    And the field "format" is "asciidoc"

  Scenario: Request non-existing section
    When I call GET /section/nonexistent.path
    Then I receive HTTP Status 404
    And the error code is "PATH_NOT_FOUND"
    And the error message contains "nonexistent.path"

  Scenario: Read section with URL-encoded path
    Given the section "chapter-1.section-2" exists
    When I call GET /section/chapter-1.section-2
    Then I receive HTTP Status 200

  Scenario: Location information is correct
    When I call GET /section/introduction.goals
    Then the response contains a "location" object
    And "location.file" is a relative file path
    And "location.start_line" is greater than 0
    And "location.end_line" is greater than or equal to "location.start_line"

4. Feature: Update Section (UC-03)

Feature: Update section
  As an LLM Client
  I want to update the content of a section
  To maintain and improve documentation

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the section "introduction.goals" exists
    And the file system is writable

  Scenario: Successfully update section
    When I call PUT /section/introduction.goals with:
      """
      {
        "content": "== Goals\n\nUpdated goals content."
      }
      """
    Then I receive HTTP Status 200
    And the field "success" is true
    And the new content is saved in the file
    And the index is updated

  Scenario: Preserve title on update
    Given the section has the title "1.1 Goals"
    When I call PUT /section/introduction.goals with:
      """
      {
        "content": "New content without title",
        "preserve_title": true
      }
      """
    Then I receive HTTP Status 200
    And the title "1.1 Goals" is preserved

  Scenario: Change section title with preserve_title false
    Given the section has the title "1.1 Goals"
    When I call PUT /section/introduction.goals with:
      """
      {
        "content": "=== New Title\n\nNew content with new title.",
        "preserve_title": false
      }
      """
    Then I receive HTTP Status 200
    And the title "New Title" is in the file
    And the old title "1.1 Goals" is not in the file

  Scenario: Reject update with preserve_title false when content has no title (Issue #195)
    Given the section has the title "1.1 Goals"
    When I call PUT /section/introduction.goals with:
      """
      {
        "content": "Content without a section title",
        "preserve_title": false
      }
      """
    Then I receive HTTP Status 400
    And the error message contains "must include a section title"
    And the original file is unchanged

  Scenario: Update non-existing section
    When I call PUT /section/nonexistent with:
      """
      {
        "content": "Some content"
      }
      """
    Then I receive HTTP Status 404
    And the error code is "PATH_NOT_FOUND"

  Scenario: Atomic write operation on error
    Given writing to the temporary file fails
    When I call PUT /section/introduction.goals with:
      """
      {
        "content": "New content"
      }
      """
    Then I receive HTTP Status 500
    And the error code is "WRITE_FAILED"
    And the original file is unchanged
    And no .tmp or .bak files remain

  Scenario: Hash values for change tracking
    When I call PUT /section/introduction.goals with valid content
    Then the response contains "previous_hash"
    And the response contains "new_hash"
    And "previous_hash" differs from "new_hash"

5. Feature: Search Content (UC-04)

Feature: Search content
  As an LLM Client
  I want to search for content in the documentation project
  To quickly find relevant information

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the project contains sections with various content

  Scenario: Successful search with matches
    Given a section contains the text "atomic write operation"
    When I call POST /search with:
      """
      {
        "query": "atomic write"
      }
      """
    Then I receive HTTP Status 200
    And the result list contains at least 1 match
    And each match has a "path"
    And each match has a "context" with the search term
    And each match has a "score" between 0 and 1

  Scenario: Search without matches
    When I call POST /search with:
      """
      {
        "query": "xyznonexistentterm"
      }
      """
    Then I receive HTTP Status 200
    And the result list is empty
    And "total_results" is 0

  Scenario: Search with scope restriction
    When I call POST /search with:
      """
      {
        "query": "performance",
        "scope": "quality"
      }
      """
    Then I receive HTTP Status 200
    And all matches have a path starting with "quality"

  Scenario: Case-sensitive search
    Given a section contains "Performance" but not "performance"
    When I call POST /search with:
      """
      {
        "query": "performance",
        "case_sensitive": true
      }
      """
    Then the result list does not contain the section with "Performance"

  Scenario: Limit result count
    Given there are more than 10 matches for "section"
    When I call POST /search with:
      """
      {
        "query": "section",
        "max_results": 5
      }
      """
    Then the result list contains at most 5 matches

  Scenario: CLI warns for non-existent scope (Issue #226)
    When I call dacli search "query" --scope nonexistent.path
    Then the CLI displays a warning about the scope not found
    And the search continues (may return empty results)

6. Feature: Filter Elements by Type (UC-05)

Feature: Filter elements by type
  As an LLM Client
  I want to retrieve elements of a specific type
  To access diagrams, tables, or code in a targeted manner

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the project contains:
      | Type    | Count |
      | diagram | 5     |
      | table   | 8     |
      | code    | 12    |

  Scenario: Retrieve all PlantUML diagrams
    When I call GET /elements?type=plantuml
    Then I receive HTTP Status 200
    And the field "type" is "plantuml"
    And "count" is 5
    And each element has a "path"
    And each element has a "location"

  Scenario: Tables of a specific section
    Given the section "quality.performance" contains 2 tables
    When I call GET /elements?type=table&path=quality.performance
    Then I receive HTTP Status 200
    And "count" is 2
    And all elements have path "quality.performance"

  Scenario: Invalid element type
    When I call GET /elements?type=charts
    Then I receive HTTP Status 400
    And the error code is "INVALID_TYPE"
    And the response contains valid types

  Scenario: Element type without matches
    Given the project contains no images
    When I call GET /elements?type=image
    Then I receive HTTP Status 200
    And "count" is 0
    And "elements" is an empty array

  Scenario: CLI warns for invalid element type (Issue #225)
    When I call dacli elements --type charts
    Then the CLI displays a warning about invalid type
    And valid types are listed in the warning
    And the result is empty (not an error)

7. Feature: Get Metadata (UC-06)

Feature: Get metadata
  As an LLM Client
  I want to retrieve metadata about the project or sections
  To get information about scope and currency

  Background:
    Given the server is started
    And the project "test-docs" is indexed

  Scenario: Retrieve project metadata
    When I call GET /metadata without path parameter
    Then I receive HTTP Status 200
    And the response contains "total_files"
    And the response contains "total_sections"
    And the response contains "total_words"
    And the response contains "last_modified" in ISO 8601 format

  Scenario: Retrieve section metadata
    When I call GET /metadata?path=introduction
    Then I receive HTTP Status 200
    And the field "path" is "introduction"
    And the response contains "word_count"
    And the response contains "subsection_count"

  Scenario: Metadata for non-existing path
    When I call GET /metadata?path=nonexistent
    Then I receive HTTP Status 404

8. Feature: Validate Structure (UC-07)

Feature: Validate structure
  As an LLM Client
  I want to check the document structure for consistency
  To detect problems early

  Background:
    Given the server is started
    And the project "test-docs" is indexed

  Scenario: Check valid structure
    Given the project has no structural problems
    When I call POST /validate
    Then I receive HTTP Status 200
    And "valid" is true
    And "errors" is an empty array

  Scenario: Detect unresolved includes
    Given the file "chapter.adoc" contains "include::missing.adoc[]"
    And the file "missing.adoc" does not exist
    When I call POST /validate
    Then I receive HTTP Status 200
    And "valid" is false
    And "errors" contains an entry with type "unresolved_include"

  Scenario: Detect circular includes
    Given File A includes File B
    And File B includes File A
    When I call POST /validate
    Then I receive HTTP Status 200
    And "valid" is false
    And "errors" contains an entry with type "circular_include"

  Scenario: Orphaned files as warning
    Given the file "orphan.adoc" is not included by any document
    When I call POST /validate
    Then I receive HTTP Status 200
    And "warnings" contains an entry with type "orphaned_file"

9. Feature: Server Initialization (UC-08)

Feature: Server initialization
  As a system
  I must index the documentation project at startup
  To enable fast queries

  Scenario: Successful initialization
    Given the project directory contains AsciiDoc files
    When the server is started
    Then the index is built after startup
    And the health endpoint reports "index_ready": true

  Scenario: Initialization with empty directory
    Given the project directory is empty
    When the server is started
    Then the server starts with an empty index
    And a warning is logged

  Scenario: Fault-tolerant initialization
    Given a file in the project is not parseable
    When the server is started
    Then the faulty file is skipped
    And an error is logged
    And the other files are indexed

  Scenario: Include resolution during initialization
    Given the main file includes three sub-files
    When the server is started
    Then all included sections are in the index
    And the include relationships are captured

10. Feature: Insert Content (UC-09)

Feature: Insert content
  As an LLM Client
  I want to insert new content at a specific position
  To extend documentation

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the section "introduction" exists

  Scenario: Insert content before a section
    When I call POST /section/introduction/insert with:
      """
      {
        "position": "before",
        "content": "== Preface\n\nThis is a preface."
      }
      """
    Then I receive HTTP Status 200
    And the new content is before the section "introduction"

  Scenario: Insert content after a section
    When I call POST /section/introduction/insert with:
      """
      {
        "position": "after",
        "content": "== Summary\n\nThis is a summary."
      }
      """
    Then I receive HTTP Status 200
    And the new content is after the section "introduction"

  Scenario: Insert after includes all child sections (Issue #229)
    Given the section "parent" has child sections "parent.child1" and "parent.child2"
    When I call POST /section/parent/insert with:
      """
      {
        "position": "after",
        "content": "== New Section\n\nContent."
      }
      """
    Then I receive HTTP Status 200
    And the new content is after "parent.child2"
    And the new content is before the next sibling of "parent"

  Scenario: Append content to section
    When I call POST /section/introduction/insert with:
      """
      {
        "position": "append",
        "content": "\n\nAdditional paragraph."
      }
      """
    Then I receive HTTP Status 200
    And the new content is at the end of the section "introduction"

  Scenario: Invalid position
    When I call POST /section/introduction/insert with:
      """
      {
        "position": "invalid",
        "content": "Some content"
      }
      """
    Then I receive HTTP Status 400
    And the error code is "INVALID_POSITION"

11. Feature: Replace Element (UC-10)

Feature: Replace element
  As an LLM Client
  I want to replace a specific element within a section
  To update tables, code blocks, or diagrams in a targeted manner

  Background:
    Given the server is started
    And the project "test-docs" is indexed
    And the section "quality.performance" contains a table at index 0

  Scenario: Successfully replace table
    When I call PUT /element/quality.performance/0 with:
      """
      {
        "content": "[cols=\"1,2\"]\n|===\n| New | Table\n|==="
      }
      """
    Then I receive HTTP Status 200
    And "success" is true
    And the table in the file is updated

  Scenario: Element at invalid index
    When I call PUT /element/quality.performance/99 with:
      """
      {
        "content": "New content"
      }
      """
    Then I receive HTTP Status 404
    And the error code is "ELEMENT_NOT_FOUND"

  Scenario: Element in non-existing section
    When I call PUT /element/nonexistent/0 with:
      """
      {
        "content": "New content"
      }
      """
    Then I receive HTTP Status 404
    And the error code is "PATH_NOT_FOUND"

12. Feature: Non-Functional Requirements

Feature: Performance requirements
  As a system
  I must meet certain performance criteria

  Scenario: Fast response times for read operations
    Given a project with 600 pages is indexed
    When I call GET /section/some.path
    Then the response time is under 2 seconds

  Scenario: Acceptable indexing time
    Given a project with 600 pages
    When the server is started
    Then indexing is completed in under 60 seconds

  Scenario: Low idle resource consumption
    Given the server is started and idle
    When no requests come in for 60 seconds
    Then CPU utilization is under 5%
    And memory consumption is stable
Feature: Reliability requirements
  As a system
  I must ensure data integrity

  Scenario: Atomic write operations
    Given a write operation is in progress
    When an error occurs during writing
    Then the original file is unchanged
    And no temporary files remain

  Scenario: Graceful error handling
    Given a file is not readable
    When I call GET /section/path.to.file
    Then I receive HTTP Status 500
    And the error message is informative
    And the server remains stable

13. Traceability Matrix

Use Case API Endpoints Acceptance Scenarios

UC-01

GET /structure, GET /sections

3 scenarios

UC-02

GET /section/{path}

4 scenarios

UC-03

PUT /section/{path}

5 scenarios

UC-04

POST /search

5 scenarios

UC-05

GET /elements

4 scenarios

UC-06

GET /metadata, GET /dependencies

3 scenarios

UC-07

POST /validate

4 scenarios

UC-08

(System Startup)

4 scenarios

UC-09

POST /section/{path}/insert

4 scenarios

UC-10

PUT /element/{path}/{index}

3 scenarios

14. Appendix: Test Data Specification

14.1. Minimal Test Project

test-docs/
├── arc42.adoc           # Main document with includes
├── chapters/
│   ├── 01_introduction.adoc
│   ├── 02_constraints.adoc
│   └── 03_context.adoc
└── images/
    └── diagram.png

14.2. Structure of Test Documents

// arc42.adoc
= Test Documentation

include::chapters/01_introduction.adoc[]
include::chapters/02_constraints.adoc[]
include::chapters/03_context.adoc[]
// chapters/01_introduction.adoc
== Introduction

=== Goals

The goals are...

=== Scope

The scope includes...