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

```
├── .eslintrc.json
├── .github
│   └── workflows
│       └── release.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── .prettierrc
├── .releaserc.json
├── CHANGES.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── docs
│   ├── api-reference.md
│   ├── architecture.md
│   ├── development.md
│   ├── getting-started.md
│   ├── README.md
│   ├── roadmap.md
│   └── troubleshooting.md
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── index.ts
│   ├── tools
│   │   ├── add-project.ts
│   │   ├── add-todo.ts
│   │   ├── export-json.ts
│   │   ├── things-summary.ts
│   │   ├── update-project.ts
│   │   └── update-todo.ts
│   ├── types
│   │   └── things.ts
│   └── utils
│       ├── applescript.ts
│       ├── auth.ts
│       ├── encoder.ts
│       ├── json-operations.ts
│       ├── logger.ts
│       ├── url-builder.ts
│       └── verification.ts
├── tests
│   ├── integration.test.ts
│   ├── README.md
│   ├── server.test.ts
│   └── url-builder.test.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false
}
```

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

```
# Dependencies
node_modules/

# Build output
build/
dist/

# TypeScript cache
*.tsbuildinfo

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Testing
coverage/
.nyc_output/

# Temporary files
*.tmp
*.temp
.cache/
```

--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------

```json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/github",
    [
      "@semantic-release/git",
      {
        "assets": ["package.json", "CHANGELOG.md"],
        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
      }
    ]
  ]
}
```

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "@typescript-eslint/recommended"
  ],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "warn"
  },
  "env": {
    "node": true,
    "es2022": true,
    "jest": true
  }
}
```

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

```markdown
# Test Suite

Core test suite for the Things MCP Server.

## Running Tests

```bash
npm test
```

## Test Files

- `server.test.ts` - Basic server functionality and tool registration
- `url-builder.test.ts` - URL construction and parameter encoding

## Requirements

- Node.js 20+
- Jest test framework
- macOS (for full functionality)

## Note

These tests focus on core functionality and do not require Things.app to be running. For integration testing with actual Things.app functionality, use the MCP Inspector tool:

```bash
npm run inspector
```
```

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

```markdown
# Things MCP Documentation

Welcome to the Things MCP Server documentation. This Model Context Protocol (MCP) server enables AI assistants to interact with Things.app on macOS through a comprehensive set of tools and APIs.

## Documentation Structure

### Getting Started
- **[Getting Started Guide](getting-started.md)** - Quick setup and basic usage
- **[Installation](getting-started.md#installation)** - Installation methods and configuration

### API Reference  
- **[API Reference](api-reference.md)** - Complete tool documentation and examples
- **[Tool Categories](api-reference.md#tool-categories)** - Overview of available MCP tools

### Development
- **[Development Guide](development.md)** - Development workflow and commands
- **[Architecture Overview](architecture.md)** - Technical design and patterns
- **[Testing](development.md#testing)** - Test strategies and execution

### Troubleshooting & Support
- **[Troubleshooting](troubleshooting.md)** - Common issues and solutions
- **[Roadmap](roadmap.md)** - Future features and development plans

## Quick Links

- [GitHub Repository](https://github.com/BMPixel/things-mcp)
- [npm Package](https://www.npmjs.com/package/@wenbopan/things-mcp)
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [Things.app](https://culturedcode.com/things/)

## Overview

The Things MCP Server provides:

- **Task Management**: Create, update, and organize tasks
- **Project Management**: Handle complex projects with multiple tasks
- **Data Access**: Query and export your Things database
- **Schedule Integration**: Work with dates, deadlines, and schedules
- **Automation**: Streamline task workflows with AI assistance

## Requirements

- **macOS**: Things.app is macOS-only
- **Things.app**: Version 3.0 or later
- **Node.js**: Version 18 or later for development
- **MCP Client**: Claude Desktop, Cursor IDE, or other MCP-compatible AI assistant

## Support

- **Issues**: [GitHub Issues](https://github.com/BMPixel/things-mcp/issues)
- **Contributing**: See [CONTRIBUTING.md](../CONTRIBUTING.md)
- **License**: MIT - see [LICENSE](../LICENSE)
```

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

```markdown
# Things MCP Server

[![npm version](https://badge.fury.io/js/@wenbopan%2Fthings-mcp.svg)](https://badge.fury.io/js/@wenbopan%2Fthings-mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![macOS](https://img.shields.io/badge/Platform-macOS-blue.svg)](https://www.apple.com/macos/)

Control your Things.app tasks directly from Claude Code, Claude Desktop, Cursor, and other AI assistants using the Model Context Protocol (MCP).

## What It Does

This MCP server lets AI assistants interact with your Things.app tasks on macOS. You can:

- **Create** new tasks and projects
- **Update** existing items 
- **View** your task database with detailed summaries
- **Schedule** tasks for specific dates
- **Organize** with areas, tags, and deadlines

## Quick Start

### 1. Get Things Authorization Token

For updating existing tasks, you need an authorization token:

1. Open **Things.app** on macOS
2. Go to **Things → Preferences → General** 
3. Check **"Enable Things URLs"**
4. Copy the authorization token that appears

### 2. Configure Your AI Assistant

<details>
<summary><strong>Claude Desktop</strong></summary>

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "mcpServers": {
    "things": {
      "command": "npx",
      "args": ["@wenbopan/things-mcp"],
      "env": {
        "THINGS_AUTH_TOKEN": "your-token-here"
      }
    }
  }
}
```
</details>

<details>
<summary><strong>Cursor IDE</strong></summary>

Create `.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally:

```json
{
  "things": {
    "command": "npx",
    "args": ["@wenbopan/things-mcp"],
    "env": {
      "THINGS_AUTH_TOKEN": "your-token-here"
    }
  }
}
```
</details>

### 3. Restart Your AI Assistant

After configuration, restart your AI assistant to load the MCP server.

## Use Cases

### Daily Planning
"Show me my today's tasks and create a project for the new marketing campaign with initial tasks for research, design, and content creation."

### Project Management  
"Update the mobile app project to add design review and testing tasks, then schedule the design review for next Monday."

### Task Organization
"Move all my unscheduled shopping tasks to the 'Personal' area and tag them with 'weekend'."

### Progress Tracking
"Give me a summary of all active projects with their deadlines and completion status."

### Quick Capture
"Create a task to call the dentist, schedule it for tomorrow, and set a deadline for end of week."


## License

MIT

## Contributing

Issues and pull requests welcome! Please ensure all tests pass before submitting.
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing to Things MCP

Thank you for your interest in contributing to the Things MCP server! This guide will help you get started.

## Development Setup

### Prerequisites
- **macOS** (Things.app requirement)
- **Node.js** 20 or higher
- **Things.app** installed and running

### Getting Started

```bash
# Clone the repository
git clone https://github.com/your-username/things-mcp.git
cd things-mcp

# Install dependencies
npm install

# Build the project
npm run build

# Run tests
npm test
```

### Development Commands

```bash
npm run build          # Compile TypeScript
npm run dev            # Watch mode for development
npm test              # Run all tests
npm run test:watch    # Run tests in watch mode
npm start             # Run the MCP server
```

## Contributing Guidelines

### Code Style
- Follow existing TypeScript conventions
- Use meaningful variable and function names
- Add JSDoc comments for public APIs
- Ensure proper error handling

### Testing
- Write tests for new features
- Ensure all tests pass before submitting
- Test on macOS with Things.app installed
- Include integration tests for MCP tools

### Pull Request Process

1. **Fork** the repository
2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
3. **Make** your changes
4. **Test** thoroughly
5. **Commit** with clear messages
6. **Push** to your fork
7. **Submit** a pull request

### Commit Messages
Use clear, descriptive commit messages:
```
Add support for recurring tasks

- Implement recurring task creation in add_todo tool
- Add recurrence parameter to Zod schema
- Update URL builder to handle recurrence patterns
- Add tests for recurring task functionality
```

## Architecture Overview

This MCP server uses:
- **URL Scheme**: For creating and updating Things items
- **AppleScript**: For reading Things database
- **SQLite Access**: For comprehensive data summaries
- **Zod Validation**: For parameter validation

Key files:
- `src/tools/`: MCP tool implementations
- `src/utils/`: Utility functions (URL building, auth, etc.)
- `src/types/`: TypeScript type definitions
- `tests/`: Test suites

## Publishing and Version Management

### Publishing Workflow

#### 1. Prepare for Release

```bash
# Ensure working directory is clean
git status

# Run tests to ensure everything works
npm test

# Build the project
npm run build
```

#### 2. Update Version Number

Use semantic versioning (MAJOR.MINOR.PATCH):
- **PATCH** (1.0.1): Bug fixes, small changes
- **MINOR** (1.1.0): New features, backward compatible
- **MAJOR** (2.0.0): Breaking changes

```bash
# Automatically bump version and create git tag
npm version patch    # For bug fixes (1.0.0 → 1.0.1)
npm version minor    # For new features (1.0.0 → 1.1.0)
npm version major    # For breaking changes (1.0.0 → 2.0.0)
```

This command:
- Updates `package.json` version
- Creates a git commit with the version change
- Creates a git tag (e.g., `v1.0.1`)

#### 3. Update Changelog (Optional but Recommended)

If you have a `CHANGES.md` file, update it with the new version:

```bash
# Edit CHANGES.md to document changes
# Then commit the update
git add CHANGES.md
git commit -m "Update changelog for v1.0.1"
```

#### 4. Publish to npm

```bash
# Publish the package
npm publish --access public

# If you have 2FA enabled, include OTP
npm publish --access public --otp=123456
```

#### 5. Push Changes to GitHub

```bash
# Push commits and tags to remote repository
git push origin main
git push origin --tags
```

### Quick Reference Commands

#### Complete Release Process

```bash
# 1. Ensure clean state
git status
npm test
npm run build

# 2. Version bump (choose one)
npm version patch   # Bug fixes
npm version minor   # New features  
npm version major   # Breaking changes

# 3. Publish
npm publish --access public

# 4. Push to GitHub
git push origin main --follow-tags
```

#### Emergency Patch

For urgent fixes:

```bash
# Quick patch without extensive testing (use cautiously)
npm version patch
npm publish --access public
git push origin main --follow-tags
```

### Version History Tracking

#### Git Tags
View all published versions:
```bash
git tag -l
```

#### npm Versions
Check published versions on npm:
```bash
npm view @wenbopan/things-mcp versions --json
```

### Rollback Procedures

#### Unpublish (within 24 hours)
```bash
# Only works within 24 hours of publishing
npm unpublish @wenbopan/[email protected]
```

#### Deprecate Version
```bash
# Mark version as deprecated (recommended over unpublishing)
npm deprecate @wenbopan/[email protected] "This version has critical bugs, please upgrade"
```

### Pre-release Versions

For testing before official release:

```bash
# Create pre-release version
npm version prerelease --preid=beta  # 1.0.1-beta.0
npm publish --tag beta --access public

# Install pre-release
npm install -g @wenbopan/things-mcp@beta
```

### Automation Considerations

#### GitHub Actions (Future)
Consider setting up automated publishing:
- Trigger on git tag push
- Run tests automatically
- Publish to npm on success
- Create GitHub releases

#### Package.json Scripts
Add helpful scripts to `package.json`:

```json
{
  "scripts": {
    "release:patch": "npm version patch && npm publish --access public && git push --follow-tags",
    "release:minor": "npm version minor && npm publish --access public && git push --follow-tags",
    "release:major": "npm version major && npm publish --access public && git push --follow-tags"
  }
}
```

### Security Notes

- Always use 2FA for npm account
- Keep auth tokens secure
- Review changes before publishing
- Test in development environment first
- Consider using `npm pack` to inspect package contents before publishing

### Common Issues

#### Authentication
```bash
npm login     # Re-authenticate if needed
npm whoami    # Verify logged in user
```

#### Package Name Conflicts
- Use scoped packages: `@username/package-name`
- Ensure unique naming for public packages

#### Build Issues
```bash
# Clean build
rm -rf build node_modules
npm install
npm run build
```

## Getting Help

- **Issues**: Report bugs or request features via GitHub issues
- **Discussions**: Use GitHub discussions for questions
- **Documentation**: Check README.md for usage instructions

## License

By contributing, you agree that your contributions will be licensed under the MIT License.
```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Development Commands

### Essential Commands
```bash
npm run build          # Compile TypeScript to build/ directory
npm test              # Run Jest tests with ES module support
npm run dev           # Watch mode for development
npm start             # Run the MCP server
npm run inspector     # Launch MCP Inspector for testing tools
```

### Testing Commands
```bash
npm test -- --testNamePattern="URL Builder"  # Run specific test suite
npm test -- tests/server.test.ts             # Run specific test file
npm run test:watch                           # Watch mode for tests
```

## Memories

- Do not run npm run inspector on your own
- github repo is https://github.com/BMPixel/things-mcp

## Architecture Overview

### MCP Server Design
This is a **Model Context Protocol (MCP) server** that integrates with Things.app on macOS via URL scheme automation. The architecture follows these key patterns:

**Core Flow:**
1. MCP tools receive structured parameters (validated with Zod schemas)
2. Tools convert parameters to Things URL scheme format via `url-builder.ts`
3. URLs are executed using macOS `open` command to trigger Things.app actions
4. All operations return MCP-compliant content responses

**Tool Registration Pattern:**
Each tool follows a consistent pattern in `src/tools/`:
- Export a `registerXxxTool(server: McpServer)` function
- Use Zod schemas for input validation with `.shape` property
- Call `server.tool(name, schema.shape, handler)` to register
- Return `{ content: [{ type: "text", text: "..." }] }` format

### Key Components

**URL Construction (`src/utils/url-builder.ts`):**
- `buildThingsUrl()`: Converts parameters to Things URL scheme
- `openThingsUrl()`: Executes URLs via macOS `open` command (async/await)
- Uses `encodeURIComponent()` for proper percent encoding (spaces become `%20`)

**Authentication (`src/utils/auth.ts`):**
- Update operations require `THINGS_AUTH_TOKEN` environment variable
- Use `requireAuthToken()` for operations that modify existing items
- Token obtained from Things.app Preferences → General → Enable Things URLs

**Type System (`src/types/things.ts`):**
- Complete TypeScript definitions for Things URL scheme parameters
- Supports core Things commands: add, add-project, update, update-project
- Includes union types for schedule values (`WhenValue`) and parameter types

**AppleScript Integration (`src/utils/applescript.ts`):**
- `executeAppleScript()`: Executes AppleScript commands via `osascript`
- Data access functions: `listTodos()`, `listProjects()`, `listAreas()`, `listTags()`
- Filtering support for status, dates, projects, areas, and tags

**JSON Operations (`src/utils/json-operations.ts`):**
- `executeJsonOperation()`: Executes JSON-based operations (primarily for completion/cancellation)
- `waitForOperation()`: Utility for waiting before verification
- Handles proper JSON encoding for Things URL scheme

**Verification (`src/utils/verification.ts`):**
- `verifyItemExists()`: Check if item exists in database after operation
- `verifyItemCompleted()`: Verify item completion status
- `verifyItemUpdated()`: Verify item updates with expected values
- Direct SQLite database access for real-time verification

### ES Module Configuration
- **Important**: Project uses ES modules (`"type": "module"` in package.json)
- All imports must use `.js` extensions for TypeScript files
- Jest requires `--experimental-vm-modules` flag
- Use `import` statements, never `require()`

### Tool Categories
1. **Creation Tools** (`add_todo`, `add_project`): No auth required, use URL scheme with JSON schemas
   - Direct parameter structure with validation and descriptions
   - Array fields (tags, checklist-items, initial-todos) converted to comma-separated strings for URL scheme
   - Field validation with max lengths, regex patterns, and enum constraints
2. **Update Tools** (`update_todo`, `update_project`): Require auth token, use hybrid approach
   - **JSON Operations**: Completion/cancellation operations use JSON format for reliability
   - **URL Scheme**: Other updates (title, notes, tags, etc.) use URL scheme for compatibility
   - Array fields (tags, checklist-items) converted to comma-separated strings for URL scheme
   - Support for append/prepend operations on notes and checklist items
   - Field validation with max lengths, regex patterns, and enum constraints
3. **Summary Tool** (`things_summary`): Primary data access tool - Direct database access
   - Returns formatted Markdown or structured JSON for tasks, projects, areas, and tags
   - Advanced filtering by areas, tags, projects, date ranges, and completion status
   - Direct SQLite database access with proper bit-packed date conversion
4. **Export Tool** (`export_json`): Database export tool
   - Exports entire Things database as structured JSON for debugging, backup, or data processing
   - Includes all relationships, metadata, and raw database structures
   - Options for including completed/trashed items and minimal vs. full data export

### Testing Strategy
- Unit tests for URL building and encoding logic (`url-builder.test.ts`)
- Integration tests for tool registration (`server.test.ts`)
- Full integration tests for Things URL scheme operations (`integration.test.ts`)
- **Verification Testing**: Tests include database verification for completion operations
- Platform detection - tests skip on non-macOS systems
- ES module compatibility with Jest requires specific configuration
- Create → Update → Complete lifecycle pattern for comprehensive testing

### macOS Integration
- Only works on macOS (Things.app requirement)
- Uses `child_process.exec` with `open` command for URL schemes
- Uses `sqlite3` command for direct database access in summary tool
- Validates `process.platform === 'darwin'`
- URL scheme for create/update operations, direct database access for comprehensive summaries

### Tool Descriptions
All tools follow MCP best practices with:
- Direct, action-oriented descriptions
- Clear parameter explanations
- Specific format requirements (ISO dates, array values)
- JSON schema compliance for all creation and update operations

### Date Handling
- **Creation Date**: Unix epoch format (seconds since 1970)
- **Start Date/Deadline**: Things bit-packed format requiring bitwise extraction
- Uses Things.py approach for proper date conversion with year/month/day masks
```
```

--------------------------------------------------------------------------------
/src/utils/auth.ts:
--------------------------------------------------------------------------------

```typescript
export function getAuthToken(): string | undefined {
  return process.env.THINGS_AUTH_TOKEN;
}

export function requireAuthToken(): string {
  const token = getAuthToken();
  if (!token) {
    throw new Error('THINGS_AUTH_TOKEN environment variable is required for update operations');
  }
  return token;
}
```

--------------------------------------------------------------------------------
/src/utils/encoder.ts:
--------------------------------------------------------------------------------

```typescript
export function encodeValue(value: any): string {
  if (value === null || value === undefined) {
    return '';
  }
  
  if (typeof value === 'boolean') {
    return value.toString();
  }
  
  if (Array.isArray(value)) {
    return value.map(v => encodeURIComponent(String(v))).join(',');
  }
  
  return encodeURIComponent(String(value));
}

export function encodeJsonData(data: any): string {
  return encodeURIComponent(JSON.stringify(data));
}
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
/** @type {import('jest').Config} */
export default {
  preset: 'ts-jest/presets/default-esm',
  extensionsToTreatAsEsm: ['.ts'],
  testEnvironment: 'node',
  roots: ['<rootDir>/tests'],
  testMatch: ['**/*.test.ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/index.ts'
  ],
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1'
  },
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      useESM: true
    }]
  }
};
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "isolatedModules": true
  },
  "include": ["src/**/*", "tests/**/*"],
  "exclude": ["node_modules", "build"]
}
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
export class Logger {
  private context: string;
  
  constructor(context: string) {
    this.context = context;
  }
  
  private log(level: string, message: string, data?: any): void {
    // MCP servers should log to stderr
    console.error(JSON.stringify({
      timestamp: new Date().toISOString(),
      level,
      context: this.context,
      message,
      ...(data && { data })
    }));
  }
  
  info(message: string, data?: any): void {
    this.log('info', message, data);
  }
  
  error(message: string, data?: any): void {
    this.log('error', message, data);
  }
  
  warn(message: string, data?: any): void {
    this.log('warn', message, data);
  }
  
  debug(message: string, data?: any): void {
    if (process.env.DEBUG) {
      this.log('debug', message, data);
    }
  }
}

export const logger = new Logger('things-mcp');
```

--------------------------------------------------------------------------------
/src/utils/json-operations.ts:
--------------------------------------------------------------------------------

```typescript
import { buildThingsUrl, openThingsUrl } from './url-builder.js';
import { logger } from './logger.js';

export interface JsonOperation {
  type: 'to-do' | 'project';
  operation: 'update';
  id: string;
  attributes: Record<string, any>;
}

/**
 * Execute a JSON-based Things operation
 * @param operation The JSON operation to execute
 * @param authToken The authentication token
 * @returns Promise that resolves when the operation is complete
 */
export async function executeJsonOperation(operation: JsonOperation, authToken: string): Promise<void> {
  const jsonArray = [operation];
  logger.debug('Executing JSON operation', { operation });
  
  const url = buildThingsUrl('json', {
    data: jsonArray, // Pass the array directly, not stringified
    'auth-token': authToken
  });
  
  await openThingsUrl(url);
}

/**
 * Wait for a short period to allow Things to process the operation
 * @param ms Milliseconds to wait (default: 100)
 */
export async function waitForOperation(ms: number = 100): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

--------------------------------------------------------------------------------
/src/utils/url-builder.ts:
--------------------------------------------------------------------------------

```typescript
import { ThingsCommand, ThingsParams } from '../types/things.js';
import { encodeValue, encodeJsonData } from './encoder.js';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

export function buildThingsUrl(command: ThingsCommand, params: Record<string, any>): string {
  const baseUrl = `things:///${command}`;
  const queryParts: string[] = [];
  
  for (const [key, value] of Object.entries(params)) {
    if (value !== undefined && value !== null && value !== '') {
      let encodedValue: string;
      
      if (key === 'data' && command === 'json') {
        // JSON data needs to be stringified and encoded
        encodedValue = encodeURIComponent(JSON.stringify(value));
      } else {
        // Encode all values using proper percent encoding
        const stringValue = Array.isArray(value) ? value.join(',') : String(value);
        encodedValue = encodeURIComponent(stringValue);
      }
      
      queryParts.push(`${encodeURIComponent(key)}=${encodedValue}`);
    }
  }
  
  const queryString = queryParts.join('&');
  return queryString ? `${baseUrl}?${queryString}` : baseUrl;
}

export async function openThingsUrl(url: string): Promise<void> {
  if (process.platform !== 'darwin') {
    throw new Error('Things URL scheme is only supported on macOS');
  }
  
  try {
    await execAsync(`open "${url}"`);
  } catch (error) {
    throw error;
  }
}
```

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

```yaml
name: Release

on:
  push:
    tags:
      - 'v*'
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - run: npm test

  release:
    needs: test
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    permissions:
      contents: write
      issues: write
      pull-requests: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm run build
      - name: Extract version from tag
        id: get_version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      - name: Update package.json version
        run: npm version ${{ steps.get_version.outputs.VERSION }} --no-git-tag-version
      - name: Publish to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
      - name: Create GitHub Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ steps.get_version.outputs.VERSION }}
          draft: false
          prerelease: false
