#
tokens: 19323/50000 27/28 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/amekala/adspirer-mcp-server?page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── .npmignore
├── bin
│   └── cli.js
├── build.js
├── claude-desktop-config.json
├── index.js
├── package.json
├── Project-Docs
│   ├── Example Clients.txt
│   ├── MCP TypeScript SDK.txt
│   └── Project-plan.txt
├── README.md
├── scripts
│   ├── check-api-keys.js
│   ├── create-package.js
│   ├── explore-db.js
│   ├── explore-db.ts
│   └── test-mcp-connection.js
├── src
│   ├── config
│   │   ├── supabase.js
│   │   └── supabase.ts
│   ├── index.ts
│   ├── middleware
│   │   └── auth.middleware.ts
│   ├── services
│   │   ├── advertiser.service.js
│   │   └── advertiser.service.ts
│   ├── types
│   │   └── database.types.ts
│   └── utils
│       └── db-explorer.ts
├── test-mcp.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# Supabase credentials (required for build)
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-supabase-service-role-key

# Optional configuration
DEBUG=false
NODE_ENV=production 
```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
# Development files
node_modules/
.git/
.gitignore
*.log
.env
.env.*
!.env.example

# Test files
test-mcp.js

# Build artifacts
dist-package/
*.zip

# Documentation
Project-Docs/

# Misc
output.log
mcp-debug.log 
```

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

```
# Dependencies
node_modules/
npm-debug.log
yarn-error.log
package-lock.json

# Environment Variables and Credentials
.env
.env.*
!.env.example
.env.supabase

# Logs
logs/
*.log
mcp-debug.log

# Build files
dist/
build/

# OS specific files
.DS_Store
Thumbs.db

# IDE files
.idea/
.vscode/
*.sublime-project
*.sublime-workspace

# Credentials
.env
.env.*
!.env.example
credentials.js

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
mcp-debug.log
output.log

# Dependency directories
jspm_packages/

# Build output
dist-package/
*.zip

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# dotenv environment variable files
.env.development.local
.env.test.local
.env.production.local
.env.local
.env.supabase

# OS files
.DS_Store
Thumbs.db 
```

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

```markdown
# Ads Manager MCP server for Claude

Transform your advertising experience with AI-powered insights and management - right inside Claude!

## What It Does

This Ads MCP server is your AI assistant for managing digital advertising campaigns. Through simple conversations in Claude, you can:

- Analyze campaign performance and get actionable insights
- Create and manage advertising campaigns
- Visualize campaign metrics with interactive charts
- Receive personalized optimization recommendations
- Adjust budgets, bids, and targeting on the fly
- Get alerts for underperforming campaigns

No more switching between multiple dashboards and reports - just chat naturally with Claude!

## Platforms Supported

- **Amazon Ads** - Available now!
- **Walmart Ads** - Coming soon
- **Meta Ads** - Coming soon
- **Google Ads** - Coming soon

## Getting Started

### 1. Get Your API Key

Visit [Adspirer.com](https://www.adspirer.com/) to connect your ad accounts:
- Sign up for a free account
- Connect your advertising accounts via platform authentication
- Copy your API key from your dashboard

### 2. Install the Server

```bash
# Install globally with npm
npm install -g adspirer-mcp-server

# Configure Claude Desktop automatically
adspirer-mcp config
```

During configuration, you'll be prompted for your API key from Adspirer.

### 3. Claude Desktop Configuration

The `adspirer-mcp config` command will automatically update your Claude Desktop configuration file, but you can also manually set it up:

```json
{
  "mcpServers": {
    "adspirer": {
      "command": "adspirer-mcp",
      "args": ["start"],
      "env": {
        "API_KEY": "your_api_key_here"
      }
    }
  }
}
```

Save this to:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

### 4. Start Using It in Claude!

Open Claude and start asking about your campaigns:
- "How are my Amazon ad campaigns performing this week?"
- "Show me a chart of my best performing keywords"
- "Increase the budget for my 'Summer Sale' campaign by 20%"

## Examples

Ask Claude questions like:
- "Which campaigns have the best RoAS?"
- "Show me trends in my ad spend over the last 30 days"
- "What optimization opportunities do you see in my campaigns?"
- "Create a new Sponsored Products campaign for my top selling item"

## Troubleshooting

Having issues?
- Make sure Claude Desktop is running the latest version
- Check that your API key is entered correctly
- Run `adspirer-mcp test` to verify your connection

## Resources

- [Full Documentation](https://docs.adspirer.com)
- [Video Tutorial](https://adspirer.com/tutorials)
- [Support](https://adspirer.com/support)

---

Built with ❤️ by Adspirer - Supercharging Advertisers with AI

```

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

```typescript
 
```

--------------------------------------------------------------------------------
/claude-desktop-config.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "amazon-ads": {
      "command": "adspirer-mcp",
      "args": ["start"],
      "env": {
        "API_KEY": "your_api_key_here",
        "NODE_ENV": "production"
      }
    }
  }
} 
```

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

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": ".",
    "sourceMap": true
  },
  "include": ["src/**/*", "scripts/**/*"],
  "exclude": ["node_modules", "dist"]
} 
```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
name: Build and Publish

on:
  release:
    types: [created]
  workflow_dispatch:  # Allow manual triggering

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          registry-url: 'https://registry.npmjs.org'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build with embedded credentials
        env:
          SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
          SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
        run: npm run build
      
      - name: Publish to npm
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npm publish 
```

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

```json
{
  "name": "adspirer-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for Amazon Advertising data integration with Claude",
  "main": "dist/index.js",
  "type": "module",
  "bin": {
    "adspirer-mcp": "./bin/cli.js"
  },
  "scripts": {
    "build": "node build.js",
    "dev": "node -r dotenv/config index.js",
    "start": "node index.js",
    "explore-db": "node scripts/explore-db.js",
    "check-api-keys": "node scripts/check-api-keys.js",
    "test-mcp": "node scripts/test-mcp-connection.js",
    "package": "npm run build && node scripts/create-package.js",
    "prepare-release": "npm run build && npm prune --production"
  },
  "keywords": [
    "mcp",
    "claude",
    "amazon-advertising"
  ],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^22.13.10",
    "esbuild": "^0.19.12",
    "esbuild-node-externals": "^1.18.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "@supabase/supabase-js": "^2.49.1",
    "axios": "^1.8.3",
    "chalk": "^5.4.1",
    "dotenv": "^16.4.7",
    "zod": "^3.24.2"
  }
}

```

--------------------------------------------------------------------------------
/src/config/supabase.ts:
--------------------------------------------------------------------------------

```typescript
import { createClient } from '@supabase/supabase-js';
import * as dotenv from 'dotenv';

// Load environment variables
dotenv.config();

// Validate environment variables
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

if (!supabaseUrl || !supabaseKey) {
  throw new Error('Missing Supabase environment variables. Please check your .env file.');
}

// Create Supabase client
export const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    persistSession: false,
  }
});

// Function to validate an API key against the database
export async function validateApiKey(apiKey: string): Promise<{ 
  valid: boolean, 
  advertiserId?: string,
  error?: string 
}> {
  try {
    const { data, error } = await supabase
      .from('api_keys')
      .select('advertiser_id, active')
      .eq('key', apiKey)
      .single();

    if (error) {
      return { valid: false, error: 'Invalid API key' };
    }

    if (!data || !data.active) {
      return { valid: false, error: 'API key is inactive or invalid' };
    }

    return { valid: true, advertiserId: data.advertiser_id };
  } catch (error) {
    console.error('Error validating API key:', error);
    return { valid: false, error: 'Error validating API key' };
  }
} 
```

--------------------------------------------------------------------------------
/src/middleware/auth.middleware.ts:
--------------------------------------------------------------------------------

```typescript
import { validateApiKey } from '../config/supabase.js';
import { AdvertiserService, AdvertiserContext } from '../services/advertiser.service.js';

export interface AuthContext extends AdvertiserContext {
  apiKey: string;
}

/**
 * Authenticate a connection based on provided API key
 * This will be used to validate connections to the MCP server
 */
