@codingmantra/mcp-server 0.2.2 β†’ 0.3.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 (3) hide show
  1. package/README.md +51 -3
  2. package/build/index.js +205 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  The official **Model Context Protocol (MCP)** server connecting AI assistants (Claude Desktop, Cursor, Cline, and Antigravity) to CodingMantra's generative AI media engines.
4
4
 
5
- ## What's New in v0.2.2
5
+ ## What's New in v0.3.0
6
+ - **Social media workflows:** 15 tools for connected accounts, caption creation, drafts, gallery images, immediate publishing, scheduling, status, cancellation, and analytics. Uses the same CodingMantra API key.
7
+
6
8
  - **Current API coverage:** Story Teller, HeyGen Talking Avatar IV, and Jewellery Rate Poster tools.
7
9
  - **Video contract alignment:** Required prompt/model/duration/resolution/aspect ratio, character descriptions, seed, guidance, and local reference-image resolution.
8
10
  - **Terminal job handling:** Polling stops for completed, failed, or cancelled jobs.
9
11
 
10
12
  - πŸ–ΌοΈ **Native MCP Image Output**: Generated images (including QR codes, logos, try-on shots, and product photos) return standard MCP `ImageContent` so they render directly in your AI assistant's chat window.
11
13
  - πŸ“ **Local File Path Resolution**: Pass local paths (e.g. `./shoe.png` or `/Users/.../portrait.jpg`) directly in tool argumentsβ€”the server automatically reads and encodes them as base64 data URIs.
12
- - ⚑ **Expanded Tool Suite (28 Tools)**: Expanded image generation support including Passport Photos, Baby Milestones, YouTube Thumbnails, Blog Images, Nano Banana, Professional Photography, Sketch-to-Real, Human Builder, Apparel/Product/Jewelry Try-On, Festival Posts, Upscaling, and Discovery.
14
+ - ⚑ **Expanded Tool Suite (43 Tools)**: Expanded image generation support including Passport Photos, Baby Milestones, YouTube Thumbnails, Blog Images, Nano Banana, Professional Photography, Sketch-to-Real, Human Builder, Apparel/Product/Jewelry Try-On, Festival Posts, Upscaling, and Discovery.
13
15
  - 🎯 **Advanced Model & Resolution Parameters**: Direct support for `model`, `modelId`, `promptId`, custom reference images, and server-side credit synchronization.
14
16
  - πŸ›‘οΈ **Graceful Error Handling & 120s Timeout**: Informative error diagnostics when API keys are missing or requests time out, with clean stdio protocol execution.
15
17
 
