@cencori/mcp 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -147,6 +147,37 @@ var PlatformClient = class {
147
147
  del(path) {
148
148
  return this.request("DELETE", path);
149
149
  }
150
+ /** POST JSON and read a BINARY response (e.g. TTS audio). Returns base64 + mime. */
151
+ async postBinary(path, body) {
152
+ const url = new URL(`/api${path}`, this.baseUrl);
153
+ const response = await fetch(url, {
154
+ method: "POST",
155
+ headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
156
+ body: JSON.stringify(body),
157
+ signal: fetchSignal()
158
+ });
159
+ if (!response.ok) {
160
+ throw new Error(`Cencori API error: ${await readHttpErrorMessage(response)}`);
161
+ }
162
+ const mimeType = response.headers.get("content-type")?.split(";")[0]?.trim() || "application/octet-stream";
163
+ const buf = Buffer.from(await response.arrayBuffer());
164
+ return { base64: buf.toString("base64"), mimeType };
165
+ }
166
+ /** POST multipart/form-data (e.g. STT audio upload). Let fetch set the boundary. */
167
+ async postForm(path, form) {
168
+ const url = new URL(`/api${path}`, this.baseUrl);
169
+ const response = await fetch(url, {
170
+ method: "POST",
171
+ headers: { Authorization: `Bearer ${this.apiKey}` },
172
+ body: form,
173
+ signal: fetchSignal()
174
+ });
175
+ if (!response.ok) {
176
+ throw new Error(`Cencori API error: ${await readHttpErrorMessage(response)}`);
177
+ }
178
+ const text = await response.text();
179
+ return text ? JSON.parse(text) : void 0;
180
+ }
150
181
  listModels() {
151
182
  return this.get("/v1/models");
152
183
  }
@@ -199,6 +230,16 @@ var DocsClient = class {
199
230
  }
200
231
  return response.json();
201
232
  }
233
+ /** Fetch the raw `llm.txt` integration contract (public, no auth). */
234
+ async getIntegrationGuide() {
235
+ const url = new URL("/llm.txt", this.baseUrl);
236
+ const response = await fetch(url, { signal: fetchSignal() });
237
+ if (!response.ok) {
238
+ const message = await readHttpErrorMessage(response);
239
+ throw new Error(`Integration guide fetch failed (${response.status}): ${message}`);
240
+ }
241
+ return response.text();
242
+ }
202
243
  };
203
244
 
204
245
  // src/tools/docs.ts
@@ -287,6 +328,28 @@ function registerDocsTools(server, docs, docsBaseUrl) {
287
328
  return jsonResult(navigation);
288
329
  }
289
330
  );
331
+ server.registerTool(
332
+ "get_integration_guide",
333
+ {
334
+ title: "Get the Cencori integration guide (llm.txt)",
335
+ description: 'Fetch the full Cencori "Integration Contract for Code Agents" (llm.txt) \u2014 the authoritative, always-current guide for setting up Cencori in a codebase: package names, import paths, env vars, base URLs, SDK usage, request/response shapes, and step-by-step setup. Call this FIRST when asked to add, integrate, or set up Cencori in a project.',
336
+ inputSchema: {},
337
+ annotations: READ_ONLY_ANNOTATIONS
338
+ },
339
+ async () => {
340
+ const guide = await docs.getIntegrationGuide();
341
+ return {
342
+ content: [
343
+ {
344
+ type: "text",
345
+ text: `Source: ${docsBaseUrl}/llm.txt
346
+
347
+ ${guide}`
348
+ }
349
+ ]
350
+ };
351
+ }
352
+ );
290
353
  }
291
354
 
292
355
  // src/tools/gateway.ts