export async function authenticate(apiKey: string): Promise<{ success: boolean; error?: string; session?: AdvertiserContext }> {
  try {
    if (!apiKey) {
      return { success: false, error: 'No API key provided' };
    }
    
    const validation = await validateApiKey(apiKey);
    if (!validation.valid || !validation.advertiserId) {
      return { success: false, error: validation.error || 'Invalid API key' };
    }
    
    const advertiser = await AdvertiserService.getAdvertiserContext(validation.advertiserId);
    if (!advertiser) {
      return { success: false, error: 'Failed to retrieve advertiser context' };
    }
    
    await AdvertiserService.updateApiKeyUsage(apiKey);
    
    return { 
      success: true,
      session: advertiser
    };
  } catch (error) {
    console.error('Authentication error:', error);
    return { 
      success: false, 
      error: 'Authentication failed due to an internal error' 
    };
  }
} 
```

--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------

```javascript
// build.js
import esbuild from 'esbuild';
import { nodeExternalsPlugin } from 'esbuild-node-externals';
import dotenv from 'dotenv';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Create a custom dotenv plugin since esbuild-plugin-dotenv is not available
const dotenvPlugin = () => ({
  name: 'dotenv',
  setup(build) {
    // Load .env file
    dotenv.config();
    
    // Whitelisted environment variables to include in the build
    const include = ['SUPABASE_URL', 'SUPABASE_KEY'];
    
    // Replace process.env.X with actual values during build
    build.onResolve({ filter: /^process\.env\./ }, args => {
      const envVar = args.path.substring('process.env.'.length);
      if (include.includes(envVar)) {
        return {
          path: args.path,
          namespace: 'env-ns',
        };
      }
      return null;
    });

    build.onLoad({ filter: /.*/, namespace: 'env-ns' }, args => {
      const envVar = args.path.substring('process.env.'.length);
      const value = process.env[envVar] || '';
      return {
        contents: JSON.stringify(value),
        loader: 'json',
      };
    });
  },
});

async function build() {
  console.log('Building MCP server...');
  
  try {
    await esbuild.build({
      entryPoints: ['index.js'],
      bundle: true,
      platform: 'node',
      target: 'node16',
      outfile: 'dist/index.js',
      format: 'esm',
      minify: true,
      plugins: [
        nodeExternalsPlugin(),
        dotenvPlugin()
      ]
    });
    
    console.log('Build completed successfully!');
  } catch (error) {
    console.error('Build failed:', error);
    process.exit(1);
  }
}

// Run the build
build(); 
```

--------------------------------------------------------------------------------
/scripts/explore-db.js:
--------------------------------------------------------------------------------

```javascript
// CommonJS script for exploring Supabase database
const { createClient } = require('@supabase/supabase-js');
const dotenv = require('dotenv');
const path = require('path');

// Load environment variables from project root
dotenv.config({ path: path.resolve(__dirname, '../.env') });

// Validate environment variables
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

if (!supabaseUrl || !supabaseKey) {
  throw new Error('Missing Supabase environment variables. Please check your .env file.');
}

// Create Supabase client
const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    persistSession: false,
  }
});

async function exploreTables() {
  console.log('Exploring Supabase database tables...\n');

  try {
    // Get list of tables
    const tables = ['advertisers', 'amazon_tokens', 'api_keys', 'campaign_metrics', 'campaigns'];
    
    for (const table of tables) {
      console.log(`\n=== Table: ${table} ===`);
      
      // Get sample data
      const { data, error } = await supabase
        .from(`${table}`)
        .select('*')
        .limit(3);
        
      if (error) {
        console.error(`Error fetching data from ${table}:`, error);
        continue;
      }
      
      // Display column names based on the first row
      if (data && data.length > 0) {
        console.log('Columns:', Object.keys(data[0]).join(', '));
      } else {
        console.log('No data found in table');
      }
      
      console.log(`\nSample data (${data?.length || 0} rows):`);
      if (data && data.length > 0) {
        console.log(JSON.stringify(data, null, 2));
      } else {
        console.log('No data found');
      }
    }
    
    console.log('\nDatabase exploration complete!');
  } catch (error) {
    console.error('Error exploring database:', error);
  }
}

// Run the exploration
exploreTables(); 
```

--------------------------------------------------------------------------------
/scripts/explore-db.ts:
--------------------------------------------------------------------------------

```typescript
import { createClient } from '@supabase/supabase-js';
import * as dotenv from 'dotenv';
import * as path from 'path';
import { fileURLToPath } from 'url';

// Load environment variables from project root
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config({ path: path.resolve(__dirname, '../.env') });

// Validate environment variables
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

if (!supabaseUrl || !supabaseKey) {
  throw new Error('Missing Supabase environment variables. Please check your .env file.');
}

// Create Supabase client
const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    persistSession: false,
  }
});

async function exploreTables() {
  console.log('Exploring Supabase database tables...\n');

  try {
    // Get list of tables
    const tables = ['advertisers', 'amazon_tokens', 'api_keys', 'campaign_metrics', 'campaigns'];
    
    for (const table of tables) {
      console.log(`\n=== Table: ${table} ===`);
      
      // Get sample data
      const { data, error } = await supabase
        .from(`${table}`)
        .select('*')
        .limit(3);
        
      if (error) {
        console.error(`Error fetching data from ${table}:`, error);
        continue;
      }
      
      // Display column names based on the first row
      if (data && data.length > 0) {
        console.log('Columns:', Object.keys(data[0]).join(', '));
      } else {
        // If no data, try to get column info from an empty select
        const { error: schemaError } = await supabase
          .from(`${table}`)
          .select('*')
          .limit(0);
          
        if (schemaError) {
          console.error(`Error fetching schema for ${table}:`, schemaError);
        } else {
          console.log('Table exists but no data found');
        }
      }
      
      console.log(`\nSample data (${data?.length || 0} rows):`);
      if (data && data.length > 0) {
        console.log(JSON.stringify(data, null, 2));
      } else {
        console.log('No data found');
      }
    }
    
    console.log('\nDatabase exploration complete!');
  } catch (error) {
    console.error('Error exploring database:', error);
  }
}

// Run the exploration
exploreTables(); 
```

--------------------------------------------------------------------------------
/src/utils/db-explorer.ts:
--------------------------------------------------------------------------------

```typescript
import { supabase } from '../config/supabase.js';

async function exploreTables() {
  console.log('Exploring Supabase database tables...\n');

  try {
    // Get list of tables
    const tables = ['advertisers', 'amazon_tokens', 'api_keys', 'campaign_metrics', 'campaigns'];
    
    for (const table of tables) {
      console.log(`\n=== Table: ${table} ===`);
      
      // Get table schema
      const { data: columns, error: schemaError } = await supabase
        .from(`${table}`)
        .select('*')
        .limit(0);
        
      if (schemaError) {
        console.error(`Error fetching schema for ${table}:`, schemaError);
        continue;
      }
      
      // Display column names based on the first row structure
      if (columns) {
        console.log('Columns:', Object.keys(columns).length > 0 
          ? Object.keys(columns[0] || {}).join(', ') 
          : 'No columns found');
      }
      
      // Get sample data
      const { data, error } = await supabase
        .from(`${table}`)
        .select('*')
        .limit(3);
        
      if (error) {
        console.error(`Error fetching data from ${table}:`, error);
        continue;
      }
      
      console.log(`\nSample data (${data?.length || 0} rows):`);
      if (data && data.length > 0) {
        console.log(JSON.stringify(data, null, 2));
      } else {
        console.log('No data found');
      }
    }
    
    console.log('\nDatabase exploration complete!');
  } catch (error) {
    console.error('Error exploring database:', error);
  }
}

// Run the exploration
exploreTables();

export async function listTables(): Promise<string[]> {
  try {
    const { data, error } = await supabase
      .from('information_schema.tables')
      .select('table_name')
      .eq('table_schema', 'public');

    if (error) {
      console.error('Error listing tables:', error);
      return [];
    }

    return data.map(table => table.table_name);
  } catch (error) {
    console.error('Error in listTables:', error);
    return [];
  }
}

export async function describeTable(tableName: string): Promise<any[]> {
  try {
    const { data, error } = await supabase
      .from('information_schema.columns')
      .select('column_name, data_type, is_nullable')
      .eq('table_schema', 'public')
      .eq('table_name', tableName);

    if (error) {
      console.error(`Error describing table ${tableName}:`, error);
      return [];
    }

    return data;
  } catch (error) {
    console.error(`Error in describeTable for ${tableName}:`, error);
    return [];
  }
} 
```

--------------------------------------------------------------------------------
/src/config/supabase.js:
--------------------------------------------------------------------------------

```javascript
import { createClient } from '@supabase/supabase-js';
import * as dotenv from 'dotenv';
import fs from 'fs';

// Load environment variables (for development)
dotenv.config();

