@alfe.ai/mcp-tools 0.2.2 → 0.2.4

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
@@ -105,7 +105,7 @@ const registerMemoryTools = (server, ctx) => {
105
105
  description: "Search conversation memory and knowledge graph. Returns structured facts and relevant conversation excerpts.",
106
106
  inputSchema: {
107
107
  query: zod.z.string().describe("What to search for"),
108
- limit: zod.z.number().optional().describe("Maximum results (default 10)"),
108
+ limit: zod.z.coerce.number().optional().describe("Maximum results (default 10)"),
109
109
  topic: zod.z.string().optional().describe("Filter by topic"),
110
110
  tag: zod.z.string().optional().describe("Filter by tag (fact/decision/preference/event/discovery)")
111
111
  }
@@ -126,7 +126,7 @@ const registerMemoryTools = (server, ctx) => {
126
126
  text: zod.z.string().describe("The information to remember"),
127
127
  topic: zod.z.string().optional().describe("Topic category (e.g., person name, project)"),
128
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)")
129
+ importance: zod.z.coerce.number().optional().describe("Importance 0-1 (default 0.7)")
130
130
  }
131
131
  }, async (args) => {
132
132
  try {
@@ -389,7 +389,7 @@ const registerVoiceTools = (server, ctx) => {
389
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
390
  inputSchema: {
391
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.")
392
+ sampleRate: zod.z.coerce.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
393
  }
394
394
  }, async (args) => {
395
395
  try {
package/dist/index.js CHANGED
@@ -104,7 +104,7 @@ const registerMemoryTools = (server, ctx) => {
104
104
  description: "Search conversation memory and knowledge graph. Returns structured facts and relevant conversation excerpts.",
105
105
  inputSchema: {
106
106
  query: z.string().describe("What to search for"),
107
- limit: z.number().optional().describe("Maximum results (default 10)"),
107
+ limit: z.coerce.number().optional().describe("Maximum results (default 10)"),
108
108
  topic: z.string().optional().describe("Filter by topic"),
109
109
  tag: z.string().optional().describe("Filter by tag (fact/decision/preference/event/discovery)")
110
110
  }
@@ -125,7 +125,7 @@ const registerMemoryTools = (server, ctx) => {
125
125
  text: z.string().describe("The information to remember"),
126
126
  topic: z.string().optional().describe("Topic category (e.g., person name, project)"),
127
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)")
128
+ importance: z.coerce.number().optional().describe("Importance 0-1 (default 0.7)")
129
129
  }
130
130
  }, async (args) => {
131
131
  try {
@@ -388,7 +388,7 @@ const registerVoiceTools = (server, ctx) => {
388
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
389
  inputSchema: {
390
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.")
391
+ sampleRate: z.coerce.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
392
  }
393
393
  }, async (args) => {
394
394
  try {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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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"}
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.coerce.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.coerce.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 .coerce.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.2.2",
3
+ "version": "0.2.4",
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.11.2"
21
+ "@alfe.ai/agent-api-client": "0.13.0"
22
22
  },
23
23
  "license": "UNLICENSED",
24
24
  "homepage": "https://alfe.ai",