meta-ads-mcp 0.2.4__tar.gz → 0.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/PKG-INFO +21 -11
  2. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/README.md +20 -10
  3. meta_ads_mcp-0.2.5/future_improvements.md +11 -0
  4. meta_ads_mcp-0.2.5/images/meta-ads-example.png +0 -0
  5. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/__init__.py +1 -1
  6. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/adsets.py +32 -2
  7. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/api.py +68 -24
  8. meta_ads_mcp-0.2.5/meta_ads_mcp/core/auth.py +1693 -0
  9. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/authentication.py +10 -1
  10. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/utils.py +10 -0
  11. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/pyproject.toml +1 -1
  12. meta_ads_mcp-0.2.4/future_improvements.md +0 -63
  13. meta_ads_mcp-0.2.4/meta_ads_mcp/core/auth.py +0 -863
  14. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/.gitignore +0 -0
  15. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/META_API_NOTES.md +0 -0
  16. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta-ads-mcp +0 -0
  17. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/__main__.py +0 -0
  18. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/api.py +0 -0
  19. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/__init__.py +0 -0
  20. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/accounts.py +0 -0
  21. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/ads.py +0 -0
  22. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/campaigns.py +0 -0
  23. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/insights.py +0 -0
  24. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/resources.py +0 -0
  25. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/meta_ads_mcp/core/server.py +0 -0
  26. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/requirements.txt +0 -0
  27. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/setup.py +0 -0
  28. {meta_ads_mcp-0.2.4 → meta_ads_mcp-0.2.5}/test_meta_ads_auth.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Model Calling Protocol (MCP) plugin for interacting with Meta Ads API
5
5
  Project-URL: Homepage, https://github.com/nictuku/meta-ads-mcp
6
6
  Project-URL: Bug Tracker, https://github.com/nictuku/meta-ads-mcp/issues
@@ -21,16 +21,26 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # Meta Ads MCP
23
23
 