// Function to write debug info
const writeDebug = (message) => {
  if (process.env.DEBUG === 'true') {
    const timestamp = new Date().toISOString();
    const logMessage = `${timestamp} - ${message}\n`;
    try {
      const logFile = fs.createWriteStream('mcp-debug.log', { flags: 'a' });
      logFile.write(logMessage);
      console.error(logMessage);
    } catch (error) {
      console.error(`Error writing to log: ${error.message}`);
    }
  }
};

// These values will be replaced during build with actual values from .env
// Users will never see or need to provide these credentials
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

writeDebug('Supabase client initialization complete');

// Create Supabase client
export const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    persistSession: false,
  }
});

// Function to validate an API key against the database
export async function validateApiKey(apiKey) {
  try {
    // First check the 'key' column (based on database.types.ts)
    const { data: keyData, error: keyError } = await supabase
      .from('api_keys')
      .select('advertiser_id, active')
      .eq('key', apiKey)
      .single();

    if (!keyError && keyData) {
      return { 
        valid: keyData.active === true, 
        advertiserId: keyData.advertiser_id,
        error: keyData.active !== true ? 'API key is inactive' : undefined
      };
    }

    // If not found, check the 'key_value' column (based on explore-db.js output)
    const { data, error } = await supabase
      .from('api_keys')
      .select('id, user_id')
      .eq('key_value', apiKey)
      .eq('is_active', true)
      .single();

    if (error) {
      return { valid: false, error: 'Invalid API key' };
    }

    if (!data) {
      return { valid: false, error: 'API key not found' };
    }

    // If we have a user_id but no advertiser_id, try to get the first advertiser for this user
    if (data.user_id) {
      const { data: advertiser, error: advError } = await supabase
        .from('advertisers')
        .select('id')
        .eq('user_id', data.user_id)
        .limit(1)
        .single();

      if (!advError && advertiser) {
        return { valid: true, advertiserId: advertiser.id };
      }
    }

    return { valid: true, advertiserId: data.id };
  } catch (error) {
    console.error('Error validating API key:', error);
    return { valid: false, error: 'Error validating API key' };
  }
} 
```

--------------------------------------------------------------------------------
/src/services/advertiser.service.js:
--------------------------------------------------------------------------------

```javascript
import { supabase } from '../config/supabase.js';
import crypto from 'crypto';

export class AdvertiserService {
  /**
   * Get advertiser context by ID
   * This will be used after API key validation
   */
  static async getAdvertiserContext(advertiserId) {
    try {
      const { data, error } = await supabase
        .from('advertisers')
        .select('id, user_id, profile_id, account_name, marketplace, account_type, metadata')
        .eq('id', advertiserId)
        .single();

      if (error || !data) {
        console.error('Error fetching advertiser:', error);
        return null;
      }

      return {
        id: data.id,
        userId: data.user_id,
        profileId: data.profile_id,
        accountName: data.account_name,
        marketplace: data.marketplace,
        accountType: data.account_type,
        metadata: data.metadata
      };
    } catch (error) {
      console.error('Unexpected error fetching advertiser:', error);
      return null;
    }
  }

  /**
   * Update the last_used_at timestamp for an API key
   */
  static async updateApiKeyUsage(apiKey) {
    try {
      // Try updating key column
      const { error } = await supabase
        .from('api_keys')
        .update({ last_used_at: new Date().toISOString() })
        .eq('key', apiKey);

      if (error) {
        // Try updating key_value column
        await supabase
          .from('api_keys')
          .update({ last_used: new Date().toISOString() })
          .eq('key_value', apiKey);
      }
    } catch (error) {
      console.error('Error updating API key usage:', error);
    }
  }

  /**
   * Create a new API key for an advertiser
   */
  static async createApiKey(advertiserId) {
    try {
      const apiKey = crypto.randomUUID();
      
      const { error } = await supabase
        .from('api_keys')
        .insert({
          key: apiKey,
          advertiser_id: advertiserId,
          active: true
        });

      if (error) {
        console.error('Error creating API key:', error);
        return null;
      }

      return apiKey;
    } catch (error) {
      console.error('Unexpected error creating API key:', error);
      return null;
    }
  }

  /**
   * Get basic info for all advertisers (for debugging/admin purposes)
   */
  static async getAllAdvertisers() {
    try {
      const { data, error } = await supabase
        .from('advertisers')
        .select('id, account_name');

      if (error) {
        console.error('Error fetching advertisers:', error);
        return null;
      }

      return data.map(adv => ({
        id: adv.id,
        accountName: adv.account_name
      }));
    } catch (error) {
      console.error('Unexpected error fetching advertisers:', error);
      return null;
    }
  }
} 
```

--------------------------------------------------------------------------------
/scripts/check-api-keys.js:
--------------------------------------------------------------------------------

```javascript
// Script to check the api_keys table structure
const { createClient } = require('@supabase/supabase-js');
const dotenv = require('dotenv');
const path = require('path');

// Load environment variables from project root
dotenv.config({ path: path.resolve(__dirname, '../.env') });

// Validate environment variables
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

if (!supabaseUrl || !supabaseKey) {
  throw new Error('Missing Supabase environment variables. Please check your .env file.');
}

// Create Supabase client
const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    persistSession: false,
  }
});

async function checkApiKeysTable() {
  try {
    // Option 1: List all tables to confirm api_keys exists
    console.log('Checking database schema...');
    const { data: tables, error: schemaError } = await supabase
      .rpc('get_tables'); // This RPC function might not exist, so we'll try another approach if it fails
    
    if (!schemaError && tables) {
      console.log('Tables in database:', tables);
    } else {
      console.log('Could not retrieve tables using RPC, trying a different approach...');
    }
    
    // Option 2: Direct SQL query to get table info (this requires higher privileges)
    const { data: tableInfo, error: tableError } = await supabase
      .from('api_keys')
      .select('*')
      .limit(1);
    
    if (tableError) {
      console.error('Error accessing api_keys table:', tableError);
    } else {
      console.log('Successfully accessed api_keys table!');
      if (tableInfo && tableInfo.length > 0) {
        console.log('First record:', tableInfo[0]);
        console.log('Columns:', Object.keys(tableInfo[0]).join(', '));
      } else {
        console.log('The api_keys table exists but contains no records.');
        // Try to get column names from an empty table
        const { data: emptyData, error: emptyError } = await supabase
          .from('api_keys')
          .select('*')
          .limit(0);
          
        if (!emptyError) {
          console.log('Table exists, but no data to show column structure.');
        }
      }
    }
    
    // Option 3: Try introspection (may or may not work depending on permissions)
    console.log('\nTrying to get all tables via introspection...');
    try {
      const { data: allTables, error: allError } = await supabase
        .from('information_schema.tables')
        .select('table_name')
        .eq('table_schema', 'public');
        
      if (!allError) {
        console.log('Tables found via introspection:', allTables.map(t => t.table_name).join(', '));
      } else {
        console.error('Error getting tables via introspection:', allError);
      }
    } catch (e) {
      console.error('Exception during introspection:', e);
    }
  } catch (error) {
    console.error('Unexpected error:', error);
  }
}

// Run the function
checkApiKeysTable(); 
```

--------------------------------------------------------------------------------
/scripts/test-mcp-connection.js:
--------------------------------------------------------------------------------

```javascript
// Script to test MCP server connection
import axios from 'axios';
import dotenv from 'dotenv';
import path from 'path';
import { fileURLToPath } from 'url';

// Set up __dirname equivalent for ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Load environment variables
dotenv.config({ path: path.resolve(__dirname, '../.env') });

// API Key from environment or use your hard-coded key for testing
const API_KEY = process.env.TEST_API_KEY || 'amzn_ads_0018ad0985a04acc9b4ee7ea791192ba';

// MCP server URL
const MCP_SERVER_URL = process.env.MCP_SERVER_URL || 'http://localhost:3000';

