meta-ads-mcp 0.7.9__py3-none-any.whl → 0.7.10__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.9"
10
+ __version__ = "0.7.10"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
meta_ads_mcp/core/ads.py CHANGED
@@ -670,31 +670,31 @@ async def create_ad_creative(
670
670
  if not account_id.startswith("act_"):
671
671
  account_id = f"act_{account_id}"
672
672
 
673
- # If no page ID is provided, try to find a page associated with the account
673
+ # Enhanced page discovery: If no page ID is provided, use robust discovery methods
674
674
  if not page_id:
675
675
  try:
676
- # Query to get pages associated with the account
677
- pages_endpoint = f"{account_id}/assigned_pages"
678
- pages_params = {
679
- "fields": "id,name",
680
- "limit": 1
681
- }
682
-
683
- pages_data = await make_api_request(pages_endpoint, access_token, pages_params)
676
+ # Use the comprehensive page discovery logic from get_account_pages
677
+ page_discovery_result = await _discover_pages_for_account(account_id, access_token)
684
678
 
685
- if "data" in pages_data and pages_data["data"]:
686
- page_id = pages_data["data"][0]["id"]
687
- print(f"Using page ID: {page_id} ({pages_data['data'][0].get('name', 'Unknown')})")
679
+ if page_discovery_result.get("success"):
680
+ page_id = page_discovery_result["page_id"]
681
+ page_name = page_discovery_result.get("page_name", "Unknown")
682
+ print(f"Auto-discovered page ID: {page_id} ({page_name})")
688
683
  else:
689
684
  return json.dumps({
690
- "error": "No page ID provided and no pages found for this account",
691
- "suggestion": "Please provide a page_id parameter"
685
+ "error": "No page ID provided and no suitable pages found for this account",
686
+ "details": page_discovery_result.get("message", "Page discovery failed"),
687
+ "suggestions": [
688
+ "Use get_account_pages to see available pages",
689
+ "Use search_pages_by_name to find specific pages",
690
+ "Provide a page_id parameter manually"
691
+ ]
692
692
  }, indent=2)
693
693
  except Exception as e:
694
694
  return json.dumps({
695
- "error": "Error finding page for account",
695
+ "error": "Error during page discovery",
696
696
  "details": str(e),
697
- "suggestion": "Please provide a page_id parameter"
697
+ "suggestion": "Please provide a page_id parameter or use get_account_pages to find available pages"
698
698
  }, indent=2)
699
699
 
700
700
  # Prepare the creative data
@@ -759,6 +759,191 @@ async def create_ad_creative(
759
759
  }, indent=2)
760
760
 
761
761
 