24
- A [Model Calling Protocol (MCP)](https://github.com/anthropics/anthropic-tools) plugin for interacting with Meta Ads API.
24
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for interacting with Meta Ads API. This tool enables AI models to access, analyze, and manage Meta advertising campaigns through a standardized interface, allowing LLMs to retrieve performance data, visualize ad creatives, and provide strategic insights for Facebook, Instagram, and other Meta platforms.
25
+
26
+ > **DISCLAIMER:** This is an unofficial third-party tool and is not associated with, endorsed by, or affiliated with Meta in any way. This project is maintained independently and uses Meta's public APIs according to their terms of service. Meta, Facebook, Instagram, and other Meta brand names are trademarks of their respective owners.
27
+
28
+ Screenhot: using an LLM to understand your ad performance.
29
+
30
+ ![Meta Ads MCP in action: Visualize ad performance metrics and creative details directly in Claude or your favorite MCP client, with rich insights about campaign reach, engagement, and costs](./images/meta-ads-example.png)
25
31
 
26
32
  ## Features
27
33
 
28
- - Seamless authentication with Meta's Graph API for desktop applications
29
- - Automatic token caching across sessions
30
- - Cross-platform support (Windows, macOS, Linux)
31
- - Access to ad accounts, campaigns, ad sets, and ads
32
- - Image download and analysis capabilities
33
- - Performance insights
34
+ - **AI-Powered Campaign Analysis**: Let your favorite LLM analyze your campaigns and provide actionable insights on performance
35
+ - **Strategic Recommendations**: Receive data-backed suggestions for optimizing ad spend, targeting, and creative content
36
+ - **Automated Monitoring**: Ask any MCP-compatible LLM to track performance metrics and alert you about significant changes
37
+ - **Budget Optimization**: Get recommendations for reallocating budget to better-performing ad sets
38
+ - **Creative Improvement**: Receive feedback on ad copy, imagery, and calls-to-action
39
+ - **Campaign Management**: Request changes to campaigns, ad sets, and ads (all changes require explicit confirmation)
40
+ - **Cross-Platform Integration**: Works with Facebook, Instagram, and all Meta ad platforms
41
+ - **Universal LLM Support**: Compatible with any MCP client including Claude Desktop, Cursor, Cherry Studio, and more
42
+ - **Simple Authentication**: Easy setup with secure OAuth authentication
43
+ - **Cross-Platform Support**: Works on Windows, macOS, and Linux
34
44
 
35
45
  ## Installation
36
46
 
@@ -39,7 +49,7 @@ A [Model Calling Protocol (MCP)](https://github.com/anthropics/anthropic-tools)
39
49
  When using uv no specific installation is needed. We can use uvx to directly run meta-ads-mcp:
40
50
 
41
51
  ```bash
42
- uvx meta-ads-mcp
52
+ uvx meta-ads-mcp --app-id YOUR_META_ADS_APP_ID
43
53
  ```
44
54
 
45
55
  If you want to install the package:
@@ -79,7 +89,7 @@ Add this to your `claude_desktop_config.json` to integrate with Claude in Cursor
79
89
  "mcpServers": {
80
90
  "meta-ads": {
81
91
  "command": "uvx",
82
- "args": ["meta-ads-mcp"]
92
+ "args": ["meta-ads-mcp", "--app-id", "YOUR_META_ADS_APP_ID"]
83
93
  }
84
94
  }
85
95
  ```
@@ -350,4 +360,4 @@ You can check the current version of the package:
350
360
  ```python
351
361
  import meta_ads_mcp
352
362
  print(meta_ads_mcp.__version__)
353
- ```
363
+ ```
@@ -1,15 +1,25 @@
1
1
  # Meta Ads MCP
2
2
 
3
- A [Model Calling Protocol (MCP)](https://github.com/anthropics/anthropic-tools) plugin for interacting with Meta Ads API.
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for interacting with Meta Ads API. This tool enables AI models to access, analyze, and manage Meta advertising campaigns through a standardized interface, allowing LLMs to retrieve performance data, visualize ad creatives, and provide strategic insights for Facebook, Instagram, and other Meta platforms.
4
+
5
+ > **DISCLAIMER:** This is an unofficial third-party tool and is not associated with, endorsed by, or affiliated with Meta in any way. This project is maintained independently and uses Meta's public APIs according to their terms of service. Meta, Facebook, Instagram, and other Meta brand names are trademarks of their respective owners.
6
+
7
+ Screenhot: using an LLM to understand your ad performance.
8
+
9
+ ![Meta Ads MCP in action: Visualize ad performance metrics and creative details directly in Claude or your favorite MCP client, with rich insights about campaign reach, engagement, and costs](./images/meta-ads-example.png)
4
10
 
5
11
  ## Features
6
12
 
7
- - Seamless authentication with Meta's Graph API for desktop applications
8
- - Automatic token caching across sessions
9
- - Cross-platform support (Windows, macOS, Linux)
10
- - Access to ad accounts, campaigns, ad sets, and ads
11
- - Image download and analysis capabilities
12
- - Performance insights
13
+ - **AI-Powered Campaign Analysis**: Let your favorite LLM analyze your campaigns and provide actionable insights on performance
14
+ - **Strategic Recommendations**: Receive data-backed suggestions for optimizing ad spend, targeting, and creative content
15
+ - **Automated Monitoring**: Ask any MCP-compatible LLM to track performance metrics and alert you about significant changes
16
+ - **Budget Optimization**: Get recommendations for reallocating budget to better-performing ad sets
17
+ - **Creative Improvement**: Receive feedback on ad copy, imagery, and calls-to-action
18
+ - **Campaign Management**: Request changes to campaigns, ad sets, and ads (all changes require explicit confirmation)
19
+ - **Cross-Platform Integration**: Works with Facebook, Instagram, and all Meta ad platforms
20
+ - **Universal LLM Support**: Compatible with any MCP client including Claude Desktop, Cursor, Cherry Studio, and more
21
+ - **Simple Authentication**: Easy setup with secure OAuth authentication
22
+ - **Cross-Platform Support**: Works on Windows, macOS, and Linux
13
23
 
14
24
  ## Installation
15
25
 
@@ -18,7 +28,7 @@ A [Model Calling Protocol (MCP)](https://github.com/anthropics/anthropic-tools)
18
28
  When using uv no specific installation is needed. We can use uvx to directly run meta-ads-mcp:
19
29
 
20
30
  ```bash
21
- uvx meta-ads-mcp
31
+ uvx meta-ads-mcp --app-id YOUR_META_ADS_APP_ID
22
32
  ```
23
33
 
24
34
  If you want to install the package:
@@ -58,7 +68,7 @@ Add this to your `claude_desktop_config.json` to integrate with Claude in Cursor
58
68
  "mcpServers": {
59
69
  "meta-ads": {
60
70
  "command": "uvx",
61
- "args": ["meta-ads-mcp"]
71
+ "args": ["meta-ads-mcp", "--app-id", "YOUR_META_ADS_APP_ID"]
62
72
  }
63
73
  }
64
74
  ```
@@ -329,4 +339,4 @@ You can check the current version of the package:
329
339
  ```python
330
340
  import meta_ads_mcp
331
341
  print(meta_ads_mcp.__version__)
332
- ```
342
+ ```
@@ -0,0 +1,11 @@
1
+ # Future Improvements for Meta Ads MCP
2
+
3
+ ## Note about Meta Ads development work
4
+
5
+ If you update the MCP server code, please note that *I* have to restart the MCP server. After the server code is changed, ask me to restart it and then proceed with your testing after I confirm it's restarted.
6
+
7
+ ## Access token should remain internal only
8
+
9
+ Don't share it ever with the LLM, only update the auth cache.
10
+
11
+ Future improvements can be added to this file as needed.
@@ -7,7 +7,7 @@ with the Claude LLM.
7
7
 
8
8
  from meta_ads_mcp.core.server import main
9
9
 
10
- __version__ = "0.2.4"
10
+ __version__ = "0.2.5"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -7,6 +7,7 @@ from .accounts import get_ad_accounts
7
7
  from .server import mcp_server
8
8
  import asyncio
9
9
  from .auth import start_callback_server, update_confirmation
10
+ import urllib.parse
10
11
 
11
12
 
12
13
  @mcp_server.tool()
@@ -77,7 +78,7 @@ async def get_adset_details(access_token: str = None, adset_id: str = None) -> s
77
78
  @mcp_server.tool()
78
79
  @meta_api_tool
79
80
  async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, Any]] = None, bid_strategy: str = None,
80
- bid_amount: int = None, status: str = None, access_token: str = None) -> str:
81
+ bid_amount: int = None, status: str = None, targeting: Dict[str, Any] = None, access_token: str = None) -> str:
81
82
  """
82
83
  Update an ad set with new settings including frequency caps.
83
84
 
@@ -88,6 +89,8 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
88
89
  bid_strategy: Bid strategy (e.g., 'LOWEST_COST_WITH_BID_CAP')
89
90
  bid_amount: Bid amount in account currency (in cents for USD)
90
91
  status: Update ad set status (ACTIVE, PAUSED, etc.)
92
+ targeting: Targeting specifications including targeting_automation
93
+ (e.g. {"targeting_automation":{"advantage_audience":1}})
91
94
  access_token: Meta API access token (optional - will use cached token if not provided)
92
95
  """
