# Migration Guide: v1.4 Project Filtering Enforcement

**Version**: 1.4.0
**Date**: 2025-11-29
**Commit**: 46f9e0e
**Breaking Change**: Mandatory project filtering for search and list operations

## Table of Contents

- [Overview](#overview)
- [Why This Change?](#why-this-change)
- [What Changed](#what-changed)
- [Migration Paths](#migration-paths)
- [Before/After Examples](#beforeafter-examples)
- [Tool-Specific Changes](#tool-specific-changes)
- [Finding Your Project ID](#finding-your-project-id)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)

## Overview

Version 1.4 introduces **mandatory project filtering** for all search and list operations. This is a critical security and data integrity enhancement that prevents cross-project data leakage in multi-project and multi-tenant environments.

**Impact**: Five MCP tools now require project context (either via parameter or configuration).

**Migration Time**: ~5 minutes (one-time configuration setup)

## Why This Change?

### Security Concerns
Previous versions allowed searches to span all projects, causing:
- **Cross-project data leakage**: Sensitive tickets from unrelated projects could appear in search results
- **Multi-tenant risks**: In shared environments, users could inadvertently access other teams' tickets
- **Privacy violations**: Confidential project information could leak through broad searches

### Data Integrity Issues
Without project scoping:
- **Confusion**: Tickets from unrelated projects appearing in results
- **Context loss**: Difficult to understand which project a ticket belongs to
- **Workflow disruption**: Users spending time on wrong project's tickets

### Consistency
This change aligns all tools with project-scoped operations, creating a consistent and predictable API.

## What Changed

### Affected MCP Tools (5 Total)

All of these tools now require project context:

| Tool | Previous Behavior | New Behavior |
|------|------------------|--------------|
| `ticket_search()` | Searched all projects | Requires `project_id` parameter or `default_project` config |
| `ticket_search_hierarchy()` | Searched all projects | Requires `project_id` parameter or `default_project` config |
| `ticket_list()` | Listed from all projects | Requires `project_id` parameter or `default_project` config |
| `epic_list()` | Listed all epics | Requires `project_id` parameter or `default_project` config |
| `get_my_tickets()` | Found tickets across all projects | Requires `project_id` parameter or `default_project` config |

### New Behavior
- **With project context**: Tools work as before, but scoped to specified project
- **Without project context**: Tools return clear error messages with guidance

### Error Messages
When called without project context:

```python
{
    "status": "error",
    "error": "Project filtering is now required. Set default_project via config_set_default_project() or pass project_id parameter.",
    "help": {
        "quick_fix": "config_set_default_project(project_id='YOUR-PROJECT-ID')",
        "find_projects": "Use epic_list() or adapter-specific tools to discover project IDs"
    }
}
```

## Migration Paths

### Path 1: Set Default Project (RECOMMENDED)

**Best for**: Single-project workflows, team environments, consistent project context

**Advantages**:
- ✅ One-time setup
- ✅ No code changes needed for existing tool calls
- ✅ Consistent project context across all operations
- ✅ Easy to change when switching projects

**Setup**:

```python
# Step 1: Find your project ID (see "Finding Your Project ID" section)
# For Linear: UUID from project URL
# For GitHub: Repository name
# For JIRA: Project key

# Step 2: Set default project
config_set_default_project(project_id='YOUR-PROJECT-ID')

# Step 3: All subsequent calls use this project automatically
ticket_search(query="bug")  # Scoped to YOUR-PROJECT-ID
ticket_list(state="open")   # Scoped to YOUR-PROJECT-ID
get_my_tickets()            # Scoped to YOUR-PROJECT-ID
```

**Changing Projects**:

```python
# Switch to different project
config_set_default_project(project_id='OTHER-PROJECT-ID')

# Or temporarily override for specific call
ticket_search(query="bug", project_id='TEMP-PROJECT-ID')
```

### Path 2: Explicit project_id Parameters

**Best for**: Multi-project workflows, scripts accessing multiple projects, dynamic project selection

**Advantages**:
- ✅ Explicit project control per call
- ✅ No global state changes
- ✅ Clear which project each operation targets
- ✅ Ideal for automation scripts

**Usage**:

```python
# Pass project_id to every call
ticket_search(query="bug", project_id='PROJECT-A')
ticket_list(state="open", project_id='PROJECT-B')
get_my_tickets(project_id='PROJECT-C')

# Can mix with default_project (explicit overrides default)
config_set_default_project(project_id='DEFAULT-PROJECT')
ticket_search(query="bug")  # Uses DEFAULT-PROJECT
ticket_search(query="feature", project_id='OTHER-PROJECT')  # Overrides default
```

### Hybrid Approach

**Best for**: Complex workflows with primary project and occasional cross-project operations

```python
# Set default for primary project
config_set_default_project(project_id='PRIMARY-PROJECT')

# Most operations use default
ticket_list(state="open")         # PRIMARY-PROJECT
get_my_tickets()                  # PRIMARY-PROJECT

# Override when needed
ticket_search(query="integration", project_id='INTEGRATION-PROJECT')
```

## Before/After Examples

### ticket_search()

**Before v1.4** (searched all projects):
```python
# Searched across ALL projects
result = ticket_search(query="authentication bug")
# Could return tickets from multiple unrelated projects
```

**After v1.4** (requires project context):
```python
# Option 1: With default_project configured
config_set_default_project(project_id='web-app-project')
result = ticket_search(query="authentication bug")
# Searches only 'web-app-project'

# Option 2: With explicit project_id
result = ticket_search(
    query="authentication bug",
    project_id='web-app-project'
)
# Searches only 'web-app-project'
```

### ticket_list()

**Before v1.4** (listed from all projects):
```python
# Listed tickets from ALL projects
tickets = ticket_list(state="open", limit=20)
# Returned mix of tickets from different projects
```

**After v1.4** (requires project context):
```python
# Option 1: With default_project configured
config_set_default_project(project_id='backend-services')
tickets = ticket_list(state="open", limit=20)
# Lists only from 'backend-services'

# Option 2: With explicit project_id
tickets = ticket_list(
    state="open",
    limit=20,
    project_id='backend-services'
)
# Lists only from 'backend-services'
```

### get_my_tickets()

**Before v1.4** (searched all projects):
```python
# Found user's tickets across ALL projects
my_tickets = get_my_tickets(state="in_progress")
# Returned user's tickets from all projects they have access to
```

**After v1.4** (requires project context):
```python
# Option 1: With default_project configured
config_set_default_project(project_id='mobile-app')
my_tickets = get_my_tickets(state="in_progress")
# Returns user's tickets from 'mobile-app' only

# Option 2: With explicit project_id
my_tickets = get_my_tickets(
    state="in_progress",
    project_id='mobile-app'
)
# Returns user's tickets from 'mobile-app' only
```

### epic_list()

**Before v1.4** (listed all epics):
```python
# Listed epics from ALL projects
epics = epic_list(limit=10)
# Returned epics from all accessible projects
```

**After v1.4** (requires project context):
```python
# Option 1: With default_project configured
config_set_default_project(project_id='platform-project')
epics = epic_list(limit=10)
# Lists epics from 'platform-project' only

# Option 2: With explicit project_id
epics = epic_list(
    limit=10,
    project_id='platform-project'
)
# Lists epics from 'platform-project' only
```

### ticket_search_hierarchy()

**Before v1.4** (searched all projects):
```python
# Searched across ALL projects with hierarchy
results = ticket_search_hierarchy(
    query="API redesign",
    include_children=True
)
# Could return hierarchies spanning multiple projects
```

**After v1.4** (requires project context):
```python
# Option 1: With default_project configured
config_set_default_project(project_id='api-project')
results = ticket_search_hierarchy(
    query="API redesign",
    include_children=True
)
# Searches and builds hierarchy within 'api-project'

# Option 2: With explicit project_id
results = ticket_search_hierarchy(
    query="API redesign",
    include_children=True,
    project_id='api-project'
)
# Searches and builds hierarchy within 'api-project'
```

## Tool-Specific Changes

### ticket_search()

**New Signature**:
```python
ticket_search(
    query: str,
    project_id: Optional[str] = None,  # NEW: Required (or use default_project)
    state: Optional[str] = None,
    priority: Optional[str] = None,
    tags: Optional[List[str]] = None,
    assignee: Optional[str] = None,
    limit: int = 10
)
```

**Parameter Priority**:
1. Explicit `project_id` parameter (highest priority)
2. Configuration `default_project` setting
3. Error if neither is provided

**Migration Checklist**:
- [ ] Find your project ID
- [ ] Set `default_project` OR add `project_id` to all calls
- [ ] Test search operations
- [ ] Verify results are scoped correctly

### ticket_list()

**New Signature**:
```python
ticket_list(
    project_id: Optional[str] = None,  # NEW: Required (or use default_project)
    limit: int = 20,
    offset: int = 0,
    state: Optional[str] = None,
    priority: Optional[str] = None,
    assignee: Optional[str] = None,
    compact: bool = True
)
```

**Migration Checklist**:
- [ ] Find your project ID
- [ ] Set `default_project` OR add `project_id` to all calls
- [ ] Test list operations
- [ ] Verify pagination still works

### get_my_tickets()

**New Signature**:
```python
get_my_tickets(
    project_id: Optional[str] = None,  # NEW: Required (or use default_project)
    state: Optional[str] = None,
    limit: int = 10
)
```

**Additional Requirement**: Must have `default_user` configured

**Migration Checklist**:
- [ ] Verify `default_user` is configured (`config_get()`)
- [ ] Find your project ID
- [ ] Set `default_project` OR add `project_id` to all calls
- [ ] Test user ticket filtering

### epic_list()

**New Signature**:
```python
epic_list(
    project_id: Optional[str] = None,  # NEW: Required (or use default_project)
    limit: int = 10,
    offset: int = 0,
    state: Optional[str] = None,
    include_completed: bool = False
)
```

**Note**: For Linear, `epic_list()` returns epics/projects. The `project_id` filter may behave differently depending on adapter.

**Migration Checklist**:
- [ ] Find your project ID
- [ ] Set `default_project` OR add `project_id` to all calls
- [ ] Test epic listing
- [ ] Verify completed epic filtering still works

### ticket_search_hierarchy()

**New Signature**:
```python
ticket_search_hierarchy(
    query: str,
    project_id: Optional[str] = None,  # NEW: Required (or use default_project)
    include_children: bool = True,
    max_depth: int = 3
)
```

**Migration Checklist**:
- [ ] Find your project ID
- [ ] Set `default_project` OR add `project_id` to all calls
- [ ] Test hierarchy building
- [ ] Verify cross-references within project work correctly

## Finding Your Project ID

### Linear

**Method 1: From Project URL** (Easiest)

```
https://linear.app/your-org/project/mcp-ticketer-eac28953c267
                                                    ^^^^^^^^^^^^^^^^^
                                                    This is your project_id
```

The project_id is the UUID at the end of the URL (e.g., `eac28953c267`)

**Method 2: Using epic_list()** (Before setting default_project)

```python
# Temporarily list all epics to find IDs
# This will fail after migration, so do this BEFORE v1.4
epics = epic_list(limit=50)
for epic in epics['epics']:
    print(f"Project: {epic['title']}")
    print(f"ID: {epic['id']}")
    print(f"URL: {epic.get('url', 'N/A')}")
    print("---")
```

**Method 3: Check Configuration**

```python
# If you previously set default_project
config = config_get()
print(config['config'].get('default_project'))
```

### GitHub

**Project ID**: Use repository name in format `owner/repo`

```python
# Example: For https://github.com/mcp-ticketer/mcp-ticketer
project_id = 'mcp-ticketer/mcp-ticketer'
```

### JIRA

**Project ID**: Use project key (uppercase, usually 2-4 letters)

```python
# Example: For project "Mobile App" with key "MAPP"
project_id = 'MAPP'
```

**Finding JIRA Project Key**:
1. Go to your JIRA board
2. Look at issue IDs (e.g., "MAPP-123")
3. The prefix before the dash is your project key

### AITrackdown (File-based)

**Project ID**: Directory name or path

```python
# Example: For project in ./projects/backend
project_id = 'backend'
```

## Troubleshooting

### Error: "Project filtering is now required"

**Problem**: Tool called without project context

**Solution**:
```python
# Quick fix: Set default project
config_set_default_project(project_id='YOUR-PROJECT-ID')

# Or add project_id to call
ticket_search(query="...", project_id='YOUR-PROJECT-ID')
```

### Error: "Project not found" or "Invalid project_id"

**Problem**: Incorrect project_id format or non-existent project

**Solutions**:

1. **Verify project_id format**:
   - Linear: UUID (e.g., `eac28953c267`)
   - GitHub: `owner/repo` format
   - JIRA: Project key (e.g., `PROJ`)

2. **Check project access**:
   ```python
   # List accessible projects (adapter-specific)
   # For Linear:
   epics = epic_list(limit=100)  # This will fail after migration!

   # Better: Check adapter directly
   config = config_get()
   print(config)  # Review configured adapter and team
   ```

3. **Verify adapter configuration**:
   ```bash
   # Validate configuration
   mcp-ticketer doctor
   ```

### Error: "default_user not configured" (get_my_tickets)

**Problem**: `get_my_tickets()` requires both `project_id` AND `default_user`

**Solution**:
```python
# Set default user first
config_set_default_user(user_id='your-email@example.com')

# Then set default project
config_set_default_project(project_id='YOUR-PROJECT-ID')

# Now get_my_tickets works
my_tickets = get_my_tickets()
```

### Search returns no results after migration

**Problem**: Searching in wrong project

**Solutions**:

1. **Verify correct project_id**:
   ```python
   config = config_get()
   print(f"Current default_project: {config['config'].get('default_project')}")
   ```

2. **Try explicit project_id**:
   ```python
   # Override default to test different project
   ticket_search(query="...", project_id='DIFFERENT-PROJECT-ID')
   ```

3. **Check if tickets exist in project**:
   ```python
   # List all tickets in project
   all_tickets = ticket_list(project_id='YOUR-PROJECT-ID', limit=100)
   print(f"Total tickets in project: {len(all_tickets['tickets'])}")
   ```

### Existing scripts broke after upgrade

**Problem**: Scripts calling affected tools without project context

**Solutions**:

1. **Add default_project setup at script start**:
   ```python
   # At top of script
   config_set_default_project(project_id='YOUR-PROJECT-ID')

   # Rest of script works unchanged
   ```

2. **Add project_id to all calls** (if multi-project script):
   ```python
   # Find all calls to affected tools
   # Add project_id parameter to each

   # Before:
   tickets = ticket_search(query="bug")

   # After:
   tickets = ticket_search(query="bug", project_id=project_id)
   ```

3. **Use environment variable** (for flexible configuration):
   ```python
   import os

   PROJECT_ID = os.getenv('MCP_PROJECT_ID', 'default-project-id')
   config_set_default_project(project_id=PROJECT_ID)
   ```

## FAQ

### Q: Why wasn't this always required?

**A**: Early versions prioritized ease of use over security. As mcp-ticketer adoption grew in multi-project and multi-tenant environments, the security risks became clear. This change ensures mcp-ticketer is production-ready for enterprise use.

### Q: Can I still search across multiple projects?

**A**: Not in a single call. To search multiple projects:

```python
results = []
for project_id in ['PROJECT-A', 'PROJECT-B', 'PROJECT-C']:
    project_results = ticket_search(query="bug", project_id=project_id)
    results.extend(project_results['tickets'])
```

### Q: What if I only have one project?

**A**: Set it once as default_project and forget about it:

```python
config_set_default_project(project_id='YOUR-ONLY-PROJECT')
# All tools now work transparently
```

### Q: Does this affect ticket_create() or ticket_update()?

**A**: No, only the 5 tools listed in [What Changed](#what-changed) are affected. Ticket creation and update operations use `parent_epic` or direct ticket references, which already provide project context.

### Q: How do I find all my projects?

**A**: Adapter-specific methods:

**Linear**:
```bash
# Use Linear UI or API
# Projects are listed at: https://linear.app/your-org/projects
```

**GitHub**:
```bash
# List your repositories
gh repo list YOUR-ORG
```

**JIRA**:
```bash
# Use JIRA UI: Settings → Projects
# Or API: GET /rest/api/3/project
```

### Q: What happens to my existing default_project configuration?

**A**: It continues to work! If you already have `default_project` configured, no changes needed. The affected tools will automatically use it.

### Q: Can I override default_project for specific calls?

**A**: Yes! Explicit `project_id` parameter always overrides `default_project`:

```python
config_set_default_project(project_id='PROJECT-A')

# Uses PROJECT-A
ticket_search(query="bug")

# Overrides to use PROJECT-B
ticket_search(query="bug", project_id='PROJECT-B')
```

### Q: What about backward compatibility?

**A**: This is a breaking change by necessity for security reasons. However, migration is straightforward:
- Single line of configuration for most users
- Clear error messages guide you through setup
- Migration typically takes < 5 minutes

### Q: Will future versions add more required filters?

**A**: Project filtering is foundational for security and data integrity. We don't anticipate adding more mandatory filters, but if we do, they'll follow the same pattern:
1. Clear migration guide
2. Helpful error messages
3. Simple configuration-based defaults
4. Deprecation warnings in advance (when possible)

## Related Documentation

- [CHANGELOG.md](../../CHANGELOG.md) - Full version history
- [Configuration Guide](../user-docs/configuration/CONFIGURATION.md) - Configuration options
- [MCP API Reference](../mcp-api-reference.md) - Complete tool documentation
- [Linear Adapter](../developer-docs/adapters/LINEAR.md) - Linear-specific details

## Need Help?

- **GitHub Issues**: https://github.com/mcp-ticketer/mcp-ticketer/issues
- **Discussions**: https://github.com/mcp-ticketer/mcp-ticketer/discussions
- **Linear Project**: https://linear.app/1m-hyperdev/project/mcp-ticketer-eac28953c267

---

**Last Updated**: 2025-11-29
**Version**: 1.4.0
**Commit**: 46f9e0e
