@exileum/meta-mcp 6.0.0 → 8.0.0

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.
Files changed (108) hide show
  1. package/README.md +44 -18
  2. package/dist/constants/fields.d.ts +6 -0
  3. package/dist/constants/fields.d.ts.map +1 -0
  4. package/dist/constants/fields.js +25 -0
  5. package/dist/constants/fields.js.map +1 -0
  6. package/dist/http-transport.d.ts +20 -0
  7. package/dist/http-transport.d.ts.map +1 -0
  8. package/dist/http-transport.js +228 -0
  9. package/dist/http-transport.js.map +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +77 -82
  12. package/dist/index.js.map +1 -1
  13. package/dist/prompts/index.d.ts +56 -0
  14. package/dist/prompts/index.d.ts.map +1 -1
  15. package/dist/prompts/index.js +264 -41
  16. package/dist/prompts/index.js.map +1 -1
  17. package/dist/register-all.d.ts +4 -0
  18. package/dist/register-all.d.ts.map +1 -0
  19. package/dist/register-all.js +41 -0
  20. package/dist/register-all.js.map +1 -0
  21. package/dist/resources/instagram.d.ts.map +1 -1
  22. package/dist/resources/instagram.js +13 -4
  23. package/dist/resources/instagram.js.map +1 -1
  24. package/dist/resources/threads.d.ts.map +1 -1
  25. package/dist/resources/threads.js +13 -4
  26. package/dist/resources/threads.js.map +1 -1
  27. package/dist/services/meta-client.d.ts +76 -3
  28. package/dist/services/meta-client.d.ts.map +1 -1
  29. package/dist/services/meta-client.js +330 -46
  30. package/dist/services/meta-client.js.map +1 -1
  31. package/dist/shutdown.d.ts +18 -0
  32. package/dist/shutdown.d.ts.map +1 -0
  33. package/dist/shutdown.js +60 -0
  34. package/dist/shutdown.js.map +1 -0
  35. package/dist/tools/annotations.d.ts +6 -0
  36. package/dist/tools/annotations.d.ts.map +1 -0
  37. package/dist/tools/annotations.js +27 -0
  38. package/dist/tools/annotations.js.map +1 -0
  39. package/dist/tools/instagram/comments.d.ts.map +1 -1
  40. package/dist/tools/instagram/comments.js +75 -50
  41. package/dist/tools/instagram/comments.js.map +1 -1
  42. package/dist/tools/instagram/hashtags.d.ts +1 -0
  43. package/dist/tools/instagram/hashtags.d.ts.map +1 -1
  44. package/dist/tools/instagram/hashtags.js +54 -36
  45. package/dist/tools/instagram/hashtags.js.map +1 -1
  46. package/dist/tools/instagram/media.d.ts.map +1 -1
  47. package/dist/tools/instagram/media.js +53 -34
  48. package/dist/tools/instagram/media.js.map +1 -1
  49. package/dist/tools/instagram/mentions.d.ts.map +1 -1
  50. package/dist/tools/instagram/mentions.js +22 -17
  51. package/dist/tools/instagram/mentions.js.map +1 -1
  52. package/dist/tools/instagram/messaging.d.ts.map +1 -1
  53. package/dist/tools/instagram/messaging.js +74 -50
  54. package/dist/tools/instagram/messaging.js.map +1 -1
  55. package/dist/tools/instagram/profile.d.ts +2 -0
  56. package/dist/tools/instagram/profile.d.ts.map +1 -1
  57. package/dist/tools/instagram/profile.js +63 -43
  58. package/dist/tools/instagram/profile.js.map +1 -1
  59. package/dist/tools/instagram/publishing.d.ts.map +1 -1
  60. package/dist/tools/instagram/publishing.js +202 -139
  61. package/dist/tools/instagram/publishing.js.map +1 -1
  62. package/dist/tools/meta/auth.d.ts.map +1 -1
  63. package/dist/tools/meta/auth.js +63 -21
  64. package/dist/tools/meta/auth.js.map +1 -1
  65. package/dist/tools/threads/insights.d.ts.map +1 -1
  66. package/dist/tools/threads/insights.js +22 -15
  67. package/dist/tools/threads/insights.js.map +1 -1
  68. package/dist/tools/threads/media.d.ts.map +1 -1
  69. package/dist/tools/threads/media.js +42 -70
  70. package/dist/tools/threads/media.js.map +1 -1
  71. package/dist/tools/threads/mentions.d.ts.map +1 -1
  72. package/dist/tools/threads/mentions.js +15 -16
  73. package/dist/tools/threads/mentions.js.map +1 -1
  74. package/dist/tools/threads/profile.d.ts +2 -0
  75. package/dist/tools/threads/profile.d.ts.map +1 -1
  76. package/dist/tools/threads/profile.js +18 -3
  77. package/dist/tools/threads/profile.js.map +1 -1
  78. package/dist/tools/threads/publishing.d.ts.map +1 -1
  79. package/dist/tools/threads/publishing.js +260 -157
  80. package/dist/tools/threads/publishing.js.map +1 -1
  81. package/dist/tools/threads/replies.d.ts.map +1 -1
  82. package/dist/tools/threads/replies.js +55 -45
  83. package/dist/tools/threads/replies.js.map +1 -1
  84. package/dist/utils/container.d.ts +16 -4
  85. package/dist/utils/container.d.ts.map +1 -1
  86. package/dist/utils/container.js +36 -6
  87. package/dist/utils/container.js.map +1 -1
  88. package/dist/utils/errors.d.ts +23 -3
  89. package/dist/utils/errors.d.ts.map +1 -1
  90. package/dist/utils/errors.js +32 -2
  91. package/dist/utils/errors.js.map +1 -1
  92. package/dist/utils/logger.d.ts +28 -0
  93. package/dist/utils/logger.d.ts.map +1 -0
  94. package/dist/utils/logger.js +32 -0
  95. package/dist/utils/logger.js.map +1 -0
  96. package/dist/utils/params.d.ts +3 -0
  97. package/dist/utils/params.d.ts.map +1 -0
  98. package/dist/utils/params.js +14 -0
  99. package/dist/utils/params.js.map +1 -0
  100. package/dist/utils/progress.d.ts +42 -0
  101. package/dist/utils/progress.d.ts.map +1 -0
  102. package/dist/utils/progress.js +35 -0
  103. package/dist/utils/progress.js.map +1 -0
  104. package/dist/utils/response.d.ts +11 -0
  105. package/dist/utils/response.d.ts.map +1 -0
  106. package/dist/utils/response.js +6 -0
  107. package/dist/utils/response.js.map +1 -0
  108. package/package.json +5 -4
