meta-ads-mcp 0.10.2__py3-none-any.whl → 0.10.6__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 +2 -3
- meta_ads_mcp/core/__init__.py +2 -2
- meta_ads_mcp/core/ads.py +77 -26
- meta_ads_mcp/core/authentication.py +9 -2
- {meta_ads_mcp-0.10.2.dist-info → meta_ads_mcp-0.10.6.dist-info}/METADATA +2 -2
- {meta_ads_mcp-0.10.2.dist-info → meta_ads_mcp-0.10.6.dist-info}/RECORD +9 -9
- {meta_ads_mcp-0.10.2.dist-info → meta_ads_mcp-0.10.6.dist-info}/WHEEL +0 -0
- {meta_ads_mcp-0.10.2.dist-info → meta_ads_mcp-0.10.6.dist-info}/entry_points.txt +0 -0
- {meta_ads_mcp-0.10.2.dist-info → meta_ads_mcp-0.10.6.dist-info}/licenses/LICENSE +0 -0
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.10.
|
|
10
|
+
__version__ = "0.10.6"
|
|
11
11
|
|
|
12
12
|
__all__ = [
|
|
13
13
|
'get_ad_accounts',
|
|
@@ -24,7 +24,7 @@ __all__ = [
|
|
|
24
24
|
'get_ad_image',
|
|
25
25
|
'update_ad',
|
|
26
26
|
'get_insights',
|
|
27
|
-
'get_login_link'
|
|
27
|
+
# 'get_login_link' is conditionally exported via core.__all__
|
|
28
28
|
'login_cli',
|
|
29
29
|
'main',
|
|
30
30
|
'search_interests',
|
|
@@ -51,7 +51,6 @@ from .core import (
|
|
|
51
51
|
get_ad_image,
|
|
52
52
|
update_ad,
|
|
53
53
|
get_insights,
|
|
54
|
-
get_login_link,
|
|
55
54
|
login_cli,
|
|
56
55
|
main,
|
|
57
56
|
search_interests,
|
meta_ads_mcp/core/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ from .campaigns import get_campaigns, get_campaign_details, create_campaign
|
|
|
6
6
|
from .adsets import get_adsets, get_adset_details, update_adset
|
|
7
7
|
from .ads import get_ads, get_ad_details, get_ad_creatives, get_ad_image, update_ad
|
|
8
8
|
from .insights import get_insights
|
|
9
|
-
from .
|
|
9
|
+
from . import authentication # Import module to register conditional auth tools
|
|
10
10
|
from .server import login_cli, main
|
|
11
11
|
from .auth import login
|
|
12
12
|
from . import ads_library # Import module to register conditional tools
|
|
@@ -32,7 +32,7 @@ __all__ = [
|
|
|
32
32
|
'get_ad_image',
|
|
33
33
|
'update_ad',
|
|
34
34
|
'get_insights',
|
|
35
|
-
'get_login_link'
|
|
35
|
+
# Note: 'get_login_link' is registered conditionally by the authentication module
|
|
36
36
|
'login_cli',
|
|
37
37
|
'login',
|
|
38
38
|
'main',
|
meta_ads_mcp/core/ads.py
CHANGED
|
@@ -564,7 +564,8 @@ async def update_ad(
|
|
|
564
564
|
async def upload_ad_image(
|
|
565
565
|
access_token: str = None,
|
|
566
566
|
account_id: str = None,
|
|
567
|
-
|
|
567
|
+
file: str = None,
|
|
568
|
+
image_url: str = None,
|
|
568
569
|
name: str = None
|
|
569
570
|
) -> str:
|
|
570
571
|
"""
|
|
@@ -573,7 +574,8 @@ async def upload_ad_image(
|
|
|
573
574
|
Args:
|
|
574
575
|
access_token: Meta API access token (optional - will use cached token if not provided)
|
|
575
576
|
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
|
|
576
|
-
|
|
577
|
+
file: Data URL or raw base64 string of the image (e.g., "data:image/png;base64,iVBORw0KG...")
|
|
578
|
+
image_url: Direct URL to an image to fetch and upload
|
|
577
579
|
name: Optional name for the image (default: filename)
|
|
578
580
|
|
|
579
581
|
Returns:
|
|
@@ -583,45 +585,94 @@ async def upload_ad_image(
|
|
|
583
585
|
if not account_id:
|
|
584
586
|
return json.dumps({"error": "No account ID provided"}, indent=2)
|
|
585
587
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
+
# Ensure we have image data
|
|
589
|
+
if not file and not image_url:
|
|
590
|
+
return json.dumps({"error": "Provide either 'file' (data URL or base64) or 'image_url'"}, indent=2)
|
|
588
591
|
|
|
589
592
|
# Ensure account_id has the 'act_' prefix for API compatibility
|
|
590
593
|
if not account_id.startswith("act_"):
|
|
591
594
|
account_id = f"act_{account_id}"
|
|
592
595
|
|
|
593
|
-
# Check if image file exists
|
|
594
|
-
if not os.path.exists(image_path):
|
|
595
|
-
return json.dumps({"error": f"Image file not found: {image_path}"}, indent=2)
|
|
596
|
-
|
|
597
596
|
try:
|
|
598
|
-
#
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
597
|
+
# Determine encoded_image (base64 string without data URL prefix) and a sensible name
|
|
598
|
+
encoded_image: str = ""
|
|
599
|
+
inferred_name: str = name or ""
|
|
600
|
+
|
|
601
|
+
if file:
|
|
602
|
+
# Support data URL (e.g., data:image/png;base64,...) and raw base64
|
|
603
|
+
data_url_prefix = "data:"
|
|
604
|
+
base64_marker = "base64,"
|
|
605
|
+
if file.startswith(data_url_prefix) and base64_marker in file:
|
|
606
|
+
header, base64_payload = file.split(base64_marker, 1)
|
|
607
|
+
encoded_image = base64_payload.strip()
|
|
608
|
+
|
|
609
|
+
# Infer file extension from MIME type if name not provided
|
|
610
|
+
if not inferred_name:
|
|
611
|
+
# Example header: data:image/png;...
|
|
612
|
+
mime_type = header[len(data_url_prefix):].split(";")[0].strip()
|
|
613
|
+
extension_map = {
|
|
614
|
+
"image/png": ".png",
|
|
615
|
+
"image/jpeg": ".jpg",
|
|
616
|
+
"image/jpg": ".jpg",
|
|
617
|
+
"image/webp": ".webp",
|
|
618
|
+
"image/gif": ".gif",
|
|
619
|
+
"image/bmp": ".bmp",
|
|
620
|
+
"image/tiff": ".tiff",
|
|
621
|
+
}
|
|
622
|
+
ext = extension_map.get(mime_type, ".png")
|
|
623
|
+
inferred_name = f"upload{ext}"
|
|
624
|
+
else:
|
|
625
|
+
# Assume it's already raw base64
|
|
626
|
+
encoded_image = file.strip()
|
|
627
|
+
if not inferred_name:
|
|
628
|
+
inferred_name = "upload.png"
|
|
629
|
+
else:
|
|
630
|
+
# Download image from URL
|
|
631
|
+
try:
|
|
632
|
+
image_bytes = await try_multiple_download_methods(image_url)
|
|
633
|
+
except Exception as download_error:
|
|
634
|
+
return json.dumps({
|
|
635
|
+
"error": "Failed to download image from URL",
|
|
636
|
+
"details": str(download_error),
|
|
637
|
+
"image_url": image_url,
|
|
638
|
+
}, indent=2)
|
|
639
|
+
|
|
640
|
+
if not image_bytes:
|
|
641
|
+
return json.dumps({
|
|
642
|
+
"error": "No data returned when downloading image from URL",
|
|
643
|
+
"image_url": image_url,
|
|
644
|
+
}, indent=2)
|
|
645
|
+
|
|
646
|
+
import base64 # Local import
|
|
647
|
+
encoded_image = base64.b64encode(image_bytes).decode("utf-8")
|
|
648
|
+
|
|
649
|
+
# Infer name from URL if not provided
|
|
650
|
+
if not inferred_name:
|
|
651
|
+
try:
|
|
652
|
+
path_no_query = image_url.split("?")[0]
|
|
653
|
+
filename_from_url = os.path.basename(path_no_query)
|
|
654
|
+
inferred_name = filename_from_url if filename_from_url else "upload.jpg"
|
|
655
|
+
except Exception:
|
|
656
|
+
inferred_name = "upload.jpg"
|
|
657
|
+
|
|
658
|
+
# Final name resolution
|
|
659
|
+
final_name = name or inferred_name or "upload.png"
|
|
660
|
+
|
|
606
661
|
# Prepare the API endpoint for uploading images
|
|
607
662
|
endpoint = f"{account_id}/adimages"
|
|
608
|
-
|
|
609
|
-
#
|
|
610
|
-
import base64
|
|
611
|
-
encoded_image = base64.b64encode(image_bytes).decode('utf-8')
|
|
612
|
-
|
|
613
|
-
# Prepare POST parameters
|
|
663
|
+
|
|
664
|
+
# Prepare POST parameters expected by Meta API
|
|
614
665
|
params = {
|
|
615
666
|
"bytes": encoded_image,
|
|
616
|
-
"name":
|
|
667
|
+
"name": final_name,
|
|
617
668
|
}
|
|
618
|
-
|
|
669
|
+
|
|
619
670
|
# Make API request to upload the image
|
|
620
671
|
print(f"Uploading image to Facebook Ad Account {account_id}")
|
|
621
672
|
data = await make_api_request(endpoint, access_token, params, method="POST")
|
|
622
|
-
|
|
673
|
+
|
|
623
674
|
return json.dumps(data, indent=2)
|
|
624
|
-
|
|
675
|
+
|
|
625
676
|
except Exception as e:
|
|
626
677
|
return json.dumps({
|
|
627
678
|
"error": "Failed to upload image",
|
|
@@ -21,6 +21,7 @@ Environment Variables:
|
|
|
21
21
|
- PIPEBOARD_API_TOKEN: Enables mode 2 (token-based auth)
|
|
22
22
|
- META_ADS_DISABLE_CALLBACK_SERVER: Disables local server, enables mode 3
|
|
23
23
|
- META_ACCESS_TOKEN: Direct Meta token (fallback)
|
|
24
|
+
- META_ADS_DISABLE_LOGIN_LINK: Hard-disables the get_login_link tool; returns a disabled message
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
27
|
import json
|
|
@@ -32,8 +33,10 @@ from .server import mcp_server
|
|
|
32
33
|
from .utils import logger, META_APP_SECRET
|
|
33
34
|
from .pipeboard_auth import pipeboard_auth_manager
|
|
34
35
|
|
|
36
|
+
# Only register the login link tool if not explicitly disabled
|
|
37
|
+
ENABLE_LOGIN_LINK = not bool(os.environ.get("META_ADS_DISABLE_LOGIN_LINK", ""))
|
|
38
|
+
|
|
35
39
|
|
|
36
|
-
@mcp_server.tool()
|
|
37
40
|
async def get_login_link(access_token: str = None) -> str:
|
|
38
41
|
"""
|
|
39
42
|
Get a clickable login link for Meta Ads authentication.
|
|
@@ -195,4 +198,8 @@ async def get_login_link(access_token: str = None) -> str:
|
|
|
195
198
|
# Wait a moment to ensure the server is fully started
|
|
196
199
|
await asyncio.sleep(1)
|
|
197
200
|
|
|
198
|
-
|
|
201
|
+
return json.dumps(response, indent=2)
|
|
202
|
+
|
|
203
|
+
# Conditionally register as MCP tool only when enabled
|
|
204
|
+
if ENABLE_LOGIN_LINK:
|
|
205
|
+
get_login_link = mcp_server.tool()(get_login_link)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meta-ads-mcp
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.6
|
|
4
4
|
Summary: Model Context Protocol (MCP) server 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
|
|
@@ -33,7 +33,7 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for in
|
|
|
33
33
|
|
|
34
34
|
## Community & Support
|
|
35
35
|
|
|
36
|
-
- [Discord](https://discord.gg/
|
|
36
|
+
- [Discord](https://discord.gg/YNjF7gUb5h). Join the community.
|
|
37
37
|
- [Email Support](info@pipeboard.co). Email us for support.
|
|
38
38
|
|
|
39
39
|
## Table of Contents
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
meta_ads_mcp/__init__.py,sha256=
|
|
1
|
+
meta_ads_mcp/__init__.py,sha256=8og8MsMkiqUF5xVV3Xh0Oo33QyX-hvW82NXkSDOePwk,1525
|
|
2
2
|
meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
|
|
3
|
-
meta_ads_mcp/core/__init__.py,sha256=
|
|
3
|
+
meta_ads_mcp/core/__init__.py,sha256=IEJtqpyUo0CZSUWeQPljQ-D2vKorTFwXnpBQWSi1hIM,1819
|
|
4
4
|
meta_ads_mcp/core/accounts.py,sha256=0lfyvRbhBigFKgNq5Mk3fk4Qh6MtIkKScdOnoc_rQkg,4314
|
|
5
|
-
meta_ads_mcp/core/ads.py,sha256=
|
|
5
|
+
meta_ads_mcp/core/ads.py,sha256=i7smFJSOm2DtQts1vXLt5y6q6uZE9paoKx58W4zToFo,57308
|
|
6
6
|
meta_ads_mcp/core/ads_library.py,sha256=BBGVbtjO5eFV42iiY3XPU-wIV8HupzUKpHgPBrydSvU,3232
|
|
7
7
|
meta_ads_mcp/core/adsets.py,sha256=kfggH1F2KD19iPDErE0QdKw2aVX7F409EPLDTM7MKnQ,16024
|
|
8
8
|
meta_ads_mcp/core/api.py,sha256=kWpIafvSsxnesfb5TqndA7ozKoIspby5e_6Jl23L7hY,16447
|
|
9
9
|
meta_ads_mcp/core/auth.py,sha256=2CjFbxpJM3OR3OzCipB8l_-l2xQ1nioGfdI3ZDMnjHM,23629
|
|
10
|
-
meta_ads_mcp/core/authentication.py,sha256
|
|
10
|
+
meta_ads_mcp/core/authentication.py,sha256=o5YMW0xmoTDiXDmpgVPo4mYSxCil6aGMQHbIeHxj_kg,10509
|
|
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
|
|
13
13
|
meta_ads_mcp/core/campaigns.py,sha256=0yDVgi7rN4eMQk1_w0A2vnoXd8y0t8R77Ji4gna1Gj4,14030
|
|
@@ -21,8 +21,8 @@ meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0
|
|
|
21
21
|
meta_ads_mcp/core/server.py,sha256=9SlgM_qvdlxo24ctnZzLgW1e1nfAspCSx3YyJQkKP64,17856
|
|
22
22
|
meta_ads_mcp/core/targeting.py,sha256=nl_JVXNhCu85ho5ssklZiQYC3iL_oosdlpEuyCOVXcM,13824
|
|
23
23
|
meta_ads_mcp/core/utils.py,sha256=ytj41yC5SqduLrAiZYBSd6OUwlJRaIClTwnnYKpNFds,9387
|
|
24
|
-
meta_ads_mcp-0.10.
|
|
25
|
-
meta_ads_mcp-0.10.
|
|
26
|
-
meta_ads_mcp-0.10.
|
|
27
|
-
meta_ads_mcp-0.10.
|
|
28
|
-
meta_ads_mcp-0.10.
|
|
24
|
+
meta_ads_mcp-0.10.6.dist-info/METADATA,sha256=_uXqJzjafHyiOQSPntzqWhAVOcnp2xQ6hd9xmiNqXoc,22904
|
|
25
|
+
meta_ads_mcp-0.10.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
meta_ads_mcp-0.10.6.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
|
|
27
|
+
meta_ads_mcp-0.10.6.dist-info/licenses/LICENSE,sha256=E2d762fbhwKRYn8o7J6Szr6vyBPrHVDlK3jbHPx-d84,3851
|
|
28
|
+
meta_ads_mcp-0.10.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|