Skip to main content
Glama
Unfold-it

Unfold It MCP Server

Official
by Unfold-it
README.md
# Unfold It MCP Server

Connect AI assistants to [Unfold It](https://unfoldit.ai) -- create goals with AI-generated plans, agent-assisted clarification, plan import with enrichment, individual progress tracking, cohort analytics, and skill assessments.

Built for platforms (academies, LMS tools, coaching apps) that want to use [Unfold It](https://unfoldit.ai) as their execution layer. Three autonomy tiers: fully autonomous, semi-auto with review, or import your own steps with AI enrichment. Generate skill assessments with AI-validated MCQs, score them, and automatically create targeted learning paths from the results. Tag goals with custom metadata to track and analyze entire cohorts.

## Quick Start

```bash
npx @unfoldit/mcp-server
```

Or install globally:

```bash
npm install -g @unfoldit/mcp-server
```

## Configuration

Set the following environment variables:

| Variable | Required | Description |
|----------|----------|-------------|
| `UNFOLD_API_KEY` | Yes | Org-scoped API key. Generate at app.unfoldit.com -> Organization -> API Keys |
| `UNFOLD_API_URL` | No | API base URL. Defaults to `https://api.unfoldit.com` |

### Claude Desktop / Claude Code

Add to your MCP config (`claude_desktop_config.json` or `.mcp.json`):

```json
{
  "mcpServers": {
    "unfoldit": {
      "command": "npx",
      "args": ["@unfoldit/mcp-server"],
      "env": {
        "UNFOLD_API_KEY": "unfold_sk_..."
      }
    }
  }
}
```

### Cursor

Add to `.cursor/mcp.json` in your project:

```json
{
  "mcpServers": {
    "unfoldit": {
      "command": "npx",
      "args": ["@unfoldit/mcp-server"],
      "env": {
        "UNFOLD_API_KEY": "unfold_sk_..."
      }
    }
  }
}
```

## API Key Scopes

Your API key needs specific scopes depending on which tools you use:

| Scope | Tools | Included by default? |
|-------|-------|---------------------|
| `goals:create` | create_goal, unfold_goal, submit_clarification, import_plan, delete_goal | Yes |
| `goals:read` | get_goal_status, list_goals, get_analytics, get_clarification | Yes |
| `claims:manage` | revoke_claim | Yes |
| `assessment:generate` | generate_skill_assessment | **No** -- must be granted explicitly |
| `assessment:score` | score_skill_assessment | **No** -- must be granted explicitly |
| `assessment:read_capabilities` | get_assessment_capabilities | **No** -- must be granted explicitly |

Assessment scopes are opt-in. Ask your org owner to enable them in the API Key settings.

## Available Tools (13)

### create_goal

Create a goal with an AI-generated plan. The agent auto-answers clarification questions using the context you provide. Set `auto_respond=false` to review agent suggestions before the plan generates. Tag goals with `metadata` to group and analyse entire cohorts.

**Input:**
- `title` (required) -- Goal title
- `description` -- Goal description. More detail produces a better AI plan
- `context` -- Rich context for the agent:
  - `tech_stack` -- e.g. ["Python", "React"]
  - `team_size` -- e.g. 3
  - `timeline` -- e.g. "3 months", "Q3 2026"
  - `constraints` -- e.g. "2 hours per week"
  - `experience_level` -- e.g. "beginner", "advanced"
  - `industry` -- e.g. "fintech"
  - `additional_notes` -- Any other context
- `auto_respond` -- true (default): agent answers all questions. false: returns questions with suggestions for review
- `clarification_answers` -- Pre-set answers by question ID (agent skips these)
- `goal_context` -- "personal" or "professional" (default: "professional")
- `priority` -- "low", "medium", or "high" (default: "medium")
- `claim_expires_in_days` -- Claim link validity (default: 30)
- `progress_share` -- Generate embeddable progress link (default: true)
- `metadata` -- Custom key-value tags for analytics grouping, e.g. `{ cohort: "spring-2026", track: "frontend" }`
- `category` -- Resource category hint: "learning", "health_adhd", or "general". Auto-detected from goal title and description if not provided. Determines which sources and search strategies are used when attaching resources to substeps.
- `resource_world` -- Override resource discovery for this goal:
  - `preferred_sources` -- Source domains to prioritise, e.g. `["coursera.org", "docs.python.org"]`
  - `excluded_sources` -- Source domains to suppress
  - `youtube_playlists` -- YouTube playlist IDs to draw resources from
  - `lms_search_endpoint` -- Custom LMS search endpoint URL
  - `content_policies` -- `{ require_verified_sources, show_disclaimer, disclaimer_text }`
- `assessment` (since v0.7.0) -- Structured assessment input the planner uses to bias step selection. Discriminated by `assessment_type`:
  - `skill_proficiency` v1 -- Drop the `score_skill_assessment` response straight in. The planner prioritises weak facets, compresses strong ones, and anchors steps in `work_item_context`.
  - `clinical_intake` v1 -- ADHD / coaching / clinical context. Wire shape is locked; the prompt builder is currently stubbed and returns `assessment_type_not_supported` until a real partner drives it. Sensitive type -- requires superadmin enablement per org.
  - `general` v1 -- Catch-all for assessment data that does not fit either typed shape. Treated as soft hints; only `constraints` honoured as hard limits.

  See [GUIDE_ASSESSMENT_TO_PLAN_MCP](./docs/guides/ASSESSMENT_TO_PLAN_MCP.md) for the canonical end-to-end walkthrough.
- `request_id` (since v0.8.0, optional, idempotency key) -- Within a 5-minute window, two `create_goal` calls with the same `request_id` return the SAME goal and claim link instead of creating a new one. See [Idempotency](#idempotency) below for the rules. Omit to get a fresh goal on every call (default).

**Returns:** `goalId`, `claimLink`, `claimToken`, `progressLink`, `planGenerationStatus`, `questions` (if auto_respond=false), `agentAnswersUsed`, `idempotentReplay` (since v0.8.0), `claimStatus` (since v0.8.0), `warnings` (always present, empty when none)

### get_goal_status

Get the current status and full step-by-step detail of a goal. Returns individual step data (timestamps, time spent, blocker count, substep progress) when the plan is ready.

**Input:**
- `goal_id` (required) -- The goal ID from create_goal

**Returns:** Goal status, `progress`, `resourceCategory`, `steps[]` (with per-step detail when plan is ready), `metadata`, `claimCreatedAt`, `claimedAt`, `assignedTo`, `agentAnswersUsed`

### get_analytics

Aggregated cohort analytics across all API-created goals. Returns KPIs, at-risk learners, a step-level drop-off funnel, and optional breakdowns by metadata dimension or resource type.

**Input:**
- `group_by` -- Metadata key to break down completion rates by (e.g. "track", "cohort", "department")
- `inactive_days` -- Flag goals with no step activity in this many days as at-risk (default: 7)
- `include_funnel` -- Include step-by-step completion funnel (default: true)
- `include_resources` -- Include resource engagement by type and source (default: false)
- `metadata` -- Filter to a specific cohort or segment, e.g. `{ cohort: "spring-2026" }`
- `date_from` -- ISO date (YYYY-MM-DD). Only include goals created on or after this date
- `date_to` -- ISO date (YYYY-MM-DD). Only include goals created on or before this date

**Returns:**
- `totalGoals`, `activeGoals`, `completedGoals`, `blockedGoals`, `completionRate`, `avgDaysToComplete`
- `claimsTotal`, `claimsClaimed`, `claimsPending`, `claimsExpired`, `avgHoursToClaim`
- `atRiskCount`, `atRiskGoals[]` (goalId, title, metadata, daysInactive, progressPercent)
- `completionByDimension[]` (when group_by is set)
- `stepFunnel[]` (stepOrder, stepTitle, completionRate, avgHoursToComplete)
- `resourceEngagement[]` (when include_resources=true)

### get_clarification

Get pending clarification questions with agent-suggested answers and confidence levels. Use after `create_goal` with `auto_respond=false`.

**Input:**
- `goal_id` (required) -- The goal ID from create_goal

**Returns:** Questions with `agentAnswer`, `agentConfidence` (high/medium/low/fallback), `agentSource`

### submit_clarification

Submit answers to clarification questions and trigger plan generation. Provide your own answers for questions you want to override. Agent suggestions are kept for the rest.

**Input:**
- `goal_id` (required) -- The goal ID from create_goal
- `answers` -- Your answers keyed by question ID (only include overrides)
- `accept_agent_answers` -- Accept agent suggestions for unoverridden questions (default: true)

**Returns:** `goalId`, `status`, `planGenerationStatus`, `agentAnswersUsed`

### import_plan

Import a pre-formulated plan with steps and substeps. Skips clarification entirely. AI enriches steps with dependencies, critical path, duration estimates, severity, complexity, and quick-win flags.

**Input:**
- `title` (required) -- Goal title
- `description` -- Goal description
- `steps` (required) -- Array of steps, each with:
  - `title` (required) -- Step title
  - `description` -- Step description
  - `substeps` -- Optional array of substeps with title, description, type (research/work/decision/verification)
- `enrich` -- Run AI enrichment (default: true). Set false for 0 credits
- `enrich_options` -- Control which enrichment features to run:
  - `dependencies`, `critical_path`, `duration_estimates`, `severity`, `complexity`, `quick_wins`, `resources`
- `goal_context` -- "personal" or "professional" (default: "professional")
- `priority` -- "low", "medium", or "high" (default: "medium")
- `claim_expires_in_days` -- Claim link validity (default: 30)
- `progress_share` -- Generate embeddable progress link (default: true)
- `metadata` -- Custom key-value tags for analytics grouping, e.g. `{ cohort: "spring-2026", track: "backend" }`

**Returns:** `goalId`, `planId`, enriched `steps[]` with metadata, `claimLink`

### list_resource_categories

List available resource categories for goal classification. Returns each category's active providers, content safety policies, and disclaimer text. Use this to build adaptive UIs or to discover which categories are available before creating goals.

**Input:** None

**Returns:** Array of categories, each with `id`, `name`, `description`, `activeProviders`, `showDisclaimer`, `disclaimerText`

### list_goals

List all goals in your org with optional filters. Use `metadata` to filter to a cohort, `category` to segment by goal type, `assigned_email` to look up a specific learner, or `inactive_days` to find at-risk goals without pulling full analytics.

**Input:**
- `status` -- Filter by goal status (draft, in_progress, completed, blocked, paused)
- `claim_status` -- Filter by claim status (unclaimed, claimed, expired, revoked)
- `category` -- Filter by resource category (learning, health_adhd, general). Use this to segment ADHD goals from learning goals in a mixed cohort.
- `metadata` -- Filter by metadata tag(s) in "key=value" format, e.g. `["track=frontend", "cohort=spring-2026"]`
- `assigned_email` -- Return only the goal assigned to this learner email
- `inactive_days` -- Return only goals with no step activity in the last N days (1-365)
- `include_steps` (since v0.9.1) -- Include per-step details (`steps[]`) for each goal whose plan is ready. Off by default to keep list payloads lean; set true to render step lists without a `get_goal_status` call per goal.
- `limit` -- Max results (default: 50, max: 100)
- `offset` -- Pagination offset

**Returns:** Array of goal statuses with `progress`, `resourceCategory`, `metadata`, `claimCreatedAt`, `claimedAt`. When `include_steps=true`, each goal whose plan is ready also carries `steps[]` -- the same per-step shape as `get_goal_status` (timestamps, time spent, blocker count, substep counts). Goals still generating return `steps: null`. Steps are not paginated; the full step list is returned per goal.

### revoke_claim

Invalidate a claim link so it can no longer be used.

**Input:**
- `claim_token` (required) -- The token from the claim link URL

### delete_goal

Delete a goal created via the API. Main use case: after a learner retakes a skill assessment, call `create_goal` with the new `suggested_goal_seed` and then `delete_goal` on the obsolete goal so the dashboard shows the current goal instead of duplicates. Soft delete by default (recoverable); set `hard_delete=true` to remove the row permanently. Scoped to api-created goals -- cannot delete goals a user created in the webapp UI.

**Input:**
- `goal_id` (required) -- UUID of the goal (the `goalId` returned by create_goal)
- `hard_delete` -- If true, permanently delete the row instead of soft-deleting. Default false.

### generate_skill_assessment

Generate a skill-proficiency assessment (MCQs) for a learner. Questions are AI-generated and validated (structural + semantic checks) before being returned. Returns a signed `assessment_token` that the learner's answers are scored against.

**Input:**
- `work_item_context` (required) -- The work item the learner is preparing for:
  - `title` (required) -- Work item title
  - `description` -- Work item description (max 2000 chars)
  - `domain_tags` -- Tags for question anchoring
- `skill` (required) -- Skill to assess (e.g. "Python", "SQL", "Project Management")
- `target_proficiency` (required) -- Band the learner should reach: "beginner", "low", "medium", "high"
- `num_questions` (required) -- Number of MCQs to generate (3-20)
- `difficulty_mix` -- Distribution as `{easy, medium, hard}` floats summing to 1.0. Default: `{easy: 0.2, medium: 0.5, hard: 0.3}`
- `band_thresholds` -- Custom proficiency band ranges. Default: beginner [0,10], low [11,50], medium [51,85], high [86,100]
- `language` -- ISO language code (default: "en")
- `request_id` (required) -- Idempotency key. Same request_id returns the same assessment.

**Returns:** `assessment_token`, `questions[]` (stem, options, difficulty, skill_facet), `band_map`, `max_raw_score`, `target_band`, `model_meta`

**Requires scope:** `assessment:generate`

### score_skill_assessment

Score a submitted assessment using the signed `assessment_token` from `generate_skill_assessment`. Returns the learner's proficiency band, gap vs target, and per-question results. When the learner falls short, includes a `suggested_goal_seed` you can pass to `create_goal` to create a targeted learning path.

**Input:**
- `assessment_token` (required) -- The signed token from generate_skill_assessment
- `answers` (required) -- Array of `{question_id, selected_option_id}` (at least one)
- `band_thresholds` -- Optional override of proficiency band ranges (defaults to the thresholds embedded in the token)
- `request_id` (required) -- Idempotency key

**Returns:** `raw_score`, `max_raw_score`, `raw_pct`, `band`, `target_band`, `gap_bands`, `per_question[]`, `per_facet[]` (server-side aggregation per sub-skill with `total`, `correct`, `raw_pct`, `classification: weak | strong | mixed`), `weak_facets[]`, `strong_facets[]`, `facet_coverage` (`full` | `partial` | `difficulty_fallback`), `recommended_action` (none / create_unfold_goal), `suggested_goal_seed`

> **Chaining tip:** This response is shape-compatible with `create_goal`'s new `assessment` field (skill_proficiency v1). Drop it in with three header fields added (`assessment_type: "skill_proficiency"`, `schema_version: "v1"`, `assessed_at: <ISO timestamp>`) and the planner uses it directly. No client-side join logic, no threshold tuning.

**Requires scope:** `assessment:score`

### get_assessment_capabilities

Get supported parameters for skill assessments. Use this to introspect before calling `generate_skill_assessment`. No input parameters required.

**Returns:** `schema_version`, `supported_languages`, `min_questions`, `max_questions`, `supported_proficiency_bands`, `default_band_thresholds`, `default_difficulty_mix`, `open_domain`, `token_ttl_seconds`

**Requires scope:** `assessment:read_capabilities`

## Idempotency

> Available since **v0.8.0**.

`create_goal` accepts an optional `request_id` so a partner can retry safely without producing duplicate goals.

**The rules**

1. **Scope the key to one logical operation.** A "logical operation" is one learner / one enrollment. Construct the key from your own per-learner identifier, for example:
   ```
   request_id = "enrollment:42:learner:7"
   request_id = "course:python-101:user:abc-123"
   ```
2. **Reuse the key on retries, not on new learners.** If a network blip or process restart makes you re-send the same call for the same learner, use the same `request_id`. If you are creating a goal for a DIFFERENT learner, generate a NEW key.
3. **Do NOT derive the key from the body.** Two learners enrolled in the same course will produce identical title / description / assessment payloads. If you reuse a body-derived key, the second learner will receive a claim link the first learner already claimed.
4. **Without a `request_id`, every call creates a fresh goal.** This is the default and is always safe.

**Cache window**

Two calls with the same tenant + `request_id` within 5 minutes return the same response. After 5 minutes the key falls out of cache and a fresh call creates a new goal. Best-effort across pod restarts and horizontal scaling today; treat the window as a retry helper, not a deduplication guarantee.

**Detecting a replay**

Two new fields on the response tell you what happened:

- `idempotentReplay: true` -- this response was served from cache. A prior call with the same `request_id` produced this goal.
- `claimStatus: "unclaimed" | "claimed" | "expired" | "revoked"` -- current state of the underlying claim token, refreshed from the DB on every replay. `null` on a fresh create (implicitly "unclaimed").

Branch on this when handling retries:

```ts
const result = await create_goal({ title, request_id: enrollmentKey });

if (result.idempotentReplay && result.claimStatus && result.claimStatus !== "unclaimed") {
  // The link was already consumed by the original recipient. Don't
  // forward it again -- prompt the human or open a support ticket
  // depending on your flow.
} else {
  // Safe to forward the claimLink to the learner.
}
```

## How It Works

### Tier 1 -- Semi-Auto (Review agent suggestions)

1. Call `create_goal` with `auto_respond=false` and your context
2. Get back questions with agent-suggested answers and confidence levels
3. Review suggestions, override any you disagree with
4. Call `submit_clarification` to trigger plan generation
5. Poll `get_goal_status` until `planGenerationStatus` is "completed"

### Tier 2 -- Full-Auto (Agent handles everything)

1. Call `create_goal` with context (auto_respond defaults to true)
2. Agent answers all clarification questions using your context + user history
3. Plan generates in the background (15-30s)
4. Get a claim link immediately -- send it to your user
5. Poll `get_goal_status` for completion and `agentAnswersUsed` transparency

### Tier 3 -- Import (Bring your own steps)

1. Call `import_plan` with your steps and substeps
2. AI enriches with dependencies, durations, severity, critical path
3. Plan is ready immediately (no clarification needed)
4. Get a claim link and enriched step metadata

### Assess-then-Learn (Assessment to goal) [updated v0.7.0]

The canonical chain that turns a scored assessment into a personalised plan, with no client-side join logic:

1. Call `generate_skill_assessment` with the skill, target proficiency, and work item context
2. Present questions to the learner in your UI
3. Call `score_skill_assessment` with the token and the learner's answers. Response includes `per_facet`, `weak_facets`, `strong_facets`, `facet_coverage` (server-side aggregation).
4. Call `create_goal` with the score response dropped into the new `assessment` field (skill_proficiency v1). The planner uses weak/strong facets to bias steps and anchors them in `work_item_context`.
5. Send the claim link to the learner.

Full walkthrough with payload examples for `skill_proficiency`, `general`, and `clinical_intake` lives at [GUIDE_ASSESSMENT_TO_PLAN_MCP](./docs/guides/ASSESSMENT_TO_PLAN_MCP.md).

> **Legacy path.** Pre-v0.7.0 integrations stuffed the score into `additional_context.unfold_assessment` per the [payload convention](./docs/guides/ASSESSMENT_PAYLOAD_CONVENTION.md). That still works on `POST /api/v1/ext/goals` for backwards compat; behind the scenes both paths now route through the same prompt-builder registry. New integrations should use the structured `assessment` field on `create_goal`.

## Example Prompts

> "Create a Python certification learning path for a beginner with 2 hours per week for 3 months."

> "Import our Jira sprint backlog as a goal with dependencies and time estimates."

> "Create a coaching plan for Sarah but let me review the questions before generating the plan."

> "Create an ADHD morning routine coaching plan with category health_adhd for a patient who struggles with time blindness."

> "List all health_adhd goals in the spring cohort and show me which ones are at risk."

> "Show me all goals where the claim link hasn't been used yet."

> "What's the progress on goal abc-123? Has the learner started?"

> "Generate a Python assessment with 8 questions for someone who needs medium proficiency to work on the ML pipeline."

> "Score this assessment and create a goal from the results if the learner didn't reach the target."

## Getting an API Key

1. Go to [app.unfoldit.com](https://app.unfoldit.com)
2. Create or switch to your organization
3. Go to Organization settings
4. Scroll to **API Keys** section
5. Click **+ Create Key**, give it a name, and copy the key

## Typed errors and warnings (v0.7.0+)

When a tool fails with a typed error, the response is a structured JSON envelope rather than a stringified message. Branch on `error_code`:

| `error_code` | When | Notes |
|---|---|---|
| `models_not_configured` | BYO provider role is not configured | Response includes `settings_url` |
| `provider_unauthorized` | BYO provider key rejected | Includes `settings_url` and `switch_to_unfold_ai` CTA |
| `provider_quota_exceeded` | BYO provider returned quota/billing error | Includes `switch_to_unfold_ai` CTA |
| `provider_unavailable` | BYO provider 5xx or circuit breaker open | Transient; retry later |
| `assessment_type_not_supported` | `assessment_type` recognised but builder not yet wired | Response includes `supported` list |
| `assessment_type_not_enabled_for_org` | Tenant has not opted into this assessment type | Sensitive types (clinical_intake) need superadmin enablement |
| `token_invalid` / `assessment_expired` | Assessment token tampered or past TTL | Regenerate via generate_skill_assessment |
| `idempotency_conflict` | Same request_id used with different request body | Pick a new request_id |
| `validation_failed` | Generation output failed validation after retry budget | Retry with different request_id |

Successful responses on goal creation also carry `warnings: ApiWarning[]` (always present, empty when none). Known warning codes:

| `code` | When |
|---|---|
| `category_assessment_type_mismatch` | `category` and `assessment.assessment_type` disagree. Plan was generated using assessment_type. |
| `duplicate_assessment_input` | Both structured `assessment` field and legacy `additional_context.unfold_assessment` envelope sent. Structured won. |

Both branches preserve the structured envelope so AI coding agents can deterministically branch on the `error_code` or warning `code` strings. See the [versioning policy](./docs/guides/MCP_VERSIONING.md) for stability guarantees on these codes.

## Versioning

This package follows semver. Pin a minor version range (`"@unfoldit/mcp-server": "^0.8.0"`) -- you will get fixes and additive features automatically; breaking changes require a major version bump. We support the latest two minor versions; older minors receive security fixes only.

Note: in the pre-1.0 era, each minor bump (e.g. 0.7.x -> 0.8.0) may include additive surface changes such as new optional request fields or new response fields. Existing code keeps working; opt in to new behaviour as you need it.

See [GUIDE_MCP_VERSIONING](./docs/guides/MCP_VERSIONING.md) for the full policy. See [CHANGELOG.md](./CHANGELOG.md) for what changed in each release.

## Learn More

- [Unfold It](https://unfoldit.ai) -- AI-powered goal planning and execution platform
- [Developers](https://unfoldit.ai/developers) -- API and MCP documentation
- [GitHub](https://github.com/Unfold-it/unfoldit-mcp-server) -- Source code and issues
- [Assessment-to-Plan guide](./docs/guides/ASSESSMENT_TO_PLAN_MCP.md) -- End-to-end walkthrough for the score-to-goal chain
- [Versioning policy](./docs/guides/MCP_VERSIONING.md) -- Semver discipline, supported-versions window, deprecation policy

## License

MIT

TDQS

A4.4/5.0

Scored across 13 tools

Disambiguation5/5

Each tool has a clearly distinct purpose, covering goal CRUD, claim management, clarification, plan import, analytics, and assessment lifecycle. No overlap in functionality, and descriptions make boundaries explicit.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern in snake_case (e.g., create_goal, list_goals, generate_skill_assessment). No deviations or mixed conventions.

Tool Count5/5

With 13 tools, the set is well-scoped for the domain. It provides comprehensive operations without being bloated or sparse, fitting the typical 3-15 range.

Completeness5/5

The tool surface covers the full lifecycle: goal creation, status tracking, listing, deletion; claim management; clarification handling; plan import; assessment generation, scoring, and capabilities; analytics; and resource categories. No obvious gaps for the domain.

Maintenance

ActivityInactive
ResponsivenessNo issues