package/README.md CHANGED
@@ -71,9 +71,25 @@ npm run build
71
71
  | `META_APP_SECRET` | For token/webhook tools | Meta App Secret |
72
72
  | `META_API_VERSION` | Optional | Meta Graph API version for Instagram and Facebook endpoints — defaults to `v25.0` (verified 2026-05-06). Override only when Meta deprecates a version before meta-mcp ships a new release. Format: `vMAJOR.MINOR` (e.g., `v26.0`); malformed values fall back to the default with a stderr warning. OAuth token endpoints are unversioned and unaffected by this setting |
73
73
  | `THREADS_API_VERSION` | Optional | Threads API version — defaults to `v1.0` (verified 2026-05-06). Threads runs a separate single-major-version track and is not bumped in lockstep with the Graph API. Same `vMAJOR.MINOR` format and fallback behavior as `META_API_VERSION` |
74
+ | `MCP_TRANSPORT` | Optional | Transport to serve MCP over — `stdio` (default) or `http`. See [HTTP Transport](#http-transport) |
75
+ | `MCP_HTTP_PORT` | Optional (http) | TCP port for the HTTP transport — defaults to `3000`. Must be an integer 1–65535 |
76
+ | `MCP_HTTP_HOST` | Optional (http) | Bind address for the HTTP transport — defaults to `127.0.0.1` (loopback only). Set to `0.0.0.0` to accept connections from other hosts (e.g. in Docker), but only behind a TLS/auth reverse proxy |
77
+ | `MCP_HTTP_ALLOWED_HOSTS` | Optional (http) | Comma-separated `host:port` allowlist for DNS-rebinding protection. Defaults to loopback variants at the bound port; set this when binding to a non-loopback host so legitimate requests pass the Host check |
74
78
 
75
79
  The server validates these at startup. Malformed values for `INSTAGRAM_USER_ID`, `THREADS_USER_ID`, or `META_APP_ID` cause the process to exit with `Invalid meta-mcp configuration: …`. Setting only one half of a credential pair (e.g., `INSTAGRAM_ACCESS_TOKEN` without `INSTAGRAM_USER_ID`) prints a stderr warning and continues; related tool invocations still fail at call time.
76
80
 
81
+ ## HTTP Transport
82
+
83
+ By default the server speaks MCP over **stdio** — the right choice for local clients (Claude Desktop, Claude Code, etc.). Set `MCP_TRANSPORT=http` to instead serve the SDK's **Streamable HTTP** transport, which enables remote/web-based MCP clients, cloud deployments, and multiple concurrent client sessions.
84
+
85
+ ```bash
86
+ MCP_TRANSPORT=http MCP_HTTP_PORT=3000 npx @exileum/meta-mcp
87
+ ```
88
+
89
+ The server then listens on `http://127.0.0.1:3000/mcp`: `POST` to send messages, `GET` for the server→client SSE stream, `DELETE` to end a session. Each client gets an isolated session keyed by the `Mcp-Session-Id` header, so multiple clients can connect at once.
90
+
91
+ **Security.** The transport binds to `127.0.0.1` (loopback) by default and enables DNS-rebinding protection scoped to localhost, so it is not reachable off-host out of the box. To expose it to other machines (e.g. a container), set `MCP_HTTP_HOST=0.0.0.0` and run it **behind a reverse proxy that terminates TLS and handles authentication** — the server itself performs no auth. When bound to a non-loopback address, set `MCP_HTTP_ALLOWED_HOSTS` to the `host:port` values clients will use so the Host-header check passes (otherwise rebinding protection is disabled with a stderr warning).
92
+
77
93
  ## Account Requirements
78
94
 
79
95
  | Platform | Account Type | Notes |
@@ -90,8 +106,13 @@ The server validates these at startup. Malformed values for `INSTAGRAM_USER_ID`,
90
106
  - **Meta**: Token exchange/refresh/debug, webhook management
91
107
  - **2 resources**: Instagram profile, Threads profile
92
108
  - **2 prompts**: Cross-platform content publishing, analytics report
93
- - Rate limit tracking via `x-app-usage` header
109
+ - Rate limit tracking via `x-app-usage` header — and **automatic client-side throttling** at 80% (1s slowdown) / 90% (5s backoff) so a burst of tool calls stays under Meta's per-app quota
110
+ - **Automatic retry** for transient Meta API failures (HTTP `429`/`500`/`502`/`503`/`504`, network errors, `fetch` timeouts) with exponential backoff and `Retry-After` honoring; tunable via `MetaClient`'s `maxRetries` option (default 3, set to 0 to disable)
94
111
  - **Structured error responses** with `error_type` (`auth`, `validation`, `rate_limit`, `server`, `network`, `internal`), HTTP status, Meta API code/subcode/type, and a `remediation` hint where actionable — see [`CHANGELOG.md`](./CHANGELOG.md) for the JSON shape
112
+ - **MCP server `instructions`** sent during `initialize` so clients know required env vars, the two-step publish flow, expected video processing times, and the `_rateLimit` envelope without re-reading the README
113
+ - **MCP `notifications/progress`** emitted while polling container status during publishing — attach a `progressToken` to `ig_publish_*` / `threads_publish_image|video|carousel` calls and the server reports each poll attempt
114
+ - **Structured MCP logging** via the `notifications/message` channel (the server declares the `logging` capability) — each API call logs `debug` (method + path, never the token-bearing URL), terminal failures log `error` (status/code/sanitized message), rate-limit pressure logs `warning`, and `DELETE`/publish operations log an `info` audit line. Clients can raise the floor with `logging/setLevel` (default emits all levels, including `debug`)
115
+ - **Optional HTTP transport** — set `MCP_TRANSPORT=http` to serve the MCP Streamable HTTP transport (stateful multi-session, localhost-bound by default) instead of stdio, for remote/cloud deployments — see [HTTP Transport](#http-transport)
95
116
 
96
117
  ## Tools
97
118
 
@@ -171,21 +192,22 @@ The server validates these at startup. Malformed values for `INSTAGRAM_USER_ID`,
171
192
  |------|-------------|
172
193
  | `ig_get_conversations` | List DM conversations |
173
194
  | `ig_get_messages` | Get messages in a conversation |
174
- | `ig_send_message` | Send a DM |
195
+ | `ig_send_message` | Send a DM (optional `messaging_type` = `RESPONSE`/`UPDATE`/`MESSAGE_TAG` and `tag` = `HUMAN_AGENT` for the 7-day human-agent window) |
175
196
  | `ig_get_message` | Get message details |
176
197
 
177
- ### Threads — Publishing (8)
198
+ ### Threads — Publishing (9)
178
199
 
179
200
  | Tool | Description |
180
201
  |------|-------------|
181
- | `threads_publish_text` | Publish a text post in a single API call (`auto_publish_text=true`, default; set `auto_publish=false` for the legacy two-step flow). Supports polls, GIFs, link attachments, topic tags, quote posts, spoiler flag, cross-share to IG Stories, geo-gating via `allowlisted_country_codes`, text attachments up to 10K chars with styling |
182
- | `threads_publish_image` | Publish an image post (supports alt_text, topic tags, spoiler flag, cross-share to IG Stories, geo-gating via `allowlisted_country_codes`) |
183
- | `threads_publish_video` | Publish a video post (supports alt_text, topic tags, spoiler flag, cross-share to IG Stories, geo-gating via `allowlisted_country_codes`) |
184
- | `threads_publish_carousel` | Publish a carousel (2-20 items, supports alt_text per item, cross-share to IG Stories, geo-gating via `allowlisted_country_codes` on the parent container) |
202
+ | `threads_publish_text` | Publish a text post in a single API call (`auto_publish_text=true`, default; set `auto_publish=false` for the legacy two-step flow). Supports polls, GIFs, link attachments, topic tags, quote posts, spoiler flag, cross-share to IG Stories, geo-gating via `allowlisted_country_codes`, location tagging via `location_id`, text attachments up to 10K chars with styling |
203
+ | `threads_publish_image` | Publish an image post (supports alt_text, topic tags, spoiler flag, cross-share to IG Stories, geo-gating via `allowlisted_country_codes`, location tagging via `location_id`) |
204
+ | `threads_publish_video` | Publish a video post (supports alt_text, topic tags, spoiler flag, cross-share to IG Stories, geo-gating via `allowlisted_country_codes`, location tagging via `location_id`) |
205
+ | `threads_publish_carousel` | Publish a carousel (2-20 items, supports alt_text per item, cross-share to IG Stories, geo-gating via `allowlisted_country_codes` and location tagging via `location_id` on the parent container) |
185
206
  | `threads_delete_post` | Delete a post (max 100/day) |
186
207
  | `threads_get_container_status` | Check container processing status (unpublished containers only) |
187
208
  | `threads_get_publishing_limit` | Check remaining publishing quota (250 posts/day) |
188
209
  | `threads_repost` | Repost an existing thread to your profile (requires `threads_content_publish`) |
210
+ | `threads_search_locations` | Search Threads-supported locations by query (`q`) or coordinates (`latitude`+`longitude`) to obtain a `location_id` for the four `threads_publish_*` tools (requires `threads_location_tagging` permission) |
189
211
 
190
212
  ### Threads — Media & Search (3)
191
213
 
@@ -228,14 +250,14 @@ The server validates these at startup. Malformed values for `INSTAGRAM_USER_ID`,
228
250
  | Resource URI | Description |
229
251
  |-------------|-------------|
230
252
  | `meta-mcp://instagram/profile` | Instagram account profile data |
231
- | `meta-mcp://threads/profile` | Threads account profile data (includes is_verified) |
253
+ | `meta-mcp://threads/profile` | Threads account profile data (includes is_verified and is_eligible_for_geo_gating) |
232
254
 
233
255
  ## Prompts
234
256
 
235
- | Prompt | Description |
236
- |--------|-------------|
237
- | `content_publish` | Cross-post content to Instagram and Threads |
238
- | `analytics_report` | Generate combined analytics report |
257
+ | Prompt | Description | Arguments (all optional) |
258
+ |--------|-------------|--------------------------|
259
+ | `content_publish` | Cross-post content to Instagram and Threads | `platform` (`instagram` \| `threads` \| `both`), `content_type` (`text` \| `image` \| `video` \| `carousel`), `media_url`, `caption` |
260
+ | `analytics_report` | Generate combined analytics report | `platform` (`instagram` \| `threads` \| `both`), `time_range` (`7d` \| `30d` \| `90d`), `focus` (`engagement` \| `growth` \| `content`) |
239
261
 
240
262
  ## Setup Guide
241
263
 
@@ -331,11 +353,15 @@ Access tokens expire after ~60 days. Refresh before expiration (token must be at
331
353
  &access_token=CURRENT_LONG_LIVED_TOKEN
332
354
  ```
333
355
 
356
+ When you rotate a token through `meta_refresh_token` or `meta_exchange_token`, the new token is **automatically applied in-memory** to the running MCP server — subsequent tool calls use it immediately, no server restart needed. The new token is still returned in the response so you can persist it in your environment for the next process restart. A single `[meta-mcp] <Platform> access token updated in-memory after <tool>…` line is logged to stderr when this happens.
357
+
334
358
  Check token status anytime with `meta_debug_token`.
335
359
 
336
360
  ## Troubleshooting
337
361
 
338
- Tool failures return `isError: true` with a JSON body in `content[0].text` matching the envelope documented in [`CHANGELOG.md`](./CHANGELOG.md): `{ error: true, error_type, http_status, code, subcode, type, message, remediation, fbtrace_id, raw }`. The fastest path to a fix is to read `error_type` and the Meta API `code`, then jump to the matching subsection below. The full code reference is the [Meta Graph API error handling guide](https://developers.facebook.com/docs/graph-api/guides/error-handling/).
362
+ Tool failures return `isError: true` with a JSON body in `content[0].text` matching the envelope documented in [`CHANGELOG.md`](./CHANGELOG.md): `{ error: true, error_type, http_status, code, subcode, type, step, container_id, message, remediation, fbtrace_id, raw }`. The fastest path to a fix is to read `error_type` and the Meta API `code`, then jump to the matching subsection below. The full code reference is the [Meta Graph API error handling guide](https://developers.facebook.com/docs/graph-api/guides/error-handling/).
363
+
364
+ On the publish tools (`ig_publish_*`, `threads_publish_*`, `threads_reply`), errors also include `step` (`container creation` / `processing` / `publishing`, plus `child container creation` / `child processing` / `parent container creation` / `parent processing` on carousels) and `container_id` when one was created. The `message` mirrors them: `"Publish photo failed at processing (container: 17889615324): Container processing timed out after 30s"`. Use these to decide whether to retry the publish, clean up an orphaned container, or treat the existing container as still reusable.
339
365
 
340
366
  ### `error_type: "auth"` — expired, revoked, or under-scoped token
341
367
 
@@ -355,12 +381,12 @@ What to do:
355
381
 
356
382
  ### `error_type: "rate_limit"` — application or user quota exhausted
357
383
 
358
- Triggered by Meta API codes `4`, `17`, `32`, `341`, `613`, the business-use-case range `80001`–`80008`, or HTTP `429`. Includes any `OAuthException` with code `4` / `17` (these are surfaced as `error_type: "rate_limit"`, **not** `"auth"`, despite the type field).
384
+ Triggered by Meta API codes `4`, `17`, `32`, `341`, `613`, the business-use-case range `80001`–`80008`, or HTTP `429`. Includes any `OAuthException` with code `4` / `17` (these are surfaced as `error_type: "rate_limit"`, **not** `"auth"`, despite the type field). `MetaClient` automatically retries HTTP `429` up to 3 times with exponential backoff and honors any `Retry-After` header — a `rate_limit` error reaching the caller means the retry budget was exhausted.
359
385
 
360
386
  What to do:
361
387
 
362
- 1. Inspect the `_rateLimit` field on prior successful tool responses. `callCount`, `totalCpuTime`, and `totalTime` come from Meta's `x-app-usage` header; when `callCount` approaches `100` you are near the per-app threshold.
363
- 2. Back off with exponential delay; reduce request volume; cache profile metadata between calls.
388
+ 1. Inspect the `_rateLimit` field on prior successful tool responses. `callCount`, `totalCpuTime`, and `totalTime` come from Meta's `x-app-usage` header; when any approaches `100` you are near the per-app threshold.
389
+ 2. meta-mcp already self-throttles once `max(callCount, totalCpuTime, totalTime)` crosses 80% (1s slowdown) or 90% (5s backoff) — watch for the `warning`-level MCP log message (`logger: "meta-client"`, with `usage_pct` and `delay_ms`) the server emits before each throttled call. Profile reads (`ig_get_profile`, `threads_get_profile`, and the matching `meta-mcp://*/profile` resources) and hashtag-name lookups (`ig_search_hashtag`) are also cached in-process for 5 minutes / 7 days respectively, with cache hits skipping the network entirely. If you are still hitting `rate_limit` errors despite all that, reduce request volume further.
364
390
  3. Threads has hard daily quotas (250 publishes, 100 deletes) — query the remaining quota with `threads_get_publishing_limit` before bulk operations.
365
391
 
366
392
  ### `error_type: "validation"` — bad parameter, wrong ID, or unsupported field
@@ -375,8 +401,8 @@ Triggered by Meta API codes `100`, `200`, `803`, or any unmapped 4xx HTTP status
375
401
 
376
402
  ### Other categories
377
403
 
378
- - `error_type: "server"` (codes `1`, `2`, HTTP 5xx) — transient Meta outage; retry with exponential backoff. Check [metastatus.com](https://metastatus.com/) if it persists.
379
- - `error_type: "network"` — `fetch` timed out or failed before reaching Meta. Verify outbound connectivity and retry.
404
+ - `error_type: "server"` (codes `1`, `2`, HTTP 5xx) — transient Meta outage. `MetaClient` already retried `500`/`502`/`503`/`504` up to 3 times with exponential backoff before surfacing this; check [metastatus.com](https://metastatus.com/) if it persists.
405
+ - `error_type: "network"` — `fetch` timed out or failed before reaching Meta. `MetaClient` already retried thrown network errors up to 3 times; verify outbound connectivity if the error keeps reappearing.
380
406
  - `error_type: "internal"` — unexpected condition that did not map to a Meta error code. The `raw` field carries the sanitized original message; `access_token`, `client_secret`, and `input_token` values are scrubbed to `***` before reporting.
381
407
 
382
408
  ## API Stability
@@ -0,0 +1,6 @@
1
+ export declare const IG_PROFILE_FIELDS = "id,name,username,biography,followers_count,follows_count,media_count,profile_picture_url,website";
2
+ export declare const IG_MEDIA_FIELDS = "id,caption,media_type,media_url,permalink,thumbnail_url,timestamp,like_count,comments_count";
3
+ export declare const IG_HASHTAG_MEDIA_FIELDS = "id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count";
4
+ export declare const THREADS_PROFILE_FIELDS = "id,username,name,threads_profile_picture_url,threads_biography,is_verified,is_eligible_for_geo_gating";
5
+ export declare const THREADS_MEDIA_FIELDS: string;
6
+ //# sourceMappingURL=fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/constants/fields.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,qGACsE,CAAC;AAErG,eAAO,MAAM,eAAe,gGACmE,CAAC;AAIhG,eAAO,MAAM,uBAAuB,kFAC6C,CAAC;AAElF,eAAO,MAAM,sBAAsB,0GACsE,CAAC;AAE1G,eAAO,MAAM,oBAAoB,QAiBtB,CAAC"}
@@ -0,0 +1,25 @@
1
+ export const IG_PROFILE_FIELDS = "id,name,username,biography,followers_count,follows_count,media_count,profile_picture_url,website";
2
+ export const IG_MEDIA_FIELDS = "id,caption,media_type,media_url,permalink,thumbnail_url,timestamp,like_count,comments_count";
3
+ // thumbnail_url is intentionally omitted — the Instagram Hashtag Search API
4
+ // does not return it on hashtag media results, only on user-owned media.
5
+ export const IG_HASHTAG_MEDIA_FIELDS = "id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count";
6
+ export const THREADS_PROFILE_FIELDS = "id,username,name,threads_profile_picture_url,threads_biography,is_verified,is_eligible_for_geo_gating";
7
+ export const THREADS_MEDIA_FIELDS = [
8
+ "id",
9
+ "media_product_type",
10
+ "media_type",
11
+ "media_url",
12
+ "permalink",
13
+ "text",
14
+ "timestamp",
15
+ "shortcode",
16
+ "is_quote_post",
17
+ "has_replies",
18
+ "reply_audience",
19
+ "topic_tag",
20
+ "link_attachment_url",
21
+ "poll_attachment{option_a,option_b,option_c,option_d,option_a_votes_percentage,option_b_votes_percentage,option_c_votes_percentage,option_d_votes_percentage,total_votes,expiration_timestamp}",
22
+ "gif_url",
23
+ "alt_text",
24
+ ].join(",");
25
+ //# sourceMappingURL=fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/constants/fields.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAC5B,kGAAkG,CAAC;AAErG,MAAM,CAAC,MAAM,eAAe,GAC1B,6FAA6F,CAAC;AAEhG,4EAA4E;AAC5E,yEAAyE;AACzE,MAAM,CAAC,MAAM,uBAAuB,GAClC,+EAA+E,CAAC;AAElF,MAAM,CAAC,MAAM,sBAAsB,GACjC,uGAAuG,CAAC;AAE1G,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI;IACJ,oBAAoB;IACpB,YAAY;IACZ,WAAW;IACX,WAAW;IACX,MAAM;IACN,WAAW;IACX,WAAW;IACX,eAAe;IACf,aAAa;IACb,gBAAgB;IAChB,WAAW;IACX,qBAAqB;IACrB,+LAA+L;IAC/L,SAAS;IACT,UAAU;CACX,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const DEFAULT_HTTP_HOST = "127.0.0.1";
3
+ export declare const DEFAULT_HTTP_PORT = 3000;
4
+ export declare const MCP_ENDPOINT = "/mcp";
5
+ export interface HttpTransportConfig {
6
+ host: string;
7
+ port: number;
8
+ allowedHosts: string[] | undefined;
9
+ }
10
+ export interface StartHttpTransportOptions extends HttpTransportConfig {
11
+ createServer: () => McpServer;
12
+ log?: (msg: string) => void;
13
+ }
14
+ export interface HttpTransportHandle {
15
+ port: number;
16
+ close(): Promise<void>;
17
+ }
18
+ export declare function parseHttpTransportConfig(env: NodeJS.ProcessEnv): HttpTransportConfig;
19
+ export declare function startHttpTransport(options: StartHttpTransportOptions): Promise<HttpTransportHandle>;
20
+ //# sourceMappingURL=http-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../src/http-transport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,YAAY,SAAS,CAAC;AAKnC,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IAEb,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IAEpE,YAAY,EAAE,MAAM,SAAS,CAAC;IAC9B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,mBAAmB,CAsBpF;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAoI9B"}
@@ -0,0 +1,228 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5
+ export const DEFAULT_HTTP_HOST = "127.0.0.1";
6
+ export const DEFAULT_HTTP_PORT = 3000;
7
+ export const MCP_ENDPOINT = "/mcp";
8
+ // Node's http server imposes no body limit; cap reads to avoid memory exhaustion.
9
+ const MAX_BODY_BYTES = 4 * 1024 * 1024;
10
+ export function parseHttpTransportConfig(env) {
11
+ const host = env.MCP_HTTP_HOST?.trim() || DEFAULT_HTTP_HOST;
12
+ let port = DEFAULT_HTTP_PORT;
13
+ const rawPort = env.MCP_HTTP_PORT?.trim();
14
+ if (rawPort) {
15
+ // Number() (unlike parseInt) rejects "3000abc" as NaN.
16
+ const parsed = Number(rawPort);
17
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
18
+ throw new Error(`MCP_HTTP_PORT must be an integer between 1 and 65535 (got "${rawPort}")`);
19
+ }
20
+ port = parsed;
21
+ }
22
+ const rawAllowed = env.MCP_HTTP_ALLOWED_HOSTS?.trim();
23
+ const allowedHosts = rawAllowed
24
+ ? rawAllowed.split(",").map((h) => h.trim()).filter(Boolean)
25
+ : undefined;
26
+ return { host, port, allowedHosts };
27
+ }
28
+ export async function startHttpTransport(options) {
29
+ const { host, createServer: createMcpServer } = options;
30
+ const log = options.log ?? ((msg) => console.error(msg));
31
+ const sessions = new Map();
32
+ // Resolved after listen() so DNS-rebinding allowlists can include the actual
33
+ // bound port (matters when port is 0, e.g. in tests).
34
+ let allowedHosts = [];
35
+ let dnsRebindingProtection = false;
36
+ const httpServer = createServer((req, res) => {
37
+ void handleRequest(req, res).catch((err) => {
38
+ log(`[meta-mcp] HTTP handler error — ${errMessage(err)}`);
39
+ sendJsonRpcError(res, 500, -32603, "Internal server error");
40
+ });
41
+ });
42
+ async function handleRequest(req, res) {
43
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
44
+ if (url.pathname !== MCP_ENDPOINT) {
45
+ sendJsonRpcError(res, 404, -32601, `Not found: ${url.pathname}`);
46
+ return;
47
+ }
48
+ const method = req.method ?? "GET";
49
+ const sessionId = headerValue(req.headers["mcp-session-id"]);
50
+ if (method === "POST") {
51
+ let body;
52
+ try {
53
+ body = await readJsonBody(req);
54
+ }
55
+ catch (err) {
56
+ // Fixed messages only — never echo exception text back to a remote client.
57
+ if (err instanceof PayloadTooLargeError) {
58
+ sendJsonRpcError(res, 413, -32600, "Request body too large");
59
+ }
60
+ else {
61
+ sendJsonRpcError(res, 400, -32700, "Invalid JSON in request body");
62
+ }
63
+ return;
64
+ }
65
+ if (sessionId) {
66
+ const transport = sessions.get(sessionId);
67
+ if (!transport) {
68
+ sendJsonRpcError(res, 404, -32001, "Session not found");
69
+ return;
70
+ }
71
+ await transport.handleRequest(req, res, body);
72
+ return;
73
+ }
74
+ if (isInitializeRequest(body)) {
75
+ const transport = new StreamableHTTPServerTransport({
76
+ sessionIdGenerator: () => randomUUID(),
77
+ enableDnsRebindingProtection: dnsRebindingProtection,
78
+ allowedHosts,
79
+ onsessioninitialized: (sid) => {
80
+ sessions.set(sid, transport);
81
+ },
82
+ });
83
+ // Drop the session on close (DELETE or client drop) so the map can't leak.
84
+ transport.onclose = () => {
85
+ const sid = transport.sessionId;
86
+ if (sid)
87
+ sessions.delete(sid);
88
+ };
89
+ try {
90
+ await createMcpServer().connect(transport);
91
+ await transport.handleRequest(req, res, body);
92
+ }
93
+ catch (err) {
94
+ // Close so onclose evicts a half-initialized session from the map.
95
+ await transport.close().catch(() => undefined);
96
+ throw err;
97
+ }
98
+ return;
99
+ }
100
+ sendJsonRpcError(res, 400, -32000, "Bad Request: missing or invalid session ID");
101
+ return;
102
+ }
103
+ if (method === "GET" || method === "DELETE") {
104
+ if (!sessionId) {
105
+ sendJsonRpcError(res, 400, -32000, "Bad Request: missing session ID");
106
+ return;
107
+ }
108
+ const transport = sessions.get(sessionId);
109
+ if (!transport) {
110
+ sendJsonRpcError(res, 404, -32001, "Session not found");
111
+ return;
112
+ }
113
+ await transport.handleRequest(req, res);
114
+ return;
115
+ }
116
+ sendJsonRpcError(res, 405, -32601, `Method not allowed: ${method}`);
117
+ }
118
+ await new Promise((resolve, reject) => {
119
+ const onListenError = (err) => reject(err);
120
+ httpServer.once("error", onListenError);
121
+ httpServer.listen(options.port, host, () => {
122
+ httpServer.removeListener("error", onListenError);
123
+ resolve();
124
+ });
125
+ });
126
+ const actualPort = httpServer.address().port;
127
+ if (options.allowedHosts && options.allowedHosts.length > 0) {
128
+ allowedHosts = options.allowedHosts;
129
+ dnsRebindingProtection = true;
130
+ }
131
+ else if (isLoopbackHost(host)) {
132
+ allowedHosts = loopbackHosts(actualPort);
133
+ dnsRebindingProtection = true;
134
+ }
135
+ else {
136
+ // Bound to a routable interface (e.g. 0.0.0.0 in Docker) with no allowlist —
137
+ // a Host allowlist can't be derived, so leave protection off and tell the
138
+ // operator to front it with a proxy or set MCP_HTTP_ALLOWED_HOSTS.
139
+ log(`[meta-mcp] Warning: HTTP transport bound to non-loopback host ${host} without ` +
140
+ `MCP_HTTP_ALLOWED_HOSTS — DNS-rebinding protection is disabled. Run behind a ` +
141
+ `reverse proxy and/or set MCP_HTTP_ALLOWED_HOSTS.`);
142
+ }
143
+ log(`[meta-mcp] HTTP transport listening on http://${host}:${actualPort}${MCP_ENDPOINT}`);
144
+ return {
145
+ port: actualPort,
146
+ close: () => closeHttpTransport(httpServer, sessions),
147
+ };
148
+ }
149
+ async function closeHttpTransport(httpServer, sessions) {
150
+ // Close transports first: each open SSE stream holds a socket that
151
+ // httpServer.close() would otherwise wait on forever.
152
+ for (const transport of sessions.values()) {
153
+ try {
154
+ await transport.close();
155
+ }
156
+ catch {
157
+ // best-effort — keep tearing down the rest
158
+ }
159
+ }
160
+ sessions.clear();
161
+ httpServer.closeIdleConnections();
162
+ await new Promise((resolve, reject) => {
163
+ httpServer.close((err) => (err ? reject(err) : resolve()));
164
+ });
165
+ }
166
+ class PayloadTooLargeError extends Error {
167
+ }
168
+ function readJsonBody(req) {
169
+ return new Promise((resolve, reject) => {
170
+ const chunks = [];
171
+ let size = 0;
172
+ let settled = false;
173
+ const settle = (fn) => {
174
+ if (settled)
175
+ return;
176
+ settled = true;
177
+ fn();
178
+ };
179
+ req.on("data", (chunk) => {
180
+ size += chunk.length;
181
+ if (size > MAX_BODY_BYTES) {
182
+ // Reject and stop buffering, but keep draining the socket so the 413
183
+ // response writes cleanly — destroying req here would kill res too.
184
+ settle(() => reject(new PayloadTooLargeError(`Request body exceeds ${MAX_BODY_BYTES} bytes`)));
185
+ return;
186
+ }
187
+ chunks.push(chunk);
188
+ });
189
+ req.on("end", () => {
190
+ settle(() => {
191
+ const raw = Buffer.concat(chunks).toString("utf8");
192
+ if (!raw) {
193
+ resolve(undefined);
194
+ return;
195
+ }
196
+ try {
197
+ resolve(JSON.parse(raw));
198
+ }
199
+ catch {
200
+ reject(new SyntaxError("Invalid JSON in request body"));
201
+ }
202
+ });
203
+ });
204
+ req.on("error", (err) => settle(() => reject(err)));
205
+ });
206
+ }
207
+ function sendJsonRpcError(res, status, code, message) {
208
+ if (res.headersSent || res.writableEnded || res.destroyed)
209
+ return;
210
+ res.writeHead(status, { "Content-Type": "application/json" });
211
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id: null }));
212
+ }
213
+ function headerValue(value) {
214
+ if (Array.isArray(value))
215
+ return value[0];
216
+ return value;
217
+ }
218
+ function isLoopbackHost(host) {
219
+ // The whole 127.0.0.0/8 range is loopback, plus localhost and IPv6 ::1.
220
+ return host === "localhost" || host === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
221
+ }
222
+ function loopbackHosts(port) {
223
+ return [`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`];
224
+ }
225
+ function errMessage(err) {
226
+ return err instanceof Error ? err.message : String(err);
227
+ }
228
+ //# sourceMappingURL=http-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-transport.js","sourceRoot":"","sources":["../src/http-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAC;AAGjG,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAEzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAEnC,kFAAkF;AAClF,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAoBvC,MAAM,UAAU,wBAAwB,CAAC,GAAsB;IAC7D,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,iBAAiB,CAAC;IAE5D,IAAI,IAAI,GAAG,iBAAiB,CAAC;IAC7B,MAAM,OAAO,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IAC1C,IAAI,OAAO,EAAE,CAAC;QACZ,uDAAuD;QACvD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,KAAK,EAAE,CAAC;YAC9D,MAAM,IAAI,KAAK,CACb,8DAA8D,OAAO,IAAI,CAC1E,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,UAAU,GAAG,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,CAAC;IACtD,MAAM,YAAY,GAAG,UAAU;QAC7B,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QAC5D,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAkC;IAElC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAEjE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyC,CAAC;IAElE,6EAA6E;IAC7E,sDAAsD;IACtD,IAAI,YAAY,GAAa,EAAE,CAAC;IAChC,IAAI,sBAAsB,GAAG,KAAK,CAAC;IAEnC,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3C,KAAK,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YAClD,GAAG,CAAC,mCAAmC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1D,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,KAAK,UAAU,aAAa,CAAC,GAAoB,EAAE,GAAmB;QACpE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QAC1E,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YAClC,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,cAAc,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;QACnC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAE7D,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,2EAA2E;gBAC3E,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;oBACxC,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC;gBAC/D,CAAC;qBAAM,CAAC;oBACN,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,8BAA8B,CAAC,CAAC;gBACrE,CAAC;gBACD,OAAO;YACT,CAAC;YAED,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;oBACxD,OAAO;gBACT,CAAC;gBACD,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YAED,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;oBAClD,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;oBACtC,4BAA4B,EAAE,sBAAsB;oBACpD,YAAY;oBACZ,oBAAoB,EAAE,CAAC,GAAG,EAAE,EAAE;wBAC5B,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;oBAC/B,CAAC;iBACF,CAAC,CAAC;gBACH,2EAA2E;gBAC3E,SAAS,CAAC,OAAO,GAAG,GAAG,EAAE;oBACvB,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC;oBAChC,IAAI,GAAG;wBAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChC,CAAC,CAAC;gBACF,IAAI,CAAC;oBACH,MAAM,eAAe,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;oBAC3C,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;gBAChD,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,mEAAmE;oBACnE,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAC/C,MAAM,GAAG,CAAC;gBACZ,CAAC;gBACD,OAAO;YACT,CAAC;YAED,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,4CAA4C,CAAC,CAAC;YACjF,OAAO;QACT,CAAC;QAED,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,iCAAiC,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YACD,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QAED,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,uBAAuB,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,aAAa,GAAG,CAAC,GAAU,EAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACxD,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACxC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;YACzC,UAAU,CAAC,cAAc,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAClD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAI,UAAU,CAAC,OAAO,EAAkB,CAAC,IAAI,CAAC;IAE9D,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACpC,sBAAsB,GAAG,IAAI,CAAC;IAChC,CAAC;SAAM,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,YAAY,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;QACzC,sBAAsB,GAAG,IAAI,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,6EAA6E;QAC7E,0EAA0E;QAC1E,mEAAmE;QACnE,GAAG,CACD,iEAAiE,IAAI,WAAW;YAC9E,8EAA8E;YAC9E,kDAAkD,CACrD,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,iDAAiD,IAAI,IAAI,UAAU,GAAG,YAAY,EAAE,CAAC,CAAC;IAE1F,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC;KACtD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,UAAkB,EAClB,QAAoD;IAEpD,mEAAmE;IACnE,sDAAsD;IACtD,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,2CAA2C;QAC7C,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,UAAU,CAAC,oBAAoB,EAAE,CAAC;IAClC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,oBAAqB,SAAQ,KAAK;CAAG;AAE3C,SAAS,YAAY,CAAC,GAAoB;IACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAc,EAAQ,EAAE;YACtC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,EAAE,EAAE,CAAC;QACP,CAAC,CAAC;QAEF,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC/B,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;gBAC1B,qEAAqE;gBACrE,oEAAoE;gBACpE,MAAM,CAAC,GAAG,EAAE,CACV,MAAM,CAAC,IAAI,oBAAoB,CAAC,wBAAwB,cAAc,QAAQ,CAAC,CAAC,CACjF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACjB,MAAM,CAAC,GAAG,EAAE;gBACV,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBACnD,IAAI,CAAC,GAAG,EAAE,CAAC;oBACT,OAAO,CAAC,SAAS,CAAC,CAAC;oBACnB,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC3B,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,CAAC,IAAI,WAAW,CAAC,8BAA8B,CAAC,CAAC,CAAC;gBAC1D,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CACvB,GAAmB,EACnB,MAAc,EACd,IAAY,EACZ,OAAe;IAEf,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,SAAS;QAAE,OAAO;IAClE,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,WAAW,CAAC,KAAoC;IACvD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,wEAAwE;IACxE,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,KAAK,IAAI,kCAAkC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,CAAC,aAAa,IAAI,EAAE,EAAE,aAAa,IAAI,EAAE,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,UAAU,CAAC,GAAY;IAC9B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkFpE,wBAAgB,mBAAmB,cAmClC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwEpE,wBAAgB,mBAAmB,IAAI,SAAS,CAU/C"}
package/dist/index.js CHANGED
@@ -1,79 +1,72 @@
1
1
  #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
2
3
  import { createRequire } from "node:module";
4
+ import { pathToFileURL } from "node:url";
3
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
7
  import { loadConfig } from "./config.js";
8
+ import { parseHttpTransportConfig, startHttpTransport, } from "./http-transport.js";
6
9
  import { MetaClient } from "./services/meta-client.js";
7
- // Meta platform tools
8
- import { registerMetaAuthTools } from "./tools/meta/auth.js";
9
- // Instagram tools
10
- import { registerIgPublishingTools } from "./tools/instagram/publishing.js";
11
- import { registerIgMediaTools } from "./tools/instagram/media.js";
12
- import { registerIgCommentTools } from "./tools/instagram/comments.js";
13
- import { registerIgProfileTools } from "./tools/instagram/profile.js";
14
- import { registerIgHashtagTools } from "./tools/instagram/hashtags.js";
15
- import { registerIgMentionTools } from "./tools/instagram/mentions.js";
16
- import { registerIgMessagingTools } from "./tools/instagram/messaging.js";
17
- // Threads tools
18
- import { registerThreadsPublishingTools } from "./tools/threads/publishing.js";
19
- import { registerThreadsMediaTools } from "./tools/threads/media.js";
20
- import { registerThreadsReplyTools } from "./tools/threads/replies.js";
21
- import { registerThreadsProfileTools } from "./tools/threads/profile.js";
22
- import { registerThreadsInsightTools } from "./tools/threads/insights.js";
23
- import { registerThreadsMentionsTools } from "./tools/threads/mentions.js";
24
- // Resources & Prompts
25
- import { registerInstagramResources } from "./resources/instagram.js";
26
- import { registerThreadsResources } from "./resources/threads.js";
27
- import { registerPrompts } from "./prompts/index.js";
10
+ import { registerAll } from "./register-all.js";
11
+ import { setupFatalErrorHandlers, setupShutdownHandlers } from "./shutdown.js";
12
+ import { createMcpLogger } from "./utils/logger.js";
28
13
  const require = createRequire(import.meta.url);
29
14
  const { version: SERVER_VERSION } = require("../package.json");
30
- const server = new McpServer({
31
- name: "meta-mcp",
32
- version: SERVER_VERSION,
33
- });
34
- let config;
35
- try {
36
- config = loadConfig();
37
- }
38
- catch (err) {
39
- console.error(err instanceof Error ? err.message : String(err));
40
- process.exit(1);
15
+ const SERVER_INSTRUCTIONS = [
16
+ "Meta MCP server for managing Instagram and Threads via the Meta Graph API.",
17
+ "Instagram tools require INSTAGRAM_ACCESS_TOKEN and INSTAGRAM_USER_ID; Threads tools require THREADS_ACCESS_TOKEN and THREADS_USER_ID.",
18
+ "Token-rotation tools (meta_exchange_token, meta_refresh_token) additionally need META_APP_ID and META_APP_SECRET.",
19
+ "Most publishing tools follow a two-step flow internally: create a container, wait for processing (up to 30s for images, up to 5 minutes for videos), then publish — exposed as a single MCP tool call.",
20
+ "When the client sets a progressToken on a publish call, the server emits notifications/progress events while polling container status.",
21
+ "Tool responses include a _rateLimit field when the Meta API returns rate-limit headers; check it to throttle subsequent calls.",
22
+ ].join(" ");
23
+ // Server is built before the client so the client can be handed a logger that
24
+ // emits to the MCP `notifications/message` channel. `capabilities.logging`
25
+ // must be declared or sendLoggingMessage is a silent no-op (#62).
26
+ function buildServer(config) {
27
+ const server = new McpServer({
28
+ name: "meta-mcp",
29
+ version: SERVER_VERSION,
30
+ }, {
31
+ instructions: SERVER_INSTRUCTIONS,
32
+ capabilities: { logging: {} },
33
+ });
34
+ const client = new MetaClient(config, { logger: createMcpLogger(server, "meta-client") });
35
+ registerAll(server, client);
36
+ return server;
41
37
  }
42
- const client = new MetaClient(config);
43
- // Register tools
44
- registerMetaAuthTools(server, client);
45
- registerIgPublishingTools(server, client);
46
- registerIgMediaTools(server, client);
47
- registerIgCommentTools(server, client);
48
- registerIgProfileTools(server, client);
49
- registerIgHashtagTools(server, client);
50
- registerIgMentionTools(server, client);
51
- registerIgMessagingTools(server, client);
52
- registerThreadsPublishingTools(server, client);
53
- registerThreadsMediaTools(server, client);
54
- registerThreadsReplyTools(server, client);
55
- registerThreadsProfileTools(server, client);
56
- registerThreadsInsightTools(server, client);
57
- registerThreadsMentionsTools(server, client);
58
- // Register resources
59
- registerInstagramResources(server, client);
60
- registerThreadsResources(server, client);
61
- // Register prompts
62
- registerPrompts(server);
63
38
  async function main() {
64
- const transport = new StdioServerTransport();
65
- await server.connect(transport);
39
+ let config;
40
+ let httpConfig = null;
41
+ try {
42
+ config = loadConfig();
43
+ const kind = (process.env.MCP_TRANSPORT ?? "stdio").trim().toLowerCase();
44
+ if (kind === "http") {
45
+ httpConfig = parseHttpTransportConfig(process.env);
46
+ }
47
+ else if (kind !== "stdio") {
48
+ throw new Error(`MCP_TRANSPORT must be "stdio" or "http" (got "${kind}")`);
49
+ }
50
+ }
51
+ catch (err) {
52
+ console.error(err instanceof Error ? err.message : String(err));
53
+ process.exit(1);
54
+ }
55
+ if (httpConfig) {
56
+ const handle = await startHttpTransport({
57
+ ...httpConfig,
58
+ createServer: () => buildServer(config),
59
+ });
60
+ setupShutdownHandlers(handle);
61
+ }
62
+ else {
63
+ const server = buildServer(config);
64
+ await server.connect(new StdioServerTransport());
65
+ setupShutdownHandlers(server);
66
+ }
66
67
  }
67
- main().catch((err) => {
68
- console.error("Fatal error:", err);
69
- process.exit(1);
70
- });
71
68
  // ── Smithery Sandbox ──
72
69
  export function createSandboxServer() {
73
- const sandbox = new McpServer({
74
- name: "meta-mcp",
75
- version: SERVER_VERSION,
76
- });
77
70
  const mockConfig = {
78
71
  appId: "",
79
72
  appSecret: "",
@@ -82,24 +75,26 @@ export function createSandboxServer() {
82
75
  threadsAccessToken: "",
83
76
  threadsUserId: "",
84
77
  };
85
- const mockClient = new MetaClient(mockConfig);
86
- registerMetaAuthTools(sandbox, mockClient);
87
- registerIgPublishingTools(sandbox, mockClient);
88
- registerIgMediaTools(sandbox, mockClient);
89
- registerIgCommentTools(sandbox, mockClient);
90
- registerIgProfileTools(sandbox, mockClient);
91
- registerIgHashtagTools(sandbox, mockClient);
92
- registerIgMentionTools(sandbox, mockClient);
93
- registerIgMessagingTools(sandbox, mockClient);
94
- registerThreadsPublishingTools(sandbox, mockClient);
95
- registerThreadsMediaTools(sandbox, mockClient);
96
- registerThreadsReplyTools(sandbox, mockClient);
97
- registerThreadsProfileTools(sandbox, mockClient);
98
- registerThreadsInsightTools(sandbox, mockClient);
99
- registerThreadsMentionsTools(sandbox, mockClient);
100
- registerInstagramResources(sandbox, mockClient);
101
- registerThreadsResources(sandbox, mockClient);
102
- registerPrompts(sandbox);
103
- return sandbox;
78
+ return buildServer(mockConfig);
79
+ }
80
+ // Guard keeps `import { createSandboxServer }` and test imports side-effect-free —
81
+ // without it, importing this module would always run main() and connect stdio.
82
+ function isInvokedAsCli() {
83
+ const entry = process.argv[1];
84
+ if (!entry)
85
+ return false;
86
+ try {
87
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ if (isInvokedAsCli()) {
94
+ setupFatalErrorHandlers();
95
+ main().catch((err) => {
96
+ console.error("Fatal error:", err);
97
+ process.exit(1);
98
+ });
104
99
  }
105
100
  //# sourceMappingURL=index.js.map