@lingochunk/mcp 0.3.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -22
- package/dist/client.d.ts +192 -0
- package/dist/client.js +139 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +23 -0
- package/dist/config.js +41 -14
- package/dist/config.js.map +1 -1
- package/dist/generated/guides.d.ts +13 -0
- package/dist/generated/guides.js +50 -0
- package/dist/generated/guides.js.map +1 -0
- package/dist/http.d.ts +22 -0
- package/dist/http.js +174 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +25 -2
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +26 -0
- package/dist/prompts.js.map +1 -0
- package/dist/tools.d.ts +7 -1
- package/dist/tools.js +652 -51
- package/dist/tools.js.map +1 -1
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -8,18 +8,29 @@ transcripts and clips, and your library.
|
|
|
8
8
|
It is a thin client over the LingoChunk public API (`/api/v1`): read-only tools
|
|
9
9
|
for your vocabulary, transcripts and audio, plus write tools to add review cards,
|
|
10
10
|
export Anki decks and save lessons. The app stays closed source; this repo is
|
|
11
|
-
|
|
11
|
+
the client, the committed API spec, and the skills - and the skills are open to
|
|
12
|
+
contributions (see [CONTRIBUTING.md](CONTRIBUTING.md)).
|
|
12
13
|
|
|
13
|
-
> Install: `/plugin marketplace add
|
|
14
|
+
> Install: `/plugin marketplace add lingochunk/lingochunk-mcp` in Claude Code (server
|
|
14
15
|
> plus lesson skills), or `npx -y @lingochunk/mcp` as a standalone MCP server.
|
|
15
16
|
|
|
17
|
+
**A creator rather than a coder?** If you make audio (a podcast, a YouTube
|
|
18
|
+
channel, classes) and want your episodes to become annotated transcripts,
|
|
19
|
+
lessons, courses and multi-language study guides your audience can use, start
|
|
20
|
+
with **[LingoChunk for creators](docs/creator-guide.md)** - no programming
|
|
21
|
+
required, everything happens by talking to an AI assistant.
|
|
22
|
+
|
|
16
23
|
## What it gives an agent
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
Thirty tools. Twelve read from your account; sixteen write to it; two serve
|
|
26
|
+
guidance: `whats_possible` answers "what can I do here?" with a short menu
|
|
27
|
+
and an example prompt per area, and `get_authoring_guide` serves the full
|
|
28
|
+
authoring craft guides so remote clients that never see the skills still
|
|
29
|
+
compose good lessons, cards and annotations.
|
|
20
30
|
|
|
21
31
|
| Tool | Scope | What it does |
|
|
22
32
|
|---|---|---|
|
|
33
|
+
| `whats_possible` | none | The quick tour: every area this connection covers (discuss, vocabulary, lessons, courses, cards, languages, creator notes, publishing), one example prompt each. The agent answers short and offers to go deeper per area. |
|
|
23
34
|
| `get_vocabulary` | `vocab:read` | Your vocabulary, aggregated per word with FSRS maturity (known/learning/new/due). Filterable; incremental sync via `since` + `cursor` (additive-only, so full-resync periodically). |
|
|
24
35
|
| `lookup_word` | `vocab:read` | One word: your own context plus a shared-lexicon gender/CEFR fallback. Grounds an LLM's guesses. |
|
|
25
36
|
| `list_library` | `content:read` | Your ready-to-study episodes (own + followed collections), cursor-paginated. |
|
|
@@ -27,32 +38,74 @@ the last four (phase 3) write to your account.
|
|
|
27
38
|
| `get_audio_url` | `content:read` | A short-lived presigned URL to the full native audio (Range-capable). |
|
|
28
39
|
| `search_examples` | `content:read` | Example sentences across your library, by word (`lemma`) or text (`q`). A capped sample, not exhaustive. |
|
|
29
40
|
| `get_audio_clip` | `content:read` | Cuts a short native-audio snippet, **saves it to a local file**, and returns `{path, media_type, size_bytes}` for embedding in lessons. |
|
|
41
|
+
| `get_authoring_guide` | none | Returns the full craft guide for `topic` (`overview`, `lesson`, `course`, `cards`, `annotations`, `add-language`, `discuss`) - the same content as the bundled skills, embedded in the package so remote clients get it too. Call it before composing. |
|
|
30
42
|
| `list_decks` | `cards:write` or `decks:export` | Your study decks with card counts, for picking a `deck_id` to add to or export. |
|
|
31
43
|
| `add_card` | `cards:write` | Adds a card to your review queue (FSRS, starts new). Preferred: the `card.v1` kinds (`word`, `phrase`, `collocation`, `idiom`, `chunk`, `grammar`, `cloze`, `contrast`, `qa`, `production`) anchored to a verbatim transcript sentence - the server derives the highlight/blur painting and native-audio clip, so the card matches the app's own. Legacy: `kind=vocab` from your vocabulary, or `kind=custom` front/back. Omit `deck_id` to use the deck for the card's own submission. |
|
|
32
44
|
| `export_anki_deck` | `decks:export` | Exports a deck to Anki `.apkg` (no LLM), polling internally; returns a download URL when ready. A deck with no linked episode can't be exported. |
|
|
33
|
-
| `
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
| `validate_lesson` | `lessons:write` | Dry-run validates a `lesson.v1` document WITHOUT saving it, reporting EVERY problem at once (schema faults with a `loc`, reference faults with the same codes `save_lesson` raises) so you fix a document in one pass. Stores nothing; spends no lesson-cap budget. Call it before `save_lesson`. |
|
|
46
|
+
| `save_lesson` | `lessons:write` | Saves a lesson to your private library (100 max). Preferred: a structured `lesson.v1` document the app renders natively (Lessons tab on the episode, real audio, live word state, Ask AI); returns metadata + an `app_url`. Legacy: a self-contained HTML file (10 MB cap) opened via a short-lived view URL. Optional `course_id` (+ `sequence`) files it under a course. Creators: `visibility:'public'` publishes the lesson to everyone who can view the source episode (documents only, own episodes only). |
|
|
47
|
+
| `list_lessons` | `lessons:write` | Your saved lessons, newest first (id, title, language, format, source episode, and `course_id`/`sequence`/`course_title` when filed under a course), cursor-paginated - for finding ids and seeing what already exists. |
|
|
48
|
+
| `get_lesson` | `lessons:write` | Reads back a saved `lesson.v1` document by id. Closes the revision loop: lessons are immutable, so revise = read -> save new -> delete old. |
|
|
49
|
+
| `delete_lesson` | `lessons:write` | Permanently deletes one saved lesson by id (destructive; owner-scoped server-side). Mainly for iterating: re-saving creates a new lesson, so superseded drafts count against the 100-lesson cap. |
|
|
50
|
+
| `create_course` | `lessons:write` | Creates a course: a named, ordered series to file lessons under. Returns its id, for `save_lesson`'s `course_id`. Authored via the API only (no in-app course editor). |
|
|
51
|
+
| `list_courses` | `lessons:write` | Your courses, newest first, each with its lesson count - for finding a `course_id` or seeing what series exist. |
|
|
52
|
+
| `delete_course` | `lessons:write` | Deletes one course by id (destructive to the grouping, idempotent). Its lessons SURVIVE - their `course_id` is set null, un-grouping them; authored content is never deleted. |
|
|
53
|
+
| `list_languages` | `content:read` | An episode's target languages and how to add more: the fan-out group so far (each with its own submission id + status), `available_targets` (ordinary Groq targets), `simplify_targets` (leveled same-language codes like `de-a2`) and in-progress drafts. |
|
|
54
|
+
| `get_translation_source` | `content:read` | Pages the primary's sentences to translate yourself: source text, the pivot-language gloss per sentence and per token (which fixes each word's sense). Feeds the draft flow. |
|
|
55
|
+
| `add_language` | `translations:write` | Fans an episode out into 1-10 extra **ordinary** target languages server-side (Groq, no tokens of yours); returns a job per language. Leveled same-language codes are rejected here - use the draft flow. |
|
|
56
|
+
| `put_language_translations` | `translations:write` | Uploads a batch (1-100) of agent-written draft sentences (whole-sentence translation + one meaning per token) for a target or leveled language; returns per-sentence rejections to repair. |
|
|
57
|
+
| `commit_language` | `translations:write` | Validates a complete draft and applies it, minting the sibling deck; polls the job and returns the new submission id when ready. |
|
|
58
|
+
| `discard_language_draft` | `translations:write` | Deletes the in-progress draft rows for a language (destructive; never a committed sibling). |
|
|
59
|
+
| `list_annotations` | `content:read` | An episode's creator annotations (each a markdown note on a transcript sentence span), plus `count` and `max_annotations` so you can budget and avoid duplicates. |
|
|
60
|
+
| `create_annotation` | `annotations:write` | Attaches a markdown creator note to a sentence span (Unicode code-point offsets into the sentence's `display`, or a whole-sentence note); the response echoes `selected_text` to verify the span. |
|
|
61
|
+
| `update_annotation` | `annotations:write` | Replaces one annotation's note in place (the anchor stays put). |
|
|
62
|
+
| `delete_annotation` | `annotations:write` | Deletes one annotation (destructive); also how you fix a mis-anchored span before re-creating it. |
|
|
63
|
+
|
|
64
|
+
Plus seven skills:
|
|
65
|
+
|
|
66
|
+
- **`lingochunk-overview`** - the "what can I do?" tour: a short menu of
|
|
67
|
+
every area (with an example prompt each) and instructions to answer
|
|
68
|
+
briefly, then expand whichever area the user picks.
|
|
37
69
|
- **`lingochunk-lesson`** - composes a coursebook-style `lesson.v1` document
|
|
38
70
|
(listen, text, vocabulary, one grammar point, graded exercises, review)
|
|
39
71
|
from the tools above, filtering out words you already know; the app
|
|
40
72
|
renders it natively and can export an offline HTML worksheet.
|
|
73
|
+
- **`lingochunk-course`** - plans a multi-lesson series: slices an episode (or
|
|
74
|
+
collection) into coherent parts, creates a course, then builds N lessons via
|
|
75
|
+
the lesson skill with a different grammar point and ramping difficulty per
|
|
76
|
+
lesson, each filed under the course in order.
|
|
41
77
|
- **`lingochunk-cards`** - builds native-grade flashcards with the `card.v1`
|
|
42
78
|
kinds: verbatim transcript anchors, per-kind guidance (grammar =
|
|
43
79
|
production cloze of the morpheme with a hint), and a quality rubric
|
|
44
80
|
distilled from the known failure modes of AI-generated cards.
|
|
45
81
|
- **`lingochunk-discuss`** - a lighter, conversational "talk me through this
|
|
46
82
|
episode" workflow.
|
|
83
|
+
- **`lingochunk-add-language`** - adds another language to one of your episodes
|
|
84
|
+
as a new sibling deck: either the server-side Groq fan-out for an ordinary
|
|
85
|
+
target, or an agent-supplied translation you write sentence by sentence and
|
|
86
|
+
commit - the only way to build a leveled same-language deck (e.g.
|
|
87
|
+
"German (A2)", German audio glossed in simpler A2 German).
|
|
88
|
+
- **`lingochunk-annotate`** - finds the genuinely useful expressions in one of
|
|
89
|
+
your episodes (idioms, phrasal verbs, collocations, discourse markers,
|
|
90
|
+
culture-bound references) and attaches a short markdown creator note to each
|
|
91
|
+
exact span: an iris tint + note sheet for you, a forward-only note card for
|
|
92
|
+
your followers.
|
|
93
|
+
|
|
94
|
+
The Claude Code plugin installs these as skills. Every client also gets the
|
|
95
|
+
same guidance two other ways that need no plugin: as MCP **prompts** (one per
|
|
96
|
+
skill, named `lingochunk-lesson`, `lingochunk-cards`, ...) in the client's
|
|
97
|
+
prompt/slash picker, and via the **`get_authoring_guide`** tool the model can
|
|
98
|
+
call itself before composing.
|
|
47
99
|
|
|
48
100
|
## Prerequisites
|
|
49
101
|
|
|
50
102
|
- Node.js >= 18.
|
|
51
103
|
- A LingoChunk **personal access token**: in LingoChunk, open Settings -> API
|
|
52
104
|
access, create a token, and grant the scopes you need (`vocab:read` +
|
|
53
|
-
`content:read` cover the read tools; add `cards:write`, `decks:export
|
|
54
|
-
`lessons:write` for the write
|
|
55
|
-
|
|
105
|
+
`content:read` cover the read tools; add `cards:write`, `decks:export`,
|
|
106
|
+
`lessons:write`, `translations:write` and `annotations:write` for the write
|
|
107
|
+
tools). The token is shown once and starts with `lcp_`. The 403 errors from
|
|
108
|
+
the tools name the exact scope you are missing.
|
|
56
109
|
|
|
57
110
|
## Use it
|
|
58
111
|
|
|
@@ -61,13 +114,14 @@ Plus three skills:
|
|
|
61
114
|
This repo is its own plugin marketplace. In Claude Code:
|
|
62
115
|
|
|
63
116
|
```
|
|
64
|
-
/plugin marketplace add
|
|
117
|
+
/plugin marketplace add lingochunk/lingochunk-mcp
|
|
65
118
|
/plugin install lingochunk@lingochunk-mcp
|
|
66
119
|
```
|
|
67
120
|
|
|
68
|
-
The plugin's `.mcp.json` runs the published server via
|
|
69
|
-
(no build step needed) and reads
|
|
70
|
-
in the shell you start Claude
|
|
121
|
+
The plugin's `.mcp.json` runs the published server via npx, pinned to the
|
|
122
|
+
exact version the plugin was released with (no build step needed), and reads
|
|
123
|
+
your token from the environment, so export it in the shell you start Claude
|
|
124
|
+
Code from:
|
|
71
125
|
|
|
72
126
|
```bash
|
|
73
127
|
export LINGOCHUNK_TOKEN=lcp_your_token_here
|
|
@@ -89,6 +143,134 @@ npm install # installs deps and builds dist/ via the prepare script
|
|
|
89
143
|
claude mcp add lingochunk --env LINGOCHUNK_TOKEN=lcp_... -- node /absolute/path/to/lingochunk-mcp/dist/index.js
|
|
90
144
|
```
|
|
91
145
|
|
|
146
|
+
### Option C - hosted server (no install: claude.ai, ChatGPT, Le Chat, ...)
|
|
147
|
+
|
|
148
|
+
The same server also runs hosted at `https://lingochunk.com/mcp` as a
|
|
149
|
+
standard **remote MCP server** (Streamable HTTP). Nothing to install - paste
|
|
150
|
+
the URL into any client that supports custom remote MCP servers and **sign in
|
|
151
|
+
with your LingoChunk account when the client prompts you** (OAuth 2.1 with a
|
|
152
|
+
consent screen; the grant appears in Settings -> API tokens, revocable like
|
|
153
|
+
any token). Clients with a token field can instead send a personal access
|
|
154
|
+
token as a Bearer credential:
|
|
155
|
+
|
|
156
|
+
| Client | Where |
|
|
157
|
+
|---|---|
|
|
158
|
+
| claude.ai (web/desktop/mobile, incl. Free) | Settings -> Connectors -> *Add custom connector* -> URL `https://lingochunk.com/mcp` -> a LingoChunk sign-in/consent screen opens; approve and you are connected |
|
|
159
|
+
| Claude Code | `claude mcp add --transport http lingochunk https://lingochunk.com/mcp` (OAuth on first use), or pass `--header "Authorization: Bearer lcp_..."` to use a token instead |
|
|
160
|
+
| ChatGPT (paid plans) | Settings -> enable *Developer mode* -> Apps -> "+" -> URL (OAuth), or URL + your token |
|
|
161
|
+
| Mistral Le Chat | *+ Add Connector* -> Custom MCP Connector -> URL (auth auto-detected) |
|
|
162
|
+
| Perplexity (Pro/Max), Grok, Manus | add a custom connector/MCP server by URL, sign in or paste an API key |
|
|
163
|
+
|
|
164
|
+
Differences from the local server: `get_audio_clip` is unavailable (it writes
|
|
165
|
+
files, which only makes sense on your own machine - use `get_audio_url`), and
|
|
166
|
+
skills don't auto-load (the server exposes each skill as an MCP **prompt**,
|
|
167
|
+
and agents can pull the same guidance through the `get_authoring_guide` tool).
|
|
168
|
+
|
|
169
|
+
**If your client offers neither OAuth nor a token field**, embed the token in
|
|
170
|
+
the URL: `https://lingochunk.com/mcp/t/lcp_...`. That URL then IS a
|
|
171
|
+
credential - treat it like a password, prefer a token with only the scopes you
|
|
172
|
+
need, and revoke it in Settings if it ever leaks. OAuth sign-in or a header,
|
|
173
|
+
when your client supports one, is always preferred (URLs end up in access
|
|
174
|
+
logs).
|
|
175
|
+
|
|
176
|
+
## Use with other agents
|
|
177
|
+
|
|
178
|
+
Nothing in the server is Claude-specific: it is a standard stdio MCP server,
|
|
179
|
+
so any MCP-capable agent can run it. The recipe is always the same - run
|
|
180
|
+
`npx -y @lingochunk/mcp` with `LINGOCHUNK_TOKEN` in its environment - and
|
|
181
|
+
most clients express it as JSON like this:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"mcpServers": {
|
|
186
|
+
"lingochunk": {
|
|
187
|
+
"command": "npx",
|
|
188
|
+
"args": ["-y", "@lingochunk/mcp"],
|
|
189
|
+
"env": { "LINGOCHUNK_TOKEN": "lcp_your_token_here" }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Where that config lives per client (differences noted):
|
|
196
|
+
|
|
197
|
+
| Client | Where |
|
|
198
|
+
|---|---|
|
|
199
|
+
| Claude Desktop | Settings -> Developer -> Edit Config (`claude_desktop_config.json`) |
|
|
200
|
+
| Cursor | `~/.cursor/mcp.json`, or `.cursor/mcp.json` per project |
|
|
201
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
202
|
+
| VS Code (Copilot agent mode) | `.vscode/mcp.json`, with top-level key `servers` instead of `mcpServers` |
|
|
203
|
+
| Gemini CLI | `~/.gemini/settings.json` |
|
|
204
|
+
| Codex CLI | `~/.codex/config.toml`, as `[mcp_servers.lingochunk]` with the same command/args/env in TOML |
|
|
205
|
+
|
|
206
|
+
Where your client supports it, prefer referencing an environment variable
|
|
207
|
+
over pasting the token into the config file (VS Code can prompt for it via
|
|
208
|
+
`inputs`; CLI clients usually inherit your shell environment).
|
|
209
|
+
|
|
210
|
+
The skills are not Claude-specific either: each one is a plain-markdown
|
|
211
|
+
playbook (`skills/<name>/SKILL.md`). Claude Code auto-loads them through the
|
|
212
|
+
plugin; with any other agent, point it at the file (or paste it as context)
|
|
213
|
+
and ask for a lesson. Every hard guarantee - the schema, verbatim transcript
|
|
214
|
+
quoting, sentence positions - is enforced server-side on save, so the
|
|
215
|
+
quality contract holds no matter which agent is driving.
|
|
216
|
+
|
|
217
|
+
## Updating and checking versions
|
|
218
|
+
|
|
219
|
+
Two artefacts ship from this repo and version separately:
|
|
220
|
+
|
|
221
|
+
| Artefact | Carries | Check the version with |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| Claude Code plugin `lingochunk` | the skills + the server launcher | `claude plugin list`, or `/plugin` -> Installed |
|
|
224
|
+
| npm package `@lingochunk/mcp` | the MCP server (the tools) | `npx -y @lingochunk/mcp --version` |
|
|
225
|
+
|
|
226
|
+
### Update the plugin (Claude Code)
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
/plugin marketplace update lingochunk-mcp
|
|
230
|
+
/reload-plugins
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The first refreshes the marketplace; the second activates updated skills in
|
|
234
|
+
the RUNNING session (new sessions load them automatically). To compare the
|
|
235
|
+
installed version against what the marketplace offers:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
claude plugin list --json --available
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Set-and-forget alternative: `/plugin` -> Marketplaces -> select
|
|
242
|
+
`lingochunk-mcp` -> **Enable auto-update** (third-party marketplaces have it
|
|
243
|
+
disabled by default; official ones are on). With it enabled, Claude Code
|
|
244
|
+
refreshes at startup and prompts `/reload-plugins` when something changed.
|
|
245
|
+
|
|
246
|
+
An update only appears when the plugin's `version` was bumped - Claude Code
|
|
247
|
+
uses it as the cache key, which is why our release rule (CONTRIBUTING.md)
|
|
248
|
+
bumps it on every user-visible change.
|
|
249
|
+
|
|
250
|
+
### Update the MCP server
|
|
251
|
+
|
|
252
|
+
The plugin pins the exact server version in its `.mcp.json`, so **updating
|
|
253
|
+
the plugin updates the server too** - no separate step. A running session
|
|
254
|
+
keeps its old server process; `/reload-plugins` (or a new session)
|
|
255
|
+
reconnects to the new one.
|
|
256
|
+
|
|
257
|
+
**Standalone installs** (`claude mcp add ... npx -y @lingochunk/mcp`, or the
|
|
258
|
+
other-agents configs above) are exposed to an npx trap: npx caches packages
|
|
259
|
+
in `~/.npm/_npx` and does NOT re-check the registry once cached, so an
|
|
260
|
+
unpinned `@lingochunk/mcp` can run a stale server indefinitely. To check and
|
|
261
|
+
fix:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npx -y @lingochunk/mcp --version # what npx actually runs
|
|
265
|
+
npm view @lingochunk/mcp version # latest published
|
|
266
|
+
rm -rf ~/.npm/_npx # blunt fix: clear the npx cache, relaunch
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Or make your config always-fresh by using `@lingochunk/mcp@latest` (checks
|
|
270
|
+
the registry on every launch: ~1-3 s extra startup and a registry
|
|
271
|
+
dependency), or pin `@lingochunk/mcp@<version>` and move the pin yourself
|
|
272
|
+
when you want the update.
|
|
273
|
+
|
|
92
274
|
## Configuration
|
|
93
275
|
|
|
94
276
|
| Variable | Required | Default | Meaning |
|
|
@@ -100,41 +282,80 @@ claude mcp add lingochunk --env LINGOCHUNK_TOKEN=lcp_... -- node /absolute/path/
|
|
|
100
282
|
The token is only ever sent as an `Authorization: Bearer` header to the
|
|
101
283
|
configured origin; it is never written to disk or logged.
|
|
102
284
|
|
|
285
|
+
### Hosted (remote) mode
|
|
286
|
+
|
|
287
|
+
`node dist/index.js --http` serves the same tools over Streamable HTTP for
|
|
288
|
+
remote MCP clients (see Option C). The process is stateless and multi-user:
|
|
289
|
+
`LINGOCHUNK_TOKEN` is not read - every request must carry its own
|
|
290
|
+
`Authorization: Bearer lcp_...` header, which is forwarded verbatim to the
|
|
291
|
+
API (the API stays the sole authority; the server stores nothing). A
|
|
292
|
+
`Dockerfile` is included.
|
|
293
|
+
|
|
294
|
+
| Variable | Required | Default | Meaning |
|
|
295
|
+
|---|---|---|---|
|
|
296
|
+
| `LINGOCHUNK_MCP_PORT` | no | `8100` | Listen port (`PORT` honoured as fallback). |
|
|
297
|
+
| `LINGOCHUNK_BASE_URL` | no | `https://lingochunk.com` | API origin; co-located deployments should point at the loopback origin. |
|
|
298
|
+
|
|
299
|
+
Endpoints: `POST /mcp` (the MCP wire), `GET /health` (liveness).
|
|
300
|
+
|
|
103
301
|
## Building a lesson
|
|
104
302
|
|
|
105
303
|
Ask your agent something like "build me a lesson from yesterday's German episode"
|
|
106
304
|
or "quiz me on the words I'm learning". The `lingochunk-lesson` skill drives the
|
|
107
305
|
workflow: pick the source, pull a transcript slice, gather and **filter** your
|
|
108
|
-
vocabulary (never quizzing you on mastered words),
|
|
109
|
-
|
|
306
|
+
vocabulary (never quizzing you on mastered words), then compose a structured
|
|
307
|
+
`lesson.v1` document that the app renders natively - real episode audio, live
|
|
308
|
+
word state, crosslinks, Ask AI - with an offline HTML export available in-app.
|
|
309
|
+
A submission can hold many lessons, so different skills (or repeated runs) add
|
|
310
|
+
lessons side by side under the episode's Lessons tab. See
|
|
311
|
+
`skills/lingochunk-lesson/`.
|
|
110
312
|
|
|
111
313
|
## Repository layout
|
|
112
314
|
|
|
113
315
|
```
|
|
114
316
|
src/ the MCP server (TypeScript, stdio)
|
|
115
|
-
skills/lingochunk-
|
|
116
|
-
skills/lingochunk-lesson/
|
|
317
|
+
skills/lingochunk-overview/ the "what can I do?" tour skill
|
|
318
|
+
skills/lingochunk-lesson/ the coursebook lesson skill
|
|
319
|
+
skills/lingochunk-course/ the multi-lesson course planner skill
|
|
320
|
+
skills/lingochunk-cards/ the flashcard (card.v1) skill
|
|
117
321
|
skills/lingochunk-discuss/ the "discuss an episode" skill
|
|
322
|
+
skills/lingochunk-add-language/ the add-language / draft-translation skill
|
|
323
|
+
skills/lingochunk-annotate/ the useful-expression annotation skill
|
|
324
|
+
skills/*/examples/ example lesson.v1 documents (CI-validated)
|
|
325
|
+
docs/creator-guide.md the guide for content creators (start here if you make audio)
|
|
326
|
+
docs/skill-authoring.md how to write a new skill
|
|
327
|
+
docs/skill-template.md SKILL.md starting point
|
|
118
328
|
docs/integrations/fluent.md how to plug this into the fluent tutor plugin
|
|
119
329
|
spec/openapi-public-v1.json the committed public API spec (the contract)
|
|
330
|
+
scripts/validate-lesson.ts validate a lesson.v1 document against the spec
|
|
120
331
|
scripts/smoke.ts live smoke test (run by hand, never in CI)
|
|
121
|
-
test/ vitest unit tests (mocked fetch)
|
|
332
|
+
test/ vitest unit tests (mocked fetch) + example validation
|
|
122
333
|
.claude-plugin/plugin.json Claude Code plugin manifest
|
|
123
334
|
.mcp.json MCP server definition for the plugin
|
|
124
335
|
```
|
|
125
336
|
|
|
337
|
+
## Contributing a skill
|
|
338
|
+
|
|
339
|
+
Skills are markdown pedagogy, not code: a `SKILL.md` playbook plus an example
|
|
340
|
+
`lesson.v1` document that CI validates against the committed spec. Anyone can
|
|
341
|
+
contribute one - a dictation drill, an exam rehearsal, a due-words review
|
|
342
|
+
session - and AI-drafted skills are explicitly welcome. Start with
|
|
343
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) and
|
|
344
|
+
[docs/skill-authoring.md](docs/skill-authoring.md).
|
|
345
|
+
|
|
126
346
|
## Development
|
|
127
347
|
|
|
128
348
|
```bash
|
|
129
349
|
npm install # deps + build (prepare)
|
|
130
350
|
npm run build # compile src/ -> dist/
|
|
131
351
|
npm run typecheck # type-check without emitting
|
|
132
|
-
npm test # vitest unit tests (mocked fetch; no network)
|
|
352
|
+
npm test # vitest unit tests (mocked fetch; no network) + skill example validation
|
|
353
|
+
npm run validate:lesson -- <doc.json> # validate a lesson.v1 document (Node 22.6+)
|
|
133
354
|
```
|
|
134
355
|
|
|
135
356
|
`spec/openapi-public-v1.json` is the source contract; it is exported from the
|
|
136
357
|
LingoChunk repo (`make generate-openapi-public`) and refreshed here on each API
|
|
137
|
-
release. This copy was taken from LingoChunk commit `
|
|
358
|
+
release. This copy was taken from LingoChunk commit `31c47289`.
|
|
138
359
|
|
|
139
360
|
### Live smoke test
|
|
140
361
|
|
package/dist/client.d.ts
CHANGED
|
@@ -24,6 +24,139 @@ export interface DeckExportStatus {
|
|
|
24
24
|
status: "ready" | "pending" | "failed" | "none";
|
|
25
25
|
download_url?: string;
|
|
26
26
|
}
|
|
27
|
+
/** One target language present on a submission's fan-out group. */
|
|
28
|
+
export interface SubmissionLanguage {
|
|
29
|
+
language: string;
|
|
30
|
+
submission_id: string;
|
|
31
|
+
status: string;
|
|
32
|
+
is_primary: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** An in-progress agent-supplied translation draft for one language. */
|
|
35
|
+
export interface DraftSummary {
|
|
36
|
+
language: string;
|
|
37
|
+
sentences_drafted: number;
|
|
38
|
+
sentence_count: number;
|
|
39
|
+
}
|
|
40
|
+
/** Result of GET /submissions/{id}/languages. */
|
|
41
|
+
export interface SubmissionLanguages {
|
|
42
|
+
source_language: string;
|
|
43
|
+
languages: SubmissionLanguage[];
|
|
44
|
+
/** Ordinary Groq targets addable via add_language (source + existing removed). */
|
|
45
|
+
available_targets: string[];
|
|
46
|
+
/** Leveled same-language codes (e.g. de-a2) for the draft flow only. */
|
|
47
|
+
simplify_targets: string[];
|
|
48
|
+
drafts: DraftSummary[];
|
|
49
|
+
}
|
|
50
|
+
/** One source token as the Groq translator would see it. */
|
|
51
|
+
export interface SourceToken {
|
|
52
|
+
surface: string;
|
|
53
|
+
lemma: string;
|
|
54
|
+
pos: string;
|
|
55
|
+
pivot_meaning: string;
|
|
56
|
+
}
|
|
57
|
+
/** One source sentence to translate, with its pivot-language gloss. */
|
|
58
|
+
export interface SourceSentence {
|
|
59
|
+
position: number;
|
|
60
|
+
text: string;
|
|
61
|
+
pivot_translation: string;
|
|
62
|
+
tokens: SourceToken[];
|
|
63
|
+
}
|
|
64
|
+
/** Result of GET /submissions/{id}/translation-source (one page). */
|
|
65
|
+
export interface TranslationSource {
|
|
66
|
+
source_language: string;
|
|
67
|
+
pivot_language: string;
|
|
68
|
+
sentence_count: number;
|
|
69
|
+
sentences: SourceSentence[];
|
|
70
|
+
/** null when the page exhausts the submission. */
|
|
71
|
+
next_from_position: number | null;
|
|
72
|
+
}
|
|
73
|
+
/** Result of POST /submissions/{id}/languages (Groq fan-out trigger). */
|
|
74
|
+
export interface AddLanguagesResult {
|
|
75
|
+
jobs: {
|
|
76
|
+
language: string;
|
|
77
|
+
job_id: string;
|
|
78
|
+
}[];
|
|
79
|
+
skipped: {
|
|
80
|
+
language: string;
|
|
81
|
+
reason: string;
|
|
82
|
+
}[];
|
|
83
|
+
}
|
|
84
|
+
/** One draft sentence in a PUT batch: whole-sentence target text (null = no
|
|
85
|
+
* sentence back) plus one meaning per source token, in order. */
|
|
86
|
+
export interface DraftSentence {
|
|
87
|
+
position: number;
|
|
88
|
+
translation: string | null;
|
|
89
|
+
meanings: string[];
|
|
90
|
+
}
|
|
91
|
+
/** Result of PUT /submissions/{id}/translations/{language}. */
|
|
92
|
+
export interface PutTranslationsResult {
|
|
93
|
+
accepted: number;
|
|
94
|
+
rejected: {
|
|
95
|
+
position: number;
|
|
96
|
+
reason: string;
|
|
97
|
+
expected?: number;
|
|
98
|
+
got?: number;
|
|
99
|
+
}[];
|
|
100
|
+
sentences_drafted: number;
|
|
101
|
+
sentence_count: number;
|
|
102
|
+
}
|
|
103
|
+
/** Result of POST /submissions/{id}/translations/{language}/commit. */
|
|
104
|
+
export interface CommitDraftResult {
|
|
105
|
+
job_id: string;
|
|
106
|
+
language: string;
|
|
107
|
+
}
|
|
108
|
+
/** Result of GET /jobs/{id}: status of a fan-out or draft-apply job. */
|
|
109
|
+
export interface JobStatus {
|
|
110
|
+
status: string;
|
|
111
|
+
progress?: number;
|
|
112
|
+
step?: string | null;
|
|
113
|
+
error?: string | null;
|
|
114
|
+
}
|
|
115
|
+
/** Result of DELETE /submissions/{id}/translations/{language}. */
|
|
116
|
+
export interface DeleteDraftResult {
|
|
117
|
+
deleted_sentences: number;
|
|
118
|
+
}
|
|
119
|
+
/** One creator annotation: a markdown note anchored to a transcript sentence,
|
|
120
|
+
* tinted onto a char span or the whole sentence. */
|
|
121
|
+
export interface AnnotationV1 {
|
|
122
|
+
id: number;
|
|
123
|
+
/** The sentence's stable id (survives split/merge), from get_transcript. */
|
|
124
|
+
sentence_id: number;
|
|
125
|
+
/** null (with char_end) for a whole-sentence note; else a code-point offset. */
|
|
126
|
+
char_start: number | null;
|
|
127
|
+
char_end: number | null;
|
|
128
|
+
/** Server snapshot of display[char_start:char_end]; verify it after create. */
|
|
129
|
+
selected_text: string | null;
|
|
130
|
+
note: string;
|
|
131
|
+
/** The sentence was edited since; the app hides the tint until re-anchored. */
|
|
132
|
+
stale: boolean;
|
|
133
|
+
start_time: number | null;
|
|
134
|
+
end_time: number | null;
|
|
135
|
+
}
|
|
136
|
+
/** Result of GET /submissions/{id}/annotations. */
|
|
137
|
+
export interface AnnotationList {
|
|
138
|
+
/** Ordered by sentence, then char_start. */
|
|
139
|
+
annotations: AnnotationV1[];
|
|
140
|
+
count: number;
|
|
141
|
+
/** The per-submission cap, so an agent can budget how many more to add. */
|
|
142
|
+
max_annotations: number;
|
|
143
|
+
}
|
|
144
|
+
/** Body of POST /submissions/{id}/annotations. char_start/char_end are
|
|
145
|
+
* both-or-neither (neither = a whole-sentence note); offsets are Unicode
|
|
146
|
+
* code points into the sentence's display. */
|
|
147
|
+
export interface CreateAnnotationBody {
|
|
148
|
+
sentence_id: number;
|
|
149
|
+
char_start?: number | null;
|
|
150
|
+
char_end?: number | null;
|
|
151
|
+
note: string;
|
|
152
|
+
start_time?: number | null;
|
|
153
|
+
end_time?: number | null;
|
|
154
|
+
}
|
|
155
|
+
/** Result of DELETE /submissions/{id}/annotations/{annotation_id}. */
|
|
156
|
+
export interface DeleteAnnotationResult {
|
|
157
|
+
deleted: boolean;
|
|
158
|
+
annotation_id: number;
|
|
159
|
+
}
|
|
27
160
|
export type QueryValue = string | number | boolean | undefined | null;
|
|
28
161
|
/** How long any single request may take before we abort it. */
|
|
29
162
|
export declare const REQUEST_TIMEOUT_MS = 30000;
|
|
@@ -49,8 +182,16 @@ export declare class LingoChunkClient {
|
|
|
49
182
|
private raiseForStatus;
|
|
50
183
|
/** GET a JSON endpoint. */
|
|
51
184
|
private getJson;
|
|
185
|
+
/** DELETE an endpoint; success is 204 with no body to parse. */
|
|
186
|
+
private deleteNoContent;
|
|
52
187
|
/** POST a JSON body to an endpoint and parse the JSON response. */
|
|
53
188
|
private postJson;
|
|
189
|
+
/** PUT a JSON body to an endpoint and parse the JSON response. */
|
|
190
|
+
private putJson;
|
|
191
|
+
/** PATCH a JSON body to an endpoint and parse the JSON response. */
|
|
192
|
+
private patchJson;
|
|
193
|
+
/** DELETE an endpoint that returns a JSON body (unlike deleteNoContent). */
|
|
194
|
+
private deleteJson;
|
|
54
195
|
getVocabulary(params: Record<string, QueryValue>): Promise<unknown>;
|
|
55
196
|
lookupWord(params: Record<string, QueryValue>): Promise<unknown>;
|
|
56
197
|
listLibrary(params: Record<string, QueryValue>): Promise<unknown>;
|
|
@@ -60,9 +201,60 @@ export declare class LingoChunkClient {
|
|
|
60
201
|
listDecks(): Promise<unknown>;
|
|
61
202
|
addCard(body: object): Promise<unknown>;
|
|
62
203
|
createLesson(body: object): Promise<unknown>;
|
|
204
|
+
/** Dry-run validate a lesson.v1 document without saving it: reports every
|
|
205
|
+
* failing category at once (schema + reference), so a document can be fixed
|
|
206
|
+
* in one pass instead of save -> 400 -> fix cycles. Stores nothing. */
|
|
207
|
+
validateLesson(body: object): Promise<unknown>;
|
|
208
|
+
listLessons(params: Record<string, QueryValue>): Promise<unknown>;
|
|
209
|
+
/** Create a course: a named, ordered series to file lessons under. */
|
|
210
|
+
createCourse(body: object): Promise<unknown>;
|
|
211
|
+
/** The caller's courses, newest first, each with its lesson_count. */
|
|
212
|
+
listCourses(): Promise<unknown>;
|
|
213
|
+
/** Delete a course (owner-scoped). Its lessons survive - the DB sets their
|
|
214
|
+
* course_id NULL, un-grouping them. 404 for a foreign or unknown id. */
|
|
215
|
+
deleteCourse(courseId: string): Promise<void>;
|
|
216
|
+
/** The stored lesson.v1 document. Owner-scoped server-side; 404 for HTML
|
|
217
|
+
* lessons (they have no document) and for foreign/unknown ids. */
|
|
218
|
+
getLessonDocument(lessonId: string): Promise<unknown>;
|
|
219
|
+
/** Owner-scoped server-side: a foreign or unknown id is a 404, never a leak. */
|
|
220
|
+
deleteLesson(lessonId: string): Promise<void>;
|
|
63
221
|
/** Start an Anki export (no body). 400 for a deck with no linked submission. */
|
|
64
222
|
exportDeck(deckId: number): Promise<DeckExportStart>;
|
|
65
223
|
exportDeckStatus(deckId: number): Promise<DeckExportStatus>;
|
|
66
224
|
/** GET a clip as raw audio bytes (the endpoint streams audio, not JSON). */
|
|
67
225
|
getAudioClip(submissionId: string, start: number, end: number): Promise<AudioClip>;
|
|
226
|
+
/** (a) The submission's fan-out group languages, addable targets, leveled
|
|
227
|
+
* simplify targets, and in-progress drafts. */
|
|
228
|
+
listSubmissionLanguages(submissionId: string): Promise<SubmissionLanguages>;
|
|
229
|
+
/** (b) A page of the primary's sentences (source text + pivot glosses) as the
|
|
230
|
+
* Groq translator would see them, for the agent to translate. */
|
|
231
|
+
getTranslationSource(submissionId: string, params: Record<string, QueryValue>): Promise<TranslationSource>;
|
|
232
|
+
/** (c) Trigger the server-side Groq fan-out into extra ordinary target
|
|
233
|
+
* languages; returns a job per accepted language plus per-code skips. */
|
|
234
|
+
addLanguages(submissionId: string, languages: string[]): Promise<AddLanguagesResult>;
|
|
235
|
+
/** (d) Upsert a batch of agent-supplied draft sentences for one language. */
|
|
236
|
+
putTranslations(submissionId: string, language: string, body: {
|
|
237
|
+
generator?: string;
|
|
238
|
+
sentences: DraftSentence[];
|
|
239
|
+
}): Promise<PutTranslationsResult>;
|
|
240
|
+
/** (e) Validate a complete draft and enqueue the apply job that mints the
|
|
241
|
+
* sibling submission. 409 when the draft misses sentence positions. */
|
|
242
|
+
commitTranslationDraft(submissionId: string, language: string): Promise<CommitDraftResult>;
|
|
243
|
+
/** (f) Owner-scoped job status, for polling a fan-out (c) or draft-apply (e)
|
|
244
|
+
* job to completion. */
|
|
245
|
+
getJob(jobId: string): Promise<JobStatus>;
|
|
246
|
+
/** (g) Delete the draft rows for one language (never a committed sibling). */
|
|
247
|
+
deleteTranslationDraft(submissionId: string, language: string): Promise<DeleteDraftResult>;
|
|
248
|
+
/** List a submission's creator annotations (ordered), with the per-submission
|
|
249
|
+
* cap so an agent can budget how many more to add. */
|
|
250
|
+
listAnnotations(submissionId: string): Promise<AnnotationList>;
|
|
251
|
+
/** Create one annotation on a sentence span (or the whole sentence). The
|
|
252
|
+
* response echoes the server's selected_text snapshot so the caller can
|
|
253
|
+
* verify the span it anchored. */
|
|
254
|
+
createAnnotation(submissionId: string, body: CreateAnnotationBody): Promise<AnnotationV1>;
|
|
255
|
+
/** Replace one annotation's markdown note (anchor unchanged; staleness is
|
|
256
|
+
* recomputed against the current sentence). */
|
|
257
|
+
updateAnnotation(submissionId: string, annotationId: number, note: string): Promise<AnnotationV1>;
|
|
258
|
+
/** Delete one annotation. Returns {deleted, annotation_id}. */
|
|
259
|
+
deleteAnnotation(submissionId: string, annotationId: number): Promise<DeleteAnnotationResult>;
|
|
68
260
|
}
|