Server data from the Official MCP Registry
Generate and manage Storydoc presentations from any MCP-compatible client.
Generate and manage Storydoc presentations from any MCP-compatible client.
Remote endpoints: streamable-http: https://mcp.storydoc.com/mcp
This is a well-architected OAuth-enabled MCP server with proper authentication, secure token handling, and thoughtful security defaults. The codebase demonstrates strong security practices including encrypted token sealing, PKCE validation, and careful input validation. Minor code quality issues and one unresolved token validation edge case prevent a higher score, but the design and implementation are sound for a developer tool. Supply chain analysis found 2 known vulnerabilities in dependencies (0 critical, 2 high severity).
4 files analyzed · 6 issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
This plugin requests these system permissions. Most are normal for its category.
Available as Local & Remote
This plugin can run on your machine or connect to a hosted endpoint. during install.
From the project's GitHub README.
Official Storydoc Model Context Protocol server. Lets any MCP-compatible client (Claude Desktop, Claude Code, Cursor, Windsurf, ChatGPT, claude.ai web, etc.) generate Storydocs through Storydoc's AI wizard.
Status: v0.4 — hosted on Cloud Run (staging + production) and available on npm. Six tools cover the full workflow: list templates, plan a version, create a version, generate from scratch, fetch analytics, plus a getter for full story detail.
| Name | Purpose |
|---|---|
storydoc_list_stories | List the user's stories and templates. Returns id/title/status/createdAt plus an isTemplate flag and slim variable schemas. Supports status, search, onlyTemplates, and limit filters. |
storydoc_get_story | Fetch one story's full detail by ID, including its complete dynamicVariables schema (type, tip, default, validation). |
storydoc_plan_version | Preview a version-creation against a template — reports filled values, missing required fields, the title/sender that will be used, and a ready flag. No API write happens. Run this before storydoc_create_version. |
storydoc_create_version | Create a new version of a template with supplied variable values. Returns url, shortUrl, editorUrl, and IDs. |
storydoc_generate_story | Generate a brand-new deck from scratch via the Storydoc AI wizard. Use this only when no template fits. |
storydoc_get_analytics | Rolled-up analytics: totals, unique visitors/sessions, top stories/versions, top event names, device/location breakdowns. Defaults to the last 30 days. Pass rawEvents=true for the unrolled event list. |
storydoc_generate_story inputs| Field | Type | Notes |
|---|---|---|
prompt | string | Detailed brief for the deck. |
company | string | Company name — used for branding. |
website | URL | Company website (Storydoc fetches brand assets from it). |
presentationType | enum | One of the values in storydoc://presentation-types. If nothing fits, pass "General presentation" — the server transparently falls back and folds the requested style into the prompt. |
senderEmail | Optional — falls back to STORYDOC_DEFAULT_SENDER. |
Returns url, shortUrl, editorUrl, IDs, and a _meta block (title, slide counts, brand-fetch flag, duration).
1. user: "Create a sales proposal for Pfizer"
2. model: storydoc_list_stories(search="Pfizer") OR (search="sales", onlyTemplates=true)
3. model picks a template, then storydoc_plan_version(storyId=…, title=…, values=…)
4. model shows the preview to the user, fills any missing required fields
5. storydoc_create_version(same args) → returns the public URL
For a deck where no template fits: skip steps 2–4 and call storydoc_generate_story directly. Recommend both paths so the user knows there's a choice.
storydoc://presentation-types — JSON array of the closed list of supported presentationType values for storydoc_generate_story.storydoc://account — The authenticated org's { orgId, title }. Useful as context without a tool call.You need a Storydoc API token (Bearer JWT). Two ways to connect — pick whichever your client supports.
URL: https://storydoc-mcp-728346048498.europe-west1.run.app/mcp (staging — production URL TBD)
{
"mcpServers": {
"storydoc": {
"type": "http",
"url": "https://storydoc-mcp-728346048498.europe-west1.run.app/mcp",
"headers": {
"Authorization": "Bearer eyJhbGciOi..."
}
}
}
}
.cursor/mcp.json){
"mcpServers": {
"storydoc": {
"url": "https://storydoc-mcp-728346048498.europe-west1.run.app/mcp",
"headers": {
"Authorization": "Bearer eyJhbGciOi..."
}
}
}
}
Settings → Connectors → Add custom connector:
https://storydoc-mcp-728346048498.europe-west1.run.app/mcpSame idea — these clients also accept a URL + Authorization header. UI varies, payload doesn't.
Use this if you specifically want the MCP to run on your own machine (offline, air-gapped, or you don't trust the hosted endpoint).
{
"mcpServers": {
"storydoc": {
"command": "npx",
"args": ["-y", "storydoc-mcp"],
"env": {
"STORYDOC_API_TOKEN": "eyJhbGciOi..."
}
}
}
}
npm install
npm run build
# stdio (for testing against a desktop client)
STORYDOC_API_TOKEN=eyJ... node dist/index.js
# HTTP (for testing the hosted-server path locally)
node dist/http.js
# then POST http://localhost:8080/mcp with Authorization: Bearer ...
Point a client at the local stdio binary:
{
"mcpServers": {
"storydoc": {
"command": "node",
"args": ["/absolute/path/to/storydoc-mcp/dist/index.js"],
"env": { "STORYDOC_API_TOKEN": "eyJ..." }
}
}
}
On Cloud Run (or any GCP runtime where K_SERVICE / GAE_SERVICE / GOOGLE_CLOUD_PROJECT is set), the MCP loads its config from the shared secrets Secret Manager blob at startup, using the same convention as dotell-backend (bin/secrets.js). The blob contents are loaded into process.env. Locally — or when STORYDOC_SKIP_GCP_SECRETS=1 is set — this step is skipped and process.env is used as-is.
| Var | Mode | Required for | Default / notes |
|---|---|---|---|
STORYDOC_API_TOKEN | stdio | stdio mode | read locally; HTTP mode reads token per-request from Authorization |
STORYDOC_API_BASE | both | no | base URL for all production tools — defaults to https://api.storydoc.com |
STORYDOC_GENERATE_PATH | both | no | path-only override for the generate endpoint — defaults to /v2/stories/generate |
STORYDOC_API_URL | both | no | back-compat full-URL override for storydoc_generate_story only; when set, bypasses STORYDOC_API_BASE + STORYDOC_GENERATE_PATH. Used by the existing staging deploy that points at versions-728346048498.europe-west1.run.app. |
STORYDOC_DEFAULT_SENDER | both | no | when set, senderEmail becomes optional for generate_story and create_version |
MCP_SIGNING_KEY | HTTP | OAuth flow | put inside the shared secrets blob; random 32+ bytes |
STORYDOC_API_SECRET_STAGING | HTTP | server-side token validation | already in dotell-backend's secrets staging blob |
STORYDOC_API_SECRET | HTTP | server-side token validation | already in dotell-backend's secrets production blob |
NODE_ENV | HTTP | selects staging vs prod blob | staging or production |
MCP_PUBLIC_URL | HTTP | required when OAuth on | canonical public URL (e.g. https://mcp.storydoc.com). Used as OAuth issuer. Server refuses to start if missing while MCP_SIGNING_KEY is set — request headers (Host, x-forwarded-proto) are spoofable and we don't trust them. |
MCP_ALLOWED_HOSTS | HTTP | no (auto-derived) | comma-separated Host header allowlist for the SDK's DNS-rebinding defense. Defaults to the host parsed out of MCP_PUBLIC_URL. Add the raw Cloud Run URL too if traffic can bypass your load balancer. |
MCP_ALLOWED_ORIGINS | HTTP | no | comma-separated CORS allowlist for browser-based clients. Defaults to a curated list (Claude.ai, ChatGPT, Gemini, Copilot, Mistral, Perplexity). CLI/desktop clients aren't gated by CORS. |
MCP_ALLOW_RAW_TOKEN | HTTP | no | set to 1 to keep accepting raw Storydoc JWTs in Authorization: Bearer (legacy stdio-config path). Off by default when OAuth is enabled — clients must go through /authorize. |
PORT | HTTP | no | 8080 (Cloud Run injects this) |
STORYDOC_MCP_PATH | HTTP | no | /mcp |
STORYDOC_SKIP_GCP_SECRETS | HTTP | no | set to 1 to force-skip Secret Manager load (useful for docker run) |
If MCP_SIGNING_KEY ends up unset after loading the blob, OAuth endpoints are disabled and the server only accepts raw Storydoc JWTs as bearer tokens (Claude Desktop JSON-config path still works; claude.ai web Custom Connector path doesn't).
When MCP_SIGNING_KEY is set, OAuth is the only accepted path by default — raw Storydoc JWTs sent directly in Authorization: Bearer are rejected with 401 invalid_token. Set MCP_ALLOW_RAW_TOKEN=1 to keep the legacy passthrough working for clients that haven't migrated to the OAuth flow yet.
The MCP loads secrets the same way as dotell-backend — at startup it pulls the shared secrets blob from Secret Manager and loads it into process.env via dotenv. No per-secret --update-secrets flags, no env-var management in Cloud Run config. Add a key once to the shared blob and any service that loads it picks it up.
# 1. Grant the MCP's runtime service account read access to the shared secrets blob
# (the same blob dotell-backend reads from bin/secrets.js)
gcloud secrets add-iam-policy-binding secrets \
--project platform-staging-303007 \
--member="serviceAccount:728346048498-compute@developer.gserviceaccount.com" \
--role=roles/secretmanager.secretAccessor
# 2. Add MCP_SIGNING_KEY to the shared secrets blob.
# Easiest path: open the secret in the Secret Manager UI, copy the current blob,
# add a line like:
#
# MCP_SIGNING_KEY=<base64-output-of-openssl-rand-base64-32>
#
# and add a new version. STORYDOC_API_SECRET_STAGING is already in the blob —
# no changes needed for that.
# Generate a fresh signing key value to paste:
openssl rand -base64 32
Non-secret env vars (MCP_PUBLIC_URL, MCP_ALLOWED_HOSTS, MCP_ALLOWED_ORIGINS, NODE_ENV) live in a YAML file checked into the repo. Secrets (MCP_SIGNING_KEY, STORYDOC_API_SECRET) come from the Secret Manager blob at boot — not in the file.
gcloud config set project platform-production-303007 # or platform-staging-303007
gcloud run deploy storydoc-mcp \
--source . \
--region europe-west1 \
--allow-unauthenticated \
--port 8080 \
--min-instances 0 --max-instances 10 \
--memory 512Mi \
--env-vars-file=env.production.yaml
This is the deploy command — env config and image build happen atomically in one call, so you never end up in the "container won't start because MCP_PUBLIC_URL is missing" state.
For staging, swap the project and use a env.staging.yaml with the staging host/URL.
Notes:
--env-vars-file replaces all non-secret env vars on the service. The YAML must contain every non-secret var you want set. If you only need to tweak one var without a full redeploy, use --update-env-vars=KEY=VAL (merges, doesn't wipe) — but the next --env-vars-file deploy will overwrite it, so update the file too.--update-secrets. The container pulls the shared blob from Secret Manager on startup using the runtime service account's credentials.NODE_ENV=staging (or production) controls which blob is fetched — staging at projects/728346048498/secrets/secrets/versions/latest, production at projects/59909522358/secrets/secrets/versions/latest — matching dotell-backend's bin/secrets.js.--allow-unauthenticated is correct — auth happens inside the server via the OAuth flow / Authorization header, not via Google IAM.env.production.yaml:
NODE_ENV: production
MCP_PUBLIC_URL: https://mcp.storydoc.com
MCP_ALLOWED_HOSTS: mcp.storydoc.com,storydoc-mcp-59909522358.europe-west1.run.app
MCP_ALLOWED_ORIGINS: https://claude.ai,https://chatgpt.com,https://chat.openai.com,https://gemini.google.com,https://copilot.microsoft.com,https://chat.mistral.ai,https://www.perplexity.ai
Be the first to review this server!
by Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
by Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
by mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.