Files
garfieldheron 0b9d863b38 docs(01-linkedin-video-upload): create phase plan
Phase 01: LinkedIn video upload support
- 2 plans created (01-01, 01-02)
- 5 tasks total: client upload flow, createVideoPost, MCP tool definition, REST route, e2e verify checkpoint
- Ready for execution
2026-05-11 12:14:31 -04:00

7.4 KiB
Raw Permalink Blame History

phase, plan, type, depends_on, files_modified
phase plan type depends_on files_modified
01-linkedin-video-upload 02 execute
01-01
src/tools.ts
src/index.ts
Wire createVideoPost() into the MCP tool layer and REST API.

Purpose: The upload client from Plan 01 is only callable internally. This plan exposes it as a named MCP tool (linkedin_upload_video) and a REST endpoint (POST /api/linkedin/video) so Claude and external callers can use it.

Output: A fully registered MCP tool + REST route that accepts video_url, text, visibility, and account, and returns { success, post_id, url } — matching the pattern of linkedin_create_post.

<execution_context> ~/.claude/get-shit-done/workflows/execute-plan.md ./summary.md ~/.claude/get-shit-done/references/checkpoints.md </execution_context>

@src/tools.ts @src/index.ts @src/clients/linkedin.ts

Established patterns to follow exactly:

tools.ts tool definition (copy linkedin_create_post structure, lines ~256-274): { name, description, inputSchema: { type: 'object', properties: {...}, required: [...] } }

tools.ts import (line 7): import { getProfile as getLinkedInProfile, createPost as createLinkedInPost, ... } from './clients/linkedin.js'; → add createVideoPost to this import

tools.ts handler (lines ~778-787, inside handleToolCall switch): case 'linkedin_upload_video': result = await createLinkedInVideoPost({ ... }, customer); break;

index.ts REST route (lines ~747-756, follow app.post('/api/linkedin/post') pattern): app.post('/api/linkedin/video', requireAuth, async (req, res) => { ... }) → calls handleToolCall('linkedin_upload_video', { video_url, text, visibility, account })

Tool input schema fields: video_url: { type: 'string', description: 'Publicly accessible URL of the video file to upload' } — required text: { type: 'string', description: 'Post caption / commentary text' } — required visibility: { type: 'string', enum: ['PUBLIC','CONNECTIONS'], description: 'Post visibility. Default: PUBLIC' } account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' }

Task 1: Register linkedin_upload_video MCP tool in tools.ts src/tools.ts 1. Add `createVideoPost as createLinkedInVideoPost` to the existing linkedin import on line 7. Keep all other imports on that line unchanged.
  1. In the tools array, insert a new tool definition immediately after the linkedin_create_post entry (around line 274). Use this exact structure: { name: 'linkedin_upload_video', description: 'Upload a video and create a LinkedIn post. Accepts a publicly accessible video URL, downloads it server-side, uploads it to LinkedIn via the Videos API, and publishes the post. Video must be publicly reachable (not localhost or private network). Large videos may take 3090 seconds.', inputSchema: { type: 'object', properties: { video_url: { type: 'string', description: 'Publicly accessible URL of the MP4 video to upload' }, text: { type: 'string', description: 'Post caption / commentary text' }, visibility: { type: 'string', enum: ['PUBLIC', 'CONNECTIONS'], description: 'Post visibility. Default: PUBLIC' }, account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' }, }, required: ['video_url', 'text'], }, }

  2. In handleToolCall switch, add the handler case immediately after 'linkedin_create_post': case 'linkedin_upload_video': result = await createLinkedInVideoPost( { video_url: args.video_url as string, text: args.text as string, visibility: (args.visibility as 'PUBLIC' | 'CONNECTIONS') ?? 'PUBLIC', account: args.account as string | undefined, }, customer ); break; npx tsc --noEmit passes; grep -n 'linkedin_upload_video' src/tools.ts shows both the definition and the case handler Tool appears in tools array and switch statement, tsc clean

Task 2: Add POST /api/linkedin/video REST route in index.ts src/index.ts Insert immediately after the `app.post('/api/linkedin/post', ...)` block (around line 756):

app.post('/api/linkedin/video', requireAuth, async (req, res) => { try { const { video_url, text, visibility, account } = req.body; const result = await handleToolCall('linkedin_upload_video', { video_url, text, visibility, account }); res.json(result); } catch (err) { res.status(500).json({ error: String(err) }); } });

Do NOT add input validation beyond what handleToolCall already does — the tool's required fields check ('video_url', 'text') already throws if missing. npx tsc --noEmit passes; grep -n '/api/linkedin/video' src/index.ts shows the new route Route registered, tsc clean, follows exact pattern of adjacent routes

Full linkedin_upload_video tool: client (uploadVideo + createVideoPost), MCP tool definition, REST route POST /api/linkedin/video. The server fetches the video from video_url, uploads it to LinkedIn in 4MB chunks, waits up to 90s for LinkedIn to process it, then publishes the post and returns { success, post_id, url }. 1. Build: npm run build — must complete without errors 2. Start server: npm start (or node dist/index.js) in a separate terminal 3. Test the MCP tool via Claude by saying: "Upload the SquareMCP LinkedIn video to LinkedIn with text [your caption]" and providing the video_url pointing to a publicly accessible MP4 (or use the rendered out/squaremcp-linkedin.mp4 hosted somewhere accessible) 4. Confirm the response contains { success: true, post_id: "urn:li:share:...", url: "https://www.linkedin.com/feed/update/..." } 5. Visit the returned URL in a browser and confirm the video post appears with correct caption 6. Check the audit log (if Redis is running) shows the linkedin:createVideoPost entry Type "approved" if the post appeared on LinkedIn with video, or describe the error to fix Before declaring plan complete: - [ ] `npm run build` (full TypeScript build) succeeds with zero errors - [ ] `grep -n 'linkedin_upload_video' src/tools.ts` returns two matches (definition + case) - [ ] `grep -n '/api/linkedin/video' src/index.ts` returns one match - [ ] `grep -n 'createLinkedInVideoPost' src/tools.ts` returns two matches (import + call)

<success_criteria>

  • linkedin_upload_video appears in MCP tool list (visible to Claude at http://localhost:3000/mcp)
  • POST /api/linkedin/video route returns { success, post_id, url } on valid input
  • Error from missing video_url or text propagates as 500 with { error: "..." }
  • Build is clean — no TypeScript errors in any file
  • Phase complete: LinkedIn video upload fully wired from URL → LinkedIn post </success_criteria>
After completion, create `.planning/phases/01-linkedin-video-upload/01-02-SUMMARY.md`:

Phase 01 Plan 02: Tool + Route Wiring Summary

[one-liner of what shipped]

Accomplishments

Files Created/Modified

Decisions Made

Issues Encountered

Next Step

Phase complete — linkedin_upload_video tool is live.