@alfe.ai/mcp-tools 0.1.15 → 0.2.1

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/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let zod = require("zod");
3
+ let node_path = require("node:path");
3
4
  //#region src/types.ts
4
5
  function ok(data) {
5
6
  return { content: [{
@@ -78,6 +79,382 @@ const registerIntegrationsTools = (server, ctx) => {
78
79
  });
79
80
  };
80
81
  //#endregion
82
+ //#region src/memory.ts
83
+ /**
84
+ * Memory tools — a runtime-agnostic mirror of the OpenClaw memory-cloud
85
+ * plugin (`@alfe.ai/openclaw-memory-cloud`). All 7 tools (memory_recall,
86
+ * memory_store, memory_learn, memory_forget, memory_graph, memory_stats,
87
+ * memory_navigate) keep the plugin's names, descriptions, and argument
88
+ * schemas so an agent gets the same memory behaviour whether it runs under
89
+ * OpenClaw (via the plugin) or a Claude Code session (via this MCP server's
90
+ * `claude-code` profile). Each subtool is a thin wrapper over the
91
+ * corresponding `AgentApiClient` memory method against public
92
+ * `/agent/memory/...` endpoints.
93
+ *
94
+ * One deliberate divergence: the plugin registers `memory_recall` with a
95
+ * `memory_search` alias, but the MCP `registerTool` API is single-name, so
96
+ * the alias is dropped here (agents should call `memory_recall`).
97
+ *
98
+ * NOTE for OpenClaw agents: these tools are NOT registered in the default
99
+ * server profile — OpenClaw agents already get `memory_*` from their
100
+ * plugin, and registering them here would double the surface. They only
101
+ * register under the `claude-code` profile.
102
+ */
103
+ const registerMemoryTools = (server, ctx) => {
104
+ server.registerTool("memory_recall", {
105
+ description: "Search conversation memory and knowledge graph. Returns structured facts and relevant conversation excerpts.",
106
+ inputSchema: {
107
+ query: zod.z.string().describe("What to search for"),
108
+ limit: zod.z.number().optional().describe("Maximum results (default 10)"),
109
+ topic: zod.z.string().optional().describe("Filter by topic"),
110
+ tag: zod.z.string().optional().describe("Filter by tag (fact/decision/preference/event/discovery)")
111
+ }
112
+ }, async (args) => {
113
+ try {
114
+ return ok(await ctx.client.memorySearch(args.query, {
115
+ limit: args.limit,
116
+ topic: args.topic,
117
+ tag: args.tag
118
+ }));
119
+ } catch (error) {
120
+ return err(error instanceof Error ? error.message : "memory_recall failed");
121
+ }
122
+ });
123
+ server.registerTool("memory_store", {
124
+ description: "Explicitly save a piece of information to long-term memory.",
125
+ inputSchema: {
126
+ text: zod.z.string().describe("The information to remember"),
127
+ topic: zod.z.string().optional().describe("Topic category (e.g., person name, project)"),
128
+ tag: zod.z.string().optional().describe("Memory type: fact, decision, preference, event, discovery"),
129
+ importance: zod.z.number().optional().describe("Importance 0-1 (default 0.7)")
130
+ }
131
+ }, async (args) => {
132
+ try {
133
+ const result = await ctx.client.memoryStore(args.text, {
134
+ topic: args.topic,
135
+ tag: args.tag,
136
+ importance: args.importance
137
+ });
138
+ return ok({
139
+ memoryId: result.memoryId,
140
+ message: `Stored memory: ${result.memoryId}`
141
+ });
142
+ } catch (error) {
143
+ return err(error instanceof Error ? error.message : "memory_store failed");
144
+ }
145
+ });
146
+ server.registerTool("memory_learn", {
147
+ description: "Save arbitrary content (a paragraph, a doc, a fact list) into long-term memory. The system auto-classifies and extracts entities. Use this when the user asks you to learn, remember, or study something — or when you read a document worth retaining. Pass the source text directly; do not paraphrase first.",
148
+ inputSchema: {
149
+ content: zod.z.string().optional().describe("Inline text to ingest. Provide either content OR path, not both."),
150
+ path: zod.z.string().optional().describe("Workspace-relative file path to read and ingest. Provide either content OR path, not both."),
151
+ source: zod.z.string().optional().describe("Optional label describing where the content came from (e.g. doc title, url).")
152
+ }
153
+ }, async (args) => {
154
+ const content = typeof args.content === "string" ? args.content : void 0;
155
+ const filePath = typeof args.path === "string" ? args.path : void 0;
156
+ const source = typeof args.source === "string" ? args.source : void 0;
157
+ if (!content && !filePath) return err("Error: provide either `content` or `path`.");
158
+ if (content && filePath) return err("Error: provide either `content` or `path`, not both.");
159
+ let text;
160
+ let resolvedSource = source;
161
+ let sourceType = "inline";
162
+ if (filePath !== void 0) {
163
+ const nodePath = await import("node:path");
164
+ const fs = await import("node:fs/promises");
165
+ const cwd = process.cwd();
166
+ const resolved = nodePath.resolve(cwd, filePath);
167
+ if (!resolved.startsWith(cwd + nodePath.sep) && resolved !== cwd) return err(`Error: path "${filePath}" escapes the workspace.`);
168
+ try {
169
+ text = await fs.readFile(resolved, "utf8");
170
+ } catch (readErr) {
171
+ return err(`Error reading "${filePath}": ${readErr instanceof Error ? readErr.message : String(readErr)}`);
172
+ }
173
+ resolvedSource = source ?? filePath;
174
+ sourceType = "file";
175
+ } else if (content !== void 0) text = content;
176
+ else return err("Error: provide either `content` or `path`.");
177
+ try {
178
+ const result = await ctx.client.memoryLearn({
179
+ text,
180
+ source: resolvedSource,
181
+ sourceType
182
+ });
183
+ const label = result.source ?? "content";
184
+ return ok({
185
+ memoriesStored: result.memoriesStored,
186
+ triplesStored: result.triplesStored,
187
+ chunks: result.chunks,
188
+ source: result.source,
189
+ message: `Stored ${String(result.memoriesStored)} memories (${String(result.triplesStored)} facts, ${String(result.chunks)} chunks) from ${label}.`
190
+ });
191
+ } catch (error) {
192
+ return err(`Error: failed to store memory — ${error instanceof Error ? error.message : String(error)}`);
193
+ }
194
+ });
195
+ server.registerTool("memory_forget", {
196
+ description: "Search for and delete memories matching a query.",
197
+ inputSchema: { query: zod.z.string().describe("Search for memories to delete") }
198
+ }, async (args) => {
199
+ try {
200
+ const results = await ctx.client.memorySearch(args.query, { limit: 5 });
201
+ if (results.memories.length === 0) return ok({
202
+ deleted: 0,
203
+ message: "No matching memories found."
204
+ });
205
+ const deleted = [];
206
+ for (const mem of results.memories) try {
207
+ await ctx.client.memoryDelete(mem.id);
208
+ deleted.push(mem.id);
209
+ } catch {}
210
+ return ok({
211
+ deleted: deleted.length,
212
+ matched: results.memories.length,
213
+ message: `Deleted ${String(deleted.length)} of ${String(results.memories.length)} matching memories.`
214
+ });
215
+ } catch (error) {
216
+ return err(error instanceof Error ? error.message : "memory_forget failed");
217
+ }
218
+ });
219
+ server.registerTool("memory_graph", {
220
+ description: "Look up what you know about a specific entity from the knowledge graph.",
221
+ inputSchema: { entity: zod.z.string().describe("The entity to look up (person, concept, system)") }
222
+ }, async ({ entity }) => {
223
+ try {
224
+ const result = await ctx.client.memoryLookupEntity(entity);
225
+ if (result.triples.length === 0) return ok({
226
+ subject: entity,
227
+ triples: [],
228
+ message: `No knowledge found about "${entity}".`
229
+ });
230
+ const facts = result.triples.map((t) => `- ${result.subject} ${t.predicate} ${t.object} (since ${t.validFrom.slice(0, 10)})`);
231
+ return ok({
232
+ subject: result.subject,
233
+ triples: result.triples,
234
+ message: `Known facts about ${result.subject}:\n${facts.join("\n")}`
235
+ });
236
+ } catch (error) {
237
+ return err(error instanceof Error ? error.message : "memory_graph failed");
238
+ }
239
+ });
240
+ server.registerTool("memory_stats", {
241
+ description: "Get memory storage statistics.",
242
+ inputSchema: {}
243
+ }, async () => {
244
+ try {
245
+ const s = await ctx.client.memoryStats();
246
+ return ok({
247
+ vectorCount: s.vectorCount,
248
+ tripleCount: s.tripleCount,
249
+ storageEstimateBytes: s.storageEstimateBytes,
250
+ message: `Memories: ${String(s.vectorCount)}, Knowledge facts: ${String(s.tripleCount)}, Storage: ${String(Math.round(s.storageEstimateBytes / 1024))} KB`
251
+ });
252
+ } catch (error) {
253
+ return err(error instanceof Error ? error.message : "memory_stats failed");
254
+ }
255
+ });
256
+ server.registerTool("memory_navigate", {
257
+ description: "Browse your memory palace structure — list topics, subtopics, and memory counts.",
258
+ inputSchema: {}
259
+ }, async () => {
260
+ try {
261
+ return ok(await ctx.client.memoryNavigate());
262
+ } catch (error) {
263
+ return err(error instanceof Error ? error.message : "memory_navigate failed");
264
+ }
265
+ });
266
+ };
267
+ //#endregion
268
+ //#region src/audio.ts
269
+ /** Prepend a canonical 44-byte PCM WAV header to raw PCM samples. */
270
+ function pcmToWav(pcm, framing) {
271
+ const { sampleRate, channels, bitDepth } = framing;
272
+ const blockAlign = channels * (bitDepth / 8);
273
+ const byteRate = sampleRate * blockAlign;
274
+ const header = Buffer.alloc(44);
275
+ header.write("RIFF", 0, "ascii");
276
+ header.writeUInt32LE(36 + pcm.length, 4);
277
+ header.write("WAVE", 8, "ascii");
278
+ header.write("fmt ", 12, "ascii");
279
+ header.writeUInt32LE(16, 16);
280
+ header.writeUInt16LE(1, 20);
281
+ header.writeUInt16LE(channels, 22);
282
+ header.writeUInt32LE(sampleRate, 24);
283
+ header.writeUInt32LE(byteRate, 28);
284
+ header.writeUInt16LE(blockAlign, 32);
285
+ header.writeUInt16LE(bitDepth, 34);
286
+ header.write("data", 36, "ascii");
287
+ header.writeUInt32LE(pcm.length, 40);
288
+ return Buffer.concat([header, pcm]);
289
+ }
290
+ /**
291
+ * Parse a WAV buffer into its PCM payload + framing. Returns `null` when the
292
+ * buffer is not a RIFF/WAVE file (caller should then treat the bytes as raw
293
+ * PCM). Walks the chunk list so it tolerates `fmt `/`data` ordering, extra
294
+ * chunks (e.g. `LIST`/`fact`), and word-alignment padding.
295
+ */
296
+ function parseWav(buf) {
297
+ if (buf.length < 44) return null;
298
+ if (buf.toString("ascii", 0, 4) !== "RIFF") return null;
299
+ if (buf.toString("ascii", 8, 12) !== "WAVE") return null;
300
+ let sampleRate = 0;
301
+ let channels = 0;
302
+ let bitDepth = 0;
303
+ let pcm = null;
304
+ let offset = 12;
305
+ while (offset + 8 <= buf.length) {
306
+ const chunkId = buf.toString("ascii", offset, offset + 4);
307
+ const chunkSize = buf.readUInt32LE(offset + 4);
308
+ const bodyStart = offset + 8;
309
+ if (chunkId === "fmt " && bodyStart + 16 <= buf.length) {
310
+ channels = buf.readUInt16LE(bodyStart + 2);
311
+ sampleRate = buf.readUInt32LE(bodyStart + 4);
312
+ bitDepth = buf.readUInt16LE(bodyStart + 14);
313
+ } else if (chunkId === "data") {
314
+ const end = Math.min(bodyStart + chunkSize, buf.length);
315
+ pcm = buf.subarray(bodyStart, end);
316
+ }
317
+ offset = bodyStart + chunkSize + chunkSize % 2;
318
+ }
319
+ if (!pcm || sampleRate === 0 || channels === 0 || bitDepth === 0) return null;
320
+ return {
321
+ pcm,
322
+ sampleRate,
323
+ channels,
324
+ bitDepth
325
+ };
326
+ }
327
+ //#endregion
328
+ //#region src/voice.ts
329
+ /**
330
+ * Voice tools — a runtime-agnostic mirror of the one-shot TTS/STT tools in
331
+ * the OpenClaw voice plugin (`@alfe.ai/openclaw-voice`). The channel-only
332
+ * tools (hangup/transfer/dtmf) are intentionally omitted — they require a
333
+ * live channel service (Discord/Twilio) and are meaningless in a standalone
334
+ * MCP session.
335
+ *
336
+ * Both tools read/write files on disk (the MCP transport can't cleanly carry
337
+ * raw audio bytes as JSON), converting between the voice service's headerless
338
+ * PCM and a playable WAV via `./audio.js`.
339
+ *
340
+ * `tts`/`stt` on `AgentApiClient` hit `/voice/tts` and `/voice/stt` relative
341
+ * to the client's `apiUrl`, so we prefer `ctx.voiceClient` (keyed on the
342
+ * voice service URL) and fall back to `ctx.client` when it's absent (safe
343
+ * today — voice is co-located on the shared api gateway).
344
+ */
345
+ const registerVoiceTools = (server, ctx) => {
346
+ const voiceClient = ctx.voiceClient ?? ctx.client;
347
+ const resolveWorkspacePath = (p) => (0, node_path.isAbsolute)(p) ? p : (0, node_path.resolve)(process.cwd(), p);
348
+ server.registerTool("voice_tts", {
349
+ description: "Convert text to speech using the Alfe voice service (ElevenLabs). Writes a playable audio file to the workspace and returns its path. Billed per character to your tenant credit pool.",
350
+ inputSchema: {
351
+ text: zod.z.string().describe("Text to synthesize (1–5000 characters)."),
352
+ outputPath: zod.z.string().optional().describe("Where to write the audio file (absolute, or relative to the working directory). Defaults to alfe-tts-<timestamp>.wav in the working directory."),
353
+ format: zod.z.enum(["wav", "pcm"]).optional().describe("Output container. 'wav' (default) is a playable file; 'pcm' is headerless 24kHz/mono/16-bit raw PCM."),
354
+ voiceId: zod.z.string().optional().describe("ElevenLabs voice ID. Platform default when omitted."),
355
+ model: zod.z.enum(["eleven_turbo_v2_5", "eleven_multilingual_v2"]).optional().describe("TTS model. eleven_turbo_v2_5 (lower latency) when omitted.")
356
+ }
357
+ }, async (args) => {
358
+ try {
359
+ const { writeFile } = await import("node:fs/promises");
360
+ const format = args.format ?? "wav";
361
+ const result = await voiceClient.tts({
362
+ text: args.text,
363
+ voiceId: args.voiceId,
364
+ model: args.model
365
+ });
366
+ const bytes = format === "wav" ? pcmToWav(result.audio, {
367
+ sampleRate: result.sampleRate,
368
+ channels: result.channels,
369
+ bitDepth: result.bitDepth
370
+ }) : result.audio;
371
+ const outputPath = args.outputPath ?? `alfe-tts-${String(Date.now())}.${format}`;
372
+ const absolutePath = resolveWorkspacePath(outputPath);
373
+ await writeFile(absolutePath, bytes);
374
+ return ok({
375
+ path: outputPath,
376
+ absolutePath,
377
+ format,
378
+ sampleRate: result.sampleRate,
379
+ channels: result.channels,
380
+ bitDepth: result.bitDepth,
381
+ bytes: bytes.length,
382
+ characters: args.text.length
383
+ });
384
+ } catch (error) {
385
+ return err(error instanceof Error ? error.message : "voice_tts failed");
386
+ }
387
+ });
388
+ server.registerTool("voice_stt", {
389
+ description: "Transcribe an audio file to text using the Alfe voice service (Deepgram). Accepts a WAV file or headerless mono 16-bit PCM. Billed per audio-second to your tenant credit pool.",
390
+ inputSchema: {
391
+ path: zod.z.string().describe("Path to the audio file (absolute, or relative to the working directory). WAV or raw linear16 mono PCM."),
392
+ sampleRate: zod.z.number().optional().describe("Sample rate in Hz (8000–48000). Only used for headerless PCM input; ignored for WAV (its header wins). Defaults to 24000.")
393
+ }
394
+ }, async (args) => {
395
+ try {
396
+ const { readFile } = await import("node:fs/promises");
397
+ const raw = await readFile(resolveWorkspacePath(args.path));
398
+ const wav = parseWav(raw);
399
+ let audio;
400
+ let sampleRate;
401
+ if (wav) {
402
+ if (wav.bitDepth !== 16 || wav.channels !== 1) return err(`voice_stt needs 16-bit mono audio; got ${String(wav.bitDepth)}-bit / ${String(wav.channels)}-channel WAV. Re-export as 16-bit mono.`);
403
+ audio = wav.pcm;
404
+ sampleRate = wav.sampleRate;
405
+ } else {
406
+ audio = raw;
407
+ sampleRate = args.sampleRate ?? 24e3;
408
+ }
409
+ return ok(await voiceClient.stt({
410
+ audio,
411
+ sampleRate
412
+ }));
413
+ } catch (error) {
414
+ return err(error instanceof Error ? error.message : "voice_stt failed");
415
+ }
416
+ });
417
+ };
418
+ //#endregion
419
+ //#region src/messaging.ts
420
+ /**
421
+ * Messaging tools — outbound SMS from the agent's own assigned phone number.
422
+ *
423
+ * `send_text_message` wraps `AgentApiClient.sendSms`, which hits the
424
+ * agent-authed `POST /mobile/sms/send` endpoint. The FROM number is resolved
425
+ * server-side from the agent identity on the token (an agent sends from the
426
+ * single active number assigned to it, or gets a 400 if it has none) — the
427
+ * tool only picks the recipient and body. The send is preflighted against
428
+ * and metered to the tenant credit pool server-side.
429
+ */
430
+ const registerMessagingTools = (server, ctx) => {
431
+ server.registerTool("send_text_message", {
432
+ description: "Send an SMS text message to a phone number from your assigned phone number. The recipient is any E.164 number (e.g. +14155550123). You send from the single number assigned to you — you cannot choose the sender. Billed per message segment to your tenant credit pool. Fails if you have no active phone number.",
433
+ inputSchema: {
434
+ to: zod.z.string().min(1).describe("Recipient phone number in E.164 format (e.g. +14155550123)."),
435
+ body: zod.z.string().min(1).describe("The text message body to send.")
436
+ }
437
+ }, async (args) => {
438
+ try {
439
+ const result = await ctx.client.sendSms({
440
+ to: args.to,
441
+ body: args.body
442
+ });
443
+ return ok({
444
+ sent: result.sent,
445
+ sid: result.sid,
446
+ to: args.to,
447
+ message: `Sent SMS to ${args.to} (sid: ${result.sid}).`
448
+ });
449
+ } catch (error) {
450
+ return err(error instanceof Error ? error.message : "send_text_message failed");
451
+ }
452
+ });
453
+ };
454
+ //#endregion
81
455
  exports.err = err;
82
456
  exports.ok = ok;
83
457
  exports.registerIntegrationsTools = registerIntegrationsTools;
458
+ exports.registerMemoryTools = registerMemoryTools;
459
+ exports.registerMessagingTools = registerMessagingTools;
460
+ exports.registerVoiceTools = registerVoiceTools;
package/dist/index.d.cts CHANGED
@@ -19,6 +19,24 @@ interface ToolContext {
19
19
  apiUrl: string;
20
20
  agentId: string;
21
21
  tenantId: string;
22
+ /**
23
+ * Base URL for the voice service's agent-authed one-shot endpoints
24
+ * (`/voice/tts`, `/voice/stt`). Currently equals `apiUrl` (the endpoints
25
+ * are co-located on the shared api gateway) but carried separately so the
26
+ * voice tools have a stable seam if voice ever moves hosts — mirrors
27
+ * `ResolvedConfig.voiceServiceUrl` in `@alfe.ai/config`. Only consumed by
28
+ * `registerVoiceTools`; when omitted, the voice tools fall back to `apiUrl`.
29
+ */
30
+ voiceApiUrl?: string;
31
+ /**
32
+ * A dedicated `AgentApiClient` whose `apiUrl` is the voice service base
33
+ * (`voiceApiUrl`). `AgentApiClient.tts`/`stt` hit `/voice/tts` and
34
+ * `/voice/stt` RELATIVE to the client's own `apiUrl`, so the voice tools
35
+ * need a client keyed on the voice URL rather than the main `client`. The
36
+ * server (which holds the api key) builds this; `registerVoiceTools` falls
37
+ * back to `client` if it's absent (safe today since voice is co-located).
38
+ */
39
+ voiceClient?: AgentApiClient;
22
40
  }
23
41
  /**
24
42
  * Tool registration function shape. Each domain module exports one of
@@ -72,6 +90,66 @@ declare function err(message: string): ToolResult;
72
90
  */
73
91
  declare const registerIntegrationsTools: RegisterToolsFn;
74
92
  //# sourceMappingURL=integrations.d.ts.map
93
+
94
+ //#endregion
95
+ //#region src/memory.d.ts
96
+ /**
97
+ * Memory tools — a runtime-agnostic mirror of the OpenClaw memory-cloud
98
+ * plugin (`@alfe.ai/openclaw-memory-cloud`). All 7 tools (memory_recall,
99
+ * memory_store, memory_learn, memory_forget, memory_graph, memory_stats,
100
+ * memory_navigate) keep the plugin's names, descriptions, and argument
101
+ * schemas so an agent gets the same memory behaviour whether it runs under
102
+ * OpenClaw (via the plugin) or a Claude Code session (via this MCP server's
103
+ * `claude-code` profile). Each subtool is a thin wrapper over the
104
+ * corresponding `AgentApiClient` memory method against public
105
+ * `/agent/memory/...` endpoints.
106
+ *
107
+ * One deliberate divergence: the plugin registers `memory_recall` with a
108
+ * `memory_search` alias, but the MCP `registerTool` API is single-name, so
109
+ * the alias is dropped here (agents should call `memory_recall`).
110
+ *
111
+ * NOTE for OpenClaw agents: these tools are NOT registered in the default
112
+ * server profile — OpenClaw agents already get `memory_*` from their
113
+ * plugin, and registering them here would double the surface. They only
114
+ * register under the `claude-code` profile.
115
+ */
116
+ declare const registerMemoryTools: RegisterToolsFn;
117
+ //# sourceMappingURL=memory.d.ts.map
118
+ //#endregion
119
+ //#region src/voice.d.ts
120
+ /**
121
+ * Voice tools — a runtime-agnostic mirror of the one-shot TTS/STT tools in
122
+ * the OpenClaw voice plugin (`@alfe.ai/openclaw-voice`). The channel-only
123
+ * tools (hangup/transfer/dtmf) are intentionally omitted — they require a
124
+ * live channel service (Discord/Twilio) and are meaningless in a standalone
125
+ * MCP session.
126
+ *
127
+ * Both tools read/write files on disk (the MCP transport can't cleanly carry
128
+ * raw audio bytes as JSON), converting between the voice service's headerless
129
+ * PCM and a playable WAV via `./audio.js`.
130
+ *
131
+ * `tts`/`stt` on `AgentApiClient` hit `/voice/tts` and `/voice/stt` relative
132
+ * to the client's `apiUrl`, so we prefer `ctx.voiceClient` (keyed on the
133
+ * voice service URL) and fall back to `ctx.client` when it's absent (safe
134
+ * today — voice is co-located on the shared api gateway).
135
+ */
136
+ declare const registerVoiceTools: RegisterToolsFn;
137
+ //# sourceMappingURL=voice.d.ts.map
138
+ //#endregion
139
+ //#region src/messaging.d.ts
140
+ /**
141
+ * Messaging tools — outbound SMS from the agent's own assigned phone number.
142
+ *
143
+ * `send_text_message` wraps `AgentApiClient.sendSms`, which hits the
144
+ * agent-authed `POST /mobile/sms/send` endpoint. The FROM number is resolved
145
+ * server-side from the agent identity on the token (an agent sends from the
146
+ * single active number assigned to it, or gets a 400 if it has none) — the
147
+ * tool only picks the recipient and body. The send is preflighted against
148
+ * and metered to the tenant credit pool server-side.
149
+ */
150
+ declare const registerMessagingTools: RegisterToolsFn;
151
+ //# sourceMappingURL=messaging.d.ts.map
152
+
75
153
  //#endregion
76
- export { type RegisterToolsFn, type ToolContext, type ToolResult, err, ok, registerIntegrationsTools };
154
+ export { type RegisterToolsFn, type ToolContext, type ToolResult, err, ok, registerIntegrationsTools, registerMemoryTools, registerMessagingTools, registerVoiceTools };
77
155
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/integrations.ts"],"mappings":";;;;;;;AAcA;AAgBA;;;;;AAYA;AAMA;AAMA;UAxCiB,WAAA;UACP;;ECiBG,OAAA,EAAA,MAAA;;;;;;;;;;;;KDFD,eAAA,YAA2B,gBAAgB;;;;;;;;;;;UAYtC,UAAA;;;;;;;;iBAMD,EAAA,iBAAmB;iBAMnB,GAAA,mBAAsB;;;;;;;AAxCtC;AAgBA;;;;;AAYA;AAMA;AAMA;;;;ACtBA;;cAAa,2BAA2B"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/integrations.ts","../src/memory.ts","../src/voice.ts","../src/messaging.ts"],"mappings":";;;;;;;AAcA;;;;;AAkCA;;;;AAAkE,UAlCjD,WAAA,CAkCiD;EAYjD,MAAA,EA7CP,cA6CiB;EAMX,MAAE,EAAA,MAAA;EAMF,OAAG,EAAA,MAAA;;;;ACxCnB;;;;ACTA;;;;ACDA;;;;ACTA;;;gBJuBgB;;;;;;;;;;;KAYJ,eAAA,YAA2B,gBAAgB;;;;;;;;;;;UAYtC,UAAA;;;;;;;;iBAMD,EAAA,iBAAmB;iBAMnB,GAAA,mBAAsB;;;;;;;AA1DtC;;;;;AAkCA;;;;;AAYA;AAMA;AAMA;;cCxCa,2BAA2B;;;;;;;;ADlBxC;;;;;AAkCA;;;;;AAYA;AAMA;AAMA;;;;ACxCA;cCTa,qBAAqB;;;;;;;AFTlC;;;;;AAkCA;;;;;AAYA;AAMA;AAMA;cGlDa,oBAAoB;;;;;;;AHRjC;;;;;AAkCA;;AAAuC,cInC1B,sBJmC0B,EInCF,eJmCE"}
package/dist/index.d.ts CHANGED
@@ -19,6 +19,24 @@ interface ToolContext {
19
19
  apiUrl: string;
20
20
  agentId: string;
21
21
  tenantId: string;
22
+ /**
23
+ * Base URL for the voice service's agent-authed one-shot endpoints
24
+ * (`/voice/tts`, `/voice/stt`). Currently equals `apiUrl` (the endpoints
25
+ * are co-located on the shared api gateway) but carried separately so the
26
+ * voice tools have a stable seam if voice ever moves hosts — mirrors
27
+ * `ResolvedConfig.voiceServiceUrl` in `@alfe.ai/config`. Only consumed by
28
+ * `registerVoiceTools`; when omitted, the voice tools fall back to `apiUrl`.
29
+ */
30
+ voiceApiUrl?: string;
31
+ /**
32
+ * A dedicated `AgentApiClient` whose `apiUrl` is the voice service base
33
+ * (`voiceApiUrl`). `AgentApiClient.tts`/`stt` hit `/voice/tts` and
34
+ * `/voice/stt` RELATIVE to the client's own `apiUrl`, so the voice tools
35
+ * need a client keyed on the voice URL rather than the main `client`. The
36
+ * server (which holds the api key) builds this; `registerVoiceTools` falls
37
+ * back to `client` if it's absent (safe today since voice is co-located).
38
+ */
39
+ voiceClient?: AgentApiClient;
22
40
  }
