@exileum/meta-mcp 7.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 (96) hide show
  1. package/README.md +31 -7
  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 +76 -26
  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/resources/instagram.d.ts.map +1 -1
  18. package/dist/resources/instagram.js +13 -4
  19. package/dist/resources/instagram.js.map +1 -1
  20. package/dist/resources/threads.d.ts.map +1 -1
  21. package/dist/resources/threads.js +13 -4
  22. package/dist/resources/threads.js.map +1 -1
  23. package/dist/services/meta-client.d.ts +22 -3
  24. package/dist/services/meta-client.d.ts.map +1 -1
  25. package/dist/services/meta-client.js +132 -6
  26. package/dist/services/meta-client.js.map +1 -1
  27. package/dist/shutdown.d.ts +18 -0
  28. package/dist/shutdown.d.ts.map +1 -0
  29. package/dist/shutdown.js +60 -0
  30. package/dist/shutdown.js.map +1 -0
  31. package/dist/tools/instagram/comments.d.ts.map +1 -1
  32. package/dist/tools/instagram/comments.js +14 -19
  33. package/dist/tools/instagram/comments.js.map +1 -1
  34. package/dist/tools/instagram/hashtags.d.ts +1 -0
  35. package/dist/tools/instagram/hashtags.d.ts.map +1 -1
  36. package/dist/tools/instagram/hashtags.js +19 -19
  37. package/dist/tools/instagram/hashtags.js.map +1 -1
  38. package/dist/tools/instagram/media.d.ts.map +1 -1
  39. package/dist/tools/instagram/media.js +8 -11
  40. package/dist/tools/instagram/media.js.map +1 -1
  41. package/dist/tools/instagram/mentions.d.ts.map +1 -1
  42. package/dist/tools/instagram/mentions.js +2 -7
  43. package/dist/tools/instagram/mentions.js.map +1 -1
  44. package/dist/tools/instagram/messaging.d.ts.map +1 -1
  45. package/dist/tools/instagram/messaging.js +10 -20
  46. package/dist/tools/instagram/messaging.js.map +1 -1
  47. package/dist/tools/instagram/profile.d.ts +2 -0
  48. package/dist/tools/instagram/profile.d.ts.map +1 -1
  49. package/dist/tools/instagram/profile.js +13 -15
  50. package/dist/tools/instagram/profile.js.map +1 -1
  51. package/dist/tools/instagram/publishing.d.ts.map +1 -1
  52. package/dist/tools/instagram/publishing.js +133 -96
  53. package/dist/tools/instagram/publishing.js.map +1 -1
  54. package/dist/tools/meta/auth.d.ts.map +1 -1
  55. package/dist/tools/meta/auth.js +18 -2
  56. package/dist/tools/meta/auth.js.map +1 -1
  57. package/dist/tools/threads/insights.d.ts.map +1 -1
  58. package/dist/tools/threads/insights.js +2 -5
  59. package/dist/tools/threads/insights.js.map +1 -1
  60. package/dist/tools/threads/media.d.ts.map +1 -1
  61. package/dist/tools/threads/media.js +5 -47
  62. package/dist/tools/threads/media.js.map +1 -1
  63. package/dist/tools/threads/mentions.d.ts.map +1 -1
  64. package/dist/tools/threads/mentions.js +3 -10
  65. package/dist/tools/threads/mentions.js.map +1 -1
  66. package/dist/tools/threads/profile.d.ts +2 -0
  67. package/dist/tools/threads/profile.d.ts.map +1 -1
  68. package/dist/tools/threads/profile.js +10 -1
  69. package/dist/tools/threads/profile.js.map +1 -1
  70. package/dist/tools/threads/publishing.d.ts.map +1 -1
  71. package/dist/tools/threads/publishing.js +122 -103
  72. package/dist/tools/threads/publishing.js.map +1 -1
  73. package/dist/tools/threads/replies.d.ts.map +1 -1
  74. package/dist/tools/threads/replies.js +15 -23
  75. package/dist/tools/threads/replies.js.map +1 -1
  76. package/dist/utils/container.d.ts +16 -4
  77. package/dist/utils/container.d.ts.map +1 -1
  78. package/dist/utils/container.js +36 -6
  79. package/dist/utils/container.js.map +1 -1
  80. package/dist/utils/errors.d.ts +23 -3
  81. package/dist/utils/errors.d.ts.map +1 -1
  82. package/dist/utils/errors.js +32 -2
  83. package/dist/utils/errors.js.map +1 -1
  84. package/dist/utils/logger.d.ts +28 -0
  85. package/dist/utils/logger.d.ts.map +1 -0
  86. package/dist/utils/logger.js +32 -0
  87. package/dist/utils/logger.js.map +1 -0
  88. package/dist/utils/params.d.ts +3 -0
  89. package/dist/utils/params.d.ts.map +1 -0
  90. package/dist/utils/params.js +14 -0
  91. package/dist/utils/params.js.map +1 -0
  92. package/dist/utils/progress.d.ts +42 -0
  93. package/dist/utils/progress.d.ts.map +1 -0
  94. package/dist/utils/progress.js +35 -0
  95. package/dist/utils/progress.js.map +1 -0
  96. package/package.json +3 -2
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 |
@@ -93,6 +109,10 @@ The server validates these at startup. Malformed values for `INSTAGRAM_USER_ID`,
93
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
94
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)
95
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)
96
116
 