@@ -449,39 +512,50 @@ function registerAgentsTools(server, client, caps) {
449
512
  import { z as z4 } from "zod";
450
513
  var scopeShape = {
451
514
  namespace: z4.string().optional().describe("Memory namespace to scope to."),
452
- scope: z4.string().optional().describe("Memory scope (e.g. project, user, session)."),
453
- user_id: z4.string().optional().describe("Scope to a specific end-user id."),
454
- session_id: z4.string().optional().describe("Scope to a specific session id.")
515
+ scope: z4.enum(["user", "session"]).optional().describe('Memory scope. Defaults to "user".'),
516
+ user_id: z4.string().optional().describe("End-user id. REQUIRED for user scope (the default)."),
517
+ session_id: z4.string().optional().describe("Session id. REQUIRED for session scope.")
455
518
  };
519
+ function missingScopeKey({ scope, user_id, session_id }) {
520
+ if ((scope ?? "user") === "session") {
521
+ return session_id || user_id ? null : "session_id (or user_id) is required for session scope.";
522
+ }
523
+ return user_id ? null : 'user_id is required for user scope (the default). Pass user_id, or use scope="session" with session_id.';
524
+ }
525
+ var scopeErr = (msg) => jsonResult({ error: "missing_scope", message: msg });
456
526
  function registerMemoryTools(server, client, caps) {
457
527
  server.registerTool(
458
528
  "list_memories",
459
529
  {
460
530
  title: "List memories",
461
- description: "List stored memories for the project, optionally scoped by namespace/user/session.",
531
+ description: "List stored memories for a user or session. Requires user_id (or session_id for session scope).",
462
532
  inputSchema: {
463
533
  ...scopeShape,
464
- limit: z4.number().int().positive().max(200).optional(),
534
+ limit: z4.number().int().positive().max(100).optional(),
465
535
  cursor: z4.string().optional().describe("Pagination cursor from a previous response.")
466
536
  },
467
537
  annotations: READ_ONLY_ANNOTATIONS
468
538
  },
469
- async ({ namespace, scope, user_id, session_id, limit, cursor }) => jsonResult(
470
- await client.get("/v1/memory/list", {
471
- namespace,
472
- scope,
473
- userId: user_id,
474
- sessionId: session_id,
475
- limit: limit?.toString(),
476
- cursor
477
- })
478
- )
539
+ async ({ namespace, scope, user_id, session_id, limit, cursor }) => {
540
+ const err = missingScopeKey({ scope, user_id, session_id });
541
+ if (err) return scopeErr(err);
542
+ return jsonResult(
543
+ await client.get("/v1/memory/list", {
544
+ namespace,
545
+ scope,
546
+ userId: user_id,
547
+ sessionId: session_id,
548
+ limit: limit?.toString(),
549
+ cursor
550
+ })
551
+ );
552
+ }
479
553
  );
480
554
  server.registerTool(
481
555
  "search_memory",
482
556
  {
483
557
  title: "Search memory (semantic)",
484
- description: "Semantically search stored memories by query.",
558
+ description: "Semantically search a user\u2019s or session\u2019s memories. Requires user_id (or session_id for session scope).",
485
559
  inputSchema: {
486
560
  query: z4.string().min(1).describe("Natural-language search query."),
487
561
  ...scopeShape,
@@ -490,17 +564,21 @@ function registerMemoryTools(server, client, caps) {
490
564
  },
491
565
  annotations: READ_ONLY_ANNOTATIONS
492
566
  },
493
- async ({ query, namespace, scope, user_id, session_id, top_k, threshold }) => jsonResult(
494
- await client.post("/v1/memory/search", {
495
- query,
496
- namespace,
497
- scope,
498
- userId: user_id,
499
- sessionId: session_id,
500
- topK: top_k,
501
- threshold
502
- })
503
- )
567
+ async ({ query, namespace, scope, user_id, session_id, top_k, threshold }) => {
568
+ const err = missingScopeKey({ scope, user_id, session_id });
569
+ if (err) return scopeErr(err);
570
+ return jsonResult(
571
+ await client.post("/v1/memory/search", {
572
+ query,
573
+ namespace,
574
+ scope,
575
+ userId: user_id,
576
+ sessionId: session_id,
577
+ topK: top_k,
578
+ threshold
579
+ })
580
+ );
581
+ }
504
582
  );
505
583
  server.registerTool(
506
584
  "get_memory",
@@ -516,24 +594,23 @@ function registerMemoryTools(server, client, caps) {
516
594
  "list_memory_entities",
517
595
  {
518
596
  title: "List memory entities",
519
- description: "List entities resolved from the memory graph.",
597
+ description: "List entities resolved from a user\u2019s or session\u2019s memory graph. Requires user_id (or session_id).",
520
598
  inputSchema: scopeShape,
521
599
  annotations: READ_ONLY_ANNOTATIONS
522
600
  },
523
- async ({ namespace, scope, user_id, session_id }) => jsonResult(
524
- await client.get("/v1/memory/entities", {
525
- namespace,
526
- scope,
527
- userId: user_id,
528
- sessionId: session_id
529
- })
530
- )
601
+ async ({ namespace, scope, user_id, session_id }) => {
602
+ const err = missingScopeKey({ scope, user_id, session_id });
603
+ if (err) return scopeErr(err);
604
+ return jsonResult(
605
+ await client.get("/v1/memory/entities", { namespace, scope, userId: user_id, sessionId: session_id })
606
+ );
607
+ }
531
608
  );
532
609
  server.registerTool(
533
610
  "get_memory_graph",
534
611
  {
535
612
  title: "Get memory entity graph",
536
- description: "Traverse the memory entity graph from an optional starting entity.",
613
+ description: "Traverse a user\u2019s or session\u2019s memory entity graph. Requires user_id (or session_id).",
537
614
  inputSchema: {
538
615
  ...scopeShape,
539
616
  entity: z4.string().optional().describe("Entity to start traversal from."),
@@ -541,40 +618,48 @@ function registerMemoryTools(server, client, caps) {
541
618
  },
542
619
  annotations: READ_ONLY_ANNOTATIONS
543
620
  },
544
- async ({ namespace, scope, user_id, session_id, entity, hops }) => jsonResult(
545
- await client.get("/v1/memory/graph", {
546
- namespace,
547
- scope,
548
- userId: user_id,
549
- sessionId: session_id,
550
- entity,
551
- hops: hops?.toString()
552
- })
553
- )
621
+ async ({ namespace, scope, user_id, session_id, entity, hops }) => {
622
+ const err = missingScopeKey({ scope, user_id, session_id });
623
+ if (err) return scopeErr(err);
624
+ return jsonResult(
625
+ await client.get("/v1/memory/graph", {
626
+ namespace,
627
+ scope,
628
+ userId: user_id,
629
+ sessionId: session_id,
630
+ entity,
631
+ hops: hops?.toString()
632
+ })
633
+ );
634
+ }
554
635
  );
555
636
  server.registerTool(
556
637
  "get_forget_suggestions",
557
638
  {
558
639
  title: "Get forget suggestions",
559
- description: "List memories the system suggests forgetting (stale/superseded).",
640
+ description: "List memories the system suggests forgetting for a user or session. Requires user_id (or session_id).",
560
641
  inputSchema: scopeShape,
561
642
  annotations: READ_ONLY_ANNOTATIONS
562
643
  },
563
- async ({ namespace, scope, user_id, session_id }) => jsonResult(
564
- await client.get("/v1/memory/forget-suggestions", {
565
- namespace,
566
- scope,
567
- userId: user_id,
568
- sessionId: session_id
569
- })
570
- )
644
+ async ({ namespace, scope, user_id, session_id }) => {
645
+ const err = missingScopeKey({ scope, user_id, session_id });
646
+ if (err) return scopeErr(err);
647
+ return jsonResult(
648
+ await client.get("/v1/memory/forget-suggestions", {
649
+ namespace,
650
+ scope,
651
+ userId: user_id,
652
+ sessionId: session_id
653
+ })
654
+ );
655
+ }
571
656
  );
572
657
  if (caps.write) {
573
658
  server.registerTool(
574
659
  "remember_memory",
575
660
  {
576
661
  title: "Remember a conversation turn",
577
- description: "Store a user/assistant exchange as memory for later retrieval.",
662
+ description: "Store a user/assistant exchange as memory. Requires user_id (or session_id for session scope).",
578
663
  inputSchema: {
579
664
  user: z4.string().min(1).describe("The user message."),
580
665
  assistant: z4.string().min(1).describe("The assistant response."),
@@ -582,22 +667,26 @@ function registerMemoryTools(server, client, caps) {
582
667
  },
583
668
  annotations: WRITE_ANNOTATIONS
584
669
  },
585
- async ({ user, assistant, namespace, scope, user_id, session_id }) => jsonResult(
586
- await client.post("/v1/memory/remember", {
587
- user,
588
- assistant,
589
- namespace,
590
- scope,
591
- userId: user_id,
592
- sessionId: session_id
593
- })
594
- )
670
+ async ({ user, assistant, namespace, scope, user_id, session_id }) => {
671
+ const err = missingScopeKey({ scope, user_id, session_id });
672
+ if (err) return scopeErr(err);
673
+ return jsonResult(
674
+ await client.post("/v1/memory/remember", {
675
+ user,
676
+ assistant,
677
+ namespace,
678
+ scope,
679
+ userId: user_id,
680
+ sessionId: session_id
681
+ })
682
+ );
683
+ }
595
684
  );
596
685
  server.registerTool(
597
686
  "write_memory",
598
687
  {
599
688
  title: "Write a memory",
600
- description: "Store a single memory (a fact/note) directly.",
689
+ description: "Store a single memory (a fact/note). Requires user_id (or session_id for session scope).",
601
690
  inputSchema: {
602
691
  content: z4.string().min(1).describe("The memory content to store."),
603
692
  importance: z4.number().min(0).max(1).optional().describe("Importance weight 0\u20131."),
@@ -605,16 +694,20 @@ function registerMemoryTools(server, client, caps) {
605
694
  },
606
695
  annotations: WRITE_ANNOTATIONS
607
696
  },
608
- async ({ content, importance, namespace, scope, user_id, session_id }) => jsonResult(
609
- await client.post("/v1/memory/write", {
610
- content,
611
- importance,
612
- namespace,
613
- scope,
614
- userId: user_id,
615
- sessionId: session_id
616
- })
617
- )
697
+ async ({ content, importance, namespace, scope, user_id, session_id }) => {
698
+ const err = missingScopeKey({ scope, user_id, session_id });
699
+ if (err) return scopeErr(err);
700
+ return jsonResult(
701
+ await client.post("/v1/memory/write", {
702
+ content,
703
+ importance,
704
+ namespace,
705
+ scope,
706
+ userId: user_id,
707
+ sessionId: session_id
708
+ })
709
+ );
710
+ }
618
711
  );
619
712
  server.registerTool(
620
713
  "create_namespace",
@@ -1016,11 +1109,77 @@ function registerMultimodalTools(server, client) {
1016
1109
  );
1017
1110
  }
1018
1111
 
1019
- // src/tools/guidance.ts
1112
+ // src/tools/audio.ts
1020
1113
  import { z as z8 } from "zod";
1114
+ function registerAudioTools(server, client) {
1115
+ server.registerTool(
1116
+ "text_to_speech",
1117
+ {
1118
+ title: "Text to speech",
1119
+ description: "Synthesize speech from text. Returns audio. Incurs usage/cost.",
1120
+ inputSchema: {
1121
+ input: z8.string().min(1).describe("The text to speak."),
1122
+ model: z8.string().optional().describe("TTS model. Defaults to tts-1."),
1123
+ voice: z8.string().optional().describe("Voice id/name (provider-specific)."),
1124
+ response_format: z8.string().optional().describe("Audio format, e.g. mp3, wav, opus."),
1125
+ provider: z8.string().optional().describe("Override the TTS provider.")
1126
+ },
1127
+ annotations: WRITE_ANNOTATIONS
1128
+ },
1129
+ async ({ input, model, voice, response_format, provider }) => {
1130
+ const { base64, mimeType } = await client.postBinary("/ai/audio/speech", {
1131
+ input,
1132
+ model,
1133
+ voice,
1134
+ response_format,
1135
+ provider
1136
+ });
1137
+ return {
1138
+ content: [
1139
+ { type: "audio", data: base64, mimeType },
1140
+ { type: "text", text: `Synthesized ${input.length} chars \u2192 ${mimeType} (${base64.length} b64 bytes).` }
1141
+ ]
1142
+ };
1143
+ }
1144
+ );
1145
+ server.registerTool(
1146
+ "transcribe_audio",
1147
+ {
1148
+ title: "Transcribe audio (speech to text)",
1149
+ description: "Transcribe base64-encoded audio to text. Incurs usage/cost.",
1150
+ inputSchema: {
1151
+ audio_base64: z8.string().min(1).describe("Base64-encoded audio bytes."),
1152
+ mime_type: z8.string().optional().describe("Audio MIME type, e.g. audio/mpeg, audio/wav."),
1153
+ filename: z8.string().optional().describe("Original filename (helps format detection)."),
1154
+ model: z8.string().optional().describe("STT model. Defaults to whisper-1."),
1155
+ language: z8.string().optional().describe("ISO language code, e.g. en."),
1156
+ prompt: z8.string().optional().describe("Optional prompt to guide transcription."),
1157
+ response_format: z8.enum(["json", "text", "srt", "verbose_json", "vtt"]).optional().describe("Transcript format. verbose_json adds segments/words."),
1158
+ temperature: z8.number().min(0).max(1).optional(),
1159
+ provider: z8.string().optional().describe("Override the STT provider.")
1160
+ },
1161
+ annotations: WRITE_ANNOTATIONS
1162
+ },
1163
+ async ({ audio_base64, mime_type, filename, model, language, prompt, response_format, temperature, provider }) => {
1164
+ const bytes = Buffer.from(audio_base64, "base64");
1165
+ const form = new FormData();
1166
+ form.append("file", new Blob([bytes], { type: mime_type || "application/octet-stream" }), filename || "audio");
1167
+ if (model) form.append("model", model);
1168
+ if (language) form.append("language", language);
1169
+ if (prompt) form.append("prompt", prompt);
1170
+ if (response_format) form.append("response_format", response_format);
1171
+ if (temperature !== void 0) form.append("temperature", String(temperature));
1172
+ if (provider) form.append("provider", provider);
1173
+ return jsonResult(await client.postForm("/ai/audio/transcriptions", form));
1174
+ }
1175
+ );
1176
+ }
1177
+
1178
+ // src/tools/guidance.ts
1179
+ import { z as z9 } from "zod";
1021
1180
  var orgProjectShape = {
1022
- org_slug: z8.string().min(1).optional().describe("Your organization slug, to build an exact dashboard link."),
1023
- project_slug: z8.string().min(1).optional().describe("Your project slug, to build an exact dashboard link.")
1181
+ org_slug: z9.string().min(1).optional().describe("Your organization slug, to build an exact dashboard link."),
1182
+ project_slug: z9.string().min(1).optional().describe("Your project slug, to build an exact dashboard link.")
1024
1183
  };
1025
1184
  function registerGuide(server, name, title, description, buildPath, steps, baseUrl) {
1026
1185
  server.registerTool(
@@ -1151,7 +1310,7 @@ function registerGuidanceTools(server, baseUrl) {
1151
1310
 
1152
1311
  // src/server.ts
1153
1312
  var SERVER_NAME = "cencori";
1154
- var SERVER_VERSION = "0.4.0";
1313
+ var SERVER_VERSION = "0.6.0";
1155
1314
  function createServer(config) {
1156
1315
  const server = new McpServer(
1157
1316
  {
@@ -1182,6 +1341,7 @@ function createServer(config) {
1182
1341
  if (features.governance) registerGovernanceTools(server, client, capabilities);
1183
1342
  if (features.multimodal && capabilities.write) {
1184
1343
  registerMultimodalTools(server, client);
1344
+ registerAudioTools(server, client);
1185
1345
  }
1186
1346
  }
1187
1347
  return server;