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