93
96
  if not adset_id:
@@ -106,6 +109,32 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
106
109
 
107
110
  if status is not None:
108
111
  changes['status'] = status
112
+
113
+ if targeting is not None:
114
+ # Get current ad set details to preserve existing targeting settings
115
+ current_details_json = await get_adset_details(adset_id=adset_id, access_token=access_token)
116
+ current_details = json.loads(current_details_json)
117
+
118
+ # Check if the current ad set has targeting information
119
+ current_targeting = current_details.get('targeting', {})
120
+
121
+ if 'targeting_automation' in targeting:
122
+ # Only update targeting_automation while preserving other targeting settings
123
+ if current_targeting:
124
+ merged_targeting = current_targeting.copy()
125
+ merged_targeting['targeting_automation'] = targeting['targeting_automation']
126
+ changes['targeting'] = merged_targeting
127
+ else:
128
+ # If there's no existing targeting, we need to create a basic one
129
+ # Meta requires at least a geo_locations setting
130
+ basic_targeting = {
131
+ 'targeting_automation': targeting['targeting_automation'],
132
+ 'geo_locations': {'countries': ['US']} # Using US as default location
133
+ }
134
+ changes['targeting'] = basic_targeting
135
+ else:
136
+ # Full targeting replacement
137
+ changes['targeting'] = targeting
109
138
 
