# Story 3.1: Implement Basic `fabric_run_pattern` Tool (Non-Streaming)

## Status: Done

## Story

- As an MCP Client Developer
- I want to use the `fabric_run_pattern` tool to execute a named Fabric pattern with optional `input_text`, and receive its complete output in a non-streaming manner
- so that I can integrate Fabric's fundamental pattern execution capability into my MCP client applications.

## Background

Currently, the `fabric_run_pattern` tool exists as a placeholder stub that returns mock responses like `f"Pattern {pattern_name} executed with input: {input_text}"`. This story implements the real functionality by integrating with the Fabric API's `/chat` endpoint to execute actual patterns and return real LLM output.

## Acceptance Criteria (ACs)

### AC1: Real Fabric API Integration

- [x] Replace placeholder implementation in `src/fabric_mcp/core.py` with actual Fabric API calls
- [x] Use `FabricApiClient.post()` method to call Fabric's `/chat` endpoint
- [x] Pass `pattern_name` and `input_text` in the request payload
- [x] Handle non-streaming requests only (streaming will be implemented in Story 3.4)

### AC2: Tool Registration and Signature

- [x] Tool is properly registered and advertised via `list_tools()`
- [x] Tool signature includes: `pattern_name: string (required)`, `input_text: string (optional)`, `stream: boolean (optional, default: false)`
- [x] For this story, `stream` parameter should be ignored/forced to false
- [x] Tool docstring accurately describes the non-streaming behavior

### AC3: Request Payload Structure

- [x] Construct proper JSON payload for Fabric API `/chat` endpoint
- [x] Use correct Fabric format with `prompts` array containing `PromptRequest` objects
- [x] Include required fields: `userInput`, `patternName`, `model`, `vendor` in prompts array
- [x] Handle optional parameters: `contextName`, `strategyName`, `temperature`, `topP`, etc.
- [x] Ensure request format matches real Fabric API expectations (not simplified mock format)

### AC4: Response Processing

- [x] Handle Server-Sent Events (SSE) stream from Fabric API `/chat` endpoint
- [x] Collect all streaming chunks until completion (`type: "complete"`)
- [x] Parse each SSE data chunk as JSON with `type`, `format`, `content` fields
- [x] Concatenate all `content` chunks with `type: "content"` to build complete output
- [x] Handle error chunks with `type: "error"` appropriately
- [x] Return structured MCP success response with complete concatenated output

### AC5: Error Handling

- [x] Return MCP client-side error if `pattern_name` is missing or empty
- [x] Return structured MCP error for Fabric API HTTP errors (4xx, 5xx)
- [x] Return structured MCP error for connection failures
- [x] Return structured MCP error for invalid/malformed JSON responses
- [x] Log appropriate error details for debugging

### AC6: Return Format

- [x] Return `dict[str, Any]` with structured content
- [x] Include `output_format` field (e.g., "markdown", "text")
- [x] Include `output_text` field with the actual LLM-generated content
- [x] Ensure return format is consistent with MCP tool response expectations

### AC7: Unit Tests

- [x] Mock `FabricApiClient` for testing correct request construction
- [x] Test successful response parsing with various output formats
- [x] Test error scenarios: missing pattern_name, API errors, connection failures
- [x] Test request payload structure includes correct fields
- [x] Test that `stream=true` is ignored (non-streaming behavior)
- [x] Achieve 100% test coverage for the new implementation

### AC8: Mock Server API Compliance

- [x] Update `tests/shared/fabric_api/server.py` to match real Fabric API format
- [x] Replace `/patterns/{name}/run` endpoint with `/chat` endpoint
- [x] Implement correct request format: `{"prompts": [{"userInput": "...", "patternName": "...", "model": "...", "vendor": "..."}], ...}`
- [x] Implement Server-Sent Events (SSE) response format with JSON chunks
- [x] Return SSE chunks with `type: "content"`, `format: "markdown"/"text"`, and `content: "..."`
- [x] End SSE stream with `type: "complete"` chunk
- [x] Handle error cases with `type: "error"` chunks
- [x] Ensure mock server exactly matches the real Fabric API request/response format for testing

### AC9: Integration Tests

- [x] Test against mock Fabric API server (via `MockFabricAPIServer`)
- [x] Execute simple pattern with pattern name and input text via real API calls
- [x] Verify complete non-streaming output is returned through full MCP flow
- [x] Test non-existent pattern returns appropriate MCP error from API
- [x] Test empty input_text handling through complete integration
- [x] Test various pattern types available in mock server data
- [x] Ensure integration tests run in CI/CD without external dependencies
- [x] Ensure mock server exactly matches the real Fabric API request/response format for testing

## Implementation Notes

### Current Placeholder Code Location

```python
# In src/fabric_mcp/core.py, lines ~173-212
def fabric_run_pattern(
    pattern_name: str,
    input_text: str = "",
    stream: bool = False,
    config: PatternExecutionConfig | None = None,
) -> dict[Any, Any]:
    # Replace this entire implementation
```

### Expected Fabric API Request Format

Based on the real Fabric REST API (`fabric_restapi.json`):

```json
{
  "prompts": [
    {
      "userInput": "This is the user input text",
      "patternName": "example_pattern",
      "model": "gpt-4",
      "vendor": "openai",
      "contextName": "",
      "strategyName": ""
    }
  ],
  "language": "en",
  "temperature": 0.7,
  "topP": 0.9,
  "frequencyPenalty": 0.0,
  "presencePenalty": 0.0
}
```

**Key Notes:**