97
117
  ## Tools
98
118
 
@@ -230,14 +250,14 @@ The server validates these at startup. Malformed values for `INSTAGRAM_USER_ID`,
230
250
  | Resource URI | Description |
231
251
  |-------------|-------------|
232
252
  | `meta-mcp://instagram/profile` | Instagram account profile data |
233
- | `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) |
234
254
 
235
255
  ## Prompts
236
256
 
237
- | Prompt | Description |
238
- |--------|-------------|
239
- | `content_publish` | Cross-post content to Instagram and Threads |
240
- | `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`) |
241
261
 
242
262
  ## Setup Guide
243
263
 
@@ -333,11 +353,15 @@ Access tokens expire after ~60 days. Refresh before expiration (token must be at
333
353
  &access_token=CURRENT_LONG_LIVED_TOKEN
334
354
  ```
335
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
+
336
358
  Check token status anytime with `meta_debug_token`.
337
359
 
338
360
  ## Troubleshooting
339
361
 
340
- 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.
341
365
 
342
366
  ### `error_type: "auth"` — expired, revoked, or under-scoped token
343
367
 
@@ -362,7 +386,7 @@ Triggered by Meta API codes `4`, `17`, `32`, `341`, `613`, the business-use-case
362
386
  What to do:
363
387
 
364
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.
365
- 2. meta-mcp already self-throttles once `max(callCount, totalCpuTime, totalTime)` crosses 80% (1s slowdown) or 90% (5s backoff) — see the `[meta-mcp] x-app-usage at N%…` lines on stderr. If you are still hitting `rate_limit` errors despite that, reduce request volume further and cache profile metadata between calls.
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.
366
390
  3. Threads has hard daily quotas (250 publishes, 100 deletes) — query the remaining quota with `threads_get_publishing_limit` before bulk operations.
367
391
 
368
392
  ### `error_type: "validation"` — bad parameter, wrong ID, or unsupported field
@@ -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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqCpE,wBAAgB,mBAAmB,cAmBlC"}
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,40 +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
10
  import { registerAll } from "./register-all.js";
11
+ import { setupFatalErrorHandlers, setupShutdownHandlers } from "./shutdown.js";
12
+ import { createMcpLogger } from "./utils/logger.js";
8
13
  const require = createRequire(import.meta.url);
9
14
  const { version: SERVER_VERSION } = require("../package.json");
10
- const server = new McpServer({
11
- name: "meta-mcp",
12
- version: SERVER_VERSION,
13
- });
14
- let config;
15
- try {
16
- config = loadConfig();
17
- }
18
- catch (err) {
19
- console.error(err instanceof Error ? err.message : String(err));
20
- 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;
21
37
  }
22
- const client = new MetaClient(config);
23
- registerAll(server, client);
24
38
  async function main() {
25
- const transport = new StdioServerTransport();
26
- 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
+ }
27
67
  }
28
- main().catch((err) => {
29
- console.error("Fatal error:", err);
30
- process.exit(1);
31
- });
32
68
  // ── Smithery Sandbox ──