23
41
  /**
24
42
  * Tool registration function shape. Each domain module exports one of
@@ -72,6 +90,66 @@ declare function err(message: string): ToolResult;
72
90
  */
73
91
  declare const registerIntegrationsTools: RegisterToolsFn;
74
92
  //# sourceMappingURL=integrations.d.ts.map
93
+
94
+ //#endregion
95
+ //#region src/memory.d.ts
96
+ /**
97
+ * Memory tools — a runtime-agnostic mirror of the OpenClaw memory-cloud
98
+ * plugin (`@alfe.ai/openclaw-memory-cloud`). All 7 tools (memory_recall,
99
+ * memory_store, memory_learn, memory_forget, memory_graph, memory_stats,
100
+ * memory_navigate) keep the plugin's names, descriptions, and argument
101
+ * schemas so an agent gets the same memory behaviour whether it runs under
102
+ * OpenClaw (via the plugin) or a Claude Code session (via this MCP server's
103
+ * `claude-code` profile). Each subtool is a thin wrapper over the
104
+ * corresponding `AgentApiClient` memory method against public
105
+ * `/agent/memory/...` endpoints.
106
+ *
107
+ * One deliberate divergence: the plugin registers `memory_recall` with a
108
+ * `memory_search` alias, but the MCP `registerTool` API is single-name, so
109
+ * the alias is dropped here (agents should call `memory_recall`).
110
+ *
111
+ * NOTE for OpenClaw agents: these tools are NOT registered in the default
112
+ * server profile — OpenClaw agents already get `memory_*` from their
113
+ * plugin, and registering them here would double the surface. They only
114
+ * register under the `claude-code` profile.
115
+ */
116
+ declare const registerMemoryTools: RegisterToolsFn;
117
+ //# sourceMappingURL=memory.d.ts.map
118
+ //#endregion
119
+ //#region src/voice.d.ts
120
+ /**
121
+ * Voice tools — a runtime-agnostic mirror of the one-shot TTS/STT tools in
122
+ * the OpenClaw voice plugin (`@alfe.ai/openclaw-voice`). The channel-only
123
+ * tools (hangup/transfer/dtmf) are intentionally omitted — they require a
124
+ * live channel service (Discord/Twilio) and are meaningless in a standalone
125
+ * MCP session.
126
+ *
127
+ * Both tools read/write files on disk (the MCP transport can't cleanly carry
128
+ * raw audio bytes as JSON), converting between the voice service's headerless
129
+ * PCM and a playable WAV via `./audio.js`.
130
+ *
131
+ * `tts`/`stt` on `AgentApiClient` hit `/voice/tts` and `/voice/stt` relative
132
+ * to the client's `apiUrl`, so we prefer `ctx.voiceClient` (keyed on the
133
+ * voice service URL) and fall back to `ctx.client` when it's absent (safe
134
+ * today — voice is co-located on the shared api gateway).
135
+ */
136
+ declare const registerVoiceTools: RegisterToolsFn;
137
+ //# sourceMappingURL=voice.d.ts.map
138
+ //#endregion
139
+ //#region src/messaging.d.ts
140
+ /**
141
+ * Messaging tools — outbound SMS from the agent's own assigned phone number.
142
+ *
143
+ * `send_text_message` wraps `AgentApiClient.sendSms`, which hits the
144
+ * agent-authed `POST /mobile/sms/send` endpoint. The FROM number is resolved
145
+ * server-side from the agent identity on the token (an agent sends from the
146
+ * single active number assigned to it, or gets a 400 if it has none) — the
147
+ * tool only picks the recipient and body. The send is preflighted against
148
+ * and metered to the tenant credit pool server-side.
149
+ */
150
+ declare const registerMessagingTools: RegisterToolsFn;
151
+ //# sourceMappingURL=messaging.d.ts.map
152
+
75
153
  //#endregion
