Skill: samuellie/outline-app-mcp (version 1.3.4)

--- name: outline-app-mcp description: Model Context Protocol (MCP) bridge for Outline (getoutline.com). Enables AI agents to search, read, create, and manage documents, collections, and comments in an Outline workspace via SSE transport. Includes local filesystem access for bulk file/media uploads to Outline. metadata: version: "1.3.4" platform: Outline author: Tinker transport: Streamable HTTP (SSE) mcp_version: "1.0.0" openclaw: primaryEnv: OUTLINE_API_KEY requires: env: - OUTLINE_API_KEY - OUTLINE_URL bins: - node - npm install: - id: deps kind: exec command: npm install label: Installing Node.js dependencies (@modelcontextprotocol/sdk) --- # Outline MCP Skill This skill provides a high-fidelity bridge between OpenClaw and an Outline workspace. It utilizes the **Model Context Protocol (MCP) v1.0.0** to dynamically expose Outline's internal documentation tools to AI agents. ## ⚠️ Security & Permissions Warning **Host Filesystem Access:** The `upload` (in `mcp_bridge.mjs`) and `bulk-sync` (in `bulk_sync.mjs`) tools require access to the local host filesystem. These scripts use `fs.readFileSync` to read files from **arbitrary absolute paths** provided by the agent and upload them to the configured Outline workspace. - **Risk:** If an agent is compromised or misconfigured, it could potentially exfiltrate sensitive local files by providing their absolute paths to these tools. - **Scope:** Access is limited to the permissions of the user running the OpenClaw process. - **Recommendation:** Only enable this skill in trusted environments and monitor agent logs for unexpected file access patterns. ## 📋 Prerequisites Before setting up the skill in OpenClaw, ensure you have completed the following steps in your Outline instance: 1. **Enable AI Features:** Ensure AI features are enabled for your workspace under **Settings** -> **AI**. 2. **Enable MCP:** In the same **AI** settings tab, toggle the **MCP** switch to "On". This enables the Model Context Protocol endpoint for your workspace. 3. **Generate API Key:** Click **Generate API Key** (also in the AI tab). Copy this key immediately as it will only be shown once. 4. **Locate your MCP URL:** Your MCP endpoint is typically your workspace URL with `/mcp` appended (e.g., `https://docs.yourcompany.com/mcp`). 5. **Node.js Environment:** Ensure Node.js (v18+) is installed on your OpenClaw host. ## 🛠️ Setup & Configuration To use this skill, you must provide your Outline workspace details via environment variables. ### Configuration Commands ```bash openclaw config set skills.entries.outline-app-mcp.env.OUTLINE_API_KEY "your_ol_api_key" openclaw config set skills.entries.outline-app-mcp.env.OUTLINE_URL "https://your-workspace.getoutline.com/mcp" ``` ## 🧰 API Reference & Detailed Tool Parameters The following tools are available via the `scripts/mcp_bridge.mjs` executor. All arguments are passed as a JSON object. ### 1. Document Management #### `list_documents` Searches documents or lists recent ones. - `query` (string, optional): Full-text search query. - `collectionId` (string, optional): Filter by collection. - `offset` (integer, optional): Pagination offset. - `limit` (integer, optional): Max results (default 25, max 100). #### `read_document` Retrieves the full Markdown content. - `id` (string, required): Unique identifier of the document. #### `create_document` Creates a new document. - `title` (string, required): Document title. - `text` (string, optional): Markdown content. - `collectionId` (string, required if no `parentDocumentId`): Target collection. - `parentDocumentId` (string, optional): Nest under this document. - `icon` (string, optional): Emoji icon. - `color` (string, optional): Hex color (e.g., `#FF0000`). - `publish` (boolean, optional): Default `true`. Set `false` for draft. #### `update_document` Edits an existing document. - `id` (string, required): Document ID. - `title` (string, optional): New title. - `text` (string, optional): New markdown content. - `editMode` (enum: `replace`, `append`, `prepend`, optional): Default `replace`. - `collectionId` (string, optional): Required when publishing a draft with no collection. - `icon` (string/null, optional): Update or remove emoji. - `color` (string/null, optional): Update or remove hex color. - `publish` (boolean, optional): Publish draft or revert to draft. #### `move_document` Relocates a document. - `id` (string, required): Document ID. - `collectionId` (string, optional): Target collection. - `parentDocumentId` (string, optional): Target parent document. - `index` (integer, optional): Zero-based position among siblings. ### 2. Collection Management #### `list_collections` Lists available collections. - `query` (string, optional): Filter by name. - `offset` (integer, optional): Pagination offset. - `limit` (integer, optional): Max results (max 100). #### `create_collection` Creates a new collection. - `name` (string, required): Collection name. - `description` (string, optional): Markdown description. - `icon` (string, optional): Emoji icon. - `color` (string, optional): Hex color. #### `update_collection` Updates collection metadata. - `id` (string, required): Collection ID. - `name` (string, optional): New name. - `description` (string, optional): New description. - `icon` (string/null, optional): Update or remove icon. - `color` (string/null, optional): Update or remove color. ### 3. Commenting & Collaboration #### `list_comments` Lists comments (requires `documentId` or `collectionId`). - `documentId` (string, optional): Filter by document. - `collectionId` (string, optional): Filter by collection. - `parentCommentId` (string, optional): List only replies to this comment. - `statusFilter` (array of `resolved`/`unresolved`, optional): Filter by status. - `offset` (integer, optional): Pagination offset. - `limit` (integer, optional): Max results. #### `create_comment` Adds a comment to a document. - `documentId` (string, required): Document to comment on. - `text` (string, required): Markdown content. - `parentCommentId` (string, optional): Nest as a reply. #### `update_comment` Updates or resolves a comment. - `id` (string, required): Comment ID. - `text` (string, optional): New markdown text. - `status` (enum: `resolved`, `unresolved`, optional): Resolve thread. #### `delete_comment` Deletes a comment. - `id` (string, required): Comment ID. ### 4. User Directory #### `list_users` Lists workspace members. - `query` (string, optional): Search by name or email. - `role` (enum: `admin`, `member`, `viewer`, `guest`, optional): Filter by role. - `filter` (enum: `active`, `suspended`, `invited`, `all`, optional): Filter by status. - `offset` (integer, optional): Pagination offset. - `limit` (integer, optional): Max results. --- ## 🚀 Usage Examples (Common Actions) ### 1. Search for project documentation ```bash OUTLINE_API_KEY="..." OUTLINE_URL="..." node skills/outline-app-mcp/scripts/mcp_bridge.mjs call "list_documents" '{"query": "Project Roadmap"}' ``` ### 2. Create a new status update ```bash OUTLINE_API_KEY="..." OUTLINE_URL="..." node skills/outline-app-mcp/scripts/mcp_bridge.mjs call "create_document" '{ "title": "Weekly Sync - April 1", "collectionId": "col_uuid_here", "text": "# Update\nEverything is on track." }' ``` ### 3. Append a new row to a table (Token Efficient) ```bash OUTLINE_API_KEY="..." OUTLINE_URL="..." node skills/outline-app-mcp/scripts/mcp_bridge.mjs call "update_document" '{ "id": "doc_uuid_here", "text": "\n| New Entry | Ready | [Link](url) |", "editMode": "append" }' ``` ### 4. Bulk Upload Images & Update Document (Power-Tool) Use the `bulk_sync.mjs` script for unified Turnaround (Upload + Update). ```bash OUTLINE_API_KEY="..." OUTLINE_URL="..." node skills/outline-app-mcp/scripts/bulk_sync.mjs "bulk-sync" "target_doc_id" "/path/1.png,/path/2.png" "## 🎨 New Media Assets\n{{MEDIA}}\n---\n*Synced via AI Agent*" "append" ``` - **Inputs**: 1. Document ID 2. Comma-separated absolute file paths. 3. **Template Text**: Use `{{MEDIA}}` as a placeholder for uploaded images. 4. **Edit Mode**: `replace`, `append`, or `prepend`. ### 5. Add a Review Comment to a Document ```bash OUTLINE_API_KEY="..." OUTLINE_URL="..." node skills/outline-app-mcp/scripts/mcp_bridge.mjs call "create_comment" '{ "documentId": "doc_uuid_here", "text": "Draft is ready for review." }' ``` ### 6. Relocate a document to a new Collection ```bash OUTLINE_API_KEY="..." OUTLINE_URL="..." node skills/outline-app-mcp/scripts/mcp_bridge.mjs call "move_document" '{ "id": "doc_uuid_here", "collectionId": "new_collection_uuid" }' ``` --- ## 💡 Best Practices ### 🚀 Token Efficiency & Cost Optimization (Mandatory) To minimize token consumption and improve responsiveness, **always prefer `append` or `prepend` modes** when adding new information to existing documents. - **Avoid Full Rewrites:** Do NOT use `replace` mode (default) if you only need to add a new row or section. - **Bulk Sync for Media:** Always use `bulk_sync.mjs` for uploading multiple images to avoid redundant Turnarounds. ## 📜 Technical Requirements - **Runtime:** Node.js v18+ - **Protocol:** MCP v1.0.0 (over SSE) - **Dependency:** `@modelcontextprotocol/sdk` (local) - **Authentication:** Bearer Token via `OUTLINE_API_KEY` - **Transport:** `StreamableHTTPClientTransport` (POST-based SSE)

