Skip to content
Sign in Waitlist
Docs/MCP server

Praeko MCP server

One address, https://praeko.app/mcp, and one of two credentials: the same API key you use with the REST API, or your agent's OAuth sign-in. The agent reads these docs, looks up your account and, with your approval, creates, schedules, edits and cancels posts. Every write that publishes or changes content takes two steps: the first call does nothing.

18 toolsStreamable HTTP, POST onlyBearer key or OAuthTwo-step writes

What it is

MCP (Model Context Protocol) is the standard way for an AI agent to call tools. Praeko's server runs on the same host as the API and keeps no session: each call is a JSON-RPC POST and the response is a JSON object. Under the hood, each tool calls the same REST route you would call, with the English contract (X-Praeko-Contract: en). So validation, scopes, workspace isolation and errors are the API's own, and field names match the contract.

Use Tools
Read the docs search_docs, read_page: search and read routes (body, example, errors) and short guides, in their current version.
Look up the account list_channels, list_media, get_media, list_posts, get_calendar, get_insights, get_retention, get_usage.
Act, always in two steps create_post, schedule_post, update_post, cancel_post, upload_media, create_idea.
Upload a local file create_media_upload, complete_media_upload: the server prepares the upload and the agent runs curl in its own shell (a single step: creating the slot publishes nothing).
Left out on purposeWebhooks, API keys, channel connection (signing in to the networks), publish now, comments and replies are not tools: they stay in the panel and the API. The file never goes through the conversation with the agent: with a shell, it uses create_media_upload (see "Local file"); without one, upload_media with a public URL.

Two MCP servers: the remote one publishes, the local one makes the video

An agent can use both at once. This one, praeko, is the remote server: it runs at praeko.app/mcp and is the only one that creates, schedules, edits and cancels posts. praeko-studio is the local server of Praeko Studio (@praeko/studio-mcp): it runs on your machine over stdio, serves an agent without a terminal and never publishes. It needs Node, Chrome/Remotion to render and your own keys, all on your computer.

The full flow: create and render the video on the local server, upload the result with studio_upload (it returns only the mediaId, in two steps) and publish with create_post on this server, with your approval. Details and the list of the 14 local tools in Studio in Claude Desktop: the local MCP server.

Connect

The endpoint accepts two credentials, always in Authorization: Bearer … and on every call: an API key (praeko_…) that you create and paste into the agent's config, or an OAuth token (mcpat_…) that the agent gets by itself after you sign in and approve the connection. Both give the same tools, with the same scopes and the same two-step confirmation.

Connection details
URL            https://praeko.app/mcp
Transport      Streamable HTTP (POST only; no session, no stream)
Auth           Authorization: Bearer praeko_…   (API key, on EVERY call; never in the URL)
               Authorization: Bearer mcpat_…    (OAuth token, obtained by the agent at sign-in)
OAuth          https://praeko.app/.well-known/oauth-protected-resource/mcp
Credential When to use it How
API key Terminal and editor agents that accept a header (Claude Code, Codex, Cursor, Gemini CLI, opencode and others), automations, and local file uploads from the shell. Create the key and set the header (below).
OAuth sign-in Connectors that only speak OAuth (Claude.ai, Claude Desktop and mobile), and any agent that would rather sign in than hold a key, such as Claude Code. Give it only the URL; the agent opens the consent screen (below).

With an API key

Create the key in the panel (Developers, API keys) or with praeko key create (which needs an admin key). The read scope is enough to read; the tools that write need write or admin. Keep the key in an environment variable, never in a versioned file. Each agent registers the server its own way: pick yours in AI agents and copy the ready-made block.

With sign-in (OAuth)

Add https://praeko.app/mcp to the agent with no header at all. The first call gets a 401 with resource_metadata in WWW-Authenticate; the agent discovers Praeko's authorization server and opens your browser. There is no key to copy or to leak.

