meta-ads-mcp 0.4.1__py3-none-any.whl → 0.4.2__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.4.1"
10
+ __version__ = "0.4.2"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -6,6 +6,8 @@ import httpx
6
6
  from typing import Optional, Dict, Any, List, Union
7
7
  from .server import mcp_server
8
8
  from .api import meta_api_tool
9
+ from .auth import get_current_access_token
10
+ from .http_auth_integration import FastMCPAuthIntegration
9
11
 
10
12
 
11
13
  # Only register the duplication functions if the environment variable is set
@@ -178,21 +180,50 @@ if ENABLE_DUPLICATION:
178
180
 
179
181
  async def _forward_duplication_request(resource_type: str, resource_id: str, access_token: str, options: Dict[str, Any]) -> str:
180
182
  """
181
- Forward duplication request to the cloud-hosted MCP API.
183
+ Forward duplication request to the cloud-hosted MCP API using dual-header authentication.
184
+
185
+ This implements the dual-header authentication pattern for MCP server callbacks:
186
+ - Authorization: Bearer <facebook_token> - Facebook access token for Meta API calls
187
+ - X-Pipeboard-Token: <pipeboard_token> - Pipeboard API token for authentication
182
188
 
183
189
  Args:
184
190
  resource_type: Type of resource to duplicate (campaign, adset, ad, creative)
185
191
  resource_id: ID of the resource to duplicate
186
- access_token: Meta API access token from the request
192
+ access_token: Meta API access token (optional, will use context if not provided)
187
193
  options: Duplication options
188
194
  """
189
195
  try:
190
- if not access_token:
196
+ # Get tokens from the request context that were set by the HTTP auth middleware
197
+ # In the dual-header authentication pattern:
198
+ # - Pipeboard token comes from X-Pipeboard-Token header (for authentication)
199
+ # - Facebook token comes from Authorization header (for Meta API calls)
200
+
201
+ # Get tokens from context set by AuthInjectionMiddleware
202
+ pipeboard_token = FastMCPAuthIntegration.get_pipeboard_token()
203
+ facebook_token = FastMCPAuthIntegration.get_auth_token()
204
+
205
+ # Use provided access_token parameter if no Facebook token found in context
206
+ if not facebook_token:
207
+ facebook_token = access_token if access_token else await get_current_access_token()
208
+
209
+ # Validate we have both required tokens
210
+ if not pipeboard_token:
211
+ return json.dumps({
212
+ "error": "authentication_required",
213
+ "message": "Pipeboard API token not found",
214
+ "details": {
215
+ "required": "Valid Pipeboard token via X-Pipeboard-Token header",
216
+ "received_headers": "Check that the MCP server is forwarding the X-Pipeboard-Token header"
217
+ }
218
+ }, indent=2)
219
+
220
+ if not facebook_token:
191
221
  return json.dumps({
192
222
  "error": "authentication_required",
193
223
  "message": "Meta Ads access token not found",
194
224
  "details": {
195
- "required": "Valid access token from authenticated session"
225
+ "required": "Valid Meta access token from authenticated session",
226
+ "check": "Ensure Facebook account is connected and token is valid"
196
227
  }
197
228
  }, indent=2)
198
229
 