33
69
  export function createSandboxServer() {
34
- const sandbox = new McpServer({
35
- name: "meta-mcp",
36
- version: SERVER_VERSION,
37
- });
38
70
  const mockConfig = {
39
71
  appId: "",
40
72
  appSecret: "",
@@ -43,8 +75,26 @@ export function createSandboxServer() {
43
75
  threadsAccessToken: "",
44
76
  threadsUserId: "",
45
77
  };
46
- const mockClient = new MetaClient(mockConfig);
47
- registerAll(sandbox, mockClient);
48
- 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
+ });
49
99
  }
50
100
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAEtF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,cAAc;CACxB,CAAC,CAAC;AAEH,IAAI,MAAkB,CAAC;AACvB,IAAI,CAAC;IACH,MAAM,GAAG,UAAU,EAAE,CAAC;AACxB,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AAEtC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,yBAAyB;AAEzB,MAAM,UAAU,mBAAmB;IACjC,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC;QAC5B,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,cAAc;KACxB,CAAC,CAAC;IAEH,MAAM,UAAU,GAAe;QAC7B,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE;QACb,oBAAoB,EAAE,EAAE;QACxB,eAAe,EAAE,EAAE;QACnB,kBAAkB,EAAE,EAAE;QACtB,aAAa,EAAE,EAAE;KAClB,CAAC;IACF,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IAE9C,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEjC,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,59 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ /**
4
+ * Tool names referenced by prompts. Cross-checked against the registered tool
5
+ * list in `src/prompts/index.test.ts` — if any tool is renamed/removed/split,
6
+ * the test fails and forces both the registration site and the prompt to
7
+ * update together. `ig_publish_video` is deliberately omitted: it's deprecated
8
+ * (CHANGELOG v4.0.0 / #118) and the prompt steers callers to
9
+ * `ig_publish_reel` for video content instead.
10
+ */
11
+ export declare const PROMPT_TOOL_NAMES: {
12
+ readonly IG_PUBLISH_PHOTO: "ig_publish_photo";
13
+ readonly IG_PUBLISH_REEL: "ig_publish_reel";
14
+ readonly IG_PUBLISH_CAROUSEL: "ig_publish_carousel";
15
+ readonly THREADS_PUBLISH_TEXT: "threads_publish_text";
16
+ readonly THREADS_PUBLISH_IMAGE: "threads_publish_image";
17
+ readonly THREADS_PUBLISH_VIDEO: "threads_publish_video";
18
+ readonly THREADS_PUBLISH_CAROUSEL: "threads_publish_carousel";
19
+ readonly IG_GET_PROFILE: "ig_get_profile";
20
+ readonly IG_GET_ACCOUNT_INSIGHTS: "ig_get_account_insights";
21
+ readonly IG_GET_MEDIA_LIST: "ig_get_media_list";
22
+ readonly THREADS_GET_PROFILE: "threads_get_profile";
23
+ readonly THREADS_GET_USER_INSIGHTS: "threads_get_user_insights";
24
+ readonly THREADS_GET_POSTS: "threads_get_posts";
25
+ };
26
+ export declare const contentPublishArgsSchema: {
27
+ platform: z.ZodOptional<z.ZodEnum<{
28
+ instagram: "instagram";
29
+ threads: "threads";
30
+ both: "both";
31
+ }>>;
32
+ content_type: z.ZodOptional<z.ZodEnum<{
33
+ text: "text";
34
+ image: "image";
35
+ video: "video";
36
+ carousel: "carousel";
37
+ }>>;
38
+ media_url: z.ZodOptional<z.ZodString>;
39
+ caption: z.ZodOptional<z.ZodString>;
40
+ };
41
+ export declare const analyticsReportArgsSchema: {
42
+ platform: z.ZodOptional<z.ZodEnum<{
43
+ instagram: "instagram";
44
+ threads: "threads";
45
+ both: "both";
46
+ }>>;
47
+ time_range: z.ZodOptional<z.ZodEnum<{
48
+ "7d": "7d";
49
+ "30d": "30d";
50
+ "90d": "90d";
51
+ }>>;
52
+ focus: z.ZodOptional<z.ZodEnum<{
53
+ content: "content";
54
+ engagement: "engagement";
55
+ growth: "growth";
56
+ }>>;
57
+ };
2
58
  export declare function registerPrompts(server: McpServer): void;
3
59
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAuEhD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;CAcpB,CAAC;AAEX,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;CAkBpC,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;CAerC,CAAC;AAsQF,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAwChD"}