@@ -17,7 +19,7 @@ The official **Model Context Protocol (MCP)** server connecting AI assistants (C
17
19
 
18
20
  ## Features & Tool Suite
19
21
 
20
- The server exposes **28 specialized generative and utility tools** over standard I/O (`stdio`):
22
+ The server exposes **43 specialized generative and utility tools** over standard I/O (`stdio`):
21
23
 
22
24
  ### 1. Synchronous Image Generation (Immediate Inline Image Output)
23
25
  - **`generate_product_photo`** β€” Generates studio-grade commercial product photos with custom background scenes, ambient occlusion contact shadows, camera optics, resolutions (1K, 2K, 4K), aspect ratios, and dynamic logo/text overlays.
@@ -60,6 +62,52 @@ All generations automatically deduct credits from your CodingMantra wallet and p
60
62
 
61
63
  ---
62
64
 
65
+ ### 5. Social Content & Scheduling
66
+
67
+ - **`get_social_workflow`** β€” Capabilities, workflow, content guidance, and platform limits.
68
+ - **`list_social_accounts`** β€” Connected Facebook Pages and Instagram accounts; no provider tokens.
69
+ - **`generate_social_caption`** β€” Generate or refine a caption and hashtags using images, brandBrief, and saved brand context. Uses credits.
70
+ - **`list_social_drafts`** β€” Paginated saved drafts (20 per page).
71
+ - **`save_social_draft`** β€” Create or fully replace a draft; uploads local images. Does not schedule or publish.
72
+ - **`delete_social_draft`** β€” Remove a saved draft.
73
+ - **`list_social_media`** β€” Paginate your generated image gallery.
74
+ - **`schedule_social_post`** β€” Queue final content for automatic future delivery to selected accounts.
75
+ - **`list_social_posts`** β€” Filter the posting calendar by status, platform, and date range.
76
+ - **`get_social_post`** β€” Inspect status, errors, retries, and published job IDs.
77
+ - **`update_social_post`** β€” Edit or reschedule a queued post; retry a failed post with a new future time.
78
+ - **`cancel_social_post`** β€” Stop a queued post before publication starts.
79
+ - **`delete_social_post`** β€” Delete an eligible schedule record; does not delete a live platform post.
80
+ - **`publish_social_post`** β€” Post immediately to one connected account.
81
+ - **`get_social_analytics`** β€” Recorded reach, engagement, totals, top posts, and posting activity.
82
+
83
+ #### Working with Claude, Codex, Antigravity, and other assistants
84
+
85
+ Connect accounts once in the [Social Media Hub](https://codingmantra.com/tools/digital-marketing/social-media-hub). The same `CODINGMANTRA_API_KEY` used for media generation identifies the owner of all drafts and schedules; browser cookies and Meta access tokens are not needed in your assistant. Account connection/reconnection remains in the browser. Any assistant supporting standard MCP stdio can discover the tools and their schemas. Responses include text plus structured JSON, UTC ISO timestamps, IDs, and pagination fields.
86
+
87
+ Try: β€œCreate three Facebook posts for my bakery’s launch. Use a warm voice and these verified opening hours. Save drafts for review, then schedule the approved posts at 9 AM Asia/Kolkata next week.” Or: β€œShow failed posts, explain their errors, and move this queued post to tomorrow at 6 PM Asia/Kolkata.”
88
+
89
+ Start with `get_social_workflow` and `list_social_accounts`. Use exact returned account IDs. Supply `brandBrief` to isolate a particular brand; otherwise caption generation uses saved brand context. Provide audience, verified facts, tone, offer, and CTA. Combine caption generation with existing image tools, or write captions directly in your assistant without AI credit charges.
90
+
91
+ Instagram supports **exactly one image**, with at most **2,200 characters including hashtags**. Facebook supports text and up to 10 images, with 63,206 characters including hashtags. Video/reel publishing and other networks are not implemented. Generated images may use local file paths; the server resolves them and the backend persists them when drafts or schedules are saved.
92
+
93
+ Scheduling requires `platforms`, `content.caption` (may be empty if there is an image), and a future `scheduledFor` ISO timestamp with an explicit UTC offset. Do not guess an ambiguous timezone. Use `draftId` when queuing a saved draft: the backend atomically consumes it and repeated requests return the same schedule. Immediate publishing has no idempotency guarantee; inspect publication state before retrying a timeout. Successful schedules publish on the backend cron worker even when the assistant is offline.
94
+
95
+ ```json
96
+ {
97
+ "draftId": "returned_draft_id",
98
+ "platforms": [{ "platform": "facebook", "accountId": "returned_page_id", "accountName": "My Bakery" }],
99
+ "content": {
100
+ "caption": "Our opening day is almost here. Join us for fresh bread!",
101
+ "hashtags": ["bakery", "freshbread"],
102
+ "mediaUrls": ["https://example.com/my-generated-image.png"]
103
+ },
104
+ "scheduledFor": "2026-10-10T09:00:00+05:30"
105
+ }
106
+ ```
107
+
108
+ Deployment order: deploy the updated web API, verify the cron worker, then release/use MCP v0.3.0. The backend must support bearer API keys; older browser-session-only endpoints return 401. For REST examples and the route map, see the repository’s `docs/social-mcp-api.md`.
109
+
110
+
63
111
  ## Video parameters
64
112
 
65
113
  General video tools require `prompt` (10–5000 characters), `model`, `timeDuration`, `resolution`, and `aspectRatio`. Use model-supported settings; Veo requires 8 seconds for 1080p, references, or an end frame. Choose one image mode: main image, frames, or references. Reference images accept URLs, data URIs, or local paths (up to 3 for Veo). Optional settings include characters, seed, guidanceScale, soundEffects, and styleAmbiance.
package/build/index.js CHANGED
@@ -2,12 +2,14 @@
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
5
+ import { AjvJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/ajv";
5
6
  import { apiClient } from './api-client.js';
6
7
  import { formatImageToolResult, resolveImageInput, resolveImagesArray } from './image-utils.js';
7
8
  const server = new Server({
8
9
  name: "codingmantra-mcp",
9
- version: "0.2.2",
10
+ version: "0.3.0",
10
11
  }, {
12
+ instructions: "For social media, start with get_social_workflow and list_social_accounts. Create captions and images, save drafts for review, then schedule or publish according to the user’s request. Use exact connected account IDs and explicit timezone offsets. Only Facebook Pages and Instagram image posts are supported. Keep social provider tokens on the backend.",
11
13
  capabilities: {
12
14
  tools: {},
13
15
  },
@@ -942,10 +944,209 @@ const CHECK_JOB_STATUS_TOOL = {
942
944
  required: ["jobId"]
943
945
  }
944
946
  };
947
+ // Social tools share the same API key as media generation; social tokens stay on the server.
948
+ const SOCIAL_ACCOUNT_SCHEMA = {
949
+ type: 'object', properties: {
950
+ platform: { type: 'string', enum: ['facebook', 'instagram'], description: 'Connected social platform.' },
951
+ accountId: { type: 'string', minLength: 1, description: 'Exact account ID returned by list_social_accounts.' },
952
+ accountName: { type: 'string', minLength: 1, description: 'Account display name returned by list_social_accounts.' },
953
+ }, required: ['platform', 'accountId', 'accountName'],
954
+ };
955
+ const SOCIAL_CONTENT_PROPERTIES = {
956
+ caption: { type: 'string', maxLength: 63206, description: 'Post text. Instagram caption plus hashtags must fit 2200 characters.' },
957
+ brandBrief: { type: 'string', maxLength: 4000, description: 'Brand name, audience, voice, offer, facts, and call to action for this post.' },
958
+ hashtags: { type: 'array', maxItems: 30, items: { type: 'string', description: 'Hashtag with or without a leading #.' }, description: 'Relevant hashtags; duplicates already in the caption are omitted during publication.' },
959
+ mediaUrls: { type: 'array', maxItems: 10, items: { type: 'string', description: 'HTTPS image URL, PNG/JPEG/WebP data URI, or local image path.' }, description: 'Images only. Instagram currently requires exactly one image; Facebook accepts up to 10.' },
960
+ };
961
+ const SOCIAL_SCHEDULE_PROPERTIES = {
962
+ draftId: { type: 'string', description: 'Optional saved draft ID. Scheduling consumes it atomically; repeating this request returns the same scheduled post.' },
963
+ socialContentId: { type: 'string', description: 'Optional media library content ID for tracking.' },
964
+ platforms: { type: 'array', minItems: 1, maxItems: 20, items: SOCIAL_ACCOUNT_SCHEMA, description: 'Connected accounts to publish to; select each account once.' },
965
+ content: { type: 'object', properties: SOCIAL_CONTENT_PROPERTIES, required: ['caption'], description: 'Final post caption, hashtags, brand brief, and images.' },
966
+ scheduledFor: { type: 'string', format: 'date-time', description: 'Future ISO 8601 timestamp with explicit UTC offset, e.g. 2026-10-10T09:00:00+05:30. Resolve the user’s timezone before scheduling.' },
967
+ };
968
+ const SOCIAL_ID = { type: 'string', minLength: 1, pattern: '^[A-Za-z0-9_-]+$', description: 'Exact draft or scheduled post ID returned by a social tool.' };
969
+ const socialRead = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
970
+ const socialWrite = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
971
+ const socialDelete = { ...socialWrite, destructiveHint: true, idempotentHint: true };
972
+ const SOCIAL_TOOLS = [
973
+ { name: 'get_social_workflow', description: 'Read the social workflow, capabilities, timezone guidance, and platform restrictions before planning content or posting. Read-only; no API call.', annotations: { ...socialRead, openWorldHint: false }, inputSchema: { type: 'object', properties: {} } },
974
+ { name: 'list_social_accounts', description: 'List connected Facebook Pages and Instagram accounts with IDs, names, permissions, and reconnect status. Read-only; OAuth tokens are never returned. Connect accounts in the web hub first.', annotations: socialRead, inputSchema: { type: 'object', properties: {} } },
975
+ { name: 'generate_social_caption', description: 'Synchronously create or refine a caption and hashtags from a brief or images. Uses wallet credits and saved brand context unless brandBrief is supplied. Does not publish. Include verified facts, audience, tone, and CTA in the brief; review the result.', annotations: socialWrite, inputSchema: { type: 'object', properties: { ...SOCIAL_CONTENT_PROPERTIES, platform: { type: 'string', enum: ['facebook', 'instagram'], description: 'Target platform; defaults to Instagram.' } } } },
976
+ { name: 'list_social_drafts', description: 'Read saved drafts, including captions, images, account selections, and proposed dates. Returns 20 per page; use offset for pagination.', annotations: socialRead, inputSchema: { type: 'object', properties: { offset: { type: 'integer', minimum: 0, description: 'Number of drafts to skip.' } } } },
977
+ { name: 'save_social_draft', description: 'Save a new draft or replace an existing draft by id. Does not queue or publish. Send the complete draft when editing; omitted fields use API defaults. Local images are uploaded and persisted.', annotations: socialWrite, inputSchema: { type: 'object', properties: { ...SOCIAL_CONTENT_PROPERTIES, id: SOCIAL_ID, platforms: { type: 'array', items: SOCIAL_ACCOUNT_SCHEMA, description: 'Optional target accounts; drafts may have no accounts yet.' }, scheduledFor: { type: ['string', 'null'], format: 'date-time', description: 'Optional proposed date with explicit offset; saving does not schedule.' }, socialContentId: { type: 'string', description: 'Optional media library content ID.' } } } },
978
+ { name: 'delete_social_draft', description: 'Delete one saved draft belonging to the API-key owner. Does not delete a scheduled or published post.', annotations: socialDelete, inputSchema: { type: 'object', properties: { id: SOCIAL_ID }, required: ['id'] } },
979
+ { name: 'list_social_media', description: 'Read generated images from your gallery for use in posts. Returns public media URLs; use existing image generation tools to create new visuals.', annotations: socialRead, inputSchema: { type: 'object', properties: { page: { type: 'integer', minimum: 1, description: 'Gallery page (default 1).' }, limit: { type: 'integer', minimum: 1, maximum: 50, description: 'Images per page (default 24).' } } } },
980
+ { name: 'schedule_social_post', description: 'Queue a final post for automatic future publication to connected accounts. Returns a scheduled post ID. ISO timestamps require a UTC offset; the server scheduler publishes even when this assistant is offline. Use draftId to avoid duplicate draft scheduling.', annotations: socialWrite, inputSchema: { type: 'object', properties: SOCIAL_SCHEDULE_PROPERTIES, required: ['platforms', 'content', 'scheduledFor'] } },
981
+ { name: 'list_social_posts', description: 'Read scheduled, published, failed, or cancelled posts. Filter by platform, status, or date range; use limit/offset for pagination. Dates must include a UTC offset.', annotations: socialRead, inputSchema: { type: 'object', properties: {
982
+ status: { type: 'string', enum: ['scheduled', 'published', 'failed', 'cancelled'], description: 'Post status filter.' },
983
+ platform: { type: 'string', enum: ['facebook', 'instagram'], description: 'Platform filter.' },
984
+ fromDate: { type: 'string', format: 'date-time', description: 'Inclusive start timestamp with offset.' },
985
+ toDate: { type: 'string', format: 'date-time', description: 'Inclusive end timestamp with offset.' },
986
+ limit: { type: 'integer', minimum: 1, maximum: 500, description: 'Page size, default 50.' },
987
+ offset: { type: 'integer', minimum: 0, description: 'Number of posts to skip.' },
988
+ } } },
989
+ { name: 'get_social_post', description: 'Read one scheduled post, including status, error, retries, and published job IDs. Check this before retrying a failed or ambiguous publication.', annotations: socialRead, inputSchema: { type: 'object', properties: { id: SOCIAL_ID }, required: ['id'] } },
990
+ { name: 'update_social_post', description: 'Edit a pending schedule, content, or target accounts. Provide a new future scheduledFor to reschedule or retry a failed post. The API rejects changes to posts that are publishing or already published.', annotations: { ...socialWrite, idempotentHint: true }, inputSchema: { type: 'object', properties: { id: SOCIAL_ID, content: { type: 'object', properties: SOCIAL_CONTENT_PROPERTIES, description: 'Fields to change; omitted fields retain their existing values.' }, platforms: SOCIAL_SCHEDULE_PROPERTIES.platforms, scheduledFor: SOCIAL_SCHEDULE_PROPERTIES.scheduledFor }, required: ['id'] } },
991
+ { name: 'cancel_social_post', description: 'Cancel a queued post so it will not publish. The API rejects cancellation while publishing or after publication. Does not remove an already published platform post.', annotations: socialDelete, inputSchema: { type: 'object', properties: { id: SOCIAL_ID }, required: ['id'] } },
992
+ { name: 'delete_social_post', description: 'Remove a schedule record. The API prevents deletion during publishing or after publication. Does not delete a live Facebook or Instagram post.', annotations: socialDelete, inputSchema: { type: 'object', properties: { id: SOCIAL_ID }, required: ['id'] } },
993
+ { name: 'publish_social_post', description: 'Publish immediately to one connected Facebook Page or Instagram account when the user requests posting now. Returns the platform publication result. Images only; Instagram requires exactly one image. Avoid blind retries after timeouts because a post may already be live.', annotations: { ...socialWrite, destructiveHint: true }, inputSchema: { type: 'object', properties: { ...SOCIAL_CONTENT_PROPERTIES, platform: { type: 'string', enum: ['facebook', 'instagram'], description: 'Platform to publish to.' }, accountId: { type: 'string', minLength: 1, description: 'Exact connected account ID from list_social_accounts.' }, socialContentId: { type: 'string', description: 'Optional media library content ID for publication tracking.' }, link: { type: 'string', description: 'Optional HTTPS link for a Facebook post.' } }, required: ['platform', 'accountId', 'caption'] } },
994
+ { name: 'get_social_analytics', description: 'Read recorded publishing totals, reach, engagement, top posts, and posting activity. Metrics reflect stored provider data and may be incomplete; these are not guaranteed optimal posting times.', annotations: socialRead, inputSchema: { type: 'object', properties: { range: { type: 'string', enum: ['7d', '30d', '90d'], description: 'Reporting range, default 30d.' }, platform: { type: 'string', enum: ['facebook', 'instagram'], description: 'Optional platform filter.' }, accountId: { type: 'string', description: 'Optional connected account filter.' } } } },
995
+ ];
996
+ const socialSchemaValidator = new AjvJsonSchemaValidator();
997
+ const socialValidators = new Map(SOCIAL_TOOLS.map(tool => [tool.name, socialSchemaValidator.getValidator(tool.inputSchema)]));
998
+ function normalizeSocialDates(value) {
999
+ if (Array.isArray(value))
1000
+ return value.map(normalizeSocialDates);
1001
+ if (value && typeof value === 'object') {
1002
+ const object = value;
1003
+ const seconds = object._seconds ?? object.seconds;
1004
+ if (typeof seconds === 'number' && Object.keys(object).every(key => ['_seconds', '_nanoseconds', 'seconds', 'nanoseconds'].includes(key))) {
1005
+ return new Date(seconds * 1000).toISOString();
1006
+ }
1007
+ return Object.fromEntries(Object.entries(object).map(([key, nested]) => [key, normalizeSocialDates(nested)]));
1008
+ }
1009
+ return value;
1010
+ }
1011
+ function socialImage(value) {
1012
+ if (typeof value !== 'string')
1013
+ throw new Error('Each media image must be a URL, data URI, or local file path.');
1014
+ const resolved = resolveImageInput(value);
1015
+ if (/^data:image\/(jpeg|png|webp);base64,/i.test(resolved))
1016
+ return resolved;
1017
+ try {
1018
+ if (new URL(resolved).protocol === 'https:')
1019
+ return resolved;
1020
+ }
1021
+ catch { /* invalid source */ }
1022
+ throw new Error('Use an HTTPS image URL or a local PNG, JPEG, or WebP file.');
1023
+ }
1024
+ function socialQuery(args, allowed) {
1025
+ const query = new URLSearchParams();
1026
+ for (const key of allowed)
1027
+ if (args[key] !== undefined)
1028
+ query.set(key, String(args[key]));
1029
+ return query.size ? `?${query}` : '';
1030
+ }
1031
+ function socialId(args) {
1032
+ if (typeof args.id !== 'string' || !/^[A-Za-z0-9_-]+$/.test(args.id))
1033
+ throw new Error('Provide a valid post or draft ID.');
1034
+ return encodeURIComponent(args.id);
1035
+ }
1036
+ async function executeSocialTool(name, rawArgs) {
1037
+ const validated = socialValidators.get(name)(rawArgs);
1038
+ if (!validated.valid)
1039
+ throw new Error(`Invalid ${name} parameters: ${validated.errorMessage}`);
1040
+ const args = { ...rawArgs };
1041
+ if (args.mediaUrls !== undefined) {
1042
+ if (!Array.isArray(args.mediaUrls))
1043
+ throw new Error('mediaUrls must be an array.');
1044
+ args.mediaUrls = args.mediaUrls.map(socialImage);
1045
+ }
1046
+ if (args.content !== undefined) {
1047
+ if (!args.content || typeof args.content !== 'object' || Array.isArray(args.content))
1048
+ throw new Error('content must be a post object.');
1049
+ const content = { ...args.content };
1050
+ if (content.mediaUrls !== undefined) {
1051
+ if (!Array.isArray(content.mediaUrls))
1052
+ throw new Error('content.mediaUrls must be an array.');
1053
+ content.mediaUrls = content.mediaUrls.map(socialImage);
1054
+ }
1055
+ args.content = content;
1056
+ }
1057
+ let result;
1058
+ if (name === 'get_social_workflow') {
1059
+ result = {
1060
+ supportedPlatforms: ['facebook', 'instagram'],
1061
+ connectUrl: 'https://codingmantra.com/tools/digital-marketing/social-media-hub',
1062
+ steps: ['list_social_accounts', 'generate_social_caption or write a caption with your assistant', 'generate an image with existing MCP tools or list_social_media', 'save_social_draft for review', 'schedule_social_post or publish_social_post as requested', 'list_social_posts / get_social_post / get_social_analytics'],
1063
+ limits: { instagram: { images: 1, captionIncludingHashtags: 2200 }, facebook: { maxImages: 10, captionIncludingHashtags: 63206 }, videoPublishing: false },
1064
+ timezone: 'Ask for the user’s intended timezone when ambiguous; convert to an ISO 8601 timestamp with an explicit UTC offset. Dates are returned as ISO 8601 UTC timestamps.',
1065
+ contentGuidance: 'Use a clear hook, useful verified facts, the intended audience and brand voice, and one CTA. Supply brandBrief to generate_social_caption for per-brand context. Never invent prices, claims, discounts, or business details. Caption generation uses credits; drafts and schedules do not run AI.',
1066
+ scheduling: 'The deployed backend requires its cron scheduler to run. The assistant does not need to remain online. Saved drafts are consumed atomically; reuse draftId to make repeated scheduling requests safe.',
1067
+ retries: 'Inspect failed post errors and publishedJobIds before rescheduling. Immediate publication has no idempotency guarantee; do not automatically retry ambiguous timeouts.',
1068
+ };
1069
+ }
1070
+ else {
1071
+ let endpoint = '';
1072
+ let method = 'GET';
1073
+ let body;
1074
+ if (name === 'list_social_accounts')
1075
+ endpoint = '/social/meta/connection';
1076
+ if (name === 'generate_social_caption') {
1077
+ endpoint = '/social/caption';
1078
+ method = 'POST';
1079
+ body = { caption: args.caption || '', brandBrief: args.brandBrief || '', mediaUrls: args.mediaUrls || [], platform: args.platform || 'instagram' };
1080
+ }
1081
+ if (name === 'list_social_drafts')
1082
+ endpoint = '/social/drafts' + socialQuery(args, ['offset']);
1083
+ if (name === 'save_social_draft') {
1084
+ endpoint = '/social/drafts';
1085
+ method = 'POST';
1086
+ body = args;
1087
+ }
1088
+ if (name === 'delete_social_draft') {
1089
+ endpoint = `/social/drafts?id=${socialId(args)}`;
1090
+ method = 'DELETE';
1091
+ }
1092
+ if (name === 'list_social_media')
1093
+ endpoint = '/social/gallery' + socialQuery({ ...args, type: 'image' }, ['type', 'page', 'limit']);
1094
+ if (name === 'schedule_social_post') {
1095
+ endpoint = '/social/schedule';
1096
+ method = 'POST';
1097
+ body = args;
1098
+ }
1099
+ if (name === 'list_social_posts')
1100
+ endpoint = '/social/schedule' + socialQuery(args, ['status', 'platform', 'fromDate', 'toDate', 'limit', 'offset']);
1101
+ if (['get_social_post', 'update_social_post', 'cancel_social_post', 'delete_social_post'].includes(name)) {
1102
+ endpoint = `/social/schedule/${socialId(args)}`;
1103
+ if (name === 'update_social_post') {
1104
+ method = 'PATCH';
1105
+ const { id: _id, ...updates } = args;
1106
+ body = updates;
1107
+ }
1108
+ if (name === 'cancel_social_post') {
1109
+ method = 'PATCH';
1110
+ body = { status: 'cancelled' };
1111
+ }
1112
+ if (name === 'delete_social_post')
1113
+ method = 'DELETE';
1114
+ }
1115
+ if (name === 'publish_social_post') {
1116
+ endpoint = '/social/meta/publish';
1117
+ method = 'POST';
1118
+ const caption = typeof args.caption === 'string' ? args.caption.trim() : '';
1119
+ if (args.hashtags !== undefined && (!Array.isArray(args.hashtags) || args.hashtags.some(tag => typeof tag !== 'string' || !/^#?[\p{L}\p{N}_]+$/u.test(tag))))
1120
+ throw new Error('Use valid hashtags containing letters, numbers, or underscores.');
1121
+ const existing = new Set((caption.match(/#[\p{L}\p{N}_]+/gu) || []).map(tag => tag.toLowerCase()));
1122
+ const tags = (args.hashtags || []).map(tag => `#${tag.replace(/^#/, '')}`).filter(tag => {
1123
+ if (existing.has(tag.toLowerCase()))
1124
+ return false;
1125
+ existing.add(tag.toLowerCase());
1126
+ return true;
1127
+ });
1128
+ const text = [caption, tags.join(' ')].filter(Boolean).join('\n\n');
1129
+ const images = args.mediaUrls || [];
1130
+ if (!['facebook', 'instagram'].includes(String(args.platform)))
1131
+ throw new Error('Choose Facebook or Instagram.');
1132
+ if (args.platform === 'instagram' && images.length !== 1)
1133
+ throw new Error('Instagram requires exactly one image.');
1134
+ body = args.platform === 'instagram'
1135
+ ? { platform: 'instagram', instagramId: args.accountId, caption: text, imageDataUrl: images[0]?.startsWith('data:') ? images[0] : undefined, imageUrl: images[0]?.startsWith('https:') ? images[0] : undefined, socialContentId: args.socialContentId }
1136
+ : { platform: 'facebook', pageId: args.accountId, message: text, imageUrls: images.length ? images : undefined, link: args.link, socialContentId: args.socialContentId };
1137
+ }
1138
+ if (name === 'get_social_analytics')
1139
+ endpoint = '/social/analytics' + socialQuery(args, ['range', 'platform', 'accountId']);
1140
+ result = await apiClient(endpoint, { method, ...(body ? { body: JSON.stringify(body) } : {}) });
1141
+ }
1142
+ result = normalizeSocialDates(result);
1143
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result, ...(result.success === false ? { isError: true } : {}) };
1144
+ }
945
1145
  // ─── Tool Registration ─────────────────────────────────────────────────────────
946
1146
  server.setRequestHandler(ListToolsRequestSchema, async () => {
947
1147
  return {
948
1148
  tools: [
1149
+ ...SOCIAL_TOOLS,
949
1150
  GENERATE_JEWELLERY_RATE_POSTER_TOOL,
950
1151
  GENERATE_PRODUCT_PHOTO_TOOL,
951
1152
  GENERATE_LOGO_TOOL,
@@ -981,6 +1182,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
981
1182
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
982
1183
  try {
983
1184
  const { name, arguments: rawArgs } = request.params;
1185
+ if (SOCIAL_TOOLS.some(tool => tool.name === name))
1186
+ return await executeSocialTool(name, rawArgs || {});
984
1187
  const args = rawArgs || {};
985
1188
  if (name === 'generate_jewellery_rate_poster') {
986
1189
  const response = await apiClient('/image/jewellery-rate-poster', {
@@ -1346,7 +1549,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1346
1549
  async function run() {
1347
1550
  const transport = new StdioServerTransport();
1348
1551
  await server.connect(transport);
1349
- console.error("[CodingMantra MCP] Server v0.2.2 initialized and running on stdio (28 tools active)");
1552
+ console.error("[CodingMantra MCP] Server v0.3.0 initialized and running on stdio (43 tools active)");
1350
1553
  }
1351
1554
  run().catch((error) => {
1352
1555
  console.error("Fatal error running CodingMantra MCP server:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codingmantra/mcp-server",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Official Model Context Protocol (MCP) Server for CodingMantra AI Generation Tools",
5
5
  "type": "module",
6
6
  "main": "build/index.js",