@@ -200,9 +231,10 @@ async def _forward_duplication_request(resource_type: str, resource_id: str, acc
200
231
  base_url = "https://mcp.pipeboard.co"
201
232
  endpoint = f"{base_url}/api/meta/duplicate/{resource_type}/{resource_id}"
202
233
 
203
- # Prepare the request
234
+ # Prepare the dual-header authentication as per API documentation
204
235
  headers = {
205
- "Authorization": f"Bearer {access_token}",
236
+ "Authorization": f"Bearer {facebook_token}", # Facebook token for Meta API
237
+ "X-Pipeboard-Token": pipeboard_token, # Pipeboard token for auth
206
238
  "Content-Type": "application/json",
207
239
  "User-Agent": "meta-ads-mcp/1.0"
208
240
  }
@@ -13,6 +13,7 @@ import json
13
13
 
14
14
  # Use context variables instead of thread-local storage for better async support
15
15
  _auth_token: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar('auth_token', default=None)
16
+ _pipeboard_token: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar('pipeboard_token', default=None)
16
17
 
17
18
  class FastMCPAuthIntegration:
18
19
  """Direct integration with FastMCP for HTTP authentication"""
@@ -35,11 +36,34 @@ class FastMCPAuthIntegration:
35
36
  """
36
37
  return _auth_token.get(None)
37
38
 
39
+ @staticmethod
40
+ def set_pipeboard_token(token: str) -> None:
41
+ """Set Pipeboard token for the current context
42
+
43
+ Args:
44
+ token: Pipeboard API token to use for this request
45
+ """
46
+ _pipeboard_token.set(token)
47
+
48
+ @staticmethod
49
+ def get_pipeboard_token() -> Optional[str]:
50
+ """Get Pipeboard token for the current context
51
+
52
+ Returns:
53
+ Pipeboard token if set, None otherwise
54
+ """
55
+ return _pipeboard_token.get(None)
56
+
38
57
  @staticmethod
39
58
  def clear_auth_token() -> None:
40
59
  """Clear authentication token for the current context"""
41
60
  _auth_token.set(None)
42
61
 
62
+ @staticmethod
63
+ def clear_pipeboard_token() -> None:
64
+ """Clear Pipeboard token for the current context"""
65
+ _pipeboard_token.set(None)
66
+
43
67
  @staticmethod
44
68
  def extract_token_from_headers(headers: dict) -> Optional[str]:
45
69
  """Extract token from HTTP headers
@@ -69,6 +93,30 @@ class FastMCPAuthIntegration:
69
93
  return pipeboard_token
70
94
 
71
95
  return None
96
+
97
+ @staticmethod
98
+ def extract_pipeboard_token_from_headers(headers: dict) -> Optional[str]:
99
+ """Extract Pipeboard token from HTTP headers
100
+
101
+ Args:
102
+ headers: HTTP request headers
103
+
104
+ Returns:
105
+ Pipeboard token if found, None otherwise
106
+ """
107
+ # Check for Pipeboard token in X-Pipeboard-Token header (duplication API pattern)
108
+ pipeboard_token = headers.get('X-Pipeboard-Token') or headers.get('x-pipeboard-token')
109
+ if pipeboard_token:
110
+ logger.debug("Found Pipeboard token in X-Pipeboard-Token header")
111
+ return pipeboard_token
112
+
113
+ # Check for legacy Pipeboard token header
114
+ legacy_token = headers.get('X-PIPEBOARD-API-TOKEN') or headers.get('x-pipeboard-api-token')
115
+ if legacy_token:
116
+ logger.debug("Found Pipeboard token in legacy X-PIPEBOARD-API-TOKEN header")
117
+ return legacy_token
118
+
119
+ return None
72
120
 
73
121
  def patch_fastmcp_server(mcp_server):
74
122
  """Patch FastMCP server to inject authentication from HTTP headers
@@ -203,21 +251,32 @@ class AuthInjectionMiddleware(BaseHTTPMiddleware):
203
251
  logger.debug(f"HTTP Auth Middleware: Processing request to {request.url.path}")
204
252
  logger.debug(f"HTTP Auth Middleware: Request headers: {list(request.headers.keys())}")
205
253
 
206
- token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers))
254
+ # Extract both types of tokens for dual-header authentication
255
+ auth_token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers))
256
+ pipeboard_token = FastMCPAuthIntegration.extract_pipeboard_token_from_headers(dict(request.headers))
207
257
 
208
- if token:
209
- logger.debug(f"HTTP Auth Middleware: Extracted token: {token[:10]}...")
258
+ if auth_token:
259
+ logger.debug(f"HTTP Auth Middleware: Extracted auth token: {auth_token[:10]}...")
210
260
  logger.debug("Injecting auth token into request context")
211
- FastMCPAuthIntegration.set_auth_token(token)
212
- else:
213
- logger.warning("HTTP Auth Middleware: No authentication token found in headers")
261
+ FastMCPAuthIntegration.set_auth_token(auth_token)
262
+
263
+ if pipeboard_token:
264
+ logger.debug(f"HTTP Auth Middleware: Extracted Pipeboard token: {pipeboard_token[:10]}...")
265
+ logger.debug("Injecting Pipeboard token into request context")
266
+ FastMCPAuthIntegration.set_pipeboard_token(pipeboard_token)
267
+
268
+ if not auth_token and not pipeboard_token:
269
+ logger.warning("HTTP Auth Middleware: No authentication tokens found in headers")
214
270
 
215
271
  try:
216
272
  response = await call_next(request)
217
273
  return response
218
274
  finally:
219
- if token: # Clear only if a token was set for this request
275
+ # Clear tokens that were set for this request
276
+ if auth_token:
220
277
  FastMCPAuthIntegration.clear_auth_token()
278
+ if pipeboard_token:
279
+ FastMCPAuthIntegration.clear_pipeboard_token()
221
280
 
222
281
  def setup_starlette_middleware(app):
223
282
  """Add AuthInjectionMiddleware to the Starlette app if not already present.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.4.1
3
+ Version: 0.4.2
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,4 +1,4 @@
1
- meta_ads_mcp/__init__.py,sha256=L28BjkKhMZJU54HlrDum5EOovTvkzo5jSvFDE_g8QJw,1182
1
+ meta_ads_mcp/__init__.py,sha256=r7i4rteDOpqUZK4ZZkN5oihxeyUjOIAx-0xIyMqwPkU,1182
2
2
  meta_ads_mcp/__main__.py,sha256=XaQt3iXftG_7f0Zu7Wop9SeFgrD2WBn0EQOaPMc27d8,207
3
3
  meta_ads_mcp/core/__init__.py,sha256=XVJjMOfdgnqxy3k8vCn2PCf7za8fMk4BdgJGiSFCVZY,1209
4
4
  meta_ads_mcp/core/accounts.py,sha256=Nmp7lPxO9wmq25jWV7_H0LIqnEbBhpCVBlLGW2HUaq0,2277
@@ -11,16 +11,16 @@ meta_ads_mcp/core/authentication.py,sha256=4CH2Fe3w7Al7YE2wgoa0DW5qOXTp_5Lsa4T6_
11
11
  meta_ads_mcp/core/budget_schedules.py,sha256=UxseExsvKAiPwfDCY9aycT4kys4xqeNytyq-yyDOxrs,2901
12
12
  meta_ads_mcp/core/callback_server.py,sha256=wNuxmj7YTFeSdVGi_iJ9vberNy3VdzBIP0uSsqn7g5Q,43888
13
13
  meta_ads_mcp/core/campaigns.py,sha256=Fd477GsD1Gx08Ve0uXUCvr4fC-xQCeVHPBwRVaeRQKk,10965
14
- meta_ads_mcp/core/duplication.py,sha256=o9vYczBCiF7bnRZBUGjI2ib06z44E7e7kvJM44jr83k,17052
15
- meta_ads_mcp/core/http_auth_integration.py,sha256=ZJHuxK1Kwtr9gvwfC5HZOLH5MW-HnDDKqJc4xuG5yVE,10060
14
+ meta_ads_mcp/core/duplication.py,sha256=UUmTDFx9o5ZsPQG2Rb9c4ZyuKUVN3FfTjebfTIHHdo4,18984
15
+ meta_ads_mcp/core/http_auth_integration.py,sha256=lGpKhfzJcyWugBcYEvypY-qnlt-3UDBLqh7xAUH0DGw,12473
16
16
  meta_ads_mcp/core/insights.py,sha256=U7KYdWQpGcdykE1WUtdJdYR3VTwKrXUzIzCREwWbf48,2599
17
17
  meta_ads_mcp/core/pipeboard_auth.py,sha256=VvbxEB8ZOhnMccLU7HI1HgaPWHCl5NGrzZCm-zzHze4,22798
18
18
  meta_ads_mcp/core/reports.py,sha256=Dv3hfsPOR7IZ9WrYrKd_6SNgZl-USIphg7knva3UYAw,5747
19
19
  meta_ads_mcp/core/resources.py,sha256=-zIIfZulpo76vcKv6jhAlQq91cR2SZ3cjYZt3ek3x0w,1236
20
20
  meta_ads_mcp/core/server.py,sha256=mmhtcyB7h1aO6jK4njLztPdAebPDmc3mhA7DksR1nlY,17583
21
21
  meta_ads_mcp/core/utils.py,sha256=DsizDYuJnWUpkbShV1y5Qe8t47Qf59aPZ6O9v0hzdkY,6705
22
- meta_ads_mcp-0.4.1.dist-info/METADATA,sha256=XFm8KEWGs0Ej4GQtppFW5kANqnO6URP2FkCJCUHxIH4,17239
23
- meta_ads_mcp-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
- meta_ads_mcp-0.4.1.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
25
- meta_ads_mcp-0.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
- meta_ads_mcp-0.4.1.dist-info/RECORD,,
22
+ meta_ads_mcp-0.4.2.dist-info/METADATA,sha256=M2VY7kNALGcXOYYjvwkxbmp3Ced9m3JO_hzAkjTxEQU,17239
23
+ meta_ads_mcp-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ meta_ads_mcp-0.4.2.dist-info/entry_points.txt,sha256=Dv2RkoBjRJBqj6CyhwqGIiwPCD-SCL1-7B9-zmVRuv0,57
25
+ meta_ads_mcp-0.4.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
+ meta_ads_mcp-0.4.2.dist-info/RECORD,,