This is page 1 of 2. Use http://codebase.md/amekala/adspirer-mcp-server?lines=true&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:
--------------------------------------------------------------------------------
```
1 | # Supabase credentials (required for build)
2 | SUPABASE_URL=https://your-project.supabase.co
3 | SUPABASE_KEY=your-supabase-service-role-key
4 | 
5 | # Optional configuration
6 | DEBUG=false
7 | NODE_ENV=production 
```
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
 1 | # Development files
 2 | node_modules/
 3 | .git/
 4 | .gitignore
 5 | *.log
 6 | .env
 7 | .env.*
 8 | !.env.example
 9 | 
10 | # Test files
11 | test-mcp.js
12 | 
13 | # Build artifacts
14 | dist-package/
15 | *.zip
16 | 
17 | # Documentation
18 | Project-Docs/
19 | 
20 | # Misc
21 | output.log
22 | mcp-debug.log 
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log
 4 | yarn-error.log
 5 | package-lock.json
 6 | 
 7 | # Environment Variables and Credentials
 8 | .env
 9 | .env.*
10 | !.env.example
11 | .env.supabase
12 | 
13 | # Logs
14 | logs/
15 | *.log
16 | mcp-debug.log
17 | 
18 | # Build files
19 | dist/
20 | build/
21 | 
22 | # OS specific files
23 | .DS_Store
24 | Thumbs.db
25 | 
26 | # IDE files
27 | .idea/
28 | .vscode/
29 | *.sublime-project
30 | *.sublime-workspace
31 | 
32 | # Credentials
33 | .env
34 | .env.*
35 | !.env.example
36 | credentials.js
37 | 
38 | # Logs
39 | logs
40 | *.log
41 | npm-debug.log*
42 | yarn-debug.log*
43 | yarn-error.log*
44 | lerna-debug.log*
45 | .pnpm-debug.log*
46 | mcp-debug.log
47 | output.log
48 | 
49 | # Dependency directories
50 | jspm_packages/
51 | 
52 | # Build output
53 | dist-package/
54 | *.zip
55 | 
56 | # TypeScript cache
57 | *.tsbuildinfo
58 | 
59 | # Optional npm cache directory
60 | .npm
61 | 
62 | # Optional eslint cache
63 | .eslintcache
64 | 
65 | # dotenv environment variable files
66 | .env.development.local
67 | .env.test.local
68 | .env.production.local
69 | .env.local
70 | .env.supabase
71 | 
72 | # OS files
73 | .DS_Store
74 | Thumbs.db 
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
 1 | # Ads Manager MCP server for Claude
 2 | 
 3 | Transform your advertising experience with AI-powered insights and management - right inside Claude!
 4 | 
 5 | ## What It Does
 6 | 
 7 | This Ads MCP server is your AI assistant for managing digital advertising campaigns. Through simple conversations in Claude, you can:
 8 | 
 9 | - Analyze campaign performance and get actionable insights
