Skip to main content
Glama

create_note_with_link

Create and publish a Substack note with an attached link that displays as a rich card below the text. Use this tool to share content with embedded URL previews.

Instructions

Create a Substack Note with a link attachment. The link is displayed as a rich card below the note text. Publishes immediately.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
bodyYesNote content in markdown format
urlYesURL to attach as a link card

Implementation Reference

  • src/server.ts:300-334 (registration)
    Tool registration for 'create_note_with_link' using server.tool() with Zod schema (body: string, url: string.url()) and async handler that orchestrates attachment creation and note publishing
    server.tool(
      "create_note_with_link",
      "Create a Substack Note with a link attachment. The link is displayed as a rich card below the note text. Publishes immediately.",
      {
        body: z.string().describe("Note content in markdown format"),
        url: z.string().url().describe("URL to attach as a link card"),
      },
      async ({ body, url }) => {
        const attachment = await client.createNoteAttachment(url);
        const bodyJson = {
          type: "doc" as const,
          attrs: { schemaVersion: "v1" as const },
          content: markdownToProseMirrorContent(body),
        };
        const note = await client.createNote(bodyJson, [attachment.id]);
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  id: note.id,
                  body: note.body,
                  date: note.date,
                  attachment_id: attachment.id,
                  message: "Note with link published successfully.",
                },
                null,
                2,
              ),
            },
          ],
        };
      },
    );
  • Handler function that: (1) creates link attachment via client.createNoteAttachment(url), (2) converts markdown to ProseMirror JSON format, (3) creates note with attachment via client.createNote(bodyJson, [attachment.id]), (4) returns JSON result with note id, body, date, and attachment_id
    async ({ body, url }) => {
      const attachment = await client.createNoteAttachment(url);
      const bodyJson = {
        type: "doc" as const,
        attrs: { schemaVersion: "v1" as const },
        content: markdownToProseMirrorContent(body),
      };
      const note = await client.createNote(bodyJson, [attachment.id]);
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(
              {
                id: note.id,
                body: note.body,
                date: note.date,
                attachment_id: attachment.id,
                message: "Note with link published successfully.",
              },
              null,
              2,
            ),
          },
        ],
      };
    },
  • createNoteAttachment method - makes POST request to /api/v1/comment/attachment with {url, type: 'link'} payload, returns NoteAttachment with id
    async createNoteAttachment(url: string): Promise<NoteAttachment> {
      return this.request<NoteAttachment>(
        `${this.publicationUrl}/api/v1/comment/attachment`,
        {
          method: "POST",
          body: JSON.stringify({ url, type: "link" }),
        },
      );
    }
  • createNote method - makes POST request to /api/v1/comment/feed with bodyJson, tabId, surface, replyMinimumRole, and optional attachmentIds
    async createNote(
      bodyJson: NoteCreatePayload["bodyJson"],
      attachmentIds?: string[],
    ): Promise<SubstackNote> {
      const payload: NoteCreatePayload = {
        bodyJson,
        tabId: "for-you",
        surface: "feed",
        replyMinimumRole: "everyone",
      };
      if (attachmentIds?.length) {
        payload.attachmentIds = attachmentIds;
      }
      return this.request<SubstackNote>(
        `${this.publicationUrl}/api/v1/comment/feed`,
        {
          method: "POST",
          body: JSON.stringify(payload),
        },
      );
    }
  • markdownToProseMirrorContent helper function - converts markdown text to ProseMirror content array format used for Notes
    export function markdownToProseMirrorContent(markdown: string): PMNode[] {
      const doc = JSON.parse(markdownToProseMirror(markdown));
      return doc.content;
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behavioral traits: the tool creates a note with a link attachment displayed as a rich card, and it publishes immediately (implying a mutation that is not reversible without additional steps). It doesn't mention permissions, rate limits, or error handling, but for a tool with two parameters and no annotations, it provides sufficient operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded, consisting of two concise sentences that directly state the tool's purpose and key behavior ('Publishes immediately'). Every sentence earns its place by providing essential information without redundancy or unnecessary elaboration.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (2 parameters, no output schema, no annotations), the description is mostly complete. It covers the action, resource, unique feature (link attachment), and immediate publishing behavior. However, it lacks details on error cases, response format, or how the link card is integrated, which could be helpful for an AI agent. No output schema exists, so some return value context is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters ('body' and 'url') with their types and descriptions. The description adds marginal value by implying that 'url' is used for a link card and 'body' is the note content, but it doesn't provide additional syntax, format details, or constraints beyond what the schema specifies. Baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Create a Substack Note with a link attachment'), identifies the resource ('Substack Note'), and distinguishes it from siblings like 'create_note' (which presumably doesn't include link attachments) and 'create_draft' (which likely doesn't publish immediately). It uses precise verbs and specifies the unique feature of link attachment.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool ('Create a Substack Note with a link attachment') and implies when not to use it (e.g., for drafts or notes without links). However, it doesn't explicitly name alternatives like 'create_note' or 'create_draft', nor does it detail exclusions such as when a user might want to save as a draft instead of publishing immediately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conorbronsdon/substack-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server