# Skyp MCP Skill Guide

You are working with the Skyp MCP server — an email and LinkedIn campaign management platform that lets you create, manage, and analyze outreach campaigns programmatically.

## Authentication

All tools require authentication via an API key passed in the MCP client config. If a tool returns an authentication error, the user needs to check their API key in **Settings > API Keys** on the Skyp dashboard.

## Campaign Lifecycle

Campaigns follow a strict lifecycle. Always follow this order:

1. **Create** the campaign (`create_new_campaign`)
2. **Add contacts** (`add_contact_to_existing_campaign`)
3. **Generate a strategy** (`generate_campaign_strategy`) — this creates the sequence plan with timing and focus for each email
4. **Review the strategy** (`get_strategy`) — present it to the user before proceeding
5. **Finalize** (`finalize_campaign`) — this triggers AI message generation for all contacts and cannot be undone

Never finalize a campaign without contacts and a strategy. The campaign must have both before finalization will succeed.

## Tool Reference

### Campaigns

- `list_campaigns(skip?, limit?)` — List all campaigns with status and metrics. Returns: id, name, description, status, contacts_count, sent/opens/clicks/replies/bounce counts.
- `get_campaign_details(campaign_id)` — Full campaign config: touches, max_words, closing_style, tone, timezone, sending_times, sending_days, plus all metrics.
- `create_new_campaign(name, description?, touches?, max_words?, closing_style?, tone?, timezone?)` — Create a campaign. Defaults: 3 touches, 80 max words, soft close, America/New_York timezone. Tone is a list of strings like `["friendly", "professional"]`. Closing style is one of: `soft`, `mix`, `hard`.
- `update_existing_campaign(campaign_id, name?, description?, touches?, max_words?, closing_style?, tone?)` — Update campaign config. Only provided fields change. Campaigns in active, evergreen, or completed status are locked — only status changes are allowed. Pause the campaign first to edit config.
- `pause_email_campaign(campaign_id)` — Pause an active or evergreen campaign. Halts all sending. The pre-pause status is saved so the campaign can be resumed to the correct state.
- `resume_email_campaign(campaign_id)` — Resume a paused campaign. Restores it to its pre-pause status (active or evergreen).
- `finalize_campaign(campaign_id)` — Start AI message generation. Campaign must have contacts and a strategy. This is irreversible — always confirm with the user first.
- `get_campaign_messages(campaign_id, skip?, limit?)` — Get sent messages with contact info, open/reply status, and full reply details including sentiment and interest level.
- `get_campaign_threads(campaign_id, skip?, limit?)` — Get conversation threads grouped by contact. Each thread shows all messages sent and replies received. Useful for reviewing full conversations.

### Strategy

- `generate_campaign_strategy(campaign_id)` — Generate an AI-powered sequence strategy. Creates a plan with gap timing (days between emails), focus area, and suggested CTA for each touch. Saves automatically.
- `get_strategy(campaign_id)` — Retrieve the saved strategy. Returns touches, total_days, strategy_summary, and per-message details (order, gap, focus, suggested_cta).

### Contacts

- `list_contacts(skip?, limit?)` — List all contacts with full details including twitter and timestamps.
- `create_new_contact(email, first_name?, last_name?, title?, company?, location?, linkedin?, context?)` — Create a contact. Email is required. The `context` field is used for AI email personalization — include relevant details about the contact here.
- `update_existing_contact(contact_id, first_name?, last_name?, email?, title?, company?, location?, linkedin?, context?)` — Update contact fields. Only provided fields change.
- `delete_existing_contact(contact_id)` — Permanently delete a contact. Cannot be undone.
- `add_contact_to_existing_campaign(campaign_id, email, first_name?, last_name?, title?, company?, context?)` — Add a contact to a campaign. Creates the contact if it doesn't exist. If the campaign is already active, this also triggers message generation for the new contact automatically.

### Analytics

- `get_account_analytics()` — Account-level metrics over the last 30 days: total_contacts, active_campaigns, emails_sent_30d, delivery_rate, open_rate, click_rate, reply_rate, bounce_rate, positive_replies, negative_replies, neutral_replies. Rates are percentages (0-100).

### LinkedIn Campaigns