762
+ async def _discover_pages_for_account(account_id: str, access_token: str) -> dict:
763
+ """
764
+ Internal function to discover pages for an account using multiple approaches.
765
+ Returns the best available page ID for ad creation.
766
+ """
767
+ try:
768
+ # Approach 1: Extract page IDs from tracking_specs in ads (most reliable)
769
+ endpoint = f"{account_id}/ads"
770
+ params = {
771
+ "fields": "id,name,adset_id,campaign_id,status,creative,created_time,updated_time,bid_amount,conversion_domain,tracking_specs",
772
+ "limit": 100
773
+ }
774
+
775
+ tracking_ads_data = await make_api_request(endpoint, access_token, params)
776
+
777
+ tracking_page_ids = set()
778
+ if "data" in tracking_ads_data:
779
+ for ad in tracking_ads_data.get("data", []):
780
+ tracking_specs = ad.get("tracking_specs", [])
781
+ if isinstance(tracking_specs, list):
782
+ for spec in tracking_specs:
783
+ if isinstance(spec, dict) and "page" in spec:
784
+ page_list = spec["page"]
785
+ if isinstance(page_list, list):
786
+ for page_id in page_list:
787
+ if isinstance(page_id, (str, int)) and str(page_id).isdigit():
788
+ tracking_page_ids.add(str(page_id))
789
+
790
+ if tracking_page_ids:
791
+ # Get details for the first page found
792
+ page_id = list(tracking_page_ids)[0]
793
+ page_endpoint = f"{page_id}"
794
+ page_params = {
795
+ "fields": "id,name,username,category,fan_count,link,verification_status,picture"
796
+ }
797
+
798
+ page_data = await make_api_request(page_endpoint, access_token, page_params)
799
+ if "id" in page_data:
800
+ return {
801
+ "success": True,
802
+ "page_id": page_id,
803
+ "page_name": page_data.get("name", "Unknown"),
804
+ "source": "tracking_specs",
805
+ "note": "Page ID extracted from existing ads - most reliable for ad creation"
806
+ }
807
+
808
+ # Approach 2: Try client_pages endpoint
809
+ endpoint = f"{account_id}/client_pages"
810
+ params = {
811
+ "fields": "id,name,username,category,fan_count,link,verification_status,picture"
812
+ }
813
+
814
+ client_pages_data = await make_api_request(endpoint, access_token, params)
815
+
816
+ if "data" in client_pages_data and client_pages_data["data"]:
817
+ page = client_pages_data["data"][0]
818
+ return {
819
+ "success": True,
820
+ "page_id": page["id"],
821
+ "page_name": page.get("name", "Unknown"),
822
+ "source": "client_pages"
823
+ }
824
+
825
+ # Approach 3: Try assigned_pages endpoint
826
+ pages_endpoint = f"{account_id}/assigned_pages"
827
+ pages_params = {
828
+ "fields": "id,name",
829
+ "limit": 1
830
+ }
831
+
832
+ pages_data = await make_api_request(pages_endpoint, access_token, pages_params)
833
+
834
+ if "data" in pages_data and pages_data["data"]:
835
+ page = pages_data["data"][0]
836
+ return {
837
+ "success": True,
838
+ "page_id": page["id"],
839
+ "page_name": page.get("name", "Unknown"),
840
+ "source": "assigned_pages"
841
+ }
842
+
843
+ # If all approaches failed
844
+ return {
845
+ "success": False,
846
+ "message": "No suitable pages found for this account",
847
+ "note": "Try using get_account_pages to see all available pages or provide page_id manually"
848
+ }
849
+
850
+ except Exception as e:
851
+ return {
852
+ "success": False,
853
+ "message": f"Error during page discovery: {str(e)}"
854
+ }
855
+
856
+
857
+ async def _search_pages_by_name_core(access_token: str, account_id: str, search_term: str = None) -> str:
858
+ """
859
+ Core logic for searching pages by name.
860
+
861
+ Args:
862
+ access_token: Meta API access token
863
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX)
864
+ search_term: Search term to find pages by name (optional - returns all pages if not provided)
865
+
866
+ Returns:
867
+ JSON string with search results
868
+ """
869
+ # Ensure account_id has the 'act_' prefix
870
+ if not account_id.startswith("act_"):
871
+ account_id = f"act_{account_id}"
872
+
873
+ try:
874
+ # Use the internal discovery function directly
875
+ page_discovery_result = await _discover_pages_for_account(account_id, access_token)
876
+
877
+ if not page_discovery_result.get("success"):
878
+ return json.dumps({
879
+ "data": [],
880
+ "message": "No pages found for this account",
881
+ "details": page_discovery_result.get("message", "Page discovery failed")
882
+ }, indent=2)
883
+
884
+ # Create a single page result
885
+ page_data = {
886
+ "id": page_discovery_result["page_id"],
887
+ "name": page_discovery_result.get("page_name", "Unknown"),
888
+ "source": page_discovery_result.get("source", "unknown")
889
+ }
890
+
891
+ all_pages_data = {"data": [page_data]}
892
+
893
+ # Filter pages by search term if provided
894
+ if search_term:
895
+ search_term_lower = search_term.lower()
896
+ filtered_pages = []
897
+
898
+ for page in all_pages_data["data"]:
899
+ page_name = page.get("name", "").lower()
900
+ if search_term_lower in page_name:
901
+ filtered_pages.append(page)
902
+
903
+ return json.dumps({
904
+ "data": filtered_pages,
905
+ "search_term": search_term,
906
+ "total_found": len(filtered_pages),
907
+ "total_available": len(all_pages_data["data"])
908
+ }, indent=2)
909
+ else:
910
+ # Return all pages if no search term provided
911
+ return json.dumps({
912
+ "data": all_pages_data["data"],
913
+ "total_available": len(all_pages_data["data"]),
914
+ "note": "Use search_term parameter to filter pages by name"
915
+ }, indent=2)
916
+
917
+ except Exception as e:
918
+ return json.dumps({
919
+ "error": "Failed to search pages by name",
920
+ "details": str(e)
921
+ }, indent=2)
922
+
923
+
924
+ @mcp_server.tool()
925
+ @meta_api_tool
926
+ async def search_pages_by_name(access_token: str = None, account_id: str = None, search_term: str = None) -> str:
927
+ """
928
+ Search for pages by name within an account.
929
+
930
+ Args:
931
+ access_token: Meta API access token (optional - will use cached token if not provided)
932
+ account_id: Meta Ads account ID (format: act_XXXXXXXXX)
933
+ search_term: Search term to find pages by name (optional - returns all pages if not provided)
934
+
935
+ Returns:
936
+ JSON response with matching pages
937
+ """
938
+ # Check required parameters
939
+ if not account_id:
940
+ return json.dumps({"error": "No account ID provided"}, indent=2)
941
+
942
+ # Call the core function
943
+ result = await _search_pages_by_name_core(access_token, account_id, search_term)
944
+ return result
945
+
946
+
762
947
  @mcp_server.tool()
