meta-ads-mcp-python 1.0.79__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.
@@ -0,0 +1,79 @@
1
+ """
2
+ Meta Ads MCP - Python Package
3
+
4
+ This package provides a Meta Ads MCP integration
5
+ """
6
+
7
+ from meta_ads_mcp.core.server import main
8
+
9
+ __version__ = "1.0.79"
10
+
11
+ __all__ = [
12
+ 'get_ad_accounts',
13
+ 'get_account_info',
14
+ 'get_campaigns',
15
+ 'get_campaign_details',
16
+ 'create_campaign',
17
+ 'get_adsets',
18
+ 'get_adset_details',
19
+ 'update_adset',
20
+ 'get_ads',
21
+ 'get_ad_details',
22
+ 'get_ad_creatives',
23
+ 'get_ad_image',
24
+ 'update_ad',
25
+ 'get_insights',
26
+ # 'get_login_link' is conditionally exported via core.__all__
27
+ 'login_cli',
28
+ 'main',
29
+ 'search_interests',
30
+ 'get_interest_suggestions',
31
+ 'estimate_audience_size',
32
+ 'search_behaviors',
33
+ 'search_demographics',
34
+ 'search_geo_locations',
35
+ # MCC / Business Manager tools
36
+ 'get_businesses',
37
+ 'get_business_ad_accounts',
38
+ 'find_client_account',
39
+ 'get_business_details',
40
+ ]
41
+
42
+ # Import key functions to make them available at package level
43
+ from .core import (
44
+ get_ad_accounts,
45
+ get_account_info,
46
+ get_campaigns,
47
+ get_campaign_details,
48
+ create_campaign,
49
+ get_adsets,
50
+ get_adset_details,
51
+ update_adset,
52
+ get_ads,
53
+ get_ad_details,
54
+ get_ad_creatives,
55
+ get_ad_image,
56
+ update_ad,
57
+ get_insights,
58
+ login_cli,
59
+ main,
60
+ search_interests,
61
+ get_interest_suggestions,
62
+ estimate_audience_size,
63
+ search_behaviors,
64
+ search_demographics,
65
+ search_geo_locations,
66
+ # MCC / Business Manager tools
67
+ get_businesses,
68
+ get_business_ad_accounts,
69
+ find_client_account,
70
+ get_business_details,
71
+ )
72
+
73
+ # Define a main function to be used as a package entry point
74
+ def entrypoint():
75
+ """Main entry point for the package when invoked with uvx."""
76
+ return main()
77
+
78
+ # Re-export main for direct access
79
+ main = main
@@ -0,0 +1,10 @@
1
+ """
2
+ Meta Ads MCP - Main Entry Point
3
+
4
+ This module allows the package to be executed directly via `python -m meta_ads_mcp`
5
+ """
6
+
7
+ from meta_ads_mcp.core.server import main
8
+
9
+ if __name__ == "__main__":
10
+ main()
@@ -0,0 +1,55 @@
1
+ """Core functionality for Meta Ads API MCP package."""
2
+
3
+ from .server import mcp_server
4
+ from .accounts import get_ad_accounts, get_account_info
5
+ from .campaigns import get_campaigns, get_campaign_details
6
+ from .adsets import get_adsets, get_adset_details
7
+ from .ads import get_ads, get_ad_details, get_creative_details, get_ad_creatives, get_ad_image
8
+ from .insights import get_insights
9
+ from . import authentication # Import module to register conditional auth tools
10
+ from .server import login_cli, main
11
+ from .auth import login
12
+ from . import ads_library # Import module to register conditional tools
13
+ from .targeting import search_interests, get_interest_suggestions, estimate_audience_size, search_behaviors, search_demographics, search_geo_locations
14
+ from . import reports # Import module to register conditional tools
15
+ from . import duplication # Import module to register conditional duplication tools
16
+ from .openai_deep_research import search, fetch # OpenAI MCP Deep Research tools
17
+ from .mcc import ( # Business Manager (MCC) tools
18
+ get_businesses,
19
+ get_business_ad_accounts,
20
+ find_client_account,
21
+ get_business_details,
22
+ )
23
+
24
+ __all__ = [
25
+ 'mcp_server',
26
+ 'get_ad_accounts',
27
+ 'get_account_info',
28
+ 'get_campaigns',
29
+ 'get_campaign_details',
30
+ 'get_adsets',
31
+ 'get_adset_details',
32
+ 'get_ads',
33
+ 'get_ad_details',
34
+ 'get_creative_details',
35
+ 'get_ad_creatives',
36
+ 'get_ad_image',
37
+ 'get_insights',
38
+ # Note: 'get_login_link' is registered conditionally by the authentication module
39
+ 'login_cli',
40
+ 'login',
41
+ 'main',
42
+ 'search_interests',
43
+ 'get_interest_suggestions',
44
+ 'estimate_audience_size',
45
+ 'search_behaviors',
46
+ 'search_demographics',
47
+ 'search_geo_locations',
48
+ 'search', # OpenAI MCP Deep Research search tool
49
+ 'fetch', # OpenAI MCP Deep Research fetch tool
50
+ # MCC / Business Manager tools
51
+ 'get_businesses',
52
+ 'get_business_ad_accounts',
53
+ 'find_client_account',
54
+ 'get_business_details',
55
+ ]
@@ -0,0 +1,141 @@
1
+ """Account-related functionality for Meta Ads API."""
2
+
3
+ import json
4
+ from typing import Optional, Dict, Any
5
+ from .api import meta_api_tool, make_api_request, ensure_act_prefix
6
+ from .server import mcp_server
7
+
8
+ # Currencies that have no sub-units (i.e., are not denominated in cents).
9
+ # Meta API returns amount_spent and balance as integers in the smallest currency
10
+ # unit, which is cents for most currencies but the base unit for these.
11
+ _ZERO_DECIMAL_CURRENCIES = {
12
+ "BIF", "CLP", "DJF", "GNF", "JPY", "KMF", "KRW", "MGA",
13
+ "PYG", "RWF", "UGX", "VND", "VUV", "XAF", "XOF", "XPF",
14
+ }
15
+
16
+
17
+ def _cents_to_currency(amount, currency: str) -> str:
18
+ """Convert a Meta API monetary value (cents) to a currency-unit string.
19
+
20
+ Meta returns amount_spent and balance as integers representing the smallest
21
+ currency unit (cents for USD/EUR/GBP, base unit for zero-decimal currencies
22
+ like JPY). This converts to the human-readable decimal amount.
23
+ """
24
+ try:
25
+ amount_int = int(amount)
26
+ except (TypeError, ValueError):
27
+ return str(amount)
28
+ if currency.upper() in _ZERO_DECIMAL_CURRENCIES:
29
+ return str(amount_int)
30
+ return f"{amount_int / 100:.2f}"
31
+
32
+
33
+ def _normalize_account_monetary_fields(account: dict) -> dict:
34
+ """Convert amount_spent and balance from cents to currency units in-place."""
35
+ currency = account.get("currency", "USD")
36
+ for field in ("amount_spent", "balance"):
37
+ if field in account:
38
+ account[field] = _cents_to_currency(account[field], currency)
39
+ return account
40
+
41
+
42
+ @mcp_server.tool()
43
+ @meta_api_tool
44
+ async def get_ad_accounts(access_token: Optional[str] = None, user_id: str = "me", limit: int = 200) -> str:
45
+ """
46
+ Get ad accounts accessible by a user.
47
+
48
+ amount_spent and balance are returned in currency units (e.g. USD dollars),
49
+ not cents.
50
+
51
+ Args:
52
+ access_token: Meta API access token (optional - will use cached token if not provided)
53
+ user_id: Meta user ID or "me" for the current user
54
+ limit: Maximum number of accounts to return (default: 200)
55
+ """
56
+ endpoint = f"{user_id}/adaccounts"
57
+ params = {
58
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,business_city,business_country_code",
59
+ "limit": limit
60
+ }
61
+
62
+ data = await make_api_request(endpoint, access_token, params)
63
+
64
+ if "data" in data:
65
+ data["data"] = [_normalize_account_monetary_fields(acc) for acc in data["data"]]
66
+
67
+ return json.dumps(data, indent=2)
68
+
69
+
70
+ @mcp_server.tool()
71
+ @meta_api_tool
72
+ async def get_account_info(account_id: str, access_token: Optional[str] = None) -> str:
73
+ """
74
+ Get detailed information about a specific ad account.
75
+
76
+ Args:
77
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX)
78
+ access_token: Meta API access token (optional - will use cached token if not provided)
79
+ """
80
+ if not account_id:
81
+ return {
82
+ "error": {
83
+ "message": "Account ID is required",
84
+ "details": "Please specify an account_id parameter",
85
+ "example": "Use account_id='act_123456789' or account_id='123456789'"
86
+ }
87
+ }
88
+
89
+ account_id = ensure_act_prefix(account_id)
90
+
91
+ # Try to get the account info directly first
92
+ endpoint = f"{account_id}"
93
+ params = {
94
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,business_city,business_country_code,timezone_name"
95
+ }
96
+
97
+ data = await make_api_request(endpoint, access_token, params)
98
+
99
+ # Check if the API request returned an error
100
+ if "error" in data:
101
+ # If access was denied, provide helpful error message with accessible accounts
102
+ if "access" in str(data.get("error", {})).lower() or "permission" in str(data.get("error", {})).lower():
103
+ # Get list of accessible accounts for helpful error message
104
+ accessible_endpoint = "me/adaccounts"
105
+ accessible_params = {
106
+ "fields": "id,name,account_id,account_status,amount_spent,balance,currency,age,business_city,business_country_code",
107
+ "limit": 50
108
+ }
109
+ accessible_accounts_data = await make_api_request(accessible_endpoint, access_token, accessible_params)
110
+
111
+ if "data" in accessible_accounts_data:
112
+ accessible_accounts = [
113
+ {"id": acc["id"], "name": acc["name"]}
114
+ for acc in accessible_accounts_data["data"][:10] # Show first 10
115
+ ]
116
+ return {
117
+ "error": {
118
+ "message": f"Account {account_id} is not accessible to your user account",
119
+ "details": "This account either doesn't exist or you don't have permission to access it",
120
+ "accessible_accounts": accessible_accounts,
121
+ "total_accessible_accounts": len(accessible_accounts_data["data"]),
122
+ "suggestion": "Try using one of the accessible account IDs listed above"
123
+ }
124
+ }
125
+
126
+ # Return the original error for non-permission related issues
127
+ return data
128
+
129
+ _normalize_account_monetary_fields(data)
130
+
131
+ # Add DSA requirement detection
132
+ if "business_country_code" in data:
133
+ european_countries = ["DE", "FR", "IT", "ES", "NL", "BE", "AT", "IE", "DK", "SE", "FI", "NO"]
134
+ if data["business_country_code"] in european_countries:
135
+ data["dsa_required"] = True
136
+ data["dsa_compliance_note"] = "This account is subject to European DSA (Digital Services Act) requirements"
137
+ else:
138
+ data["dsa_required"] = False
139
+ data["dsa_compliance_note"] = "This account is not subject to European DSA requirements"
140
+
141
+ return data