110
139
  if not changes:
111
140
  return json.dumps({"error": "No update parameters provided"}, indent=2)
@@ -119,7 +148,8 @@ async def update_adset(adset_id: str, frequency_control_specs: List[Dict[str, An
119
148
 
120
149
  # Generate confirmation URL with properly encoded parameters
121
150
  changes_json = json.dumps(changes)
122
- confirmation_url = f"http://localhost:{port}/confirm-update?adset_id={adset_id}&token={access_token}&changes={changes_json}"
151
+ encoded_changes = urllib.parse.quote(changes_json)
152
+ confirmation_url = f"http://localhost:{port}/confirm-update?adset_id={adset_id}&token={access_token}&changes={encoded_changes}"
123
153
 
124
154
  # Reset the update confirmation
125
155
  update_confirmation.clear()
@@ -59,9 +59,9 @@ async def make_api_request(
59
59
  if not access_token:
60
60
  logger.error("API request attempted with blank access token")
61
61
  return {
62
- "error": "Authentication Required",
63
- "details": {
64
- "message": "A valid access token is required to access the Meta API",
62
+ "error": {
63
+ "message": "Authentication Required",
64
+ "details": "A valid access token is required to access the Meta API",
65
65
  "action_required": "Please authenticate first"
66
66
  }
67
67
  }
@@ -89,7 +89,18 @@ async def make_api_request(
89
89
  if method == "GET":
90
90
  response = await client.get(url, params=request_params, headers=headers, timeout=30.0)
91
91
  elif method == "POST":
92
- response = await client.post(url, json=request_params, headers=headers, timeout=30.0)
92
+ # For Meta API, POST requests need data, not JSON
93
+ if 'targeting' in request_params and isinstance(request_params['targeting'], dict):
94
+ # Convert targeting dict to string for the API
95
+ request_params['targeting'] = json.dumps(request_params['targeting'])
96
+
97
+ # Convert lists and dicts to JSON strings
98
+ for key, value in request_params.items():
99
+ if isinstance(value, (list, dict)):
100
+ request_params[key] = json.dumps(value)
101
+
102
+ logger.debug(f"POST params (prepared): {masked_params}")
103
+ response = await client.post(url, data=request_params, headers=headers, timeout=30.0)
93
104
  elif method == "DELETE":
94
105
  response = await client.delete(url, params=request_params, headers=headers, timeout=30.0)
95
106
  else:
@@ -97,7 +108,16 @@ async def make_api_request(
97
108
 
98
109
  response.raise_for_status()
99
110
  logger.debug(f"API Response status: {response.status_code}")
100
- return response.json()
111
+
112
+ # Ensure the response is JSON and return it as a dictionary
113
+ try:
114
+ return response.json()
115
+ except json.JSONDecodeError:
116
+ # If not JSON, return text content in a structured format
117
+ return {
118
+ "text_response": response.text,
119
+ "status_code": response.status_code
120
+ }
101
121
 
102
122
  except httpx.HTTPStatusError as e:
103
123
  error_info = {}
@@ -123,8 +143,7 @@ async def make_api_request(
123
143
  logger.error(f"Current app_id: {app_id}")
124
144
  # Provide a clearer error message without the confusing "Provide valid app ID" message
125
145
  return {
126
- "error": f"HTTP Error: {e.response.status_code}",
127
- "details": {
146
+ "error": {
128
147
  "message": "Meta API authentication configuration issue. Please check your app credentials.",
129
148
  "original_error": error_obj.get("message"),
130
149
  "code": error_obj.get("code")
@@ -132,11 +151,28 @@ async def make_api_request(
132
151
  }
133
152
  auth_manager.invalidate_token()
134
153
 
135
- return {"error": f"HTTP Error: {e.response.status_code}", "details": error_info}
154
+ # Include full details for technical users
155
+ full_response = {
156
+ "headers": dict(e.response.headers),
157
+ "status_code": e.response.status_code,
158
+ "url": str(e.response.url),
159
+ "reason": getattr(e.response, "reason_phrase", "Unknown reason"),
160
+ "request_method": e.request.method,
161
+ "request_url": str(e.request.url)
162
+ }
163
+
164
+ # Return a properly structured error object
165
+ return {
166
+ "error": {
167
+ "message": f"HTTP Error: {e.response.status_code}",
168
+ "details": error_info,
169
+ "full_response": full_response
170
+ }
171
+ }
136
172
 
137
173
  except Exception as e:
138
174
  logger.error(f"Request Error: {str(e)}")
139
- return {"error": str(e)}
175
+ return {"error": {"message": str(e)}}
140
176
 
141
177
 
142
178
  # Generic wrapper for all Meta API tools
@@ -174,12 +210,14 @@ def meta_api_tool(func):
174
210
  logger.warning("No access token available, authentication needed")
175
211
  auth_url = auth_manager.get_auth_url()
176
212
  return json.dumps({
177
- "error": "Authentication Required",
178
- "details": {
179
- "message": "You need to authenticate with the Meta API before using this tool",
180
- "action_required": "Please authenticate first",
181
- "auth_url": auth_url,
182
- "markdown_link": f"[Click here to authenticate with Meta Ads API]({auth_url})"
213
+ "error": {
214
+ "message": "Authentication Required",
215
+ "details": {
216
+ "description": "You need to authenticate with the Meta API before using this tool",
217
+ "action_required": "Please authenticate first",
218
+ "auth_url": auth_url,
219
+ "markdown_link": f"[Click here to authenticate with Meta Ads API]({auth_url})"
220
+ }
183
221
  }
184
222
  }, indent=2)
185
223
 
@@ -200,18 +238,24 @@ def meta_api_tool(func):
200
238
  logger.error(f"Current app_id: {app_id}")
201
239
  # Replace the confusing error with a more user-friendly one
202
240
  return json.dumps({
203
- "error": "Meta API Configuration Issue",
204
- "details": {
205
- "message": "Your Meta API app is not properly configured",
206
- "action_required": "Check your META_APP_ID environment variable",
207
- "current_app_id": app_id,
208
- "original_error": error_obj.get("message")
241
+ "error": {
242
+ "message": "Meta API Configuration Issue",
243
+ "details": {
244
+ "description": "Your Meta API app is not properly configured",
245
+ "action_required": "Check your META_APP_ID environment variable",
246
+ "current_app_id": app_id,
247
+ "original_error": error_obj.get("message")
248
+ }
209
249
  }
210
250
  }, indent=2)
211
251
  except Exception:
212
- # Not JSON or other parsing error, just continue
213
- pass
214
-
252
+ # Not JSON or other parsing error, wrap it in a dictionary
253
+ return json.dumps({"data": result}, indent=2)
254
+
255
+ # If result is already a dictionary, ensure it's properly serialized
256
+ if isinstance(result, dict):
257
+ return json.dumps(result, indent=2)
258
+
215
259
  return result
216
260
  except Exception as e:
217
261
  logger.error(f"Error in {func.__name__}: {str(e)}")