async function testMcpConnection() {
  console.log('Testing MCP server connection...');
  console.log(`Server URL: ${MCP_SERVER_URL}`);
  console.log(`Using API Key: ${API_KEY}`);
  
  try {
    // Test the info endpoint
    console.log('\n1. Testing server info endpoint...');
    const infoResponse = await axios.get(`${MCP_SERVER_URL}/mcp/info`);
    console.log('Server Info:');
    console.log(JSON.stringify(infoResponse.data, null, 2));
    
    // Test authentication
    console.log('\n2. Testing authentication...');
    const authResponse = await axios.post(`${MCP_SERVER_URL}/mcp/authenticate`, {
      authToken: API_KEY
    });
    console.log('Authentication Response:');
    console.log(JSON.stringify(authResponse.data, null, 2));
    
    if (!authResponse.data.success) {
      console.error('Authentication failed. Cannot proceed with context tests.');
      return;
    }
    
    // Get the session token from the auth response
    const sessionToken = authResponse.data.sessionToken;
    
    // Test current advertiser context provider
    console.log('\n3. Testing current advertiser context provider...');
    const advertiserResponse = await axios.post(`${MCP_SERVER_URL}/mcp/context`, {
      sessionToken,
      providerName: 'advertiser',
      parameters: {}
    });
    console.log('Current Advertiser Response:');
    console.log(JSON.stringify(advertiserResponse.data, null, 2));
    
    // Test all advertisers context provider
    console.log('\n4. Testing all advertisers context provider...');
    const advertisersResponse = await axios.post(`${MCP_SERVER_URL}/mcp/context`, {
      sessionToken,
      providerName: 'advertisers',
      parameters: {}
    });
    console.log('All Advertisers Response:');
    console.log(JSON.stringify(advertisersResponse.data, null, 2));
    
    // Test campaigns context provider
    console.log('\n5. Testing campaigns context provider...');
    const campaignsResponse = await axios.post(`${MCP_SERVER_URL}/mcp/context`, {
      sessionToken,
      providerName: 'campaigns',
      parameters: {}
    });
    console.log('Campaigns Context Response:');
    console.log(JSON.stringify(campaignsResponse.data, null, 2));
    
    console.log('\nMCP connection test completed successfully!');
  } catch (error) {
    console.error('Error testing MCP connection:', error.message);
    if (error.response) {
      console.error('Response data:', error.response.data);
      console.error('Response status:', error.response.status);
    }
  }
}

// Run the test
testMcpConnection(); 
```

--------------------------------------------------------------------------------
/test-mcp.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import chalk from 'chalk';
import { spawn } from 'child_process';

console.log(chalk.blue('=== MCP Server Test Client ==='));
console.log(chalk.yellow('Starting test of Amazon Advertising MCP server...'));

// Configuration
const serverPath = './index.js';

// Create a transport that will spawn the server process
const transport = new StdioClientTransport({
  command: 'node',
  args: [serverPath],
  cwd: process.cwd(),
  env: {
    ...process.env,
    DEBUG: 'true',
    SUPABASE_URL: 'https://sdpmxiyzkdypufeedhoz.supabase.co',
    SUPABASE_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNkcG14aXl6a2R5cHVmZWVkaG96Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTY1NTc3MiwiZXhwIjoyMDU3MjMxNzcyfQ.x9tFYuoewa0I03UoJfAuwZJJLyHkSPCghjTeSLb7EqE',
    API_KEY: 'amzn_ads_0018ad0985a04acc9b4ee7ea791192ba'
  }
});

// Create the client
const client = new Client(
  {
    name: 'test-client',
    version: '1.0.0'
  },
  {
    capabilities: {
      tools: {},
      resources: {}
    }
  }
);

async function runTests() {
  try {
    console.log(chalk.yellow('Connecting to MCP server...'));
    await client.connect(transport);
    console.log(chalk.green('✓ Connected to MCP server'));

    // List available tools
    console.log(chalk.yellow('Listing tools...'));
    const tools = await client.listTools();
    console.log(chalk.green('✓ Available tools:'), tools);

    // Test ping tool
    console.log(chalk.yellow('Testing ping tool...'));
    const pingResult = await client.callTool({
      name: 'ping',
      arguments: {}
    });
    console.log(chalk.green('✓ Ping result:'), pingResult);

    // Test echo tool
    console.log(chalk.yellow('Testing echo tool...'));
    const echoResult = await client.callTool({
      name: 'echo',
      arguments: {
        message: 'Hello, MCP Server!'
      }
    });
    console.log(chalk.green('✓ Echo result:'), echoResult);
    
    // Test validateApiKey tool
    console.log(chalk.yellow('Testing validateApiKey tool...'));
    const validationResult = await client.callTool({
      name: 'validateApiKey',
      arguments: {}
    });
    console.log(chalk.green('✓ API Key validation result:'), validationResult);

    // Test advertiser tools if available
    try {
      console.log(chalk.yellow('Testing getAdvertiserInfo tool...'));
      const advertiserInfo = await client.callTool({
        name: 'getAdvertiserInfo',
        arguments: {}
      });
      console.log(chalk.green('✓ Advertiser info result:'), advertiserInfo);
    } catch (error) {
      console.log(chalk.red('✗ getAdvertiserInfo tool failed:'), error.message);
    }

    console.log(chalk.green('✓ All tests completed successfully'));
  } catch (error) {
    console.error(chalk.red('Error running tests:'), error);
  } finally {
    // Disconnect client
    try {
      console.log(chalk.yellow('Disconnecting...'));
      // Close the transport instead of calling disconnect
      await transport.close();
      console.log(chalk.green('✓ Disconnected from MCP server'));
    } catch (error) {
      console.error(chalk.red('Error disconnecting:'), error);
    }
    process.exit(0);
  }
}

runTests(); 
```

--------------------------------------------------------------------------------
/src/services/advertiser.service.ts:
--------------------------------------------------------------------------------

```typescript
import { supabase } from '../config/supabase.js';

export interface AdvertiserContext {
  id: string;
  userId: string;
  accountName: string;
  marketplace: string;
  accountType: string;
  profileId: string;
  metadata?: {
    countryCode?: string;
    currencyCode?: string;
    [key: string]: any;
  };
}

export class AdvertiserService {
  /**
   * Get advertiser context by ID
   * This will be used after API key validation
   */
  static async getAdvertiserContext(advertiserId: string): Promise<AdvertiserContext | null> {
    try {
      const { data, error } = await supabase
        .from('advertisers')
        .select('*')
        .eq('id', advertiserId)
        .single();

      if (error) {
        console.error('Error fetching advertiser:', error);
        return null;
      }

      if (!data) {
        return null;
      }

      return {
        id: data.id,
        userId: data.user_id,
        accountName: data.account_name,
        marketplace: data.marketplace,
        accountType: data.account_type,
        profileId: data.profile_id,
        metadata: data.metadata
      };
    } catch (error) {
      console.error('Error in getAdvertiserContext:', error);
      return null;
    }
  }

  /**
   * Update the last_used_at timestamp for an API key
   */
  static async updateApiKeyUsage(apiKey: string): Promise<boolean> {
    try {
      const { error } = await supabase
        .from('api_keys')
        .update({ last_used: new Date().toISOString() })
        .eq('key', apiKey);

      if (error) {
        console.error('Error updating API key usage:', error);
        return false;
      }

      return true;
    } catch (error) {
      console.error('Error in updateApiKeyUsage:', error);
      return false;
    }
  }

  /**
   * Create a new API key for an advertiser
   */
  static async createApiKey(advertiserId: string): Promise<string | null> {
    try {
      const apiKey = crypto.randomUUID();
      
      const { error } = await supabase
        .from('api_keys')
        .insert({
          key: apiKey,
          advertiser_id: advertiserId,
          active: true
        });

      if (error) {
        console.error('Error creating API key:', error);
        return null;
      }

      return apiKey;
    } catch (error) {
      console.error('Unexpected error creating API key:', error);
      return null;
    }
  }

  /**
   * Get basic info for all advertisers (for debugging/admin purposes)
   */
  static async getAllAdvertisers(): Promise<{ id: string; accountName: string }[] | null> {
    try {
      const { data, error } = await supabase
        .from('advertisers')
        .select('id, account_name');

      if (error) {
        console.error('Error fetching advertisers:', error);
        return null;
      }

      return data.map(adv => ({
        id: adv.id,
        accountName: adv.account_name
      }));
    } catch (error) {
      console.error('Unexpected error fetching advertisers:', error);
      return null;
    }
  }

  static async listAdvertisers(): Promise<AdvertiserContext[]> {
    try {
      const { data, error } = await supabase
        .from('advertisers')
        .select('*')
        .order('account_name', { ascending: true });

      if (error) {
        console.error('Error listing advertisers:', error);
        return [];
      }

      if (!data || data.length === 0) {
        return [];
      }

      return data.map((adv: any) => ({
        id: adv.id,
        userId: adv.user_id,
        accountName: adv.account_name,
        marketplace: adv.marketplace,
        accountType: adv.account_type,
        profileId: adv.profile_id,
        metadata: adv.metadata
      }));
    } catch (error) {
      console.error('Error in listAdvertisers:', error);
      return [];
    }
  }
} 
```