763
948
  @meta_api_tool
764
949
  async def get_account_pages(access_token: str = None, account_id: str = None) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.7.9
3
+ Version: 0.7.10
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,8 +1,8 @@
1
- meta_ads_mcp/__init__.py,sha256=Ge5-JTZbLcmw6V6e25ojPoaGG6le-XRkrrbxBV5ipH8,1492
1
+ meta_ads_mcp/__init__.py,sha256=Y0Oj0zMOmh2TX_E4iBNehH9NFlM1w9O3injKT8FJMbw,1493
2
2
  meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
3
  meta_ads_mcp/core/__init__.py,sha256=6nYdue6yRepkt6JTAoPGhGbS51qfDSvmczRrDwYOG6A,1709
4
4
  meta_ads_mcp/core/accounts.py,sha256=4IAdGLZ4WE4j4pGW6E0qaXcXqbUIW6Wk2kuQUtlmRTQ,4030
5
- meta_ads_mcp/core/ads.py,sha256=Fcdi1Oa3W-NyY8lXdglkCP1xrYT4YdNSy6acxl6qtzY,37002
5
+ meta_ads_mcp/core/ads.py,sha256=lUq5te8SDFHS2gn8IlwVa0svyFyqJBMvWtuxy1hacFI,44428
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
@@ -21,8 +21,8 @@ meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0
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=ytj41yC5SqduLrAiZYBSd6OUwlJRaIClTwnnYKpNFds,9387
24
- meta_ads_mcp-0.7.9.dist-info/METADATA,sha256=BThlFWPWAY5G88QRv0JzX_A79WcyTTtbUAbikcfYbhc,20409
25
- meta_ads_mcp-0.7.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- meta_ads_mcp-0.7.9.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
- meta_ads_mcp-0.7.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
28
- meta_ads_mcp-0.7.9.dist-info/RECORD,,
24
+ meta_ads_mcp-0.7.10.dist-info/METADATA,sha256=DCV6TdsYdyLogsgvOpIu8hBjawrGo1FWiF4PuKp20AU,20410
25
+ meta_ads_mcp-0.7.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ meta_ads_mcp-0.7.10.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
27
+ meta_ads_mcp-0.7.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
28
+ meta_ads_mcp-0.7.10.dist-info/RECORD,,