```

--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------

```markdown
# Changes Summary

## Tasks Completed

### 1. Area Creation Investigation
- Confirmed that Things.app does NOT support creating areas via URL scheme or JSON import
- Areas must be created manually in the Things.app interface
- Projects can only be assigned to existing areas

### 2. Removed Show and Search Functionality
- Removed `show` and `search` commands from `ThingsCommand` type
- Removed `ShowTarget`, `ShowParams`, and `SearchParams` interfaces
- Updated `ThingsParams` union type to exclude show/search params

### 3. Updated Integration Tests
- Removed all show/search related tests that caused "You didn't assign which list to access" errors
- Removed invalid URL test that caused annoying popup errors
- Restructured tests to follow lifecycle pattern: Create → Update → Complete
- Updated area assignment test to use "Work" area with warning message
- Added proper auth token checks for update operations
- Added edge case tests for special characters and deadlines

### 4. Test Improvements
- Tests now properly skip update/complete operations when no auth token is available
- Added informative console messages for test execution
- Maintained all URL building validation tests
- Improved test organization with descriptive test suite names

## Files Modified

1. `/src/types/things.ts` - Removed show/search types
2. `/tests/integration.test.ts` - Complete rewrite with lifecycle pattern
3. `/test-improvement-plan.md` - Created to document issues found
4. `/CHANGES.md` - This summary file

## Notes

- The integration tests now create test items but cannot automatically clean them up without real IDs from Things
- Update and complete operations require `THINGS_AUTH_TOKEN` environment variable
- Area assignment only works if the specified area already exists in Things
```

--------------------------------------------------------------------------------
/tests/url-builder.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from '@jest/globals';
import { buildThingsUrl } from '../src/utils/url-builder.js';
import { encodeValue } from '../src/utils/encoder.js';

describe('URL Builder', () => {
  it('should build basic add todo URL', () => {
    const url = buildThingsUrl('add', {
      title: 'Test Todo'
    });
    
    expect(url).toBe('things:///add?title=Test%20Todo');
  });

  it('should build URL with multiple parameters', () => {
    const url = buildThingsUrl('add', {
      title: 'Test Todo',
      notes: 'Some notes',
      when: 'today'
    });
    
    expect(url).toContain('things:///add');
    expect(url).toContain('title=Test%20Todo');
    expect(url).toContain('notes=Some%20notes');
    expect(url).toContain('when=today');
  });

  it('should skip undefined parameters', () => {
    const url = buildThingsUrl('add', {
      title: 'Test Todo',
      notes: undefined,
      when: 'today'
    });
    
    expect(url).not.toContain('notes');
    expect(url).toContain('title=Test%20Todo');
    expect(url).toContain('when=today');
  });

  it('should handle JSON data for json command', () => {
    const data = [{ type: 'to-do', attributes: { title: 'Test' } }];
    const url = buildThingsUrl('json', { data });
    
    expect(url).toContain('things:///json');
    expect(url).toContain('data=');
  });
});

describe('Encoder', () => {
  it('should encode special characters', () => {
    expect(encodeValue('Hello World')).toBe('Hello%20World');
    expect(encodeValue('Test & More')).toBe('Test%20%26%20More');
  });

  it('should handle boolean values', () => {
    expect(encodeValue(true)).toBe('true');
    expect(encodeValue(false)).toBe('false');
  });

  it('should handle arrays', () => {
    expect(encodeValue(['tag1', 'tag2'])).toBe('tag1,tag2');
  });
});
```

--------------------------------------------------------------------------------
/tests/server.test.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { describe, it, expect, beforeEach, beforeAll } from '@jest/globals';
import { registerAddTodoTool } from '../src/tools/add-todo.js';
import { registerAddProjectTool } from '../src/tools/add-project.js';
import { registerUpdateTodoTool } from '../src/tools/update-todo.js';
import { registerUpdateProjectTool } from '../src/tools/update-project.js';

describe('Things MCP Server', () => {
  beforeAll(() => {
    if (process.platform !== 'darwin') {
      console.log('⚠️ Skipping tests on non-macOS platform');
      return;
    }
  });
  let server: McpServer;

  beforeEach(() => {
    server = new McpServer({
      name: 'test-things-mcp',
      version: '1.0.0'
    });
  });

  it('should create server instance', () => {
    if (process.platform !== 'darwin') {
      console.log('Skipping macOS-specific test');
      return;
    }
    expect(server).toBeDefined();
  });

  it('should register add todo tool', () => {
    if (process.platform !== 'darwin') {
      console.log('Skipping macOS-specific test');
      return;
    }
    expect(() => registerAddTodoTool(server)).not.toThrow();
  });

  it('should register add project tool', () => {
    if (process.platform !== 'darwin') {
      console.log('Skipping macOS-specific test');
      return;
    }
    expect(() => registerAddProjectTool(server)).not.toThrow();
  });

  it('should register update todo tool', () => {
    if (process.platform !== 'darwin') {
      console.log('Skipping macOS-specific test');
      return;
    }
    expect(() => registerUpdateTodoTool(server)).not.toThrow();
  });

  it('should register update project tool', () => {
    if (process.platform !== 'darwin') {
      console.log('Skipping macOS-specific test');
      return;
    }
    expect(() => registerUpdateProjectTool(server)).not.toThrow();
  });
});
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@wenbopan/things-mcp",
  "version": "1.0.0",
  "description": "MCP server for Things.app integration on macOS",
  "main": "./build/src/index.js",
  "type": "module",
  "bin": {
    "things-mcp": "./build/src/index.js"
  },
  "scripts": {
    "build": "tsc && chmod +x build/src/index.js",
    "dev": "tsc --watch",
    "start": "node build/src/index.js",
    "inspector": "npm run build && npx @modelcontextprotocol/inspector build/src/index.js",
    "test": "node --experimental-vm-modules node_modules/.bin/jest",
    "test:watch": "jest --watch",
    "test:applescript": "npm run build && node build/tests/run-applescript-tests.js",
    "test:applescript:basic": "npm run build && node build/tests/run-applescript-tests.js --basic",
    "test:applescript:todos": "npm run build && node build/tests/run-applescript-tests.js --todos",
    "test:applescript:projects": "npm run build && node build/tests/run-applescript-tests.js --projects",
    "test:applescript:complex": "npm run build && node build/tests/run-applescript-tests.js --complex",
    "test:applescript:areas": "npm run build && node build/tests/applescript-areas-tags.js",
    "lint": "eslint src/**/*.ts tests/**/*.ts",
    "lint:fix": "eslint src/**/*.ts tests/**/*.ts --fix",
    "format": "prettier --write src/**/*.ts tests/**/*.ts",
    "format:check": "prettier --check src/**/*.ts tests/**/*.ts",
    "prepare": "husky"
  },
  "lint-staged": {
    "*.{ts,js}": [
      "eslint --fix",
      "prettier --write"
    ]
  },
  "keywords": [
    "mcp",
    "things",
    "macos",
    "task-management"
  ],
  "author": "panwenbo",
  "license": "MIT",
  "files": [
    "build"
  ],
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.1",
    "zod": "^3.25.56"
  },
  "devDependencies": {
    "@semantic-release/changelog": "^6.0.3",
    "@semantic-release/git": "^10.0.1",
    "@types/jest": "^29.5.14",
    "@types/node": "^22.15.30",
    "@typescript-eslint/eslint-plugin": "^8.33.1",
    "@typescript-eslint/parser": "^8.33.1",
    "eslint": "^9.28.0",
    "husky": "^9.1.7",
    "jest": "^29.7.0",
    "lint-staged": "^16.1.0",
    "prettier": "^3.5.3",
    "semantic-release": "^24.2.5",
    "ts-jest": "^29.3.4",
    "tsx": "^4.19.4",
    "typescript": "^5.8.3"
  }
}

```

--------------------------------------------------------------------------------
/src/types/things.ts:
--------------------------------------------------------------------------------

```typescript
export type ThingsCommand = 
  | 'add'
  | 'add-project'
  | 'update'
  | 'update-project'
  | 'json';

export type WhenValue = 
  | 'today'
  | 'tomorrow'
  | 'evening'
  | 'anytime'
  | 'someday'
  | string; // ISO date format


export interface AddTodoParams {
  title: string;
  notes?: string;
  when?: WhenValue;
  deadline?: string;
  tags?: string;
  'checklist-items'?: string;
  list?: string;
  heading?: string;
  completed?: boolean;
  canceled?: boolean;
}

export interface AddProjectParams {
  title: string;
  notes?: string;
  when?: WhenValue;
  deadline?: string;
  tags?: string;
  area?: string;
  'to-dos'?: string;
  completed?: boolean;
  canceled?: boolean;
}

export interface UpdateTodoParams {
  id: string;
  'auth-token': string;
  title?: string;
  notes?: string;
  when?: WhenValue;
  deadline?: string;
  tags?: string;
  'checklist-items'?: string;
  list?: string;
  heading?: string;
  completed?: boolean;
  canceled?: boolean;
  'prepend-notes'?: string;
  'append-notes'?: string;
  'add-tags'?: string;
  'add-checklist-items'?: string;
}

export interface UpdateProjectParams {
  id: string;
  'auth-token': string;
  title?: string;
  notes?: string;
  when?: WhenValue;
  deadline?: string;
  tags?: string;
  area?: string;
  completed?: boolean;
  canceled?: boolean;
  'prepend-notes'?: string;
  'append-notes'?: string;
  'add-tags'?: string;
}


export interface JsonTodo {
  type: 'to-do';
  attributes: {
    title: string;
    notes?: string;
    when?: WhenValue;
    deadline?: string;
    tags?: string[];
    'checklist-items'?: Array<{ title: string; completed?: boolean }>;
    completed?: boolean;
    canceled?: boolean;
  };
}

export interface JsonProject {
  type: 'project';
  attributes: {
    title: string;
    notes?: string;
    when?: WhenValue;
    deadline?: string;
    tags?: string[];
    area?: string;
    items?: Array<JsonTodo | JsonHeading>;
    completed?: boolean;
    canceled?: boolean;
  };
}

export interface JsonHeading {
  type: 'heading';
  attributes: {
    title: string;
  };
}

export interface JsonImportParams {
  data: Array<JsonTodo | JsonProject | JsonHeading>;
  reveal?: boolean;
}

export type ThingsParams = 
  | AddTodoParams
  | AddProjectParams
  | UpdateTodoParams
  | UpdateProjectParams
  | JsonImportParams;
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

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

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { logger } from './utils/logger.js';

// Import tool registration functions
import { registerAddTodoTool } from './tools/add-todo.js';
import { registerAddProjectTool } from './tools/add-project.js';
import { registerUpdateTodoTool } from './tools/update-todo.js';
import { registerUpdateProjectTool } from './tools/update-project.js';
import { registerThingsSummaryTool } from './tools/things-summary.js';
import { registerExportJsonTool } from './tools/export-json.js';

const server = new McpServer({
  name: 'things-mcp',
  version: '1.0.0'
});

// Register all tools
registerAddTodoTool(server);
registerAddProjectTool(server);
registerUpdateTodoTool(server);
registerUpdateProjectTool(server);
registerThingsSummaryTool(server);
registerExportJsonTool(server);

// Add a resource that provides information about the server
server.resource(
  'server-info',
  'things-mcp://info',
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: JSON.stringify({
        name: 'Things MCP Server',
        version: '1.0.0',
        description: 'MCP server for Things.app integration on macOS',
        platform: process.platform,
        authTokenRequired: !!process.env.THINGS_AUTH_TOKEN,
        availableTools: [
          'add_todo',
          'add_project', 
          'update_todo',
          'update_project',
          'things_summary',
          'export_json',
        ]
      }, null, 2)
    }]
  })
);

async function main(): Promise<void> {
  try {
    // Check if running on macOS
    if (process.platform !== 'darwin') {
      logger.warn('Things URL scheme is only supported on macOS', { platform: process.platform });
    }
    
    // Check for auth token (required for update operations)
    const hasAuthToken = !!process.env.THINGS_AUTH_TOKEN;
    if (!hasAuthToken) {
      logger.warn('THINGS_AUTH_TOKEN not set - update operations will fail');
    }
    
    const transport = new StdioServerTransport();
    await server.connect(transport);
    
    logger.info('Things MCP server started successfully', {
      platform: process.platform,
      hasAuthToken,
      toolCount: 6
    });
  } catch (error) {
    logger.error('Failed to start server', { error: error instanceof Error ? error.message : error });
    process.exit(1);
  }
}

// Handle process termination gracefully
process.on('SIGINT', () => {
  logger.info('Received SIGINT, shutting down gracefully');
  process.exit(0);
});

process.on('SIGTERM', () => {
  logger.info('Received SIGTERM, shutting down gracefully');
  process.exit(0);
});