1. Add the URLIn Claude.ai or Claude Desktop, a custom connector with https://praeko.app/mcp. In Claude Code, claude mcp add --transport http praeko https://praeko.app/mcp, then /mcp.
2. Sign in to PraekoThe browser opens the panel. If you are not signed in yet, sign in as usual (Google, password or passkey, plus two-step verification if it is on).
3. ConsentThe "Connect {agent} to Praeko" screen shows the app name, the return address, the workspace and the access (Read or Write). You choose and click Allow or Deny.
4. ConnectedThe agent comes back with a token and starts calling tools. The connection shows up under Agent connections in the panel.
Claude Code with sign-in, no key
# registers the server with no header; sign-in happens in the browser
claude mcp add --transport http praeko https://praeko.app/mcp
# inside Claude Code: /mcp, pick praeko and sign in
Topic How it works
Access Read = the read scope (the ten read and docs tools). Write = the write scope (all eighteen). admin is never granted over OAuth: API keys, channels, members and settings stay out of the agent's reach. The client asks for a ceiling and the screen lets you pick that access or less; with no request, Read applies.
Effective scope The lower of what you granted and what your workspace role allows today. If you lose rights in the workspace, the agent loses them too.
Tokens The mcpat_… access token lasts 1 hour; the mcprt_… refresh token lasts 30 days and is rotated on every use. Presenting a refresh token that was already used revokes the whole connection (the agent has to sign in again). The server stores only the hash.
Where the token works Only on /mcp. The REST API and the panel refuse the OAuth token; use an API key for those.
Manage and revoke In the panel, under Developers, API keys, section Agent connections: app, access, person and last use, with a Revoke button that cuts access immediately. Each person sees and revokes their own connections; a workspace admin sees and revokes all of them. Authorizing the same app again replaces the previous connection.
Standards OAuth 2.1 with public clients only: authorization code with mandatory PKCE S256 and resource (RFC 8707). Discovery through Protected Resource Metadata (/.well-known/oauth-protected-resource/mcp) and authorization server metadata (/.well-known/oauth-authorization-server). Client registration through a Client ID Metadata Document (the client_id is an https URL) or dynamic client registration (RFC 7591, POST /oauth/register). The authorization response includes iss (RFC 9207).
Redirect URI https, or http only on localhost, 127.0.0.1 and [::1] (any port, as RFC 8252 allows). Custom schemes such as cursor:// and vscode:// are refused: use an API key with those clients.
A fixed header wins over sign-inIn Claude Code, a configured Authorization header takes precedence over OAuth: if the key is refused, it does not fall back to sign-in. To use sign-in, register the server without --header. Sign-in from ChatGPT has not been checked against OpenAI's documentation yet.

Authentication responses

Situation Response
No Authorization 401 with WWW-Authenticate: Bearer realm="Praeko MCP", scope="read write", resource_metadata="https://praeko.app/.well-known/oauth-protected-resource/mcp".
Unknown, revoked or expired key or token 401 with error="invalid_token" and the same resource_metadata in WWW-Authenticate.
Scope too low for the tool 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="write" (plus resource_metadata).
Panel cookie Not accepted here: only an API key or an OAuth token.
GET, DELETE or another method 405 with Allow: POST.
An Origin that is neither Praeko nor localhost 403 (DNS rebinding protection). Command-line clients do not send Origin.
Over 300 requests per minute 429. Same limit as the REST API, counted per API key or per OAuth connection.

Tools

Eighteen tools. The list the agent gets from tools/list depends on the scope: read sees the ten read and docs tools; write and admin see all eighteen (the server exposes nothing extra for admin: avoid admin for agents). The "What the agent gets" column is the literal description the server sends, in English; the two-step text that closes the six tools that publish or change content is right after the table. create_media_upload and complete_media_upload are the only writes without two steps (see "Local file").