path: package-lock.json
content: {
  "name": "outline-integration",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "outline-integration",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "@modelcontextprotocol/sdk": "^1.27.1"
      }
    },
    "node_modules/@hono/node-server": {
      "version": "1.19.11",
      "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.14.1"
      },
      "peerDependencies": {
        "hono": "^4"
      }
    },
    "node_modules/@modelcontextprotocol/sdk": {
      "version": "1.27.1",
      "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@hono/node-server": "^1.19.9",
        "ajv": "^8.17.1",
        "ajv-formats": "^3.0.1",
        "content-type": "^1.0.5",
        "cors": "^2.8.5",
        "cross-spawn": "^7.0.5",
        "eventsource": "^3.0.2",
        "eventsource-parser": "^3.0.0",
        "express": "^5.2.1",
        "express-rate-limit": "^8.2.1",
        "hono": "^4.11.4",
        "jose": "^6.1.3",
        "json-schema-typed": "^8.0.2",
        "pkce-challenge": "^5.0.0",
        "raw-body": "^3.0.0",
        "zod": "^3.25 || ^4.0",
        "zod-to-json-schema": "^3.25.1"
      },
      "engines": {
        "node": ">=18"
      },
      "peerDependencies": {
        "@cfworker/json-schema": "^4.1.1",
        "zod": "^3.25 || ^4.0"
      },
      "peerDependenciesMeta": {
        "@cfworker/json-schema": {
          "optional": true
        },
        "zod": {
          "optional": false
        }
      }
    },
    "node_modules/accepts": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-types": "^3.0.0",
        "negotiator": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/ajv": {
      "version": "8.18.0",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "fast-deep-equal": "^3.1.3",
        "fast-uri": "^3.0.1",
        "json-schema-traverse": "^1.0.0",
        "require-from-string": "^2.0.2"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/epoberezkin"
      }
    },
    "node_modules/ajv-formats": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ajv": "^8.0.0"
      },
      "peerDependencies": {
        "ajv": "^8.0.0"
      },
      "peerDependenciesMeta": {
        "ajv": {
          "optional": true
        }
      }
    },
    "node_modules/body-parser": {
      "version": "2.2.2",
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bytes": "^3.1.2",
        "content-type": "^1.0.5",
        "debug": "^4.4.3",
        "http-errors": "^2.0.0",
        "iconv-lite": "^0.7.0",
        "on-finished": "^2.4.1",
        "qs": "^6.14.1",
        "raw-body": "^3.0.1",
        "type-is": "^2.0.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/bytes": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/call-bound": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "get-intrinsic": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/content-disposition": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/content-type": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie-signature": {
      "version": "1.2.2",
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6.6.0"
      }
    },
    "node_modules/cors": {
      "version": "2.8.6",
      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "object-assign": "^4",
        "vary": "^1"
      },
      "engines": {
        "node": ">= 0.10"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/cross-spawn": {
      "version": "7.0.6",
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/depd": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/ee-first": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/encodeurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/escape-html": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/eventsource": {
      "version": "3.0.7",
      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "eventsource-parser": "^3.0.1"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/eventsource-parser": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/express": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "accepts": "^2.0.0",
        "body-parser": "^2.2.1",
        "content-disposition": "^1.0.0",
        "content-type": "^1.0.5",
        "cookie": "^0.7.1",
        "cookie-signature": "^1.2.1",
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "finalhandler": "^2.1.0",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.0",
        "merge-descriptors": "^2.0.0",
        "mime-types": "^3.0.0",
        "on-finished": "^2.4.1",
        "once": "^1.4.0",
        "parseurl": "^1.3.3",
        "proxy-addr": "^2.0.7",
        "qs": "^6.14.0",
        "range-parser": "^1.2.1",
        "router": "^2.2.0",
        "send": "^1.1.0",
        "serve-static": "^2.2.0",
        "statuses": "^2.0.1",
        "type-is": "^2.0.1",
        "vary": "^1.1.2"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/express-rate-limit": {
      "version": "8.3.1",
      "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ip-address": "10.1.0"
      },
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://github.com/sponsors/express-rate-limit"
      },
      "peerDependencies": {
        "express": ">= 4.11"
      }
    },
    "node_modules/fast-deep-equal": {
      "version": "3.1.3",
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fast-uri": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/fastify"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/fastify"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/finalhandler": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "on-finished": "^2.4.1",
        "parseurl": "^1.3.3",
        "statuses": "^2.0.1"
      },
      "engines": {
        "node": ">= 18.0.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/forwarded": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/fresh": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/hono": {
      "version": "4.12.9",
      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=16.9.0"
      }
    },
    "node_modules/http-errors": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "depd": "~2.0.0",
        "inherits": "~2.0.4",
        "setprototypeof": "~1.2.0",
        "statuses": "~2.0.2",
        "toidentifier": "~1.0.1"
      },
      "engines": {
        "node": ">= 0.8"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/iconv-lite": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "safer-buffer": ">= 2.1.2 < 3.0.0"
      },
      "engines": {
        "node": ">=0.10.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/ip-address": {
      "version": "10.1.0",
      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 12"
      }
    },
    "node_modules/ipaddr.js": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/is-promise": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/jose": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/panva"
      }
    },
    "node_modules/json-schema-traverse": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/json-schema-typed": {
      "version": "8.0.2",
      "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause"
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/media-typer": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/merge-descriptors": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/mime-db": {
      "version": "1.54.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-db": "^1.54.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/negotiator": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/object-assign": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/object-inspect": {
      "version": "1.13.4",
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/on-finished": {
      "version": "2.4.1",
      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ee-first": "1.1.1"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "wrappy": "1"
      }
    },
    "node_modules/parseurl": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/path-key": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/path-to-regexp": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/pkce-challenge": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=16.20.0"
      }
    },
    "node_modules/proxy-addr": {
      "version": "2.0.7",
      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "forwarded": "0.2.0",
        "ipaddr.js": "1.9.1"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/qs": {
      "version": "6.15.0",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "side-channel": "^1.1.0"
      },
      "engines": {
        "node": ">=0.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/range-parser": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/raw-body": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bytes": "~3.1.2",
        "http-errors": "~2.0.1",
        "iconv-lite": "~0.7.0",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/require-from-string": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/router": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "is-promise": "^4.0.0",
        "parseurl": "^1.3.3",
        "path-to-regexp": "^8.0.0"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/send": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.3",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.1",
        "mime-types": "^3.0.2",
        "ms": "^2.1.3",
        "on-finished": "^2.4.1",
        "range-parser": "^1.2.1",
        "statuses": "^2.0.2"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/serve-static": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "parseurl": "^1.3.3",
        "send": "^1.2.0"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/setprototypeof": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/shebang-command": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "shebang-regex": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/shebang-regex": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/side-channel": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.3",
        "side-channel-list": "^1.0.0",
        "side-channel-map": "^1.0.1",
        "side-channel-weakmap": "^1.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-list": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-map": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-weakmap": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3",
        "side-channel-map": "^1.0.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/statuses": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/toidentifier": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.6"
      }
    },
    "node_modules/type-is": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "content-type": "^1.0.5",
        "media-typer": "^1.1.0",
        "mime-types": "^3.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/unpipe": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/vary": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/which": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "isexe": "^2.0.0"
      },
      "bin": {
        "node-which": "bin/node-which"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/zod": {
      "version": "4.3.6",
      "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    },
    "node_modules/zod-to-json-schema": {
      "version": "3.25.1",
      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "peerDependencies": {
        "zod": "^3.25 || ^4"
      }
    }
  }
}


