create_draft
Create a new Substack draft post using markdown. The body is converted to Substack's format; the draft is saved without publishing.
Instructions
Create a new draft post. Accepts markdown body which is converted to Substack's format. Does NOT publish — creates a draft only.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Post title | |
| body | No | Post body in markdown format | |
| subtitle | No | Post subtitle | |
| audience | No | Who can see this post | everyone |
Implementation Reference
- src/server.ts:167-205 (handler)MCP tool registration for 'create_draft' - the handler function that receives title, body, subtitle, audience; converts markdown body to ProseMirror format via markdownToProseMirror(); calls client.createDraft(); and returns the draft ID with success message.
server.tool( "create_draft", "Create a new draft post. Accepts markdown body which is converted to Substack's format. Does NOT publish — creates a draft only.", { title: z.string().describe("Post title"), body: z.string().optional().describe("Post body in markdown format"), subtitle: z.string().optional().describe("Post subtitle"), audience: z .enum(["everyone", "only_paid", "founding", "only_free"]) .optional() .default("everyone") .describe("Who can see this post"), }, async ({ title, body, subtitle, audience }) => { const prosemirrorBody = body ? markdownToProseMirror(body) : undefined; const draft = await client.createDraft( title, prosemirrorBody, subtitle, audience, ); return { content: [ { type: "text", text: JSON.stringify( { id: draft.id, title: draft.draft_title, message: "Draft created successfully. Open Substack to review and publish.", }, null, 2, ), }, ], }; }, ); - src/api/client.ts:123-146 (handler)SubstackClient.createDraft() - the actual API client method that constructs the DraftCreatePayload with title, optional body/subtitle, audience and type, then sends a POST request to the Substack API to create a draft.
async createDraft( title: string, body?: string, subtitle?: string, audience: string = "everyone", ): Promise<SubstackDraft> { const payload: DraftCreatePayload = { draft_title: title, draft_bylines: [{ id: this.userId, is_guest: false }], audience: audience as DraftCreatePayload["audience"], type: "newsletter", }; if (subtitle) payload.draft_subtitle = subtitle; if (body) payload.draft_body = body; return this.request<SubstackDraft>( `${this.publicationUrl}/api/v1/drafts`, { method: "POST", body: JSON.stringify(payload), }, ); } - src/api/types.ts:53-61 (schema)DraftCreatePayload interface - defines the shape of the payload sent to the Substack API when creating a draft (draft_title, draft_subtitle, draft_body, draft_bylines, audience, type, section_id).
export interface DraftCreatePayload { draft_title: string; draft_subtitle?: string; draft_body?: string; draft_bylines: Array<{ id: number; is_guest: boolean }>; audience?: "everyone" | "only_paid" | "founding" | "only_free"; type?: "newsletter" | "podcast" | "thread"; section_id?: number | null; } - src/server.ts:167-205 (registration)Tool registration via server.tool('create_draft', ...) - registers the tool with MCP server with Zod schema for input validation (title required, body/subtitle optional, audience with enum/default).
server.tool( "create_draft", "Create a new draft post. Accepts markdown body which is converted to Substack's format. Does NOT publish — creates a draft only.", { title: z.string().describe("Post title"), body: z.string().optional().describe("Post body in markdown format"), subtitle: z.string().optional().describe("Post subtitle"), audience: z .enum(["everyone", "only_paid", "founding", "only_free"]) .optional() .default("everyone") .describe("Who can see this post"), }, async ({ title, body, subtitle, audience }) => { const prosemirrorBody = body ? markdownToProseMirror(body) : undefined; const draft = await client.createDraft( title, prosemirrorBody, subtitle, audience, ); return { content: [ { type: "text", text: JSON.stringify( { id: draft.id, title: draft.draft_title, message: "Draft created successfully. Open Substack to review and publish.", }, null, 2, ), }, ], }; }, ); - markdownToProseMirror() helper - converts markdown text to Substack's ProseMirror JSON format (handles headings, code blocks, horizontal rules, paragraphs with inline formatting).
export function markdownToProseMirror(markdown: string): string {