--------------------------------------------------------------------------------
/bin/cli.js:
--------------------------------------------------------------------------------

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

import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import os from 'os';
import chalk from 'chalk';
import { spawn } from 'child_process';
import readline from 'readline';

// Get the path to the index.js file
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const indexPath = path.resolve(__dirname, '../index.js');

// Available commands
const commands = {
  start: 'Start the MCP server',
  config: 'Configure Claude Desktop',
  test: 'Test the MCP connection'
};

// Extract command from arguments
const args = process.argv.slice(2);
const command = args[0] || 'help';

// Helper function to print help
function printHelp() {
  console.log(chalk.blue('\nAmazon Advertising MCP Server for Claude Desktop\n'));
  console.log('Usage: adspirer-mcp [command]\n');
  console.log('Commands:');
  Object.entries(commands).forEach(([cmd, desc]) => {
    console.log(`  ${chalk.green(cmd.padEnd(12))} ${desc}`);
  });
  console.log(`  ${chalk.green('help'.padEnd(12))} Show this help message\n`);
}

// Config command - configure Claude Desktop
function configureClaudeDesktop() {
  console.log(chalk.blue('\nConfiguring Claude Desktop...\n'));
  
  const configPaths = {
    win32: path.join(process.env.APPDATA, 'Claude', 'config.json'),
    darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'config.json'),
    linux: path.join(os.homedir(), '.config', 'Claude', 'config.json')
  };
  
  const configPath = configPaths[process.platform];
  if (!configPath) {
    console.log(chalk.red('\nUnsupported operating system for automatic Claude Desktop configuration.'));
    console.log('Please manually add the MCP server configuration to your Claude Desktop config file.');
    return;
  }
  
  console.log(`Configuring Claude Desktop at: ${configPath}`);
  
  // Check if the Claude config directory exists
  const configDir = path.dirname(configPath);
  if (!fs.existsSync(configDir)) {
    console.log('Claude Desktop config directory does not exist. Creating it...');
    fs.mkdirSync(configDir, { recursive: true });
  }
  
  // Read existing config or create new one
  let config = {};
  if (fs.existsSync(configPath)) {
    try {
      config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
      console.log('Existing Claude Desktop configuration found.');
    } catch (e) {
      console.log('Error reading Claude Desktop config. Creating a new one.');
    }
  }
  
  // Ensure mcpServers object exists
  if (!config.mcpServers) config.mcpServers = {};

  // Create readline interface for input
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  
  // Prompt for API key
  rl.question(chalk.yellow('\nEnter your Amazon Advertising API Key: '), (apiKey) => {
    // Configure the MCP server in Claude Desktop
    config.mcpServers['amazon-ads'] = {
      command: 'adspirer-mcp',
      args: ['start'],
      env: {
        API_KEY: apiKey,
        NODE_ENV: 'production'
      }
    };
    
    // Write the updated config
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    console.log(chalk.green(`\nClaude Desktop configuration updated at: ${configPath}`));
    console.log('You can now use the Amazon Advertising MCP Server with Claude Desktop.');
    rl.close();
  });
}

// Test command - run the test-mcp-connection.js script
function runTest() {
  console.log(chalk.blue('\nTesting MCP connection...\n'));
  const testPath = path.resolve(__dirname, '../scripts/test-mcp-connection.js');
  
  // Use dynamic import for the test script
  import(testPath)
    .catch(error => {
      console.error(chalk.red('\nError during test:'), error);
    });
}

// Start command - run the MCP server
function startMcpServer() {
  console.log(chalk.blue('\nStarting Amazon Advertising MCP Server...\n'));
  
  // Run the index.js file directly
  const child = spawn('node', [indexPath], {
    stdio: 'inherit'
  });
  
  child.on('error', (error) => {
    console.error(chalk.red('\nError starting MCP server:'), error);
  });
}

// Process commands
switch (command) {
  case 'start':
    startMcpServer();
    break;
  case 'config':
    configureClaudeDesktop();
    break;
  case 'test':
    runTest();
    break;
  case 'help':
    printHelp();
    break;
  default:
    console.log(chalk.red(`\nUnknown command: ${command}`));
    printHelp();
} 
```

--------------------------------------------------------------------------------
/src/types/database.types.ts:
--------------------------------------------------------------------------------

```typescript
export interface Database {
  public: {
    Tables: {
      api_keys: {
        Row: {
          id: string;
          key: string;
          advertiser_id: string;
          active: boolean;
          created_at: string;
          last_used_at: string | null;
        };
        Insert: {
          id?: string;
          key: string;
          advertiser_id: string;
          active?: boolean;
          created_at?: string;
          last_used_at?: string | null;
        };
        Update: {
          id?: string;
          key?: string;
          advertiser_id?: string;
          active?: boolean;
          created_at?: string;
          last_used_at?: string | null;
        };
      };
      advertisers: {
        Row: {
          id: string;
          user_id: string;
          profile_id: string;
          account_name: string;
          marketplace: string;
          account_type: string;
          connected_since: string;
          metadata: {
            timezone: string;
            accountInfo: {
              id: string;
              name: string;
              type: string;
              validPaymentMethod: boolean;
              marketplaceStringId: string;
            };
            countryCode: string;
            currencyCode: string;
          };
        };
        Insert: {
          id?: string;
          user_id: string;
          profile_id: string;
          account_name: string;
          marketplace: string;
          account_type: string;
          connected_since?: string;
          metadata?: {
            timezone: string;
            accountInfo: {
              id: string;
              name: string;
              type: string;
              validPaymentMethod: boolean;
              marketplaceStringId: string;
            };
            countryCode: string;
            currencyCode: string;
          };
        };
        Update: {
          id?: string;
          user_id?: string;
          profile_id?: string;
          account_name?: string;
          marketplace?: string;
          account_type?: string;
          connected_since?: string;
          metadata?: {
            timezone: string;
            accountInfo: {
              id: string;
              name: string;
              type: string;
              validPaymentMethod: boolean;
              marketplaceStringId: string;
            };
            countryCode: string;
            currencyCode: string;
          };
        };
      };
      amazon_tokens: {
        Row: {
          id: string;
          user_id: string;
          access_token: string;
          refresh_token: string;
          token_type: string;
          expires_at: string;
          created_at: string;
          updated_at: string;
        };
        Insert: {
          id?: string;
          user_id: string;
          access_token: string;
          refresh_token: string;
          token_type: string;
          expires_at: string;
          created_at?: string;
          updated_at?: string;
        };
        Update: {
          id?: string;
          user_id?: string;
          access_token?: string;
          refresh_token?: string;
          token_type?: string;
          expires_at?: string;
          created_at?: string;
          updated_at?: string;
        };
      };
      campaigns: {
        Row: {
          id: string;
          advertiser_id: string;
          campaign_id: string;
          name: string;
          state: string;
          type: string;
          targeting_type: string;
          start_date: string;
          end_date: string | null;
          budget: number;
          budget_type: string;
          created_at: string;
          updated_at: string;
        };
        Insert: {
          id?: string;
          advertiser_id: string;
          campaign_id: string;
          name: string;
          state: string;
          type: string;
          targeting_type: string;
          start_date: string;
          end_date?: string | null;
          budget: number;
          budget_type: string;
          created_at?: string;
          updated_at?: string;
        };
        Update: {
          id?: string;
          advertiser_id?: string;
          campaign_id?: string;
          name?: string;
          state?: string;
          type?: string;
          targeting_type?: string;
          start_date?: string;
          end_date?: string | null;
          budget?: number;
          budget_type?: string;
          created_at?: string;
          updated_at?: string;
        };
      };
      campaign_metrics: {
        Row: {
          id: string;
          campaign_id: string;
          date: string;
          impressions: number;
          clicks: number;
          spend: number;
          sales: number;
          units_sold: number;
          acos: number;
          roas: number;
          created_at: string;
          updated_at: string | null;
        };
        Insert: {
          id?: string;
          campaign_id: string;
          date: string;
          impressions: number;
          clicks: number;
          spend: number;
          sales: number;
          units_sold: number;
          acos: number;
          roas: number;
          created_at?: string;
          updated_at?: string | null;
        };
        Update: {
          id?: string;
          campaign_id?: string;
          date?: string;
          impressions?: number;
          clicks?: number;
          spend?: number;
          sales?: number;
          units_sold?: number;
          acos?: number;
          roas?: number;
          created_at?: string;
          updated_at?: string | null;
        };
      };
    };
    Views: {
      [_ in never]: never;
    };
    Functions: {
      [_ in never]: never;
    };
    Enums: {
      [_ in never]: never;
    };
  };
} 
```

--------------------------------------------------------------------------------
/Project-Docs/Project-plan.txt:
--------------------------------------------------------------------------------

```
# Amazon Ads MCP Server Implementation Plan