path: package.json
content: {
  "name": "outline-app-mcp",
  "version": "1.3.4",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.27.1"
  }
}


path: scripts/bulk_sync.mjs
content: import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import fs from "fs";
import path from "path";

/**
 * Outline Media Power-Tool (Bulk Upload & Content Sync)
 * This script combines:
 * 1. attachments.create (API)
 * 2. File Upload (S3/DigitalOcean)
 * 3. update_document (MCP Call - Replace/Append/Prepend)
 * into a single execution to minimize AI agent context waiting time and token burn.
 */

async function run() {
  const action = process.argv[2]; // "bulk-sync"
  const documentId = process.argv[3];
  const filePathsStr = process.argv[4]; // Comma-separated absolute paths
  const templateText = process.argv[5] || ""; // The markdown text template (e.g. "## Sync Log\n{{MEDIA}}\n")
  const editMode = process.argv[6] || "append"; // replace, append, prepend

  const [REDACTED_SECRET];
  const outlineUrl = process.env.OUTLINE_URL;

  if (!apiKey || !outlineUrl) {
    console.error("Error: OUTLINE_API_KEY and OUTLINE_URL are required.");
    process.exit(1);
  }

  if (action !== "bulk-sync") {
    console.error("Unknown action. Only 'bulk-sync' is supported.");
    process.exit(1);
  }

  const filePaths = filePathsStr.split(",").map(p => p.trim()).filter(p => fs.existsSync(p));
  if (filePaths.length === 0 && !templateText) {
    console.error("Error: No files or template text provided.");
    process.exit(1);
  }

  const baseUrl = outlineUrl.replace(/\/mcp$/, "/api");
  const mcpUrl = new URL(outlineUrl);

  console.log(`🚀 Starting Sync for document ${documentId}...`);

  let mediaMarkdown = "";

  for (const filePath of filePaths) {
    const fileName = path.basename(filePath);
    const stats = fs.statSync(filePath);
    const ext = path.extname(filePath).toLowerCase();
    const contentType = ext === ".png" ? "image/png" : ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : "application/octet-stream";

    try {
      // 1. Create Attachment Placeholder
      const createRes = await fetch(`${baseUrl}/attachments.create`, {
        method: "POST",
        headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
        body: JSON.stringify({ documentId, name: fileName, contentType, size: stats.size })
      });
      const createData = await createRes.json();

      if (!createRes.ok) throw new Error(`Create failed: ${JSON.stringify(createData)}`);

      // 2. Upload to Storage
      const formData = new FormData();
      for (const [key, value] of Object.entries(createData.data.form)) {
        formData.append(key, value);
      }
      const fileBuffer = fs.readFileSync(filePath);
      const blob = new Blob([fileBuffer], { type: contentType });
      formData.append("file", blob, fileName);

      const uploadRes = await fetch(createData.data.uploadUrl, { method: "POST", body: formData });
      if (!uploadRes.ok) throw new Error(`Upload failed: ${uploadRes.status}`);

      console.log(`✅ Uploaded: ${fileName}`);

      // 3. Collect Markdown reference
      const attachmentUrl = createData.data.attachment.url;
      mediaMarkdown += `![${fileName}](${attachmentUrl})\n`;

    } catch (err) {
      console.error(`❌ Failed processing ${fileName}:`, err.message);
    }
  }

  // 4. Resolve Template: Replace {{MEDIA}} placeholder with uploaded items
  const finalContent = templateText.includes("{{MEDIA}}") 
    ? templateText.replace("{{MEDIA}}", mediaMarkdown)
    : templateText + "\n" + mediaMarkdown;

  // 5. Final MCP Call
  const transport = new StreamableHTTPClientTransport(mcpUrl, {
    fetch: async (input, init) => {
      const headers = new Headers(init.headers || {});
      headers.set("Authorization", `Bearer ${apiKey}`);
      headers.set("Accept", "application/json, text/event-stream");
      headers.set("Content-Type", "application/json");
      return fetch(input, { ...init, headers });
    }
  });

  const client = new Client({ name: "bulk-sync-tool", version: "1.0.0" }, { capabilities: {} });
  await client.connect(transport);

  try {
    const result = await client.callTool({
      name: "update_document",
      arguments: { id: documentId, text: finalContent, editMode: editMode }
    });
    console.log(`🎊 Sync Complete! editMode: ${editMode}`);
    console.log(JSON.stringify(result, null, 2));
  } catch (error) {
    console.error("❌ Final MCP update Failed:", error);
  } finally {
    if (transport.close) await transport.close();
  }
}

