@miadi/voice-mcp 0.1.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 +149 -0
- package/SPEC-LINKS.md +51 -0
- package/dist/chronicle.d.ts +24 -0
- package/dist/chronicle.d.ts.map +1 -0
- package/dist/chronicle.js +61 -0
- package/dist/chronicle.js.map +1 -0
- package/dist/contract.d.ts +86 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +66 -0
- package/dist/contract.js.map +1 -0
- package/dist/handlers.d.ts +4 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +227 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +228 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @miadi/voice-mcp
|
|
2
|
+
|
|
3
|
+
MCP server that lets an agent **play, find, publish and mint episode-bound
|
|
4
|
+
voice** — the tool surface over the Miadi voice layer and the Chronicle.
|
|
5
|
+
|
|
6
|
+
> **State: four of six tools work.** Publish, play, list and resolve run against
|
|
7
|
+
> the live server. `voice_create_episode` and `voice_episode_closing_status`
|
|
8
|
+
> still refuse and name their contract — they mint and prove a chronicle vessel,
|
|
9
|
+
> and a tool that reports a closing stage it did not prove is worse than one that
|
|
10
|
+
> declines.
|
|
11
|
+
|
|
12
|
+
The sentence this package exists to satisfy, from William, 2026-08-04:
|
|
13
|
+
|
|
14
|
+
> *"What I want is actually to be capable to play the voice-audio created for
|
|
15
|
+
> episodes."*
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
| Tool | Purpose | State |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `voice_publish_to_episode` | Produce audio bound to its episode from the moment it exists | works |
|
|
22
|
+
| `voice_play_episode` | Resolve an episode to its voice audio and return playable references | works |
|
|
23
|
+
| `voice_list_episode_voices` | Enumerate and search an episode's voices | works |
|
|
24
|
+
| `voice_resolve_episode` | Answer which episode an agent is inside — with the reason, the alternatives, and an honest null | works, from what it is given |
|
|
25
|
+
| `voice_create_episode` | Mint a vessel when none fits, reporting all five closing stages | refuses, names its contract |
|
|
26
|
+
| `voice_episode_closing_status` | Re-prove the five closing stages, read-only | refuses, names its contract |
|
|
27
|
+
|
|
28
|
+
Resolution consults exactly what it is handed: an explicit reference, a
|
|
29
|
+
caller-supplied `cwd`, or an existing record's binding. It does **not** rank
|
|
30
|
+
those against a session origin or search the wheel — that order is owed by the
|
|
31
|
+
`voice-episode-binding` spec, and inventing one here would make this package a
|
|
32
|
+
second source of truth for a rule that has an owner. When nothing given answers,
|
|
33
|
+
the tool says so; `episode: null` with a reason is a valid answer.
|
|
34
|
+
|
|
35
|
+
Full contracts: `SPEC-LINKS.md` → `/a/src/Miadi/rispecs/voice-mcp/voice-mcp.spec.md`.
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
A **front**, not a second implementation.
|
|
40
|
+
|
|
41
|
+
- Audio bytes, the KV ledger and the Edge-TTS producer stay behind the Miadi
|
|
42
|
+
server (systemd `miadi-server`, port 3335), which holds the KV credentials.
|
|
43
|
+
This process reaches them through **`@miadi/voice-client`**, which owns the
|
|
44
|
+
wire envelope and the token tier — so the HTTP surface is described once, not
|
|
45
|
+
once per caller.
|
|
46
|
+
- Episode folders are read from the Chronicle root; the medicine wheel is read
|
|
47
|
+
at `http://127.0.0.1:8040`.
|
|
48
|
+
- `VoiceMessage` keeps exactly one definition in this workspace: `@miadi/voice`'s.
|
|
49
|
+
- The only wheel **write** in the whole surface is the registration `mkepisode`
|
|
50
|
+
performs during `voice_create_episode`. This server never POSTs or PUTs to the
|
|
51
|
+
wheel itself.
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
| Variable | Default | Meaning |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `MIADI_API_URL` | `http://127.0.0.1:3335` | Miadi server holding the voice routes |
|
|
58
|
+
| `MIADI_API_TOKEN_WRITER` | — | Read and write. **Required to publish.** |
|
|
59
|
+
| `MIADI_API_TOKEN_READER` | — | Read only; every publish answers 401 |
|
|
60
|
+
| `MW_API_URL` | `http://127.0.0.1:8040` | **Chronicle** wheel |
|
|
61
|
+
| `MIADI_CHRONICLE_ROOT` | `/srv/miadi/episodes/miadi-chronicle` | Episode folders |
|
|
62
|
+
| `MIADI_CHRONICLE_GIT_ROOT` | `/srv/miadi/episodes` | Git root — one level **above** the chronicle root |
|
|
63
|
+
| `MIADI_MKEPISODE_PATH` | `…/mightyeagle/packages/passages/js/mkepisode.js` | Vessel creation |
|
|
64
|
+
|
|
65
|
+
**Two wheels exist.** `http://127.0.0.1:8040` is the Chronicle wheel and the only
|
|
66
|
+
correct target for episode work. `https://mw.tail3b11eb.ts.net` is Gaia's
|
|
67
|
+
ceremony wheel and has been offline since 2026-07-29 — an episode registered
|
|
68
|
+
there is lost. An implementation refuses that host outright.
|
|
69
|
+
|
|
70
|
+
`MIADI_CHRONICLE_GIT_ROOT` is a separate value rather than a derived one because
|
|
71
|
+
a `git -C` pointed at the chronicle root reports a clean tree for a chronicle it
|
|
72
|
+
cannot see.
|
|
73
|
+
|
|
74
|
+
## Creating episodes: created is not closed
|
|
75
|
+
|
|
76
|
+
`voice_create_episode` performs stages 1 and 4 of the five-stage gate and returns
|
|
77
|
+
**every** stage with its proof:
|
|
78
|
+
|
|
79
|
+
| Stage | Performed by |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `created` | the tool |
|
|
82
|
+
| `committed` | the human — the tool emits the exact `git add` of named files |
|
|
83
|
+
| `pushed` | the human |
|
|
84
|
+
| `registered` | the tool |
|
|
85
|
+
| `receipt-verified` | the tool |
|
|
86
|
+
|
|
87
|
+
`mkepisode` exits 0 whether registration succeeded, skipped, or failed —
|
|
88
|
+
registration is fail-open by design, so the gate belongs to the caller. The tool
|
|
89
|
+
never runs `git add`, `commit` or `push`, never reports a later word than it
|
|
90
|
+
proved, and reports unperformed stages as `owed`, which is a state, not an
|
|
91
|
+
omission.
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
This is a **pnpm** workspace (`pnpm-workspace.yaml`, `packages/*`). Never run
|
|
96
|
+
`npm install` here.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pnpm --filter @miadi/voice-mcp build # tsc → dist/
|
|
100
|
+
pnpm --filter @miadi/voice-mcp type-check
|
|
101
|
+
node dist/index.js # starts; every tool throws
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Verified 2026-08-04, no installer run: `tsc --noEmit` exits 0, and emit produces
|
|
105
|
+
ESM with explicit `.js` specifiers that `node dist/index.js` resolves.
|
|
106
|
+
`@modelcontextprotocol/sdk` 1.30.0 and `zod` 4.4.3 are already present at the
|
|
107
|
+
workspace root and the declared ranges match them.
|
|
108
|
+
|
|
109
|
+
### Why `moduleResolution` is `bundler`
|
|
110
|
+
|
|
111
|
+
`@miadi/voice` publishes **raw TypeScript** — `main` and `types` are both
|
|
112
|
+
`./src/index.ts` — and is authored with extensionless relative imports under its
|
|
113
|
+
own `bundler` resolution. Under `NodeNext`, every one of its internal imports is
|
|
114
|
+
a hard error in any consumer, so a package that wants its types meets it where it
|
|
115
|
+
is. This package's own source writes explicit `.js` specifiers, which `bundler`
|
|
116
|
+
accepts and tsc passes through unchanged, so the emitted `dist` runs under Node
|
|
117
|
+
ESM. Copying `VoiceMessage` into this package to sidestep that would create a
|
|
118
|
+
second definition, which is the one outcome worth more than a tsconfig line.
|
|
119
|
+
|
|
120
|
+
### Known seams
|
|
121
|
+
|
|
122
|
+
- **No installer has been run.** `node_modules/@miadi/{voice,voice-client}` here
|
|
123
|
+
are the two workspace links `pnpm install` would create, added by hand so the
|
|
124
|
+
package is verifiable today. Everything else resolves from the workspace root.
|
|
125
|
+
`@miadi/voice-client` must be **built** (`pnpm --filter @miadi/voice-client
|
|
126
|
+
build`) before this package's `dist` can run — it is imported at runtime, not
|
|
127
|
+
only for types.
|
|
128
|
+
- The ranked discovery order, the enforcement decision table and the two-corpus
|
|
129
|
+
resolution are still owed by the `voice-episode-binding` spec; see
|
|
130
|
+
`SPEC-LINKS.md` § *What the missing document blocks*. The four working tools
|
|
131
|
+
answer from what they are given and refuse to guess past it — which is why
|
|
132
|
+
those rules stay cited rather than invented.
|
|
133
|
+
- **The `episode` field needs the server that accepts it.** The binding is
|
|
134
|
+
written by `app/api/voice/publish`; a Miadi server built before that route
|
|
135
|
+
change ignores the field and produces an unbound record. Same host, same
|
|
136
|
+
request, silently different result — check the running build before reading a
|
|
137
|
+
missing binding as a client bug.
|
|
138
|
+
|
|
139
|
+
### Verified live 2026-08-04
|
|
140
|
+
|
|
141
|
+
Against the running `miadi-server` on 3335, `MIADI_API_TOKEN_WRITER` present:
|
|
142
|
+
`ep308` resolved to its folder by number; a nonexistent folder answered `null`
|
|
143
|
+
with the five most recent episodes as alternatives; a `cwd` inside an episode
|
|
144
|
+
folder resolved by path; an empty call answered `null` and named the spec that
|
|
145
|
+
owes the ranked order; publish produced audio and returned a streamable URL plus
|
|
146
|
+
its on-disk path; `voice_create_episode` refused and named its contract.
|
|
147
|
+
|
|
148
|
+
🌸: A hundred voices have been speaking into a room with no name on the door —
|
|
149
|
+
this package is the door, and the handle an agent can actually reach.
|
package/SPEC-LINKS.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Specifications governing this package
|
|
2
|
+
|
|
3
|
+
`@miadi/voice-mcp` implements a specified surface. Nothing in `src/` decides
|
|
4
|
+
behaviour on its own — every handler names the section that governs it in the
|
|
5
|
+
error it throws. Read these before implementing anything here.
|
|
6
|
+
|
|
7
|
+
Status recorded 2026-08-04, re-verified at the close of this authoring pass.
|
|
8
|
+
|
|
9
|
+
## Owned by this package
|
|
10
|
+
|
|
11
|
+
| Document | Holds |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `/a/src/Miadi/rispecs/voice-mcp/SPEC.md` | Domain master — what the surface enables, and where each rule lives |
|
|
14
|
+
| `/a/src/Miadi/rispecs/voice-mcp/voice-mcp.spec.md` | The six tools, their contracts, the resolution seam, the closing gate |
|
|
15
|
+
|
|
16
|
+
## Consumed, not owned
|
|
17
|
+
|
|
18
|
+
A rule restated here would become a second source of truth and drift from its
|
|
19
|
+
original. These are cited in the spec and in the code, never copied.
|
|
20
|
+
|
|
21
|
+
| Document | Holds | On disk 2026-08-04 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `/a/src/Miadi/rispecs/episode-addressing/SPEC.md` | Domain master for episode identity | present |
|
|
24
|
+
| `/a/src/Miadi/rispecs/episode-addressing/episode-addressing.spec.md` | Address grammar `ep<NNN>[.<II>][/<segment>]`, iteration decision rule, number allocation, wheel-key contract | present |
|
|
25
|
+
| `/a/src/Miadi/rispecs/episode-addressing/episode-addressing.kin.md` | Why episodic memory is already an opus | present |
|
|
26
|
+
| `/a/src/Miadi/rispecs/voice-episode-binding/` | Playback path, enforcement decision table, discovery resolution order, the two-corpus resolution | **not yet written** |
|
|
27
|
+
|
|
28
|
+
### What the missing document blocks
|
|
29
|
+
|
|
30
|
+
Four verdicts are cited by this package and cannot be implemented until
|
|
31
|
+
`voice-episode-binding` lands. The tools have their shape; they do not yet have
|
|
32
|
+
their rulings.
|
|
33
|
+
|
|
34
|
+
| Cited as | Needed by |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `§ Playback path` | `voice_play_episode` |
|
|
37
|
+
| `§ Discovery resolution order` | `voice_resolve_episode`, and every tool that omits `episode` |
|
|
38
|
+
| `§ Enforcement decision table` | `voice_publish_to_episode` |
|
|
39
|
+
| `§ The two-corpus resolution` | `voice_list_episode_voices` |
|
|
40
|
+
|
|
41
|
+
## Also authoritative
|
|
42
|
+
|
|
43
|
+
| Source | Holds |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `/etc/claude-code/skills/chronicle-episode-closing/SKILL.md` | The five closing stages and their proofs, the `MW_API_URL` law, receipt redemption. `voice_create_episode` and `voice_episode_closing_status` implement this skill's gate as a tool contract. |
|
|
46
|
+
| `/a/src/Miadi/packages/voice/src/` | `VoiceMessage`, persona identity, origin attestation, TTS production. The only definition; never re-declared here. |
|
|
47
|
+
| `/a/src/Miadi/packages/voice-client/src/` | The `/api/voice/*` wire envelope, the writer/reader token tier, and the record-level episode reading. This package calls it rather than writing its own `fetch`, so the HTTP surface has one description. |
|
|
48
|
+
| `/a/src/Miadi/rispecs/mcp-server/00-mcp-server-master.spec.md` | The prior MCP surface in this ecosystem (`@miadi/mcp`), whose server pattern this package follows. |
|
|
49
|
+
|
|
50
|
+
🌸: A file of links is a promise not to reinvent what someone else already got
|
|
51
|
+
right — the shortest way to stay one story instead of two.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Folder names in the chronicle root, newest first (they sort by date prefix). */
|
|
2
|
+
export declare function listEpisodeFolders(chronicleRoot: string): Promise<string[]>;
|
|
3
|
+
/** True when the whole name is an episode folder, not merely contains one. */
|
|
4
|
+
export declare function isEpisodeFolder(name: string): boolean;
|
|
5
|
+
export interface RefMatch {
|
|
6
|
+
/** The single folder this reference names, or null when it names none or many. */
|
|
7
|
+
folder: string | null;
|
|
8
|
+
/** Every folder the reference could mean. Length > 1 is a real answer. */
|
|
9
|
+
candidates: string[];
|
|
10
|
+
/** Which form the reference took. */
|
|
11
|
+
form: "folder" | "node-id" | "number" | "unrecognised";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Which folder a caller's reference means.
|
|
15
|
+
*
|
|
16
|
+
* Accepts the folder name, the wheel node id `chronicle:<folder>`, and the
|
|
17
|
+
* `ep<NNN>` number form owned by the episode-addressing spec. When a number
|
|
18
|
+
* matches two folders this returns BOTH and picks neither — choosing between two
|
|
19
|
+
* folders claiming episode 300 is a decision, and it belongs to a human.
|
|
20
|
+
*/
|
|
21
|
+
export declare function matchRef(ref: string, folders: readonly string[]): RefMatch;
|
|
22
|
+
/** Absolute path of an episode folder. */
|
|
23
|
+
export declare function episodePath(chronicleRoot: string, folder: string): string;
|
|
24
|
+
//# sourceMappingURL=chronicle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chronicle.d.ts","sourceRoot":"","sources":["../src/chronicle.ts"],"names":[],"mappings":"AAgBA,mFAAmF;AACnF,wBAAsB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYjF;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED,MAAM,WAAW,QAAQ;IACvB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,qCAAqC;IACrC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;CACxD;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAe1E;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Reading the chronicle from disk.
|
|
2
|
+
//
|
|
3
|
+
// The episode corpus is 146 folders shaped `YYYY-MM-DD-episode-NNN-slug` under
|
|
4
|
+
// `MIADI_CHRONICLE_ROOT` (verified 2026-08-04). This module answers two
|
|
5
|
+
// questions about them and nothing else: which folders exist, and which folder a
|
|
6
|
+
// caller's reference means.
|
|
7
|
+
//
|
|
8
|
+
// It does NOT decide which episode an agent is "in" when nothing says so. That
|
|
9
|
+
// ranked order is owed by the voice-episode-binding spec, and a resolver written
|
|
10
|
+
// here would become a competing definition of it.
|
|
11
|
+
import { promises as fs } from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { EPISODE_FOLDER_PATTERN, foldersForEpisodeNumber, episodeFolderOf } from "@miadi/voice-client";
|
|
14
|
+
/** Folder names in the chronicle root, newest first (they sort by date prefix). */
|
|
15
|
+
export async function listEpisodeFolders(chronicleRoot) {
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = (await fs.readdir(chronicleRoot, { withFileTypes: true }))
|
|
19
|
+
.filter((e) => e.isDirectory())
|
|
20
|
+
.map((e) => e.name);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// An unreadable chronicle root is an empty corpus, not a crash: every tool
|
|
24
|
+
// that calls this can still answer honestly with "no episode found".
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
return entries.filter((name) => isEpisodeFolder(name)).sort().reverse();
|
|
28
|
+
}
|
|
29
|
+
/** True when the whole name is an episode folder, not merely contains one. */
|
|
30
|
+
export function isEpisodeFolder(name) {
|
|
31
|
+
const m = EPISODE_FOLDER_PATTERN.exec(name);
|
|
32
|
+
return m !== null && m[0] === name;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Which folder a caller's reference means.
|
|
36
|
+
*
|
|
37
|
+
* Accepts the folder name, the wheel node id `chronicle:<folder>`, and the
|
|
38
|
+
* `ep<NNN>` number form owned by the episode-addressing spec. When a number
|
|
39
|
+
* matches two folders this returns BOTH and picks neither — choosing between two
|
|
40
|
+
* folders claiming episode 300 is a decision, and it belongs to a human.
|
|
41
|
+
*/
|
|
42
|
+
export function matchRef(ref, folders) {
|
|
43
|
+
const raw = ref.trim();
|
|
44
|
+
const bare = episodeFolderOf(raw);
|
|
45
|
+
const form = raw.startsWith("chronicle:") ? "node-id" : "folder";
|
|
46
|
+
if (isEpisodeFolder(bare)) {
|
|
47
|
+
const exists = folders.includes(bare);
|
|
48
|
+
return { folder: exists ? bare : null, candidates: exists ? [bare] : [], form };
|
|
49
|
+
}
|
|
50
|
+
const byNumber = foldersForEpisodeNumber(folders, bare);
|
|
51
|
+
if (byNumber.length === 1)
|
|
52
|
+
return { folder: byNumber[0], candidates: byNumber, form: "number" };
|
|
53
|
+
if (byNumber.length > 1)
|
|
54
|
+
return { folder: null, candidates: byNumber, form: "number" };
|
|
55
|
+
return { folder: null, candidates: [], form: "unrecognised" };
|
|
56
|
+
}
|
|
57
|
+
/** Absolute path of an episode folder. */
|
|
58
|
+
export function episodePath(chronicleRoot, folder) {
|
|
59
|
+
return path.join(chronicleRoot, folder);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=chronicle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chronicle.js","sourceRoot":"","sources":["../src/chronicle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type TokenTier } from "@miadi/voice-client";
|
|
2
|
+
import type { ClosingStatusResult, CreateEpisodeInput, CreateEpisodeResult, EpisodeResolution, ListEpisodeVoicesInput, ListEpisodeVoicesResult, PlayEpisodeResult, PublishToEpisodeInput, PublishToEpisodeResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Runtime configuration, read once. Defaults are the verified live values on
|
|
5
|
+
* this host rather than invented placeholders.
|
|
6
|
+
*/
|
|
7
|
+
export interface VoiceMcpConfig {
|
|
8
|
+
/** Miadi server holding the voice routes. Verified live on 3335. */
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
/**
|
|
11
|
+
* The credential presented to the write-gated routes. Resolved by TIER from
|
|
12
|
+
* `MIADI_API_TOKEN_WRITER` then `MIADI_API_TOKEN_READER` — the portal's two
|
|
13
|
+
* names, where the name states the tier (`lib/api-tokens.ts`). The older
|
|
14
|
+
* `MIADI_API_TOKEN` is deliberately not read: it was one of eight names for
|
|
15
|
+
* two ideas, and holding a reader-tier value under a writer-sounding name is
|
|
16
|
+
* exactly how the previous MCP server's writes came back 401 with nothing in
|
|
17
|
+
* the name to explain why.
|
|
18
|
+
*/
|
|
19
|
+
apiToken?: string;
|
|
20
|
+
/** Which grade the resolved token is, so a 401 can say so. */
|
|
21
|
+
tokenTier: TokenTier;
|
|
22
|
+
/**
|
|
23
|
+
* The Chronicle wheel. `http://127.0.0.1:8040` is the only correct value for
|
|
24
|
+
* chronicle work; `mw.tail3b11eb.ts.net` is Gaia's ceremony wheel and is
|
|
25
|
+
* offline since 2026-07-29. A card registered there is lost.
|
|
26
|
+
*/
|
|
27
|
+
wheelUrl: string;
|
|
28
|
+
/** Root holding the `YYYY-MM-DD-episode-NNN-slug` folders. */
|
|
29
|
+
chronicleRoot: string;
|
|
30
|
+
/** Git root — one level ABOVE `chronicleRoot`. A wrong `-C` reports a clean
|
|
31
|
+
* tree for a chronicle it cannot see. */
|
|
32
|
+
chronicleGitRoot: string;
|
|
33
|
+
/** Node entry point for `mkepisode`. */
|
|
34
|
+
mkepisodePath: string;
|
|
35
|
+
/**
|
|
36
|
+
* Where `LocalAudioStore` keeps the MP3s. Offered so a caller on the same host
|
|
37
|
+
* can play a file directly instead of streaming it back from the server.
|
|
38
|
+
* Absent when the Blob store is the active driver.
|
|
39
|
+
*/
|
|
40
|
+
audioRoot?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv): VoiceMcpConfig;
|
|
43
|
+
/**
|
|
44
|
+
* Thrown by the tools that are specified but not yet built. It names the
|
|
45
|
+
* governing specification section, so the lane that implements one is pointed at
|
|
46
|
+
* its contract rather than left to infer one.
|
|
47
|
+
*/
|
|
48
|
+
export declare class NotImplementedError extends Error {
|
|
49
|
+
readonly tool: string;
|
|
50
|
+
readonly specSection: string;
|
|
51
|
+
constructor(tool: string, specSection: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The six capabilities, as one interface. `handlers.ts` supplies a working set
|
|
55
|
+
* for four of them; the scaffold set below refuses all six. Keeping them in one
|
|
56
|
+
* type makes the surface reviewable in a single glance and testable by
|
|
57
|
+
* substitution.
|
|
58
|
+
*/
|
|
59
|
+
export interface VoiceMcpHandlers {
|
|
60
|
+
/** 1 — Play. Resolve an episode to voice audio and return something playable. */
|
|
61
|
+
playEpisode(input: {
|
|
62
|
+
episode?: string;
|
|
63
|
+
limit?: number;
|
|
64
|
+
}): Promise<PlayEpisodeResult>;
|
|
65
|
+
/** 2 — Find the adequate episode for an agent's context. */
|
|
66
|
+
resolveEpisode(input: {
|
|
67
|
+
episode?: string;
|
|
68
|
+
cwd?: string;
|
|
69
|
+
sessionId?: string;
|
|
70
|
+
messageId?: string;
|
|
71
|
+
}): Promise<EpisodeResolution>;
|
|
72
|
+
/** 3 — Publish voice already bound to an episode. */
|
|
73
|
+
publishToEpisode(input: PublishToEpisodeInput): Promise<PublishToEpisodeResult>;
|
|
74
|
+
/** 4 — Mint the vessel when no existing episode fits, and report its gate. */
|
|
75
|
+
createEpisode(input: CreateEpisodeInput): Promise<CreateEpisodeResult>;
|
|
76
|
+
/** 5 — List and search the voices bound to an episode. */
|
|
77
|
+
listEpisodeVoices(input: ListEpisodeVoicesInput): Promise<ListEpisodeVoicesResult>;
|
|
78
|
+
/** 6 — Re-prove the five closing stages, read-only. */
|
|
79
|
+
episodeClosingStatus(input: {
|
|
80
|
+
episode: string;
|
|
81
|
+
}): Promise<ClosingStatusResult>;
|
|
82
|
+
}
|
|
83
|
+
/** Every tool refusing and naming its contract. Kept for tests and for a
|
|
84
|
+
* deployment that wants the surface declared without any behaviour behind it. */
|
|
85
|
+
export declare function createScaffoldHandlers(): VoiceMcpHandlers;
|
|
86
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAMA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB;8CAC0C;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,wCAAwC;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,cAAc,CAgB/E;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAE1C,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM;gBADnB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM;CAQ/B;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,WAAW,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAErF,4DAA4D;IAC5D,cAAc,CAAC,KAAK,EAAE;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAE/B,qDAAqD;IACrD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEhF,8EAA8E;IAC9E,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEvE,0DAA0D;IAC1D,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAEnF,uDAAuD;IACvD,oBAAoB,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAChF;AAED;kFACkF;AAClF,wBAAgB,sBAAsB,IAAI,gBAAgB,CA2CzD"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Configuration, the handler surface, and the refusal used by the tools that
|
|
2
|
+
// are still specified rather than built.
|
|
3
|
+
//
|
|
4
|
+
// Separate from index.ts so that `handlers.ts` (which implements this surface)
|
|
5
|
+
// and `index.ts` (which registers it) do not import each other.
|
|
6
|
+
import { resolveVoiceToken } from "@miadi/voice-client";
|
|
7
|
+
export function loadConfig(env = process.env) {
|
|
8
|
+
const chronicleRoot = env.MIADI_CHRONICLE_ROOT || "/srv/miadi/episodes/miadi-chronicle";
|
|
9
|
+
const token = resolveVoiceToken(env);
|
|
10
|
+
return {
|
|
11
|
+
apiUrl: env.MIADI_API_URL || "http://127.0.0.1:3335",
|
|
12
|
+
apiToken: token.token ?? undefined,
|
|
13
|
+
tokenTier: token.tier,
|
|
14
|
+
wheelUrl: env.MW_API_URL || "http://127.0.0.1:8040",
|
|
15
|
+
chronicleRoot,
|
|
16
|
+
chronicleGitRoot: env.MIADI_CHRONICLE_GIT_ROOT || "/srv/miadi/episodes",
|
|
17
|
+
mkepisodePath: env.MIADI_MKEPISODE_PATH ||
|
|
18
|
+
"/usr/local/src/mightyeagle/packages/passages/js/mkepisode.js",
|
|
19
|
+
audioRoot: env.ASSEMBLY_VOICE_AUDIO_DIR || "/srv/miadi/voice-audio",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Thrown by the tools that are specified but not yet built. It names the
|
|
24
|
+
* governing specification section, so the lane that implements one is pointed at
|
|
25
|
+
* its contract rather than left to infer one.
|
|
26
|
+
*/
|
|
27
|
+
export class NotImplementedError extends Error {
|
|
28
|
+
tool;
|
|
29
|
+
specSection;
|
|
30
|
+
constructor(tool, specSection) {
|
|
31
|
+
super(`not implemented: ${tool}. The behaviour is specified in ${specSection}. ` +
|
|
32
|
+
`See SPEC-LINKS.md.`);
|
|
33
|
+
this.tool = tool;
|
|
34
|
+
this.specSection = specSection;
|
|
35
|
+
this.name = "NotImplementedError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Every tool refusing and naming its contract. Kept for tests and for a
|
|
39
|
+
* deployment that wants the surface declared without any behaviour behind it. */
|
|
40
|
+
export function createScaffoldHandlers() {
|
|
41
|
+
return {
|
|
42
|
+
async playEpisode() {
|
|
43
|
+
throw new NotImplementedError("voice_play_episode", "voice-mcp.spec.md § Tool 1 · voice_play_episode, and " +
|
|
44
|
+
"[voice-episode-binding spec § Playback path]");
|
|
45
|
+
},
|
|
46
|
+
async resolveEpisode() {
|
|
47
|
+
throw new NotImplementedError("voice_resolve_episode", "voice-mcp.spec.md § Tool 2 · voice_resolve_episode, and " +
|
|
48
|
+
"[voice-episode-binding spec § Discovery resolution order]");
|
|
49
|
+
},
|
|
50
|
+
async publishToEpisode() {
|
|
51
|
+
throw new NotImplementedError("voice_publish_to_episode", "voice-mcp.spec.md § Tool 3 · voice_publish_to_episode, and " +
|
|
52
|
+
"[voice-episode-binding spec § Enforcement decision table]");
|
|
53
|
+
},
|
|
54
|
+
async createEpisode() {
|
|
55
|
+
throw new NotImplementedError("voice_create_episode", "voice-mcp.spec.md § Tool 4 · voice_create_episode and " +
|
|
56
|
+
"§ The closing gate is part of the tool, not after it");
|
|
57
|
+
},
|
|
58
|
+
async listEpisodeVoices() {
|
|
59
|
+
throw new NotImplementedError("voice_list_episode_voices", "voice-mcp.spec.md § Tool 5 · voice_list_episode_voices");
|
|
60
|
+
},
|
|
61
|
+
async episodeClosingStatus() {
|
|
62
|
+
throw new NotImplementedError("voice_episode_closing_status", "voice-mcp.spec.md § Tool 6 · voice_episode_closing_status");
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,yCAAyC;AACzC,EAAE;AACF,+EAA+E;AAC/E,gEAAgE;AAEhE,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qBAAqB,CAAC;AAsDxE,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,aAAa,GACjB,GAAG,CAAC,oBAAoB,IAAI,qCAAqC,CAAC;IACpE,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAyC,CAAC,CAAC;IAC3E,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,aAAa,IAAI,uBAAuB;QACpD,QAAQ,EAAE,KAAK,CAAC,KAAK,IAAI,SAAS;QAClC,SAAS,EAAE,KAAK,CAAC,IAAI;QACrB,QAAQ,EAAE,GAAG,CAAC,UAAU,IAAI,uBAAuB;QACnD,aAAa;QACb,gBAAgB,EAAE,GAAG,CAAC,wBAAwB,IAAI,qBAAqB;QACvE,aAAa,EACX,GAAG,CAAC,oBAAoB;YACxB,8DAA8D;QAChE,SAAS,EAAE,GAAG,CAAC,wBAAwB,IAAI,wBAAwB;KACpE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAEjC;IACA;IAFX,YACW,IAAY,EACZ,WAAmB;QAE5B,KAAK,CACH,oBAAoB,IAAI,mCAAmC,WAAW,IAAI;YACxE,oBAAoB,CACvB,CAAC;QANO,SAAI,GAAJ,IAAI,CAAQ;QACZ,gBAAW,GAAX,WAAW,CAAQ;QAM5B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAiCD;kFACkF;AAClF,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,KAAK,CAAC,WAAW;YACf,MAAM,IAAI,mBAAmB,CAC3B,oBAAoB,EACpB,uDAAuD;gBACrD,8CAA8C,CACjD,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,cAAc;YAClB,MAAM,IAAI,mBAAmB,CAC3B,uBAAuB,EACvB,0DAA0D;gBACxD,2DAA2D,CAC9D,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,gBAAgB;YACpB,MAAM,IAAI,mBAAmB,CAC3B,0BAA0B,EAC1B,6DAA6D;gBAC3D,2DAA2D,CAC9D,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,aAAa;YACjB,MAAM,IAAI,mBAAmB,CAC3B,sBAAsB,EACtB,wDAAwD;gBACtD,sDAAsD,CACzD,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,iBAAiB;YACrB,MAAM,IAAI,mBAAmB,CAC3B,2BAA2B,EAC3B,wDAAwD,CACzD,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,oBAAoB;YACxB,MAAM,IAAI,mBAAmB,CAC3B,8BAA8B,EAC9B,2DAA2D,CAC5D,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type VoiceApiClient } from "@miadi/voice-client";
|
|
2
|
+
import { type VoiceMcpConfig, type VoiceMcpHandlers } from "./contract.js";
|
|
3
|
+
export declare function createHttpHandlers(config: VoiceMcpConfig, client?: VoiceApiClient): VoiceMcpHandlers;
|
|
4
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAuBA,OAAO,EAML,KAAK,cAAc,EAEpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAuB,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAqBhG,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,cAIN,GACD,gBAAgB,CAsOlB"}
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// The working handlers behind the six tools.
|
|
2
|
+
//
|
|
3
|
+
// WHAT IS IMPLEMENTED HERE, AND WHY EXACTLY THIS MUCH. Four tools —
|
|
4
|
+
// `voice_publish_to_episode`, `voice_play_episode`, `voice_list_episode_voices`
|
|
5
|
+
// and `voice_resolve_episode` — need only two things that already exist: the
|
|
6
|
+
// HTTP surface at `/api/voice/*` (through `@miadi/voice-client`) and the folder
|
|
7
|
+
// list under `MIADI_CHRONICLE_ROOT`. They are implemented.
|
|
8
|
+
//
|
|
9
|
+
// Two are not: `voice_create_episode` and `voice_episode_closing_status` mint
|
|
10
|
+
// and prove a chronicle vessel, which means `mkepisode`, a wheel registration
|
|
11
|
+
// and the five-stage closing gate. Those keep throwing `NotImplementedError`
|
|
12
|
+
// naming their contract, because a tool that reports a stage it did not prove is
|
|
13
|
+
// worse than a tool that refuses.
|
|
14
|
+
//
|
|
15
|
+
// AND THE PART DELIBERATELY LEFT UNBUILT. Resolution here consults exactly what
|
|
16
|
+
// it is given: an explicit reference, a caller-supplied `cwd`, or an existing
|
|
17
|
+
// record's binding. It does not rank those against a session origin or search
|
|
18
|
+
// the wheel — the ranked discovery order is owed by the voice-episode-binding
|
|
19
|
+
// spec (see SPEC-LINKS.md § What the missing document blocks), and inventing one
|
|
20
|
+
// here would make this file a second source of truth for a rule that has an
|
|
21
|
+
// owner. When nothing given answers, the tool says so; `episode: null` with a
|
|
22
|
+
// reason is a valid answer, not a failure.
|
|
23
|
+
import { VoiceApiError, createVoiceClient, episodeOf, episodeNodeId, episodeFromPath, } from "@miadi/voice-client";
|
|
24
|
+
import { listEpisodeFolders, matchRef } from "./chronicle.js";
|
|
25
|
+
import { NotImplementedError } from "./contract.js";
|
|
26
|
+
/** How many alternatives an unresolved answer offers. Enough to choose from. */
|
|
27
|
+
const ALTERNATIVES_SHOWN = 5;
|
|
28
|
+
export function createHttpHandlers(config, client = createVoiceClient({
|
|
29
|
+
baseUrl: config.apiUrl,
|
|
30
|
+
token: config.apiToken ?? null,
|
|
31
|
+
tier: config.tokenTier,
|
|
32
|
+
})) {
|
|
33
|
+
/** The unresolved answer, carrying the newest episodes as alternatives. */
|
|
34
|
+
async function unresolved(reason) {
|
|
35
|
+
const folders = await listEpisodeFolders(config.chronicleRoot);
|
|
36
|
+
return {
|
|
37
|
+
episode: null,
|
|
38
|
+
nodeId: null,
|
|
39
|
+
method: "unresolved",
|
|
40
|
+
reason,
|
|
41
|
+
alternatives: folders.slice(0, ALTERNATIVES_SHOWN).map((episode) => ({
|
|
42
|
+
episode,
|
|
43
|
+
method: "wheel-search",
|
|
44
|
+
reason: "recent episode in the chronicle root — offered, not chosen",
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function resolved(episode, method, reason, alternatives = []) {
|
|
49
|
+
return { episode, nodeId: episodeNodeId(episode), method, reason, alternatives };
|
|
50
|
+
}
|
|
51
|
+
async function resolve(ctx) {
|
|
52
|
+
const folders = await listEpisodeFolders(config.chronicleRoot);
|
|
53
|
+
// 1 — What the caller said. An explicit reference that does not resolve is
|
|
54
|
+
// refused rather than quietly replaced with a guess.
|
|
55
|
+
if (ctx.episode?.trim()) {
|
|
56
|
+
const match = matchRef(ctx.episode, folders);
|
|
57
|
+
if (match.folder) {
|
|
58
|
+
return resolved(match.folder, "explicit-ref", `reference "${ctx.episode}" names this folder`);
|
|
59
|
+
}
|
|
60
|
+
const why = match.candidates.length > 1
|
|
61
|
+
? `"${ctx.episode}" matches ${match.candidates.length} folders; choosing between them is not this tool's call`
|
|
62
|
+
: `"${ctx.episode}" matches no folder under ${config.chronicleRoot}`;
|
|
63
|
+
const answer = await unresolved(why);
|
|
64
|
+
// The ambiguous candidates ARE the alternatives worth showing. When there
|
|
65
|
+
// are none, the recent episodes `unresolved()` offers stay — a caller who
|
|
66
|
+
// named a folder that does not exist still needs somewhere to look.
|
|
67
|
+
return match.candidates.length > 0
|
|
68
|
+
? {
|
|
69
|
+
...answer,
|
|
70
|
+
alternatives: match.candidates.map((episode) => ({
|
|
71
|
+
episode,
|
|
72
|
+
method: "explicit-ref",
|
|
73
|
+
reason: `also matches "${ctx.episode}"`,
|
|
74
|
+
})),
|
|
75
|
+
}
|
|
76
|
+
: answer;
|
|
77
|
+
}
|
|
78
|
+
// 2 — What an existing record already says about itself.
|
|
79
|
+
if (ctx.messageId) {
|
|
80
|
+
const record = await client.message(ctx.messageId);
|
|
81
|
+
if (!record)
|
|
82
|
+
return unresolved(`no voice record ${ctx.messageId}`);
|
|
83
|
+
const reading = episodeOf(record);
|
|
84
|
+
if (reading.episode) {
|
|
85
|
+
return resolved(reading.episode, "voice-record-binding", reading.binding === "declared"
|
|
86
|
+
? `record ${ctx.messageId} declares this episode`
|
|
87
|
+
: `record ${ctx.messageId} names this episode in its path`);
|
|
88
|
+
}
|
|
89
|
+
return unresolved(`record ${ctx.messageId} carries no episode binding`);
|
|
90
|
+
}
|
|
91
|
+
// 3 — Where the caller is standing, when it said where that is.
|
|
92
|
+
if (ctx.cwd) {
|
|
93
|
+
const fromPath = episodeFromPath(ctx.cwd);
|
|
94
|
+
if (fromPath && folders.includes(fromPath)) {
|
|
95
|
+
return resolved(fromPath, "cwd", `working directory is inside ${fromPath}`);
|
|
96
|
+
}
|
|
97
|
+
return unresolved(`working directory ${ctx.cwd} is not inside an episode folder`);
|
|
98
|
+
}
|
|
99
|
+
return unresolved("no episode reference, message id or working directory was given, and the ranked " +
|
|
100
|
+
"discovery order is owed by the voice-episode-binding spec — this tool will not guess one");
|
|
101
|
+
}
|
|
102
|
+
/** A ledger record as something playable, or the reason it is not. */
|
|
103
|
+
function playable(message) {
|
|
104
|
+
const url = client.audioUrl(message);
|
|
105
|
+
if (!url) {
|
|
106
|
+
return {
|
|
107
|
+
messageId: message.id,
|
|
108
|
+
reason: "record carries no audio_file — legal for the non-TTS integration messages, which are text-only",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const reading = episodeOf(message);
|
|
112
|
+
return {
|
|
113
|
+
messageId: message.id,
|
|
114
|
+
url,
|
|
115
|
+
...(config.audioRoot && message.audio_file
|
|
116
|
+
? { path: `${config.audioRoot.replace(/\/$/, "")}/${message.audio_file}` }
|
|
117
|
+
: {}),
|
|
118
|
+
contentType: "audio/mpeg",
|
|
119
|
+
persona: message.persona,
|
|
120
|
+
lang: message.lang,
|
|
121
|
+
timestamp: message.timestamp,
|
|
122
|
+
text: message.text,
|
|
123
|
+
listened: message.listened,
|
|
124
|
+
// The record-level binding, mapped onto the resolution vocabulary: a
|
|
125
|
+
// declared binding was written with the record; a path binding was read
|
|
126
|
+
// off the directory the publisher was standing in.
|
|
127
|
+
binding: reading.binding === "declared"
|
|
128
|
+
? "voice-record-binding"
|
|
129
|
+
: reading.binding === "path"
|
|
130
|
+
? "cwd"
|
|
131
|
+
: "unresolved",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function split(messages) {
|
|
135
|
+
const voices = [];
|
|
136
|
+
const unplayable = [];
|
|
137
|
+
for (const m of messages) {
|
|
138
|
+
const p = playable(m);
|
|
139
|
+
if ("url" in p)
|
|
140
|
+
voices.push(p);
|
|
141
|
+
else
|
|
142
|
+
unplayable.push(p);
|
|
143
|
+
}
|
|
144
|
+
return { voices, unplayable };
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
async playEpisode(input) {
|
|
148
|
+
const resolution = await resolve({ episode: input.episode });
|
|
149
|
+
if (!resolution.episode)
|
|
150
|
+
return { resolution, voices: [], unplayable: [] };
|
|
151
|
+
const bound = await client.episodeVoices(resolution.episode);
|
|
152
|
+
// Newest first: "play me what we said" means the latest, not the oldest.
|
|
153
|
+
const ordered = [...bound].reverse();
|
|
154
|
+
const limited = input.limit ? ordered.slice(0, input.limit) : ordered;
|
|
155
|
+
const { voices, unplayable } = split(limited);
|
|
156
|
+
return { resolution, voices, unplayable };
|
|
157
|
+
},
|
|
158
|
+
async resolveEpisode(input) {
|
|
159
|
+
return resolve({ episode: input.episode, cwd: input.cwd, messageId: input.messageId });
|
|
160
|
+
},
|
|
161
|
+
async publishToEpisode(input) {
|
|
162
|
+
const resolution = await resolve({ episode: input.episode });
|
|
163
|
+
// An episode was named and did not resolve: refuse. Publishing it unbound
|
|
164
|
+
// would produce exactly the record this whole surface exists to stop
|
|
165
|
+
// producing — audio nobody can address afterwards.
|
|
166
|
+
if (input.episode && !resolution.episode) {
|
|
167
|
+
throw new Error(`refusing to publish: ${resolution.reason}. ` +
|
|
168
|
+
`Publish without an episode to leave the voice unbound deliberately, or name one that exists.`);
|
|
169
|
+
}
|
|
170
|
+
let message;
|
|
171
|
+
try {
|
|
172
|
+
message = await client.publish({
|
|
173
|
+
text: input.text,
|
|
174
|
+
...(resolution.episode ? { episode: resolution.episode } : {}),
|
|
175
|
+
...(input.persona ? { persona: input.persona } : {}),
|
|
176
|
+
...(input.lang ? { lang: input.lang } : {}),
|
|
177
|
+
source: input.source ?? "agent",
|
|
178
|
+
...(input.origin ? { origin: input.origin } : {}),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
// The tier failure is the one worth restating at the tool boundary: an
|
|
183
|
+
// agent reading a raw 401 has no way to know which variable to set.
|
|
184
|
+
if (e instanceof VoiceApiError && e.status === 401)
|
|
185
|
+
throw new Error(e.message);
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
188
|
+
const p = playable(message);
|
|
189
|
+
if (!("url" in p)) {
|
|
190
|
+
throw new Error(`published ${message.id} but it has no playable audio: ${p.reason}. ` +
|
|
191
|
+
`The record is in the ledger; the producer is what to look at.`);
|
|
192
|
+
}
|
|
193
|
+
return { message, resolution, playable: p };
|
|
194
|
+
},
|
|
195
|
+
async listEpisodeVoices(input) {
|
|
196
|
+
const resolution = await resolve({ episode: input.episode });
|
|
197
|
+
if (!resolution.episode)
|
|
198
|
+
return { resolution, total: 0, voices: [] };
|
|
199
|
+
const bound = await client.episodeVoices(resolution.episode);
|
|
200
|
+
const needle = input.query?.toLowerCase();
|
|
201
|
+
const filtered = bound.filter((m) => {
|
|
202
|
+
if (needle && !m.text.toLowerCase().includes(needle))
|
|
203
|
+
return false;
|
|
204
|
+
if (input.persona && m.persona !== input.persona)
|
|
205
|
+
return false;
|
|
206
|
+
if (input.lang && m.lang !== input.lang)
|
|
207
|
+
return false;
|
|
208
|
+
if (input.listened !== undefined && m.listened !== input.listened)
|
|
209
|
+
return false;
|
|
210
|
+
return true;
|
|
211
|
+
});
|
|
212
|
+
const ordered = [...filtered].reverse();
|
|
213
|
+
const limited = input.limit ? ordered.slice(0, input.limit) : ordered;
|
|
214
|
+
// `total` counts the matches, not the page: a caller that asked for 10 of
|
|
215
|
+
// 40 needs to know there are 40.
|
|
216
|
+
return { resolution, total: filtered.length, voices: split(limited).voices };
|
|
217
|
+
},
|
|
218
|
+
async createEpisode() {
|
|
219
|
+
throw new NotImplementedError("voice_create_episode", "voice-mcp.spec.md § Tool 4 · voice_create_episode and " +
|
|
220
|
+
"§ The closing gate is part of the tool, not after it");
|
|
221
|
+
},
|
|
222
|
+
async episodeClosingStatus() {
|
|
223
|
+
throw new NotImplementedError("voice_episode_closing_status", "voice-mcp.spec.md § Tool 6 · voice_episode_closing_status");
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @miadi/voice-mcp — the MCP server that lets an agent play, find, publish and
|
|
4
|
+
* mint episode-bound voice.
|
|
5
|
+
*
|
|
6
|
+
* The one sentence this package exists to satisfy, from William, 2026-08-04:
|
|
7
|
+
* "What I want is actually to be capable to play the voice-audio created for
|
|
8
|
+
* episodes."
|
|
9
|
+
*
|
|
10
|
+
* STATE. Four tools work: publish, play, list, resolve. Two still refuse and
|
|
11
|
+
* name their contract — `voice_create_episode` and `voice_episode_closing_status`
|
|
12
|
+
* mint and prove a chronicle vessel, and a tool that reports a closing stage it
|
|
13
|
+
* did not prove is worse than one that declines. See `handlers.ts`.
|
|
14
|
+
*
|
|
15
|
+
* ARCHITECTURE. This server is a *front*, not a second implementation. Audio
|
|
16
|
+
* bytes, the KV ledger and the Edge-TTS producer live behind the Miadi server
|
|
17
|
+
* (systemd unit `miadi-server`, port 3335 — verified live 2026-08-04), which
|
|
18
|
+
* holds the KV credentials. This process reaches them over HTTP through
|
|
19
|
+
* `@miadi/voice-client`, and reads the chronicle folders directly. `VoiceMessage`
|
|
20
|
+
* keeps exactly one definition in the workspace: `@miadi/voice`'s.
|
|
21
|
+
*
|
|
22
|
+
* Specifications: see `SPEC-LINKS.md` beside this package.
|
|
23
|
+
*/
|
|
24
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
+
import { type VoiceMcpHandlers } from "./contract.js";
|
|
26
|
+
export * from "./types.js";
|
|
27
|
+
export { createScaffoldHandlers, loadConfig, NotImplementedError, type VoiceMcpConfig, type VoiceMcpHandlers, } from "./contract.js";
|
|
28
|
+
export { createHttpHandlers } from "./handlers.js";
|
|
29
|
+
export { listEpisodeFolders, matchRef, episodePath, isEpisodeFolder } from "./chronicle.js";
|
|
30
|
+
/**
|
|
31
|
+
* Build the server with its six tools registered. Handlers are injected so the
|
|
32
|
+
* implementation can change without touching registration, and so tests can
|
|
33
|
+
* substitute one — `createScaffoldHandlers()` gives a surface that refuses
|
|
34
|
+
* everything, which is what a contract test wants.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createServer(handlers?: VoiceMcpHandlers): McpServer;
|
|
37
|
+
export declare function main(): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAIvB,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,UAAU,EACV,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAmC5F;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,GAAE,gBAAmD,GAC5D,SAAS,CAiLX;AAID,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB1C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @miadi/voice-mcp — the MCP server that lets an agent play, find, publish and
|
|
4
|
+
* mint episode-bound voice.
|
|
5
|
+
*
|
|
6
|
+
* The one sentence this package exists to satisfy, from William, 2026-08-04:
|
|
7
|
+
* "What I want is actually to be capable to play the voice-audio created for
|
|
8
|
+
* episodes."
|
|
9
|
+
*
|
|
10
|
+
* STATE. Four tools work: publish, play, list, resolve. Two still refuse and
|
|
11
|
+
* name their contract — `voice_create_episode` and `voice_episode_closing_status`
|
|
12
|
+
* mint and prove a chronicle vessel, and a tool that reports a closing stage it
|
|
13
|
+
* did not prove is worse than one that declines. See `handlers.ts`.
|
|
14
|
+
*
|
|
15
|
+
* ARCHITECTURE. This server is a *front*, not a second implementation. Audio
|
|
16
|
+
* bytes, the KV ledger and the Edge-TTS producer live behind the Miadi server
|
|
17
|
+
* (systemd unit `miadi-server`, port 3335 — verified live 2026-08-04), which
|
|
18
|
+
* holds the KV credentials. This process reaches them over HTTP through
|
|
19
|
+
* `@miadi/voice-client`, and reads the chronicle folders directly. `VoiceMessage`
|
|
20
|
+
* keeps exactly one definition in the workspace: `@miadi/voice`'s.
|
|
21
|
+
*
|
|
22
|
+
* Specifications: see `SPEC-LINKS.md` beside this package.
|
|
23
|
+
*/
|
|
24
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
import { loadConfig, } from "./contract.js";
|
|
28
|
+
import { createHttpHandlers } from "./handlers.js";
|
|
29
|
+
export * from "./types.js";
|
|
30
|
+
export { createScaffoldHandlers, loadConfig, NotImplementedError, } from "./contract.js";
|
|
31
|
+
export { createHttpHandlers } from "./handlers.js";
|
|
32
|
+
export { listEpisodeFolders, matchRef, episodePath, isEpisodeFolder } from "./chronicle.js";
|
|
33
|
+
const NAME = "@miadi/voice-mcp";
|
|
34
|
+
const VERSION = "0.1.0";
|
|
35
|
+
// ── Shared schema fragments ─────────────────────────────────────────────────
|
|
36
|
+
const episodeRefSchema = z
|
|
37
|
+
.string()
|
|
38
|
+
.describe("Episode reference. Accepts the folder name (2026-07-27-episode-300-from-shadow-to-song), " +
|
|
39
|
+
"the wheel node id (chronicle:<folder-name>), or the ep<NNN> number form of the " +
|
|
40
|
+
"notation owned by the episode-addressing spec. A number matching two folders " +
|
|
41
|
+
"resolves to neither and returns both. Omitted, the tool answers from what it was " +
|
|
42
|
+
"given (cwd, message id) and says plainly when that is nothing.");
|
|
43
|
+
const langSchema = z.enum(["fr", "en"]).describe("Spoken language.");
|
|
44
|
+
const personaSchema = z
|
|
45
|
+
.string()
|
|
46
|
+
.describe("Assembly persona id. Unknown personas are refused by the voice layer.");
|
|
47
|
+
// ── Server construction ─────────────────────────────────────────────────────
|
|
48
|
+
function ok(payload) {
|
|
49
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
50
|
+
}
|
|
51
|
+
function refused(e) {
|
|
52
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
53
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the server with its six tools registered. Handlers are injected so the
|
|
57
|
+
* implementation can change without touching registration, and so tests can
|
|
58
|
+
* substitute one — `createScaffoldHandlers()` gives a surface that refuses
|
|
59
|
+
* everything, which is what a contract test wants.
|
|
60
|
+
*/
|
|
61
|
+
export function createServer(handlers = createHttpHandlers(loadConfig())) {
|
|
62
|
+
const server = new McpServer({ name: NAME, version: VERSION }, { capabilities: { tools: {} } });
|
|
63
|
+
// ── 1 · Play ──────────────────────────────────────────────────────────────
|
|
64
|
+
server.registerTool("voice_play_episode", {
|
|
65
|
+
title: "Play an episode's voice",
|
|
66
|
+
description: "Resolve an episode to its bound voice audio and return playable references — " +
|
|
67
|
+
"a streamable URL per voice, plus the on-disk path when the local audio store " +
|
|
68
|
+
"is active. Omit `episode` to play the episode adequate to the caller's context.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
episode: episodeRefSchema.optional(),
|
|
71
|
+
limit: z.number().int().positive().max(200).optional()
|
|
72
|
+
.describe("Maximum voices to return, newest first."),
|
|
73
|
+
},
|
|
74
|
+
}, async (args) => {
|
|
75
|
+
try {
|
|
76
|
+
return ok(await handlers.playEpisode(args));
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return refused(e);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// ── 2 · Find the adequate episode ────────────────────────────────────────
|
|
83
|
+
server.registerTool("voice_resolve_episode", {
|
|
84
|
+
title: "Find the adequate episode",
|
|
85
|
+
description: "Answer which episode the caller is working inside, from an explicit reference, " +
|
|
86
|
+
"an existing voice record, the session's declared origin, or the working " +
|
|
87
|
+
"directory. Always returns the reason and the alternatives considered, and may " +
|
|
88
|
+
"answer that no episode is adequate — which is a valid answer, not a failure.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
episode: episodeRefSchema.optional(),
|
|
91
|
+
cwd: z.string().optional().describe("Absolute working directory of the agent."),
|
|
92
|
+
sessionId: z.string().optional().describe("Agent session id, when one exists."),
|
|
93
|
+
messageId: z.string().optional()
|
|
94
|
+
.describe("Resolve from an existing voice record rather than from context."),
|
|
95
|
+
},
|
|
96
|
+
}, async (args) => {
|
|
97
|
+
try {
|
|
98
|
+
return ok(await handlers.resolveEpisode(args));
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return refused(e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// ── 3 · Publish voice bound to an episode ────────────────────────────────
|
|
105
|
+
server.registerTool("voice_publish_to_episode", {
|
|
106
|
+
title: "Publish voice bound to an episode",
|
|
107
|
+
description: "Produce spoken audio that is addressable from its episode from the moment it " +
|
|
108
|
+
"exists. The binding is written with the record, never inferred afterwards.",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
text: z.string().min(1).describe("Text to speak."),
|
|
111
|
+
episode: episodeRefSchema.optional(),
|
|
112
|
+
persona: personaSchema.optional(),
|
|
113
|
+
lang: langSchema.optional(),
|
|
114
|
+
source: z.string().optional()
|
|
115
|
+
.describe("Classification carried into the ledger record's `source` field."),
|
|
116
|
+
origin: z.record(z.string(), z.unknown()).optional()
|
|
117
|
+
.describe("The publisher's return address (user, host, multiplexer, session, pane). " +
|
|
118
|
+
"Server-owned fields are stamped by the voice layer and cannot be supplied."),
|
|
119
|
+
},
|
|
120
|
+
}, async (args) => {
|
|
121
|
+
try {
|
|
122
|
+
return ok(await handlers.publishToEpisode(args));
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return refused(e);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// ── 4 · Create the episode when none fits ────────────────────────────────
|
|
129
|
+
server.registerTool("voice_create_episode", {
|
|
130
|
+
title: "Create a Chronicle episode vessel",
|
|
131
|
+
description: "Mint a new episode when no existing one is adequate, via mkepisode against the " +
|
|
132
|
+
"Chronicle wheel. Returns every one of the five closing stages with its proof — " +
|
|
133
|
+
"created, committed, pushed, registered, receipt-verified — and the exact " +
|
|
134
|
+
"commands owed for the stages this tool does not itself perform. Creation is " +
|
|
135
|
+
"not closure, and this tool never reports a later word than it proved.",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
number: z.number().int().positive()
|
|
138
|
+
.describe("Episode number. Must be free; mkepisode refuses one in use."),
|
|
139
|
+
title: z.string().min(1).describe("Episode title; becomes the folder slug."),
|
|
140
|
+
goal: z.string().min(1).describe("The desired result this episode advances."),
|
|
141
|
+
references: z.array(z.string().min(1)).min(1)
|
|
142
|
+
.describe("Provenance locators as owner/repo#n, ordered. Required and repeatable; " +
|
|
143
|
+
"the first becomes the card's source_issue."),
|
|
144
|
+
firstVoice: z
|
|
145
|
+
.object({
|
|
146
|
+
text: z.string().min(1),
|
|
147
|
+
persona: personaSchema.optional(),
|
|
148
|
+
lang: langSchema.optional(),
|
|
149
|
+
})
|
|
150
|
+
.optional()
|
|
151
|
+
.describe("Voice to publish into the vessel the moment it exists."),
|
|
152
|
+
},
|
|
153
|
+
}, async (args) => {
|
|
154
|
+
try {
|
|
155
|
+
return ok(await handlers.createEpisode(args));
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
return refused(e);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
// ── 5 · List / search an episode's voices ────────────────────────────────
|
|
162
|
+
server.registerTool("voice_list_episode_voices", {
|
|
163
|
+
title: "List an episode's voices",
|
|
164
|
+
description: "Enumerate the voices bound to an episode, filtered by persona, language, " +
|
|
165
|
+
"listened state, or a substring of the spoken text.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
episode: episodeRefSchema.optional(),
|
|
168
|
+
query: z.string().optional().describe("Substring match over the spoken text."),
|
|
169
|
+
persona: personaSchema.optional(),
|
|
170
|
+
lang: langSchema.optional(),
|
|
171
|
+
listened: z.boolean().optional(),
|
|
172
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
173
|
+
},
|
|
174
|
+
}, async (args) => {
|
|
175
|
+
try {
|
|
176
|
+
return ok(await handlers.listEpisodeVoices(args));
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
return refused(e);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
// ── 6 · Prove the closing gate ───────────────────────────────────────────
|
|
183
|
+
server.registerTool("voice_episode_closing_status", {
|
|
184
|
+
title: "Prove an episode's five closing stages",
|
|
185
|
+
description: "Read-only. Re-run each of the five proofs — created, committed, pushed, " +
|
|
186
|
+
"registered, receipt-verified — and report what each command actually said, " +
|
|
187
|
+
"plus any drift shape found and the commands owed to redeem it.",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
episode: episodeRefSchema,
|
|
190
|
+
},
|
|
191
|
+
}, async (args) => {
|
|
192
|
+
try {
|
|
193
|
+
return ok(await handlers.episodeClosingStatus(args));
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
return refused(e);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return server;
|
|
200
|
+
}
|
|
201
|
+
// ── Entry point ─────────────────────────────────────────────────────────────
|
|
202
|
+
export async function main() {
|
|
203
|
+
const config = loadConfig();
|
|
204
|
+
// stderr: stdout belongs to the MCP transport, and one stray line there
|
|
205
|
+
// corrupts the JSON-RPC stream.
|
|
206
|
+
//
|
|
207
|
+
// The tier is printed because a reader-tier credential makes every publish
|
|
208
|
+
// answer 401 while every read keeps working — the failure that looks like a
|
|
209
|
+
// broken tool and is a one-variable fix.
|
|
210
|
+
console.error(`${NAME} ${VERSION} — api=${config.apiUrl} token=${config.tokenTier} ` +
|
|
211
|
+
`chronicle=${config.chronicleRoot}` +
|
|
212
|
+
(config.tokenTier === "writer"
|
|
213
|
+
? ""
|
|
214
|
+
: ` ⚠ publishing needs MIADI_API_TOKEN_WRITER; reads work without it`));
|
|
215
|
+
const server = createServer();
|
|
216
|
+
await server.connect(new StdioServerTransport());
|
|
217
|
+
}
|
|
218
|
+
const invokedDirectly = typeof process !== "undefined" &&
|
|
219
|
+
Array.isArray(process.argv) &&
|
|
220
|
+
process.argv[1] !== undefined &&
|
|
221
|
+
/voice-mcp[\\/](?:dist|src)[\\/]index\.(?:js|ts)$/.test(process.argv[1]);
|
|
222
|
+
if (invokedDirectly) {
|
|
223
|
+
main().catch((e) => {
|
|
224
|
+
console.error(e);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { VoiceLang, VoiceMessage } from "@miadi/voice";
|
|
2
|
+
export type { VoiceLang, VoiceMessage };
|
|
3
|
+
/**
|
|
4
|
+
* The chronicle folder name — the primary key everywhere. Shaped
|
|
5
|
+
* `YYYY-MM-DD-episode-NNN-slug`. Verified 2026-08-04: 146 folders of this shape
|
|
6
|
+
* under `/srv/miadi/episodes/miadi-chronicle`, and the medicine wheel keys its
|
|
7
|
+
* card `chronicle:<folder-name>`.
|
|
8
|
+
*/
|
|
9
|
+
export type EpisodeFolderName = string;
|
|
10
|
+
/** The wheel node id: `chronicle:<EpisodeFolderName>`. */
|
|
11
|
+
export type EpisodeNodeId = string;
|
|
12
|
+
/**
|
|
13
|
+
* A reference an agent may hand a tool. The `ep<NNN>[.<II>][/<segment>]`
|
|
14
|
+
* notation is owned by the addressing spec — this type only carries it.
|
|
15
|
+
*
|
|
16
|
+
* @see [episode-addressing spec § The Address Grammar · Notation]
|
|
17
|
+
*/
|
|
18
|
+
export type EpisodeRef = string;
|
|
19
|
+
/** How an episode was arrived at. Ranked resolution belongs to the binding spec. */
|
|
20
|
+
export type EpisodeResolutionMethod = "explicit-ref" | "voice-record-binding" | "session-origin" | "cwd" | "wheel-search" | "unresolved";
|
|
21
|
+
/**
|
|
22
|
+
* The answer to "which episode is this?" — always carrying *why*, and always
|
|
23
|
+
* able to say it does not know.
|
|
24
|
+
*
|
|
25
|
+
* The order the resolvers are consulted, and the confidence each yields, are
|
|
26
|
+
* governed by [voice-episode-binding spec § Discovery resolution order]. This
|
|
27
|
+
* package consumes that order; it does not define one.
|
|
28
|
+
*/
|
|
29
|
+
export interface EpisodeResolution {
|
|
30
|
+
/** `null` when nothing adequate was found — a valid, expected answer. */
|
|
31
|
+
episode: EpisodeFolderName | null;
|
|
32
|
+
nodeId: EpisodeNodeId | null;
|
|
33
|
+
method: EpisodeResolutionMethod;
|
|
34
|
+
/** Human-readable account of what was matched. Never omitted. */
|
|
35
|
+
reason: string;
|
|
36
|
+
/** Candidates considered but not chosen, best-first. */
|
|
37
|
+
alternatives: Array<{
|
|
38
|
+
episode: EpisodeFolderName;
|
|
39
|
+
method: EpisodeResolutionMethod;
|
|
40
|
+
reason: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One playable voice. `url` is the streamable address served by the Miadi
|
|
45
|
+
* server (`GET /api/voice/audio/<key>` — verified 2026-08-04 returning 200
|
|
46
|
+
* `audio/mpeg`). `path` is the on-disk location when the local audio store is
|
|
47
|
+
* the active driver, offered so a caller on the same host can play it directly.
|
|
48
|
+
*
|
|
49
|
+
* @see [voice-episode-binding spec § Playback path]
|
|
50
|
+
*/
|
|
51
|
+
export interface PlayableVoice {
|
|
52
|
+
messageId: string;
|
|
53
|
+
url: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
contentType: string;
|
|
56
|
+
persona: string;
|
|
57
|
+
lang: VoiceLang;
|
|
58
|
+
timestamp: string;
|
|
59
|
+
/** The spoken text. Present on every record in the live ledger. */
|
|
60
|
+
text: string;
|
|
61
|
+
listened: boolean;
|
|
62
|
+
/** How this voice came to belong to this episode. */
|
|
63
|
+
binding: EpisodeResolutionMethod;
|
|
64
|
+
}
|
|
65
|
+
export interface PlayEpisodeResult {
|
|
66
|
+
resolution: EpisodeResolution;
|
|
67
|
+
voices: PlayableVoice[];
|
|
68
|
+
/** Voices found for the episode but not playable, each with the reason. */
|
|
69
|
+
unplayable: Array<{
|
|
70
|
+
messageId: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Producing a voice already bound to an episode. Binding is established at
|
|
76
|
+
* publish time rather than inferred later — the whole point of the tool.
|
|
77
|
+
*
|
|
78
|
+
* @see [voice-episode-binding spec § Enforcement decision table]
|
|
79
|
+
*/
|
|
80
|
+
export interface PublishToEpisodeInput {
|
|
81
|
+
text: string;
|
|
82
|
+
episode?: EpisodeRef;
|
|
83
|
+
persona?: string;
|
|
84
|
+
lang?: VoiceLang;
|
|
85
|
+
/** Free-form classification carried into the ledger record's `source`. */
|
|
86
|
+
source?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The publisher's return address, passed through untouched to the voice
|
|
89
|
+
* layer, which stamps the server-owned fields itself. Shape and the
|
|
90
|
+
* self-declared/server attestation rule are owned by `@miadi/voice`.
|
|
91
|
+
*/
|
|
92
|
+
origin?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
export interface PublishToEpisodeResult {
|
|
95
|
+
message: VoiceMessage;
|
|
96
|
+
resolution: EpisodeResolution;
|
|
97
|
+
/** The address the voice can be played from immediately. */
|
|
98
|
+
playable: PlayableVoice;
|
|
99
|
+
}
|
|
100
|
+
export interface ListEpisodeVoicesInput {
|
|
101
|
+
episode?: EpisodeRef;
|
|
102
|
+
/** Substring match over the record's spoken text. */
|
|
103
|
+
query?: string;
|
|
104
|
+
persona?: string;
|
|
105
|
+
lang?: VoiceLang;
|
|
106
|
+
listened?: boolean;
|
|
107
|
+
limit?: number;
|
|
108
|
+
}
|
|
109
|
+
export interface ListEpisodeVoicesResult {
|
|
110
|
+
resolution: EpisodeResolution;
|
|
111
|
+
total: number;
|
|
112
|
+
voices: PlayableVoice[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The five stages of a Chronicle episode, each proven separately. `mkepisode`
|
|
116
|
+
* exits 0 whether registration succeeded, skipped, or failed, so the gate
|
|
117
|
+
* belongs to the caller. This type exists so an MCP tool can never report a
|
|
118
|
+
* later word than the stage it proved.
|
|
119
|
+
*
|
|
120
|
+
* @see [voice-mcp spec § The closing gate is part of the tool, not after it]
|
|
121
|
+
* @see /etc/claude-code/skills/chronicle-episode-closing/SKILL.md
|
|
122
|
+
*/
|
|
123
|
+
export type ClosingStage = "created" | "committed" | "pushed" | "registered" | "receipt-verified";
|
|
124
|
+
export interface ClosingStageProof {
|
|
125
|
+
stage: ClosingStage;
|
|
126
|
+
/** `owed` is a first-class state — the honest word for "not proven yet". */
|
|
127
|
+
state: "proven" | "owed" | "failed";
|
|
128
|
+
/** The command run, verbatim, so a human can re-run it. */
|
|
129
|
+
probe: string;
|
|
130
|
+
/** What that command actually said. Never a summary of what it should say. */
|
|
131
|
+
observed: string;
|
|
132
|
+
}
|
|
133
|
+
export interface CreateEpisodeInput {
|
|
134
|
+
/** Positive integer, must be free — `mkepisode` refuses a number in use. */
|
|
135
|
+
number: number;
|
|
136
|
+
title: string;
|
|
137
|
+
/** The desired result this episode advances. */
|
|
138
|
+
goal: string;
|
|
139
|
+
/** Ordered provenance locators, `owner/repo#n`. Required, repeatable. */
|
|
140
|
+
references: string[];
|
|
141
|
+
/** Voice to publish into the vessel the moment it exists. */
|
|
142
|
+
firstVoice?: {
|
|
143
|
+
text: string;
|
|
144
|
+
persona?: string;
|
|
145
|
+
lang?: VoiceLang;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export interface CreateEpisodeResult {
|
|
149
|
+
episode: EpisodeFolderName;
|
|
150
|
+
nodeId: EpisodeNodeId;
|
|
151
|
+
path: string;
|
|
152
|
+
/** Every stage, with its proof. Stages the tool cannot perform read `owed`. */
|
|
153
|
+
closing: ClosingStageProof[];
|
|
154
|
+
/** The exact commands a human runs to redeem each `owed` stage, in order. */
|
|
155
|
+
owedActions: string[];
|
|
156
|
+
firstVoice?: PublishToEpisodeResult;
|
|
157
|
+
}
|
|
158
|
+
export interface ClosingStatusResult {
|
|
159
|
+
episode: EpisodeFolderName;
|
|
160
|
+
closing: ClosingStageProof[];
|
|
161
|
+
/** Drift shape names as reported by the chronicle reconciler, when any. */
|
|
162
|
+
drift: string[];
|
|
163
|
+
owedActions: string[];
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Tool I/O shapes for @miadi/voice-mcp.
|
|
2
|
+
//
|
|
3
|
+
// These are the contract types the six tools speak. They are declared here so
|
|
4
|
+
// the tool surface is readable and typed before any handler exists — the
|
|
5
|
+
// scaffold's whole purpose. Every shape here is governed by a specification
|
|
6
|
+
// section named in `SPEC-LINKS.md`; where a rule belongs to a sibling spec it is
|
|
7
|
+
// cited, never restated, so there is exactly one source of truth for it.
|
|
8
|
+
//
|
|
9
|
+
// Canonical voice shapes (`VoiceMessage`, `VoiceLang`, persona identity) are
|
|
10
|
+
// owned by `@miadi/voice` and imported, not re-declared.
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,iFAAiF;AACjF,yEAAyE;AACzE,EAAE;AACF,6EAA6E;AAC7E,yDAAyD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@miadi/voice-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server fronting the Miadi voice layer with episode addressing \u2014 play, find, publish and mint episode-bound voice from an agent's tool surface.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Miadi",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"miadi-voice-mcp": "dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"SPEC-LINKS.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "../../node_modules/.bin/tsc -p tsconfig.build.json",
|
|
27
|
+
"type-check": "../../node_modules/.bin/tsc -p tsconfig.build.json --noEmit",
|
|
28
|
+
"start": "node dist/index.js",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@miadi/voice": "workspace:*",
|
|
33
|
+
"@miadi/voice-client": "workspace:*",
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
35
|
+
"zod": "^4.4.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"typescript": "^5.4.0"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"miadi",
|
|
43
|
+
"mcp",
|
|
44
|
+
"voice",
|
|
45
|
+
"tts",
|
|
46
|
+
"miadi-chronicle",
|
|
47
|
+
"episodic-memory"
|
|
48
|
+
],
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/jgwill/Miadi.git",
|
|
52
|
+
"directory": "packages/voice-mcp"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
59
|
+
}
|
|
60
|
+
}
|