@cencori/mcp 0.2.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 ADDED
@@ -0,0 +1,954 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+
6
+ // src/config.ts
7
+ var DEFAULT_BASE_URL = "https://cencori.com";
8
+ var KNOWN_FEATURES = [
9
+ "docs",
10
+ "gateway",
11
+ "agents",
12
+ "memory",
13
+ "sessions",
14
+ "multimodal",
15
+ "governance",
16
+ "guidance"
17
+ ];
18
+ function normalizeBaseUrl(value) {
19
+ return value.replace(/\/$/, "");
20
+ }
21
+ function parseBooleanEnv(value, defaultValue) {
22
+ if (value === void 0) {
23
+ return defaultValue;
24
+ }
25
+ return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
26
+ }
27
+ function isMcpFeature(value) {
28
+ return KNOWN_FEATURES.includes(value);
29
+ }
30
+ function allFeaturesEnabled() {
31
+ return {
32
+ docs: true,
33
+ gateway: true,
34
+ agents: true,
35
+ memory: true,
36
+ sessions: true,
37
+ multimodal: true,
38
+ governance: true,
39
+ guidance: true
40
+ };
41
+ }
42
+ function parseFeatures(value) {
43
+ if (!value || value.trim() === "") {
44
+ return allFeaturesEnabled();
45
+ }
46
+ const tokens = value.split(",").map((feature) => feature.trim().toLowerCase()).filter(Boolean);
47
+ const unrecognized = tokens.filter((token) => !isMcpFeature(token));
48
+ if (unrecognized.length > 0) {
49
+ console.error(
50
+ `[cencori-mcp] Ignoring unrecognized CENCORI_MCP_FEATURES value(s): ${unrecognized.join(", ")}. Known features: ${KNOWN_FEATURES.join(", ")}.`
51
+ );
52
+ }
53
+ const enabled = new Set(tokens.filter(isMcpFeature));
54
+ return {
55
+ docs: enabled.has("docs"),
56
+ gateway: enabled.has("gateway"),
57
+ agents: enabled.has("agents"),
58
+ memory: enabled.has("memory"),
59
+ sessions: enabled.has("sessions"),
60
+ multimodal: enabled.has("multimodal"),
61
+ governance: enabled.has("governance"),
62
+ guidance: enabled.has("guidance")
63
+ };
64
+ }
65
+ function loadConfig() {
66
+ const docsBaseUrl = normalizeBaseUrl(process.env.CENCORI_DOCS_BASE_URL ?? DEFAULT_BASE_URL);
67
+ const baseUrl = normalizeBaseUrl(process.env.CENCORI_BASE_URL ?? DEFAULT_BASE_URL);
68
+ const apiKey = process.env.CENCORI_API_KEY?.trim() || void 0;
69
+ const features = parseFeatures(process.env.CENCORI_MCP_FEATURES);
70
+ const destructive = parseBooleanEnv(process.env.CENCORI_MCP_DESTRUCTIVE, false);
71
+ const write = destructive || parseBooleanEnv(process.env.CENCORI_MCP_WRITE, false);
72
+ return {
73
+ docsBaseUrl,
74
+ baseUrl,
75
+ apiKey,
76
+ capabilities: { write, destructive },
77
+ features
78
+ };
79
+ }
80
+
81
+ // src/server.ts
82
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
83
+
84
+ // src/http.ts
85
+ var FETCH_TIMEOUT_MS = 15e3;
86
+ function fetchSignal() {
87
+ return AbortSignal.timeout(FETCH_TIMEOUT_MS);
88
+ }
89
+ async function readHttpErrorMessage(response) {
90
+ const errorData = await response.json().catch(() => null);
91
+ if (typeof errorData?.error === "string") {
92
+ return errorData.error;
93
+ }
94
+ if (errorData?.error && typeof errorData.error === "object" && errorData.error.message) {
95
+ return errorData.error.message;
96
+ }
97
+ return response.statusText || `HTTP ${response.status}`;
98
+ }
99
+
100
+ // src/client.ts
101
+ var PlatformClient = class {
102
+ constructor(baseUrl, apiKey) {
103
+ this.baseUrl = baseUrl;
104
+ this.apiKey = apiKey;
105
+ }
106
+ /**
107
+ * Low-level request against the Cencori API. Sends `Authorization: Bearer`,
108
+ * which the gateway accepts for both `/v1/*` and `/api/ai/*` routes.
109
+ */
110
+ async request(method, path, options = {}) {
111
+ const url = new URL(`/api${path}`, this.baseUrl);
112
+ if (options.searchParams) {
113
+ for (const [key, value] of Object.entries(options.searchParams)) {
114
+ if (value !== void 0) {
115
+ url.searchParams.set(key, value);
116
+ }
117
+ }
118
+ }
119
+ const headers = {
120
+ Authorization: `Bearer ${this.apiKey}`
121
+ };
122
+ const init = { method, headers, signal: fetchSignal() };
123
+ if (options.body !== void 0) {
124
+ headers["Content-Type"] = "application/json";
125
+ init.body = JSON.stringify(options.body);
126
+ }
127
+ const response = await fetch(url, init);
128
+ if (!response.ok) {
129
+ const message = await readHttpErrorMessage(response);
130
+ throw new Error(`Cencori API error: ${message}`);
131
+ }
132
+ const text = await response.text();
133
+ if (!text) {
134
+ return void 0;
135
+ }
136
+ return JSON.parse(text);
137
+ }
138
+ get(path, searchParams) {
139
+ return this.request("GET", path, { searchParams });
140
+ }
141
+ post(path, body, searchParams) {
142
+ return this.request("POST", path, { body, searchParams });
143
+ }
144
+ patch(path, body) {
145
+ return this.request("PATCH", path, { body });
146
+ }
147
+ del(path) {
148
+ return this.request("DELETE", path);
149
+ }
150
+ listModels() {
151
+ return this.get("/v1/models");
152
+ }
153
+ getMetrics(period = "7d") {
154
+ return this.get("/v1/metrics", { period });
155
+ }
156
+ listAgents() {
157
+ return this.get("/v1/agents");
158
+ }
159
+ getAgent(agentId) {
160
+ return this.get(`/v1/agents/${agentId}`);
161
+ }
162
+ };
163
+
164
+ // src/docs/client.ts
165
+ var DocsClient = class {
166
+ constructor(baseUrl) {
167
+ this.baseUrl = baseUrl;
168
+ }
169
+ async search(query) {
170
+ const url = new URL("/api/docs/search", this.baseUrl);
171
+ url.searchParams.set("q", query);
172
+ const response = await fetch(url, { signal: fetchSignal() });
173
+ if (!response.ok) {
174
+ const message = await readHttpErrorMessage(response);
175
+ throw new Error(`Docs search failed (${response.status}): ${message}`);
176
+ }
177
+ return response.json();
178
+ }
179
+ async getDoc(slug) {
180
+ const normalizedSlug = slug.replace(/^\/docs\//, "").replace(/^\//, "");
181
+ const url = new URL("/api/docs/raw", this.baseUrl);
182
+ url.searchParams.set("slug", normalizedSlug);
183
+ const response = await fetch(url, { signal: fetchSignal() });
184
+ if (response.status === 404) {
185
+ return { content: "", error: `Document not found: ${normalizedSlug}` };
186
+ }
187
+ if (!response.ok) {
188
+ const message = await readHttpErrorMessage(response);
189
+ throw new Error(`Docs fetch failed (${response.status}): ${message}`);
190
+ }
191
+ return response.json();
192
+ }
193
+ async listNavigation() {
194
+ const url = new URL("/api/docs/navigation", this.baseUrl);
195
+ const response = await fetch(url, { signal: fetchSignal() });
196
+ if (!response.ok) {
197
+ const message = await readHttpErrorMessage(response);
198
+ throw new Error(`Docs navigation failed (${response.status}): ${message}`);
199
+ }
200
+ return response.json();
201
+ }
202
+ };
203
+
204
+ // src/tools/docs.ts
205
+ import { z } from "zod";
206
+
207
+ // src/tools/shared.ts
208
+ var READ_ONLY_ANNOTATIONS = {
209
+ title: "Read-only",
210
+ readOnlyHint: true,
211
+ destructiveHint: false,
212
+ openWorldHint: false
213
+ };
214
+ var WRITE_ANNOTATIONS = {
215
+ title: "Write",
216
+ readOnlyHint: false,
217
+ destructiveHint: false,
218
+ openWorldHint: false
219
+ };
220
+ function jsonResult(data) {
221
+ return {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: JSON.stringify(data, null, 2)
226
+ }
227
+ ]
228
+ };
229
+ }
230
+
231
+ // src/tools/docs.ts
232
+ function registerDocsTools(server, docs, docsBaseUrl) {
233
+ server.registerTool(
234
+ "search_docs",
235
+ {
236
+ title: "Search Cencori docs",
237
+ description: "Search Cencori documentation by keyword. Returns matching pages with title, section, URL, and snippet.",
238
+ inputSchema: {
239
+ query: z.string().min(2).describe('Search query (minimum 2 characters). Example: "rate limiting", "api keys", "failover".')
240
+ },
241
+ annotations: READ_ONLY_ANNOTATIONS
242
+ },
243
+ async ({ query }) => {
244
+ const { results } = await docs.search(query);
245
+ return jsonResult({ query, count: results.length, results });
246
+ }
247
+ );
248
+ server.registerTool(
249
+ "get_doc",
250
+ {
251
+ title: "Get Cencori doc page",
252
+ description: 'Fetch the raw markdown content of a Cencori documentation page by slug. Example slugs: "quick-start", "ai/sdk", "api/chat".',
253
+ inputSchema: {
254
+ slug: z.string().min(1).describe('Doc slug without /docs/ prefix. Example: "ai/gateway" or "security/scan".')
255
+ },
256
+ annotations: READ_ONLY_ANNOTATIONS
257
+ },
258
+ async ({ slug }) => {
259
+ const doc = await docs.getDoc(slug);
260
+ const normalizedSlug = slug.replace(/^\/docs\//, "").replace(/^\//, "");
261
+ if (doc.error) {
262
+ return jsonResult({ slug: normalizedSlug, error: doc.error });
263
+ }
264
+ return jsonResult({
265
+ slug: normalizedSlug,
266
+ url: `${docsBaseUrl}/docs/${normalizedSlug}`,
267
+ content: doc.content
268
+ });
269
+ }
270
+ );
271
+ server.registerTool(
272
+ "list_docs",
273
+ {
274
+ title: "List Cencori docs",
275
+ description: "List the Cencori documentation table of contents grouped by section (Getting Started, Platform, AI, API Reference, etc.).",
276
+ inputSchema: {},
277
+ annotations: READ_ONLY_ANNOTATIONS
278
+ },
279
+ async () => {
280
+ const navigation = await docs.listNavigation();
281
+ return jsonResult(navigation);
282
+ }
283
+ );
284
+ }
285
+
286
+ // src/tools/gateway.ts
287
+ import { z as z2 } from "zod";
288
+ function registerGatewayTools(server, client) {
289
+ server.registerTool(
290
+ "list_models",
291
+ {
292
+ title: "List Cencori gateway models",
293
+ description: "List models available through the Cencori gateway for the authenticated project.",
294
+ inputSchema: {},
295
+ annotations: READ_ONLY_ANNOTATIONS
296
+ },
297
+ async () => {
298
+ const models = await client.listModels();
299
+ return jsonResult(models);
300
+ }
301
+ );
302
+ server.registerTool(
303
+ "get_metrics",
304
+ {
305
+ title: "Get Cencori gateway metrics",
306
+ description: "Get gateway usage metrics for the authenticated project, including requests, cost, tokens, latency, and model/provider breakdowns.",
307
+ inputSchema: {
308
+ period: z2.enum(["1h", "24h", "7d", "30d", "mtd"]).optional().describe("Metrics time window. Allowed values: 1h, 24h, 7d, 30d, mtd.")
309
+ },
310
+ annotations: READ_ONLY_ANNOTATIONS
311
+ },
312
+ async ({ period }) => {
313
+ const metrics = await client.getMetrics(period);
314
+ return jsonResult(metrics);
315
+ }
316
+ );
317
+ server.registerTool(
318
+ "get_health",
319
+ {
320
+ title: "Get Cencori gateway health",
321
+ description: "Check gateway/platform health for the authenticated project.",
322
+ inputSchema: {},
323
+ annotations: READ_ONLY_ANNOTATIONS
324
+ },
325
+ async () => jsonResult(await client.get("/v1/health"))
326
+ );
327
+ server.registerTool(
328
+ "check_quota",
329
+ {
330
+ title: "Check billing quota",
331
+ description: "Check remaining quota/credits and usage limits for the authenticated project.",
332
+ inputSchema: {},
333
+ annotations: READ_ONLY_ANNOTATIONS
334
+ },
335
+ async () => jsonResult(await client.get("/v1/billing/check-quota"))
336
+ );
337
+ }
338
+
339
+ // src/tools/agents.ts
340
+ import { z as z3 } from "zod";
341
+ function registerAgentsTools(server, client) {
342
+ server.registerTool(
343
+ "list_agents",
344
+ {
345
+ title: "List Cencori agents",
346
+ description: "List agents available to the authenticated project.",
347
+ inputSchema: {},
348
+ annotations: READ_ONLY_ANNOTATIONS
349
+ },
350
+ async () => {
351
+ const agents = await client.listAgents();
352
+ return jsonResult(agents);
353
+ }
354
+ );
355
+ server.registerTool(
356
+ "get_agent",
357
+ {
358
+ title: "Get Cencori agent",
359
+ description: "Fetch the full configuration for one Cencori agent by ID.",
360
+ inputSchema: {
361
+ agent_id: z3.string().min(1).describe("The agent ID to fetch.")
362
+ },
363
+ annotations: READ_ONLY_ANNOTATIONS
364
+ },
365
+ async ({ agent_id }) => {
366
+ const agent = await client.getAgent(agent_id);
367
+ return jsonResult(agent);
368
+ }
369
+ );
370
+ server.registerTool(
371
+ "poll_agent_actions",
372
+ {
373
+ title: "Poll pending agent actions",
374
+ description: "Poll for pending actions queued for agents in the authenticated project.",
375
+ inputSchema: {},
376
+ annotations: READ_ONLY_ANNOTATIONS
377
+ },
378
+ async () => jsonResult(await client.get("/v1/agent/actions/poll"))
379
+ );
380
+ }
381
+
382
+ // src/tools/memory.ts
383
+ import { z as z4 } from "zod";
384
+ var scopeShape = {
385
+ namespace: z4.string().optional().describe("Memory namespace to scope to."),
386
+ scope: z4.string().optional().describe("Memory scope (e.g. project, user, session)."),
387
+ user_id: z4.string().optional().describe("Scope to a specific end-user id."),
388
+ session_id: z4.string().optional().describe("Scope to a specific session id.")
389
+ };
390
+ function registerMemoryTools(server, client) {
391
+ server.registerTool(
392
+ "list_memories",
393
+ {
394
+ title: "List memories",
395
+ description: "List stored memories for the project, optionally scoped by namespace/user/session.",
396
+ inputSchema: {
397
+ ...scopeShape,
398
+ limit: z4.number().int().positive().max(200).optional(),
399
+ cursor: z4.string().optional().describe("Pagination cursor from a previous response.")
400
+ },
401
+ annotations: READ_ONLY_ANNOTATIONS
402
+ },
403
+ async ({ namespace, scope, user_id, session_id, limit, cursor }) => jsonResult(
404
+ await client.get("/v1/memory/list", {
405
+ namespace,
406
+ scope,
407
+ userId: user_id,
408
+ sessionId: session_id,
409
+ limit: limit?.toString(),
410
+ cursor
411
+ })
412
+ )
413
+ );
414
+ server.registerTool(
415
+ "search_memory",
416
+ {
417
+ title: "Search memory (semantic)",
418
+ description: "Semantically search stored memories by query.",
419
+ inputSchema: {
420
+ query: z4.string().min(1).describe("Natural-language search query."),
421
+ ...scopeShape,
422
+ top_k: z4.number().int().positive().max(100).optional().describe("Max results."),
423
+ threshold: z4.number().min(0).max(1).optional().describe("Similarity threshold.")
424
+ },
425
+ annotations: READ_ONLY_ANNOTATIONS
426
+ },
427
+ async ({ query, namespace, scope, user_id, session_id, top_k, threshold }) => jsonResult(
428
+ await client.post("/v1/memory/search", {
429
+ query,
430
+ namespace,
431
+ scope,
432
+ userId: user_id,
433
+ sessionId: session_id,
434
+ topK: top_k,
435
+ threshold
436
+ })
437
+ )
438
+ );
439
+ server.registerTool(
440
+ "get_memory",
441
+ {
442
+ title: "Get a memory",
443
+ description: "Fetch a single memory by id.",
444
+ inputSchema: { memory_id: z4.string().min(1).describe("The memory id.") },
445
+ annotations: READ_ONLY_ANNOTATIONS
446
+ },
447
+ async ({ memory_id }) => jsonResult(await client.get(`/v1/memory/${memory_id}`))
448
+ );
449
+ server.registerTool(
450
+ "list_memory_entities",
451
+ {
452
+ title: "List memory entities",
453
+ description: "List entities resolved from the memory graph.",
454
+ inputSchema: scopeShape,
455
+ annotations: READ_ONLY_ANNOTATIONS
456
+ },
457
+ async ({ namespace, scope, user_id, session_id }) => jsonResult(
458
+ await client.get("/v1/memory/entities", {
459
+ namespace,
460
+ scope,
461
+ userId: user_id,
462
+ sessionId: session_id
463
+ })
464
+ )
465
+ );
466
+ server.registerTool(
467
+ "get_memory_graph",
468
+ {
469
+ title: "Get memory entity graph",
470
+ description: "Traverse the memory entity graph from an optional starting entity.",
471
+ inputSchema: {
472
+ ...scopeShape,
473
+ entity: z4.string().optional().describe("Entity to start traversal from."),
474
+ hops: z4.number().int().positive().max(5).optional().describe("Traversal depth.")
475
+ },
476
+ annotations: READ_ONLY_ANNOTATIONS
477
+ },
478
+ async ({ namespace, scope, user_id, session_id, entity, hops }) => jsonResult(
479
+ await client.get("/v1/memory/graph", {
480
+ namespace,
481
+ scope,
482
+ userId: user_id,
483
+ sessionId: session_id,
484
+ entity,
485
+ hops: hops?.toString()
486
+ })
487
+ )
488
+ );
489
+ server.registerTool(
490
+ "get_forget_suggestions",
491
+ {
492
+ title: "Get forget suggestions",
493
+ description: "List memories the system suggests forgetting (stale/superseded).",
494
+ inputSchema: scopeShape,
495
+ annotations: READ_ONLY_ANNOTATIONS
496
+ },
497
+ async ({ namespace, scope, user_id, session_id }) => jsonResult(
498
+ await client.get("/v1/memory/forget-suggestions", {
499
+ namespace,
500
+ scope,
501
+ userId: user_id,
502
+ sessionId: session_id
503
+ })
504
+ )
505
+ );
506
+ }
507
+
508
+ // src/tools/sessions.ts
509
+ import { z as z5 } from "zod";
510
+ function registerSessionsTools(server, client) {
511
+ server.registerTool(
512
+ "list_sessions",
513
+ {
514
+ title: "List agent sessions",
515
+ description: "List agent sessions for the project, optionally filtered by status or agent.",
516
+ inputSchema: {
517
+ status: z5.string().optional().describe("Filter by session status."),
518
+ agent_id: z5.string().optional().describe("Filter by agent id."),
519
+ page: z5.number().int().positive().optional(),
520
+ limit: z5.number().int().positive().max(100).optional()
521
+ },
522
+ annotations: READ_ONLY_ANNOTATIONS
523
+ },
524
+ async ({ status, agent_id, page, limit }) => jsonResult(
525
+ await client.get("/v1/sessions", {
526
+ status,
527
+ agent_id,
528
+ page: page?.toString(),
529
+ limit: limit?.toString()
530
+ })
531
+ )
532
+ );
533
+ server.registerTool(
534
+ "get_session",
535
+ {
536
+ title: "Get an agent session",
537
+ description: "Fetch one agent session by id.",
538
+ inputSchema: { session_id: z5.string().min(1).describe("The session id.") },
539
+ annotations: READ_ONLY_ANNOTATIONS
540
+ },
541
+ async ({ session_id }) => jsonResult(await client.get(`/v1/sessions/${session_id}`))
542
+ );
543
+ server.registerTool(
544
+ "get_session_events",
545
+ {
546
+ title: "Get agent session events",
547
+ description: "List the event timeline for one agent session.",
548
+ inputSchema: { session_id: z5.string().min(1).describe("The session id.") },
549
+ annotations: READ_ONLY_ANNOTATIONS
550
+ },
551
+ async ({ session_id }) => jsonResult(await client.get(`/v1/sessions/${session_id}/events`))
552
+ );
553
+ }
554
+
555
+ // src/tools/governance.ts
556
+ import { z as z6 } from "zod";
557
+ function registerGovernanceTools(server, client) {
558
+ server.registerTool(
559
+ "list_policies",
560
+ {
561
+ title: "List governance policies",
562
+ description: "List governance policies for the org, optionally filtered by status.",
563
+ inputSchema: {
564
+ status: z6.enum(["draft", "pending_review", "active", "retired"]).optional()
565
+ },
566
+ annotations: READ_ONLY_ANNOTATIONS
567
+ },
568
+ async ({ status }) => jsonResult(await client.get("/v1/governance/policies", { status }))
569
+ );
570
+ server.registerTool(
571
+ "list_roles",
572
+ {
573
+ title: "List governance roles",
574
+ description: "List governance roles defined for the org.",
575
+ inputSchema: {},
576
+ annotations: READ_ONLY_ANNOTATIONS
577
+ },
578
+ async () => jsonResult(await client.get("/v1/governance/roles"))
579
+ );
580
+ server.registerTool(
581
+ "list_change_requests",
582
+ {
583
+ title: "List governance change requests",
584
+ description: "List maker-checker change requests for governance policies.",
585
+ inputSchema: {},
586
+ annotations: READ_ONLY_ANNOTATIONS
587
+ },
588
+ async () => jsonResult(await client.get("/v1/governance/change-requests"))
589
+ );
590
+ server.registerTool(
591
+ "get_governance_ledger",
592
+ {
593
+ title: "Get governance audit ledger",
594
+ description: "Read the immutable governance audit ledger.",
595
+ inputSchema: {},
596
+ annotations: READ_ONLY_ANNOTATIONS
597
+ },
598
+ async () => jsonResult(await client.get("/v1/governance/ledger"))
599
+ );
600
+ server.registerTool(
601
+ "get_governance_evidence",
602
+ {
603
+ title: "Get governance evidence",
604
+ description: "Read governance evidence records (enforcement decisions).",
605
+ inputSchema: {},
606
+ annotations: READ_ONLY_ANNOTATIONS
607
+ },
608
+ async () => jsonResult(await client.get("/v1/governance/evidence"))
609
+ );
610
+ server.registerTool(
611
+ "list_governance_templates",
612
+ {
613
+ title: "List governance policy templates",
614
+ description: "List installable governance policy templates.",
615
+ inputSchema: {},
616
+ annotations: READ_ONLY_ANNOTATIONS
617
+ },
618
+ async () => jsonResult(await client.get("/v1/governance/templates"))
619
+ );
620
+ }
621
+
622
+ // src/tools/multimodal.ts
623
+ import { z as z7 } from "zod";
624
+ var messageSchema = z7.array(
625
+ z7.object({
626
+ role: z7.enum(["system", "user", "assistant"]),
627
+ content: z7.string()
628
+ })
629
+ ).min(1).describe("Chat messages in OpenAI format.");
630
+ var imageSourceShape = {
631
+ image_url: z7.string().url().optional().describe("Public URL of the image."),
632
+ image_base64: z7.string().optional().describe("Base64-encoded image bytes (use with mime_type)."),
633
+ mime_type: z7.string().optional().describe("MIME type for image_base64, e.g. image/png."),
634
+ prompt: z7.string().optional().describe("Optional instruction to steer the analysis."),
635
+ model: z7.string().optional().describe("Override the model.")
636
+ };
637
+ var documentSourceShape = {
638
+ document_url: z7.string().url().optional().describe("Public URL of the PDF/image."),
639
+ document_base64: z7.string().optional().describe("Base64-encoded document bytes (use with mime_type)."),
640
+ mime_type: z7.string().optional().describe("MIME type for document_base64, e.g. application/pdf."),
641
+ model: z7.string().optional().describe("Override the model.")
642
+ };
643
+ function registerMultimodalTools(server, client) {
644
+ server.registerTool(
645
+ "generate_text",
646
+ {
647
+ title: "Generate text (chat completion)",
648
+ description: "Run a chat completion through the Cencori gateway. Incurs usage/cost.",
649
+ inputSchema: {
650
+ model: z7.string().describe("Model id, e.g. llama-3.1-8b-instant or claude-opus-5."),
651
+ messages: messageSchema,
652
+ temperature: z7.number().min(0).max(2).optional(),
653
+ max_tokens: z7.number().int().positive().optional()
654
+ },
655
+ annotations: WRITE_ANNOTATIONS
656
+ },
657
+ async ({ model, messages, temperature, max_tokens }) => jsonResult(await client.post("/v1/chat/completions", { model, messages, temperature, max_tokens }))
658
+ );
659
+ server.registerTool(
660
+ "generate_rag",
661
+ {
662
+ title: "RAG chat over a memory namespace",
663
+ description: "Answer a question grounded in stored memories for a namespace. Incurs usage/cost.",
664
+ inputSchema: {
665
+ model: z7.string().describe("Model id for the answer."),
666
+ messages: messageSchema,
667
+ namespace: z7.string().min(1).describe("Memory namespace to retrieve context from."),
668
+ limit: z7.number().int().positive().optional().describe("Max memories to retrieve.")
669
+ },
670
+ annotations: WRITE_ANNOTATIONS
671
+ },
672
+ async ({ model, messages, namespace, limit }) => jsonResult(await client.post("/ai/rag", { model, messages, namespace, limit }))
673
+ );
674
+ server.registerTool(
675
+ "create_embeddings",
676
+ {
677
+ title: "Create embeddings",
678
+ description: "Generate vector embeddings for text. Incurs usage/cost.",
679
+ inputSchema: {
680
+ input: z7.union([z7.string(), z7.array(z7.string())]).describe("Text or array of texts to embed."),
681
+ model: z7.string().optional().describe("Embedding model. Defaults to text-embedding-3-small."),
682
+ dimensions: z7.number().int().positive().optional()
683
+ },
684
+ annotations: WRITE_ANNOTATIONS
685
+ },
686
+ async ({ input, model, dimensions }) => jsonResult(await client.post("/ai/embeddings", { input, model, dimensions }))
687
+ );
688
+ server.registerTool(
689
+ "moderate_content",
690
+ {
691
+ title: "Moderate content",
692
+ description: "Classify text for policy violations via the moderation endpoint.",
693
+ inputSchema: {
694
+ input: z7.union([z7.string(), z7.array(z7.string())]).describe("Text or array of texts to moderate."),
695
+ model: z7.string().optional()
696
+ },
697
+ annotations: WRITE_ANNOTATIONS
698
+ },
699
+ async ({ input, model }) => jsonResult(await client.post("/ai/moderation", { input, model }))
700
+ );
701
+ server.registerTool(
702
+ "generate_image",
703
+ {
704
+ title: "Generate an image",
705
+ description: "Generate image(s) from a text prompt. Incurs usage/cost.",
706
+ inputSchema: {
707
+ prompt: z7.string().min(1).describe("Text prompt describing the image."),
708
+ model: z7.string().optional(),
709
+ n: z7.number().int().positive().optional().describe("Number of images."),
710
+ size: z7.string().optional().describe("Image size, e.g. 1024x1024.")
711
+ },
712
+ annotations: WRITE_ANNOTATIONS
713
+ },
714
+ async ({ prompt, model, n, size }) => jsonResult(await client.post("/ai/images/generate", { prompt, model, n, size }))
715
+ );
716
+ for (const [name, path, title] of [
717
+ ["describe_image", "/ai/vision/describe", "Describe an image"],
718
+ ["ocr_image", "/ai/vision/ocr", "Extract text from an image (OCR)"],
719
+ ["classify_image", "/ai/vision/classify", "Classify an image (JSON tags)"]
720
+ ]) {
721
+ server.registerTool(
722
+ name,
723
+ {
724
+ title,
725
+ description: `${title} via the Cencori vision endpoint. Incurs usage/cost.`,
726
+ inputSchema: imageSourceShape,
727
+ annotations: WRITE_ANNOTATIONS
728
+ },
729
+ async (args) => jsonResult(await client.post(path, args))
730
+ );
731
+ }
732
+ server.registerTool(
733
+ "extract_document",
734
+ {
735
+ title: "Extract text from a document",
736
+ description: "Extract clean text from a PDF or image (native extraction for text PDFs). Incurs usage/cost for OCR.",
737
+ inputSchema: documentSourceShape,
738
+ annotations: WRITE_ANNOTATIONS
739
+ },
740
+ async (args) => jsonResult(await client.post("/ai/documents/extract", args))
741
+ );
742
+ server.registerTool(
743
+ "summarize_document",
744
+ {
745
+ title: "Summarize a document",
746
+ description: "Extract and summarize a PDF or image. Incurs usage/cost.",
747
+ inputSchema: { ...documentSourceShape, prompt: z7.string().optional().describe("Optional summary instruction.") },
748
+ annotations: WRITE_ANNOTATIONS
749
+ },
750
+ async (args) => jsonResult(await client.post("/ai/documents/summarize", args))
751
+ );
752
+ server.registerTool(
753
+ "query_document",
754
+ {
755
+ title: "Ask a question about a document",
756
+ description: "Extract a document and answer a question about it. Incurs usage/cost.",
757
+ inputSchema: {
758
+ ...documentSourceShape,
759
+ question: z7.string().min(1).describe("The question to answer about the document.")
760
+ },
761
+ annotations: WRITE_ANNOTATIONS
762
+ },
763
+ async (args) => jsonResult(await client.post("/ai/documents/query", args))
764
+ );
765
+ }
766
+
767
+ // src/tools/guidance.ts
768
+ import { z as z8 } from "zod";
769
+ var orgProjectShape = {
770
+ org_slug: z8.string().min(1).optional().describe("Your organization slug, to build an exact dashboard link."),
771
+ project_slug: z8.string().min(1).optional().describe("Your project slug, to build an exact dashboard link.")
772
+ };
773
+ function registerGuide(server, name, title, description, buildPath, steps, baseUrl) {
774
+ server.registerTool(
775
+ name,
776
+ {
777
+ title,
778
+ description,
779
+ inputSchema: orgProjectShape,
780
+ annotations: READ_ONLY_ANNOTATIONS
781
+ },
782
+ async (args) => {
783
+ return jsonResult({
784
+ manual_action: true,
785
+ note: "This action is intentionally not automated. Tell the user to complete it themselves in the Cencori dashboard.",
786
+ steps,
787
+ dashboard_url: `${baseUrl}${buildPath(args)}`
788
+ });
789
+ }
790
+ );
791
+ }
792
+ var ORG = (s) => s.org_slug ?? "<your-org>";
793
+ var PROJ = (s) => s.project_slug ?? "<your-project>";
794
+ function registerGuidanceTools(server, baseUrl) {
795
+ registerGuide(
796
+ server,
797
+ "how_to_create_api_key",
798
+ "How to create a Cencori API key",
799
+ "Steps for the USER to create a Cencori project API key. The MCP never creates keys.",
800
+ (s) => `/${ORG(s)}/${PROJ(s)}/api-keys`,
801
+ [
802
+ "Open the Cencori dashboard and select your organization, then your project.",
803
+ "Go to the project\u2019s API Keys page.",
804
+ "Click \u201CCreate key\u201D, name it, choose the environment, and copy the `csk_...` secret.",
805
+ "Store it server-side as CENCORI_API_KEY \u2014 it is shown only once."
806
+ ],
807
+ baseUrl
808
+ );
809
+ registerGuide(
810
+ server,
811
+ "how_to_edit_api_key",
812
+ "How to edit a Cencori API key",
813
+ "Steps for the USER to rename or change scopes/limits on an API key. The MCP never edits keys.",
814
+ (s) => `/${ORG(s)}/${PROJ(s)}/api-keys`,
815
+ [
816
+ "Open the project\u2019s API Keys page in the dashboard.",
817
+ "Find the key and open its actions menu.",
818
+ "Update its name, environment, or limits and save."
819
+ ],
820
+ baseUrl
821
+ );
822
+ registerGuide(
823
+ server,
824
+ "how_to_revoke_api_key",
825
+ "How to revoke a Cencori API key",
826
+ "Steps for the USER to revoke/rotate an API key. The MCP never revokes keys.",
827
+ (s) => `/${ORG(s)}/${PROJ(s)}/api-keys`,
828
+ [
829
+ "Open the project\u2019s API Keys page in the dashboard.",
830
+ "Find the key to revoke and choose \u201CRevoke\u201D. This immediately invalidates it.",
831
+ "If rotating, create a new key first and update CENCORI_API_KEY before revoking the old one."
832
+ ],
833
+ baseUrl
834
+ );
835
+ registerGuide(
836
+ server,
837
+ "how_to_activate_policy",
838
+ "How to activate a governance policy",
839
+ "Steps for the USER to activate/retire a governance policy. Activation is a maker-checker human step \u2014 the MCP can draft a policy but never activates one.",
840
+ (s) => `/${ORG(s)}/~/governance`,
841
+ [
842
+ "Open the Governance page for your organization in the dashboard.",
843
+ "Find the draft (or pending-review) policy version.",
844
+ "Review it and, as an authorized checker, activate it. Exactly one version per policy can be active."
845
+ ],
846
+ baseUrl
847
+ );
848
+ registerGuide(
849
+ server,
850
+ "how_to_respond_to_change_request",
851
+ "How to respond to a governance change request",
852
+ "Steps for the USER (as checker) to approve or reject a governance change request. The MCP never approves/rejects on the user\u2019s behalf.",
853
+ (s) => `/${ORG(s)}/~/governance`,
854
+ [
855
+ "Open the Governance page and go to Change Requests.",
856
+ "Open the pending request and review the proposed change.",
857
+ "As an authorized checker, approve or reject it."
858
+ ],
859
+ baseUrl
860
+ );
861
+ registerGuide(
862
+ server,
863
+ "how_to_change_plan",
864
+ "How to change your Cencori plan",
865
+ "Steps for the USER to change plan/tier. The MCP never changes plans or charges.",
866
+ (s) => `/${ORG(s)}/~/billing`,
867
+ [
868
+ "Open the Billing page for your organization in the dashboard.",
869
+ "Choose the plan you want and confirm the change."
870
+ ],
871
+ baseUrl
872
+ );
873
+ registerGuide(
874
+ server,
875
+ "how_to_manage_billing",
876
+ "How to manage billing, payment methods, and credits",
877
+ "Steps for the USER to update payment methods or buy credits. The MCP never manages payment or credits.",
878
+ (s) => `/${ORG(s)}/~/billing`,
879
+ [
880
+ "Open the Billing page for your organization in the dashboard.",
881
+ "Update a payment method, or purchase credits, from the billing controls."
882
+ ],
883
+ baseUrl
884
+ );
885
+ registerGuide(
886
+ server,
887
+ "how_to_manage_members",
888
+ "How to manage members, roles, and SSO",
889
+ "Steps for the USER to invite/remove members, change roles, or configure SSO. The MCP never alters access.",
890
+ (s) => `/${ORG(s)}/~/teams`,
891
+ [
892
+ "Open the Teams page for your organization in the dashboard.",
893
+ "Invite or remove members, or change a member\u2019s role.",
894
+ "Configure SSO from the organization settings if applicable."
895
+ ],
896
+ baseUrl
897
+ );
898
+ }
899
+
900
+ // src/server.ts
901
+ var SERVER_NAME = "cencori";
902
+ var SERVER_VERSION = "0.2.0";
903
+ function createServer(config) {
904
+ const server = new McpServer(
905
+ {
906
+ name: SERVER_NAME,
907
+ version: SERVER_VERSION,
908
+ websiteUrl: "https://cencori.com/docs"
909
+ },
910
+ {
911
+ capabilities: {
912
+ tools: {}
913
+ }
914
+ }
915
+ );
916
+ const { features, capabilities } = config;
917
+ if (features.docs) {
918
+ const docs = new DocsClient(config.docsBaseUrl);
919
+ registerDocsTools(server, docs, config.docsBaseUrl);
920
+ }
921
+ if (features.guidance) {
922
+ registerGuidanceTools(server, config.baseUrl);
923
+ }
924
+ if (config.apiKey) {
925
+ const client = new PlatformClient(config.baseUrl, config.apiKey);
926
+ if (features.gateway) registerGatewayTools(server, client);
927
+ if (features.agents) registerAgentsTools(server, client);
928
+ if (features.memory) registerMemoryTools(server, client);
929
+ if (features.sessions) registerSessionsTools(server, client);
930
+ if (features.governance) registerGovernanceTools(server, client);
931
+ if (features.multimodal && capabilities.write) {
932
+ registerMultimodalTools(server, client);
933
+ }
934
+ }
935
+ return server;
936
+ }
937
+
938
+ // src/index.ts
939
+ async function main() {
940
+ const config = loadConfig();
941
+ const server = createServer(config);
942
+ const transport = new StdioServerTransport();
943
+ await server.connect(transport);
944
+ const enabledFeatures = Object.entries(config.features).filter(([, enabled]) => enabled).map(([feature]) => feature).join(", ");
945
+ const tier = config.capabilities.destructive ? "read+write+destructive" : config.capabilities.write ? "read+write" : "read-only";
946
+ console.error(
947
+ `Cencori MCP server running (tier=${tier}, base=${config.baseUrl}, features=${enabledFeatures}, apiKey=${config.apiKey ? "configured" : "not set"})`
948
+ );
949
+ }
950
+ main().catch((error) => {
951
+ console.error("Fatal error:", error);
952
+ process.exit(1);
953
+ });
954
+ //# sourceMappingURL=index.mjs.map