run().catch(console.error);


path: scripts/get_doc.js
content: const https = require('https');

const [REDACTED_SECRET];
const outlineUrl = process.env.OUTLINE_URL.replace('/mcp', '/api');

async function getDocument(id) {
  const url = `${outlineUrl}/documents.info`;
  const data = JSON.stringify({ id });
  
  const options = {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(url, options, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => resolve(JSON.parse(body)));
    });
    req.on('error', reject);
    req.write(data);
    req.end();
  });
}

const documentId = process.argv[2];
if (!documentId) {
  console.error("Error: Document ID is required.");
  process.exit(1);
}

getDocument(documentId).then(doc => {
  console.log(doc.data.text);
}).catch(console.error);


path: scripts/mcp_bridge.mjs
content: import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import fs from "fs";
import path from "path";

async function run() {
  const action = process.argv[2];
  const toolName = process.argv[3];
  const toolArgsStr = process.argv[4];

  const [REDACTED_SECRET];
  const outlineUrl = process.env.OUTLINE_URL;

  if (!apiKey) {
    console.error("Error: OUTLINE_API_KEY environment variable is missing.");
    process.exit(1);
  }

  if (!outlineUrl) {
    console.error("Error: OUTLINE_URL environment variable is missing (e.g., https://your-workspace.getoutline.com/mcp).");
    process.exit(1);
  }

  // Handle direct API actions
  if (action === "api") {
    const apiPath = toolName;
    const body = toolArgsStr ? JSON.parse(toolArgsStr) : {};
    const baseUrl = outlineUrl.replace(/\/mcp$/, "/api");
    const endpoint = `${baseUrl}/${apiPath}`;

    try {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json",
          "Accept": "application/json"
        },
        body: JSON.stringify(body)
      });
      const data = await response.json();
      console.log(JSON.stringify(data, null, 2));
    } catch (error) {
      console.error("Outline API Request Failed:", error);
    }
    return;
  }

  // Handle local file uploads using S3-style form data
  if (action === "upload") {
    const uploadUrl = toolName;
    const uploadDataStr = toolArgsStr; // This should be the full response from attachments.create

    if (!uploadUrl || !uploadDataStr) {
      console.error("Error: uploadUrl and uploadData (JSON string) are required for 'upload' action.");
      process.exit(1);
    }

    try {
      const uploadData = JSON.parse(uploadDataStr);
      const filePath = uploadData.filePath; // We expect filePath to be added to the object for convenience
      
      if (!filePath || !fs.existsSync(filePath)) {
        console.error("Error: Valid filePath is required in uploadData.");
        process.exit(1);
      }

      const formData = new FormData();
      
      // Append all form fields from Outline response
      if (uploadData.data && uploadData.data.form) {
        for (const [key, value] of Object.entries(uploadData.data.form)) {
          formData.append(key, value);
        }
      }

      // Append the file itself (must be the last field for some S3 providers)
      const fileBuffer = fs.readFileSync(filePath);
      const fileName = path.basename(filePath);
      const blob = new Blob([fileBuffer], { type: uploadData.data.form?.["Content-Type"] || "application/octet-stream" });
      formData.append("file", blob, fileName);

      const response = await fetch(uploadUrl, {
        method: "POST",
        body: formData
      });

      if (response.ok) {
        console.log(JSON.stringify({ 
          status: "success", 
          message: `File ${fileName} uploaded successfully.`,
          attachmentId: uploadData.data.attachment.id,
          attachmentUrl: uploadData.data.attachment.url
        }, null, 2));
      } else {
        const errText = await response.text();
        console.error("Upload Failed:", response.status, errText);
      }
    } catch (error) {
      console.error("File Upload Failed:", error);
    }
    return;
  }

  const url = new URL(outlineUrl);

  const customFetch = async (input, init) => {
    const headers = new Headers(init.headers || {});
    headers.set("Authorization", `Bearer ${apiKey}`);
    headers.set("Accept", "application/json, text/event-stream");
    headers.set("Content-Type", "application/json");
    
    return fetch(input, {
      ...init,
      headers
    });
  };

  const transport = new StreamableHTTPClientTransport(url, {
    fetch: customFetch
  });

  const client = new Client(
    { name: "openclaw-outline-bridge", version: "1.0.0" },
    { capabilities: {} }
  );

  await client.connect(transport);

  try {
    if (action === "list") {
      const tools = await client.listTools();
      console.log(JSON.stringify(tools, null, 2));
    } else if (action === "call") {
      if (!toolName) {
        console.error("Error: Tool name is required for 'call' action.");
        process.exit(1);
      }
      const args = toolArgsStr ? JSON.parse(toolArgsStr) : {};
      const result = await client.callTool({ name: toolName, arguments: args });
      console.log(JSON.stringify(result, null, 2));
    } else {
      console.error("Unknown action. Use 'list', 'call <tool>', 'api <endpoint>', or 'upload <url> <jsonData>'.");
    }
  } catch (error) {
    console.error("MCP Request Failed:", error);
  } finally {
    if (transport.close) await transport.close();
  }
}

run().catch(console.error);


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)