---
phase: 01-linkedin-video-upload
plan: 02
type: execute
depends_on: ["01-01"]
files_modified:
  - src/tools.ts
  - src/index.ts
---

<objective>
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.
</objective>

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

<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")' }
</context>

<tasks>

<task type="auto">
  <name>Task 1: Register linkedin_upload_video MCP tool in tools.ts</name>
  <files>src/tools.ts</files>
  <action>
1. Add `createVideoPost as createLinkedInVideoPost` to the existing linkedin import on line 7.
   Keep all other imports on that line unchanged.

2. 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 30–90 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'],
     },
   }

3. 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;
  </action>
  <verify>npx tsc --noEmit passes; grep -n 'linkedin_upload_video' src/tools.ts shows both the definition and the case handler</verify>
  <done>Tool appears in tools array and switch statement, tsc clean</done>
</task>

<task type="auto">
  <name>Task 2: Add POST /api/linkedin/video REST route in index.ts</name>
  <files>src/index.ts</files>
  <action>
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.
  </action>
  <verify>npx tsc --noEmit passes; grep -n '/api/linkedin/video' src/index.ts shows the new route</verify>
  <done>Route registered, tsc clean, follows exact pattern of adjacent routes</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>
    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 }.
  </what-built>
  <how-to-verify>
    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
  </how-to-verify>
  <resume-signal>Type "approved" if the post appeared on LinkedIn with video, or describe the error to fix</resume-signal>
</task>

</tasks>

<verification>
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)
</verification>

<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>

<output>
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.
</output>