## Overall Project Context

### The Amazon Advertising Challenge

Amazon sellers and brands face increasingly complex challenges managing their advertising campaigns. With multiple campaign types, constantly changing metrics, and competitive marketplaces, advertisers need both powerful analytics and actionable insights. Many struggle with:

- Understanding campaign performance across different products
- Optimizing budgets based on performance data
- Identifying trends and opportunities in complex datasets
- Making data-driven decisions quickly without technical expertise

### Our Integrated Solution

We're building a comprehensive ecosystem for Amazon advertisers consisting of two key components:

1. **Web Application** - The central hub where advertisers:
   - Authenticate with Amazon through Login with Amazon (LWA)
   - Manage Amazon advertising profiles, campaigns, Ads groups, metrics in supabase
   - Generate API keys for each advertiser
   - Thw web app all it does it Auth with LWA and pulls advertiser data and generated an API keys
   - Token refresh, API life cycle is all managed in the webapp

2. **MCP Server** - The Claude integration layer that allows advertisers to:
   - Have natural language conversations about their ad performance
   - Make campaign adjustments through simple commands
   - Receive AI-powered recommendations and insights
   - Analyze performance without needing to understand complex metrics
   - Take actions directly within Claude Desktop

This dual approach combines structured dashboard analytics with conversational AI capabilities, meeting advertisers where they work.

## The Advertiser Journey

1. **Onboarding & Authentication**
   - Advertiser signs up on our web application
   - Connects their Amazon Advertising account via LWA
   - Web app exchanges OAuth code for access tokens
   - System retrieves and stores profile data and campaign history

2. **Data Analysis & Management**
   - Web app provides traditional dashboard analytics
   - Advertiser can view and manage campaigns through familiar interfaces
   - System regularly syncs campaign data to maintain current metrics

3. **AI-Powered Assistance**
   - Advertiser generates an API key from the web application
   - Configures Claude Desktop with the MCP server and API key
   - Begins asking questions and managing campaigns conversationally
   - Claude interprets data and presents insights in natural language

## MCP Server Implementation

### Architecture Overview

The MCP server will be designed as a standalone Node.js application that:

1. Validates advertiser API keys against our Supabase database
2. Retrieves advertiser-specific campaign data
3. Exposes standardized tools and resources through the MCP protocol
4. Enables Claude to access and interpret campaign performance

The server will use a service account to access Supabase, with all queries scoped to the authenticated advertiser's ID to maintain data isolation.

### Implementation Phases

#### Phase 1: Foundation & Authentication

We'll begin by establishing the MCP server's core structure and authentication mechanism:

1. Set up the TypeScript project with MCP SDK dependencies
2. Implement API key validation against the Supabase `api_keys` table
3. Create the advertiser context once authentication succeeds
4. Establish secure connection to Supabase

#### Phase 2: Data Access Layer

Next, we'll develop a comprehensive data access layer:

1. Create query functions for campaigns, metrics, and advertiser data
2. Implement smart filtering by advertiser ID on all queries
3. Build data aggregation utilities for metrics analysis
4. Add caching for frequently accessed data

#### Phase 3: MCP Resources & Tools

With data access in place, we'll implement the MCP interface:

1. Build campaign resources (listings, details, metrics)
2. Create campaign management tools (pause, enable, archive)
3. Implement budget adjustment tools with validation
4. Develop performance analysis and recommendation tools

#### Phase 4: Packaging & Distribution

Finally, we'll prepare for distribution:

1. Set up NPM packaging for easy installation
2. Create user documentation and examples
3. Implement versioning strategy
4. Establish testing pipeline

## Value Proposition for Advertisers

The integration of our web application with Claude through the MCP server delivers unique value to advertisers:

1. **Democratized Analytics** - Non-technical advertisers can access complex performance data through natural conversation
2. **Contextual Insights** - Claude can interpret trends and suggest optimizations based on historical and current data
3. **Operational Efficiency** - Advertisers can quickly make campaign adjustments without switching contexts
4. **Reduced Learning Curve** - Natural language interface eliminates need to learn complex dashboard navigation
5. **Proactive Management** - Claude can suggest optimizations based on performance patterns

## Security & Privacy Considerations

Throughout implementation, we'll maintain strict security practices:

1. **Data Isolation** - Each advertiser's data is strictly isolated
2. **Minimal Permissions** - Service accounts use row-level security with minimal access
3. **Secure Authentication** - API keys are validated and never exposed
4. **No Data Storage** - The MCP server doesn't store data locally

## Next Steps

The immediate actions will be:

1. Finalize the web application's API key generation capability
2. Create the MCP server repository and development environment
3. Implement the core authentication and data access layers
4. Develop initial tools and resources for campaign management
5. Begin testing with Claude Desktop using sample advertiser accounts

This implementation plan connects our existing web application with Claude's capabilities through a secure, focused MCP server, creating a powerful ecosystem for Amazon advertisers to manage and optimize their campaigns through both traditional interfaces and conversational AI.
```

--------------------------------------------------------------------------------
/scripts/create-package.js:
--------------------------------------------------------------------------------

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

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
const rootDir = path.join(__dirname, '..');
const packageJson = require('../package.json');
const distDir = path.join(rootDir, 'dist-package');

async function createPackage() {
  console.log('Creating distribution package...');
  
  // Create dist directory if it doesn't exist
  if (!fs.existsSync(distDir)) {
    fs.mkdirSync(distDir, { recursive: true });
  } else {
    // Clean existing files in dist directory
    fs.readdirSync(distDir).forEach(file => {
      const filePath = path.join(distDir, file);
      if (fs.lstatSync(filePath).isDirectory()) {
        fs.rmSync(filePath, { recursive: true, force: true });
      } else {
        fs.unlinkSync(filePath);
      }
    });
  }

  // Copy necessary files
  const filesToCopy = [
    'dist', 
    'node_modules',
    'package.json',
    'package-lock.json',
    'README.md',
    'setup-credentials.js',
    'index.js',
    'claude-desktop-config.json',
    '.env.example'
  ];

  filesToCopy.forEach(file => {
    const sourcePath = path.join(rootDir, file);
    const destPath = path.join(distDir, file);
    
    if (fs.existsSync(sourcePath)) {
      if (fs.lstatSync(sourcePath).isDirectory()) {
        fs.cpSync(sourcePath, destPath, { recursive: true });
      } else {
        fs.copyFileSync(sourcePath, destPath);
      }
    }
  });

  // Create installation script
  const installScript = `#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const os = require('os');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

console.log('\\n=== Amazon Advertising MCP Server for Claude Desktop - Setup ===\\n');

function runSetup() {
  console.log('Running setup...');
  try {
    // Run setup-credentials.js to securely set up Supabase credentials
    console.log('\\nSetting up Supabase credentials...');
    require('./setup-credentials.js');
    
    // Create Claude Desktop configuration
    setupClaudeDesktop();
  } catch (error) {
    console.error('Error during setup:', error);
  }
}

