meta-ads-mcp 0.7.7__py3-none-any.whl → 0.7.8__py3-none-any.whl

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.
meta_ads_mcp/__init__.py CHANGED
@@ -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.7.7"
10
+ __version__ = "0.7.8"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -36,29 +36,58 @@ async def get_account_info(access_token: str = None, account_id: str = None) ->
36
36
 
37
37
  Args:
38
38
  access_token: Meta API access token (optional - will use cached token if not provided)
39
- account_id: Meta Ads account ID (format: act_XXXXXXXXX)
39
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX) - REQUIRED
40
40
  """
41
- # If no account ID is specified, try to get the first one for the user
42
41
  if not account_id:
43
- accounts_json = await get_ad_accounts("me", json.dumps({"limit": 1}), access_token)
44
- accounts_data = json.loads(accounts_json)
45
-
46
- if "data" in accounts_data and accounts_data["data"]:
47
- account_id = accounts_data["data"][0]["id"]
48
- else:
49
- return json.dumps({"error": "No account ID specified and no accounts found for user"}, indent=2)
42
+ return {
43
+ "error": {
44
+ "message": "Account ID is required",
45
+ "details": "Please specify an account_id parameter",
46
+ "example": "Use account_id='act_123456789' or account_id='123456789'"
47
+ }
48
+ }
50
49
 
51
50
  # Ensure account_id has the 'act_' prefix for API compatibility
52
51
  if not account_id.startswith("act_"):
53
52
  account_id = f"act_{account_id}"
54
53
 
54
+ # First, check if the account is accessible to the user
55
+ endpoint = "me/adaccounts"
56
+ params = {
57
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,business_city,business_country_code",
58
+ "limit": 50
59
+ }
60
+ accessible_accounts_data = await make_api_request(endpoint, access_token, params)
61
+
62
+ if "data" in accessible_accounts_data:
63
+ accessible_account_ids = [acc["id"] for acc in accessible_accounts_data["data"]]
64
+ if account_id not in accessible_account_ids:
65
+ # Provide a helpful error message with accessible accounts
66
+ accessible_accounts = [
67
+ {"id": acc["id"], "name": acc["name"]}
68
+ for acc in accessible_accounts_data["data"][:10] # Show first 10
69
+ ]
70
+ return {
71
+ "error": {
72
+ "message": f"Account {account_id} is not accessible to your user account",
73
+ "details": "This account either doesn't exist or you don't have permission to access it",
74
+ "accessible_accounts": accessible_accounts,
75
+ "total_accessible_accounts": len(accessible_accounts_data["data"]),
76
+ "suggestion": "Try using one of the accessible account IDs listed above"
77
+ }
78
+ }
79
+
55
80
  endpoint = f"{account_id}"
56
81
  params = {
57
- "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,funding_source_details,business_city,business_country_code,timezone_name,owner"
82
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,business_city,business_country_code,timezone_name"
58
83
  }
59
84
 
60
85
  data = await make_api_request(endpoint, access_token, params)
61
86
 
87
+ # Check if the API request returned an error
88
+ if "error" in data:
89
+ return data
90
+
62
91
  # Add DSA requirement detection
63
92
  if "business_country_code" in data:
64
93
  european_countries = ["DE", "FR", "IT", "ES", "NL", "BE", "AT", "IE", "DK", "SE", "FI", "NO"]
@@ -69,4 +98,4 @@ async def get_account_info(access_token: str = None, account_id: str = None) ->
69
98
  data["dsa_required"] = False
70
99
  data["dsa_compliance_note"] = "This account is not subject to European DSA requirements"
71
100
 
72
- return json.dumps(data, indent=2)
101
+ return data
meta_ads_mcp/core/auth.py CHANGED
@@ -25,7 +25,7 @@ from .pipeboard_auth import pipeboard_auth_manager
25
25
  # Auth constants
26
26
  # Scope includes pages_show_list and pages_read_engagement to fix issue #16
27
27
  # where get_account_pages failed for regular users due to missing page permissions
28
- AUTH_SCOPE = "ads_management,ads_read,business_management,public_profile,pages_show_list,pages_read_engagement"
28
+ AUTH_SCOPE = "business_management,public_profile,pages_show_list,pages_read_engagement"
29
29
  AUTH_REDIRECT_URI = "http://localhost:8888/callback"
30
30
  AUTH_RESPONSE_TYPE = "token"
31
31
 
@@ -159,11 +159,41 @@ class AuthManager:
159
159
  try:
160
160
  with open(cache_path, "r") as f:
161
161
  data = json.load(f)
162
+
163
+ # Validate the cached data structure
164
+ required_fields = ["access_token", "created_at"]
165
+ if not all(field in data for field in required_fields):
166
+ logger.warning("Cached token data is missing required fields")
167
+ return False
168
+
169
+ # Check if the token looks valid (basic format check)
170
+ if not data.get("access_token") or len(data["access_token"]) < 20:
171
+ logger.warning("Cached token appears malformed")
172
+ return False
173
+
162
174
  self.token_info = TokenInfo.deserialize(data)
163
175
 
164
176
  # Check if token is expired
165
177
  if self.token_info.is_expired():
166
- logger.info("Cached token is expired")
178
+ logger.info("Cached token is expired, removing cache file")
179
+ # Remove the expired cache file
180
+ try:
181
+ cache_path.unlink()
182
+ logger.info(f"Removed expired token cache: {cache_path}")
183
+ except Exception as e:
184
+ logger.warning(f"Could not remove expired cache file: {e}")
185
+ self.token_info = None
186
+ return False
187
+
188
+ # Additional validation: check if token is too old (more than 60 days)
189
+ current_time = int(time.time())
190
+ if self.token_info.created_at and (current_time - self.token_info.created_at) > (60 * 24 * 3600):
191
+ logger.warning("Cached token is too old (more than 60 days), removing cache file")
192
+ try:
193
+ cache_path.unlink()
194
+ logger.info(f"Removed old token cache: {cache_path}")
195
+ except Exception as e:
196
+ logger.warning(f"Could not remove old cache file: {e}")
167
197
  self.token_info = None
168
198
  return False
169
199
 
@@ -171,6 +201,12 @@ class AuthManager:
171
201
  return True
172
202
  except Exception as e:
173
203
  logger.error(f"Error loading cached token: {e}")
204
+ # If there's any error reading the cache, try to remove the corrupted file
205
+ try:
206
+ cache_path.unlink()
207
+ logger.info(f"Removed corrupted token cache: {cache_path}")
208
+ except Exception as cleanup_error:
209
+ logger.warning(f"Could not remove corrupted cache file: {cleanup_error}")
174
210
  return False
175
211
 
176
212
  def _save_token_to_cache(self) -> None:
@@ -151,6 +151,18 @@ class PipeboardAuthManager:
151
151
  with open(cache_path, "r") as f:
152
152
  logger.debug(f"Reading token cache from {cache_path}")
153
153
  data = json.load(f)
154
+
155
+ # Validate the cached data structure
156
+ required_fields = ["access_token"]
157
+ if not all(field in data for field in required_fields):
158
+ logger.warning("Cached token data is missing required fields")
159
+ return False
160
+
161
+ # Check if the token looks valid (basic format check)
162
+ if not data.get("access_token") or len(data["access_token"]) < 20:
163
+ logger.warning("Cached token appears malformed")
164
+ return False
165
+
154
166
  self.token_info = TokenInfo.deserialize(data)
155
167
 
156
168
  # Log token details (partial token for security)
@@ -159,7 +171,25 @@ class PipeboardAuthManager:
159
171
 
160
172
  # Check if token is expired
161
173
  if self.token_info.is_expired():
162
- logger.info("Cached token is expired")
174
+ logger.info("Cached token is expired, removing cache file")
175
+ # Remove the expired cache file
176
+ try:
177
+ cache_path.unlink()
178
+ logger.info(f"Removed expired token cache: {cache_path}")
179
+ except Exception as e:
180
+ logger.warning(f"Could not remove expired cache file: {e}")
181
+ self.token_info = None
182
+ return False
183
+
184
+ # Additional validation: check if token is too old (more than 60 days)
185
+ current_time = int(time.time())
186
+ if self.token_info.created_at and (current_time - self.token_info.created_at) > (60 * 24 * 3600):
187
+ logger.warning("Cached token is too old (more than 60 days), removing cache file")
188
+ try:
189
+ cache_path.unlink()
190
+ logger.info(f"Removed old token cache: {cache_path}")
191
+ except Exception as e:
192
+ logger.warning(f"Could not remove old cache file: {e}")
163
193
  self.token_info = None
164
194
  return False
165
195
 
@@ -174,9 +204,21 @@ class PipeboardAuthManager:
174
204
  logger.debug(f"Raw cache file content (first 100 chars): {raw_content[:100]}")
175
205
  except Exception as e2:
176
206
  logger.error(f"Could not read raw cache file: {e2}")
207
+ # If there's any error reading the cache, try to remove the corrupted file
208
+ try:
209
+ cache_path.unlink()
210
+ logger.info(f"Removed corrupted token cache: {cache_path}")
211
+ except Exception as cleanup_error:
212
+ logger.warning(f"Could not remove corrupted cache file: {cleanup_error}")
177
213
  return False
178
214
  except Exception as e:
179
215
  logger.error(f"Error loading cached token: {e}")
216
+ # If there's any error reading the cache, try to remove the corrupted file
217
+ try:
218
+ cache_path.unlink()
219
+ logger.info(f"Removed corrupted token cache: {cache_path}")
220
+ except Exception as cleanup_error:
221
+ logger.warning(f"Could not remove corrupted cache file: {cleanup_error}")
180
222
  return False
181
223
 
182
224
  def _save_token_to_cache(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.7.7
3
+ Version: 0.7.8
4
4
  Summary: Model Context Protocol (MCP) plugin for interacting with Meta Ads API
5
5
  Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
6
6
  Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
@@ -1,12 +1,12 @@
1
- meta_ads_mcp/__init__.py,sha256=QqUl7E3XnhXF-mwxUWeKy6BW2_Ta1NreFhlvF2MK_BI,1492
1
+ meta_ads_mcp/__init__.py,sha256=BiSbN8PrAeLKejVnkAfsz06KLVM59vQJmehwssDnEzc,1492
2
2
  meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
3
  meta_ads_mcp/core/__init__.py,sha256=6nYdue6yRepkt6JTAoPGhGbS51qfDSvmczRrDwYOG6A,1709
4
- meta_ads_mcp/core/accounts.py,sha256=h7opM9SO_2iHZkyZn4O5xWTq9Z78Dy9PNcObGkbPOZc,2840
4
+ meta_ads_mcp/core/accounts.py,sha256=4IAdGLZ4WE4j4pGW6E0qaXcXqbUIW6Wk2kuQUtlmRTQ,4030
5
5
  meta_ads_mcp/core/ads.py,sha256=aaK70mgfhBJRXr4cdkKag5mjYzvHuHpRttJvTMzPk4Y,36156
6
6
  meta_ads_mcp/core/ads_library.py,sha256=BBGVbtjO5eFV42iiY3XPU-wIV8HupzUKpHgPBrydSvU,3232
7
7
  meta_ads_mcp/core/adsets.py,sha256=vY5JNHmGK1a_sQ5B1LnjxLYXzs5_jOajTTjWHRDJ4_Y,12518
8
8
  meta_ads_mcp/core/api.py,sha256=aAzM6Q75VQOFXtr5D-mDmBRhxWK4wsiODsJYnR3mpDI,14994
9
- meta_ads_mcp/core/auth.py,sha256=H-0s0O2fLo14rmi81Hh5S64pyRl1HS7dm9Q_8UCa3Jg,21622
9
+ meta_ads_mcp/core/auth.py,sha256=2CjFbxpJM3OR3OzCipB8l_-l2xQ1nioGfdI3ZDMnjHM,23629
10
10
  meta_ads_mcp/core/authentication.py,sha256=-AJxa3a5ZshRCvmJThBaNwCAJ1D2_qOgUkvu539c_MY,10159
11
11
  meta_ads_mcp/core/budget_schedules.py,sha256=UxseExsvKAiPwfDCY9aycT4kys4xqeNytyq-yyDOxrs,2901
12
12
  meta_ads_mcp/core/callback_server.py,sha256=LIAJv9DW--83kdZ7VWWZal8xEprYjRZ8iug4rMczYbQ,9372
@@ -15,14 +15,14 @@ meta_ads_mcp/core/duplication.py,sha256=UUmTDFx9o5ZsPQG2Rb9c4ZyuKUVN3FfTjebfTIHH
15
15
  meta_ads_mcp/core/http_auth_integration.py,sha256=lGpKhfzJcyWugBcYEvypY-qnlt-3UDBLqh7xAUH0DGw,12473
16
16
  meta_ads_mcp/core/insights.py,sha256=Qr1wq-1VT9HwF4w11rIRM4IBYdrksJ-6EOv3p33ZtKw,2613
17
17
  meta_ads_mcp/core/openai_deep_research.py,sha256=Ocs8bmNNBLZQLmWfL6azlC3RNzevVzV5WgcEp4H2wdY,13240
18
- meta_ads_mcp/core/pipeboard_auth.py,sha256=VvbxEB8ZOhnMccLU7HI1HgaPWHCl5NGrzZCm-zzHze4,22798
18
+ meta_ads_mcp/core/pipeboard_auth.py,sha256=yT2-e9dpmkMOg6rMJWaQAMg4DZu4RxlDV5zkTm-G--o,25168
19
19
  meta_ads_mcp/core/reports.py,sha256=Dv3hfsPOR7IZ9WrYrKd_6SNgZl-USIphg7knva3UYAw,5747
20
20
  meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0w,1236
21
21
  meta_ads_mcp/core/server.py,sha256=WhbAag7xdhbGcp7rnU4sKhqXJ8Slapa_ba3T23Yp_2U,17889
22
22
  meta_ads_mcp/core/targeting.py,sha256=3HW1qirEdwaQurlBZGenbIwawcb5J06ghJKRfgu9ZEs,6318
23
23
  meta_ads_mcp/core/utils.py,sha256=ofKUhyo-5SZoJVuBeTVFPPQCffk0UKpwmDMrd8qQxNc,8715
24
- meta_ads_mcp-0.7.7.dist-info/METADATA,sha256=2Ao4wr-v_4w6Ttc4mrJxE4HKDzlwbq_Ygjc_ovZB8eg,20409
25
- meta_ads_mcp-0.7.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- meta_ads_mcp-0.7.7.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
- meta_ads_mcp-0.7.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
28
- meta_ads_mcp-0.7.7.dist-info/RECORD,,
24
+ meta_ads_mcp-0.7.8.dist-info/METADATA,sha256=HjCu4iRgkgfEv5WYS3-E6CchfrkassZHZjMVbgfOphE,20409
25
+ meta_ads_mcp-0.7.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ meta_ads_mcp-0.7.8.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
+ meta_ads_mcp-0.7.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
28
+ meta_ads_mcp-0.7.8.dist-info/RECORD,,