- `list_linkedin_campaigns()` — List all LinkedIn campaigns with status and lead funnel metrics (pending, invite_sent, message_sent, replied).
- `get_linkedin_campaign_details(campaign_id)` — Full campaign config: session, messages, limits, scheduling, and lead funnel breakdown.
- `create_linkedin_campaign(linkedin_session_id, name, followup_message, connection_note?, invites_per_launch?, weekly_invite_limit?, daily_message_limit?, timezone?, sending_hours_start?, sending_hours_end?, sending_days?)` — Create a LinkedIn outreach campaign in draft status. Requires a LinkedIn session ID. `connection_note` is the optional note with connection requests (max 300 chars). `followup_message` is sent after connection is accepted. Defaults: 10 invites/launch, 100/week limit, 9-17 hours, weekdays only.
- `update_linkedin_campaign(campaign_id, name?, connection_note?, followup_message?, invites_per_launch?, weekly_invite_limit?, daily_message_limit?, timezone?, sending_hours_start?, sending_hours_end?, sending_days?)` — Update campaign settings. Only allowed when campaign is in draft or paused status.
- `activate_linkedin_campaign(campaign_id)` — Activate a campaign to start automated connection requests and followup messages. Requires an active LinkedIn session and at least one lead.
- `pause_linkedin_campaign(campaign_id)` — Pause an active campaign. Can be reactivated later.
- `add_linkedin_leads(campaign_id, leads)` — Bulk add up to 500 leads. Each lead requires `linkedin_url`; optional: `first_name`, `last_name`, `headline`, `company`. Cannot add to completed campaigns.
- `list_linkedin_leads(campaign_id, status_filter?)` — List leads with optional status filter. Valid statuses: `pending`, `invite_sent`, `message_sent`, `replied`, `invite_failed`, `message_failed`, `skipped`.
- `send_connection_request(linkedin_url, connection_note?)` — Send a single LinkedIn connection request immediately. Uses the user's active LinkedIn session directly — no campaign required. `connection_note` is an optional message (max 300 chars) included with the request.

## Recommended Patterns

### Before taking action, discover first

Always call `list_campaigns` or `list_contacts` before operating on a specific item. Use the returned IDs for subsequent calls. Never guess or hardcode IDs.

### Use context for better personalization

When creating contacts, populate the `context` field with relevant details (recent achievements, shared connections, mutual interests). This is what the AI uses to personalize emails.

### Batch contact additions

When adding multiple contacts to a campaign, call `add_contact_to_existing_campaign` for each one. There is no bulk endpoint — but each call is fast and will create the contact if it doesn't exist.

### Present strategy before finalizing

After `generate_campaign_strategy`, always retrieve and present the strategy to the user with `get_strategy`. Let them review the sequence plan (timing, focus areas, CTAs) before calling `finalize_campaign`. Finalization generates all emails and cannot be reversed.

### Interpreting analytics

- **delivery_rate**: Percentage of emails that didn't bounce. Below 95% may indicate list quality issues.
- **open_rate**: Percentage of delivered emails opened. Industry average for cold email is 40-60%.
- **reply_rate**: Percentage of sent emails that got replies. Above 5% is strong for cold outreach.
- **Sentiment breakdown**: positive/negative/neutral reply counts. Focus follow-up on positive replies.

### Pagination

List endpoints support `skip` and `limit` parameters (default limit: 50). For large datasets, paginate by incrementing `skip`. The `total` field in responses tells you the full count.

## Common Workflows

### Full campaign setup

```
1. create_new_campaign(name="Q2 Outreach", description="Target Series A founders", touches=3, tone=["friendly", "concise"])
2. add_contact_to_existing_campaign(campaign_id, email="jane@acme.co", first_name="Jane", ...)  // repeat for each contact
3. generate_campaign_strategy(campaign_id)
4. get_strategy(campaign_id)  // present to user for review
5. finalize_campaign(campaign_id)  // only after user confirms
```

### Morning briefing

```
1. get_account_analytics()  // overview metrics
2. list_campaigns()  // find active campaigns
3. get_campaign_threads(campaign_id)  // check for new replies on each active campaign
```

### Contact research and outreach