function setupClaudeDesktop() {
  const configPaths = {
    win32: path.join(process.env.APPDATA, 'Claude', 'config.json'),
    darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'config.json'),
    linux: path.join(os.homedir(), '.config', 'Claude', 'config.json')
  };
  
  const configPath = configPaths[process.platform];
  if (!configPath) {
    console.log('\\nUnsupported operating system for automatic Claude Desktop configuration.');
    console.log('Please manually add the MCP server configuration to your Claude Desktop config file.');
    return;
  }
  
  console.log(\`\\nConfiguring Claude Desktop at: \${configPath}\`);
  
  // Check if the Claude config directory exists
  const configDir = path.dirname(configPath);
  if (!fs.existsSync(configDir)) {
    console.log('Claude Desktop config directory does not exist. Creating it...');
    fs.mkdirSync(configDir, { recursive: true });
  }
  
  // Read existing config or create new one
  let config = {};
  if (fs.existsSync(configPath)) {
    try {
      config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
      console.log('Existing Claude Desktop configuration found.');
    } catch (e) {
      console.log('Error reading Claude Desktop config. Creating a new one.');
    }
  }
  
  // Ensure mcpServers object exists
  if (!config.mcpServers) config.mcpServers = {};
  
  // Get current directory
  const currentDir = process.cwd();
  
  // Set up the MCP server configuration
  rl.question('\\nEnter your Amazon Advertising API Key: ', (apiKey) => {
    config.mcpServers['amazon-ads'] = {
      command: 'node',
      args: [path.join(currentDir, 'index.js')],
      cwd: currentDir,
      env: {
        API_KEY: apiKey,
        NODE_ENV: 'production'
      }
    };
    
    // Write the updated config
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
    console.log(\`\\nClaude Desktop configuration updated at: \${configPath}\`);
    console.log('\\nSetup complete! You can now use the Amazon Advertising MCP Server with Claude Desktop.');
    rl.close();
  });
}

runSetup();
`;

  fs.writeFileSync(path.join(distDir, 'install.js'), installScript);
  fs.chmodSync(path.join(distDir, 'install.js'), '755');

  // Create simple README with installation instructions
  const readmeContent = `# Amazon Advertising MCP Server for Claude Desktop

## Installation

1. Extract this package to a directory on your computer
2. Install dependencies:
   \`\`\`
   npm install
   \`\`\`
3. Run the installation script:
   \`\`\`
   node install.js
   \`\`\`
   This will:
   - Set up your Supabase credentials securely
   - Configure Claude Desktop to use this MCP server
   - Prompt you for your Amazon Advertising API Key

## Manual Configuration

If the automatic setup doesn't work, you can configure Claude Desktop manually:

- Edit your Claude Desktop config file:
  - Windows: \`%APPDATA%\\Claude\\config.json\`
  - macOS: \`~/Library/Application Support/Claude/config.json\`
  - Linux: \`~/.config/Claude/config.json\`

- Add this configuration (replace paths and API key with your own):
\`\`\`json
{
  "mcpServers": {
    "amazon-ads": {
      "command": "node",
      "args": ["/absolute/path/to/extracted/package/index.js"],
      "cwd": "/absolute/path/to/extracted/package",
      "env": {
        "API_KEY": "your_api_key_here",
        "NODE_ENV": "production"
      }
    }
  }
}
\`\`\`
`;

  fs.writeFileSync(path.join(distDir, 'INSTALL.md'), readmeContent);

  // Create archive
  try {
    const packageName = `${packageJson.name}-${packageJson.version}`;
    const archivePath = path.join(rootDir, `${packageName}.zip`);
    
    console.log(`Creating archive: ${archivePath}`);
    await execAsync(`cd "${distDir}" && zip -r "${archivePath}" .`);
    
    console.log(`\nDistribution package created successfully at: ${archivePath}`);
    console.log('You can distribute this ZIP file to your users.');
  } catch (error) {
    console.error('Error creating archive:', error);
  }
}

createPackage().catch(console.error); 
```

--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------

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

// Amazon Advertising MCP Server for Claude Desktop
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import dotenv from 'dotenv';
import { supabase, validateApiKey } from './src/config/supabase.js';
import { AdvertiserService } from './src/services/advertiser.service.js';
import fs from 'fs';
import { z } from 'zod';

// Load environment variables
dotenv.config();

// Set up debugging log
const logFile = fs.createWriteStream('mcp-debug.log', { flags: 'a' });
function debug(message) {
  const timestamp = new Date().toISOString();
  const logMessage = `${timestamp} - ${message}\n`;
  logFile.write(logMessage);
  // Also log to stderr for console viewing during development
  console.error(logMessage);
}

debug('Starting Amazon Advertising MCP Server...');
debug(`MCP Server Version: ${process.env.MCP_SERVER_VERSION || 'dev'}`);
debug(`Received API_KEY: ${process.env.API_KEY ? 'YES (set)' : 'NO (not set)'}`);
debug(`Environment variables: ${JSON.stringify(process.env, (key, value) => {
  // Mask sensitive values but show which variables exist
  if (key.toUpperCase().includes('KEY') || key.toUpperCase().includes('SECRET')) {
    return value ? '[SET]' : '[NOT SET]';
  }
  return value;
}, 2)}`);

// Initialize MCP Server
const server = new McpServer({
  name: "Amazon Advertising",
  version: process.env.MCP_SERVER_VERSION || '1.0.0'
});

// Define the getAdvertiserInfo tool with proper schema format
server.tool("getAdvertiserInfo", 
  {}, // Empty schema since no parameters needed
  async () => {
    debug('Received request for getAdvertiserInfo');
    
    try {
      // For testing, get the first advertiser from the database
      const { data: advertisers, error } = await supabase
        .from('advertisers')
        .select('*')
        .limit(1);
        
      if (error || !advertisers || advertisers.length === 0) {
        debug(`Error or no advertisers found: ${error ? error.message : 'No advertisers'}`);
        return {
          content: [{ type: "text", text: "Error: No advertiser accounts found" }],
          isError: true
        };
      }
      
      const advertiser = advertisers[0];
      debug(`Found advertiser: ${advertiser.account_name}`);
      
      return {
        content: [{ 
          type: "text", 
          text: JSON.stringify({
            id: advertiser.id,
            name: advertiser.account_name,
            marketplace: advertiser.marketplace,
            accountType: advertiser.account_type,
            profileId: advertiser.profile_id,
            countryCode: advertiser.metadata?.countryCode || 'Unknown',
            currencyCode: advertiser.metadata?.currencyCode || 'USD'
          }, null, 2)
        }]
      };
    } catch (error) {
      debug(`Error getting advertiser info: ${error.message}`);
      return {
        content: [{ type: "text", text: `Error: ${error.message}` }],
        isError: true
      };
    }
  }
);

// Define the listAdvertiserAccounts tool with proper schema format
server.tool("listAdvertiserAccounts", 
  {}, // Empty schema since no parameters needed
  async () => {
    debug('Received request for listAdvertiserAccounts');
    
    try {
      // Get all advertisers
      const { data: advertisers, error } = await supabase
        .from('advertisers')
        .select('id, account_name, marketplace, account_type, metadata')
        .order('account_name', { ascending: true });
        
      if (error) {
        debug(`Error fetching advertisers: ${error.message}`);
        return {
          content: [{ type: "text", text: `Error: ${error.message}` }],
          isError: true
        };
      }
      
      if (!advertisers || advertisers.length === 0) {
        debug('No advertisers found');
        return {
          content: [{ 
            type: "text", 
            text: "No advertiser accounts were found in the database"
          }]
        };
      }
      
      // Format the advertiser data
      const formattedAdvertisers = advertisers.map(adv => ({
        id: adv.id,
        accountName: adv.account_name,
        marketplace: adv.marketplace,
        accountType: adv.account_type,
        countryCode: adv.metadata?.countryCode || 'Unknown',
        currencyCode: adv.metadata?.currencyCode || 'USD'
      }));
      
      debug(`Found ${formattedAdvertisers.length} advertisers`);
      
      return {
        content: [{ 
          type: "text", 
          text: JSON.stringify(formattedAdvertisers, null, 2)
        }]
      };
    } catch (error) {
      debug(`Error listing advertiser accounts: ${error.message}`);
      return {
        content: [{ type: "text", text: `Error: ${error.message}` }],
        isError: true
      };
    }
  }
);

// Add a simple non-database dependent tool for testing connectivity
server.tool("ping", 
  {}, // No parameters needed
  async () => {
    debug('Received ping request - testing connection');
    return {
      content: [{ 
        type: "text", 
        text: `Server is running correctly!