- The `prompts` array can contain multiple `PromptRequest` objects
- `model` and `vendor` are required in each prompt
- Optional strategy and context parameters can be empty strings
- Temperature and other sampling parameters are at the root level

### Expected Fabric API SSE Response Format

```text
data: {"type": "content", "format": "markdown", "content": "# Generated Output\n\n"}

data: {"type": "content", "format": "markdown", "content": "This is the LLM-generated response"}

data: {"type": "content", "format": "markdown", "content": " from the Fabric pattern..."}

data: {"type": "complete", "format": "plain", "content": ""}
```

### Expected MCP Response Format

```json
{
  "output_format": "markdown",
  "output_text": "# Generated Output\n\nThis is the LLM-generated response from the Fabric pattern..."
}
```

### Fabric API Client Usage Pattern

```python
# Use existing FabricApiClient methods for SSE streaming
client = FabricApiClient()
try:
    # Note: Fabric /chat endpoint returns SSE stream, not regular JSON
    response = client.post("/chat", json_data={
        "prompts": [
            {
                "userInput": input_text,
                "patternName": pattern_name,
                "model": "gpt-4",  # or from tool parameters
                "vendor": "openai",  # or from tool parameters
                "contextName": "",
                "strategyName": ""
            }
        ],
        "language": "en",
        "temperature": 0.7,
        "topP": 0.9,
        "frequencyPenalty": 0.0,
        "presencePenalty": 0.0
    })

    # Process SSE stream response:
    # - Parse each "data: {...}" line as JSON
    # - Collect content chunks where type="content"
    # - Accumulate "content" field from each chunk
    # - Stop when type="complete"
    # - Return concatenated content with format metadata

    output_chunks = []
    output_format = "text"  # default

    for line in response.iter_lines():
        if line.startswith("data: "):
            data = json.loads(line[6:])  # Remove "data: " prefix
            if data["type"] == "content":
                output_chunks.append(data["content"])
                output_format = data.get("format", "text")
            elif data["type"] == "complete":
                break
            elif data["type"] == "error":
                raise Exception(f"Fabric API error: {data['content']}")

    return {
        "output_format": output_format,
        "output_text": "".join(output_chunks)
    }
finally:
    client.close()
```

### Mock Server Updates Required

The current mock server in `tests/shared/fabric_api/server.py` needs significant updates to match the real Fabric API:

**Current Issues:**

- Uses `/patterns/{name}/run` endpoint instead of `/chat`
- Expects simple `{"input": "..."}` request format
- Returns single JSON response instead of SSE stream
- Response format doesn't match real API structure

**Required Changes:**

```python
# Update endpoint from:
@app.post("/patterns/{pattern_name}/run")
# To:
@app.post("/chat")

# Update to accept real Fabric API request format:
{
  "prompts": [{"userInput": "...", "patternName": "...", "model": "...", "vendor": "..."}],
  "language": "en",
  "temperature": 0.7,
  # ... other parameters
}

# Update to return SSE stream instead of JSON:
from fastapi.responses import StreamingResponse

@app.post("/chat")
async def chat_endpoint(request_data: dict[str, Any]):
    def generate_sse():
        yield f"data: {json.dumps({'type': 'content', 'format': 'markdown', 'content': '# Mock Response'})}\n\n"
        yield f"data: {json.dumps({'type': 'content', 'format': 'markdown', 'content': 'Mock output...'})}\n\n"
        yield f"data: {json.dumps({'type': 'complete', 'format': 'plain', 'content': ''})}\n\n"

    return StreamingResponse(generate_sse(), media_type="text/plain")
```

## Definition of Done

- [x] All Acceptance Criteria are met
- [x] Code review completed and approved
- [x] Unit tests pass with 100% coverage for new code
- [x] Integration tests pass against live Fabric instance
- [x] No regressions in existing Epic 1 & 2 functionality
- [x] Code follows project style guidelines (pylint 10/10)
- [x] Type annotations are complete (mypy/pyright clean)
- [x] Documentation updated if needed

## Dependencies

- **Prerequisite**: Epic 1 & 2 must be complete (✅ Already satisfied)
- **Fabric API**: Requires running Fabric instance (`fabric --serve`)
- **FabricApiClient**: Uses existing API client (no changes needed)

## Testing Strategy

### Unit Tests

- Mock all `FabricApiClient` calls
- Test request construction
- Test response parsing
- Test error scenarios
- Test parameter validation

### Integration Tests

- Test against real Fabric API
- Use simple, reliable patterns for testing
- Test error conditions with invalid patterns
- Verify end-to-end MCP tool functionality

### Manual Testing

- Use MCP client (e.g., Claude Desktop) to test tool
- Execute various patterns with different inputs
- Verify output quality and format
- Test error handling with invalid inputs

## Epic Context

This story is **Story 3.1** of Epic 3: "Core Fabric Pattern Execution with Strategy & Parameter Control". It provides the foundation for:

- **Story 3.2**: `fabric_list_strategies` (can be developed in parallel)
- **Story 3.3**: Enhanced parameter control (model, temperature, strategy)
- **Story 3.4**: Streaming implementation (builds on this foundation)
- **Story 3.5**: Variables and attachments support

## Risk Assessment

**Low Risk**: This story builds on the proven foundation of Epic 1 & 2 and uses existing `FabricApiClient` infrastructure.

**Potential Challenges**:

- Fabric API response format variations
- Error handling for various Fabric failure modes
- Integration test reliability with external Fabric instance

**Mitigation**:

- Comprehensive error handling and logging
- Mock-based unit tests for reliability
- Clear integration test setup documentation