Tool Scope and annotations What the agent gets Arguments
search_docs read read-only Search the Praeko API documentation (routes, fields, enums, errors, guides). Returns the best pages with a snippet; open one with read_page. Use it before building a create_post or update_post body. query (required, up to 200 characters), limit (1 to 20, default 8)
read_page read read-only Read one documentation page in full (a route with its body schema, example and errors, or a guide). Ids come from search_docs: route:posts.create, guide:overview, guide:mcp-two-step... id (required)
list_channels read read-only List the connected channels (one item per account; default marks the one used when a post item has no channel), with state and slots count. Use the channel ids in create_post. none
list_media read read-only List the media library (last 100 items): id, type, name, state, size and duration. Use a ready media id in create_post. type (video, cover or image)
get_media read read-only One media item of the library: state (uploading or ready), type, size and, when ready, publicUrl, durationMs, width and height. Use it to check a media id (for example after complete_media_upload) before create_post or schedule_post. mediaId (required)
list_posts read read-only List posts, newest first (queue and drafts nearest first). Filters: state, channel, tag. state (queued, draft, sent, error, canceled), channel, tag, limit (1 to 200), offset
get_calendar read read-only Scheduled items in a period plus the free weekly slots of each channel (default: next 14 days; at most 100 days). from, to (ISO-8601 with time zone), channel, tag
get_insights read read-only Cards, top posts, per-channel results and a summary for a period (views, likes, comments, followers), optionally compared with the previous period. from, to, channel, tag, type, compare, sort, direction, page, limit
get_retention read read-only Audience retention, impressions and CTR per post of the period (YouTube) and reach/watch time (Instagram). A missing number is null, never 0; channels[].state says whether each account can deliver them. from, to, channel, limit
get_usage read read-only API usage of this workspace over the last N days (total, errors, p95, peak per minute vs the limit, by day and by key). MCP calls appear with the route prefix mcp:. days (1 to 30, default 7)
create_post write writes, open-world Create a post with one item per network. Items are drafts by default or take the next free slot of the channel (queue: true). For a fixed publication time use schedule_post. For a single-photo Instagram post send kind image with media.images (one JPEG image media, optional alt text). Never publishes immediately. TWO-STEP (closing paragraph shared by the six two-step tools, right below the table) media, texts, items (required); kind (video or image), title, baseText, queue, draft, options, tags, confirm. With kind: image, media.images carries the photo (one). Refuses scheduledFor
schedule_post write writes, open-world Create a post and publish it at a fixed time per item (scheduledFor, ISO-8601 with timezone: ALWAYS state the BRT time to the user as well; the panel runs on America/Sao_Paulo). The post goes out by itself at that time on the real account. For a single-photo Instagram post send kind image with media.images. TWO-STEP (closing paragraph shared by the six two-step tools, right below the table) the same as create_post, but every item requires scheduledFor; no queue or draft
update_post write writes, destructive, idempotent, open-world Edit a post: title, texts, media, tags, items (mode, scheduledFor, queue, draft) or moveTo draft/queue. Send only what changes. WHAT CAN CHANGE, per network and when: (1) Any item still pending (draft or scheduled, not yet sent): time, mode, texts, queue/draft. (2) YouTube item ALREADY UPLOADED (the video sits in the channel as private with a future publish time, state "scheduled", or private without a time): it can still be RESCHEDULED (scheduledFor) and its title, description (texts.youtube), videoTags, categoryId and a not-yet-posted pinnedComment can change, until the video goes live. The server checks live on YouTube first; if the video is already public, the scheduled time already passed, the video was deleted, or Praeko did not upload it, nothing is written and the error names the reason and the manual alternative (YouTube Studio: https://studio.youtube.com/video/<videoId>/edit). The new time must be at least 5 minutes ahead; asking for the same time again changes nothing. A YouTube video never becomes public by this tool. (3) Instagram Reels in Buffer: text, time and draft only. (4) TikTok/Instagram items already sent or in the TikTok inbox cannot change. A request touching several items applies each one independently and the result lists itemResults (applied or not, and why): read it, nothing is skipped silently. TWO-STEP (closing paragraph shared by the six two-step tools, right below the table) The preview lists, per item, "will_change" or "cannot_change" with the reason. postId (required); title, texts, media, tags, items (id or network, mode, scheduledFor, queue, draft, videoTags, categoryId), moveTo (draft or queue), confirm
cancel_post write writes, destructive, idempotent, open-world Cancel the pending items of a post (they will not be published; Instagram items scheduled in Buffer are removed there). Items already sent are kept. IMPORTANT for YouTube: a video Praeko already uploaded and scheduled (private with a future publish time) is NOT cancelled by this tool: it stays and YouTube will still publish it on time (the result says so). To stop it, either reschedule it with update_post, or manage it in YouTube Studio (https://studio.youtube.com/video/<videoId>/edit: remove the schedule to keep it private, or delete it). TWO-STEP (closing paragraph shared by the six two-step tools, right below the table) postId (required), confirm
upload_media write writes, open-world Add a video, a cover or the photo of an image post (type image: JPEG only) to the media library by giving a PUBLIC https URL that the Praeko server downloads (video/mp4, video/quicktime, image/png, image/jpeg; up to 50 MB; private networks are refused). Files are never sent through MCP: for larger files use the REST/CLI upload and then use the media id. TWO-STEP (closing paragraph shared by the six two-step tools, right below the table) url and type (video, cover or image) required; name, confirm
create_media_upload write writes, open-world Use this to publish a video, cover or photo (type image, JPEG) that is a file on the user's machine (you need a shell; with no shell, use upload_media with a public URL). The file never goes through MCP: this tool only creates the media slot and returns the exact upload command (curl; a loop of parts above 50 MiB). Steps: 1) call this with the real size in bytes (wc -c < file), type (video, cover or image), mime (video/mp4, video/quicktime, image/png, image/jpeg; an image type takes only image/jpeg); 2) run upload.command in the shell after replacing /path/to/file; the API key is read from the shell environment (PRAEKO_KEY): NEVER ask for the key, paste it in the chat, print it or write it to a file; 3) call complete_media_upload with the mediaId; 4) use that media id in create_post or schedule_post (their two-step preview and confirmation still protect the publication). Creating the slot publishes nothing, so there is no confirmation code. Limits: video up to 2 GiB, cover and image up to 20 MiB (Instagram itself takes photos up to 8 MB: that is checked when the post is created), 20 pending uploads per workspace, 60 per hour; an upload not finished in 24 hours is discarded. type (video, cover or image), name, mime (video/mp4, video/quicktime, image/png, image/jpeg) and size in bytes, all required. No confirm: creating the upload slot publishes nothing
complete_media_upload write writes, idempotent Call this after running the upload command from create_media_upload. It finishes the upload (joins the parts of a large file; a single-PUT file is already complete), verifies it and returns status ready or uploading, with the media record: publicUrl, durationMs, width and height (read from the video header). Safe to call again. Then use the media id in create_post or schedule_post. mediaId (required). No confirm
create_idea write writes Add a card to the idea board (the "Create" screen). Nothing is published. TWO-STEP (closing paragraph shared by the six two-step tools, right below the table) title (required, up to 200 characters); note, group (idea, scripting, ready, posted), tags, confirm
Closing paragraph of the six two-step tools
TWO-STEP: the first call (without `confirm`) executes NOTHING: it returns a preview and a single-use `confirmation_code` (10 minutes). Show the preview (action, channels, time in BRT and UTC, media, text, warnings) to the user and WAIT for explicit approval. Only then call this tool again with the SAME arguments plus `confirm: "<code>"`. Changing any argument invalidates the code.

Annotations go in tools/list (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) and the column spells them out: "read-only" is readOnlyHint, "destructive" is destructiveHint, "idempotent" is idempotentHint and "open-world" is openWorldHint (it talks to a social network or downloads from the internet). The client decides whether to trust them: the spec says to treat them as hints.

What each write does

Two-step writes

The confirmation lives on the server, not in the agent: even if the agent is set to approve everything, a write tool never runs on the first call. This holds the same for API keys and OAuth sign-in.

1. Call without confirmThe server validates and returns the preview. Nothing is saved, nothing is sent.
2. The previewThe exact action, channels with id and name, time in BRT and UTC, whether the media was found, texts and warnings, plus a confirmation_code.
3. You approveThe agent shows the preview and waits for your "yes".
4. Call with confirmThe same arguments plus confirm: "<code>". Only now does it run.
Step 1: the agent calls (no confirm)
{
  "name": "schedule_post",
  "arguments": {
    "title": "The test that only failed on Mondays",
    "media": {
      "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
    },
    "texts": {
      "youtube": {
        "title": "The test that only failed on Mondays",
        "text": "…"
      }
    },
    "items": [
      {
        "network": "youtube",
        "mode": "scheduled",
        "scheduledFor": "2026-11-13T20:30:00Z"
      }
    ]
  }
}
Step 1: the preview (nothing was saved)
{
  "preview": true,
  "executed": false,
  "tool": "schedule_post",
  "action": "Create a post and schedule it at fixed times",
  "title": "The test that only failed on Mondays",
  "items": [
    {
      "network": "youtube",
      "channel": {
        "id": "…",
        "name": "My channel",
        "connected": true
      },
      "mode": "network default",
      "timing": "fixed_time",
      "scheduledFor": {
        "utc": "2026-11-13T20:30:00.000Z",
        "brt": "2026-11-13 17:30 (BRT, UTC-03:00)"
      }
    }
  ],
  "media": [
    {
      "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
      "found": true,
      "type": "video",
      "state": "ready"
    }
  ],
  "warnings": [
    "Confirming schedules a REAL publication on the connected account(s) at the time above. It can be edited or cancelled until then (update_post, cancel_post)."
  ],
  "confirmation_code": "K7MQ-2VXZ-9RT4",
  "expires_at": "2026-11-13T12:10:00.000Z",
  "expires_in_seconds": 600,
  "next_step": "NOTHING was done yet. Show this preview to the user and wait for approval. …"
}
Step 2: the agent repeats with the code
{
  "name": "schedule_post",
  "arguments": {
    "title": "The test that only failed on Mondays",
    "media": {
      "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
    },
    "texts": {
      "youtube": {
        "title": "The test that only failed on Mondays",
        "text": "…"
      }
    },
    "items": [
      {
        "network": "youtube",
        "mode": "scheduled",
        "scheduledFor": "2026-11-13T20:30:00Z"
      }
    ],
    "confirm": "K7MQ-2VXZ-9RT4"
  }
}
Step 2: result
{
  "preview": false,
  "executed": true,
  "tool": "schedule_post",
  "result": {
    "id": "…",
    "items": [
      {
        "network": "youtube",
        "state": "scheduled"
      }
    ]
  }
}

The confirmation code

Property Value
Format XXXX-XXXX-XXXX: 12 random characters (about 58 bits), for example K7MQ-2VXZ-9RT4.
Lifetime 10 minutes (expires_in_seconds: 600).
Use Single use. Two confirmations at the same time: only one runs.
Bound to The workspace, the credential that asked for the preview (the API key or the OAuth connection: refreshing the token does not invalidate the code), the tool and the arguments (SHA-256 of the canonical JSON: key order does not matter).
Stored as Only its hash, in the database.
Refused by validation If the route refuses the request (4xx, nothing saved), the code becomes valid again until it expires: the user fixes what was missing (media still uploading, for example) and confirms again.

Changing any argument, reusing the code, or using an expired code or one from another credential, tool or workspace fails. The response is a tool execution error (isError: true) with error.code confirmation_invalid and the reason in error.reason (the message is in English):

`reason` Meaning What the agent does
unknown_code The code does not exist in this workspace. Call without confirm for a new preview.
already_used The code was already used. Ask for a new preview to repeat the action.
expired The 10 minutes are up. Ask for a new preview and show it to the user again.
wrong_key The code was issued to another API key or another OAuth connection. Ask for a preview with the current credential.
wrong_tool The code belongs to another tool. Use the tool that issued the code.
arguments_changed The arguments differ from the ones in the preview. Ask for a new preview with the final arguments.
Error example
{
  "error": {
    "code": "confirmation_invalid",
    "message": "this confirmation code expired (10 minutes). Request a new preview and show it to the user again.",
    "reason": "expired"
  }
}

upload_media: media from a URL

The tool takes a public https URL and the server downloads the file. The file never goes through the conversation with the agent.

Rule Value
Types video: video/mp4, video/quicktime. cover: image/png, image/jpeg. image (the photo of an image post): image/jpeg only. The type is checked on download (unsupported_mime).
Size Up to 50 MB, checked in the header and during the download. Above that, file_too_large: use the local file upload below, or the multipart upload of the REST API, the CLI or the SDK.
Redirects Up to 3 hops, and each hop goes through the same check again.
Refused addresses https only, no user and password, no localhost, internal names, private, reserved or cloud-metadata IPs (the same barrier as webhooks).

The Worker does not resolve DNS before downloading: against a public name that resolves to a private IP, the protection is Cloudflare's own network, which cannot reach anyone's private network.

Local file: create_media_upload and complete_media_upload

The most common case for an agent with a shell (Claude Code, Codex, Gemini CLI, opencode, Cursor, Kimi, Cline): the video is on disk. MCP does not carry large files, so the server only prepares the upload and the agent's own curl sends it straight to the API, with the key that is already in the user's environment.

1. create_media_uploadThe agent passes type, name, mime and the exact size in bytes (wc -c < file). The server creates the media and returns the mediaId, the ready curl command and the HTTP spec.
2. Run the commandThe agent runs upload.command in the shell. The key comes from $PRAEKO_KEY (export it from PRAEKO_API_KEY if that is the name you use) and never shows up in the conversation.
3. complete_media_uploadJoins the parts (large file), verifies and returns status: ready with publicUrl, durationMs, width and height. Safe to repeat; get_media checks the state.
4. create_post or schedule_postWith the mediaId. These still take two steps: preview, your "yes", code.
Size How the file goes up
Up to 50 MiB One PUT to /v1/media/{id}/content, with the file as the body.
Above 50 MiB (video up to 2 GiB) 64 MiB parts to PUT /v1/media/{id}/parts/{n} (all equal, the last one smaller; each part can be retried). The returned command is already the dd and curl loop; the response also lists the parts with the URL of each.
create_media_upload response (file up to 50 MiB)
{
  "executed": true,
  "tool": "create_media_upload",
  "upload": {
    "mediaId": "01KB7J3V9Q2W8E5R1T6Y4N0M3P",
    "state": "uploading",
    "expectedSize": 48213377,
    "mime": "video/mp4",
    "expiresAt": "2026-11-08T12:00:00.000Z",
    "keyHandling": "The API key is already in the user's shell environment as PRAEKO_KEY (…)",
    "method": "single_put",
    "http": {
      "method": "PUT",
      "url": "https://praeko.app/v1/media/01KB7J3V9Q2W8E5R1T6Y4N0M3P/content",
      "headers": {
        "Authorization": "Bearer $PRAEKO_KEY",
        "Content-Type": "video/mp4"
      },
      "body": "the raw bytes of the file (exactly 48213377 bytes)"
    },
    "command": "curl -sS --fail-with-body -X PUT -H \"Authorization: Bearer $PRAEKO_KEY\" -H \"Content-Type: video/mp4\" -H \"Expect:\" -T \"/path/to/file\" \"https://praeko.app/v1/media/01KB7J3V9Q2W8E5R1T6Y4N0M3P/content\""
  },
  "next_step": "Run `upload.command` in the shell (replace /path/to/file; the key comes from the environment, do not print it), then call complete_media_upload with this mediaId, then create_post or schedule_post with media.video = mediaId (those keep their two-step preview/confirmation)."
}

Why there is no confirmation code

Creating the upload slot publishes nothing, sends nothing to a network and changes no post. What protects the account is the next step, create_post or schedule_post, which still takes two steps. So these two tools are single-step, with limits instead of a code:

Limit Value
Scope write (a read-only list does not show them).
Types video/mp4, video/quicktime (video), image/png, image/jpeg (cover) and image/jpeg (image); the type must match type.
Size Video up to 2 GiB, cover and image up to 20 MiB. The server refuses an upload whose size differs from the declared one (size_mismatch).
Pending uploads At most 20 per workspace (too_many_pending_uploads, with the pending ids) and 60 created per hour (rate_limited).
Expiry An upload not finished within 24 hours is discarded (the partial file is deleted and the media is removed).
Isolation The media belongs to the credential's workspace; another workspace gets media_not_found.
The key The curl uses an API key with the write scope, read from the shell environment ($PRAEKO_KEY). The tools never return the key, only the $PRAEKO_KEY placeholder, and tell the agent not to ask for it, print it or paste it in the conversation.
With OAuth sign-in The OAuth token works only on /mcp, and the curl talks to the REST API: even when connected by sign-in, the shell needs a write key in $PRAEKO_KEY. Without one, use upload_media with a public URL.
The client may ask you to approve the commandClaude Code asks for approval of a shell command that uses a variable such as $PRAEKO_KEY and, when non-interactive (claude -p), refuses anything not allowed in its permissions. Approve the curl in the session or allow it in the permissions. The returned command uses plain $PRAEKO_KEY, without the ${VAR:-…} form that Claude Code blocks. This is the client's rule, not Praeko's.
Agent without a shellClaude.ai, Claude Desktop and ChatGPT connectors have no shell and no disk access: use upload_media with a public https URL (up to 50 MB). Without a URL, upload through the CLI (praeko post), the SDK (uploadFile) or the REST API and hand over the media id.

Protocol

The endpoint speaks two MCP eras at the same address, as the spec allows. Most clients today still use 2025-11-25; 2026-07-28 (the newest when this was checked, Nov 7, 2026) has no initialize and no session.

Modern era `2026-07-28` Legacy era `2025-11-25`, `2025-06-18`, `2025-03-26`
How it is detected _meta["io.modelcontextprotocol/protocolVersion"] in the body or MCP-Protocol-Version: 2026-07-28 Any other request. Without a version header, 2025-03-26 applies.
Opening None. server/discover is optional. initialize, a response with the requested version (or 2025-11-25), notifications/initialized (202).
Required headers MCP-Protocol-Version matching the body, Mcp-Method matching method, Mcp-Name matching params.name in tools/call. Mismatch: 400 and JSON-RPC -32020. MCP-Protocol-Version, if sent, must be a known version (otherwise 400).
Unknown version 400 and -32022, with data.supported and data.requested. Same.
Unknown method 404 and -32601. 200 and -32601.
Result Includes "resultType": "complete". No resultType.
JSON-RPC batch Refused (400). Accepted.
Try it with curl (legacy era, no session)
curl -s https://praeko.app/mcp \
  -H "Authorization: Bearer $PRAEKO_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Security

Safeguard How it works
Server-side confirmation No write that publishes or changes content runs without the second step. The code is single-use, lasts 10 minutes and is bound to the credential, the tool and the arguments.
Least privilege A read key (or Read access, with sign-in) for an agent that only looks things up; write only for one that schedules. The scope is checked at /mcp and again on the REST route.
Explicit consent The consent screen shows up on every authorization, even for a known app, and highlights when it asks for more access than before. It shows the return address and warns when the app is unknown or only has a local address.
Tool text is data The server instructions tell the model to treat returned titles, comments and notes as user data, never as instructions.
Local file upload Only an upload slot with limits (20 pending, 60 per hour, 24 h expiry, 2 GiB); the file goes from the agent's shell straight to the API. Publishing still takes two steps.
Credentials out of code Keep the key in an environment variable. Keys and tokens never appear in responses, logs or the database (only their hash). If a key leaked, revoke it in API keys; a connection, in Agent connections.
Audit trail Each MCP call becomes one row in Usage and log, with the route mcp:tools/call create_post, mcp:tools/list and so on, and the status; calls made through sign-in show up with the key mcp-oauth. Filter by the mcp: route in the panel or with GET /v1/usage/log?route=mcp%3A.

Clients checked

Tested with the real Claude Code: with an API key (initialization, tools/list, list_channels and the create_idea preview) and with OAuth sign-in (discovery, registration through the client metadata document, consent, connection and revocation). The blocks for the other agents in AI agents come from each vendor's documentation. Sign-in from ChatGPT has not been checked against a primary source.

Question or mistake on this page? contato@praeko.appView .md

Type to search pages, routes and error codes.