Time: ${new Date().toISOString()}
Server name: Amazon Advertising
Server version: ${process.env.MCP_SERVER_VERSION || '1.0.0'}`
      }]
    };
  }
);

// Add an echo tool for testing with parameters
server.tool("echo", 
  { message: z.string() },
  async ({ message }) => {
    debug(`Received echo request with message: ${message}`);
    return {
      content: [{ 
        type: "text", 
        text: `You said: ${message}`
      }]
    };
  }
);

// Add a tool for validating API key that doesn't rely on database access
server.tool("validateApiKey", 
  {}, // No parameters needed
  async () => {
    debug('Received request to validate API key');
    const apiKey = process.env.API_KEY;
    
    if (!apiKey) {
      debug('No API key provided in environment variables');
      return {
        content: [{ 
          type: "text", 
          text: `Error: No API_KEY provided in environment variables.`
        }],
        isError: true
      };
    }
    
    try {
      debug(`Attempting to validate API key: ${apiKey.substring(0, 8)}...`);
      const { valid, advertiserId, error } = await validateApiKey(apiKey);
      
      if (!valid) {
        debug(`API key validation failed: ${error}`);
        return {
          content: [{ 
            type: "text", 
            text: `API key validation failed: ${error || 'Unknown error'}`
          }],
          isError: true
        };
      }
      
      debug(`API key validated successfully for advertiser ID: ${advertiserId}`);
      return {
        content: [{ 
          type: "text", 
          text: `API key is valid for advertiser ID: ${advertiserId}`
        }]
      };
    } catch (error) {
      debug(`Error validating API key: ${error.message}`);
      return {
        content: [{ 
          type: "text", 
          text: `Error validating API key: ${error.message}. This could be due to missing database credentials.`
        }],
        isError: true
      };
    }
  }
);

// Start the server with stdin/stdout transport
debug('Starting MCP server with stdio transport');
const transport = new StdioServerTransport();

// Add better error handling for server connection
try {
  debug('Attempting to connect MCP server to transport');
  server.connect(transport).then(() => {
    debug('MCP server successfully connected to transport and ready to handle requests from Claude Desktop');
    
    // Keep the process alive
    process.on('SIGINT', () => {
      debug('Received SIGINT, shutting down server...');
      process.exit(0);
    });
  }).catch(error => {
    debug(`Error connecting MCP server to transport: ${error.message}\n${error.stack}`);
  });
} catch (error) {
  debug(`Critical error starting MCP server: ${error.message}\n${error.stack}`);
}

// Add heartbeat logging to confirm server is alive
setInterval(() => {
  debug('MCP server heartbeat - still running');
}, 60000); // Log every minute

// Add error handling for uncaught exceptions
process.on('uncaughtException', (error) => {
  debug(`Uncaught exception: ${error.message}\n${error.stack}`);
  // Keep the server running despite errors
});

process.on('unhandledRejection', (reason, promise) => {
  debug(`Unhandled promise rejection: ${reason}`);
  // Keep the server running despite errors
}); 
```

--------------------------------------------------------------------------------
/Project-Docs/MCP TypeScript SDK.txt:
--------------------------------------------------------------------------------

```
# MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)

## Table of Contents
- [Overview](#overview)
- [Installation](#installation)
- [Quickstart](#quickstart)
- [What is MCP?](#what-is-mcp)
- [Core Concepts](#core-concepts)
  - [Server](#server)
  - [Resources](#resources)
  - [Tools](#tools)
  - [Prompts](#prompts)
- [Running Your Server](#running-your-server)
  - [stdio](#stdio)
  - [HTTP with SSE](#http-with-sse)
  - [Testing and Debugging](#testing-and-debugging)
- [Examples](#examples)
  - [Echo Server](#echo-server)
  - [SQLite Explorer](#sqlite-explorer)
- [Advanced Usage](#advanced-usage)
  - [Low-Level Server](#low-level-server)
  - [Writing MCP Clients](#writing-mcp-clients)
  - [Server Capabilities](#server-capabilities)

## Overview

The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:

- Build MCP clients that can connect to any MCP server
- Create MCP servers that expose resources, prompts and tools
- Use standard transports like stdio and SSE
- Handle all MCP protocol messages and lifecycle events

## Installation

```bash
npm install @modelcontextprotocol/sdk
```

## Quick Start

Let's create a simple MCP server that exposes a calculator tool and some data:

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create an MCP server
const server = new McpServer({
  name: "Demo",
  version: "1.0.0"
});

// Add an addition tool
server.tool("add",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }]
  })
);

// Add a dynamic greeting resource
server.resource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  async (uri, { name }) => ({
    contents: [{
      uri: uri.href,
      text: `Hello, ${name}!`
    }]
  })
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
```

## What is MCP?

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:

- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
- And more!

## Core Concepts

### Server

The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:

```typescript
const server = new McpServer({
  name: "My App",
  version: "1.0.0"
});
```

### Resources

Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:

```typescript
// Static resource
server.resource(
  "config",
  "config://app",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// Dynamic resource with parameters
server.resource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);
```

### Tools

Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:

```typescript
// Simple tool with parameters
server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

// Async tool with external API call
server.tool(
  "fetch-weather",
  { city: z.string() },
  async ({ city }) => {
    const response = await fetch(`https://api.weather.com/${city}`);
    const data = await response.text();
    return {
      content: [{ type: "text", text: data }]
    };
  }
);
```

### Prompts

Prompts are reusable templates that help LLMs interact with your server effectively:

```typescript
server.prompt(
  "review-code",
  { code: z.string() },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please review this code:\n\n${code}`
      }
    }]
  })
);
```

## Running Your Server

MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:

### stdio

For command-line tools and direct integrations:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const transport = new StdioServerTransport();
await server.connect(transport);
```

### HTTP with SSE

For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:

```typescript
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const app = express();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  // Note: to support multiple simultaneous connections, these messages will
  // need to be routed to a specific matching transport. (This logic isn't
  // implemented here, for simplicity.)
  await transport.handlePostMessage(req, res);
});

app.listen(3001);
```

### Testing and Debugging

To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.

## Examples

### Echo Server

A simple server demonstrating resources, tools, and prompts:

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "Echo",
  version: "1.0.0"
});

server.resource(
  "echo",
  new ResourceTemplate("echo://{message}", { list: undefined }),
  async (uri, { message }) => ({
    contents: [{
      uri: uri.href,
      text: `Resource echo: ${message}`
    }]
  })
);

server.tool(
  "echo",
  { message: z.string() },
  async ({ message }) => ({
    content: [{ type: "text", text: `Tool echo: ${message}` }]
  })
);

server.prompt(
  "echo",
  { message: z.string() },
  ({ message }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please process this message: ${message}`
      }
    }]
  })
);
```

### SQLite Explorer

A more complex example showing database integration:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";

const server = new McpServer({
  name: "SQLite Explorer",
  version: "1.0.0"
});

// Helper to create DB connection
const getDb = () => {
  const db = new sqlite3.Database("database.db");
  return {
    all: promisify<string, any[]>(db.all.bind(db)),
    close: promisify(db.close.bind(db))
  };
};

server.resource(
  "schema",
  "schema://main",
  async (uri) => {
    const db = getDb();
    try {
      const tables = await db.all(
        "SELECT sql FROM sqlite_master WHERE type='table'"
      );
      return {
        contents: [{
          uri: uri.href,
          text: tables.map((t: {sql: string}) => t.sql).join("\n")
        }]
      };
    } finally {
      await db.close();
    }
  }
);

server.tool(
  "query",
  { sql: z.string() },
  async ({ sql }) => {
    const db = getDb();
    try {
      const results = await db.all(sql);
      return {
        content: [{
          type: "text",
          text: JSON.stringify(results, null, 2)
        }]
      };
    } catch (err: unknown) {
      const error = err as Error;
      return {
        content: [{
          type: "text",
          text: `Error: ${error.message}`
        }],
        isError: true
      };
    } finally {
      await db.close();
    }
  }
);
```

## Advanced Usage

### Low-Level Server

For more control, you can use the low-level Server class directly:

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListPromptsRequestSchema,
  GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {}
    }
  }
);

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [{
      name: "example-prompt",
      description: "An example prompt template",
      arguments: [{
        name: "arg1",
        description: "Example argument",
        required: true
      }]
    }]
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  if (request.params.name !== "example-prompt") {
    throw new Error("Unknown prompt");
  }
  return {
    description: "Example prompt",
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: "Example prompt text"
      }
    }]
  };
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Writing MCP Clients

The SDK provides a high-level client interface:

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"]
});

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {},
      resources: {},
      tools: {}
    }
  }
);

await client.connect(transport);

// List prompts
const prompts = await client.listPrompts();

// Get a prompt
const prompt = await client.getPrompt("example-prompt", {
  arg1: "value"
});

// List resources
const resources = await client.listResources();

// Read a resource
const resource = await client.readResource("file:///example.txt");

// Call a tool
const result = await client.callTool({
  name: "example-tool",
  arguments: {
    arg1: "value"
  }
});
```

## Documentation

- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [MCP Specification](https://spec.modelcontextprotocol.io)
- [Example Servers](https://github.com/modelcontextprotocol/servers)

## Contributing

Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.

## License

This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
```
Page 1/2FirstPrevNextLast