main().catch((error) => {
  logger.error('Unhandled error in main', { error: error instanceof Error ? error.message : error });
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------

```markdown
# Getting Started

This guide will help you set up and start using the Things MCP Server with your AI assistant.

## Prerequisites

- **macOS**: Things.app is exclusively available on macOS
- **Things.app**: Download from [culturedcode.com/things](https://culturedcode.com/things/)
- **MCP-compatible AI assistant**: Claude Desktop, Cursor IDE, or other MCP clients

## Installation

### Option 1: Using npx (Recommended)

The easiest way to use Things MCP is with npx - no installation required:

```json
{
  "mcpServers": {
    "things": {
      "command": "npx",
      "args": ["@wenbopan/things-mcp"]
    }
  }
}
```

### Option 2: Global Installation

```bash
npm install -g @wenbopan/things-mcp
```

Then reference the global installation:

```json
{
  "mcpServers": {
    "things": {
      "command": "things-mcp"
    }
  }
}
```

## Configuration

### 1. Enable Things URLs

1. Open **Things.app** on your Mac
2. Go to **Things → Preferences → General**
3. Check **"Enable Things URLs"**
4. Copy the **authorization token** that appears (needed for updates)

### 2. Configure Your AI Assistant

#### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "things": {
      "command": "npx",
      "args": ["@wenbopan/things-mcp"],
      "env": {
        "THINGS_AUTH_TOKEN": "your-authorization-token-here"
      }
    }
  }
}
```

#### Cursor IDE

Create `.cursor/mcp.json` in your project root or `~/.cursor/mcp.json` globally:

```json
{
  "things": {
    "command": "npx", 
    "args": ["@wenbopan/things-mcp"],
    "env": {
      "THINGS_AUTH_TOKEN": "your-authorization-token-here"
    }
  }
}
```

#### Other MCP Clients

Refer to your AI assistant's MCP configuration documentation and use the same command structure.

### 3. Restart Your AI Assistant

After configuration changes, completely restart your AI assistant to load the MCP server.

## Verification

Test the setup by asking your AI assistant:

```
"Show me a summary of my Things tasks"
```

If configured correctly, you should see your current tasks and projects.

## Basic Usage

### Creating Tasks

```
"Create a task to call the dentist and schedule it for tomorrow"
```

### Creating Projects

```
"Create a project called 'Website Redesign' with tasks for research, design, and development"
```

### Viewing Your Data

```
"Show me all my active projects with their deadlines"
```

### Updating Tasks

```
"Mark the 'call dentist' task as completed"
```

## Understanding Permissions

- **Reading data**: No authorization required
- **Creating new items**: No authorization required  
- **Updating existing items**: Requires authorization token

## Next Steps

- Explore the [API Reference](api-reference.md) for all available tools
- Review [common use cases](#common-use-cases) below
- Check [troubleshooting](troubleshooting.md) if you encounter issues

## Common Use Cases

### Daily Planning
Ask your AI assistant to:
- Review today's tasks
- Create new tasks for unexpected items
- Reschedule tasks as priorities change

### Project Management
- Create project templates with common tasks
- Track project progress and deadlines
- Organize tasks by areas and tags

### Quick Capture
- Rapidly capture ideas and tasks
- Set appropriate deadlines and schedules
- Tag and categorize for later organization

### Weekly Reviews
- Export data for analysis
- Review completed and pending items
- Plan upcoming priorities

## Tips for Effective Use

1. **Be specific with dates**: Use clear date formats like "next Monday" or "2024-01-15"
2. **Use natural language**: The AI assistant will translate to proper Things.app parameters
3. **Leverage areas and tags**: Organize tasks for better filtering and management
4. **Set deadlines strategically**: Use deadlines for truly time-sensitive items
5. **Regular reviews**: Use summary tools to maintain overview of your task system
```

--------------------------------------------------------------------------------
/src/utils/verification.ts:
--------------------------------------------------------------------------------

```typescript
import { execSync } from 'child_process';
import { existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { logger } from './logger.js';
import { waitForOperation } from './json-operations.js';

interface TaskData {
  id: string;
  title: string | null;
  type: number;
  status: number;
  trashed: boolean;
  area_id: string | null;
  project_id: string | null;
}

function findThingsDatabase(): string {
  const homeDir = process.env.HOME || '/Users/' + process.env.USER;
  const thingsGroupContainer = join(homeDir, 'Library/Group Containers');
  
  if (!existsSync(thingsGroupContainer)) {
    throw new Error('Things group container not found. Please ensure Things.app is installed on macOS.');
  }
  
  const containers = readdirSync(thingsGroupContainer);
  const thingsContainer = containers.find(dir => 
    dir.includes('JLMPQHK86H.com.culturedcode.ThingsMac')
  );
  
  if (!thingsContainer) {
    throw new Error('Things container not found. Please ensure Things.app is installed and has been launched at least once.');
  }
  
  const containerPath = join(thingsGroupContainer, thingsContainer);
  const contents = readdirSync(containerPath);
  const thingsDataDir = contents.find(dir => dir.startsWith('ThingsData-'));
  
  if (!thingsDataDir) {
    throw new Error('ThingsData directory not found.');
  }
  
  const dbPath = join(containerPath, thingsDataDir, 'Things Database.thingsdatabase', 'main.sqlite');
  
  if (!existsSync(dbPath)) {
    throw new Error('Things database file not found.');
  }
  
  return dbPath;
}

function executeSqlQuery(dbPath: string, query: string): any[] {
  try {
    const result = execSync(`sqlite3 "${dbPath}" "${query}"`, { 
      encoding: 'utf8',
      maxBuffer: 10 * 1024 * 1024 // 10MB buffer
    });
    
    if (!result.trim()) {
      return [];
    }
    
    return result.trim().split('\n').map(row => {
      return row.split('|');
    });
  } catch (error) {
    logger.error('SQL Query failed', { error: error instanceof Error ? error.message : error, query });
    return [];
  }
}

/**
 * Verify that a task or project exists in the Things database
 * @param id The ID of the item to verify
 * @param waitMs Time to wait before checking (default: 100ms)
 * @returns The task data if found, null otherwise
 */
export async function verifyItemExists(id: string, waitMs: number = 100): Promise<TaskData | null> {
  // Wait for operation to complete
  await waitForOperation(waitMs);
  
  try {
    const dbPath = findThingsDatabase();
    const query = `SELECT uuid, title, type, status, trashed, area, project FROM TMTask WHERE uuid = '${id}'`;
    const result = executeSqlQuery(dbPath, query);
    
    if (result.length === 0) {
      return null;
    }
    
    const row = result[0];
    return {
      id: row[0],
      title: row[1] || null,
      type: parseInt(row[2]) || 0, // 0=task, 1=project, 2=heading
      status: parseInt(row[3]) || 0, // 0=open, 3=completed, 2=canceled
      trashed: row[4] === '1',
      area_id: row[5] || null,
      project_id: row[6] || null
    };
  } catch (error) {
    logger.error('Failed to verify item exists', { error: error instanceof Error ? error.message : error, id });
    return null;
  }
}

/**
 * Verify that a task or project has been completed
 * @param id The ID of the item to verify
 * @param waitMs Time to wait before checking (default: 100ms)
 * @returns True if the item is completed, false otherwise
 */
export async function verifyItemCompleted(id: string, waitMs: number = 100): Promise<boolean> {
  const item = await verifyItemExists(id, waitMs);
  return item !== null && item.status === 3; // 3 = completed
}

/**
 * Verify that a task or project has been updated with specific attributes
 * @param id The ID of the item to verify
 * @param expectedTitle The expected title (optional)
 * @param waitMs Time to wait before checking (default: 100ms)
 * @returns True if the item matches expectations, false otherwise
 */
export async function verifyItemUpdated(
  id: string, 
  expectedTitle?: string, 
  waitMs: number = 100
): Promise<boolean> {
  const item = await verifyItemExists(id, waitMs);
  
  if (!item) {
    return false;
  }
  
  if (expectedTitle && item.title !== expectedTitle) {
    return false;
  }
  
  return true;
}
```

--------------------------------------------------------------------------------
/src/tools/add-project.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { buildThingsUrl, openThingsUrl } from '../utils/url-builder.js';
import { logger } from '../utils/logger.js';

const addProjectSchema = z.object({
  title: z.string().min(1).describe('Project title (required). Clear name describing the project goal, outcome, or deliverable'),
  notes: z.string().max(10000).optional().describe('Project description, objectives, scope, or additional context (max 10,000 characters). Supports markdown formatting for rich text documentation'),
  when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday'])
    .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
    .optional()
    .describe('Schedule when to start working on this project. Use "today" to start immediately, "tomorrow" to start next day, "evening" to start later today, "anytime" for flexible timing, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific start date'),
  deadline: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .optional()
    .describe('Set a deadline for project completion in ISO date format (YYYY-MM-DD). Creates deadline tracking and reminders in Things.app'),
  tags: z.array(z.string().min(1))
    .max(20)
    .optional()
    .describe('Array of tag names for categorizing and organizing the project (max 20 tags). Tags help with filtering and project management'),
  areaId: z.string()
    .optional()
    .describe('ID of the area of responsibility to assign this project to. Use this when you know the specific area ID'),
  areaName: z.string()
    .optional()
    .describe('Name of the area of responsibility to assign this project to (e.g., "Work", "Personal", "Health", "Finance"). Areas help organize projects by life domain'),
  initialTodos: z.array(z.string().min(1))
    .max(50)
    .optional()
    .describe('Array of initial to-do item descriptions to create within the project (max 50 items). Each string becomes a separate task within the project'),
  completed: z.boolean()
    .optional()
    .default(false)
    .describe('Mark the project as completed immediately upon creation (default: false). Useful for logging already completed projects'),
  canceled: z.boolean()
    .optional()
    .default(false)
    .describe('Mark the project as canceled immediately upon creation (default: false). Useful for recording projects that are no longer viable'),
  creationDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for importing historical project data'),
  completionDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when completed is true')
});

export function registerAddProjectTool(server: McpServer): void {
  server.tool(
    'add_project',
    'Create a new project in Things.app. Add notes, tags, assign to areas, and pre-populate with initial to-dos.',
    addProjectSchema.shape,
    async (params) => {
      try {
        logger.info('Adding new project', { title: params.title });
        
        const urlParams: Record<string, any> = {
          title: params.title
        };

        // Map schema parameters to Things URL scheme parameters
        if (params.notes) urlParams.notes = params.notes;
        if (params.when) urlParams.when = params.when;
        if (params.deadline) urlParams.deadline = params.deadline;
        if (params.tags) urlParams.tags = params.tags.join(',');
        if (params.areaId) urlParams['area-id'] = params.areaId;
        if (params.areaName) urlParams.area = params.areaName;
        if (params.initialTodos) urlParams['to-dos'] = params.initialTodos.join('\n');
        if (params.completed) urlParams.completed = params.completed;
        if (params.canceled) urlParams.canceled = params.canceled;
        if (params.creationDate) urlParams['creation-date'] = params.creationDate;
        if (params.completionDate) urlParams['completion-date'] = params.completionDate;
        
        const url = buildThingsUrl('add-project', urlParams);
        logger.debug('Generated URL', { url });
        
        await openThingsUrl(url);
        
        return {
          content: [{
            type: "text",
            text: `Successfully created project: ${params.title}`
          }]
        };
      } catch (error) {
        logger.error('Failed to add project', { error: error instanceof Error ? error.message : error });
        throw error;
      }
    }
  );
}
```

--------------------------------------------------------------------------------
/src/tools/add-todo.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { buildThingsUrl, openThingsUrl } from '../utils/url-builder.js';
import { logger } from '../utils/logger.js';

const addTodoSchema = z.object({
  title: z.string().min(1).describe('To-do title (required). Clear, actionable description of the task'),
  notes: z.string().max(10000).optional().describe('Additional notes or details for the to-do (max 10,000 characters). Supports markdown formatting for rich text'),
  when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday'])
    .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
    .optional()
    .describe('Schedule the to-do for a specific time. Use "today" for immediate action, "tomorrow" for next day, "evening" for later today, "anytime" for no specific time, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific date'),
  deadline: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .optional()
    .describe('Set a deadline for the to-do in ISO date format (YYYY-MM-DD). Creates a deadline reminder in Things.app'),
  tags: z.array(z.string().min(1))
    .max(20)
    .optional()
    .describe('Array of tag names for organizing and categorizing the to-do (max 20 tags). Tags help with filtering and organization'),
  checklistItems: z.array(z.string().min(1))
    .max(100)
    .optional()
    .describe('Array of checklist item descriptions to add as sub-tasks (max 100 items). Each item becomes a checkable sub-task within the to-do'),
  projectId: z.string()
    .optional()
    .describe('ID of the project to add this to-do to. Use this when you know the specific project ID'),
  projectName: z.string()
    .optional()
    .describe('Name of the project to add this to-do to. Things.app will find the project by name and add the to-do there'),
  areaId: z.string()
    .optional()
    .describe('ID of the area of responsibility to assign this to-do to. Use this when you know the specific area ID'),
  areaName: z.string()
    .optional()
    .describe('Name of the area of responsibility to assign this to-do to (e.g., "Work", "Personal", "Health")'),
  headingId: z.string()
    .optional()
    .describe('ID of a specific heading within the target project to organize the to-do under'),
  headingName: z.string()
    .optional()
    .describe('Name of a heading within the target project to organize the to-do under (e.g., "Phase 1", "Research")'),
  completed: z.boolean()
    .optional()
    .default(false)
    .describe('Mark the to-do as completed immediately upon creation (default: false). Useful for logging already completed tasks'),
  canceled: z.boolean()
    .optional()
    .default(false)
    .describe('Mark the to-do as canceled immediately upon creation (default: false). Useful for recording tasks that are no longer needed'),
  creationDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for importing historical data'),
  completionDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when completed is true')
});

export function registerAddTodoTool(server: McpServer): void {
  server.tool(
    'add_todo',
    'Create a new to-do item in Things.app. Add notes, tags, checklist items, and assign to projects or areas.',
    addTodoSchema.shape,
    async (params) => {
      try {
        logger.info('Adding new to-do', { title: params.title });
        
        const urlParams: Record<string, any> = {
          title: params.title
        };

        // Map schema parameters to Things URL scheme parameters
        if (params.notes) urlParams.notes = params.notes;
        if (params.when) urlParams.when = params.when;
        if (params.deadline) urlParams.deadline = params.deadline;
        if (params.tags) urlParams.tags = params.tags.join(',');
        if (params.checklistItems) urlParams['checklist-items'] = params.checklistItems.join(',');
        if (params.projectId) urlParams['list-id'] = params.projectId;
        if (params.projectName) urlParams.list = params.projectName;
        if (params.areaId) urlParams['area-id'] = params.areaId;
        if (params.areaName) urlParams.area = params.areaName;
        if (params.headingId) urlParams['heading-id'] = params.headingId;
        if (params.headingName) urlParams.heading = params.headingName;
        if (params.completed) urlParams.completed = params.completed;
        if (params.canceled) urlParams.canceled = params.canceled;
        if (params.creationDate) urlParams['creation-date'] = params.creationDate;
        if (params.completionDate) urlParams['completion-date'] = params.completionDate;
        
        const url = buildThingsUrl('add', urlParams);
        logger.debug('Generated URL', { url });
        
        await openThingsUrl(url);
        
        return {
          content: [{
            type: "text",
            text: `Successfully created to-do: ${params.title}`
          }]
        };
      } catch (error) {
        logger.error('Failed to add to-do', { error: error instanceof Error ? error.message : error });
        throw error;
      }
    }
  );
}
```

--------------------------------------------------------------------------------
/src/tools/update-project.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { buildThingsUrl, openThingsUrl } from '../utils/url-builder.js';
import { requireAuthToken } from '../utils/auth.js';
import { logger } from '../utils/logger.js';
import { executeJsonOperation, JsonOperation } from '../utils/json-operations.js';

const updateProjectSchema = z.object({
  id: z.string().min(1).describe('The unique ID of the project to update. This ID can be obtained from the list_projects tool'),
  title: z.string().min(1).optional().describe('Update the project title with a new clear name describing the project goal, outcome, or deliverable'),
  notes: z.string().max(10000).optional().describe('Replace existing notes with new project description, objectives, or context (max 10,000 characters). Supports markdown formatting. This completely replaces existing notes'),
  prependNotes: z.string().optional().describe('Add text to the beginning of existing notes without replacing them. Useful for adding project updates or new objectives'),
  appendNotes: z.string().optional().describe('Add text to the end of existing notes without replacing them. Useful for adding progress updates or new requirements'),
  when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday'])
    .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
    .optional()
    .describe('Reschedule when to start working on this project. Use "today" to start immediately, "tomorrow" to start next day, "evening" to start later today, "anytime" for flexible timing, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific start date'),
  deadline: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .optional()
    .describe('Update the project deadline in ISO date format (YYYY-MM-DD). Creates or updates deadline tracking and reminders in Things.app'),
  tags: z.array(z.string().min(1))
    .max(20)
    .optional()
    .describe('Replace all current tags with this new set of tag names (max 20 tags). This completely replaces existing tags for the project'),
  addTags: z.array(z.string().min(1))
    .max(20)
    .optional()
    .describe('Add these tag names to existing tags without removing current ones (max 20 total tags). Preserves existing project tags'),
  areaId: z.string()
    .optional()
    .describe('Move the project to a different area of responsibility by specifying the area ID'),
  areaName: z.string()
    .optional()
    .describe('Move the project to a different area of responsibility by specifying the area name (e.g., "Work", "Personal", "Health", "Finance")'),
  completed: z.boolean()
    .optional()
    .describe('Mark the project as completed (true) or reopen it (false). Completed projects are moved to the Logbook along with all their to-dos'),
  canceled: z.boolean()
    .optional()
    .describe('Mark the project as canceled (true) or restore it (false). Canceled projects are moved to the Trash along with all their to-dos'),
  creationDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for data migration or historical project tracking'),
  completionDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when marking the project as completed')
});

export function registerUpdateProjectTool(server: McpServer): void {
  server.tool(
    'update_project',
    'Update an existing project in Things.app. Modify title, notes, scheduling, tags, area assignment, and completion status.',
    updateProjectSchema.shape,
    async (params) => {
      try {
        logger.info('Updating project', { id: params.id });
        
        const authToken = requireAuthToken();
        
        // Check if we're doing a completion/cancellation operation (use JSON)
        if (params.completed !== undefined || params.canceled !== undefined) {
          const attributes: Record<string, any> = {};
          
          if (params.completed !== undefined) {
            attributes.completed = params.completed;
            if (params.completionDate) {
              attributes['completion-date'] = params.completionDate;
            }
          }
          
          if (params.canceled !== undefined) {
            attributes.canceled = params.canceled;
          }
          
          const operation: JsonOperation = {
            type: 'project',
            operation: 'update',
            id: params.id,
            attributes
          };
          
          await executeJsonOperation(operation, authToken);
        } else {
          // Use URL scheme for other updates
          const urlParams: Record<string, any> = {
            id: params.id,
            'auth-token': authToken
          };

          // Map schema parameters to Things URL scheme parameters
          if (params.title) urlParams.title = params.title;
          if (params.notes) urlParams.notes = params.notes;
          if (params.prependNotes) urlParams['prepend-notes'] = params.prependNotes;
          if (params.appendNotes) urlParams['append-notes'] = params.appendNotes;
          if (params.when) urlParams.when = params.when;
          if (params.deadline) urlParams.deadline = params.deadline;
          if (params.tags) urlParams.tags = params.tags.join(',');
          if (params.addTags) urlParams['add-tags'] = params.addTags.join(',');
          if (params.areaId) urlParams['area-id'] = params.areaId;
          if (params.areaName) urlParams.area = params.areaName;
          if (params.creationDate) urlParams['creation-date'] = params.creationDate;
          
          const url = buildThingsUrl('update-project', urlParams);
          logger.debug('Generated URL', { url: url.replace(authToken, '***') });
          
          await openThingsUrl(url);
        }
        
        return {
          content: [{
            type: "text",
            text: `Successfully updated project: ${params.id}`
          }]
        };
      } catch (error) {
        logger.error('Failed to update project', { error: error instanceof Error ? error.message : error });
        throw error;
      }
    }
  );
}
```

--------------------------------------------------------------------------------
/src/tools/update-todo.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { buildThingsUrl, openThingsUrl } from '../utils/url-builder.js';
import { requireAuthToken } from '../utils/auth.js';
import { logger } from '../utils/logger.js';
import { executeJsonOperation, JsonOperation } from '../utils/json-operations.js';

const updateTodoSchema = z.object({
  id: z.string().min(1).describe('The unique ID of the to-do to update. This ID can be obtained from the list_todos tool'),
  title: z.string().min(1).optional().describe('Update the to-do title with a new clear, actionable description of the task'),
  notes: z.string().max(10000).optional().describe('Replace existing notes with new content (max 10,000 characters). Supports markdown formatting. This completely replaces existing notes'),
  prependNotes: z.string().optional().describe('Add text to the beginning of existing notes without replacing them. Useful for adding updates or new information'),
  appendNotes: z.string().optional().describe('Add text to the end of existing notes without replacing them. Useful for adding follow-up information or status updates'),
  when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday'])
    .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
    .optional()
    .describe('Reschedule the to-do. Use "today" for immediate action, "tomorrow" for next day, "evening" for later today, "anytime" for no specific time, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific date'),
  deadline: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .optional()
    .describe('Update the deadline in ISO date format (YYYY-MM-DD). Creates or updates deadline reminder in Things.app'),
  tags: z.array(z.string().min(1))
    .max(20)
    .optional()
    .describe('Replace all current tags with this new set of tag names (max 20 tags). This completely replaces existing tags'),
  addTags: z.array(z.string().min(1))
    .max(20)
    .optional()
    .describe('Add these tag names to existing tags without removing current ones (max 20 total tags). Preserves existing tags'),
  checklistItems: z.array(z.string().min(1))
    .max(100)
    .optional()
    .describe('Replace all current checklist items with this new set (max 100 items). This completely replaces existing checklist items'),
  prependChecklistItems: z.array(z.string().min(1))
    .optional()
    .describe('Add these checklist items to the beginning of the existing checklist without removing current items'),
  appendChecklistItems: z.array(z.string().min(1))
    .optional()
    .describe('Add these checklist items to the end of the existing checklist without removing current items'),
  projectId: z.string()
    .optional()
    .describe('Move the to-do to a different project by specifying the project ID'),
  projectName: z.string()
    .optional()
    .describe('Move the to-do to a different project by specifying the project name. Things.app will find the project by name'),
  areaId: z.string()
    .optional()
    .describe('Move the to-do to a different area by specifying the area ID'),
  areaName: z.string()
    .optional()
    .describe('Move the to-do to a different area by specifying the area name (e.g., "Work", "Personal", "Health")'),
  headingId: z.string()
    .optional()
    .describe('Move the to-do under a specific heading within the target project by heading ID'),
  headingName: z.string()
    .optional()
    .describe('Move the to-do under a specific heading within the target project by heading name (e.g., "Phase 1", "Research")'),
  completed: z.boolean()
    .optional()
    .describe('Mark the to-do as completed (true) or reopen it (false). Completed to-dos are moved to the Logbook'),
  canceled: z.boolean()
    .optional()
    .describe('Mark the to-do as canceled (true) or restore it (false). Canceled to-dos are moved to the Trash'),
  creationDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for data migration'),
  completionDate: z.string()
    .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
    .optional()
    .describe('Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when marking as completed')
});

export function registerUpdateTodoTool(server: McpServer): void {
  server.tool(
    'update_todo',
    'Update an existing to-do item in Things.app. Modify title, notes, scheduling, tags, checklist items, project/area assignment, and completion status.',
    updateTodoSchema.shape,
    async (params) => {
      try {
        logger.info('Updating to-do', { id: params.id });
        
        const authToken = requireAuthToken();
        
        // Check if we're doing a completion/cancellation operation (use JSON)
        if (params.completed !== undefined || params.canceled !== undefined) {
          const attributes: Record<string, any> = {};
          
          if (params.completed !== undefined) {
            attributes.completed = params.completed;
            if (params.completionDate) {
              attributes['completion-date'] = params.completionDate;
            }
          }
          
          if (params.canceled !== undefined) {
            attributes.canceled = params.canceled;
          }
          
          const operation: JsonOperation = {
            type: 'to-do',
            operation: 'update',
            id: params.id,
            attributes
          };
          
          await executeJsonOperation(operation, authToken);
        } else {
          // Use URL scheme for other updates
          const urlParams: Record<string, any> = {
            id: params.id,
            'auth-token': authToken
          };

          // Map schema parameters to Things URL scheme parameters
          if (params.title) urlParams.title = params.title;
          if (params.notes) urlParams.notes = params.notes;
          if (params.prependNotes) urlParams['prepend-notes'] = params.prependNotes;
          if (params.appendNotes) urlParams['append-notes'] = params.appendNotes;
          if (params.when) urlParams.when = params.when;
          if (params.deadline) urlParams.deadline = params.deadline;
          if (params.tags) urlParams.tags = params.tags.join(',');
          if (params.addTags) urlParams['add-tags'] = params.addTags.join(',');
          if (params.checklistItems) urlParams['checklist-items'] = params.checklistItems.join(',');
          if (params.prependChecklistItems) urlParams['prepend-checklist-items'] = params.prependChecklistItems.join(',');
          if (params.appendChecklistItems) urlParams['append-checklist-items'] = params.appendChecklistItems.join(',');
          if (params.projectId) urlParams['list-id'] = params.projectId;
          if (params.projectName) urlParams.list = params.projectName;
          if (params.areaId) urlParams['area-id'] = params.areaId;
          if (params.areaName) urlParams.area = params.areaName;
          if (params.headingId) urlParams['heading-id'] = params.headingId;
          if (params.headingName) urlParams.heading = params.headingName;
          if (params.creationDate) urlParams['creation-date'] = params.creationDate;
          
          const url = buildThingsUrl('update', urlParams);
          logger.debug('Generated URL', { url: url.replace(authToken, '***') });
          
          await openThingsUrl(url);
        }
        
        return {
          content: [{
            type: "text",
            text: `Successfully updated to-do: ${params.id}`
          }]
        };
      } catch (error) {
        logger.error('Failed to update to-do', { error: error instanceof Error ? error.message : error });
        throw error;
      }
    }
  );
}
```

--------------------------------------------------------------------------------
/docs/development.md:
--------------------------------------------------------------------------------

```markdown
# Development Guide

Guide for developers working on the Things MCP Server, including setup, testing, and contribution workflows.

## Prerequisites

- **macOS**: Required for Things.app integration and testing
- **Node.js**: Version 18 or later
- **Things.app**: Version 3.0+ for testing integration
- **Git**: For version control

## Development Setup

### 1. Clone and Install

```bash
git clone https://github.com/BMPixel/things-mcp.git
cd things-mcp
npm install
```

### 2. Configure Things.app

1. Open Things.app and enable URL scheme support:
   - **Things → Preferences → General**
   - Check **"Enable Things URLs"**
   - Copy the authorization token

2. Set up environment variables:
   ```bash
   export THINGS_AUTH_TOKEN="your-token-here"
   ```

### 3. Build the Project

```bash
npm run build
```

## Development Commands

### Build and Development
```bash
npm run build          # Compile TypeScript to build/ directory
npm run dev            # Watch mode for development
npm start              # Run the MCP server
```

### Testing
```bash
npm test               # Run Jest tests with ES module support
npm run test:watch     # Watch mode for tests
npm test -- --testNamePattern="URL Builder"  # Run specific test suite
npm test -- tests/server.test.ts             # Run specific test file
```

### Code Quality
```bash
npm run lint           # Check code with ESLint
npm run lint:fix       # Fix linting issues automatically
npm run format         # Format code with Prettier
npm run format:check   # Check code formatting
```

### MCP Testing
```bash
npm run inspector      # Launch MCP Inspector for tool testing
```

**Note**: Do not run `npm run inspector` in CI or automated environments.

## Project Structure

```
src/
├── index.ts           # MCP server entry point
├── tools/             # MCP tool implementations
│   ├── add-todo.ts    # Task creation tool
│   ├── add-project.ts # Project creation tool
│   ├── update-todo.ts # Task update tool
│   ├── update-project.ts # Project update tool
│   ├── things-summary.ts # Database query tool
│   └── export-json.ts # Database export tool
├── types/             # TypeScript type definitions
│   └── things.ts      # Things URL scheme types
└── utils/             # Utility modules
    ├── applescript.ts # AppleScript integration
    ├── auth.ts        # Authorization handling
    ├── encoder.ts     # URL encoding utilities
    ├── json-operations.ts # JSON-based operations
    ├── logger.ts      # Logging utilities
    ├── url-builder.ts # Things URL construction
    └── verification.ts # Operation verification

tests/
├── integration.test.ts # Full workflow tests
├── server.test.ts     # Tool registration tests
└── url-builder.test.ts # URL building tests
```

## Architecture Patterns

### Tool Registration Pattern

Each tool follows a consistent registration pattern:

```typescript
// src/tools/example-tool.ts
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const exampleSchema = z.object({
  // Define parameters with validation and descriptions
});

export function registerExampleTool(server: McpServer): void {
  server.tool(
    'tool_name',
    'Tool description for AI assistants',
    exampleSchema.shape, // Use .shape for proper schema registration
    async (params) => {
      // Tool implementation
      return {
        content: [{
          type: "text",
          text: "Result message"
        }]
      };
    }
  );
}
```

### URL Construction Pattern

All Things.app interactions use the URL scheme:

```typescript
import { buildThingsUrl, openThingsUrl } from '../utils/url-builder.js';

// Build URL with parameters
const url = buildThingsUrl('add', {
  title: 'Task title',
  when: 'today'
});

// Execute URL
await openThingsUrl(url);
```

### ES Module Configuration

This project uses ES modules with specific requirements:

- All imports must use `.js` extensions for TypeScript files
- `package.json` has `"type": "module"`
- Jest requires `--experimental-vm-modules` flag
- Use `import` statements, never `require()`

## Testing Strategy

### Unit Tests

Focus on core logic and URL building:

```bash
npm test -- tests/url-builder.test.ts
```

### Integration Tests

Test full MCP tool workflows:

```bash
npm test -- tests/integration.test.ts
```

### Platform Detection

Tests automatically skip on non-macOS platforms:

```typescript
beforeEach(() => {
  if (process.platform !== 'darwin') {
    test.skip('macOS-only test');
  }
});
```

### Test Patterns

1. **Create → Update → Complete**: Test full task lifecycle
2. **Database Verification**: Verify operations using SQLite queries
3. **Error Handling**: Test invalid inputs and error responses
4. **Platform Compatibility**: Ensure graceful handling on non-macOS

## Code Standards

### TypeScript

- Strict TypeScript configuration
- Comprehensive type definitions in `src/types/`
- Use Zod schemas for runtime validation

### Code Style

- ESLint configuration enforces consistency
- Prettier for automatic formatting
- Pre-commit hooks ensure quality

### Documentation

- JSDoc comments for public APIs
- Clear parameter descriptions in Zod schemas
- Comprehensive error messages

## Development Workflow

### 1. Feature Development

```bash
# Create feature branch
git checkout -b feature/new-tool

# Develop with watch mode
npm run dev

# Test during development
npm run test:watch
```

### 2. Testing

```bash
# Run all tests
npm test

# Test specific functionality
npm test -- --testNamePattern="add_todo"

# Integration testing with MCP Inspector
npm run inspector
```

### 3. Code Quality

```bash
# Check and fix code quality
npm run lint:fix
npm run format

# Verify build
npm run build
```

### 4. Commit and Push

```bash
# Commits trigger pre-commit hooks
git add .
git commit -m "feat: add new tool functionality"
git push origin feature/new-tool
```

## Debugging

### MCP Inspector

The MCP Inspector provides a web interface for testing tools:

```bash
npm run inspector
```

Access at `http://localhost:3000` to:
- Test tool parameters
- View responses
- Debug tool behavior

### Logging

The project uses structured logging:

```typescript
import { logger } from '../utils/logger.js';

logger.info('Operation started', { tool: 'add_todo', params });
logger.error('Operation failed', { error: error.message });
```

### Database Debugging

Direct SQLite access for debugging:

```bash
# Find Things database
ls ~/Library/Group\ Containers/*/ThingsData-*/Things\ Database.thingsdatabase/

# Query database
sqlite3 "path/to/main.sqlite" "SELECT * FROM TMTask LIMIT 5;"
```

## Common Development Tasks

### Adding a New Tool

1. Create tool file in `src/tools/`
2. Define Zod schema with descriptions
3. Implement tool logic
4. Register in `src/index.ts`
5. Add tests
6. Update documentation

### Updating Types

1. Modify `src/types/things.ts`
2. Update affected tools
3. Run tests to verify compatibility
4. Update API documentation

### Performance Optimization

1. Profile using Node.js built-in profiler
2. Optimize database queries in summary tool
3. Minimize URL scheme calls
4. Use efficient JSON operations

## Contribution Guidelines

### Before Contributing

1. Read [CONTRIBUTING.md](../CONTRIBUTING.md)
2. Check existing issues and PRs
3. Discuss major changes in GitHub issues

### Pull Request Process

1. Ensure all tests pass
2. Add tests for new functionality
3. Update documentation
4. Follow commit message conventions
5. Request review from maintainers

### Release Process

The project uses semantic versioning with automated releases:

1. Commits to `main` trigger CI/CD
2. Semantic commit messages determine version bumps
3. Releases are automatically published to npm

## Environment Variables

| Variable | Description | Required |
|----------|-------------|----------|
| `THINGS_AUTH_TOKEN` | Authorization token from Things.app | For update operations |
| `NODE_ENV` | Environment: `development`, `production`, `test` | No |
| `LOG_LEVEL` | Logging level: `debug`, `info`, `warn`, `error` | No |

## Troubleshooting

### Common Issues

**Build Errors**: Ensure TypeScript and dependencies are up to date
**Test Failures**: Check macOS version and Things.app installation
**Permission Errors**: Verify Things URL authorization is enabled

### Getting Help

- Check [troubleshooting guide](troubleshooting.md)
- Review existing [GitHub issues](https://github.com/BMPixel/things-mcp/issues)
- Create new issue with detailed reproduction steps
```

--------------------------------------------------------------------------------
/docs/api-reference.md:
--------------------------------------------------------------------------------

```markdown
# API Reference

Complete reference for all MCP tools provided by the Things MCP Server.

## Tool Categories

### Creation Tools
- [`add_todo`](#add_todo) - Create new tasks with scheduling and organization
- [`add_project`](#add_project) - Create new projects with initial tasks

### Update Tools
- [`update_todo`](#update_todo) - Modify existing tasks (requires auth token)
- [`update_project`](#update_project) - Modify existing projects (requires auth token)

### Data Access Tools
- [`things_summary`](#things_summary) - Query and summarize your Things database
- [`export_json`](#export_json) - Export complete database as structured JSON

---

## add_todo

Create a new to-do item in Things.app with full scheduling and organization features.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | Yes | Clear, actionable description of the task |
| `notes` | string | No | Additional details (max 10,000 characters, supports markdown) |
| `when` | string | No | Schedule: `today`, `tomorrow`, `evening`, `anytime`, `someday`, or ISO date (YYYY-MM-DD) |
| `deadline` | string | No | Deadline in ISO date format (YYYY-MM-DD) |
| `tags` | string[] | No | Array of tag names for organization (max 20) |
| `checklistItems` | string[] | No | Array of sub-task descriptions (max 100) |
| `projectId` | string | No | Specific project ID to add task to |
| `projectName` | string | No | Project name (Things will find by name) |
| `areaId` | string | No | Specific area ID for assignment |
| `areaName` | string | No | Area name (e.g., "Work", "Personal") |
| `headingId` | string | No | Specific heading ID within project |
| `headingName` | string | No | Heading name within project |
| `completed` | boolean | No | Mark as completed immediately (default: false) |
| `canceled` | boolean | No | Mark as canceled immediately (default: false) |
| `creationDate` | string | No | Override creation date (ISO8601 format) |
| `completionDate` | string | No | Set completion date when `completed` is true |

### Example Usage

```typescript
// Basic task
{
  "title": "Call dentist for appointment",
  "when": "tomorrow",
  "deadline": "2024-01-15"
}

// Task with full organization
{
  "title": "Review quarterly budget",
  "notes": "Focus on Q4 expenses and Q1 projections",
  "when": "today",
  "deadline": "2024-01-10",
  "tags": ["urgent", "finance"],
  "areaName": "Work",
  "checklistItems": [
    "Gather expense reports",
    "Analyze trends",
    "Prepare recommendations"
  ]
}
```

### Response

```json
{
  "content": [{
    "type": "text",
    "text": "Successfully created to-do: Call dentist for appointment"
  }]
}
```

---

## add_project

Create a new project in Things.app with optional initial tasks and full organization.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | Yes | Project name and description |
| `notes` | string | No | Project details and context (max 10,000 characters) |
| `when` | string | No | Project start schedule (same options as `add_todo`) |
| `deadline` | string | No | Project deadline in ISO date format |
| `tags` | string[] | No | Project tags for organization |
| `areaId` | string | No | Area ID to assign project to |
| `areaName` | string | No | Area name for assignment |
| `initialTodos` | string[] | No | Array of initial task titles (max 100) |
| `completed` | boolean | No | Mark as completed immediately |
| `canceled` | boolean | No | Mark as canceled immediately |

### Example Usage

```typescript
// Project with initial tasks
{
  "title": "Website Redesign",
  "notes": "Complete overhaul of company website with modern design",
  "when": "2024-01-08",
  "deadline": "2024-03-01",
  "areaName": "Work",
  "tags": ["design", "development"],
  "initialTodos": [
    "Research competitor websites",
    "Create wireframes",
    "Design mockups",
    "Develop front-end",
    "User testing"
  ]
}
```

---

## update_todo

Modify existing tasks in Things.app. Requires authorization token.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | Unique identifier of the task to update |
| `title` | string | No | New task title |
| `notes` | string | No | Update notes content |
| `when` | string | No | Reschedule the task |
| `deadline` | string | No | Update deadline |
| `tags` | string[] | No | Replace all tags |
| `completed` | boolean | No | Mark as completed/incomplete |
| `canceled` | boolean | No | Mark as canceled/active |
| `checklistItems` | string[] | No | Replace checklist items |
| `appendNotes` | string | No | Add text to end of existing notes |
| `prependNotes` | string | No | Add text to beginning of notes |

### Example Usage

```typescript
// Complete a task
{
  "id": "task-uuid-here",
  "completed": true
}

// Reschedule and add notes
{
  "id": "task-uuid-here", 
  "when": "tomorrow",
  "appendNotes": "\n\nPostponed due to client feedback"
}
```

---

## update_project

Modify existing projects in Things.app. Requires authorization token.

### Parameters

Similar to `update_todo` but for projects, with additional project-specific operations.

---

## things_summary

Generate comprehensive summaries of your Things database with advanced filtering.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `format` | string | No | Output format: `markdown` (default) or `json` |
| `includeCompleted` | boolean | No | Include completed items (default: false) |
| `areas` | string[] | No | Filter by specific area names |
| `tags` | string[] | No | Filter by specific tag names |
| `projects` | string[] | No | Filter by specific project names |

### Example Usage

```typescript
// Basic summary
{
  "format": "markdown"
}

// Filtered summary
{
  "format": "json",
  "areas": ["Work", "Personal"],
  "tags": ["urgent"],
  "includeCompleted": true
}
```

### Response Format

#### Markdown Format
Returns a comprehensive, readable summary with:
- Overview statistics
- Today's tasks (prioritized)
- Inbox items
- Areas with projects and tasks
- Standalone projects
- Active tags
- Quick navigation links

#### JSON Format
Returns structured data perfect for programmatic processing:

```json
{
  "summary": {
    "totalOpenTasks": 42,
    "totalActiveProjects": 8,
    "totalAreas": 4,
    "totalTags": 12,
    "lastUpdated": "2024-01-08T10:30:00.000Z"
  },
  "areas": [...],
  "inboxTasks": [...],
  "todayTasks": [...],
  "projects": [...],
  "tags": [...],
  "urls": {
    "showToday": "things:///show?list=today",
    "showInbox": "things:///show?list=inbox",
    "showProjects": "things:///show?list=projects",
    "showAreas": "things:///show?list=areas"
  }
}
```

---

## export_json

Export your complete Things database as structured JSON for backup, analysis, or migration.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `includeCompleted` | boolean | No | Include completed items (default: false) |
| `includeTrashed` | boolean | No | Include trashed items (default: false) |
| `minimal` | boolean | No | Export minimal data only (default: false) |

### Example Usage

```typescript
// Full export for backup
{
  "includeCompleted": true,
  "includeTrashed": true
}

// Active items only
{
  "minimal": true
}
```

---

## Common Patterns

### Date Formats

**ISO Date Format**: `YYYY-MM-DD` (e.g., `2024-01-15`)
**ISO DateTime Format**: `YYYY-MM-DDTHH:MM:SS` (e.g., `2024-01-15T14:30:00`)

### Schedule Values

- `today` - Add to Today list
- `tomorrow` - Schedule for tomorrow
- `evening` - Add to This Evening  
- `anytime` - No specific schedule
- `someday` - Add to Someday list
- `YYYY-MM-DD` - Specific date

### Error Handling

All tools return structured error responses:

```json
{
  "error": {
    "type": "ValidationError",
    "message": "Invalid date format",
    "details": "Expected YYYY-MM-DD format"
  }
}
```

### Authorization

Update operations require the `THINGS_AUTH_TOKEN` environment variable:
1. Enable Things URLs in Things.app Preferences
2. Copy the authorization token
3. Set as environment variable in your MCP configuration

### Performance Notes

- `things_summary` accesses the SQLite database directly for fast queries
- Creation tools use URL scheme for immediate response
- Update tools use hybrid approach (JSON + URL scheme) for reliability
- All operations are designed for minimal latency and maximum compatibility
```

--------------------------------------------------------------------------------
/docs/troubleshooting.md:
--------------------------------------------------------------------------------

```markdown
# Troubleshooting

Common issues and solutions for the Things MCP Server.

## Installation Issues

### npm Package Not Found

**Problem**: `npm: package '@wenbopan/things-mcp' not found`

**Solutions**:
```bash
# Clear npm cache
npm cache clean --force

# Use full package name
npx @wenbopan/things-mcp

# Check npm registry
npm config get registry
npm config set registry https://registry.npmjs.org/
```

### Permission Errors

**Problem**: Permission denied when installing globally

**Solutions**:
```bash
# Use npx instead of global install
npx @wenbopan/things-mcp

# Or fix npm permissions (macOS)
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

# Or use node version manager
brew install nvm
nvm install node
```

## Configuration Issues

### MCP Server Not Loading

**Problem**: AI assistant doesn't recognize the MCP server

**Solutions**:

1. **Check configuration file location**:
   ```bash
   # Claude Desktop
   ls ~/Library/Application\ Support/Claude/claude_desktop_config.json
   
   # Cursor IDE
   ls ~/.cursor/mcp.json
   ls .cursor/mcp.json
   ```

2. **Validate JSON syntax**:
   ```bash
   # Check for JSON errors
   cat ~/.config/claude/config.json | jq .
   ```

3. **Verify configuration format**:
   ```json
   {
     "mcpServers": {
       "things": {
         "command": "npx",
         "args": ["@wenbopan/things-mcp"],
         "env": {
           "THINGS_AUTH_TOKEN": "your-token-here"
         }
       }
     }
   }
   ```

4. **Restart AI assistant completely**:
   - Quit application entirely
   - Wait 5 seconds
   - Restart application

### Authorization Token Issues

**Problem**: "THINGS_AUTH_TOKEN required" error

**Solutions**:

1. **Enable Things URLs**:
   - Open Things.app
   - Go to **Things → Preferences → General**
   - Check **"Enable Things URLs"**
   - Copy the authorization token

2. **Set token in configuration**:
   ```json
   {
     "env": {
       "THINGS_AUTH_TOKEN": "your-actual-token-here"
     }
   }
   ```

3. **Verify token format**:
   - Token should be a long alphanumeric string
   - No spaces or special characters
   - Generated fresh from Things.app preferences

## Runtime Issues

### "Things.app not found" Error

**Problem**: Cannot locate Things.app on system

**Solutions**:

1. **Install Things.app**:
   ```bash
   # Check if Things is installed
   ls /Applications/Things3.app
   
   # Download from Mac App Store if missing
   open "macappstore://apps.apple.com/app/things-3/id904280696"
   ```

2. **Launch Things.app at least once**:
   - Open Things.app manually
   - Allow it to create database
   - Close and try MCP operation again

### Database Access Errors

**Problem**: "Things database not found" or permission errors

**Solutions**:

1. **Check database location**:
   ```bash
   # Find Things database
   find ~/Library/Group\ Containers -name "*ThingsMac*" -type d 2>/dev/null
   ```

2. **Ensure Things.app has run**:
   - Launch Things.app
   - Create at least one task
   - Quit Things.app
   - Try MCP operation

3. **Check permissions**:
   ```bash
   # Terminal needs Full Disk Access for database reading
   # System Preferences → Security & Privacy → Privacy → Full Disk Access
   # Add Terminal.app or your AI assistant
   ```

### Platform Compatibility

**Problem**: "macOS required" error on non-Mac systems

**Explanation**: Things.app is macOS-only, so the MCP server only functions on macOS.

**Workarounds**:
- Use macOS virtual machine
- Use remote macOS development environment
- Consider alternative task management MCP servers

## Tool-Specific Issues

### Task Creation Fails

**Problem**: Tasks not appearing in Things.app

**Debugging Steps**:

1. **Test URL scheme manually**:
   ```bash
   open "things:///add?title=Test%20Task"
   ```

2. **Check Things.app is responding**:
   - Ensure Things.app is running
   - Try creating task manually in Things.app
   - Check for system dialog requesting permissions

3. **Verify parameters**:
   ```typescript
   // Valid date formats
   "when": "today"           // ✓ Valid
   "when": "2024-01-15"      // ✓ Valid
   "when": "next week"       // ✗ Invalid
   
   // Valid deadline format
   "deadline": "2024-01-15"  // ✓ Valid
   "deadline": "tomorrow"    // ✗ Invalid
   ```

### Update Operations Fail

**Problem**: Cannot update existing tasks

**Debugging Steps**:

1. **Verify authorization token**:
   ```bash
   echo $THINGS_AUTH_TOKEN
   ```

2. **Check task ID format**:
   ```typescript
   // Valid UUID format
   "id": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
   ```

3. **Test with things_summary first**:
   ```typescript
   // Get valid task IDs
   {
     "format": "json"
   }
   ```

### Summary Tool Issues

**Problem**: Empty or incomplete summaries

**Debugging Steps**:

1. **Check database permissions**:
   ```bash
   # Test SQLite access
   find ~/Library/Group\ Containers -name "main.sqlite" -exec sqlite3 {} "SELECT COUNT(*) FROM TMTask;" \;
   ```

2. **Verify Things data exists**:
   - Open Things.app
   - Confirm tasks and projects exist
   - Check that items aren't all completed

3. **Test with different filters**:
   ```typescript
   // Include completed items
   {
     "includeCompleted": true,
     "format": "json"
   }
   ```

## AI Assistant Integration Issues

### Claude Desktop

**Problem**: MCP tools not appearing

**Solutions**:
1. Check config file location: `~/Library/Application Support/Claude/claude_desktop_config.json`
2. Ensure JSON is valid
3. Restart Claude Desktop completely
4. Check Claude Desktop logs for errors

### Cursor IDE

**Problem**: MCP server not connecting

**Solutions**:
1. Create `.cursor/mcp.json` in project root or globally
2. Verify Cursor has MCP support enabled
3. Check Cursor console for connection errors
4. Try reloading Cursor window

## Performance Issues

### Slow Summary Generation

**Problem**: Summary takes too long to generate

**Solutions**:

1. **Filter data**:
   ```typescript
   {
     "areas": ["Work"],           // Limit to specific areas
     "includeCompleted": false    // Exclude completed items
   }
   ```

2. **Use JSON format for large datasets**:
   ```typescript
   {
     "format": "json"  // Faster than markdown formatting
   }
   ```

3. **Check database size**:
   ```bash
   # Find large Things databases
   find ~/Library/Group\ Containers -name "main.sqlite" -exec du -h {} \;
   ```

### Memory Usage

**Problem**: High memory consumption

**Solutions**:
- Restart MCP server periodically
- Use filtered queries instead of full exports
- Monitor Node.js process memory usage

## Development Issues

### Build Errors

**Problem**: TypeScript compilation fails

**Solutions**:
```bash
# Clean build
rm -rf build/
npm run build

# Check TypeScript version
npx tsc --version

# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install
```

### Test Failures

**Problem**: Tests failing on macOS

**Solutions**:
```bash
# Run specific test
npm test -- --testNamePattern="URL Builder"

# Check Things.app installation
ls /Applications/Things3.app

# Verify test database access
npm test -- tests/integration.test.ts --verbose
```

### ES Module Issues

**Problem**: Import/export errors

**Solutions**:
```typescript
// Use .js extensions for TypeScript imports
import { tool } from './utils/tool.js';  // ✓ Correct
import { tool } from './utils/tool';     // ✗ Wrong

// Check package.json
"type": "module"  // Required for ES modules
```

## Getting Additional Help

### Debug Mode

Enable verbose logging:
```bash
export LOG_LEVEL=debug
npx @wenbopan/things-mcp
```

### System Information

Collect system info for bug reports:
```bash
# System details
sw_vers
node --version
npm --version

# Things.app version
system_profiler SPApplicationsDataType | grep -A 3 "Things"

# Database location
find ~/Library/Group\ Containers -name "*ThingsMac*" 2>/dev/null
```

### Reporting Issues

When reporting issues, include:

1. **System Information**:
   - macOS version
   - Node.js version
   - Things.app version
   - AI assistant type and version

2. **Configuration**:
   - MCP configuration (remove sensitive tokens)
   - Environment variables
   - Installation method

3. **Error Details**:
   - Complete error message
   - Steps to reproduce
   - Expected vs actual behavior

4. **Debug Output**:
   - Enable debug logging
   - Include relevant log entries
   - Sanitize personal information

### Support Channels

- **GitHub Issues**: [https://github.com/BMPixel/things-mcp/issues](https://github.com/BMPixel/things-mcp/issues)
- **Documentation**: [https://github.com/BMPixel/things-mcp/docs](https://github.com/BMPixel/things-mcp/docs)
- **MCP Community**: Model Context Protocol community resources

### Emergency Recovery

If the MCP server becomes completely unresponsive:

1. **Remove from AI assistant configuration**
2. **Restart AI assistant**
3. **Clear npm cache**: `npm cache clean --force`
4. **Reinstall**: `npm uninstall -g @wenbopan/things-mcp && npx @wenbopan/things-mcp`
5. **Reconfigure with minimal settings**
```

--------------------------------------------------------------------------------
/docs/roadmap.md:
--------------------------------------------------------------------------------

```markdown
# Roadmap

Future development plans and feature roadmap for the Things MCP Server.

## Current Status

**Version**: 1.0.0  
**Stability**: Production Ready  
**Platform**: macOS only  
**AI Assistants**: Claude Desktop, Cursor IDE, and other MCP-compatible clients

## Short Term (Next 3 Months)

### Enhanced Task Management
- **Smart Scheduling**: AI-powered task scheduling suggestions based on workload and deadlines
- **Batch Operations**: Support for bulk task creation, updates, and management
- **Template System**: Pre-defined project and task templates for common workflows
- **Quick Actions**: Streamlined commands for frequent operations (defer, reschedule, delegate)

### Improved Data Access
- **Advanced Filtering**: More sophisticated filtering options (date ranges, priority levels, effort estimates)
- **Custom Views**: Saved filter combinations for personalized task views
- **Search Capabilities**: Full-text search across tasks, projects, and notes
- **Statistics Dashboard**: Task completion rates, productivity metrics, and insights

### Developer Experience
- **Enhanced Debugging**: Better error messages and debugging tools
- **Performance Optimization**: Faster database queries and reduced memory usage
- **Documentation Expansion**: Video tutorials and interactive examples
- **Developer API**: Programmatic access for third-party integrations

## Medium Term (3-6 Months)

### Cross-Platform Foundation
- **Architecture Refactoring**: Abstract interfaces for future platform support
- **Protocol Abstraction**: Generic task management protocol beyond Things.app
- **Configuration System**: Flexible configuration for different task managers
- **Plugin Architecture**: Support for extending functionality with custom plugins

### AI-Powered Features
- **Natural Language Processing**: Better understanding of complex task descriptions
- **Intelligent Categorization**: Automatic tagging and area assignment
- **Predictive Scheduling**: Machine learning for optimal task timing
- **Context Awareness**: Integration with calendar and other productivity tools

### Collaboration Features
- **Shared Projects**: Support for team projects and shared task lists
- **Assignment System**: Task delegation and responsibility tracking
- **Progress Notifications**: Automated status updates and reminders
- **Integration Hub**: Connect with popular collaboration tools (Slack, Teams, etc.)

## Long Term (6-12 Months)

### Multi-Platform Support
- **Windows Integration**: Support for alternative task managers on Windows
- **Linux Compatibility**: Integration with open-source task management solutions
- **Web-Based Tasks**: Support for web-based task management platforms
- **Mobile Integration**: Indirect mobile support through cloud synchronization

### Advanced Automation
- **Workflow Engine**: Complex task automation and conditional logic
- **Time Tracking**: Automatic time tracking and productivity analysis
- **Smart Reminders**: Context-aware reminders based on location, time, and workload
- **Habit Tracking**: Integration with habit formation and routine management

### Enterprise Features
- **Team Management**: Multi-user support and team productivity insights
- **Reporting System**: Advanced analytics and reporting capabilities
- **Compliance Tools**: Integration with enterprise compliance and audit requirements
- **Single Sign-On**: Enterprise authentication and access control

## Feature Requests

### Community-Driven Features

Based on user feedback and community requests:

#### High Priority
- **Voice Input**: Support for voice-to-task conversion
- **Calendar Integration**: Bi-directional sync with calendar applications
- **File Attachments**: Support for attaching files and documents to tasks
- **Recurring Tasks**: Advanced recurring task patterns and management

#### Medium Priority
- **Time Boxing**: Pomodoro timer integration and time boxing support
- **Goal Tracking**: Long-term goal management and progress tracking
- **Mood Tracking**: Emotional state tracking related to productivity
- **Weather Integration**: Weather-aware scheduling and task suggestions

#### Low Priority
- **Gamification**: Achievement system and productivity rewards
- **Social Features**: Task sharing and productivity social networking
- **IoT Integration**: Smart home and device integration for task triggers
- **Augmented Reality**: AR interfaces for spatial task management

## Technical Roadmap

### Architecture Evolution

#### Phase 1: Stabilization
- **Error Handling**: Comprehensive error handling and recovery
- **Performance Optimization**: Database query optimization and caching
- **Test Coverage**: Increase test coverage to 95%+
- **Documentation**: Complete API documentation and tutorials

#### Phase 2: Extensibility
- **Plugin System**: Modular plugin architecture for custom functionality
- **Event System**: Event-driven architecture for real-time updates
- **API Gateway**: RESTful API for external integrations
- **Webhook Support**: Outbound notifications and integrations

#### Phase 3: Scalability
- **Microservices**: Break down into focused microservices
- **Cloud Integration**: Cloud-based synchronization and backup
- **Load Balancing**: Support for high-availability deployments
- **Monitoring**: Comprehensive logging, metrics, and alerting

### Technology Roadmap

#### Current Stack Evolution
- **TypeScript 5.0+**: Upgrade to latest TypeScript features
- **Node.js LTS**: Support for latest Node.js long-term support versions
- **Database Optimization**: SQLite performance improvements and indexing
- **Security Hardening**: Enhanced security measures and audit compliance

#### New Technologies
- **WebAssembly**: High-performance modules for complex operations
- **GraphQL**: Flexible query interface for advanced data access
- **Real-time Sync**: WebSocket support for live updates
- **AI/ML Integration**: Machine learning for intelligent task management

## Version Milestones

### v1.1.0 - Enhanced Productivity (Q2 2024)
- Batch operations for multiple tasks
- Advanced filtering and search
- Template system for common workflows
- Performance optimizations

### v1.2.0 - Smart Features (Q3 2024)
- AI-powered scheduling suggestions
- Intelligent task categorization
- Natural language processing improvements
- Statistics and analytics dashboard

### v1.3.0 - Collaboration (Q4 2024)
- Shared project support
- Team productivity features
- Integration with popular tools
- Mobile-friendly improvements

### v2.0.0 - Multi-Platform (Q1 2025)
- Cross-platform architecture
- Support for alternative task managers
- Plugin system launch
- Major API redesign

## Contributing to the Roadmap

### How to Influence Development

1. **GitHub Issues**: Submit feature requests and bug reports
2. **Community Discussions**: Participate in roadmap discussions
3. **Pull Requests**: Contribute code for priority features
4. **User Feedback**: Share usage patterns and pain points

### Feature Request Process

1. **Research**: Check existing issues and documentation
2. **Proposal**: Create detailed feature request with use cases
3. **Discussion**: Community feedback and technical feasibility
4. **Implementation**: Development prioritization and timeline
5. **Testing**: Beta testing with community volunteers

### Development Priorities

Priority factors for new features:

1. **User Impact**: Number of users who would benefit
2. **Technical Feasibility**: Implementation complexity and maintainability
3. **Platform Alignment**: Compatibility with Things.app and MCP standards
4. **Community Support**: Level of community interest and contribution
5. **Strategic Value**: Alignment with long-term project goals

## Success Metrics

### User Adoption
- **Active Users**: Monthly active users of the MCP server
- **AI Assistant Integration**: Number of supported AI assistants
- **Community Growth**: GitHub stars, contributors, and community engagement

### Technical Excellence
- **Performance**: Response time and resource efficiency metrics
- **Reliability**: Uptime, error rates, and user satisfaction
- **Code Quality**: Test coverage, documentation completeness, and maintainability

### Feature Completion
- **Roadmap Progress**: Percentage of planned features delivered on time
- **User Satisfaction**: Feature usage rates and user feedback scores
- **Innovation Index**: Introduction of novel AI-powered productivity features

## Feedback and Contact

### Stay Updated
- **GitHub Releases**: Watch repository for release notifications
- **Changelog**: Regular updates on feature progress and releases
- **Community Forum**: Participate in roadmap discussions

### Contact Information
- **Issues**: [GitHub Issues](https://github.com/BMPixel/things-mcp/issues)
- **Discussions**: [GitHub Discussions](https://github.com/BMPixel/things-mcp/discussions)
- **Email**: Project maintainer contact for strategic partnerships

---

*This roadmap is a living document that evolves based on user feedback, technical constraints, and strategic priorities. Timelines are estimates and may adjust based on development progress and community needs.*
```

--------------------------------------------------------------------------------
/src/tools/export-json.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { execSync } from 'child_process';
import { existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { logger } from '../utils/logger.js';

const exportJsonSchema = z.object({
  includeCompleted: z.boolean()
    .optional()
    .default(false)
    .describe('Include completed/canceled tasks and projects in the export (default: false)'),
  includeTrash: z.boolean()
    .optional()
    .default(false)
    .describe('Include trashed items in the export (default: false). Use with caution as this includes deleted data'),
  minimal: z.boolean()
    .optional()
    .default(false)
    .describe('Export minimal data structure with only essential fields (default: false). Reduces output size for processing'),
  prettify: z.boolean()
    .optional()
    .default(true)
    .describe('Pretty-print JSON with indentation (default: true). Set to false for compact single-line output')
});

function findThingsDatabase(): string {
  const homeDir = process.env.HOME || '/Users/' + process.env.USER;
  const thingsGroupContainer = join(homeDir, 'Library/Group Containers');
  
  if (!existsSync(thingsGroupContainer)) {
    throw new Error('Things group container not found. Please ensure Things.app is installed on macOS.');
  }
  
  const containers = readdirSync(thingsGroupContainer);
  const thingsContainer = containers.find(dir => 
    dir.includes('JLMPQHK86H.com.culturedcode.ThingsMac')
  );
  
  if (!thingsContainer) {
    throw new Error('Things container not found. Please ensure Things.app is installed and has been launched at least once.');
  }
  
  const containerPath = join(thingsGroupContainer, thingsContainer);
  const contents = readdirSync(containerPath);
  const thingsDataDir = contents.find(dir => dir.startsWith('ThingsData-'));
  
  if (!thingsDataDir) {
    throw new Error('ThingsData directory not found.');
  }
  
  const dbPath = join(containerPath, thingsDataDir, 'Things Database.thingsdatabase', 'main.sqlite');
  
  if (!existsSync(dbPath)) {
    throw new Error('Things database file not found.');
  }
  
  return dbPath;
}

function executeSqlQuery(dbPath: string, query: string): any[] {
  try {
    const result = execSync(`sqlite3 "${dbPath}" "${query}"`, { 
      encoding: 'utf8',
      maxBuffer: 50 * 1024 * 1024 // 50MB for large exports
    });
    
    if (!result.trim()) {
      return [];
    }
    
    return result.trim().split('\n').map(row => {
      return row.split('|');
    });
  } catch (error) {
    logger.error('SQL Query failed', { error: error instanceof Error ? error.message : error, query });
    return [];
  }
}

function formatDate(timestamp: string): string | null {
  if (!timestamp || timestamp === '' || timestamp === 'NULL') {
    return null;
  }
  
  try {
    const date = new Date((parseFloat(timestamp) + 978307200) * 1000);
    return date.toISOString();
  } catch {
    return null;
  }
}

function exportThingsData(params: any): any {
  const dbPath = findThingsDatabase();
  logger.info('Exporting Things database', { path: dbPath, params });
  
  // Build status filters
  let statusFilter = 'status = 0 AND trashed = 0'; // Open items only
  if (params.includeCompleted && params.includeTrash) {
    statusFilter = '1=1'; // All items
  } else if (params.includeCompleted) {
    statusFilter = 'trashed = 0'; // All non-trashed
  } else if (params.includeTrash) {
    statusFilter = 'status = 0'; // Open and trashed
  }
  
  const export_data: any = {
    export_info: {
      exported_at: new Date().toISOString(),
      source: 'Things MCP Server',
      database_path: dbPath,
      filters: {
        include_completed: params.includeCompleted,
        include_trash: params.includeTrash,
        minimal: params.minimal
      }
    }
  };
  
  // Export Areas
  const areasData = executeSqlQuery(dbPath, "SELECT uuid, title, visible, index1 FROM TMArea ORDER BY index1");
  export_data.areas = areasData.map(row => {
    const area: any = {
      id: row[0],
      title: row[1] || null,
      visible: row[2] === '1',
      sort_order: parseInt(row[3]) || 0
    };
    
    if (params.minimal) {
      return { id: area.id, title: area.title };
    }
    return area;
  });
  
  // Export Tags
  const tagsData = executeSqlQuery(dbPath, "SELECT uuid, title, shortcut, index1 FROM TMTag ORDER BY index1");
  export_data.tags = tagsData.map(row => {
    const tag: any = {
      id: row[0],
      title: row[1] || null,
      shortcut: row[2] || null,
      sort_order: parseInt(row[3]) || 0
    };
    
    if (params.minimal) {
      return { id: tag.id, title: tag.title };
    }
    return tag;
  });
  
  // Export Tasks and Projects
  const tasksQuery = params.minimal 
    ? `SELECT uuid, title, type, status, trashed, area, project FROM TMTask WHERE ${statusFilter} ORDER BY creationDate`
    : `SELECT uuid, title, notes, type, status, trashed, creationDate, userModificationDate, startDate, deadline, completionDate, area, project, checklistItemsCount, openChecklistItemsCount, index1 FROM TMTask WHERE ${statusFilter} ORDER BY creationDate`;
  
  const tasksData = executeSqlQuery(dbPath, tasksQuery);
  export_data.tasks = tasksData.map(row => {
    if (params.minimal) {
      return {
        id: row[0],
        title: row[1] || null,
        type: parseInt(row[2]) || 0, // 0=task, 1=project, 2=heading
        status: parseInt(row[3]) || 0, // 0=open, 3=completed, 2=canceled
        trashed: row[4] === '1',
        area_id: row[5] || null,
        project_id: row[6] || null
      };
    }
    
    const task: any = {
      id: row[0],
      title: row[1] || null,
      notes: row[2] || null,
      type: parseInt(row[3]) || 0,
      status: parseInt(row[4]) || 0,
      trashed: row[5] === '1',
      creation_date: formatDate(row[6]),
      modification_date: formatDate(row[7]),
      start_date: formatDate(row[8]),
      deadline: formatDate(row[9]),
      completion_date: formatDate(row[10]),
      area_id: row[11] || null,
      project_id: row[12] || null,
      checklist_items_total: parseInt(row[13]) || 0,
      checklist_items_open: parseInt(row[14]) || 0,
      sort_order: parseInt(row[15]) || 0
    };
    
    return task;
  });
  
  // Export Task-Tag relationships
  const taskTagData = executeSqlQuery(dbPath, "SELECT tasks, tags FROM TMTaskTag");
  export_data.task_tags = taskTagData.map(row => ({
    task_id: row[0],
    tag_id: row[1]
  }));
  
  // Export Checklist Items (if not minimal)
  if (!params.minimal) {
    const checklistQuery = `SELECT uuid, title, status, creationDate, task FROM TMChecklistItem WHERE task IN (SELECT uuid FROM TMTask WHERE ${statusFilter}) ORDER BY creationDate`;
    const checklistData = executeSqlQuery(dbPath, checklistQuery);
    export_data.checklist_items = checklistData.map(row => ({
      id: row[0],
      title: row[1] || null,
      status: parseInt(row[2]) || 0, // 0=open, 3=completed
      creation_date: formatDate(row[3]),
      task_id: row[4]
    }));
  }
  
  // Add summary statistics
  export_data.summary = {
    total_areas: export_data.areas.length,
    total_tags: export_data.tags.length,
    total_items: export_data.tasks.length,
    total_tasks: export_data.tasks.filter((t: any) => t.type === 0).length,
    total_projects: export_data.tasks.filter((t: any) => t.type === 1).length,
    total_headings: export_data.tasks.filter((t: any) => t.type === 2).length,
    open_items: export_data.tasks.filter((t: any) => t.status === 0 && !t.trashed).length,
    completed_items: export_data.tasks.filter((t: any) => t.status === 3).length,
    trashed_items: export_data.tasks.filter((t: any) => t.trashed).length
  };
  
  if (!params.minimal) {
    export_data.summary.total_checklist_items = export_data.checklist_items?.length || 0;
    export_data.summary.total_task_tag_relationships = export_data.task_tags.length;
  }
  
  return export_data;
}

export function registerExportJsonTool(server: McpServer): void {
  server.tool(
    'export_json',
    'Export complete Things database as structured JSON for debugging, backup, or data processing.',
    exportJsonSchema.shape,
    async (params) => {
      try {
        // Validate macOS platform
        if (process.platform !== 'darwin') {
          throw new Error('Things database access is only available on macOS');
        }

        logger.info('Exporting Things database to JSON', { 
          includeCompleted: params.includeCompleted,
          includeTrash: params.includeTrash,
          minimal: params.minimal,
          prettify: params.prettify
        });
        
        const data = exportThingsData(params);
        const jsonOutput = params.prettify ? 
          JSON.stringify(data, null, 2) : 
          JSON.stringify(data);
        
        return {
          content: [{
            type: "text",
            text: jsonOutput
          }]
        };
      } catch (error) {
        logger.error('Failed to export Things database', { 
          error: error instanceof Error ? error.message : error 
        });
        throw error;
      }
    }
  );
}
```

--------------------------------------------------------------------------------
/tests/integration.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeAll } from '@jest/globals';
import { buildThingsUrl, openThingsUrl } from '../src/utils/url-builder.js';
import { exec } from 'child_process';
import { promisify } from 'util';
import { verifyItemExists, verifyItemCompleted, verifyItemUpdated } from '../src/utils/verification.js';
import { executeJsonOperation, waitForOperation } from '../src/utils/json-operations.js';
import { existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { execSync } from 'child_process';

const execAsync = promisify(exec);

function findThingsDatabase(): string {
  const homeDir = process.env.HOME || '/Users/' + process.env.USER;
  const thingsGroupContainer = join(homeDir, 'Library/Group Containers');
  
  if (!existsSync(thingsGroupContainer)) {
    throw new Error('Things group container not found. Please ensure Things.app is installed on macOS.');
  }
  
  const containers = readdirSync(thingsGroupContainer);
  const thingsContainer = containers.find(dir => 
    dir.includes('JLMPQHK86H.com.culturedcode.ThingsMac')
  );
  
  if (!thingsContainer) {
    throw new Error('Things container not found. Please ensure Things.app is installed and has been launched at least once.');
  }
  
  const containerPath = join(thingsGroupContainer, thingsContainer);
  const contents = readdirSync(containerPath);
  const thingsDataDir = contents.find(dir => dir.startsWith('ThingsData-'));
  
  if (!thingsDataDir) {
    throw new Error('ThingsData directory not found.');
  }
  
  const dbPath = join(containerPath, thingsDataDir, 'Things Database.thingsdatabase', 'main.sqlite');
  
  if (!existsSync(dbPath)) {
    throw new Error('Things database file not found.');
  }
  
  return dbPath;
}

function executeSqlQuery(dbPath: string, query: string): any[] {
  try {
    const result = execSync(`sqlite3 "${dbPath}" "${query}"`, { 
      encoding: 'utf8',
      maxBuffer: 10 * 1024 * 1024 // 10MB buffer
    });
    
    if (!result.trim()) {
      return [];
    }
    
    return result.trim().split('\n').map(row => {
      return row.split('|');
    });
  } catch (error) {
    console.error('SQL Query failed', { error: error instanceof Error ? error.message : error, query });
    return [];
  }
}

async function cleanupTestItems(authToken: string): Promise<void> {
  try {
    const dbPath = findThingsDatabase();
    // Find all test items (todos and projects) created during testing
    const testItemsQuery = `
      SELECT uuid, type, title FROM TMTask 
      WHERE (title LIKE '%integration-test-%' OR title LIKE '%Test Todo%' OR title LIKE '%Test Project%' OR title LIKE '%deadline-test-%')
      AND status IN (0, 3)
      AND trashed = 0
      ORDER BY creationDate DESC
      LIMIT 50
    `;
    const testItems = executeSqlQuery(dbPath, testItemsQuery);
    
    if (testItems.length === 0) {
      console.log('✅ No test items to clean up');
      return;
    }
    
    console.log(`Found ${testItems.length} test items to clean up`);
    
    for (let i = 0; i < testItems.length; i++) {
      const item = testItems[i];
      const itemId = item[0];
      const itemType = parseInt(item[1]); // 0=todo, 1=project
      const itemTitle = item[2];
      
      try {
        const itemTypeString = itemType === 1 ? 'project' : 'to-do';
        const deleteOperation = {
          type: itemTypeString as 'project' | 'to-do',
          operation: 'update' as const,
          id: itemId,
          attributes: {
            canceled: true
          }
        };
        
        await executeJsonOperation(deleteOperation, authToken);
        console.log(`✅ Cleaned up ${itemTypeString}: ${itemTitle}`);
        
        // Only wait between operations, not after the last one
        if (i < testItems.length - 1) {
          await waitForOperation(100);
        }
      } catch (error) {
        console.log(`⚠️ Failed to clean up ${itemTitle}:`, error);
      }
    }
    
    console.log(`✅ Cleanup complete - processed ${testItems.length} items`);
  } catch (error) {
    console.log('⚠️ Error during cleanup:', error);
  }
}

describe('Things Integration Tests', () => {
  let canRunIntegrationTests = false;
  const authToken = process.env.THINGS_AUTH_TOKEN;

  beforeAll(async () => {
    // Skip all tests if not on macOS
    if (process.platform !== 'darwin') {
      console.log('⚠️ Skipping integration tests - macOS required');
      return;
    }

    // Check if Things URL scheme is available by testing version command
    try {
      await execAsync('open "things:///version"');
      // Wait a moment for the command to execute
      await new Promise(resolve => setTimeout(resolve, 1000));
      canRunIntegrationTests = true;
      console.log('✅ Things URL scheme is available');
    } catch (error) {
      console.log('⚠️ Things URL scheme not available - skipping integration tests');
      console.log('To enable: Things.app → Preferences → General → Enable Things URLs');
    }
  });

  afterAll(async () => {
    // Clean up any remaining test items after all tests complete
    if (canRunIntegrationTests && authToken) {
      console.log('🧹 Running test cleanup...');
      await cleanupTestItems(authToken);
    }
  }, 10000); // 10 second timeout for cleanup

  describe('Todo Lifecycle (Create → Update → Complete)', () => {
    const testTodoId = `integration-test-${Date.now()}`;

    it('should create, update, and complete a todo', async () => {
      if (!canRunIntegrationTests) {
        console.log('Skipping - Things URL scheme not enabled');
        return;
      }

      let createdTodoId: string | null = null;

      // Step 1: Create the todo
      const createUrl = buildThingsUrl('add', {
        title: `Test Todo ${testTodoId}`,
        notes: 'Created by integration test\n\nThis will be updated and then completed',
        tags: 'test,integration',
        when: 'today',
        'checklist-items': 'Step 1\nStep 2\nStep 3'
      });

      expect(createUrl).toContain('things:///add');
      expect(createUrl).toContain('title=Test%20Todo');
      expect(createUrl).toContain('notes=Created%20by%20integration%20test');
      expect(createUrl).toContain('tags=test%2Cintegration');
      expect(createUrl).toContain('when=today');
      expect(createUrl).toContain('checklist-items=Step%201%0AStep%202%0AStep%203');

      await expect(openThingsUrl(createUrl)).resolves.not.toThrow();
      console.log('✅ Created test todo');
      
      // Wait and find the created todo in the database
      await waitForOperation(500);
      
      try {
        const dbPath = findThingsDatabase();
        const query = `SELECT uuid FROM TMTask WHERE title LIKE '%${testTodoId}%' AND type = 0 ORDER BY creationDate DESC LIMIT 1`;
        const result = executeSqlQuery(dbPath, query);
        if (result.length > 0) {
          createdTodoId = result[0][0];
          console.log(`✅ Found created todo ID: ${createdTodoId}`);
        } else {
          console.log('⚠️ Could not find created todo in database');
          return; // Exit test if we can't find the todo
        }
      } catch (error) {
        console.log('⚠️ Error finding created todo:', error);
        return; // Exit test on error
      }

      // Step 2: Update the todo (if auth token is available)
      if (authToken && createdTodoId) {
        const updateUrl = buildThingsUrl('update', {
          id: createdTodoId,
          'auth-token': authToken,
          'append-notes': '\n\nUpdated via integration test',
          'add-tags': 'updated',
          'append-checklist-items': 'Step 4,Step 5'
        });

        expect(updateUrl).toContain('things:///update');
        expect(updateUrl).toContain(`id=${createdTodoId}`);
        expect(updateUrl).toContain('auth-token=');
        expect(updateUrl).toContain('append-notes=');
        expect(updateUrl).toContain('add-tags=updated');

        await expect(openThingsUrl(updateUrl)).resolves.not.toThrow();
        
        // Verify the update worked
        const isUpdated = await verifyItemUpdated(createdTodoId);
        if (isUpdated) {
          console.log('✅ Updated and verified test todo');
        } else {
          console.log('⚠️ Todo updated but verification failed');
        }

        // Step 3: Complete the todo using JSON operation
        const operation = {
          type: 'to-do' as const,
          operation: 'update' as const,
          id: createdTodoId,
          attributes: {
            completed: true
          }
        };

        await expect(executeJsonOperation(operation, authToken)).resolves.not.toThrow();
        
        // Verify completion with longer wait
        const isCompleted = await verifyItemCompleted(createdTodoId, 1000); // Wait 1 second
        if (isCompleted) {
          console.log('✅ Completed and verified test todo');
        } else {
          console.log('⚠️ Todo completed but verification failed');
        }
      } else {
        console.log('⚠️ Skipping update and completion - no auth token available');
      }
    });
  });

  describe('Project Lifecycle (Create → Update → Complete)', () => {
    const testProjectId = `integration-test-project-${Date.now()}`;

    it('should create, update, and complete a project', async () => {
      if (!canRunIntegrationTests) {
        console.log('Skipping - Things URL scheme not enabled');
        return;
      }

      let createdProjectId: string | null = null;

      // Step 1: Create the project
      const createUrl = buildThingsUrl('add-project', {
        title: `Test Project ${testProjectId}`,
        notes: 'Created by integration test',
        tags: 'test,project',
        area: 'Work', // Will only assign if area exists
        'to-dos': 'Task 1\nTask 2\nTask 3'
      });

      expect(createUrl).toContain('things:///add-project');
      expect(createUrl).toContain('title=Test%20Project');
      expect(createUrl).toContain('to-dos=Task%201%0ATask%202%0ATask%203');
      
      await expect(openThingsUrl(createUrl)).resolves.not.toThrow();
      console.log('✅ Created test project');
      
      // Wait and find the created project in the database
      await waitForOperation(500);
      
      try {
        const dbPath = findThingsDatabase();
        const query = `SELECT uuid FROM TMTask WHERE title LIKE '%${testProjectId}%' AND type = 1 ORDER BY creationDate DESC LIMIT 1`;
        const result = executeSqlQuery(dbPath, query);
        if (result.length > 0) {
          createdProjectId = result[0][0];
          console.log(`✅ Found created project ID: ${createdProjectId}`);
        } else {
          console.log('⚠️ Could not find created project in database');
          return; // Exit test if we can't find the project
        }
      } catch (error) {
        console.log('⚠️ Error finding created project:', error);
        return; // Exit test on error
      }

      // Step 2: Update the project (if auth token is available)
      if (authToken && createdProjectId) {
        const updateUrl = buildThingsUrl('update-project', {
          id: createdProjectId,
          'auth-token': authToken,
          'append-notes': '\n\nProject updated via integration test',
          'add-tags': 'updated'
        });

        expect(updateUrl).toContain('things:///update-project');
        expect(updateUrl).toContain(`id=${createdProjectId}`);
        expect(updateUrl).toContain('auth-token=');

        await expect(openThingsUrl(updateUrl)).resolves.not.toThrow();
        
        // Verify the update worked
        const isUpdated = await verifyItemUpdated(createdProjectId);
        if (isUpdated) {
          console.log('✅ Updated and verified test project');
        } else {
          console.log('⚠️ Project updated but verification failed');
        }

        // Step 3: Complete all child tasks first, then complete the project
        try {
          const dbPath = findThingsDatabase();
          const childTasksQuery = `SELECT uuid FROM TMTask WHERE project = '${createdProjectId}' AND type = 0 AND status = 0`;
          const childTasks = executeSqlQuery(dbPath, childTasksQuery);
          
          for (const task of childTasks) {
            const taskId = task[0];
            const completeTaskOperation = {
              type: 'to-do' as const,
              operation: 'update' as const,
              id: taskId,
              attributes: { completed: true }
            };
            await executeJsonOperation(completeTaskOperation, authToken);
            await waitForOperation(100); // Small delay between operations
          }
          
          if (childTasks.length > 0) {
            console.log(`✅ Completed ${childTasks.length} child tasks`);
            await waitForOperation(500); // Wait for all child tasks to be processed
          }
        } catch (error) {
          console.log('⚠️ Error completing child tasks:', error);
        }
        
        // Now complete the project using JSON operation
        const operation = {
          type: 'project' as const,
          operation: 'update' as const,
          id: createdProjectId,
          attributes: {
            completed: true
          }
        };

        await expect(executeJsonOperation(operation, authToken)).resolves.not.toThrow();
        
        // Verify completion with longer wait
        const isCompleted = await verifyItemCompleted(createdProjectId, 1000); // Wait 1 second
        if (isCompleted) {
          console.log('✅ Completed and verified test project');
        } else {
          console.log('⚠️ Project completed but verification failed (may need all child tasks completed first)');
        }
      } else {
        console.log('⚠️ Skipping update and completion - no auth token available');
      }
    });
  });

  describe('JSON Import Operations', () => {
    it('should build JSON import URL', () => {
      if (!canRunIntegrationTests) {
        console.log('Skipping - Things URL scheme not enabled');
        return;
      }

      const testData = [
        {
          type: 'to-do',
          attributes: {
            title: 'JSON Test Todo',
            notes: 'Created via JSON import'
          }
        }
      ];

      const url = buildThingsUrl('json', {
        data: testData,
        reveal: true
      });

      expect(url).toContain('things:///json');
      expect(url).toContain('data=');
      expect(url).toContain('reveal=true');
    });
  });

  describe('Error Handling', () => {
    it('should handle missing required parameters', () => {
      // Test URL building with missing title
      const url = buildThingsUrl('add', {
        notes: 'Notes without title'
      });

      // URL should still be built (validation happens at the app level)
      expect(url).toContain('things:///add');
      expect(url).toContain('notes=Notes%20without%20title');
      expect(url).not.toContain('title=');
    });
  });

  describe('Edge Cases', () => {
    it('should handle special characters in parameters', () => {
      const url = buildThingsUrl('add', {
        title: 'Test & Special < > Characters',
        notes: 'Line 1\nLine 2\nSpecial chars: & < > " \' %'
      });

      expect(url).toContain('title=Test%20%26%20Special%20%3C%20%3E%20Characters');
      expect(url).toContain('notes=Line%201%0ALine%202%0ASpecial');
    });

    it('should handle empty parameters', () => {
      const url = buildThingsUrl('add', {
        title: 'Test Todo',
        notes: '',
        tags: ''
      });

      expect(url).toContain('title=Test%20Todo');
      expect(url).not.toContain('notes=');
      expect(url).not.toContain('tags=');
    });

    it('should create todo with deadline and clean up', async () => {
      if (!canRunIntegrationTests) {
        console.log('Skipping - Things URL scheme not enabled');
        return;
      }

      const testId = `deadline-test-${Date.now()}`;
      const tomorrow = new Date();
      tomorrow.setDate(tomorrow.getDate() + 1);
      const deadlineStr = tomorrow.toISOString().split('T')[0];

      const url = buildThingsUrl('add', {
        title: `Test Todo with Deadline ${testId}`,
        deadline: deadlineStr
      });

      expect(url).toContain('deadline=');
      await expect(openThingsUrl(url)).resolves.not.toThrow();
      console.log('✅ Created test todo with deadline');

      // Clean up: find and delete the created todo
      if (authToken) {
        await waitForOperation(500);
        
        try {
          const dbPath = findThingsDatabase();
          const query = `SELECT uuid FROM TMTask WHERE title LIKE '%${testId}%' AND type = 0 ORDER BY creationDate DESC LIMIT 1`;
          const result = executeSqlQuery(dbPath, query);
          if (result.length > 0) {
            const createdTodoId = result[0][0];
            console.log(`✅ Found deadline test todo ID: ${createdTodoId}`);
            
            // Delete the todo using JSON operation
            const deleteOperation = {
              type: 'to-do' as const,
              operation: 'update' as const,
              id: createdTodoId,
              attributes: {
                canceled: true
              }
            };
            
            await executeJsonOperation(deleteOperation, authToken);
            console.log('✅ Cleaned up deadline test todo');
          }
        } catch (error) {
          console.log('⚠️ Could not clean up deadline test todo:', error);
        }
      } else {
        console.log('⚠️ Cannot clean up - no auth token');
      }
    });
  });
});
```

--------------------------------------------------------------------------------
/src/utils/applescript.ts:
--------------------------------------------------------------------------------

```typescript
import { exec } from 'child_process';
import { promisify } from 'util';
import { logger } from './logger.js';

const execAsync = promisify(exec);

export interface ThingsTodo {
  id: string;
  name: string;
  notes?: string;
  project?: string;
  projectId?: string;
  area?: string;
  areaId?: string;
  tags: string[];
  completionDate?: Date;
  dueDate?: Date;
  activationDate?: Date;
  status: 'open' | 'completed' | 'canceled';
  creationDate: Date;
  modificationDate: Date;
  type: 'to-do';
}

export interface ThingsProject {
  id: string;
  name: string;
  notes?: string;
  area?: string;
  areaId?: string;
  tags: string[];
  completionDate?: Date;
  dueDate?: Date;
  activationDate?: Date;
  status: 'open' | 'completed' | 'canceled';
  creationDate: Date;
  modificationDate: Date;
  type: 'project';
}

export interface ThingsArea {
  id: string;
  name: string;
  notes?: string;
  tags: string[];
}

export interface ThingsTag {
  id: string;
  name: string;
}

export interface ListOptions {
  status?: 'open' | 'completed' | 'canceled' | 'all';
  project?: string;
  area?: string;
  tags?: string[];
  dueBefore?: Date;
  dueAfter?: Date;
  modifiedAfter?: Date;
  limit?: number;
}

// Utility functions for TypeScript filtering
function applyTodoFilters(todos: ThingsTodo[], options: ListOptions): ThingsTodo[] {
  let filtered = todos;

  if (options.status && options.status !== 'all') {
    filtered = filtered.filter(todo => todo.status === options.status);
  }

  if (options.project) {
    filtered = filtered.filter(todo => 
      todo.project?.toLowerCase().includes(options.project!.toLowerCase())
    );
  }

  if (options.area) {
    filtered = filtered.filter(todo => 
      todo.area?.toLowerCase().includes(options.area!.toLowerCase())
    );
  }

  if (options.tags && options.tags.length > 0) {
    filtered = filtered.filter(todo => 
      options.tags!.every(tag => 
        todo.tags.some(todoTag => 
          todoTag.toLowerCase().includes(tag.toLowerCase())
        )
      )
    );
  }

  if (options.dueBefore) {
    filtered = filtered.filter(todo => 
      todo.dueDate && todo.dueDate < options.dueBefore!
    );
  }

  if (options.dueAfter) {
    filtered = filtered.filter(todo => 
      todo.dueDate && todo.dueDate > options.dueAfter!
    );
  }

  if (options.modifiedAfter) {
    filtered = filtered.filter(todo => 
      todo.modificationDate > options.modifiedAfter!
    );
  }

  if (options.limit) {
    filtered = filtered.slice(0, options.limit);
  }

  return filtered;
}

function applyProjectFilters(projects: ThingsProject[], options: ListOptions): ThingsProject[] {
  let filtered = projects;

  if (options.status && options.status !== 'all') {
    filtered = filtered.filter(project => project.status === options.status);
  }

  if (options.area) {
    filtered = filtered.filter(project => 
      project.area?.toLowerCase().includes(options.area!.toLowerCase())
    );
  }

  if (options.tags && options.tags.length > 0) {
    filtered = filtered.filter(project => 
      options.tags!.every(tag => 
        project.tags.some(projectTag => 
          projectTag.toLowerCase().includes(tag.toLowerCase())
        )
      )
    );
  }

  if (options.dueBefore) {
    filtered = filtered.filter(project => 
      project.dueDate && project.dueDate < options.dueBefore!
    );
  }

  if (options.dueAfter) {
    filtered = filtered.filter(project => 
      project.dueDate && project.dueDate > options.dueAfter!
    );
  }

  if (options.modifiedAfter) {
    filtered = filtered.filter(project => 
      project.modificationDate > options.modifiedAfter!
    );
  }

  if (options.limit) {
    filtered = filtered.slice(0, options.limit);
  }

  return filtered;
}

export async function executeAppleScript(script: string): Promise<string> {
  if (process.platform !== 'darwin') {
    throw new Error('AppleScript is only supported on macOS');
  }

  try {
    const { stdout, stderr } = await execAsync(`osascript -e '${script.replace(/'/g, "\\'")}'`);
    if (stderr) {
      logger.warn('AppleScript warning', { stderr });
    }
    return stdout.trim();
  } catch (error) {
    logger.error('AppleScript execution failed', { error: error instanceof Error ? error.message : error });
    throw error;
  }
}

export async function listTodos(options: ListOptions = {}): Promise<ThingsTodo[]> {
  const script = `
    tell application "Things3"
      set todoList to every to do
      set todoData to ""
      
      -- Process todos
      repeat with todo in todoList
        set todoId to id of todo
        set todoName to name of todo
        set todoNotes to ""
        try
          set todoNotes to notes of todo
        end try
        set todoStatus to status of todo as string
        set todoCreationDate to creation date of todo as string
        set todoModificationDate to modification date of todo as string
        
        set todoProject to ""
        set todoProjectId to ""
        try
          set todoProject to name of project of todo
          set todoProjectId to id of project of todo
        end try
        
        set todoArea to ""
        set todoAreaId to ""
        try
          set todoArea to name of area of todo
          set todoAreaId to id of area of todo
        end try
        
        set todoDueDate to ""
        try
          set dueDate to due date of todo
          if dueDate is not missing value then
            set todoDueDate to dueDate as string
          end if
        end try
        
        set todoScheduledDate to ""
        
        set todoActivationDate to ""
        try
          set activationDate to activation date of todo
          if activationDate is not missing value then
            set todoActivationDate to activationDate as string
          end if
        end try
        
        set todoCompletionDate to ""
        try
          set completionDate to completion date of todo
          if completionDate is not missing value then
            set todoCompletionDate to completionDate as string
          end if
        end try
        
        set todoDelegatedPerson to ""
        
        set todoListName to ""
        
        set todoTags to ""
        try
          set tagList to tags of todo
          repeat with i from 1 to count of tagList
            set tagName to name of item i of tagList
            if todoTags is "" then
              set todoTags to tagName
            else
              set todoTags to todoTags & "," & tagName
            end if
          end repeat
        end try
        
        set todoEntry to todoId & "|" & todoName & "|" & todoNotes & "|" & todoStatus & "|" & todoProject & "|" & todoProjectId & "|" & todoArea & "|" & todoAreaId & "|" & todoDueDate & "|" & todoScheduledDate & "|" & todoActivationDate & "|" & todoCompletionDate & "|" & todoDelegatedPerson & "|" & todoListName & "|" & todoTags & "|" & todoCreationDate & "|" & todoModificationDate
        
        if todoData is "" then
          set todoData to todoEntry
        else
          set todoData to todoData & "\n" & todoEntry
        end if
      end repeat
      
      return todoData
    end tell
  `;

  const result = await executeAppleScript(script);
  const todos = parseAppleScriptTodoList(result);
  return applyTodoFilters(todos, options);
}

export async function listProjects(options: ListOptions = {}): Promise<ThingsProject[]> {
  const script = `
    tell application "Things3"
      set projectList to every project
      set projectData to ""
      
      repeat with proj in projectList
        set projId to id of proj
        set projName to name of proj
        set projNotes to ""
        try
          set projNotes to notes of proj
        end try
        set projStatus to status of proj as string
        set projCreationDate to creation date of proj as string
        set projModificationDate to modification date of proj as string
        
        set projArea to ""
        set projAreaId to ""
        try
          set projArea to name of area of proj
          set projAreaId to id of area of proj
        end try
        
        set projDueDate to ""
        try
          set dueDate to due date of proj
          if dueDate is not missing value then
            set projDueDate to dueDate as string
          end if
        end try
        
        set projScheduledDate to ""
        
        set projActivationDate to ""
        try
          set activationDate to activation date of proj
          if activationDate is not missing value then
            set projActivationDate to activationDate as string
          end if
        end try
        
        set projCompletionDate to ""
        try
          set completionDate to completion date of proj
          if completionDate is not missing value then
            set projCompletionDate to completionDate as string
          end if
        end try
        
        set projDelegatedPerson to ""
        
        set projListName to ""
        
        set projTags to ""
        try
          set tagList to tags of proj
          repeat with i from 1 to count of tagList
            set tagName to name of item i of tagList
            if projTags is "" then
              set projTags to tagName
            else
              set projTags to projTags & "," & tagName
            end if
          end repeat
        end try
        
        set projEntry to projId & "|" & projName & "|" & projNotes & "|" & projStatus & "|" & projArea & "|" & projAreaId & "|" & projDueDate & "|" & projScheduledDate & "|" & projActivationDate & "|" & projCompletionDate & "|" & projDelegatedPerson & "|" & projListName & "|" & projTags & "|" & projCreationDate & "|" & projModificationDate
        
        if projectData is "" then
          set projectData to projEntry
        else
          set projectData to projectData & "\n" & projEntry
        end if
      end repeat
      
      return projectData
    end tell
  `;

  const result = await executeAppleScript(script);
  const projects = parseAppleScriptProjectList(result);
  return applyProjectFilters(projects, options);
}

export async function listAreas(): Promise<ThingsArea[]> {
  const script = `
    tell application "Things3"
      set areaList to every area
      set areaData to ""
      
      repeat with i from 1 to count of areaList
        set currentArea to item i of areaList
        set areaId to id of currentArea
        set areaName to name of currentArea
        
        set areaNotes to ""
        try
          set areaNotes to notes of currentArea
        end try
        
        set areaTags to ""
        try
          set tagList to tags of currentArea
          repeat with j from 1 to count of tagList
            set tagName to name of item j of tagList
            if areaTags is "" then
              set areaTags to tagName
            else
              set areaTags to areaTags & "," & tagName
            end if
          end repeat
        end try
        
        set areaEntry to areaId & "|" & areaName & "|" & areaNotes & "|" & areaTags
        
        if areaData is "" then
          set areaData to areaEntry
        else
          set areaData to areaData & "\n" & areaEntry
        end if
      end repeat
      
      return areaData
    end tell
  `;

  const result = await executeAppleScript(script);
  return parseAppleScriptAreaList(result);
}

export async function listTags(): Promise<ThingsTag[]> {
  const script = `
    tell application "Things3"
      set tagList to every tag
      set tagData to ""
      
      repeat with i from 1 to count of tagList
        set currentTag to item i of tagList
        set tagId to id of currentTag
        set tagName to name of currentTag
        
        set tagEntry to tagId & "|" & tagName
        
        if tagData is "" then
          set tagData to tagEntry
        else
          set tagData to tagData & "\n" & tagEntry
        end if
      end repeat
      
      return tagData
    end tell
  `;

  const result = await executeAppleScript(script);
  return parseAppleScriptTagList(result);
}

export async function deleteTodo(id: string): Promise<void> {
  const script = `
    tell application "Things3"
      delete to do id "${id}"
    end tell
  `;

  await executeAppleScript(script);
  logger.info('Todo deleted successfully', { id });
}

export async function deleteProject(id: string): Promise<void> {
  const script = `
    tell application "Things3"
      delete project id "${id}"
    end tell
  `;

  await executeAppleScript(script);
  logger.info('Project deleted successfully', { id });
}

// Helper function to parse AppleScript dates
function parseAppleScriptDate(dateString: string): Date | undefined {
  if (!dateString || dateString === '' || dateString === 'missing value') {
    return undefined;
  }
  
  try {
    // First try standard parsing
    let parsed = new Date(dateString);
    if (!isNaN(parsed.getTime())) {
      return parsed;
    }
    
    // Try removing English day name and "at"
    const cleanedEnglishDate = dateString
      .replace(/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),?\s*/i, '')
      .replace(/\sat\s/i, ' ');
    
    parsed = new Date(cleanedEnglishDate);
    if (!isNaN(parsed.getTime())) {
      return parsed;
    }
    
    // Try manual parsing for English "Month DD, YYYY at HH:MM:SS AM/PM" format
    const englishMatch = dateString.match(/(\w+)\s+(\d{1,2}),\s+(\d{4})\s+at\s+(\d{1,2}):(\d{2}):(\d{2})\s+(AM|PM)/i);
    if (englishMatch) {
      const [, month, day, year, hour, minute, second, ampm] = englishMatch;
      const date = new Date(`${month} ${day}, ${year} ${hour}:${minute}:${second} ${ampm}`);
      if (!isNaN(date.getTime())) {
        return date;
      }
    }
    
    // Try parsing Chinese date format: "2025年6月6日 星期五 09:40:34"
    const chineseMatch = dateString.match(/(\d{4})年(\d{1,2})月(\d{1,2})日\s+(?:星期[一二三四五六日])\s+(\d{1,2}):(\d{2}):(\d{2})/);
    if (chineseMatch) {
      const [, year, month, day, hour, minute, second] = chineseMatch;
      // Month is 0-indexed in JavaScript Date
      const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
      if (!isNaN(date.getTime())) {
        return date;
      }
    }
    
    // Try parsing Chinese date format without weekday: "2025年6月6日 09:40:34"
    const chineseMatchNoWeekday = dateString.match(/(\d{4})年(\d{1,2})月(\d{1,2})日\s+(\d{1,2}):(\d{2}):(\d{2})/);
    if (chineseMatchNoWeekday) {
      const [, year, month, day, hour, minute, second] = chineseMatchNoWeekday;
      const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
      if (!isNaN(date.getTime())) {
        return date;
      }
    }
    
    // Try parsing Chinese date format for midnight: "2025年7月1日 星期二 00:00:00"
    const chineseMidnightMatch = dateString.match(/(\d{4})年(\d{1,2})月(\d{1,2})日(?:\s+星期[一二三四五六日])?\s+00:00:00/);
    if (chineseMidnightMatch) {
      const [, year, month, day] = chineseMidnightMatch;
      const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), 0, 0, 0);
      if (!isNaN(date.getTime())) {
        return date;
      }
    }
    
    return undefined;
  } catch {
    return undefined;
  }
}

// Helper functions to parse AppleScript output
function parseAppleScriptTodoList(output: string): ThingsTodo[] {
  if (!output || output.trim() === '') {
    return [];
  }
  
  try {
    const entries = output.split('\n').filter(line => line.trim() !== '');
    const parsedTodos: ThingsTodo[] = [];
    
    for (const entry of entries) {
      try {
        const parts = entry.split('|');
        if (parts.length < 17) {
          logger.warn('Skipping entry with insufficient fields', { entry, fieldCount: parts.length });
          continue;
        }
        
        const [id, name, notes, status, project, projectId, area, areaId, dueDate, scheduledDate, activationDate, completionDate, delegatedPerson, listName, tags, creationDate, modificationDate] = parts;
        
        const todo: ThingsTodo = {
          id,
          name,
          notes: notes || undefined,
          project: project || undefined,
          projectId: projectId || undefined,
          area: area || undefined,
          areaId: areaId || undefined,
          tags: tags ? tags.split(',').filter(Boolean) : [],
          completionDate: parseAppleScriptDate(completionDate),
          dueDate: parseAppleScriptDate(dueDate),
          activationDate: parseAppleScriptDate(activationDate),
          status: status as 'open' | 'completed' | 'canceled',
          creationDate: parseAppleScriptDate(creationDate) || new Date(),
          modificationDate: parseAppleScriptDate(modificationDate) || new Date(),
          type: 'to-do' as const
        };
        
        parsedTodos.push(todo);
      } catch (entryError) {
        logger.warn('Failed to parse individual todo entry', { entry, error: entryError });
        // Continue with next entry instead of failing entire parse
      }
    }
    
    return parsedTodos;
  } catch (error) {
    logger.error('Failed to parse todo list output', { output: output.length > 1000 ? `${output.substring(0, 1000)}...` : output, error });
    return [];
  }
}

function parseAppleScriptProjectList(output: string): ThingsProject[] {
  if (!output || output.trim() === '') {
    return [];
  }
  
  try {
    const entries = output.split('\n').filter(line => line.trim() !== '');
    return entries.map(entry => {
      const parts = entry.split('|');
      if (parts.length < 15) {
        throw new Error(`Invalid entry format: ${entry}`);
      }
      
      const [id, name, notes, status, area, areaId, dueDate, scheduledDate, activationDate, completionDate, delegatedPerson, listName, tags, creationDate, modificationDate] = parts;
      
      return {
        id,
        name,
        notes: notes || undefined,
        area: area || undefined,
        areaId: areaId || undefined,
        tags: tags ? tags.split(',').filter(Boolean) : [],
        completionDate: parseAppleScriptDate(completionDate),
        dueDate: parseAppleScriptDate(dueDate),
        activationDate: parseAppleScriptDate(activationDate),
        status: status as 'open' | 'completed' | 'canceled',
        creationDate: parseAppleScriptDate(creationDate) || new Date(),
        modificationDate: parseAppleScriptDate(modificationDate) || new Date(),
        type: 'project' as const
      };
    });
  } catch (error) {
    logger.error('Failed to parse project list output', { output, error });
    return [];
  }
}

function parseAppleScriptAreaList(output: string): ThingsArea[] {
  if (!output || output.trim() === '') {
    return [];
  }
  
  try {
    const entries = output.split('\n').filter(line => line.trim() !== '');
    return entries.map(entry => {
      const parts = entry.split('|');
      if (parts.length < 4) {
        throw new Error(`Invalid area entry format: ${entry}`);
      }
      
      const [id, name, notes, tags] = parts;
      
      return {
        id,
        name,
        notes: notes || undefined,
        tags: tags ? tags.split(',').filter(Boolean) : []
      };
    });
  } catch (error) {
    logger.error('Failed to parse area list output', { output, error });
    return [];
  }
}

function parseAppleScriptTagList(output: string): ThingsTag[] {
  if (!output || output.trim() === '') {
    return [];
  }
  
  try {
    const entries = output.split('\n').filter(line => line.trim() !== '');
    return entries.map(entry => {
      const parts = entry.split('|');
      if (parts.length < 2) {
        throw new Error(`Invalid tag entry format: ${entry}`);
      }
      
      const [id, name] = parts;
      
      return {
        id,
        name
      };
    });
  } catch (error) {
    logger.error('Failed to parse tag list output', { output, error });
    return [];
  }
}
```

--------------------------------------------------------------------------------
/src/tools/things-summary.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { execSync } from 'child_process';
import { existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { logger } from '../utils/logger.js';

interface ThingsTask {
  id: string;
  title: string;
  notes?: string;
  type: 'task' | 'project' | 'heading';
  creationDate?: string;
  startDate?: string;
  deadline?: string;
  area?: {
    id: string;
    name: string;
  };
  project?: {
    id: string;
    name: string;
  };
  tags?: Array<{
    id: string;
    name: string;
  }>;
  checklistItems?: {
    total: number;
    open: number;
  };
  tasks?: ThingsTask[];
  thingsUrl: string;
}

interface ThingsArea {
  id: string;
  name: string;
  visible?: boolean;
  projects?: ThingsTask[];
  tasks?: ThingsTask[];
  thingsUrl: string;
}

interface ThingsTag {
  id: string;
  name: string;
  shortcut?: string;
  taskCount: number;
  thingsUrl: string;
}

interface ThingsSummary {
  summary: {
    totalOpenTasks: number;
    totalActiveProjects: number;
    totalAreas: number;
    totalTags: number;
    lastUpdated: string;
  };
  areas?: ThingsArea[];
  inboxTasks?: ThingsTask[];
  todayTasks?: ThingsTask[];
  projects?: ThingsTask[];
  tags?: ThingsTag[];
  urls: {
    showToday: string;
    showInbox: string;
    showProjects: string;
    showAreas: string;
  };
}

const summarySchema = z.object({
  format: z.enum(['markdown', 'json'])
    .optional()
    .default('markdown')
    .describe('Output format for the summary. Use "markdown" for readable formatted summary (default) or "json" for structured data that can be processed by other tools'),
  includeCompleted: z.boolean()
    .optional()
    .default(false)
    .describe('Include completed tasks and projects in the summary (default: false). When true, shows recently completed items for reference'),
  areas: z.array(z.string())
    .optional()
    .describe('Filter to show only specific areas by name (e.g., ["Work", "Personal"]). If not provided, shows all areas'),
  tags: z.array(z.string())
    .optional()
    .describe('Filter to show only tasks/projects with specific tags (e.g., ["urgent", "review"]). If not provided, shows all items'),
  projects: z.array(z.string())
    .optional()
    .describe('Filter to show only specific projects by name. If not provided, shows all projects'),
});

function findThingsDatabase(): string {
  const homeDir = homedir();
  const thingsGroupContainer = join(homeDir, 'Library/Group Containers');
  
  if (!existsSync(thingsGroupContainer)) {
    throw new Error(`Things group container not found in ${thingsGroupContainer}. Please ensure Things.app is installed on macOS.`);
  }
  
  const containers = readdirSync(thingsGroupContainer);
  const thingsContainer = containers.find(dir => 
    dir.includes('JLMPQHK86H.com.culturedcode.ThingsMac')
  );
  
  if (!thingsContainer) {
    throw new Error(`Things container not found in ${thingsGroupContainer}. Please ensure Things.app is installed and has been launched at least once.`);
  }
  
  const containerPath = join(thingsGroupContainer, thingsContainer);
  const contents = readdirSync(containerPath);
  const thingsDataDir = contents.find(dir => dir.startsWith('ThingsData-'));
  
  if (!thingsDataDir) {
    throw new Error('ThingsData directory not found.');
  }
  
  const dbPath = join(containerPath, thingsDataDir, 'Things Database.thingsdatabase', 'main.sqlite');
  
  if (!existsSync(dbPath)) {
    throw new Error('Things database file not found.');
  }
  
  return dbPath;
}

function executeSqlQuery(dbPath: string, query: string): any[] {
  try {
    const result = execSync(`sqlite3 "${dbPath}" "${query}"`, { 
      encoding: 'utf8',
      maxBuffer: 10 * 1024 * 1024
    });
    
    if (!result.trim()) {
      return [];
    }
    
    return result.trim().split('\n').map(row => {
      return row.split('|');
    });
  } catch (error) {
    logger.error('SQL Query failed', { error: error instanceof Error ? error.message : error, query });
    return [];
  }
}

function formatDate(timestamp: string, isDateField: boolean = false): string {
  if (!timestamp || timestamp === '' || timestamp === 'NULL') {
    return '';
  }
  
  try {
    if (isDateField) {
      // startDate and deadline are stored as bit-packed integers in Things format
      // Based on Things.py implementation: year, month, day packed in bits
      const thingsDate = parseInt(timestamp);
      
      if (!thingsDate) return '';
      
      // Bit masks from Things.py
      const y_mask = 0b111111111110000000000000000; // Year mask
      const m_mask = 0b000000000001111000000000000; // Month mask  
      const d_mask = 0b000000000000000111110000000; // Day mask
      
      // Extract year, month, day using bitwise operations
      const year = (thingsDate & y_mask) >> 16;
      const month = (thingsDate & m_mask) >> 12;
      const day = (thingsDate & d_mask) >> 7;
      
      // Format as ISO date
      return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
    } else {
      // creationDate and other timestamps are in Unix epoch format (seconds since 1970)
      const date = new Date(parseFloat(timestamp) * 1000);
      return date.toISOString().split('T')[0];
    }
  } catch {
    return '';
  }
}

function generateThingsUrl(type: 'show' | 'update', id?: string, params?: Record<string, string>): string {
  let url = `things:///${type}`;
  
  if (id) {
    url += `?id=${encodeURIComponent(id)}`;
  }
  
  if (params) {
    const paramString = Object.entries(params)
      .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
      .join('&');
    
    if (id) {
      url += `&${paramString}`;
    } else {
      url += `?${paramString}`;
    }
  }
  
  return url;
}

function compressObject(obj: any): any {
  if (Array.isArray(obj)) {
    const filtered = obj.map(compressObject).filter(item => item !== null && item !== undefined);
    return filtered.length > 0 ? filtered : undefined;
  }
  
  if (obj && typeof obj === 'object') {
    const compressed: any = {};
    
    for (const [key, value] of Object.entries(obj)) {
      const compressedValue = compressObject(value);
      
      if (compressedValue === null || 
          compressedValue === undefined || 
          compressedValue === '' ||
          (Array.isArray(compressedValue) && compressedValue.length === 0) ||
          (key === 'checklistItems' && compressedValue.total === 0 && compressedValue.open === 0)) {
        continue;
      }
      
      compressed[key] = compressedValue;
    }
    
    return Object.keys(compressed).length > 0 ? compressed : undefined;
  }
  
  return obj;
}

function getThingsSummary(params: any): ThingsSummary {
  const dbPath = findThingsDatabase();
  logger.info('Found Things database', { path: dbPath });
  
  // Build status filter
  let statusFilter = 'status = 0 AND trashed = 0';
  if (params.includeCompleted) {
    statusFilter = 'trashed = 0';
  }
  
  // Get all areas
  const areasData = executeSqlQuery(dbPath, "SELECT uuid, title, visible FROM TMArea");
  const areas: ThingsArea[] = areasData.map(row => {
    const area: any = {
      id: row[0],
      name: row[1] || 'Unnamed Area',
      thingsUrl: generateThingsUrl('show', row[0])
    };
    
    if (row[2] === '1') area.visible = true;
    
    return area;
  });
  
  // Filter areas if specified
  let filteredAreas = areas;
  if (params.areas && params.areas.length > 0) {
    filteredAreas = areas.filter(area => params.areas.includes(area.name));
  }
  
  // Get all tags
  const tagsData = executeSqlQuery(dbPath, "SELECT uuid, title, shortcut FROM TMTag");
  const tags: ThingsTag[] = tagsData.map(row => ({
    id: row[0],
    name: row[1] || 'Unnamed Tag',
    shortcut: row[2] || undefined,
    taskCount: 0,
    thingsUrl: generateThingsUrl('show', undefined, { filter: row[1] })
  }));
  
  // Get open tasks and projects
  const tasksData = executeSqlQuery(dbPath, 
    `SELECT uuid, title, notes, type, creationDate, startDate, deadline, area, project, checklistItemsCount, openChecklistItemsCount FROM TMTask WHERE ${statusFilter}`
  );
  
  // Create a map for quick lookup of project names
  const projectMap = new Map();
  tasksData.forEach(row => {
    if (row[3] === '1') {
      projectMap.set(row[0], row[1]);
    }
  });
  
  const allTasks: ThingsTask[] = tasksData.map(row => {
    const taskType = row[3] === '0' ? 'task' : row[3] === '1' ? 'project' : 'heading';
    
    const areaInfo = areas.find(area => area.id === row[7]);
    const projectName = row[8] ? projectMap.get(row[8]) : null;
    
    const task: any = {
      id: row[0],
      title: row[1] || 'Untitled',
      type: taskType,
      thingsUrl: generateThingsUrl('show', row[0])
    };
    
    if (row[2]) task.notes = row[2];
    
    const creationDate = formatDate(row[4]);
    if (creationDate) task.creationDate = creationDate;
    
    const startDate = formatDate(row[5], true);
    if (startDate) task.startDate = startDate;
    
    const deadline = formatDate(row[6], true);
    if (deadline) task.deadline = deadline;
    
    if (areaInfo) task.area = { id: areaInfo.id, name: areaInfo.name };
    if (projectName) task.project = { id: row[8], name: projectName };
    
    const checklistTotal = parseInt(row[9]) || 0;
    const checklistOpen = parseInt(row[10]) || 0;
    if (checklistTotal > 0 || checklistOpen > 0) {
      task.checklistItems = { total: checklistTotal, open: checklistOpen };
    }
    
    return task;
  });
  
  // Get task-tag relationships
  const taskTagData = executeSqlQuery(dbPath, "SELECT tasks, tags FROM TMTaskTag");
  taskTagData.forEach(row => {
    const task = allTasks.find(t => t.id === row[0]);
    const tag = tags.find(t => t.id === row[1]);
    if (task && tag) {
      if (!task.tags) task.tags = [];
      task.tags.push({ id: tag.id, name: tag.name });
      tag.taskCount++;
    }
  });
  
  // Apply tag filtering
  let filteredTasks = allTasks;
  if (params.tags && params.tags.length > 0) {
    filteredTasks = allTasks.filter(task => 
      task.tags && task.tags.some(tag => params.tags.includes(tag.name))
    );
  }
  
  // Apply project filtering
  if (params.projects && params.projects.length > 0) {
    filteredTasks = filteredTasks.filter(task => 
      (task.type === 'project' && params.projects.includes(task.title)) ||
      (task.project && params.projects.includes(task.project.name))
    );
  }
  
  
  // Separate tasks by type
  const projects = filteredTasks.filter(task => task.type === 'project');
  const tasks = filteredTasks.filter(task => task.type === 'task');
  const inboxTasks = tasks.filter(task => !task.area && !task.project);
  const todayTasks = tasks.filter(task => {
    const today = new Date().toISOString().split('T')[0];
    return task.startDate === today;
  });
  
  // Organize tasks by area and add project tasks
  filteredAreas.forEach(area => {
    const areaProjects = projects.filter(project => project.area?.id === area.id);
    const areaTasks = tasks.filter(task => task.area?.id === area.id && !task.project);
    
    if (areaProjects.length > 0) area.projects = areaProjects;
    if (areaTasks.length > 0) area.tasks = areaTasks;
  });
  
  // Add tasks to their respective projects
  projects.forEach(project => {
    const projectTasks = tasks.filter(task => task.project?.id === project.id);
    if (projectTasks.length > 0) {
      (project as any).tasks = projectTasks;
    }
  });
  
  // Filter areas and tags to show only active ones
  const activeAreas = filteredAreas.filter(area => 
    (area.projects && area.projects.length > 0) || 
    (area.tasks && area.tasks.length > 0)
  );
  
  const activeTags = tags.filter(tag => tag.taskCount > 0);
  
  const summary: any = {
    summary: {
      totalOpenTasks: tasks.length,
      totalActiveProjects: projects.length,
      totalAreas: activeAreas.length,
      totalTags: activeTags.length,
      lastUpdated: new Date().toISOString()
    },
    urls: {
      showToday: generateThingsUrl('show', undefined, { list: 'today' }),
      showInbox: generateThingsUrl('show', undefined, { list: 'inbox' }),
      showProjects: generateThingsUrl('show', undefined, { list: 'projects' }),
      showAreas: generateThingsUrl('show', undefined, { list: 'areas' })
    }
  };
  
  // Only add sections that have content
  if (activeAreas.length > 0) summary.areas = activeAreas;
  if (inboxTasks.length > 0) summary.inboxTasks = inboxTasks;
  if (todayTasks.length > 0) summary.todayTasks = todayTasks;
  if (projects.length > 0) summary.projects = projects;
  if (activeTags.length > 0) summary.tags = activeTags;
  
  return compressObject(summary);
}

function generateMarkdownSummary(data: ThingsSummary): string {
  const lines: string[] = [];
  
  // Header
  lines.push('# Things Database Summary');
  lines.push('');
  const now = new Date();
  lines.push(`**Generated:** ${now.toLocaleDateString('en-US')} ${now.toLocaleTimeString('en-US')}`);
  lines.push(`**Last Updated:** ${data.summary.lastUpdated}`);
  lines.push('');
  
  // Overview Statistics
  lines.push('## Overview');
  lines.push('');
  lines.push(`- **Open Tasks:** ${data.summary.totalOpenTasks}`);
  lines.push(`- **Active Projects:** ${data.summary.totalActiveProjects}`);
  lines.push(`- **Areas:** ${data.summary.totalAreas}`);
  lines.push(`- **Tags in Use:** ${data.summary.totalTags}`);
  lines.push('');
  
  // Today Tasks (high priority section)
  if (data.todayTasks && data.todayTasks.length > 0) {
    lines.push('## Today');
    lines.push('');
    
    data.todayTasks.forEach(task => {
      let taskLine = `- [ ] **${task.title}**`;
      if (task.deadline) taskLine += ` (due: ${task.deadline})`;
      lines.push(taskLine);
      lines.push(`  - *ID: ${task.id}*`);
      
      if (task.notes) {
        lines.push(`  - ${task.notes}`);
      }
      if (task.area) {
        lines.push(`  - Area: ${task.area.name}`);
      }
      if (task.project) {
        lines.push(`  - Project: ${task.project.name}`);
      }
      if (task.tags && task.tags.length > 0) {
        lines.push(`  - Tags: ${task.tags.map(t => `#${t.name}`).join(', ')}`);
      }
      if (task.checklistItems && task.checklistItems.total > 0) {
        lines.push(`  - Checklist: ${task.checklistItems.open}/${task.checklistItems.total} remaining`);
      }
    });
    lines.push('');
  }
  
  // Inbox Tasks
  if (data.inboxTasks && data.inboxTasks.length > 0) {
    lines.push('## Inbox');
    lines.push('');
    
    data.inboxTasks.forEach(task => {
      let taskLine = `- [ ] **${task.title}**`;
      if (task.startDate) taskLine += ` (scheduled: ${task.startDate})`;
      if (task.deadline) taskLine += ` (due: ${task.deadline})`;
      lines.push(taskLine);
      lines.push(`  - *ID: ${task.id}*`);
      
      if (task.notes) {
        lines.push(`  - ${task.notes}`);
      }
      if (task.tags && task.tags.length > 0) {
        lines.push(`  - Tags: ${task.tags.map(t => `#${t.name}`).join(', ')}`);
      }
      if (task.checklistItems && task.checklistItems.total > 0) {
        lines.push(`  - Checklist: ${task.checklistItems.open}/${task.checklistItems.total} remaining`);
      }
    });
    lines.push('');
  }
  
  // Areas
  if (data.areas && data.areas.length > 0) {
    lines.push('## Areas');
    lines.push('');
    
    data.areas.forEach(area => {
      lines.push(`### ${area.name}`);
      lines.push(`*ID: ${area.id}*`);
      if (area.visible === false) {
        lines.push('*Status: Hidden area*');
      }
      lines.push('');
      
      // Area projects
      if (area.projects && area.projects.length > 0) {
        lines.push('**Projects:**');
        area.projects.forEach(project => {
          lines.push(`- [ ] **${project.title}**`);
          lines.push(`  - *ID: ${project.id}*`);
          if (project.notes) {
            lines.push(`  - ${project.notes}`);
          }
          if (project.startDate) {
            lines.push(`  - Scheduled: ${project.startDate}`);
          }
          if (project.deadline) {
            lines.push(`  - Due: ${project.deadline}`);
          }
          if (project.tags && project.tags.length > 0) {
            lines.push(`  - Tags: ${project.tags.map(t => `#${t.name}`).join(', ')}`);
          }
          
          // Project tasks
          if (project.tasks && project.tasks.length > 0) {
            lines.push('  - **Tasks:**');
            project.tasks.forEach(task => {
              let taskLine = `    - [ ] ${task.title}`;
              if (task.startDate) taskLine += ` (${task.startDate})`;
              if (task.deadline) taskLine += ` (due: ${task.deadline})`;
              lines.push(taskLine);
              
              if (task.notes) {
                lines.push(`      - ${task.notes}`);
              }
              if (task.tags && task.tags.length > 0) {
                lines.push(`      - ${task.tags.map(t => `#${t.name}`).join(', ')}`);
              }
              if (task.checklistItems && task.checklistItems.total > 0) {
                lines.push(`      - ${task.checklistItems.open}/${task.checklistItems.total} remaining`);
              }
            });
          }
        });
        lines.push('');
      }
      
      // Area tasks
      if (area.tasks && area.tasks.length > 0) {
        lines.push('**Tasks:**');
        area.tasks.forEach(task => {
          let taskLine = `- [ ] ${task.title}`;
          if (task.startDate) taskLine += ` (${task.startDate})`;
          if (task.deadline) taskLine += ` (due: ${task.deadline})`;
          lines.push(taskLine);
          
          if (task.notes) {
            lines.push(`  - ${task.notes}`);
          }
          if (task.tags && task.tags.length > 0) {
            lines.push(`  - Tags: ${task.tags.map(t => `#${t.name}`).join(', ')}`);
          }
          if (task.checklistItems && task.checklistItems.total > 0) {
            lines.push(`  - Checklist: ${task.checklistItems.open}/${task.checklistItems.total} remaining`);
          }
        });
        lines.push('');
      }
    });
  }
  
  // Standalone Projects
  if (data.projects && data.projects.length > 0) {
    const standaloneProjects = data.projects.filter(p => !p.area);
    if (standaloneProjects.length > 0) {
      lines.push('## Projects');
      lines.push('');
      
      standaloneProjects.forEach(project => {
        lines.push(`### ${project.title}`);
        lines.push(`*ID: ${project.id}*`);
        if (project.notes) {
          lines.push(`${project.notes}`);
        }
        
        if (project.startDate) {
          lines.push(`**Start:** ${project.startDate}`);
        }
        if (project.deadline) {
          lines.push(`**Due:** ${project.deadline}`);
        }
        if (project.tags && project.tags.length > 0) {
          lines.push(`**Tags:** ${project.tags.map(t => `#${t.name}`).join(', ')}`);
        }
        lines.push('');
        
        // Project tasks
        if (project.tasks && project.tasks.length > 0) {
          lines.push('**Tasks:**');
          project.tasks.forEach(task => {
            let taskLine = `- [ ] ${task.title}`;
            if (task.startDate) taskLine += ` (${task.startDate})`;
            if (task.deadline) taskLine += ` (due: ${task.deadline})`;
            lines.push(taskLine);
            
            if (task.notes) {
              lines.push(`  - ${task.notes}`);
            }
            if (task.tags && task.tags.length > 0) {
              lines.push(`  - ${task.tags.map(t => `#${t.name}`).join(', ')}`);
            }
            if (task.checklistItems && task.checklistItems.total > 0) {
              lines.push(`  - Checklist: ${task.checklistItems.open}/${task.checklistItems.total} remaining`);
            }
          });
          lines.push('');
        }
      });
    }
  }
  
  // Tags
  if (data.tags && data.tags.length > 0) {
    lines.push('## Tags');
    lines.push('');
    
    const sortedTags = [...data.tags].sort((a, b) => b.taskCount - a.taskCount);
    
    sortedTags.forEach(tag => {
      lines.push(`### #${tag.name}`);
      lines.push(`- **Task Count:** ${tag.taskCount}`);
      if (tag.shortcut) {
        lines.push(`- **Shortcut:** ${tag.shortcut}`);
      }
      lines.push(`- *ID: ${tag.id}*`);
      lines.push('');
    });
  }
  
  // Navigation URLs
  lines.push('## Quick Navigation');
  lines.push('');
  lines.push(`- [Today](${data.urls.showToday})`);
  lines.push(`- [Inbox](${data.urls.showInbox})`);
  lines.push(`- [Projects](${data.urls.showProjects})`);
  lines.push(`- [Areas](${data.urls.showAreas})`);
  lines.push('');
  
  // Footer
  lines.push('---');
  lines.push('');
  
  return lines.join('\n');
}

export function registerThingsSummaryTool(server: McpServer): void {
  server.tool(
    'things_summary',
    'Generate a summary of your Things database with filtering options. Returns formatted Markdown or structured JSON data for tasks, projects, areas, and tags.',
    summarySchema.shape,
    async (params) => {
      try {
        // Validate macOS platform
        if (process.platform !== 'darwin') {
          throw new Error('Things database access is only available on macOS');
        }

        logger.info('Generating Things summary', { 
          format: params.format, 
          filters: {
            areas: params.areas?.length || 0,
            tags: params.tags?.length || 0,
            projects: params.projects?.length || 0,
            includeCompleted: params.includeCompleted
          }
        });
        
        const data = getThingsSummary(params);
        
        if (params.format === 'json') {
          return {
            content: [{
              type: "text",
              text: JSON.stringify(data, null, 2)
            }]
          };
        } else {
          const markdown = generateMarkdownSummary(data);
          return {
            content: [{
              type: "text",
              text: markdown
            }]
          };
        }
      } catch (error) {
        logger.error('Failed to generate Things summary', { 
          error: error instanceof Error ? error.message : error 
        });
        throw error;
      }
    }
  );
}
```