76
- export { type RegisterToolsFn, type ToolContext, type ToolResult, err, ok, registerIntegrationsTools };
154
+ export { type RegisterToolsFn, type ToolContext, type ToolResult, err, ok, registerIntegrationsTools, registerMemoryTools, registerMessagingTools, registerVoiceTools };
77
155
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integrations.ts"],"mappings":";;;;;;;AAcA;AAgBA;;;;;AAYA;AAMA;AAMA;UAxCiB,WAAA;UACP;;ECiBG,OAAA,EAAA,MAAA;;;;;;;;;;;;KDFD,eAAA,YAA2B,gBAAgB;;;;;;;;;;;UAYtC,UAAA;;;;;;;;iBAMD,EAAA,iBAAmB;iBAMnB,GAAA,mBAAsB;;;;;;;AAxCtC;AAgBA;;;;;AAYA;AAMA;AAMA;;;;ACtBA;;cAAa,2BAA2B"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integrations.ts","../src/memory.ts","../src/voice.ts","../src/messaging.ts"],"mappings":";;;;;;;AAcA;;;;;AAkCA;;;;AAAkE,UAlCjD,WAAA,CAkCiD;EAYjD,MAAA,EA7CP,cA6CiB;EAMX,MAAE,EAAA,MAAA;EAMF,OAAG,EAAA,MAAA;;;;ACxCnB;;;;ACTA;;;;ACDA;;;;ACTA;;;gBJuBgB;;;;;;;;;;;KAYJ,eAAA,YAA2B,gBAAgB;;;;;;;;;;;UAYtC,UAAA;;;;;;;;iBAMD,EAAA,iBAAmB;iBAMnB,GAAA,mBAAsB;;;;;;;AA1DtC;;;;;AAkCA;;;;;AAYA;AAMA;AAMA;;cCxCa,2BAA2B;;;;;;;;ADlBxC;;;;;AAkCA;;;;;AAYA;AAMA;AAMA;;;;ACxCA;cCTa,qBAAqB;;;;;;;AFTlC;;;;;AAkCA;;;;;AAYA;AAMA;AAMA;cGlDa,oBAAoB;;;;;;;AHRjC;;;;;AAkCA;;AAAuC,cInC1B,sBJmC0B,EInCF,eJmCE"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { isAbsolute, resolve } from "node:path";
2
3
  //#region src/types.ts