```
1. list_contacts()  // see existing contacts
2. create_new_contact(email, first_name, ..., context="Recently raised Series A, expanding engineering team")
3. add_contact_to_existing_campaign(campaign_id, email)  // add to relevant campaign
```

### Editing an evergreen campaign

Evergreen campaigns are config-locked. To make changes, pause first, then resume:

```
1. pause_email_campaign(campaign_id)  // pauses the campaign, saves "evergreen" as previous status
2. update_existing_campaign(campaign_id, max_words=120, tone=["direct"])  // now editable
3. resume_email_campaign(campaign_id)  // restores to evergreen status automatically
```

### LinkedIn campaign setup

```
1. create_linkedin_campaign(linkedin_session_id, name="Q2 LinkedIn Outreach", followup_message="Hi {name}, thanks for connecting! ...", connection_note="Hi, I'd love to connect...")
2. add_linkedin_leads(campaign_id, leads=[{linkedin_url: "https://linkedin.com/in/janedoe", first_name: "Jane", company: "Acme"}])
3. activate_linkedin_campaign(campaign_id)  // starts automated connection requests
```

### LinkedIn campaign monitoring

```
1. list_linkedin_campaigns()  // see all campaigns with funnel stats
2. list_linkedin_leads(campaign_id, status_filter="replied")  // check who replied
3. list_linkedin_leads(campaign_id, status_filter="invite_failed")  // check for failures
```

## LinkedIn Campaign Lifecycle

LinkedIn campaigns follow a different lifecycle than email campaigns:

1. **Create** the campaign (`create_linkedin_campaign`) — requires a LinkedIn session ID (set up via the dashboard)
2. **Add leads** (`add_linkedin_leads`) — bulk add LinkedIn profile URLs
3. **Activate** (`activate_linkedin_campaign`) — starts the automation:
   - Connection requests are sent automatically every 2 hours during sending hours (weekdays by default)
   - Followup messages are sent at 9:30am and 2:30pm to leads whose connections were accepted
4. **Monitor** (`list_linkedin_leads`) — track the funnel: pending → invite_sent → message_sent → replied
5. **Pause/Resume** (`pause_linkedin_campaign` / `activate_linkedin_campaign`) — pause and resume as needed

LinkedIn sessions (the `li_at` cookie) must be configured through the Skyp dashboard — they are not managed via MCP tools for security reasons.

## Error Handling

- **"Campaign not found or access denied"** — The campaign ID is wrong or belongs to a different user/API key.
- **"Contact not found or access denied"** — Same as above, for contacts.
- **"No fields to update"** — An update call was made without any fields to change.
- **"LinkedIn session not found"** — The session ID is wrong or belongs to a different user.
- **"LinkedIn session is not active"** — The session cookie has expired. The user needs to re-authenticate on the dashboard.
- **"Campaign must have at least one lead"** — Cannot activate a LinkedIn campaign without leads.
- **"Only active or evergreen campaigns can be paused"** — The campaign is not in active or evergreen status.
- **"Campaign is {status} and can only have its status changed"** — Active, evergreen, or completed campaigns are config-locked. Pause the campaign first to make changes.
- **"Cannot add leads to a completed campaign"** — The campaign has finished and no longer accepts leads.
- **Authentication errors** — The API key is missing, invalid, or revoked. Direct the user to Settings > API Keys.

## Important Constraints

- Campaign IDs and contact IDs are UUIDs. Always use the exact IDs returned by list/create tools.
- `touches` must be between 1 and 7.
- `max_words` must be between 30 and 150.
- `closing_style` must be one of: `soft`, `mix`, `hard`.
- `timezone` must be a valid IANA timezone string (e.g., `America/New_York`, `Europe/London`).
- Finalization is irreversible. Never call `finalize_campaign` without explicit user confirmation.
- LinkedIn `connection_note` max length is 300 characters (LinkedIn platform limit).
- LinkedIn `invites_per_launch` must be between 1 and 25.
- LinkedIn `weekly_invite_limit` must be between 1 and 100.
- LinkedIn `daily_message_limit` must be between 1 and 150.
- LinkedIn `sending_days` is a list of integers: 0=Monday through 6=Sunday.
- LinkedIn sessions are managed through the dashboard, not via MCP tools.