10 | - Create and manage advertising campaigns
11 | - Visualize campaign metrics with interactive charts
12 | - Receive personalized optimization recommendations
13 | - Adjust budgets, bids, and targeting on the fly
14 | - Get alerts for underperforming campaigns
15 | 
16 | No more switching between multiple dashboards and reports - just chat naturally with Claude!
17 | 
18 | ## Platforms Supported
19 | 
20 | - **Amazon Ads** - Available now!
21 | - **Walmart Ads** - Coming soon
22 | - **Meta Ads** - Coming soon
23 | - **Google Ads** - Coming soon
24 | 
25 | ## Getting Started
26 | 
27 | ### 1. Get Your API Key
28 | 
29 | Visit [Adspirer.com](https://www.adspirer.com/) to connect your ad accounts:
30 | - Sign up for a free account
31 | - Connect your advertising accounts via platform authentication
32 | - Copy your API key from your dashboard
33 | 
34 | ### 2. Install the Server
35 | 
36 | ```bash
37 | # Install globally with npm
38 | npm install -g adspirer-mcp-server
39 | 
40 | # Configure Claude Desktop automatically
41 | adspirer-mcp config
42 | ```
43 | 
44 | During configuration, you'll be prompted for your API key from Adspirer.
45 | 
46 | ### 3. Claude Desktop Configuration
47 | 
48 | The `adspirer-mcp config` command will automatically update your Claude Desktop configuration file, but you can also manually set it up:
49 | 
50 | ```json
51 | {
52 |   "mcpServers": {
53 |     "adspirer": {
54 |       "command": "adspirer-mcp",
55 |       "args": ["start"],
56 |       "env": {
57 |         "API_KEY": "your_api_key_here"
58 |       }
59 |     }
60 |   }
61 | }
62 | ```
63 | 
64 | Save this to:
65 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
66 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
67 | 
68 | ### 4. Start Using It in Claude!
69 | 
70 | Open Claude and start asking about your campaigns:
71 | - "How are my Amazon ad campaigns performing this week?"
72 | - "Show me a chart of my best performing keywords"
73 | - "Increase the budget for my 'Summer Sale' campaign by 20%"
74 | 
75 | ## Examples
76 | 
77 | Ask Claude questions like:
78 | - "Which campaigns have the best RoAS?"
79 | - "Show me trends in my ad spend over the last 30 days"
80 | - "What optimization opportunities do you see in my campaigns?"
81 | - "Create a new Sponsored Products campaign for my top selling item"
82 | 
83 | ## Troubleshooting
84 | 
85 | Having issues?
86 | - Make sure Claude Desktop is running the latest version
87 | - Check that your API key is entered correctly
88 | - Run `adspirer-mcp test` to verify your connection
89 | 
90 | ## Resources
91 | 
92 | - [Full Documentation](https://docs.adspirer.com)
93 | - [Video Tutorial](https://adspirer.com/tutorials)
94 | - [Support](https://adspirer.com/support)
95 | 
96 | ---
97 | 
98 | Built with ❤️ by Adspirer - Supercharging Advertisers with AI
99 | 
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 |  
```
--------------------------------------------------------------------------------
/claude-desktop-config.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "mcpServers": {
 3 |     "amazon-ads": {
 4 |       "command": "adspirer-mcp",
 5 |       "args": ["start"],
 6 |       "env": {
 7 |         "API_KEY": "your_api_key_here",
 8 |         "NODE_ENV": "production"
 9 |       }
10 |     }
11 |   }
12 | } 
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "strict": true,
 8 |     "skipLibCheck": true,
 9 |     "outDir": "dist",
10 |     "rootDir": ".",
11 |     "sourceMap": true
12 |   },
13 |   "include": ["src/**/*", "scripts/**/*"],
14 |   "exclude": ["node_modules", "dist"]
15 | } 
```
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
```yaml
 1 | name: Build and Publish
 2 | 
 3 | on:
 4 |   release:
 5 |     types: [created]
 6 |   workflow_dispatch:  # Allow manual triggering
 7 | 
 8 | jobs:
 9 |   build-and-publish:
10 |     runs-on: ubuntu-latest
11 |     steps:
12 |       - uses: actions/checkout@v3
13 |       
14 |       - name: Set up Node.js
15 |         uses: actions/setup-node@v3
16 |         with:
17 |           node-version: '18'
18 |           registry-url: 'https://registry.npmjs.org'
19 |       
20 |       - name: Install dependencies
21 |         run: npm ci
22 |       
23 |       - name: Build with embedded credentials
24 |         env:
25 |           SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
26 |           SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
27 |         run: npm run build
28 |       
29 |       - name: Publish to npm
30 |         env:
31 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
32 |         run: npm publish 
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "name": "adspirer-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "description": "MCP server for Amazon Advertising data integration with Claude",
 5 |   "main": "dist/index.js",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "adspirer-mcp": "./bin/cli.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "node build.js",
12 |     "dev": "node -r dotenv/config index.js",
13 |     "start": "node index.js",
14 |     "explore-db": "node scripts/explore-db.js",
15 |     "check-api-keys": "node scripts/check-api-keys.js",
16 |     "test-mcp": "node scripts/test-mcp-connection.js",
17 |     "package": "npm run build && node scripts/create-package.js",
18 |     "prepare-release": "npm run build && npm prune --production"
19 |   },
20 |   "keywords": [
21 |     "mcp",
22 |     "claude",
23 |     "amazon-advertising"
24 |   ],
25 |   "author": "",
26 |   "license": "ISC",
27 |   "devDependencies": {
28 |     "@types/node": "^22.13.10",
29 |     "esbuild": "^0.19.12",
30 |     "esbuild-node-externals": "^1.18.0",
31 |     "ts-node": "^10.9.2",
32 |     "typescript": "^5.8.2"
33 |   },
34 |   "dependencies": {
35 |     "@modelcontextprotocol/sdk": "^1.7.0",
36 |     "@supabase/supabase-js": "^2.49.1",
37 |     "axios": "^1.8.3",
38 |     "chalk": "^5.4.1",
39 |     "dotenv": "^16.4.7",
40 |     "zod": "^3.24.2"
41 |   }
42 | }
43 | 
```
--------------------------------------------------------------------------------
/src/config/supabase.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import { createClient } from '@supabase/supabase-js';
 2 | import * as dotenv from 'dotenv';
 3 | 
 4 | // Load environment variables
 5 | dotenv.config();
 6 | 
 7 | // Validate environment variables
 8 | const supabaseUrl = process.env.SUPABASE_URL;
 9 | const supabaseKey = process.env.SUPABASE_KEY;
10 | 
11 | if (!supabaseUrl || !supabaseKey) {
12 |   throw new Error('Missing Supabase environment variables. Please check your .env file.');
13 | }
14 | 
15 | // Create Supabase client
16 | export const supabase = createClient(supabaseUrl, supabaseKey, {
17 |   auth: {
18 |     persistSession: false,
19 |   }
20 | });
21 | 
22 | // Function to validate an API key against the database
23 | export async function validateApiKey(apiKey: string): Promise<{ 
24 |   valid: boolean, 
25 |   advertiserId?: string,
26 |   error?: string 
27 | }> {
28 |   try {
29 |     const { data, error } = await supabase
30 |       .from('api_keys')
31 |       .select('advertiser_id, active')
32 |       .eq('key', apiKey)
33 |       .single();
34 | 
35 |     if (error) {
36 |       return { valid: false, error: 'Invalid API key' };
37 |     }
38 | 
39 |     if (!data || !data.active) {
40 |       return { valid: false, error: 'API key is inactive or invalid' };
41 |     }
42 | 
43 |     return { valid: true, advertiserId: data.advertiser_id };
44 |   } catch (error) {
45 |     console.error('Error validating API key:', error);
46 |     return { valid: false, error: 'Error validating API key' };
47 |   }
48 | } 
```
--------------------------------------------------------------------------------
/src/middleware/auth.middleware.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import { validateApiKey } from '../config/supabase.js';
 2 | import { AdvertiserService, AdvertiserContext } from '../services/advertiser.service.js';
 3 | 
 4 | export interface AuthContext extends AdvertiserContext {
 5 |   apiKey: string;
 6 | }
 7 | 
 8 | /**
 9 |  * Authenticate a connection based on provided API key
10 |  * This will be used to validate connections to the MCP server
11 |  */
12 | export async function authenticate(apiKey: string): Promise<{ success: boolean; error?: string; session?: AdvertiserContext }> {
13 |   try {
14 |     if (!apiKey) {
15 |       return { success: false, error: 'No API key provided' };
16 |     }
17 |     
18 |     const validation = await validateApiKey(apiKey);
19 |     if (!validation.valid || !validation.advertiserId) {
20 |       return { success: false, error: validation.error || 'Invalid API key' };
21 |     }
22 |     
23 |     const advertiser = await AdvertiserService.getAdvertiserContext(validation.advertiserId);
24 |     if (!advertiser) {
25 |       return { success: false, error: 'Failed to retrieve advertiser context' };
26 |     }
27 |     
28 |     await AdvertiserService.updateApiKeyUsage(apiKey);
29 |     
30 |     return { 
31 |       success: true,
32 |       session: advertiser
33 |     };
34 |   } catch (error) {
35 |     console.error('Authentication error:', error);
36 |     return { 
37 |       success: false, 
38 |       error: 'Authentication failed due to an internal error' 
39 |     };
40 |   }
41 | } 
```
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
```javascript
 1 | // build.js
 2 | import esbuild from 'esbuild';
 3 | import { nodeExternalsPlugin } from 'esbuild-node-externals';
 4 | import dotenv from 'dotenv';
 5 | import fs from 'fs';
 6 | import { fileURLToPath } from 'url';
 7 | import { dirname, resolve } from 'path';
 8 | 
 9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = dirname(__filename);
11 | 
12 | // Create a custom dotenv plugin since esbuild-plugin-dotenv is not available
13 | const dotenvPlugin = () => ({
14 |   name: 'dotenv',
15 |   setup(build) {
16 |     // Load .env file
17 |     dotenv.config();
18 |     
19 |     // Whitelisted environment variables to include in the build
20 |     const include = ['SUPABASE_URL', 'SUPABASE_KEY'];
21 |     
22 |     // Replace process.env.X with actual values during build
23 |     build.onResolve({ filter: /^process\.env\./ }, args => {
24 |       const envVar = args.path.substring('process.env.'.length);
25 |       if (include.includes(envVar)) {
26 |         return {
27 |           path: args.path,
28 |           namespace: 'env-ns',
29 |         };
30 |       }
31 |       return null;
32 |     });
33 | 
34 |     build.onLoad({ filter: /.*/, namespace: 'env-ns' }, args => {
35 |       const envVar = args.path.substring('process.env.'.length);
36 |       const value = process.env[envVar] || '';
37 |       return {
38 |         contents: JSON.stringify(value),
39 |         loader: 'json',
40 |       };
41 |     });
42 |   },
43 | });
44 | 
45 | async function build() {
46 |   console.log('Building MCP server...');
47 |   
48 |   try {
49 |     await esbuild.build({
50 |       entryPoints: ['index.js'],
51 |       bundle: true,
52 |       platform: 'node',
53 |       target: 'node16',
54 |       outfile: 'dist/index.js',
55 |       format: 'esm',
56 |       minify: true,
57 |       plugins: [
58 |         nodeExternalsPlugin(),
59 |         dotenvPlugin()
60 |       ]
61 |     });
62 |     
63 |     console.log('Build completed successfully!');
64 |   } catch (error) {
65 |     console.error('Build failed:', error);
66 |     process.exit(1);
67 |   }
68 | }
69 | 
70 | // Run the build
71 | build(); 
```
--------------------------------------------------------------------------------
/scripts/explore-db.js:
--------------------------------------------------------------------------------
```javascript
 1 | // CommonJS script for exploring Supabase database
 2 | const { createClient } = require('@supabase/supabase-js');
 3 | const dotenv = require('dotenv');
 4 | const path = require('path');
 5 | 
 6 | // Load environment variables from project root
 7 | dotenv.config({ path: path.resolve(__dirname, '../.env') });
 8 | 
 9 | // Validate environment variables
10 | const supabaseUrl = process.env.SUPABASE_URL;
11 | const supabaseKey = process.env.SUPABASE_KEY;
12 | 
13 | if (!supabaseUrl || !supabaseKey) {
14 |   throw new Error('Missing Supabase environment variables. Please check your .env file.');
15 | }
16 | 
17 | // Create Supabase client
18 | const supabase = createClient(supabaseUrl, supabaseKey, {
19 |   auth: {
20 |     persistSession: false,
21 |   }
22 | });
23 | 
24 | async function exploreTables() {
25 |   console.log('Exploring Supabase database tables...\n');
26 | 
27 |   try {
28 |     // Get list of tables
29 |     const tables = ['advertisers', 'amazon_tokens', 'api_keys', 'campaign_metrics', 'campaigns'];
30 |     
31 |     for (const table of tables) {
32 |       console.log(`\n=== Table: ${table} ===`);
33 |       
34 |       // Get sample data
35 |       const { data, error } = await supabase
36 |         .from(`${table}`)
37 |         .select('*')
38 |         .limit(3);
39 |         
40 |       if (error) {
41 |         console.error(`Error fetching data from ${table}:`, error);
42 |         continue;
43 |       }
44 |       
45 |       // Display column names based on the first row
46 |       if (data && data.length > 0) {
47 |         console.log('Columns:', Object.keys(data[0]).join(', '));
48 |       } else {
49 |         console.log('No data found in table');
50 |       }
51 |       
52 |       console.log(`\nSample data (${data?.length || 0} rows):`);
53 |       if (data && data.length > 0) {
54 |         console.log(JSON.stringify(data, null, 2));
55 |       } else {
56 |         console.log('No data found');
57 |       }
58 |     }
59 |     
60 |     console.log('\nDatabase exploration complete!');
61 |   } catch (error) {
62 |     console.error('Error exploring database:', error);
63 |   }
64 | }
65 | 
66 | // Run the exploration
67 | exploreTables(); 
```
--------------------------------------------------------------------------------
/scripts/explore-db.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import { createClient } from '@supabase/supabase-js';
 2 | import * as dotenv from 'dotenv';
 3 | import * as path from 'path';
 4 | import { fileURLToPath } from 'url';
 5 | 
 6 | // Load environment variables from project root
 7 | const __filename = fileURLToPath(import.meta.url);
 8 | const __dirname = path.dirname(__filename);
 9 | dotenv.config({ path: path.resolve(__dirname, '../.env') });
10 | 
11 | // Validate environment variables
12 | const supabaseUrl = process.env.SUPABASE_URL;
13 | const supabaseKey = process.env.SUPABASE_KEY;
14 | 
15 | if (!supabaseUrl || !supabaseKey) {
16 |   throw new Error('Missing Supabase environment variables. Please check your .env file.');
17 | }
18 | 
19 | // Create Supabase client
20 | const supabase = createClient(supabaseUrl, supabaseKey, {
21 |   auth: {
22 |     persistSession: false,
23 |   }
24 | });
25 | 
26 | async function exploreTables() {
27 |   console.log('Exploring Supabase database tables...\n');
28 | 
29 |   try {
30 |     // Get list of tables
31 |     const tables = ['advertisers', 'amazon_tokens', 'api_keys', 'campaign_metrics', 'campaigns'];
32 |     
33 |     for (const table of tables) {
34 |       console.log(`\n=== Table: ${table} ===`);
35 |       
36 |       // Get sample data
37 |       const { data, error } = await supabase
38 |         .from(`${table}`)
39 |         .select('*')
40 |         .limit(3);
41 |         
42 |       if (error) {
43 |         console.error(`Error fetching data from ${table}:`, error);
44 |         continue;
45 |       }
46 |       
47 |       // Display column names based on the first row
48 |       if (data && data.length > 0) {
49 |         console.log('Columns:', Object.keys(data[0]).join(', '));
50 |       } else {
51 |         // If no data, try to get column info from an empty select
52 |         const { error: schemaError } = await supabase
53 |           .from(`${table}`)
54 |           .select('*')
55 |           .limit(0);
56 |           
57 |         if (schemaError) {
58 |           console.error(`Error fetching schema for ${table}:`, schemaError);
59 |         } else {
60 |           console.log('Table exists but no data found');
61 |         }
62 |       }
63 |       
64 |       console.log(`\nSample data (${data?.length || 0} rows):`);
65 |       if (data && data.length > 0) {
66 |         console.log(JSON.stringify(data, null, 2));
67 |       } else {
68 |         console.log('No data found');
69 |       }
70 |     }
71 |     
72 |     console.log('\nDatabase exploration complete!');
73 |   } catch (error) {
74 |     console.error('Error exploring database:', error);
75 |   }
76 | }
77 | 
78 | // Run the exploration
79 | exploreTables(); 
```
--------------------------------------------------------------------------------
/src/utils/db-explorer.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import { supabase } from '../config/supabase.js';
 2 | 
 3 | async function exploreTables() {
 4 |   console.log('Exploring Supabase database tables...\n');
 5 | 
 6 |   try {
 7 |     // Get list of tables
 8 |     const tables = ['advertisers', 'amazon_tokens', 'api_keys', 'campaign_metrics', 'campaigns'];
 9 |     
10 |     for (const table of tables) {
11 |       console.log(`\n=== Table: ${table} ===`);
12 |       
13 |       // Get table schema
14 |       const { data: columns, error: schemaError } = await supabase
15 |         .from(`${table}`)
16 |         .select('*')
17 |         .limit(0);
18 |         
19 |       if (schemaError) {
20 |         console.error(`Error fetching schema for ${table}:`, schemaError);
21 |         continue;
22 |       }
23 |       
24 |       // Display column names based on the first row structure
25 |       if (columns) {
26 |         console.log('Columns:', Object.keys(columns).length > 0 
27 |           ? Object.keys(columns[0] || {}).join(', ') 
28 |           : 'No columns found');
29 |       }
30 |       
31 |       // Get sample data
32 |       const { data, error } = await supabase
33 |         .from(`${table}`)
34 |         .select('*')
35 |         .limit(3);
36 |         
37 |       if (error) {
38 |         console.error(`Error fetching data from ${table}:`, error);
39 |         continue;
40 |       }
41 |       
42 |       console.log(`\nSample data (${data?.length || 0} rows):`);
43 |       if (data && data.length > 0) {
44 |         console.log(JSON.stringify(data, null, 2));
45 |       } else {
46 |         console.log('No data found');
47 |       }
48 |     }
49 |     
50 |     console.log('\nDatabase exploration complete!');
51 |   } catch (error) {
52 |     console.error('Error exploring database:', error);
53 |   }
54 | }
55 | 
56 | // Run the exploration
57 | exploreTables();
58 | 
59 | export async function listTables(): Promise<string[]> {
60 |   try {
61 |     const { data, error } = await supabase
62 |       .from('information_schema.tables')
63 |       .select('table_name')
64 |       .eq('table_schema', 'public');
65 | 
66 |     if (error) {
67 |       console.error('Error listing tables:', error);
68 |       return [];
69 |     }
70 | 
71 |     return data.map(table => table.table_name);
72 |   } catch (error) {
73 |     console.error('Error in listTables:', error);
74 |     return [];
75 |   }
76 | }
77 | 
78 | export async function describeTable(tableName: string): Promise<any[]> {
79 |   try {
80 |     const { data, error } = await supabase
81 |       .from('information_schema.columns')
82 |       .select('column_name, data_type, is_nullable')
83 |       .eq('table_schema', 'public')
84 |       .eq('table_name', tableName);
85 | 
86 |     if (error) {
87 |       console.error(`Error describing table ${tableName}:`, error);
88 |       return [];
89 |     }
90 | 
91 |     return data;
92 |   } catch (error) {
93 |     console.error(`Error in describeTable for ${tableName}:`, error);
94 |     return [];
95 |   }
96 | } 
```
--------------------------------------------------------------------------------
/src/config/supabase.js:
--------------------------------------------------------------------------------
```javascript
 1 | import { createClient } from '@supabase/supabase-js';
 2 | import * as dotenv from 'dotenv';
 3 | import fs from 'fs';
 4 | 
 5 | // Load environment variables (for development)
 6 | dotenv.config();
 7 | 
 8 | // Function to write debug info
 9 | const writeDebug = (message) => {
10 |   if (process.env.DEBUG === 'true') {
11 |     const timestamp = new Date().toISOString();
12 |     const logMessage = `${timestamp} - ${message}\n`;
13 |     try {
14 |       const logFile = fs.createWriteStream('mcp-debug.log', { flags: 'a' });
15 |       logFile.write(logMessage);
16 |       console.error(logMessage);
17 |     } catch (error) {
18 |       console.error(`Error writing to log: ${error.message}`);
19 |     }
20 |   }
21 | };
22 | 
23 | // These values will be replaced during build with actual values from .env
24 | // Users will never see or need to provide these credentials
25 | const supabaseUrl = process.env.SUPABASE_URL;
26 | const supabaseKey = process.env.SUPABASE_KEY;
27 | 
28 | writeDebug('Supabase client initialization complete');
29 | 
30 | // Create Supabase client
31 | export const supabase = createClient(supabaseUrl, supabaseKey, {
32 |   auth: {
33 |     persistSession: false,
34 |   }
35 | });
36 | 
37 | // Function to validate an API key against the database
38 | export async function validateApiKey(apiKey) {
39 |   try {
40 |     // First check the 'key' column (based on database.types.ts)
41 |     const { data: keyData, error: keyError } = await supabase
42 |       .from('api_keys')
43 |       .select('advertiser_id, active')
44 |       .eq('key', apiKey)
45 |       .single();
46 | 
47 |     if (!keyError && keyData) {
48 |       return { 
49 |         valid: keyData.active === true, 
50 |         advertiserId: keyData.advertiser_id,
51 |         error: keyData.active !== true ? 'API key is inactive' : undefined
52 |       };
53 |     }
54 | 
55 |     // If not found, check the 'key_value' column (based on explore-db.js output)
56 |     const { data, error } = await supabase
57 |       .from('api_keys')
58 |       .select('id, user_id')
59 |       .eq('key_value', apiKey)
60 |       .eq('is_active', true)
61 |       .single();
62 | 
63 |     if (error) {
64 |       return { valid: false, error: 'Invalid API key' };
65 |     }
66 | 
67 |     if (!data) {
68 |       return { valid: false, error: 'API key not found' };
69 |     }
70 | 
71 |     // If we have a user_id but no advertiser_id, try to get the first advertiser for this user
72 |     if (data.user_id) {
73 |       const { data: advertiser, error: advError } = await supabase
74 |         .from('advertisers')
75 |         .select('id')
76 |         .eq('user_id', data.user_id)
77 |         .limit(1)
78 |         .single();
79 | 
80 |       if (!advError && advertiser) {
81 |         return { valid: true, advertiserId: advertiser.id };
82 |       }
83 |     }
84 | 
85 |     return { valid: true, advertiserId: data.id };
86 |   } catch (error) {
87 |     console.error('Error validating API key:', error);
88 |     return { valid: false, error: 'Error validating API key' };
89 |   }
90 | } 
```
--------------------------------------------------------------------------------
/src/services/advertiser.service.js:
--------------------------------------------------------------------------------
```javascript
  1 | import { supabase } from '../config/supabase.js';
  2 | import crypto from 'crypto';
  3 | 
  4 | export class AdvertiserService {
  5 |   /**
  6 |    * Get advertiser context by ID
  7 |    * This will be used after API key validation
  8 |    */
  9 |   static async getAdvertiserContext(advertiserId) {
 10 |     try {
 11 |       const { data, error } = await supabase
 12 |         .from('advertisers')
 13 |         .select('id, user_id, profile_id, account_name, marketplace, account_type, metadata')
 14 |         .eq('id', advertiserId)
 15 |         .single();
 16 | 
 17 |       if (error || !data) {
 18 |         console.error('Error fetching advertiser:', error);
 19 |         return null;
 20 |       }
 21 | 
 22 |       return {
 23 |         id: data.id,
 24 |         userId: data.user_id,
 25 |         profileId: data.profile_id,
 26 |         accountName: data.account_name,
 27 |         marketplace: data.marketplace,
 28 |         accountType: data.account_type,
 29 |         metadata: data.metadata
 30 |       };
 31 |     } catch (error) {
 32 |       console.error('Unexpected error fetching advertiser:', error);
 33 |       return null;
 34 |     }
 35 |   }
 36 | 
 37 |   /**
 38 |    * Update the last_used_at timestamp for an API key
 39 |    */
 40 |   static async updateApiKeyUsage(apiKey) {
 41 |     try {
 42 |       // Try updating key column
 43 |       const { error } = await supabase
 44 |         .from('api_keys')
 45 |         .update({ last_used_at: new Date().toISOString() })
 46 |         .eq('key', apiKey);
 47 | 
 48 |       if (error) {
 49 |         // Try updating key_value column
 50 |         await supabase
 51 |           .from('api_keys')
 52 |           .update({ last_used: new Date().toISOString() })
 53 |           .eq('key_value', apiKey);
 54 |       }
 55 |     } catch (error) {
 56 |       console.error('Error updating API key usage:', error);
 57 |     }
 58 |   }
 59 | 
 60 |   /**
 61 |    * Create a new API key for an advertiser
 62 |    */
 63 |   static async createApiKey(advertiserId) {
 64 |     try {
 65 |       const apiKey = crypto.randomUUID();
 66 |       
 67 |       const { error } = await supabase
 68 |         .from('api_keys')
 69 |         .insert({
 70 |           key: apiKey,
 71 |           advertiser_id: advertiserId,
 72 |           active: true
 73 |         });
 74 | 
 75 |       if (error) {
 76 |         console.error('Error creating API key:', error);
 77 |         return null;
 78 |       }
 79 | 
 80 |       return apiKey;
 81 |     } catch (error) {
 82 |       console.error('Unexpected error creating API key:', error);
 83 |       return null;
 84 |     }
 85 |   }
 86 | 
 87 |   /**
 88 |    * Get basic info for all advertisers (for debugging/admin purposes)
 89 |    */
 90 |   static async getAllAdvertisers() {
 91 |     try {
 92 |       const { data, error } = await supabase
 93 |         .from('advertisers')
 94 |         .select('id, account_name');
 95 | 
 96 |       if (error) {
 97 |         console.error('Error fetching advertisers:', error);
 98 |         return null;
 99 |       }
100 | 
101 |       return data.map(adv => ({
102 |         id: adv.id,
103 |         accountName: adv.account_name
104 |       }));
105 |     } catch (error) {
106 |       console.error('Unexpected error fetching advertisers:', error);
107 |       return null;
108 |     }
109 |   }
110 | } 
```
--------------------------------------------------------------------------------
/scripts/check-api-keys.js:
--------------------------------------------------------------------------------
```javascript
 1 | // Script to check the api_keys table structure
 2 | const { createClient } = require('@supabase/supabase-js');
 3 | const dotenv = require('dotenv');
 4 | const path = require('path');
 5 | 
 6 | // Load environment variables from project root
 7 | dotenv.config({ path: path.resolve(__dirname, '../.env') });
 8 | 
 9 | // Validate environment variables
10 | const supabaseUrl = process.env.SUPABASE_URL;
11 | const supabaseKey = process.env.SUPABASE_KEY;
12 | 
13 | if (!supabaseUrl || !supabaseKey) {
14 |   throw new Error('Missing Supabase environment variables. Please check your .env file.');
15 | }
16 | 
17 | // Create Supabase client
18 | const supabase = createClient(supabaseUrl, supabaseKey, {
19 |   auth: {
20 |     persistSession: false,
21 |   }
22 | });
23 | 
24 | async function checkApiKeysTable() {
25 |   try {
26 |     // Option 1: List all tables to confirm api_keys exists
27 |     console.log('Checking database schema...');
28 |     const { data: tables, error: schemaError } = await supabase
29 |       .rpc('get_tables'); // This RPC function might not exist, so we'll try another approach if it fails
30 |     
31 |     if (!schemaError && tables) {
32 |       console.log('Tables in database:', tables);
33 |     } else {
34 |       console.log('Could not retrieve tables using RPC, trying a different approach...');
35 |     }
36 |     
37 |     // Option 2: Direct SQL query to get table info (this requires higher privileges)
38 |     const { data: tableInfo, error: tableError } = await supabase
39 |       .from('api_keys')
40 |       .select('*')
41 |       .limit(1);
42 |     
43 |     if (tableError) {
44 |       console.error('Error accessing api_keys table:', tableError);
45 |     } else {
46 |       console.log('Successfully accessed api_keys table!');
47 |       if (tableInfo && tableInfo.length > 0) {
48 |         console.log('First record:', tableInfo[0]);
49 |         console.log('Columns:', Object.keys(tableInfo[0]).join(', '));
50 |       } else {
51 |         console.log('The api_keys table exists but contains no records.');
52 |         // Try to get column names from an empty table
53 |         const { data: emptyData, error: emptyError } = await supabase
54 |           .from('api_keys')
55 |           .select('*')
56 |           .limit(0);
57 |           
58 |         if (!emptyError) {
59 |           console.log('Table exists, but no data to show column structure.');
60 |         }
61 |       }
62 |     }
63 |     
64 |     // Option 3: Try introspection (may or may not work depending on permissions)
65 |     console.log('\nTrying to get all tables via introspection...');
66 |     try {
67 |       const { data: allTables, error: allError } = await supabase
68 |         .from('information_schema.tables')
69 |         .select('table_name')
70 |         .eq('table_schema', 'public');
71 |         
72 |       if (!allError) {
73 |         console.log('Tables found via introspection:', allTables.map(t => t.table_name).join(', '));
74 |       } else {
75 |         console.error('Error getting tables via introspection:', allError);
76 |       }
77 |     } catch (e) {
78 |       console.error('Exception during introspection:', e);
79 |     }
80 |   } catch (error) {
81 |     console.error('Unexpected error:', error);
82 |   }
83 | }
84 | 
85 | // Run the function
86 | checkApiKeysTable(); 
```
--------------------------------------------------------------------------------
/scripts/test-mcp-connection.js:
--------------------------------------------------------------------------------
```javascript
 1 | // Script to test MCP server connection
 2 | import axios from 'axios';
 3 | import dotenv from 'dotenv';
 4 | import path from 'path';
 5 | import { fileURLToPath } from 'url';
 6 | 
 7 | // Set up __dirname equivalent for ES modules
 8 | const __filename = fileURLToPath(import.meta.url);
 9 | const __dirname = path.dirname(__filename);
10 | 
11 | // Load environment variables
12 | dotenv.config({ path: path.resolve(__dirname, '../.env') });
13 | 
14 | // API Key from environment or use your hard-coded key for testing
15 | const API_KEY = process.env.TEST_API_KEY || 'amzn_ads_0018ad0985a04acc9b4ee7ea791192ba';
16 | 
17 | // MCP server URL
18 | const MCP_SERVER_URL = process.env.MCP_SERVER_URL || 'http://localhost:3000';
19 | 
20 | async function testMcpConnection() {
21 |   console.log('Testing MCP server connection...');
22 |   console.log(`Server URL: ${MCP_SERVER_URL}`);
23 |   console.log(`Using API Key: ${API_KEY}`);
24 |   
25 |   try {
26 |     // Test the info endpoint
27 |     console.log('\n1. Testing server info endpoint...');
28 |     const infoResponse = await axios.get(`${MCP_SERVER_URL}/mcp/info`);
29 |     console.log('Server Info:');
30 |     console.log(JSON.stringify(infoResponse.data, null, 2));
31 |     
32 |     // Test authentication
33 |     console.log('\n2. Testing authentication...');
34 |     const authResponse = await axios.post(`${MCP_SERVER_URL}/mcp/authenticate`, {
35 |       authToken: API_KEY
36 |     });
37 |     console.log('Authentication Response:');
38 |     console.log(JSON.stringify(authResponse.data, null, 2));
39 |     
40 |     if (!authResponse.data.success) {
41 |       console.error('Authentication failed. Cannot proceed with context tests.');
42 |       return;
43 |     }
44 |     
45 |     // Get the session token from the auth response
46 |     const sessionToken = authResponse.data.sessionToken;
47 |     
48 |     // Test current advertiser context provider
49 |     console.log('\n3. Testing current advertiser context provider...');
50 |     const advertiserResponse = await axios.post(`${MCP_SERVER_URL}/mcp/context`, {
51 |       sessionToken,
52 |       providerName: 'advertiser',
53 |       parameters: {}
54 |     });
55 |     console.log('Current Advertiser Response:');
56 |     console.log(JSON.stringify(advertiserResponse.data, null, 2));
57 |     
58 |     // Test all advertisers context provider
59 |     console.log('\n4. Testing all advertisers context provider...');
60 |     const advertisersResponse = await axios.post(`${MCP_SERVER_URL}/mcp/context`, {
61 |       sessionToken,
62 |       providerName: 'advertisers',
63 |       parameters: {}
64 |     });
65 |     console.log('All Advertisers Response:');
66 |     console.log(JSON.stringify(advertisersResponse.data, null, 2));
67 |     
68 |     // Test campaigns context provider
69 |     console.log('\n5. Testing campaigns context provider...');
70 |     const campaignsResponse = await axios.post(`${MCP_SERVER_URL}/mcp/context`, {
71 |       sessionToken,
72 |       providerName: 'campaigns',
73 |       parameters: {}
74 |     });
75 |     console.log('Campaigns Context Response:');
76 |     console.log(JSON.stringify(campaignsResponse.data, null, 2));
77 |     
78 |     console.log('\nMCP connection test completed successfully!');
79 |   } catch (error) {
80 |     console.error('Error testing MCP connection:', error.message);
81 |     if (error.response) {
82 |       console.error('Response data:', error.response.data);
83 |       console.error('Response status:', error.response.status);
84 |     }
85 |   }
86 | }
87 | 
88 | // Run the test
89 | testMcpConnection(); 
```
--------------------------------------------------------------------------------
/test-mcp.js:
--------------------------------------------------------------------------------
```javascript
  1 | #!/usr/bin/env node
  2 | import { Client } from '@modelcontextprotocol/sdk/client/index.js';
  3 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
  4 | import chalk from 'chalk';
  5 | import { spawn } from 'child_process';
  6 | 
  7 | console.log(chalk.blue('=== MCP Server Test Client ==='));
  8 | console.log(chalk.yellow('Starting test of Amazon Advertising MCP server...'));
  9 | 
 10 | // Configuration
 11 | const serverPath = './index.js';
 12 | 
 13 | // Create a transport that will spawn the server process
 14 | const transport = new StdioClientTransport({
 15 |   command: 'node',
 16 |   args: [serverPath],
 17 |   cwd: process.cwd(),
 18 |   env: {
 19 |     ...process.env,
 20 |     DEBUG: 'true',
 21 |     SUPABASE_URL: 'https://sdpmxiyzkdypufeedhoz.supabase.co',
 22 |     SUPABASE_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNkcG14aXl6a2R5cHVmZWVkaG96Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTY1NTc3MiwiZXhwIjoyMDU3MjMxNzcyfQ.x9tFYuoewa0I03UoJfAuwZJJLyHkSPCghjTeSLb7EqE',
 23 |     API_KEY: 'amzn_ads_0018ad0985a04acc9b4ee7ea791192ba'
 24 |   }
 25 | });
 26 | 
 27 | // Create the client
 28 | const client = new Client(
 29 |   {
 30 |     name: 'test-client',
 31 |     version: '1.0.0'
 32 |   },
 33 |   {
 34 |     capabilities: {
 35 |       tools: {},
 36 |       resources: {}
 37 |     }
 38 |   }
 39 | );
 40 | 
 41 | async function runTests() {
 42 |   try {
 43 |     console.log(chalk.yellow('Connecting to MCP server...'));
 44 |     await client.connect(transport);
 45 |     console.log(chalk.green('✓ Connected to MCP server'));
 46 | 
 47 |     // List available tools
 48 |     console.log(chalk.yellow('Listing tools...'));
 49 |     const tools = await client.listTools();
 50 |     console.log(chalk.green('✓ Available tools:'), tools);
 51 | 
 52 |     // Test ping tool
 53 |     console.log(chalk.yellow('Testing ping tool...'));
 54 |     const pingResult = await client.callTool({
 55 |       name: 'ping',
 56 |       arguments: {}
 57 |     });
 58 |     console.log(chalk.green('✓ Ping result:'), pingResult);
 59 | 
 60 |     // Test echo tool
 61 |     console.log(chalk.yellow('Testing echo tool...'));
 62 |     const echoResult = await client.callTool({
 63 |       name: 'echo',
 64 |       arguments: {
 65 |         message: 'Hello, MCP Server!'
 66 |       }
 67 |     });
 68 |     console.log(chalk.green('✓ Echo result:'), echoResult);
 69 |     
 70 |     // Test validateApiKey tool
 71 |     console.log(chalk.yellow('Testing validateApiKey tool...'));
 72 |     const validationResult = await client.callTool({
 73 |       name: 'validateApiKey',
 74 |       arguments: {}
 75 |     });
 76 |     console.log(chalk.green('✓ API Key validation result:'), validationResult);
 77 | 
 78 |     // Test advertiser tools if available
 79 |     try {
 80 |       console.log(chalk.yellow('Testing getAdvertiserInfo tool...'));
 81 |       const advertiserInfo = await client.callTool({
 82 |         name: 'getAdvertiserInfo',
 83 |         arguments: {}
 84 |       });
 85 |       console.log(chalk.green('✓ Advertiser info result:'), advertiserInfo);
 86 |     } catch (error) {
 87 |       console.log(chalk.red('✗ getAdvertiserInfo tool failed:'), error.message);
 88 |     }
 89 | 
 90 |     console.log(chalk.green('✓ All tests completed successfully'));
 91 |   } catch (error) {
 92 |     console.error(chalk.red('Error running tests:'), error);
 93 |   } finally {
 94 |     // Disconnect client
 95 |     try {
 96 |       console.log(chalk.yellow('Disconnecting...'));
 97 |       // Close the transport instead of calling disconnect
 98 |       await transport.close();
 99 |       console.log(chalk.green('✓ Disconnected from MCP server'));
100 |     } catch (error) {
101 |       console.error(chalk.red('Error disconnecting:'), error);
102 |     }
103 |     process.exit(0);
104 |   }
105 | }
106 | 
107 | runTests(); 
```
--------------------------------------------------------------------------------
/src/services/advertiser.service.ts:
--------------------------------------------------------------------------------
```typescript
  1 | import { supabase } from '../config/supabase.js';
  2 | 
  3 | export interface AdvertiserContext {
  4 |   id: string;
  5 |   userId: string;
  6 |   accountName: string;
  7 |   marketplace: string;
  8 |   accountType: string;
  9 |   profileId: string;
 10 |   metadata?: {
 11 |     countryCode?: string;
 12 |     currencyCode?: string;
 13 |     [key: string]: any;
 14 |   };
 15 | }
 16 | 
 17 | export class AdvertiserService {
 18 |   /**
 19 |    * Get advertiser context by ID
 20 |    * This will be used after API key validation
 21 |    */
 22 |   static async getAdvertiserContext(advertiserId: string): Promise<AdvertiserContext | null> {
 23 |     try {
 24 |       const { data, error } = await supabase
 25 |         .from('advertisers')
 26 |         .select('*')
 27 |         .eq('id', advertiserId)
 28 |         .single();
 29 | 
 30 |       if (error) {
 31 |         console.error('Error fetching advertiser:', error);
 32 |         return null;
 33 |       }
 34 | 
 35 |       if (!data) {
 36 |         return null;
 37 |       }
 38 | 
 39 |       return {
 40 |         id: data.id,
 41 |         userId: data.user_id,
 42 |         accountName: data.account_name,
 43 |         marketplace: data.marketplace,
 44 |         accountType: data.account_type,
 45 |         profileId: data.profile_id,
 46 |         metadata: data.metadata
 47 |       };
 48 |     } catch (error) {
 49 |       console.error('Error in getAdvertiserContext:', error);
 50 |       return null;
 51 |     }
 52 |   }
 53 | 
 54 |   /**
 55 |    * Update the last_used_at timestamp for an API key
 56 |    */
 57 |   static async updateApiKeyUsage(apiKey: string): Promise<boolean> {
 58 |     try {
 59 |       const { error } = await supabase
 60 |         .from('api_keys')
 61 |         .update({ last_used: new Date().toISOString() })
 62 |         .eq('key', apiKey);
 63 | 
 64 |       if (error) {
 65 |         console.error('Error updating API key usage:', error);
 66 |         return false;
 67 |       }
 68 | 
 69 |       return true;
 70 |     } catch (error) {
 71 |       console.error('Error in updateApiKeyUsage:', error);
 72 |       return false;
 73 |     }
 74 |   }
 75 | 
 76 |   /**
 77 |    * Create a new API key for an advertiser
 78 |    */
 79 |   static async createApiKey(advertiserId: string): Promise<string | null> {
 80 |     try {
 81 |       const apiKey = crypto.randomUUID();
 82 |       
 83 |       const { error } = await supabase
 84 |         .from('api_keys')
 85 |         .insert({
 86 |           key: apiKey,
 87 |           advertiser_id: advertiserId,
 88 |           active: true
 89 |         });
 90 | 
 91 |       if (error) {
 92 |         console.error('Error creating API key:', error);
 93 |         return null;
 94 |       }
 95 | 
 96 |       return apiKey;
 97 |     } catch (error) {
 98 |       console.error('Unexpected error creating API key:', error);
 99 |       return null;
100 |     }
101 |   }
102 | 
103 |   /**
104 |    * Get basic info for all advertisers (for debugging/admin purposes)
105 |    */
106 |   static async getAllAdvertisers(): Promise<{ id: string; accountName: string }[] | null> {
107 |     try {
108 |       const { data, error } = await supabase
109 |         .from('advertisers')
110 |         .select('id, account_name');
111 | 
112 |       if (error) {
113 |         console.error('Error fetching advertisers:', error);
114 |         return null;
115 |       }
116 | 
117 |       return data.map(adv => ({
118 |         id: adv.id,
119 |         accountName: adv.account_name
120 |       }));
121 |     } catch (error) {
122 |       console.error('Unexpected error fetching advertisers:', error);
123 |       return null;
124 |     }
125 |   }
126 | 
127 |   static async listAdvertisers(): Promise<AdvertiserContext[]> {
128 |     try {
129 |       const { data, error } = await supabase
130 |         .from('advertisers')
131 |         .select('*')
132 |         .order('account_name', { ascending: true });
133 | 
134 |       if (error) {
135 |         console.error('Error listing advertisers:', error);
136 |         return [];
137 |       }
138 | 
139 |       if (!data || data.length === 0) {
140 |         return [];
141 |       }
142 | 
143 |       return data.map((adv: any) => ({
144 |         id: adv.id,
145 |         userId: adv.user_id,
146 |         accountName: adv.account_name,
147 |         marketplace: adv.marketplace,
148 |         accountType: adv.account_type,
149 |         profileId: adv.profile_id,
150 |         metadata: adv.metadata
151 |       }));
152 |     } catch (error) {
153 |       console.error('Error in listAdvertisers:', error);
154 |       return [];
155 |     }
156 |   }
157 | } 
```
--------------------------------------------------------------------------------
/bin/cli.js:
--------------------------------------------------------------------------------
```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import path from 'path';
  4 | import { fileURLToPath } from 'url';
  5 | import fs from 'fs';
  6 | import os from 'os';
  7 | import chalk from 'chalk';
  8 | import { spawn } from 'child_process';
  9 | import readline from 'readline';
 10 | 
 11 | // Get the path to the index.js file
 12 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
 13 | const indexPath = path.resolve(__dirname, '../index.js');
 14 | 
 15 | // Available commands
 16 | const commands = {
 17 |   start: 'Start the MCP server',
 18 |   config: 'Configure Claude Desktop',
 19 |   test: 'Test the MCP connection'
 20 | };
 21 | 
 22 | // Extract command from arguments
 23 | const args = process.argv.slice(2);
 24 | const command = args[0] || 'help';
 25 | 
 26 | // Helper function to print help
 27 | function printHelp() {
 28 |   console.log(chalk.blue('\nAmazon Advertising MCP Server for Claude Desktop\n'));
 29 |   console.log('Usage: adspirer-mcp [command]\n');
 30 |   console.log('Commands:');
 31 |   Object.entries(commands).forEach(([cmd, desc]) => {
 32 |     console.log(`  ${chalk.green(cmd.padEnd(12))} ${desc}`);
 33 |   });
 34 |   console.log(`  ${chalk.green('help'.padEnd(12))} Show this help message\n`);
 35 | }
 36 | 
 37 | // Config command - configure Claude Desktop
 38 | function configureClaudeDesktop() {
 39 |   console.log(chalk.blue('\nConfiguring Claude Desktop...\n'));
 40 |   
 41 |   const configPaths = {
 42 |     win32: path.join(process.env.APPDATA, 'Claude', 'config.json'),
 43 |     darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'config.json'),
 44 |     linux: path.join(os.homedir(), '.config', 'Claude', 'config.json')
 45 |   };
 46 |   
 47 |   const configPath = configPaths[process.platform];
 48 |   if (!configPath) {
 49 |     console.log(chalk.red('\nUnsupported operating system for automatic Claude Desktop configuration.'));
 50 |     console.log('Please manually add the MCP server configuration to your Claude Desktop config file.');
 51 |     return;
 52 |   }
 53 |   
 54 |   console.log(`Configuring Claude Desktop at: ${configPath}`);
 55 |   
 56 |   // Check if the Claude config directory exists
 57 |   const configDir = path.dirname(configPath);
 58 |   if (!fs.existsSync(configDir)) {
 59 |     console.log('Claude Desktop config directory does not exist. Creating it...');
 60 |     fs.mkdirSync(configDir, { recursive: true });
 61 |   }
 62 |   
 63 |   // Read existing config or create new one
 64 |   let config = {};
 65 |   if (fs.existsSync(configPath)) {
 66 |     try {
 67 |       config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
 68 |       console.log('Existing Claude Desktop configuration found.');
 69 |     } catch (e) {
 70 |       console.log('Error reading Claude Desktop config. Creating a new one.');
 71 |     }
 72 |   }
 73 |   
 74 |   // Ensure mcpServers object exists
 75 |   if (!config.mcpServers) config.mcpServers = {};
 76 | 
 77 |   // Create readline interface for input
 78 |   const rl = readline.createInterface({
 79 |     input: process.stdin,
 80 |     output: process.stdout
 81 |   });
 82 |   
 83 |   // Prompt for API key
 84 |   rl.question(chalk.yellow('\nEnter your Amazon Advertising API Key: '), (apiKey) => {
 85 |     // Configure the MCP server in Claude Desktop
 86 |     config.mcpServers['amazon-ads'] = {
 87 |       command: 'adspirer-mcp',
 88 |       args: ['start'],
 89 |       env: {
 90 |         API_KEY: apiKey,
 91 |         NODE_ENV: 'production'
 92 |       }
 93 |     };
 94 |     
 95 |     // Write the updated config
 96 |     fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
 97 |     console.log(chalk.green(`\nClaude Desktop configuration updated at: ${configPath}`));
 98 |     console.log('You can now use the Amazon Advertising MCP Server with Claude Desktop.');
 99 |     rl.close();
100 |   });
101 | }
102 | 
103 | // Test command - run the test-mcp-connection.js script
104 | function runTest() {
105 |   console.log(chalk.blue('\nTesting MCP connection...\n'));
106 |   const testPath = path.resolve(__dirname, '../scripts/test-mcp-connection.js');
107 |   
108 |   // Use dynamic import for the test script
109 |   import(testPath)
110 |     .catch(error => {
111 |       console.error(chalk.red('\nError during test:'), error);
112 |     });
113 | }
114 | 
115 | // Start command - run the MCP server
116 | function startMcpServer() {
117 |   console.log(chalk.blue('\nStarting Amazon Advertising MCP Server...\n'));
118 |   
119 |   // Run the index.js file directly
120 |   const child = spawn('node', [indexPath], {
121 |     stdio: 'inherit'
122 |   });
123 |   
124 |   child.on('error', (error) => {
125 |     console.error(chalk.red('\nError starting MCP server:'), error);
126 |   });
127 | }
128 | 
129 | // Process commands
130 | switch (command) {
131 |   case 'start':
132 |     startMcpServer();
133 |     break;
134 |   case 'config':
135 |     configureClaudeDesktop();
136 |     break;
137 |   case 'test':
138 |     runTest();
139 |     break;
140 |   case 'help':
141 |     printHelp();
142 |     break;
143 |   default:
144 |     console.log(chalk.red(`\nUnknown command: ${command}`));
145 |     printHelp();
146 | } 
```
--------------------------------------------------------------------------------
/src/types/database.types.ts:
--------------------------------------------------------------------------------
```typescript
  1 | export interface Database {
  2 |   public: {
  3 |     Tables: {
  4 |       api_keys: {
  5 |         Row: {
  6 |           id: string;
  7 |           key: string;
  8 |           advertiser_id: string;
  9 |           active: boolean;
 10 |           created_at: string;
 11 |           last_used_at: string | null;
 12 |         };
 13 |         Insert: {
 14 |           id?: string;
 15 |           key: string;
 16 |           advertiser_id: string;
 17 |           active?: boolean;
 18 |           created_at?: string;
 19 |           last_used_at?: string | null;
 20 |         };
 21 |         Update: {
 22 |           id?: string;
 23 |           key?: string;
 24 |           advertiser_id?: string;
 25 |           active?: boolean;
 26 |           created_at?: string;
 27 |           last_used_at?: string | null;
 28 |         };
 29 |       };
 30 |       advertisers: {
 31 |         Row: {
 32 |           id: string;
 33 |           user_id: string;
 34 |           profile_id: string;
 35 |           account_name: string;
 36 |           marketplace: string;
 37 |           account_type: string;
 38 |           connected_since: string;
 39 |           metadata: {
 40 |             timezone: string;
 41 |             accountInfo: {
 42 |               id: string;
 43 |               name: string;
 44 |               type: string;
 45 |               validPaymentMethod: boolean;
 46 |               marketplaceStringId: string;
 47 |             };
 48 |             countryCode: string;
 49 |             currencyCode: string;
 50 |           };
 51 |         };
 52 |         Insert: {
 53 |           id?: string;
 54 |           user_id: string;
 55 |           profile_id: string;
 56 |           account_name: string;
 57 |           marketplace: string;
 58 |           account_type: string;
 59 |           connected_since?: string;
 60 |           metadata?: {
 61 |             timezone: string;
 62 |             accountInfo: {
 63 |               id: string;
 64 |               name: string;
 65 |               type: string;
 66 |               validPaymentMethod: boolean;
 67 |               marketplaceStringId: string;
 68 |             };
 69 |             countryCode: string;
 70 |             currencyCode: string;
 71 |           };
 72 |         };
 73 |         Update: {
 74 |           id?: string;
 75 |           user_id?: string;
 76 |           profile_id?: string;
 77 |           account_name?: string;
 78 |           marketplace?: string;
 79 |           account_type?: string;
 80 |           connected_since?: string;
 81 |           metadata?: {
 82 |             timezone: string;
 83 |             accountInfo: {
 84 |               id: string;
 85 |               name: string;
 86 |               type: string;
 87 |               validPaymentMethod: boolean;
 88 |               marketplaceStringId: string;
 89 |             };
 90 |             countryCode: string;
 91 |             currencyCode: string;
 92 |           };
 93 |         };
 94 |       };
 95 |       amazon_tokens: {
 96 |         Row: {
 97 |           id: string;
 98 |           user_id: string;
 99 |           access_token: string;
100 |           refresh_token: string;
101 |           token_type: string;
102 |           expires_at: string;
103 |           created_at: string;
104 |           updated_at: string;
105 |         };
106 |         Insert: {
107 |           id?: string;
108 |           user_id: string;
109 |           access_token: string;
110 |           refresh_token: string;
111 |           token_type: string;
112 |           expires_at: string;
113 |           created_at?: string;
114 |           updated_at?: string;
115 |         };
116 |         Update: {
117 |           id?: string;
118 |           user_id?: string;
119 |           access_token?: string;
120 |           refresh_token?: string;
121 |           token_type?: string;
122 |           expires_at?: string;
123 |           created_at?: string;
124 |           updated_at?: string;
125 |         };
126 |       };
127 |       campaigns: {
128 |         Row: {
129 |           id: string;
130 |           advertiser_id: string;
131 |           campaign_id: string;
132 |           name: string;
133 |           state: string;
134 |           type: string;
135 |           targeting_type: string;
136 |           start_date: string;
137 |           end_date: string | null;
138 |           budget: number;
139 |           budget_type: string;
140 |           created_at: string;
141 |           updated_at: string;
142 |         };
143 |         Insert: {
144 |           id?: string;
145 |           advertiser_id: string;
146 |           campaign_id: string;
147 |           name: string;
148 |           state: string;
149 |           type: string;
150 |           targeting_type: string;
151 |           start_date: string;
152 |           end_date?: string | null;
153 |           budget: number;
154 |           budget_type: string;
155 |           created_at?: string;
156 |           updated_at?: string;
157 |         };
158 |         Update: {
159 |           id?: string;
160 |           advertiser_id?: string;
161 |           campaign_id?: string;
162 |           name?: string;
163 |           state?: string;
164 |           type?: string;
165 |           targeting_type?: string;
166 |           start_date?: string;
167 |           end_date?: string | null;
168 |           budget?: number;
169 |           budget_type?: string;
170 |           created_at?: string;
171 |           updated_at?: string;
172 |         };
173 |       };
174 |       campaign_metrics: {
175 |         Row: {
176 |           id: string;
177 |           campaign_id: string;
178 |           date: string;
179 |           impressions: number;
180 |           clicks: number;
181 |           spend: number;
182 |           sales: number;
183 |           units_sold: number;
184 |           acos: number;
185 |           roas: number;
186 |           created_at: string;
187 |           updated_at: string | null;
188 |         };
189 |         Insert: {
190 |           id?: string;
191 |           campaign_id: string;
192 |           date: string;
193 |           impressions: number;
194 |           clicks: number;
195 |           spend: number;
196 |           sales: number;
197 |           units_sold: number;
198 |           acos: number;
199 |           roas: number;
200 |           created_at?: string;
201 |           updated_at?: string | null;
202 |         };
203 |         Update: {
204 |           id?: string;
205 |           campaign_id?: string;
206 |           date?: string;
207 |           impressions?: number;
208 |           clicks?: number;
209 |           spend?: number;
210 |           sales?: number;
211 |           units_sold?: number;
212 |           acos?: number;
213 |           roas?: number;
214 |           created_at?: string;
215 |           updated_at?: string | null;
216 |         };
217 |       };
218 |     };
219 |     Views: {
220 |       [_ in never]: never;
221 |     };
222 |     Functions: {
223 |       [_ in never]: never;
224 |     };
225 |     Enums: {
226 |       [_ in never]: never;
227 |     };
228 |   };
229 | } 
```
--------------------------------------------------------------------------------
/Project-Docs/Project-plan.txt:
--------------------------------------------------------------------------------
```
  1 | # Amazon Ads MCP Server Implementation Plan
  2 | 
  3 | ## Overall Project Context
  4 | 
  5 | ### The Amazon Advertising Challenge
  6 | 
  7 | 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:
  8 | 
  9 | - Understanding campaign performance across different products
 10 | - Optimizing budgets based on performance data
 11 | - Identifying trends and opportunities in complex datasets
 12 | - Making data-driven decisions quickly without technical expertise
 13 | 
 14 | ### Our Integrated Solution
 15 | 
 16 | We're building a comprehensive ecosystem for Amazon advertisers consisting of two key components:
 17 | 
 18 | 1. **Web Application** - The central hub where advertisers:
 19 |    - Authenticate with Amazon through Login with Amazon (LWA)
 20 |    - Manage Amazon advertising profiles, campaigns, Ads groups, metrics in supabase
 21 |    - Generate API keys for each advertiser
 22 |    - Thw web app all it does it Auth with LWA and pulls advertiser data and generated an API keys
 23 |    - Token refresh, API life cycle is all managed in the webapp
 24 | 
 25 | 2. **MCP Server** - The Claude integration layer that allows advertisers to:
 26 |    - Have natural language conversations about their ad performance
 27 |    - Make campaign adjustments through simple commands
 28 |    - Receive AI-powered recommendations and insights
 29 |    - Analyze performance without needing to understand complex metrics
 30 |    - Take actions directly within Claude Desktop
 31 | 
 32 | This dual approach combines structured dashboard analytics with conversational AI capabilities, meeting advertisers where they work.
 33 | 
 34 | ## The Advertiser Journey
 35 | 
 36 | 1. **Onboarding & Authentication**
 37 |    - Advertiser signs up on our web application
 38 |    - Connects their Amazon Advertising account via LWA
 39 |    - Web app exchanges OAuth code for access tokens
 40 |    - System retrieves and stores profile data and campaign history
 41 | 
 42 | 2. **Data Analysis & Management**
 43 |    - Web app provides traditional dashboard analytics
 44 |    - Advertiser can view and manage campaigns through familiar interfaces
 45 |    - System regularly syncs campaign data to maintain current metrics
 46 | 
 47 | 3. **AI-Powered Assistance**
 48 |    - Advertiser generates an API key from the web application
 49 |    - Configures Claude Desktop with the MCP server and API key
 50 |    - Begins asking questions and managing campaigns conversationally
 51 |    - Claude interprets data and presents insights in natural language
 52 | 
 53 | ## MCP Server Implementation
 54 | 
 55 | ### Architecture Overview
 56 | 
 57 | The MCP server will be designed as a standalone Node.js application that:
 58 | 
 59 | 1. Validates advertiser API keys against our Supabase database
 60 | 2. Retrieves advertiser-specific campaign data
 61 | 3. Exposes standardized tools and resources through the MCP protocol
 62 | 4. Enables Claude to access and interpret campaign performance
 63 | 
 64 | The server will use a service account to access Supabase, with all queries scoped to the authenticated advertiser's ID to maintain data isolation.
 65 | 
 66 | ### Implementation Phases
 67 | 
 68 | #### Phase 1: Foundation & Authentication
 69 | 
 70 | We'll begin by establishing the MCP server's core structure and authentication mechanism:
 71 | 
 72 | 1. Set up the TypeScript project with MCP SDK dependencies
 73 | 2. Implement API key validation against the Supabase `api_keys` table
 74 | 3. Create the advertiser context once authentication succeeds
 75 | 4. Establish secure connection to Supabase
 76 | 
 77 | #### Phase 2: Data Access Layer
 78 | 
 79 | Next, we'll develop a comprehensive data access layer:
 80 | 
 81 | 1. Create query functions for campaigns, metrics, and advertiser data
 82 | 2. Implement smart filtering by advertiser ID on all queries
 83 | 3. Build data aggregation utilities for metrics analysis
 84 | 4. Add caching for frequently accessed data
 85 | 
 86 | #### Phase 3: MCP Resources & Tools
 87 | 
 88 | With data access in place, we'll implement the MCP interface:
 89 | 
 90 | 1. Build campaign resources (listings, details, metrics)
 91 | 2. Create campaign management tools (pause, enable, archive)
 92 | 3. Implement budget adjustment tools with validation
 93 | 4. Develop performance analysis and recommendation tools
 94 | 
 95 | #### Phase 4: Packaging & Distribution
 96 | 
 97 | Finally, we'll prepare for distribution:
 98 | 
 99 | 1. Set up NPM packaging for easy installation
100 | 2. Create user documentation and examples
101 | 3. Implement versioning strategy
102 | 4. Establish testing pipeline
103 | 
104 | ## Value Proposition for Advertisers
105 | 
106 | The integration of our web application with Claude through the MCP server delivers unique value to advertisers:
107 | 
108 | 1. **Democratized Analytics** - Non-technical advertisers can access complex performance data through natural conversation
109 | 2. **Contextual Insights** - Claude can interpret trends and suggest optimizations based on historical and current data
110 | 3. **Operational Efficiency** - Advertisers can quickly make campaign adjustments without switching contexts
111 | 4. **Reduced Learning Curve** - Natural language interface eliminates need to learn complex dashboard navigation
112 | 5. **Proactive Management** - Claude can suggest optimizations based on performance patterns
113 | 
114 | ## Security & Privacy Considerations
115 | 
116 | Throughout implementation, we'll maintain strict security practices:
117 | 
118 | 1. **Data Isolation** - Each advertiser's data is strictly isolated
119 | 2. **Minimal Permissions** - Service accounts use row-level security with minimal access
120 | 3. **Secure Authentication** - API keys are validated and never exposed
121 | 4. **No Data Storage** - The MCP server doesn't store data locally
122 | 
123 | ## Next Steps
124 | 
125 | The immediate actions will be:
126 | 
127 | 1. Finalize the web application's API key generation capability
128 | 2. Create the MCP server repository and development environment
129 | 3. Implement the core authentication and data access layers
130 | 4. Develop initial tools and resources for campaign management
131 | 5. Begin testing with Claude Desktop using sample advertiser accounts
132 | 
133 | 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
  1 | #!/usr/bin/env node
  2 | 
  3 | import fs from 'fs';
  4 | import path from 'path';
  5 | import { fileURLToPath } from 'url';
  6 | import { createRequire } from 'module';
  7 | import { exec } from 'child_process';
  8 | import { promisify } from 'util';
  9 | 
 10 | const execAsync = promisify(exec);
 11 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
 12 | const require = createRequire(import.meta.url);
 13 | const rootDir = path.join(__dirname, '..');
 14 | const packageJson = require('../package.json');
 15 | const distDir = path.join(rootDir, 'dist-package');
 16 | 
 17 | async function createPackage() {
 18 |   console.log('Creating distribution package...');
 19 |   
 20 |   // Create dist directory if it doesn't exist
 21 |   if (!fs.existsSync(distDir)) {
 22 |     fs.mkdirSync(distDir, { recursive: true });
 23 |   } else {
 24 |     // Clean existing files in dist directory
 25 |     fs.readdirSync(distDir).forEach(file => {
 26 |       const filePath = path.join(distDir, file);
 27 |       if (fs.lstatSync(filePath).isDirectory()) {
 28 |         fs.rmSync(filePath, { recursive: true, force: true });
 29 |       } else {
 30 |         fs.unlinkSync(filePath);
 31 |       }
 32 |     });
 33 |   }
 34 | 
 35 |   // Copy necessary files
 36 |   const filesToCopy = [
 37 |     'dist', 
 38 |     'node_modules',
 39 |     'package.json',
 40 |     'package-lock.json',
 41 |     'README.md',
 42 |     'setup-credentials.js',
 43 |     'index.js',
 44 |     'claude-desktop-config.json',
 45 |     '.env.example'
 46 |   ];
 47 | 
 48 |   filesToCopy.forEach(file => {
 49 |     const sourcePath = path.join(rootDir, file);
 50 |     const destPath = path.join(distDir, file);
 51 |     
 52 |     if (fs.existsSync(sourcePath)) {
 53 |       if (fs.lstatSync(sourcePath).isDirectory()) {
 54 |         fs.cpSync(sourcePath, destPath, { recursive: true });
 55 |       } else {
 56 |         fs.copyFileSync(sourcePath, destPath);
 57 |       }
 58 |     }
 59 |   });
 60 | 
 61 |   // Create installation script
 62 |   const installScript = `#!/usr/bin/env node
 63 | const { execSync } = require('child_process');
 64 | const fs = require('fs');
 65 | const path = require('path');
 66 | const readline = require('readline');
 67 | const os = require('os');
 68 | 
 69 | const rl = readline.createInterface({
 70 |   input: process.stdin,
 71 |   output: process.stdout
 72 | });
 73 | 
 74 | console.log('\\n=== Amazon Advertising MCP Server for Claude Desktop - Setup ===\\n');
 75 | 
 76 | function runSetup() {
 77 |   console.log('Running setup...');
 78 |   try {
 79 |     // Run setup-credentials.js to securely set up Supabase credentials
 80 |     console.log('\\nSetting up Supabase credentials...');
 81 |     require('./setup-credentials.js');
 82 |     
 83 |     // Create Claude Desktop configuration
 84 |     setupClaudeDesktop();
 85 |   } catch (error) {
 86 |     console.error('Error during setup:', error);
 87 |   }
 88 | }
 89 | 
 90 | function setupClaudeDesktop() {
 91 |   const configPaths = {
 92 |     win32: path.join(process.env.APPDATA, 'Claude', 'config.json'),
 93 |     darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'config.json'),
 94 |     linux: path.join(os.homedir(), '.config', 'Claude', 'config.json')
 95 |   };
 96 |   
 97 |   const configPath = configPaths[process.platform];
 98 |   if (!configPath) {
 99 |     console.log('\\nUnsupported operating system for automatic Claude Desktop configuration.');
100 |     console.log('Please manually add the MCP server configuration to your Claude Desktop config file.');
101 |     return;
102 |   }
103 |   
104 |   console.log(\`\\nConfiguring Claude Desktop at: \${configPath}\`);
105 |   
106 |   // Check if the Claude config directory exists
107 |   const configDir = path.dirname(configPath);
108 |   if (!fs.existsSync(configDir)) {
109 |     console.log('Claude Desktop config directory does not exist. Creating it...');
110 |     fs.mkdirSync(configDir, { recursive: true });
111 |   }
112 |   
113 |   // Read existing config or create new one
114 |   let config = {};
115 |   if (fs.existsSync(configPath)) {
116 |     try {
117 |       config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
118 |       console.log('Existing Claude Desktop configuration found.');
119 |     } catch (e) {
120 |       console.log('Error reading Claude Desktop config. Creating a new one.');
121 |     }
122 |   }
123 |   
124 |   // Ensure mcpServers object exists
125 |   if (!config.mcpServers) config.mcpServers = {};
126 |   
127 |   // Get current directory
128 |   const currentDir = process.cwd();
129 |   
130 |   // Set up the MCP server configuration
131 |   rl.question('\\nEnter your Amazon Advertising API Key: ', (apiKey) => {
132 |     config.mcpServers['amazon-ads'] = {
133 |       command: 'node',
134 |       args: [path.join(currentDir, 'index.js')],
135 |       cwd: currentDir,
136 |       env: {
137 |         API_KEY: apiKey,
138 |         NODE_ENV: 'production'
139 |       }
140 |     };
141 |     
142 |     // Write the updated config
143 |     fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
144 |     console.log(\`\\nClaude Desktop configuration updated at: \${configPath}\`);
145 |     console.log('\\nSetup complete! You can now use the Amazon Advertising MCP Server with Claude Desktop.');
146 |     rl.close();
147 |   });
148 | }
149 | 
150 | runSetup();
151 | `;
152 | 
153 |   fs.writeFileSync(path.join(distDir, 'install.js'), installScript);
154 |   fs.chmodSync(path.join(distDir, 'install.js'), '755');
155 | 
156 |   // Create simple README with installation instructions
157 |   const readmeContent = `# Amazon Advertising MCP Server for Claude Desktop
158 | 
159 | ## Installation
160 | 
161 | 1. Extract this package to a directory on your computer
162 | 2. Install dependencies:
163 |    \`\`\`
164 |    npm install
165 |    \`\`\`
166 | 3. Run the installation script:
167 |    \`\`\`
168 |    node install.js
169 |    \`\`\`
170 |    This will:
171 |    - Set up your Supabase credentials securely
172 |    - Configure Claude Desktop to use this MCP server
173 |    - Prompt you for your Amazon Advertising API Key
174 | 
175 | ## Manual Configuration
176 | 
177 | If the automatic setup doesn't work, you can configure Claude Desktop manually:
178 | 
179 | - Edit your Claude Desktop config file:
180 |   - Windows: \`%APPDATA%\\Claude\\config.json\`
181 |   - macOS: \`~/Library/Application Support/Claude/config.json\`
182 |   - Linux: \`~/.config/Claude/config.json\`
183 | 
184 | - Add this configuration (replace paths and API key with your own):
185 | \`\`\`json
186 | {
187 |   "mcpServers": {
188 |     "amazon-ads": {
189 |       "command": "node",
190 |       "args": ["/absolute/path/to/extracted/package/index.js"],
191 |       "cwd": "/absolute/path/to/extracted/package",
192 |       "env": {
193 |         "API_KEY": "your_api_key_here",
194 |         "NODE_ENV": "production"
195 |       }
196 |     }
197 |   }
198 | }
199 | \`\`\`
200 | `;
201 | 
202 |   fs.writeFileSync(path.join(distDir, 'INSTALL.md'), readmeContent);
203 | 
204 |   // Create archive
205 |   try {
206 |     const packageName = `${packageJson.name}-${packageJson.version}`;
207 |     const archivePath = path.join(rootDir, `${packageName}.zip`);
208 |     
209 |     console.log(`Creating archive: ${archivePath}`);
210 |     await execAsync(`cd "${distDir}" && zip -r "${archivePath}" .`);
211 |     
212 |     console.log(`\nDistribution package created successfully at: ${archivePath}`);
213 |     console.log('You can distribute this ZIP file to your users.');
214 |   } catch (error) {
215 |     console.error('Error creating archive:', error);
216 |   }
217 | }
218 | 
219 | createPackage().catch(console.error); 
```
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | // Amazon Advertising MCP Server for Claude Desktop
  4 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  5 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  6 | import dotenv from 'dotenv';
  7 | import { supabase, validateApiKey } from './src/config/supabase.js';
  8 | import { AdvertiserService } from './src/services/advertiser.service.js';
  9 | import fs from 'fs';
 10 | import { z } from 'zod';
 11 | 
 12 | // Load environment variables
 13 | dotenv.config();
 14 | 
 15 | // Set up debugging log
 16 | const logFile = fs.createWriteStream('mcp-debug.log', { flags: 'a' });
 17 | function debug(message) {
 18 |   const timestamp = new Date().toISOString();
 19 |   const logMessage = `${timestamp} - ${message}\n`;
 20 |   logFile.write(logMessage);
 21 |   // Also log to stderr for console viewing during development
 22 |   console.error(logMessage);
 23 | }
 24 | 
 25 | debug('Starting Amazon Advertising MCP Server...');
 26 | debug(`MCP Server Version: ${process.env.MCP_SERVER_VERSION || 'dev'}`);
 27 | debug(`Received API_KEY: ${process.env.API_KEY ? 'YES (set)' : 'NO (not set)'}`);
 28 | debug(`Environment variables: ${JSON.stringify(process.env, (key, value) => {
 29 |   // Mask sensitive values but show which variables exist
 30 |   if (key.toUpperCase().includes('KEY') || key.toUpperCase().includes('SECRET')) {
 31 |     return value ? '[SET]' : '[NOT SET]';
 32 |   }
 33 |   return value;
 34 | }, 2)}`);
 35 | 
 36 | // Initialize MCP Server
 37 | const server = new McpServer({
 38 |   name: "Amazon Advertising",
 39 |   version: process.env.MCP_SERVER_VERSION || '1.0.0'
 40 | });
 41 | 
 42 | // Define the getAdvertiserInfo tool with proper schema format
 43 | server.tool("getAdvertiserInfo", 
 44 |   {}, // Empty schema since no parameters needed
 45 |   async () => {
 46 |     debug('Received request for getAdvertiserInfo');
 47 |     
 48 |     try {
 49 |       // For testing, get the first advertiser from the database
 50 |       const { data: advertisers, error } = await supabase
 51 |         .from('advertisers')
 52 |         .select('*')
 53 |         .limit(1);
 54 |         
 55 |       if (error || !advertisers || advertisers.length === 0) {
 56 |         debug(`Error or no advertisers found: ${error ? error.message : 'No advertisers'}`);
 57 |         return {
 58 |           content: [{ type: "text", text: "Error: No advertiser accounts found" }],
 59 |           isError: true
 60 |         };
 61 |       }
 62 |       
 63 |       const advertiser = advertisers[0];
 64 |       debug(`Found advertiser: ${advertiser.account_name}`);
 65 |       
 66 |       return {
 67 |         content: [{ 
 68 |           type: "text", 
 69 |           text: JSON.stringify({
 70 |             id: advertiser.id,
 71 |             name: advertiser.account_name,
 72 |             marketplace: advertiser.marketplace,
 73 |             accountType: advertiser.account_type,
 74 |             profileId: advertiser.profile_id,
 75 |             countryCode: advertiser.metadata?.countryCode || 'Unknown',
 76 |             currencyCode: advertiser.metadata?.currencyCode || 'USD'
 77 |           }, null, 2)
 78 |         }]
 79 |       };
 80 |     } catch (error) {
 81 |       debug(`Error getting advertiser info: ${error.message}`);
 82 |       return {
 83 |         content: [{ type: "text", text: `Error: ${error.message}` }],
 84 |         isError: true
 85 |       };
 86 |     }
 87 |   }
 88 | );
 89 | 
 90 | // Define the listAdvertiserAccounts tool with proper schema format
 91 | server.tool("listAdvertiserAccounts", 
 92 |   {}, // Empty schema since no parameters needed
 93 |   async () => {
 94 |     debug('Received request for listAdvertiserAccounts');
 95 |     
 96 |     try {
 97 |       // Get all advertisers
 98 |       const { data: advertisers, error } = await supabase
 99 |         .from('advertisers')
100 |         .select('id, account_name, marketplace, account_type, metadata')
101 |         .order('account_name', { ascending: true });
102 |         
103 |       if (error) {
104 |         debug(`Error fetching advertisers: ${error.message}`);
105 |         return {
106 |           content: [{ type: "text", text: `Error: ${error.message}` }],
107 |           isError: true
108 |         };
109 |       }
110 |       
111 |       if (!advertisers || advertisers.length === 0) {
112 |         debug('No advertisers found');
113 |         return {
114 |           content: [{ 
115 |             type: "text", 
116 |             text: "No advertiser accounts were found in the database"
117 |           }]
118 |         };
119 |       }
120 |       
121 |       // Format the advertiser data
122 |       const formattedAdvertisers = advertisers.map(adv => ({
123 |         id: adv.id,
124 |         accountName: adv.account_name,
125 |         marketplace: adv.marketplace,
126 |         accountType: adv.account_type,
127 |         countryCode: adv.metadata?.countryCode || 'Unknown',
128 |         currencyCode: adv.metadata?.currencyCode || 'USD'
129 |       }));
130 |       
131 |       debug(`Found ${formattedAdvertisers.length} advertisers`);
132 |       
133 |       return {
134 |         content: [{ 
135 |           type: "text", 
136 |           text: JSON.stringify(formattedAdvertisers, null, 2)
137 |         }]
138 |       };
139 |     } catch (error) {
140 |       debug(`Error listing advertiser accounts: ${error.message}`);
141 |       return {
142 |         content: [{ type: "text", text: `Error: ${error.message}` }],
143 |         isError: true
144 |       };
145 |     }
146 |   }
147 | );
148 | 
149 | // Add a simple non-database dependent tool for testing connectivity
150 | server.tool("ping", 
151 |   {}, // No parameters needed
152 |   async () => {
153 |     debug('Received ping request - testing connection');
154 |     return {
155 |       content: [{ 
156 |         type: "text", 
157 |         text: `Server is running correctly!
158 | Time: ${new Date().toISOString()}
159 | Server name: Amazon Advertising
160 | Server version: ${process.env.MCP_SERVER_VERSION || '1.0.0'}`
161 |       }]
162 |     };
163 |   }
164 | );
165 | 
166 | // Add an echo tool for testing with parameters
167 | server.tool("echo", 
168 |   { message: z.string() },
169 |   async ({ message }) => {
170 |     debug(`Received echo request with message: ${message}`);
171 |     return {
172 |       content: [{ 
173 |         type: "text", 
174 |         text: `You said: ${message}`
175 |       }]
176 |     };
177 |   }
178 | );
179 | 
180 | // Add a tool for validating API key that doesn't rely on database access
181 | server.tool("validateApiKey", 
182 |   {}, // No parameters needed
183 |   async () => {
184 |     debug('Received request to validate API key');
185 |     const apiKey = process.env.API_KEY;
186 |     
187 |     if (!apiKey) {
188 |       debug('No API key provided in environment variables');
189 |       return {
190 |         content: [{ 
191 |           type: "text", 
192 |           text: `Error: No API_KEY provided in environment variables.`
193 |         }],
194 |         isError: true
195 |       };
196 |     }
197 |     
198 |     try {
199 |       debug(`Attempting to validate API key: ${apiKey.substring(0, 8)}...`);
200 |       const { valid, advertiserId, error } = await validateApiKey(apiKey);
201 |       
202 |       if (!valid) {
203 |         debug(`API key validation failed: ${error}`);
204 |         return {
205 |           content: [{ 
206 |             type: "text", 
207 |             text: `API key validation failed: ${error || 'Unknown error'}`
208 |           }],
209 |           isError: true
210 |         };
211 |       }
212 |       
213 |       debug(`API key validated successfully for advertiser ID: ${advertiserId}`);
214 |       return {
215 |         content: [{ 
216 |           type: "text", 
217 |           text: `API key is valid for advertiser ID: ${advertiserId}`
218 |         }]
219 |       };
220 |     } catch (error) {
221 |       debug(`Error validating API key: ${error.message}`);
222 |       return {
223 |         content: [{ 
224 |           type: "text", 
225 |           text: `Error validating API key: ${error.message}. This could be due to missing database credentials.`
226 |         }],
227 |         isError: true
228 |       };
229 |     }
230 |   }
231 | );
232 | 
233 | // Start the server with stdin/stdout transport
234 | debug('Starting MCP server with stdio transport');
235 | const transport = new StdioServerTransport();
236 | 
237 | // Add better error handling for server connection
238 | try {
239 |   debug('Attempting to connect MCP server to transport');
240 |   server.connect(transport).then(() => {
241 |     debug('MCP server successfully connected to transport and ready to handle requests from Claude Desktop');
242 |     
243 |     // Keep the process alive
244 |     process.on('SIGINT', () => {
245 |       debug('Received SIGINT, shutting down server...');
246 |       process.exit(0);
247 |     });
248 |   }).catch(error => {
249 |     debug(`Error connecting MCP server to transport: ${error.message}\n${error.stack}`);
250 |   });
251 | } catch (error) {
252 |   debug(`Critical error starting MCP server: ${error.message}\n${error.stack}`);
253 | }
254 | 
255 | // Add heartbeat logging to confirm server is alive
256 | setInterval(() => {
257 |   debug('MCP server heartbeat - still running');
258 | }, 60000); // Log every minute
259 | 
260 | // Add error handling for uncaught exceptions
261 | process.on('uncaughtException', (error) => {
262 |   debug(`Uncaught exception: ${error.message}\n${error.stack}`);
263 |   // Keep the server running despite errors
264 | });
265 | 
266 | process.on('unhandledRejection', (reason, promise) => {
267 |   debug(`Unhandled promise rejection: ${reason}`);
268 |   // Keep the server running despite errors
269 | }); 
```
--------------------------------------------------------------------------------
/Project-Docs/MCP TypeScript SDK.txt:
--------------------------------------------------------------------------------
```
  1 | # MCP TypeScript SDK  
  2 | 
  3 | ## Table of Contents
  4 | - [Overview](#overview)
  5 | - [Installation](#installation)
  6 | - [Quickstart](#quickstart)
  7 | - [What is MCP?](#what-is-mcp)
  8 | - [Core Concepts](#core-concepts)
  9 |   - [Server](#server)
 10 |   - [Resources](#resources)
 11 |   - [Tools](#tools)
 12 |   - [Prompts](#prompts)
 13 | - [Running Your Server](#running-your-server)
 14 |   - [stdio](#stdio)
 15 |   - [HTTP with SSE](#http-with-sse)
 16 |   - [Testing and Debugging](#testing-and-debugging)
 17 | - [Examples](#examples)
 18 |   - [Echo Server](#echo-server)
 19 |   - [SQLite Explorer](#sqlite-explorer)
 20 | - [Advanced Usage](#advanced-usage)
 21 |   - [Low-Level Server](#low-level-server)
 22 |   - [Writing MCP Clients](#writing-mcp-clients)
 23 |   - [Server Capabilities](#server-capabilities)
 24 | 
 25 | ## Overview
 26 | 
 27 | 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:
 28 | 
 29 | - Build MCP clients that can connect to any MCP server
 30 | - Create MCP servers that expose resources, prompts and tools
 31 | - Use standard transports like stdio and SSE
 32 | - Handle all MCP protocol messages and lifecycle events
 33 | 
 34 | ## Installation
 35 | 
 36 | ```bash
 37 | npm install @modelcontextprotocol/sdk
 38 | ```
 39 | 
 40 | ## Quick Start
 41 | 
 42 | Let's create a simple MCP server that exposes a calculator tool and some data:
 43 | 
 44 | ```typescript
 45 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
 46 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 47 | import { z } from "zod";
 48 | 
 49 | // Create an MCP server
 50 | const server = new McpServer({
 51 |   name: "Demo",
 52 |   version: "1.0.0"
 53 | });
 54 | 
 55 | // Add an addition tool
 56 | server.tool("add",
 57 |   { a: z.number(), b: z.number() },
 58 |   async ({ a, b }) => ({
 59 |     content: [{ type: "text", text: String(a + b) }]
 60 |   })
 61 | );
 62 | 
 63 | // Add a dynamic greeting resource
 64 | server.resource(
 65 |   "greeting",
 66 |   new ResourceTemplate("greeting://{name}", { list: undefined }),
 67 |   async (uri, { name }) => ({
 68 |     contents: [{
 69 |       uri: uri.href,
 70 |       text: `Hello, ${name}!`
 71 |     }]
 72 |   })
 73 | );
 74 | 
 75 | // Start receiving messages on stdin and sending messages on stdout
 76 | const transport = new StdioServerTransport();
 77 | await server.connect(transport);
 78 | ```
 79 | 
 80 | ## What is MCP?
 81 | 
 82 | 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:
 83 | 
 84 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
 85 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
 86 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
 87 | - And more!
 88 | 
 89 | ## Core Concepts
 90 | 
 91 | ### Server
 92 | 
 93 | The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
 94 | 
 95 | ```typescript
 96 | const server = new McpServer({
 97 |   name: "My App",
 98 |   version: "1.0.0"
 99 | });
100 | ```
101 | 
102 | ### Resources
103 | 
104 | 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:
105 | 
106 | ```typescript
107 | // Static resource
108 | server.resource(
109 |   "config",
110 |   "config://app",
111 |   async (uri) => ({
112 |     contents: [{
113 |       uri: uri.href,
114 |       text: "App configuration here"
115 |     }]
116 |   })
117 | );
118 | 
119 | // Dynamic resource with parameters
120 | server.resource(
121 |   "user-profile",
122 |   new ResourceTemplate("users://{userId}/profile", { list: undefined }),
123 |   async (uri, { userId }) => ({
124 |     contents: [{
125 |       uri: uri.href,
126 |       text: `Profile data for user ${userId}`
127 |     }]
128 |   })
129 | );
130 | ```
131 | 
132 | ### Tools
133 | 
134 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
135 | 
136 | ```typescript
137 | // Simple tool with parameters
138 | server.tool(
139 |   "calculate-bmi",
140 |   {
141 |     weightKg: z.number(),
142 |     heightM: z.number()
143 |   },
144 |   async ({ weightKg, heightM }) => ({
145 |     content: [{
146 |       type: "text",
147 |       text: String(weightKg / (heightM * heightM))
148 |     }]
149 |   })
150 | );
151 | 
152 | // Async tool with external API call
153 | server.tool(
154 |   "fetch-weather",
155 |   { city: z.string() },
156 |   async ({ city }) => {
157 |     const response = await fetch(`https://api.weather.com/${city}`);
158 |     const data = await response.text();
159 |     return {
160 |       content: [{ type: "text", text: data }]
161 |     };
162 |   }
163 | );
164 | ```
165 | 
166 | ### Prompts
167 | 
168 | Prompts are reusable templates that help LLMs interact with your server effectively:
169 | 
170 | ```typescript
171 | server.prompt(
172 |   "review-code",
173 |   { code: z.string() },
174 |   ({ code }) => ({
175 |     messages: [{
176 |       role: "user",
177 |       content: {
178 |         type: "text",
179 |         text: `Please review this code:\n\n${code}`
180 |       }
181 |     }]
182 |   })
183 | );
184 | ```
185 | 
186 | ## Running Your Server
187 | 
188 | 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:
189 | 
190 | ### stdio
191 | 
192 | For command-line tools and direct integrations:
193 | 
194 | ```typescript
195 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
196 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
197 | 
198 | const server = new McpServer({
199 |   name: "example-server",
200 |   version: "1.0.0"
201 | });
202 | 
203 | // ... set up server resources, tools, and prompts ...
204 | 
205 | const transport = new StdioServerTransport();
206 | await server.connect(transport);
207 | ```
208 | 
209 | ### HTTP with SSE
210 | 
211 | 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:
212 | 
213 | ```typescript
214 | import express from "express";
215 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
216 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
217 | 
218 | const server = new McpServer({
219 |   name: "example-server",
220 |   version: "1.0.0"
221 | });
222 | 
223 | // ... set up server resources, tools, and prompts ...
224 | 
225 | const app = express();
226 | 
227 | app.get("/sse", async (req, res) => {
228 |   const transport = new SSEServerTransport("/messages", res);
229 |   await server.connect(transport);
230 | });
231 | 
232 | app.post("/messages", async (req, res) => {
233 |   // Note: to support multiple simultaneous connections, these messages will
234 |   // need to be routed to a specific matching transport. (This logic isn't
235 |   // implemented here, for simplicity.)
236 |   await transport.handlePostMessage(req, res);
237 | });
238 | 
239 | app.listen(3001);
240 | ```
241 | 
242 | ### Testing and Debugging
243 | 
244 | To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.
245 | 
246 | ## Examples
247 | 
248 | ### Echo Server
249 | 
250 | A simple server demonstrating resources, tools, and prompts:
251 | 
252 | ```typescript
253 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
254 | import { z } from "zod";
255 | 
256 | const server = new McpServer({
257 |   name: "Echo",
258 |   version: "1.0.0"
259 | });
260 | 
261 | server.resource(
262 |   "echo",
263 |   new ResourceTemplate("echo://{message}", { list: undefined }),
264 |   async (uri, { message }) => ({
265 |     contents: [{
266 |       uri: uri.href,
267 |       text: `Resource echo: ${message}`
268 |     }]
269 |   })
270 | );
271 | 
272 | server.tool(
273 |   "echo",
274 |   { message: z.string() },
275 |   async ({ message }) => ({
276 |     content: [{ type: "text", text: `Tool echo: ${message}` }]
277 |   })
278 | );
279 | 
280 | server.prompt(
281 |   "echo",
282 |   { message: z.string() },
283 |   ({ message }) => ({
284 |     messages: [{
285 |       role: "user",
286 |       content: {
287 |         type: "text",
288 |         text: `Please process this message: ${message}`
289 |       }
290 |     }]
291 |   })
292 | );
293 | ```
294 | 
295 | ### SQLite Explorer
296 | 
297 | A more complex example showing database integration:
298 | 
299 | ```typescript
300 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
301 | import sqlite3 from "sqlite3";
302 | import { promisify } from "util";
303 | import { z } from "zod";
304 | 
305 | const server = new McpServer({
306 |   name: "SQLite Explorer",
307 |   version: "1.0.0"
308 | });
309 | 
310 | // Helper to create DB connection
311 | const getDb = () => {
312 |   const db = new sqlite3.Database("database.db");
313 |   return {
314 |     all: promisify<string, any[]>(db.all.bind(db)),
315 |     close: promisify(db.close.bind(db))
316 |   };
317 | };
318 | 
319 | server.resource(
320 |   "schema",
321 |   "schema://main",
322 |   async (uri) => {
323 |     const db = getDb();
324 |     try {
325 |       const tables = await db.all(
326 |         "SELECT sql FROM sqlite_master WHERE type='table'"
327 |       );
328 |       return {
329 |         contents: [{
330 |           uri: uri.href,
331 |           text: tables.map((t: {sql: string}) => t.sql).join("\n")
332 |         }]
333 |       };
334 |     } finally {
335 |       await db.close();
336 |     }
337 |   }
338 | );
339 | 
340 | server.tool(
341 |   "query",
342 |   { sql: z.string() },
343 |   async ({ sql }) => {
344 |     const db = getDb();
345 |     try {
346 |       const results = await db.all(sql);
347 |       return {
348 |         content: [{
349 |           type: "text",
350 |           text: JSON.stringify(results, null, 2)
351 |         }]
352 |       };
353 |     } catch (err: unknown) {
354 |       const error = err as Error;
355 |       return {
356 |         content: [{
357 |           type: "text",
358 |           text: `Error: ${error.message}`
359 |         }],
360 |         isError: true
361 |       };
362 |     } finally {
363 |       await db.close();
364 |     }
365 |   }
366 | );
367 | ```
368 | 
369 | ## Advanced Usage
370 | 
371 | ### Low-Level Server
372 | 
373 | For more control, you can use the low-level Server class directly:
374 | 
375 | ```typescript
376 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
377 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
378 | import {
379 |   ListPromptsRequestSchema,
380 |   GetPromptRequestSchema
381 | } from "@modelcontextprotocol/sdk/types.js";
382 | 
383 | const server = new Server(
384 |   {
385 |     name: "example-server",
386 |     version: "1.0.0"
387 |   },
388 |   {
389 |     capabilities: {
390 |       prompts: {}
391 |     }
392 |   }
393 | );
394 | 
395 | server.setRequestHandler(ListPromptsRequestSchema, async () => {
396 |   return {
397 |     prompts: [{
398 |       name: "example-prompt",
399 |       description: "An example prompt template",
400 |       arguments: [{
401 |         name: "arg1",
402 |         description: "Example argument",
403 |         required: true
404 |       }]
405 |     }]
406 |   };
407 | });
408 | 
409 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
410 |   if (request.params.name !== "example-prompt") {
411 |     throw new Error("Unknown prompt");
412 |   }
413 |   return {
414 |     description: "Example prompt",
415 |     messages: [{
416 |       role: "user",
417 |       content: {
418 |         type: "text",
419 |         text: "Example prompt text"
420 |       }
421 |     }]
422 |   };
423 | });
424 | 
425 | const transport = new StdioServerTransport();
426 | await server.connect(transport);
427 | ```
428 | 
429 | ### Writing MCP Clients
430 | 
431 | The SDK provides a high-level client interface:
432 | 
433 | ```typescript
434 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
435 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
436 | 
437 | const transport = new StdioClientTransport({
438 |   command: "node",
439 |   args: ["server.js"]
440 | });
441 | 
442 | const client = new Client(
443 |   {
444 |     name: "example-client",
445 |     version: "1.0.0"
446 |   },
447 |   {
448 |     capabilities: {
449 |       prompts: {},
450 |       resources: {},
451 |       tools: {}
452 |     }
453 |   }
454 | );
455 | 
456 | await client.connect(transport);
457 | 
458 | // List prompts
459 | const prompts = await client.listPrompts();
460 | 
461 | // Get a prompt
462 | const prompt = await client.getPrompt("example-prompt", {
463 |   arg1: "value"
464 | });
465 | 
466 | // List resources
467 | const resources = await client.listResources();
468 | 
469 | // Read a resource
470 | const resource = await client.readResource("file:///example.txt");
471 | 
472 | // Call a tool
473 | const result = await client.callTool({
474 |   name: "example-tool",
475 |   arguments: {
476 |     arg1: "value"
477 |   }
478 | });
479 | ```
480 | 
481 | ## Documentation
482 | 
483 | - [Model Context Protocol documentation](https://modelcontextprotocol.io)
484 | - [MCP Specification](https://spec.modelcontextprotocol.io)
485 | - [Example Servers](https://github.com/modelcontextprotocol/servers)
486 | 
487 | ## Contributing
488 | 
489 | Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.
490 | 
491 | ## License
492 | 
493 | This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
```