3
4
  function ok(data) {
4
5
  return { content: [{
@@ -77,6 +78,379 @@ const registerIntegrationsTools = (server, ctx) => {
77
78
  });
78
79
  };
79
80
  //#endregion
80
- export { err, ok, registerIntegrationsTools };
81
+ //#region src/memory.ts
82
+ /**
83
+ * Memory tools — a runtime-agnostic mirror of the OpenClaw memory-cloud
84
+ * plugin (`@alfe.ai/openclaw-memory-cloud`). All 7 tools (memory_recall,
85
+ * memory_store, memory_learn, memory_forget, memory_graph, memory_stats,
86
+ * memory_navigate) keep the plugin's names, descriptions, and argument
87
+ * schemas so an agent gets the same memory behaviour whether it runs under
88
+ * OpenClaw (via the plugin) or a Claude Code session (via this MCP server's
89
+ * `claude-code` profile). Each subtool is a thin wrapper over the
90
+ * corresponding `AgentApiClient` memory method against public
91
+ * `/agent/memory/...` endpoints.
92
+ *
93
+ * One deliberate divergence: the plugin registers `memory_recall` with a
94
+ * `memory_search` alias, but the MCP `registerTool` API is single-name, so
95
+ * the alias is dropped here (agents should call `memory_recall`).
96
+ *
97
+ * NOTE for OpenClaw agents: these tools are NOT registered in the default
98
+ * server profile — OpenClaw agents already get `memory_*` from their
99
+ * plugin, and registering them here would double the surface. They only
100
+ * register under the `claude-code` profile.
101
+ */
102
+ const registerMemoryTools = (server, ctx) => {
103
+ server.registerTool("memory_recall", {
104
+ description: "Search conversation memory and knowledge graph. Returns structured facts and relevant conversation excerpts.",
105
+ inputSchema: {
106
+ query: z.string().describe("What to search for"),
107
+ limit: z.number().optional().describe("Maximum results (default 10)"),
108
+ topic: z.string().optional().describe("Filter by topic"),
109
+ tag: z.string().optional().describe("Filter by tag (fact/decision/preference/event/discovery)")
110
+ }
111
+ }, async (args) => {
112
+ try {
113
+ return ok(await ctx.client.memorySearch(args.query, {
114
+ limit: args.limit,
115
+ topic: args.topic,
116
+ tag: args.tag
117
+ }));
118
+ } catch (error) {
119
+ return err(error instanceof Error ? error.message : "memory_recall failed");
120
+ }
121
+ });
122
+ server.registerTool("memory_store", {
123
+ description: "Explicitly save a piece of information to long-term memory.",
124
+ inputSchema: {
125
+ text: z.string().describe("The information to remember"),
126
+ topic: z.string().optional().describe("Topic category (e.g., person name, project)"),
127
+ tag: z.string().optional().describe("Memory type: fact, decision, preference, event, discovery"),
128
+ importance: z.number().optional().describe("Importance 0-1 (default 0.7)")
129
+ }
130
+ }, async (args) => {
131
+ try {
132
+ const result = await ctx.client.memoryStore(args.text, {
133
+ topic: args.topic,
134
+ tag: args.tag,
135
+ importance: args.importance
136
+ });
137
+ return ok({
138
+ memoryId: result.memoryId,
139
+ message: `Stored memory: ${result.memoryId}`
140
+ });
141
+ } catch (error) {
142
+ return err(error instanceof Error ? error.message : "memory_store failed");
143
+ }
144
+ });
145
+ server.registerTool("memory_learn", {
146
+ description: "Save arbitrary content (a paragraph, a doc, a fact list) into long-term memory. The system auto-classifies and extracts entities. Use this when the user asks you to learn, remember, or study something — or when you read a document worth retaining. Pass the source text directly; do not paraphrase first.",
147
+ inputSchema: {
148
+ content: z.string().optional().describe("Inline text to ingest. Provide either content OR path, not both."),
149
+ path: z.string().optional().describe("Workspace-relative file path to read and ingest. Provide either content OR path, not both."),
150
+ source: z.string().optional().describe("Optional label describing where the content came from (e.g. doc title, url).")
151
+ }
152
+ }, async (args) => {
153
+ const content = typeof args.content === "string" ? args.content : void 0;
154
+ const filePath = typeof args.path === "string" ? args.path : void 0;
155
+ const source = typeof args.source === "string" ? args.source : void 0;
156
+ if (!content && !filePath) return err("Error: provide either `content` or `path`.");
157
+ if (content && filePath) return err("Error: provide either `content` or `path`, not both.");
158
+ let text;
159
+ let resolvedSource = source;
160
+ let sourceType = "inline";
161
+ if (filePath !== void 0) {
162
+ const nodePath = await import("node:path");
163
+ const fs = await import("node:fs/promises");
164
+ const cwd = process.cwd();
165
+ const resolved = nodePath.resolve(cwd, filePath);
166
+ if (!resolved.startsWith(cwd + nodePath.sep) && resolved !== cwd) return err(`Error: path "${filePath}" escapes the workspace.`);
167
+ try {
168
+ text = await fs.readFile(resolved, "utf8");
169
+ } catch (readErr) {
170
+ return err(`Error reading "${filePath}": ${readErr instanceof Error ? readErr.message : String(readErr)}`);
171
+ }
172
+ resolvedSource = source ?? filePath;
173
+ sourceType = "file";
174
+ } else if (content !== void 0) text = content;
175
+ else return err("Error: provide either `content` or `path`.");
176
+ try {
177
+ const result = await ctx.client.memoryLearn({
178
+ text,
179
+ source: resolvedSource,
180
+ sourceType
181
+ });
182
+ const label = result.source ?? "content";
183
+ return ok({
184
+ memoriesStored: result.memoriesStored,
185
+ triplesStored: result.triplesStored,
186
+ chunks: result.chunks,
187
+ source: result.source,
188
+ message: `Stored ${String(result.memoriesStored)} memories (${String(result.triplesStored)} facts, ${String(result.chunks)} chunks) from ${label}.`
189
+ });
190
+ } catch (error) {
191
+ return err(`Error: failed to store memory — ${error instanceof Error ? error.message : String(error)}`);
192
+ }
193
+ });
194
+ server.registerTool("memory_forget", {
195
+ description: "Search for and delete memories matching a query.",
196
+ inputSchema: { query: z.string().describe("Search for memories to delete") }
197
+ }, async (args) => {
198
+ try {
199
+ const results = await ctx.client.memorySearch(args.query, { limit: 5 });
200
+ if (results.memories.length === 0) return ok({
201
+ deleted: 0,
202
+ message: "No matching memories found."
203
+ });
204
+ const deleted = [];
205
+ for (const mem of results.memories) try {
206
+ await ctx.client.memoryDelete(mem.id);
207
+ deleted.push(mem.id);
208
+ } catch {}
209
+ return ok({
210
+ deleted: deleted.length,
211
+ matched: results.memories.length,
212
+ message: `Deleted ${String(deleted.length)} of ${String(results.memories.length)} matching memories.`
213
+ });
214
+ } catch (error) {
215
+ return err(error instanceof Error ? error.message : "memory_forget failed");
216
+ }
217
+ });
218
+ server.registerTool("memory_graph", {
219
+ description: "Look up what you know about a specific entity from the knowledge graph.",
220
+ inputSchema: { entity: z.string().describe("The entity to look up (person, concept, system)") }
221
+ }, async ({ entity }) => {
222
+ try {
223
+ const result = await ctx.client.memoryLookupEntity(entity);
224
+ if (result.triples.length === 0) return ok({
225
+ subject: entity,
226
+ triples: [],
227
+ message: `No knowledge found about "${entity}".`
228
+ });
229
+ const facts = result.triples.map((t) => `- ${result.subject} ${t.predicate} ${t.object} (since ${t.validFrom.slice(0, 10)})`);
230
+ return ok({
231
+ subject: result.subject,
232
+ triples: result.triples,
233
+ message: `Known facts about ${result.subject}:\n${facts.join("\n")}`
234
+ });
235
+ } catch (error) {
236
+ return err(error instanceof Error ? error.message : "memory_graph failed");
237
+ }
238
+ });
239
+ server.registerTool("memory_stats", {
240
+ description: "Get memory storage statistics.",
241
+ inputSchema: {}
242
+ }, async () => {
243
+ try {
244
+ const s = await ctx.client.memoryStats();
245
+ return ok({
246
+ vectorCount: s.vectorCount,
247
+ tripleCount: s.tripleCount,
248
+ storageEstimateBytes: s.storageEstimateBytes,
249
+ message: `Memories: ${String(s.vectorCount)}, Knowledge facts: ${String(s.tripleCount)}, Storage: ${String(Math.round(s.storageEstimateBytes / 1024))} KB`
250
+ });
251
+ } catch (error) {
252
+ return err(error instanceof Error ? error.message : "memory_stats failed");
253
+ }
254
+ });
255
+ server.registerTool("memory_navigate", {
256
+ description: "Browse your memory palace structure — list topics, subtopics, and memory counts.",
257
+ inputSchema: {}
258
+ }, async () => {
259
+ try {
260
+ return ok(await ctx.client.memoryNavigate());
261
+ } catch (error) {
262
+ return err(error instanceof Error ? error.message : "memory_navigate failed");
263
+ }
264
+ });
265
+ };
266
+ //#endregion
267
+ //#region src/audio.ts
268
+ /** Prepend a canonical 44-byte PCM WAV header to raw PCM samples. */
269
+ function pcmToWav(pcm, framing) {
270
+ const { sampleRate, channels, bitDepth } = framing;
271
+ const blockAlign = channels * (bitDepth / 8);
272
+ const byteRate = sampleRate * blockAlign;
273
+ const header = Buffer.alloc(44);
274
+ header.write("RIFF", 0, "ascii");
275
+ header.writeUInt32LE(36 + pcm.length, 4);
276
+ header.write("WAVE", 8, "ascii");
277
+ header.write("fmt ", 12, "ascii");
278
+ header.writeUInt32LE(16, 16);
279
+ header.writeUInt16LE(1, 20);
280
+ header.writeUInt16LE(channels, 22);
281
+ header.writeUInt32LE(sampleRate, 24);
282
+ header.writeUInt32LE(byteRate, 28);
283
+ header.writeUInt16LE(blockAlign, 32);
284
+ header.writeUInt16LE(bitDepth, 34);
285
+ header.write("data", 36, "ascii");
286
+ header.writeUInt32LE(pcm.length, 40);
287
+ return Buffer.concat([header, pcm]);
288
+ }
289
+ /**
290
+ * Parse a WAV buffer into its PCM payload + framing. Returns `null` when the
291
+ * buffer is not a RIFF/WAVE file (caller should then treat the bytes as raw
292
+ * PCM). Walks the chunk list so it tolerates `fmt `/`data` ordering, extra
293
+ * chunks (e.g. `LIST`/`fact`), and word-alignment padding.
294
+ */
295
+ function parseWav(buf) {
296
+ if (buf.length < 44) return null;
297
+ if (buf.toString("ascii", 0, 4) !== "RIFF") return null;
298
+ if (buf.toString("ascii", 8, 12) !== "WAVE") return null;
299
+ let sampleRate = 0;
300
+ let channels = 0;
301
+ let bitDepth = 0;
302
+ let pcm = null;
303
+ let offset = 12;
304
+ while (offset + 8 <= buf.length) {
305
+ const chunkId = buf.toString("ascii", offset, offset + 4);
306
+ const chunkSize = buf.readUInt32LE(offset + 4);
307
+ const bodyStart = offset + 8;
308
+ if (chunkId === "fmt " && bodyStart + 16 <= buf.length) {
309
+ channels = buf.readUInt16LE(bodyStart + 2);
310
+ sampleRate = buf.readUInt32LE(bodyStart + 4);
311
+ bitDepth = buf.readUInt16LE(bodyStart + 14);
312
+ } else if (chunkId === "data") {
313
+ const end = Math.min(bodyStart + chunkSize, buf.length);
314
+ pcm = buf.subarray(bodyStart, end);
315
+ }
316
+ offset = bodyStart + chunkSize + chunkSize % 2;
317
+ }
318
+ if (!pcm || sampleRate === 0 || channels === 0 || bitDepth === 0) return null;
319
+ return {
320
+ pcm,
321
+ sampleRate,
322
+ channels,
323
+ bitDepth
324
+ };
325
+ }
326
+ //#endregion
327
+ //#region src/voice.ts
328
+ /**
329
+ * Voice tools — a runtime-agnostic mirror of the one-shot TTS/STT tools in
330
+ * the OpenClaw voice plugin (`@alfe.ai/openclaw-voice`). The channel-only
331
+ * tools (hangup/transfer/dtmf) are intentionally omitted — they require a
332
+ * live channel service (Discord/Twilio) and are meaningless in a standalone
333
+ * MCP session.
334
+ *
335
+ * Both tools read/write files on disk (the MCP transport can't cleanly carry
336
+ * raw audio bytes as JSON), converting between the voice service's headerless
337
+ * PCM and a playable WAV via `./audio.js`.
338
+ *
339
+ * `tts`/`stt` on `AgentApiClient` hit `/voice/tts` and `/voice/stt` relative
340
+ * to the client's `apiUrl`, so we prefer `ctx.voiceClient` (keyed on the
341
+ * voice service URL) and fall back to `ctx.client` when it's absent (safe
342
+ * today — voice is co-located on the shared api gateway).
343
+ */
344
+ const registerVoiceTools = (server, ctx) => {
345
+ const voiceClient = ctx.voiceClient ?? ctx.client;
346
+ const resolveWorkspacePath = (p) => isAbsolute(p) ? p : resolve(process.cwd(), p);
347
+ server.registerTool("voice_tts", {
348
+ description: "Convert text to speech using the Alfe voice service (ElevenLabs). Writes a playable audio file to the workspace and returns its path. Billed per character to your tenant credit pool.",
349
+ inputSchema: {
350
+ text: z.string().describe("Text to synthesize (1–5000 characters)."),
351
+ outputPath: z.string().optional().describe("Where to write the audio file (absolute, or relative to the working directory). Defaults to alfe-tts-<timestamp>.wav in the working directory."),
352
+ format: z.enum(["wav", "pcm"]).optional().describe("Output container. 'wav' (default) is a playable file; 'pcm' is headerless 24kHz/mono/16-bit raw PCM."),
353
+ voiceId: z.string().optional().describe("ElevenLabs voice ID. Platform default when omitted."),
354
+ model: z.enum(["eleven_turbo_v2_5", "eleven_multilingual_v2"]).optional().describe("TTS model. eleven_turbo_v2_5 (lower latency) when omitted.")
355
+ }
356
+ }, async (args) => {
357
+ try {
358
+ const { writeFile } = await import("node:fs/promises");
359
+ const format = args.format ?? "wav";
360
+ const result = await voiceClient.tts({
361
+ text: args.text,
362
+ voiceId: args.voiceId,
363
+ model: args.model
364
+ });
365
+ const bytes = format === "wav" ? pcmToWav(result.audio, {
366
+ sampleRate: result.sampleRate,
367
+ channels: result.channels,
368
+ bitDepth: result.bitDepth
369
+ }) : result.audio;
370
+ const outputPath = args.outputPath ?? `alfe-tts-${String(Date.now())}.${format}`;
371
+ const absolutePath = resolveWorkspacePath(outputPath);
372
+ await writeFile(absolutePath, bytes);
373
+ return ok({
374
+ path: outputPath,
375
+ absolutePath,
376
+ format,
377
+ sampleRate: result.sampleRate,
378
+ channels: result.channels,
379
+ bitDepth: result.bitDepth,
380
+ bytes: bytes.length,
381
+ characters: args.text.length
382
+ });
383
+ } catch (error) {
384
+ return err(error instanceof Error ? error.message : "voice_tts failed");
385
+ }
386
+ });
387
+ server.registerTool("voice_stt", {
388
+ description: "Transcribe an audio file to text using the Alfe voice service (Deepgram). Accepts a WAV file or headerless mono 16-bit PCM. Billed per audio-second to your tenant credit pool.",
389
+ inputSchema: {
390
+ path: z.string().describe("Path to the audio file (absolute, or relative to the working directory). WAV or raw linear16 mono PCM."),
391
+ sampleRate: z.number().optional().describe("Sample rate in Hz (8000–48000). Only used for headerless PCM input; ignored for WAV (its header wins). Defaults to 24000.")
392
+ }
393
+ }, async (args) => {
394
+ try {
395
+ const { readFile } = await import("node:fs/promises");
396
+ const raw = await readFile(resolveWorkspacePath(args.path));
397
+ const wav = parseWav(raw);
398
+ let audio;
399
+ let sampleRate;
400
+ if (wav) {
401
+ if (wav.bitDepth !== 16 || wav.channels !== 1) return err(`voice_stt needs 16-bit mono audio; got ${String(wav.bitDepth)}-bit / ${String(wav.channels)}-channel WAV. Re-export as 16-bit mono.`);
402
+ audio = wav.pcm;
403
+ sampleRate = wav.sampleRate;
404
+ } else {
405
+ audio = raw;
406
+ sampleRate = args.sampleRate ?? 24e3;
407
+ }
408
+ return ok(await voiceClient.stt({
409
+ audio,
410
+ sampleRate
411
+ }));
412
+ } catch (error) {
413
+ return err(error instanceof Error ? error.message : "voice_stt failed");
414
+ }
415
+ });
416
+ };
417
+ //#endregion
418
+ //#region src/messaging.ts
419
+ /**
420
+ * Messaging tools — outbound SMS from the agent's own assigned phone number.
421
+ *
422
+ * `send_text_message` wraps `AgentApiClient.sendSms`, which hits the
423
+ * agent-authed `POST /mobile/sms/send` endpoint. The FROM number is resolved
424
+ * server-side from the agent identity on the token (an agent sends from the
425
+ * single active number assigned to it, or gets a 400 if it has none) — the
426
+ * tool only picks the recipient and body. The send is preflighted against
427
+ * and metered to the tenant credit pool server-side.
428
+ */
429
+ const registerMessagingTools = (server, ctx) => {
430
+ server.registerTool("send_text_message", {
431
+ description: "Send an SMS text message to a phone number from your assigned phone number. The recipient is any E.164 number (e.g. +14155550123). You send from the single number assigned to you — you cannot choose the sender. Billed per message segment to your tenant credit pool. Fails if you have no active phone number.",
432
+ inputSchema: {
433
+ to: z.string().min(1).describe("Recipient phone number in E.164 format (e.g. +14155550123)."),
434
+ body: z.string().min(1).describe("The text message body to send.")
435
+ }
436
+ }, async (args) => {
437
+ try {
438
+ const result = await ctx.client.sendSms({
439
+ to: args.to,
440
+ body: args.body
441
+ });
442
+ return ok({
443
+ sent: result.sent,
444
+ sid: result.sid,
445
+ to: args.to,
446
+ message: `Sent SMS to ${args.to} (sid: ${result.sid}).`
447
+ });
448
+ } catch (error) {
449
+ return err(error instanceof Error ? error.message : "send_text_message failed");
450
+ }
451
+ });
452
+ };
453
+ //#endregion
454
+ export { err, ok, registerIntegrationsTools, registerMemoryTools, registerMessagingTools, registerVoiceTools };
81
455
 
82
456
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/types.ts","../src/integrations.ts"],"sourcesContent":["import type { AgentApiClient } from '@alfe.ai/agent-api-client';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n/**\n * Context every tool registration function receives. The same context is\n * used by the CLI-bundled local server (`@alfe.ai/mcp-server`) and the\n * eventual lift target for `services/mcp` once each domain's public\n * `/agent/...` endpoints land.\n *\n * `agentId` / `tenantId` are derived from `client.whoami()` at the local\n * server's startup. They're carried in the context so tools that need\n * them (e.g. OAuth-URL builders) don't have to re-issue a whoami call\n * per invocation.\n */\nexport interface ToolContext {\n client: AgentApiClient;\n apiUrl: string;\n agentId: string;\n tenantId: string;\n}\n\n/**\n * Tool registration function shape. Each domain module exports one of\n * these; the server calls them with the live `McpServer` and a\n * `ToolContext`. Implementations call `server.registerTool(name, …)` for\n * each subtool they own.\n *\n * Tool names follow the `<domain>_<verb>` convention (no `alfe_` prefix —\n * the bundler namespaces the surface as `mcp__alfe-platform__<name>`).\n */\nexport type RegisterToolsFn = (server: McpServer, ctx: ToolContext) => void;\n\n/**\n * Standard error-result envelope mirroring the existing `services/mcp`\n * shape so the lifted tool surface stays drop-in compatible for any\n * downstream MCP client.\n *\n * The index signature satisfies the MCP SDK's `CallToolResult` shape —\n * the SDK declares its result type with `[x: string]: unknown` so\n * callers can add transport-level metadata. We don't use any, but the\n * shape needs to be assignable for the registration callback to typecheck.\n */\nexport interface ToolResult {\n content: { type: 'text'; text: string }[];\n isError?: boolean;\n [key: string]: unknown;\n}\n\nexport function ok(data: unknown): ToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\nexport function err(message: string): ToolResult {\n return {\n content: [{ type: 'text', text: message }],\n isError: true,\n };\n}\n","import { z } from 'zod';\nimport { err, ok, type RegisterToolsFn, type ToolContext } from './types.js';\n\nconst OAUTH_PROVIDERS = [\n 'xero',\n 'google',\n 'notion',\n 'microsoft',\n 'atlassian',\n 'myob',\n 'github',\n 'discord',\n 'slack',\n] as const;\n\n/**\n * Thin-slice subset of the `services/mcp` `integrations` domain — only\n * the two subtools whose underlying calls already hit public\n * `/agent/...` (or unauthenticated public) endpoints today. The rest of\n * the domain stays in `services/mcp` until the per-domain endpoint\n * promotion workstream lifts it (see\n * `project_mcp_tool_lift_workstream.md`).\n *\n * - `integrations_browse_registry` — public `GET /integrations/registry`\n * via `AgentApiClient.getRegistry()`.\n * - `integrations_start_oauth` — generates the public\n * `GET /<provider>/oauth/start` URL for the user to open in a browser.\n * Pure URL builder; no server-side call. Completion is confirmed out of\n * band (the dashboard, or the hosted MCP server's\n * `integrations_check_oauth_status`) — the local server intentionally has\n * no status-check tool yet.\n */\nexport const registerIntegrationsTools: RegisterToolsFn = (server, ctx: ToolContext): void => {\n server.registerTool(\n 'integrations_browse_registry',\n {\n description:\n 'Browse available integrations in the registry. Returns the catalogue of all published integrations with their manifests.',\n inputSchema: {},\n },\n async () => {\n try {\n const data = await ctx.client.getRegistry();\n return ok(data);\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'Failed to browse integrations');\n }\n },\n );\n\n server.registerTool(\n 'integrations_start_oauth',\n {\n description:\n 'Start an OAuth connection flow for an integration. Returns a URL the user must open in their browser to authorize the connection. After the user authorizes, the connection appears in the Alfe dashboard — this local server does not expose a status-check tool, so confirm completion there.',\n inputSchema: {\n provider: z\n .enum(OAUTH_PROVIDERS)\n .describe('The OAuth provider to connect'),\n scopes: z\n .string()\n .optional()\n .describe(\"Comma-separated scope groups (e.g. 'gmail,drive' for Google)\"),\n },\n },\n ({ provider, scopes }: { provider: (typeof OAUTH_PROVIDERS)[number]; scopes?: string }) => {\n const params = new URLSearchParams({ agentId: ctx.agentId, tenantId: ctx.tenantId });\n if (scopes) params.set('scopes', scopes);\n const url = `${ctx.apiUrl}/${provider}/oauth/start?${params.toString()}`;\n return Promise.resolve(\n ok({\n url,\n provider,\n message: `Open this URL to connect ${provider}: ${url}`,\n }),\n );\n },\n );\n};\n"],"mappings":";;AAgDA,SAAgB,GAAG,MAA2B;AAC5C,QAAO,EACL,SAAS,CAAC;EAAE,MAAM;EAAQ,MAAM,KAAK,UAAU,MAAM,MAAM,EAAE;EAAE,CAAC,EACjE;;AAGH,SAAgB,IAAI,SAA6B;AAC/C,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM;GAAS,CAAC;EAC1C,SAAS;EACV;;;;ACvDH,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;AAmBD,MAAa,6BAA8C,QAAQ,QAA2B;AAC5F,QAAO,aACL,gCACA;EACE,aACE;EACF,aAAa,EAAE;EAChB,EACD,YAAY;AACV,MAAI;AAEF,UAAO,GADM,MAAM,IAAI,OAAO,aAAa,CAC5B;WACR,OAAgB;AACvB,UAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,gCAAgC;;GAGzF;AAED,QAAO,aACL,4BACA;EACE,aACE;EACF,aAAa;GACX,UAAU,EACP,KAAK,gBAAgB,CACrB,SAAS,gCAAgC;GAC5C,QAAQ,EACL,QAAQ,CACR,UAAU,CACV,SAAS,+DAA+D;GAC5E;EACF,GACA,EAAE,UAAU,aAA8E;EACzF,MAAM,SAAS,IAAI,gBAAgB;GAAE,SAAS,IAAI;GAAS,UAAU,IAAI;GAAU,CAAC;AACpF,MAAI,OAAQ,QAAO,IAAI,UAAU,OAAO;EACxC,MAAM,MAAM,GAAG,IAAI,OAAO,GAAG,SAAS,eAAe,OAAO,UAAU;AACtE,SAAO,QAAQ,QACb,GAAG;GACD;GACA;GACA,SAAS,4BAA4B,SAAS,IAAI;GACnD,CAAC,CACH;GAEJ"}
1
+ {"version":3,"file":"index.js","names":["resolvePath"],"sources":["../src/types.ts","../src/integrations.ts","../src/memory.ts","../src/audio.ts","../src/voice.ts","../src/messaging.ts"],"sourcesContent":["import type { AgentApiClient } from '@alfe.ai/agent-api-client';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n/**\n * Context every tool registration function receives. The same context is\n * used by the CLI-bundled local server (`@alfe.ai/mcp-server`) and the\n * eventual lift target for `services/mcp` once each domain's public\n * `/agent/...` endpoints land.\n *\n * `agentId` / `tenantId` are derived from `client.whoami()` at the local\n * server's startup. They're carried in the context so tools that need\n * them (e.g. OAuth-URL builders) don't have to re-issue a whoami call\n * per invocation.\n */\nexport interface ToolContext {\n client: AgentApiClient;\n apiUrl: string;\n agentId: string;\n tenantId: string;\n /**\n * Base URL for the voice service's agent-authed one-shot endpoints\n * (`/voice/tts`, `/voice/stt`). Currently equals `apiUrl` (the endpoints\n * are co-located on the shared api gateway) but carried separately so the\n * voice tools have a stable seam if voice ever moves hosts — mirrors\n * `ResolvedConfig.voiceServiceUrl` in `@alfe.ai/config`. Only consumed by\n * `registerVoiceTools`; when omitted, the voice tools fall back to `apiUrl`.\n */\n voiceApiUrl?: string;\n /**\n * A dedicated `AgentApiClient` whose `apiUrl` is the voice service base\n * (`voiceApiUrl`). `AgentApiClient.tts`/`stt` hit `/voice/tts` and\n * `/voice/stt` RELATIVE to the client's own `apiUrl`, so the voice tools\n * need a client keyed on the voice URL rather than the main `client`. The\n * server (which holds the api key) builds this; `registerVoiceTools` falls\n * back to `client` if it's absent (safe today since voice is co-located).\n */\n voiceClient?: AgentApiClient;\n}\n\n/**\n * Tool registration function shape. Each domain module exports one of\n * these; the server calls them with the live `McpServer` and a\n * `ToolContext`. Implementations call `server.registerTool(name, …)` for\n * each subtool they own.\n *\n * Tool names follow the `<domain>_<verb>` convention (no `alfe_` prefix —\n * the bundler namespaces the surface as `mcp__alfe-platform__<name>`).\n */\nexport type RegisterToolsFn = (server: McpServer, ctx: ToolContext) => void;\n\n/**\n * Standard error-result envelope mirroring the existing `services/mcp`\n * shape so the lifted tool surface stays drop-in compatible for any\n * downstream MCP client.\n *\n * The index signature satisfies the MCP SDK's `CallToolResult` shape —\n * the SDK declares its result type with `[x: string]: unknown` so\n * callers can add transport-level metadata. We don't use any, but the\n * shape needs to be assignable for the registration callback to typecheck.\n */\nexport interface ToolResult {\n content: { type: 'text'; text: string }[];\n isError?: boolean;\n [key: string]: unknown;\n}\n\nexport function ok(data: unknown): ToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\nexport function err(message: string): ToolResult {\n return {\n content: [{ type: 'text', text: message }],\n isError: true,\n };\n}\n","import { z } from 'zod';\nimport { err, ok, type RegisterToolsFn, type ToolContext } from './types.js';\n\nconst OAUTH_PROVIDERS = [\n 'xero',\n 'google',\n 'notion',\n 'microsoft',\n 'atlassian',\n 'myob',\n 'github',\n 'discord',\n 'slack',\n] as const;\n\n/**\n * Thin-slice subset of the `services/mcp` `integrations` domain — only\n * the two subtools whose underlying calls already hit public\n * `/agent/...` (or unauthenticated public) endpoints today. The rest of\n * the domain stays in `services/mcp` until the per-domain endpoint\n * promotion workstream lifts it (see\n * `project_mcp_tool_lift_workstream.md`).\n *\n * - `integrations_browse_registry` — public `GET /integrations/registry`\n * via `AgentApiClient.getRegistry()`.\n * - `integrations_start_oauth` — generates the public\n * `GET /<provider>/oauth/start` URL for the user to open in a browser.\n * Pure URL builder; no server-side call. Completion is confirmed out of\n * band (the dashboard, or the hosted MCP server's\n * `integrations_check_oauth_status`) — the local server intentionally has\n * no status-check tool yet.\n */\nexport const registerIntegrationsTools: RegisterToolsFn = (server, ctx: ToolContext): void => {\n server.registerTool(\n 'integrations_browse_registry',\n {\n description:\n 'Browse available integrations in the registry. Returns the catalogue of all published integrations with their manifests.',\n inputSchema: {},\n },\n async () => {\n try {\n const data = await ctx.client.getRegistry();\n return ok(data);\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'Failed to browse integrations');\n }\n },\n );\n\n server.registerTool(\n 'integrations_start_oauth',\n {\n description:\n 'Start an OAuth connection flow for an integration. Returns a URL the user must open in their browser to authorize the connection. After the user authorizes, the connection appears in the Alfe dashboard — this local server does not expose a status-check tool, so confirm completion there.',\n inputSchema: {\n provider: z\n .enum(OAUTH_PROVIDERS)\n .describe('The OAuth provider to connect'),\n scopes: z\n .string()\n .optional()\n .describe(\"Comma-separated scope groups (e.g. 'gmail,drive' for Google)\"),\n },\n },\n ({ provider, scopes }: { provider: (typeof OAUTH_PROVIDERS)[number]; scopes?: string }) => {\n const params = new URLSearchParams({ agentId: ctx.agentId, tenantId: ctx.tenantId });\n if (scopes) params.set('scopes', scopes);\n const url = `${ctx.apiUrl}/${provider}/oauth/start?${params.toString()}`;\n return Promise.resolve(\n ok({\n url,\n provider,\n message: `Open this URL to connect ${provider}: ${url}`,\n }),\n );\n },\n );\n};\n","import { z } from 'zod';\nimport { err, ok, type RegisterToolsFn, type ToolContext } from './types.js';\n\n/**\n * Memory tools — a runtime-agnostic mirror of the OpenClaw memory-cloud\n * plugin (`@alfe.ai/openclaw-memory-cloud`). All 7 tools (memory_recall,\n * memory_store, memory_learn, memory_forget, memory_graph, memory_stats,\n * memory_navigate) keep the plugin's names, descriptions, and argument\n * schemas so an agent gets the same memory behaviour whether it runs under\n * OpenClaw (via the plugin) or a Claude Code session (via this MCP server's\n * `claude-code` profile). Each subtool is a thin wrapper over the\n * corresponding `AgentApiClient` memory method against public\n * `/agent/memory/...` endpoints.\n *\n * One deliberate divergence: the plugin registers `memory_recall` with a\n * `memory_search` alias, but the MCP `registerTool` API is single-name, so\n * the alias is dropped here (agents should call `memory_recall`).\n *\n * NOTE for OpenClaw agents: these tools are NOT registered in the default\n * server profile — OpenClaw agents already get `memory_*` from their\n * plugin, and registering them here would double the surface. They only\n * register under the `claude-code` profile.\n */\nexport const registerMemoryTools: RegisterToolsFn = (server, ctx: ToolContext): void => {\n server.registerTool(\n 'memory_recall',\n {\n description:\n 'Search conversation memory and knowledge graph. Returns structured facts and relevant conversation excerpts.',\n inputSchema: {\n query: z.string().describe('What to search for'),\n limit: z.number().optional().describe('Maximum results (default 10)'),\n topic: z.string().optional().describe('Filter by topic'),\n tag: z\n .string()\n .optional()\n .describe('Filter by tag (fact/decision/preference/event/discovery)'),\n },\n },\n async (args: { query: string; limit?: number; topic?: string; tag?: string }) => {\n try {\n const results = await ctx.client.memorySearch(args.query, {\n limit: args.limit,\n topic: args.topic,\n tag: args.tag,\n });\n return ok(results);\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'memory_recall failed');\n }\n },\n );\n\n server.registerTool(\n 'memory_store',\n {\n description: 'Explicitly save a piece of information to long-term memory.',\n inputSchema: {\n text: z.string().describe('The information to remember'),\n topic: z.string().optional().describe('Topic category (e.g., person name, project)'),\n tag: z\n .string()\n .optional()\n .describe('Memory type: fact, decision, preference, event, discovery'),\n importance: z.number().optional().describe('Importance 0-1 (default 0.7)'),\n },\n },\n async (args: { text: string; topic?: string; tag?: string; importance?: number }) => {\n try {\n const result = await ctx.client.memoryStore(args.text, {\n topic: args.topic,\n tag: args.tag,\n importance: args.importance,\n });\n return ok({ memoryId: result.memoryId, message: `Stored memory: ${result.memoryId}` });\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'memory_store failed');\n }\n },\n );\n\n server.registerTool(\n 'memory_learn',\n {\n description:\n 'Save arbitrary content (a paragraph, a doc, a fact list) into long-term memory. The system auto-classifies and extracts entities. Use this when the user asks you to learn, remember, or study something — or when you read a document worth retaining. Pass the source text directly; do not paraphrase first.',\n inputSchema: {\n content: z\n .string()\n .optional()\n .describe('Inline text to ingest. Provide either content OR path, not both.'),\n path: z\n .string()\n .optional()\n .describe(\n 'Workspace-relative file path to read and ingest. Provide either content OR path, not both.',\n ),\n source: z\n .string()\n .optional()\n .describe('Optional label describing where the content came from (e.g. doc title, url).'),\n },\n },\n async (args: { content?: string; path?: string; source?: string }) => {\n const content = typeof args.content === 'string' ? args.content : undefined;\n const filePath = typeof args.path === 'string' ? args.path : undefined;\n const source = typeof args.source === 'string' ? args.source : undefined;\n\n if (!content && !filePath) {\n return err('Error: provide either `content` or `path`.');\n }\n if (content && filePath) {\n return err('Error: provide either `content` or `path`, not both.');\n }\n\n let text: string;\n let resolvedSource = source;\n let sourceType: 'file' | 'inline' = 'inline';\n\n if (filePath !== undefined) {\n const nodePath = await import('node:path');\n const fs = await import('node:fs/promises');\n const cwd = process.cwd();\n const resolved = nodePath.resolve(cwd, filePath);\n if (!resolved.startsWith(cwd + nodePath.sep) && resolved !== cwd) {\n return err(`Error: path \"${filePath}\" escapes the workspace.`);\n }\n try {\n text = await fs.readFile(resolved, 'utf8');\n } catch (readErr: unknown) {\n return err(\n `Error reading \"${filePath}\": ${readErr instanceof Error ? readErr.message : String(readErr)}`,\n );\n }\n resolvedSource = source ?? filePath;\n sourceType = 'file';\n } else if (content !== undefined) {\n text = content;\n } else {\n // Unreachable — the both-or-neither cases returned above — but keeps\n // `text` definitely-assigned without a non-null assertion.\n return err('Error: provide either `content` or `path`.');\n }\n\n try {\n const result = await ctx.client.memoryLearn({ text, source: resolvedSource, sourceType });\n const label = result.source ?? 'content';\n return ok({\n memoriesStored: result.memoriesStored,\n triplesStored: result.triplesStored,\n chunks: result.chunks,\n source: result.source,\n message: `Stored ${String(result.memoriesStored)} memories (${String(result.triplesStored)} facts, ${String(result.chunks)} chunks) from ${label}.`,\n });\n } catch (error: unknown) {\n return err(\n `Error: failed to store memory — ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n },\n );\n\n server.registerTool(\n 'memory_forget',\n {\n description: 'Search for and delete memories matching a query.',\n inputSchema: {\n query: z.string().describe('Search for memories to delete'),\n },\n },\n async (args: { query: string }) => {\n try {\n const results = await ctx.client.memorySearch(args.query, { limit: 5 });\n if (results.memories.length === 0) return ok({ deleted: 0, message: 'No matching memories found.' });\n\n const deleted: string[] = [];\n for (const mem of results.memories) {\n try {\n await ctx.client.memoryDelete(mem.id);\n deleted.push(mem.id);\n } catch {\n // best-effort — skip failures, mirror plugin behaviour\n }\n }\n return ok({\n deleted: deleted.length,\n matched: results.memories.length,\n message: `Deleted ${String(deleted.length)} of ${String(results.memories.length)} matching memories.`,\n });\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'memory_forget failed');\n }\n },\n );\n\n server.registerTool(\n 'memory_graph',\n {\n description:\n 'Look up what you know about a specific entity from the knowledge graph.',\n inputSchema: {\n entity: z\n .string()\n .describe('The entity to look up (person, concept, system)'),\n },\n },\n async ({ entity }) => {\n try {\n const result = await ctx.client.memoryLookupEntity(entity);\n if (result.triples.length === 0) {\n return ok({\n subject: entity,\n triples: [],\n message: `No knowledge found about \"${entity}\".`,\n });\n }\n const facts = result.triples.map(\n (t) =>\n `- ${result.subject} ${t.predicate} ${t.object} (since ${t.validFrom.slice(0, 10)})`,\n );\n return ok({\n subject: result.subject,\n triples: result.triples,\n message: `Known facts about ${result.subject}:\\n${facts.join('\\n')}`,\n });\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'memory_graph failed');\n }\n },\n );\n\n server.registerTool(\n 'memory_stats',\n {\n description: 'Get memory storage statistics.',\n inputSchema: {},\n },\n async () => {\n try {\n const s = await ctx.client.memoryStats();\n return ok({\n vectorCount: s.vectorCount,\n tripleCount: s.tripleCount,\n storageEstimateBytes: s.storageEstimateBytes,\n message: `Memories: ${String(s.vectorCount)}, Knowledge facts: ${String(s.tripleCount)}, Storage: ${String(Math.round(s.storageEstimateBytes / 1024))} KB`,\n });\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'memory_stats failed');\n }\n },\n );\n\n server.registerTool(\n 'memory_navigate',\n {\n description:\n 'Browse your memory palace structure — list topics, subtopics, and memory counts.',\n inputSchema: {},\n },\n async () => {\n try {\n const nav = await ctx.client.memoryNavigate();\n return ok(nav);\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'memory_navigate failed');\n }\n },\n );\n};\n","/**\n * Minimal WAV (RIFF/PCM) helpers — no dependencies, no re-encoding.\n *\n * Ported from `@alfe.ai/openclaw-voice`'s `audio.ts` so the voice MCP tools\n * hand the agent a playable file (WAV) rather than headerless PCM, and strip\n * the header before sending audio to the linear16 STT endpoint. Kept\n * standalone (rather than importing the plugin) so mcp-tools stays\n * runtime-agnostic with no OpenClaw plugin dep.\n */\n\nexport interface PcmFraming {\n sampleRate: number;\n channels: number;\n bitDepth: number;\n}\n\nexport interface ParsedWav extends PcmFraming {\n /** Headerless PCM sample bytes (the `data` chunk payload). */\n pcm: Buffer;\n}\n\n/** Prepend a canonical 44-byte PCM WAV header to raw PCM samples. */\nexport function pcmToWav(pcm: Buffer, framing: PcmFraming): Buffer {\n const { sampleRate, channels, bitDepth } = framing;\n const bytesPerSample = bitDepth / 8;\n const blockAlign = channels * bytesPerSample;\n const byteRate = sampleRate * blockAlign;\n\n const header = Buffer.alloc(44);\n header.write('RIFF', 0, 'ascii');\n header.writeUInt32LE(36 + pcm.length, 4);\n header.write('WAVE', 8, 'ascii');\n header.write('fmt ', 12, 'ascii');\n header.writeUInt32LE(16, 16); // fmt chunk size\n header.writeUInt16LE(1, 20); // audioFormat = PCM\n header.writeUInt16LE(channels, 22);\n header.writeUInt32LE(sampleRate, 24);\n header.writeUInt32LE(byteRate, 28);\n header.writeUInt16LE(blockAlign, 32);\n header.writeUInt16LE(bitDepth, 34);\n header.write('data', 36, 'ascii');\n header.writeUInt32LE(pcm.length, 40);\n\n return Buffer.concat([header, pcm]);\n}\n\n/**\n * Parse a WAV buffer into its PCM payload + framing. Returns `null` when the\n * buffer is not a RIFF/WAVE file (caller should then treat the bytes as raw\n * PCM). Walks the chunk list so it tolerates `fmt `/`data` ordering, extra\n * chunks (e.g. `LIST`/`fact`), and word-alignment padding.\n */\nexport function parseWav(buf: Buffer): ParsedWav | null {\n if (buf.length < 44) return null;\n if (buf.toString('ascii', 0, 4) !== 'RIFF') return null;\n if (buf.toString('ascii', 8, 12) !== 'WAVE') return null;\n\n let sampleRate = 0;\n let channels = 0;\n let bitDepth = 0;\n let pcm: Buffer | null = null;\n\n let offset = 12;\n while (offset + 8 <= buf.length) {\n const chunkId = buf.toString('ascii', offset, offset + 4);\n const chunkSize = buf.readUInt32LE(offset + 4);\n const bodyStart = offset + 8;\n\n if (chunkId === 'fmt ' && bodyStart + 16 <= buf.length) {\n channels = buf.readUInt16LE(bodyStart + 2);\n sampleRate = buf.readUInt32LE(bodyStart + 4);\n bitDepth = buf.readUInt16LE(bodyStart + 14);\n } else if (chunkId === 'data') {\n const end = Math.min(bodyStart + chunkSize, buf.length);\n pcm = buf.subarray(bodyStart, end);\n }\n\n // Chunks are word-aligned: an odd size carries a trailing pad byte.\n offset = bodyStart + chunkSize + (chunkSize % 2);\n }\n\n if (!pcm || sampleRate === 0 || channels === 0 || bitDepth === 0) return null;\n return { pcm, sampleRate, channels, bitDepth };\n}\n","import { z } from 'zod';\nimport { isAbsolute, resolve as resolvePath } from 'node:path';\nimport type { VoiceTtsModel } from '@alfe.ai/agent-api-client';\nimport { err, ok, type RegisterToolsFn, type ToolContext } from './types.js';\nimport { pcmToWav, parseWav } from './audio.js';\n\n/**\n * Voice tools — a runtime-agnostic mirror of the one-shot TTS/STT tools in\n * the OpenClaw voice plugin (`@alfe.ai/openclaw-voice`). The channel-only\n * tools (hangup/transfer/dtmf) are intentionally omitted — they require a\n * live channel service (Discord/Twilio) and are meaningless in a standalone\n * MCP session.\n *\n * Both tools read/write files on disk (the MCP transport can't cleanly carry\n * raw audio bytes as JSON), converting between the voice service's headerless\n * PCM and a playable WAV via `./audio.js`.\n *\n * `tts`/`stt` on `AgentApiClient` hit `/voice/tts` and `/voice/stt` relative\n * to the client's `apiUrl`, so we prefer `ctx.voiceClient` (keyed on the\n * voice service URL) and fall back to `ctx.client` when it's absent (safe\n * today — voice is co-located on the shared api gateway).\n */\nexport const registerVoiceTools: RegisterToolsFn = (server, ctx: ToolContext): void => {\n const voiceClient = ctx.voiceClient ?? ctx.client;\n\n const resolveWorkspacePath = (p: string): string =>\n isAbsolute(p) ? p : resolvePath(process.cwd(), p);\n\n server.registerTool(\n 'voice_tts',\n {\n description:\n 'Convert text to speech using the Alfe voice service (ElevenLabs). Writes a playable audio file to the workspace and returns its path. Billed per character to your tenant credit pool.',\n inputSchema: {\n text: z.string().describe('Text to synthesize (1–5000 characters).'),\n outputPath: z\n .string()\n .optional()\n .describe(\n 'Where to write the audio file (absolute, or relative to the working directory). Defaults to alfe-tts-<timestamp>.wav in the working directory.',\n ),\n format: z\n .enum(['wav', 'pcm'])\n .optional()\n .describe(\n \"Output container. 'wav' (default) is a playable file; 'pcm' is headerless 24kHz/mono/16-bit raw PCM.\",\n ),\n voiceId: z\n .string()\n .optional()\n .describe('ElevenLabs voice ID. Platform default when omitted.'),\n model: z\n .enum(['eleven_turbo_v2_5', 'eleven_multilingual_v2'])\n .optional()\n .describe('TTS model. eleven_turbo_v2_5 (lower latency) when omitted.'),\n },\n },\n async (args: {\n text: string;\n outputPath?: string;\n format?: 'wav' | 'pcm';\n voiceId?: string;\n model?: VoiceTtsModel;\n }) => {\n try {\n const { writeFile } = await import('node:fs/promises');\n const format = args.format ?? 'wav';\n const result = await voiceClient.tts({\n text: args.text,\n voiceId: args.voiceId,\n model: args.model,\n });\n\n const bytes =\n format === 'wav'\n ? pcmToWav(result.audio, {\n sampleRate: result.sampleRate,\n channels: result.channels,\n bitDepth: result.bitDepth,\n })\n : result.audio;\n\n const outputPath = args.outputPath ?? `alfe-tts-${String(Date.now())}.${format}`;\n const absolutePath = resolveWorkspacePath(outputPath);\n await writeFile(absolutePath, bytes);\n\n return ok({\n path: outputPath,\n absolutePath,\n format,\n sampleRate: result.sampleRate,\n channels: result.channels,\n bitDepth: result.bitDepth,\n bytes: bytes.length,\n characters: args.text.length,\n });\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'voice_tts failed');\n }\n },\n );\n\n server.registerTool(\n 'voice_stt',\n {\n description:\n 'Transcribe an audio file to text using the Alfe voice service (Deepgram). Accepts a WAV file or headerless mono 16-bit PCM. Billed per audio-second to your tenant credit pool.',\n inputSchema: {\n path: z\n .string()\n .describe(\n 'Path to the audio file (absolute, or relative to the working directory). WAV or raw linear16 mono PCM.',\n ),\n sampleRate: z\n .number()\n .optional()\n .describe(\n 'Sample rate in Hz (8000–48000). Only used for headerless PCM input; ignored for WAV (its header wins). Defaults to 24000.',\n ),\n },\n },\n async (args: { path: string; sampleRate?: number }) => {\n try {\n const { readFile } = await import('node:fs/promises');\n const inputPath = resolveWorkspacePath(args.path);\n const raw = await readFile(inputPath);\n\n const wav = parseWav(raw);\n let audio: Buffer;\n let sampleRate: number;\n if (wav) {\n if (wav.bitDepth !== 16 || wav.channels !== 1) {\n return err(\n `voice_stt needs 16-bit mono audio; got ${String(wav.bitDepth)}-bit / ${String(wav.channels)}-channel WAV. Re-export as 16-bit mono.`,\n );\n }\n audio = wav.pcm;\n sampleRate = wav.sampleRate;\n } else {\n audio = raw;\n sampleRate = args.sampleRate ?? 24000;\n }\n\n const result = await voiceClient.stt({ audio, sampleRate });\n return ok(result);\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'voice_stt failed');\n }\n },\n );\n};\n","import { z } from 'zod';\nimport { err, ok, type RegisterToolsFn, type ToolContext } from './types.js';\n\n/**\n * Messaging tools — outbound SMS from the agent's own assigned phone number.\n *\n * `send_text_message` wraps `AgentApiClient.sendSms`, which hits the\n * agent-authed `POST /mobile/sms/send` endpoint. The FROM number is resolved\n * server-side from the agent identity on the token (an agent sends from the\n * single active number assigned to it, or gets a 400 if it has none) — the\n * tool only picks the recipient and body. The send is preflighted against\n * and metered to the tenant credit pool server-side.\n */\nexport const registerMessagingTools: RegisterToolsFn = (server, ctx: ToolContext): void => {\n server.registerTool(\n 'send_text_message',\n {\n description:\n \"Send an SMS text message to a phone number from your assigned phone number. The recipient is any E.164 number (e.g. +14155550123). You send from the single number assigned to you — you cannot choose the sender. Billed per message segment to your tenant credit pool. Fails if you have no active phone number.\",\n inputSchema: {\n to: z\n .string()\n .min(1)\n .describe('Recipient phone number in E.164 format (e.g. +14155550123).'),\n body: z.string().min(1).describe('The text message body to send.'),\n },\n },\n async (args: { to: string; body: string }) => {\n try {\n const result = await ctx.client.sendSms({ to: args.to, body: args.body });\n return ok({\n sent: result.sent,\n sid: result.sid,\n to: args.to,\n message: `Sent SMS to ${args.to} (sid: ${result.sid}).`,\n });\n } catch (error: unknown) {\n return err(error instanceof Error ? error.message : 'send_text_message failed');\n }\n },\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/mcp-tools",
3
- "version": "0.1.15",
3
+ "version": "0.2.1",
4
4
  "description": "Shared MCP tool implementations for Alfe's local CLI-bundled MCP server and (later) the Fly-deployed services/mcp. All tools use AgentApiClient against public /agent/... endpoints — never InternalClient.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@modelcontextprotocol/sdk": "^1.29.0",
20
20
  "zod": "^3.25.0",
21
- "@alfe.ai/agent-api-client": "0.10.0"
21
+ "@alfe.ai/agent-api-client": "0.11.1"
22
22
  },
23
23
  "license": "UNLICENSED",
24
24
  "homepage": "https://alfe.ai",