santismm-knowledge-mcp 0.2.2 → 0.3.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 +9 -7
- package/content/architectures/ai-workforce.json +3 -1
- package/content/claims/a-harness-can-degrade-the-agent.json +51 -0
- package/content/claims/harness-changes-outcomes.json +57 -0
- package/content/claims/harness-engineering-is-a-distinct-discipline.json +43 -0
- package/content/claims/harness-is-the-durable-asset.json +43 -0
- package/content/claims/maturity-levels-are-ordered-by-dependency.json +43 -0
- package/content/claims/practice-converges-on-eight-components.json +58 -0
- package/content/claims/verified-outcome-is-the-unit.json +50 -0
- package/content/governance/agentic-ai-governance-checklist.json +3 -1
- package/content/harness/HRN-001-definition-and-overview.es.md +6 -3
- package/content/harness/HRN-001-definition-and-overview.md +6 -3
- package/content/harness/HRN-001-definition-and-overview.pt.md +6 -3
- package/content/harness/HRN-013-glossary.es.md +1 -1
- package/content/harness/HRN-013-glossary.md +1 -1
- package/content/harness/HRN-013-glossary.pt.md +1 -1
- package/content/knowledge/foundation-models.json +2 -1
- package/content/knowledge/harness-engineering.json +3 -3
- package/content/library/the-agentic-enterprise-needs-an-immune-system.md +16 -0
- package/content/library/the-stopwatch-and-the-exam.md +5 -1
- package/content/matrix/agentic-control-matrix.json +402 -3
- package/content/maturity/harness-maturity-model.json +569 -0
- package/content/patterns/evaluator-optimizer.json +1 -0
- package/content/patterns/goal-decomposition.json +1 -0
- package/content/patterns/human-approval-gate.json +1 -0
- package/content/patterns/long-term-memory.json +1 -0
- package/content/patterns/orchestrator-workers.json +1 -0
- package/content/patterns/sandboxed-execution.json +1 -0
- package/content/patterns/supervisor-agent.json +1 -0
- package/content/scorecard/harness-scorecard.json +460 -0
- package/dist/articles.js +120 -0
- package/dist/content.js +14 -1
- package/dist/shape.js +125 -3
- package/dist/tools.js +176 -4
- package/package.json +1 -1
package/dist/shape.js
CHANGED
|
@@ -184,6 +184,14 @@ function stem(token) {
|
|
|
184
184
|
return token;
|
|
185
185
|
}
|
|
186
186
|
/** Relative importance of each field when scoring a search hit. */
|
|
187
|
+
/**
|
|
188
|
+
* Cuánto puede sumar como mucho la centralidad. Por debajo del peso de un
|
|
189
|
+
* acierto en título (6) a propósito: desempata y matiza dentro de un empate,
|
|
190
|
+
* nunca adelanta a una unidad que acertó más términos de la consulta.
|
|
191
|
+
*/
|
|
192
|
+
const CENTRALITY_NUDGE = 1.5;
|
|
193
|
+
/** Satura: pasar de 0 a 4 enlaces importa; de 40 a 44, ya no. */
|
|
194
|
+
const centralityBonus = (inbound) => CENTRALITY_NUDGE * (inbound / (inbound + 4));
|
|
187
195
|
const FIELD_WEIGHTS = {
|
|
188
196
|
name: 6,
|
|
189
197
|
slug: 5,
|
|
@@ -231,6 +239,7 @@ function buildSearchFields(entry) {
|
|
|
231
239
|
name: norm(names.join(" ")),
|
|
232
240
|
slug: norm(entry.slug ?? ""),
|
|
233
241
|
id: norm(str(raw.id)),
|
|
242
|
+
aliases: norm(arr(raw.aliases)),
|
|
234
243
|
summary: norm(summaries.join(" ")),
|
|
235
244
|
category: norm(entry.category ?? ""),
|
|
236
245
|
keyConcepts: norm(concepts.join(" ")),
|
|
@@ -448,8 +457,70 @@ function summarizeHomeric(kind, e, locale) {
|
|
|
448
457
|
api_url: `${SITE_URL}/api/homeric/${kind}/${e.slug}`,
|
|
449
458
|
};
|
|
450
459
|
}
|
|
451
|
-
export function makeContent(loadAll, loadHandbook, loadHomeric) {
|
|
460
|
+
export function makeContent(loadAll, loadHandbook, loadHomeric, loadClaims) {
|
|
452
461
|
const getOne = (domain, slug) => loadAll(domain).find((e) => e.slug === slug);
|
|
462
|
+
/**
|
|
463
|
+
* Enlaces entrantes por unidad — la única señal de centralidad que este
|
|
464
|
+
* corpus ya tiene, y la que decide un empate de puntuación.
|
|
465
|
+
*
|
|
466
|
+
* Para una consulta de una palabra, toda unidad que la lleve en el título
|
|
467
|
+
* saca exactamente la misma nota: "memory" empataba 4 unidades y "harness" 7,
|
|
468
|
+
* y el orden lo resolvía `localeCompare`. Cuál es la canónica no está en el
|
|
469
|
+
* texto — está en quién apunta a quién.
|
|
470
|
+
*
|
|
471
|
+
* Cuenta TODAS las aristas declaradas, incluidas las del manual, que
|
|
472
|
+
* referencia por identificador (`HRN-003`, `GOV-001`, `ARCH-001`) y no por
|
|
473
|
+
* slug: medirlo solo sobre los cuatro dominios JSON dejaba a los 14 capítulos
|
|
474
|
+
* a cero por artefacto y habría hundido el manual entero fingiendo que era
|
|
475
|
+
* una señal.
|
|
476
|
+
*/
|
|
477
|
+
let inboundCache = null;
|
|
478
|
+
const inboundLinks = () => {
|
|
479
|
+
if (inboundCache)
|
|
480
|
+
return inboundCache;
|
|
481
|
+
const bySlug = new Map(); // ARCH-001 / HRN-003 -> slug
|
|
482
|
+
const entries = [];
|
|
483
|
+
for (const domain of DOMAINS) {
|
|
484
|
+
for (const e of loadAll(domain)) {
|
|
485
|
+
entries.push(e);
|
|
486
|
+
const id = e.id;
|
|
487
|
+
if (typeof id === "string")
|
|
488
|
+
bySlug.set(id.toUpperCase(), e.slug);
|
|
489
|
+
const aliases = e.aliases;
|
|
490
|
+
if (Array.isArray(aliases)) {
|
|
491
|
+
for (const alias of aliases)
|
|
492
|
+
if (typeof alias === "string")
|
|
493
|
+
bySlug.set(alias.toUpperCase(), e.slug);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (loadHandbook) {
|
|
498
|
+
for (const c of loadHandbook()) {
|
|
499
|
+
entries.push(c);
|
|
500
|
+
if (c.id)
|
|
501
|
+
bySlug.set(String(c.id).toUpperCase(), c.slug);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const inb = new Map();
|
|
505
|
+
for (const e of entries) {
|
|
506
|
+
for (const field of ["related", "patterns", "knowledge", "architectures", "governance"]) {
|
|
507
|
+
const refs = e[field];
|
|
508
|
+
if (!Array.isArray(refs))
|
|
509
|
+
continue;
|
|
510
|
+
for (const r of refs) {
|
|
511
|
+
if (typeof r !== "string")
|
|
512
|
+
continue;
|
|
513
|
+
// Una referencia que no resuelve acaba bajo una clave que ninguna
|
|
514
|
+
// unidad tiene, así que no confiere centralidad a nadie. Quien avisa
|
|
515
|
+
// de que existen es `npm run validate`, no esto.
|
|
516
|
+
const slug = bySlug.get(r.toUpperCase()) ?? r;
|
|
517
|
+
inb.set(slug, (inb.get(slug) ?? 0) + 1);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
inboundCache = inb;
|
|
522
|
+
return inb;
|
|
523
|
+
};
|
|
453
524
|
const card = (d, e, locale, type) => ({
|
|
454
525
|
type,
|
|
455
526
|
...summarize(d, e, locale),
|
|
@@ -497,6 +568,12 @@ export function makeContent(loadAll, loadHandbook, loadHomeric) {
|
|
|
497
568
|
llms_full_txt: `${SITE_URL}/llms-full.txt`,
|
|
498
569
|
graph: `${SITE_URL}/api/graph.json`,
|
|
499
570
|
homeric_atlas: `${SITE_URL}/api/homeric-atlas.json`,
|
|
571
|
+
articles: "https://articles.santismm.com/llms-full.txt",
|
|
572
|
+
articles_index: "https://articles.santismm.com/ai-index.json",
|
|
573
|
+
articles_api: "https://articles.santismm.com/api/articles.json",
|
|
574
|
+
labs: "https://labs.santismm.com/llms-full.txt",
|
|
575
|
+
labs_api: "https://labs.santismm.com/api/labs",
|
|
576
|
+
labs_openapi: "https://labs.santismm.com/openapi.json",
|
|
500
577
|
},
|
|
501
578
|
};
|
|
502
579
|
},
|
|
@@ -538,11 +615,13 @@ export function makeContent(loadAll, loadHandbook, loadHomeric) {
|
|
|
538
615
|
// measured across the corpus actually being searched.
|
|
539
616
|
const wantHandbook = domains.includes("handbook");
|
|
540
617
|
const structured = domains.filter((d) => d !== "handbook");
|
|
618
|
+
const inb = inboundLinks();
|
|
541
619
|
const docs = [];
|
|
542
620
|
for (const domain of structured) {
|
|
543
621
|
for (const entry of loadAll(domain)) {
|
|
544
622
|
docs.push({
|
|
545
623
|
fields: searchFields(entry),
|
|
624
|
+
slug: entry.slug,
|
|
546
625
|
card: () => summarize(domain, entry, locale),
|
|
547
626
|
});
|
|
548
627
|
}
|
|
@@ -553,6 +632,7 @@ export function makeContent(loadAll, loadHandbook, loadHomeric) {
|
|
|
553
632
|
for (const e of loadHandbook()) {
|
|
554
633
|
docs.push({
|
|
555
634
|
fields: handbookFields(e),
|
|
635
|
+
slug: e.slug,
|
|
556
636
|
card: () => summarizeHandbook(e, locale),
|
|
557
637
|
});
|
|
558
638
|
}
|
|
@@ -571,12 +651,54 @@ export function makeContent(loadAll, loadHandbook, loadHomeric) {
|
|
|
571
651
|
const scored = [];
|
|
572
652
|
for (const d of docs) {
|
|
573
653
|
const hit = scoreEntry(d.fields, tokens, idf);
|
|
574
|
-
if (hit)
|
|
575
|
-
|
|
654
|
+
if (!hit)
|
|
655
|
+
continue;
|
|
656
|
+
// La centralidad se suma DESPUÉS de puntuar el texto: decide entre
|
|
657
|
+
// unidades que ya empataron por relevancia, no compite con ella.
|
|
658
|
+
const score = Math.round((hit.score + centralityBonus(inb.get(d.slug) ?? 0)) * 100) / 100;
|
|
659
|
+
scored.push({ ...d.card(), ...hit, score });
|
|
576
660
|
}
|
|
577
661
|
scored.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
578
662
|
return scored.slice(0, limit);
|
|
579
663
|
},
|
|
664
|
+
/**
|
|
665
|
+
* Claims (ADR 0003). The card carries the type and the confidence, because
|
|
666
|
+
* that is the whole point: an agent must be able to tell an observed fact
|
|
667
|
+
* from a bet without reading the prose and guessing.
|
|
668
|
+
*/
|
|
669
|
+
listClaims(type, locale = "en") {
|
|
670
|
+
if (!loadClaims)
|
|
671
|
+
return [];
|
|
672
|
+
return loadClaims()
|
|
673
|
+
.filter((c) => !type || c.claimType === type)
|
|
674
|
+
// Orden estable por id: el del directorio no lo es, y un listado que
|
|
675
|
+
// cambia de orden entre llamadas es un listado en el que no se confía.
|
|
676
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
|
677
|
+
.map((c) => {
|
|
678
|
+
const L = (c.locales ?? {})[locale] ?? {};
|
|
679
|
+
return {
|
|
680
|
+
type: "claim",
|
|
681
|
+
id: c.id,
|
|
682
|
+
slug: c.slug,
|
|
683
|
+
claim_type: c.claimType,
|
|
684
|
+
confidence_level: c.confidenceLevel,
|
|
685
|
+
statement: L.statement,
|
|
686
|
+
supports: c.supports,
|
|
687
|
+
reviewed: c.reviewed,
|
|
688
|
+
};
|
|
689
|
+
});
|
|
690
|
+
},
|
|
691
|
+
/** By `HE-CLAIM-nnn` or slug: an agent that saw either can come back. */
|
|
692
|
+
getClaim(id, locale = "en") {
|
|
693
|
+
if (!loadClaims)
|
|
694
|
+
return undefined;
|
|
695
|
+
const key = String(id).toUpperCase();
|
|
696
|
+
const c = loadClaims().find((x) => String(x.id).toUpperCase() === key || x.slug === id);
|
|
697
|
+
if (!c)
|
|
698
|
+
return undefined;
|
|
699
|
+
const L = (c.locales ?? {})[locale] ?? {};
|
|
700
|
+
return { type: "claim", ...c, body: L };
|
|
701
|
+
},
|
|
580
702
|
listHomeric(kind, locale = "en") {
|
|
581
703
|
if (!loadHomeric)
|
|
582
704
|
return [];
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ARTICLES_API_URL, articleCard, articlesForLocale, loadArticles, searchArticleCorpus, } from "./articles.js";
|
|
2
3
|
/**
|
|
3
4
|
* Single, framework-agnostic definition of the Santismm Knowledge MCP server:
|
|
4
5
|
* its identity and the tool registry. Both transports — the stdio CLI
|
|
@@ -7,11 +8,12 @@ import { z } from "zod";
|
|
|
7
8
|
* between them. The data source is injected as `content` (an `McpContent`
|
|
8
9
|
* provider); both providers read the same `content/{domain}/*.json` files.
|
|
9
10
|
*/
|
|
10
|
-
export const SERVER_INFO = { name: "santismm-knowledge", version: "0.
|
|
11
|
+
export const SERVER_INFO = { name: "santismm-knowledge", version: "0.3.0" };
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Core tools read a static local corpus, so all four hints are literally true:
|
|
14
|
+
* nothing mutates, the same arguments produce the same answer, and no core
|
|
15
|
+
* tool reaches outside this corpus. Federated Article tools override only the
|
|
16
|
+
* open-world hint because they read another first-party origin.
|
|
15
17
|
*
|
|
16
18
|
* Declaring them matters because a client deciding whether a call needs
|
|
17
19
|
* confirmation, and an aggregator deciding how to present the server, both read
|
|
@@ -24,6 +26,8 @@ const READ_ONLY = {
|
|
|
24
26
|
idempotentHint: true,
|
|
25
27
|
openWorldHint: false,
|
|
26
28
|
};
|
|
29
|
+
/** Article tools read another first-party origin, so clients must know they are open-world. */
|
|
30
|
+
const READ_ONLY_REMOTE = { ...READ_ONLY, openWorldHint: true };
|
|
27
31
|
const localeSchema = z
|
|
28
32
|
.enum(["en", "es", "pt"])
|
|
29
33
|
.optional()
|
|
@@ -122,6 +126,8 @@ const unitOutput = z.object({
|
|
|
122
126
|
domain: z.string().optional(),
|
|
123
127
|
id: z.string().optional(),
|
|
124
128
|
slug: z.string().optional(),
|
|
129
|
+
/** Legacy identifiers that resolve to this canonical unit. */
|
|
130
|
+
aliases: z.array(z.string()).optional(),
|
|
125
131
|
category: z.string().optional(),
|
|
126
132
|
updated: z.string().optional(),
|
|
127
133
|
version: z.string().optional(),
|
|
@@ -223,6 +229,8 @@ const ESPACIOS = [
|
|
|
223
229
|
{ domain: "homeric/places", listTool: "list_homeric_places", getTool: "get_homeric_place" },
|
|
224
230
|
{ domain: "homeric/episodes", listTool: "list_homeric_episodes", getTool: "get_homeric_episode" },
|
|
225
231
|
{ domain: "homeric/routes", listTool: "list_homeric_routes", getTool: "get_homeric_route" },
|
|
232
|
+
{ domain: "claims", listTool: "list_claims", getTool: "get_claim" },
|
|
233
|
+
{ domain: "articles", listTool: "list_articles", getTool: "get_article" },
|
|
226
234
|
];
|
|
227
235
|
/** Every identifier a card answers to: its id (handbook chapters) and its slug. */
|
|
228
236
|
function identificadores(cards) {
|
|
@@ -240,8 +248,14 @@ function identificadores(cards) {
|
|
|
240
248
|
}
|
|
241
249
|
/** The cards of one space, whichever loader serves it. */
|
|
242
250
|
function cardsDe(content, domain, locale) {
|
|
251
|
+
// Federated Articles are asynchronous and have their own recovery payload;
|
|
252
|
+
// keep them out of the synchronous cross-space lookup used by local units.
|
|
253
|
+
if (domain === "articles")
|
|
254
|
+
return [];
|
|
243
255
|
if (domain === "handbook")
|
|
244
256
|
return content.listHandbook(locale);
|
|
257
|
+
if (domain === "claims")
|
|
258
|
+
return content.listClaims(undefined, locale);
|
|
245
259
|
if (domain.startsWith("homeric/")) {
|
|
246
260
|
return content.listHomeric(domain.slice("homeric/".length), locale);
|
|
247
261
|
}
|
|
@@ -416,6 +430,77 @@ const homericListOutput = {
|
|
|
416
430
|
results: z.array(homericCard),
|
|
417
431
|
};
|
|
418
432
|
const homericUnitOutput = z.object({}).passthrough();
|
|
433
|
+
/**
|
|
434
|
+
* Claims get their own shape for the same reason the atlas does: forcing them
|
|
435
|
+
* into the corpus card would mean inventing an `evidence` block they do not
|
|
436
|
+
* have. A claim is not a unit with provenance — it IS the provenance, and what
|
|
437
|
+
* it carries instead is the rung of the ladder it sits on.
|
|
438
|
+
*/
|
|
439
|
+
const claimCard = z.object({
|
|
440
|
+
type: z.literal("claim"),
|
|
441
|
+
id: z.string(),
|
|
442
|
+
slug: z.string(),
|
|
443
|
+
claim_type: z.enum(["observed_fact", "industry_synthesis", "santismm_thesis", "strategic_hypothesis"]),
|
|
444
|
+
confidence_level: z.string(),
|
|
445
|
+
statement: z.string().optional(),
|
|
446
|
+
supports: z.array(z.string()),
|
|
447
|
+
reviewed: z.string(),
|
|
448
|
+
});
|
|
449
|
+
const claimListOutput = { count: z.number(), results: z.array(claimCard) };
|
|
450
|
+
const claimUnitOutput = z.object({}).passthrough();
|
|
451
|
+
const articleCardSchema = z.object({
|
|
452
|
+
slug: z.string(),
|
|
453
|
+
title: z.string(),
|
|
454
|
+
summary: z.string(),
|
|
455
|
+
language: z.string(),
|
|
456
|
+
published: z.string(),
|
|
457
|
+
modified: z.string(),
|
|
458
|
+
topics: z.array(z.string()),
|
|
459
|
+
translation_key: z.string().optional(),
|
|
460
|
+
canonical_url: z.string().describe("Cite this URL."),
|
|
461
|
+
api_url: z.string(),
|
|
462
|
+
});
|
|
463
|
+
const articleUnitOutput = articleCardSchema.extend({
|
|
464
|
+
body: z.string().describe("Full Markdown-like article body."),
|
|
465
|
+
});
|
|
466
|
+
const articleListOutput = { count: z.number(), results: z.array(articleCardSchema) };
|
|
467
|
+
const articleSearchOutput = {
|
|
468
|
+
query: z.string(),
|
|
469
|
+
count: z.number(),
|
|
470
|
+
results: z.array(articleCardSchema.extend({
|
|
471
|
+
score: z.number(),
|
|
472
|
+
matchedFields: z.array(z.string()),
|
|
473
|
+
matchedTerms: z.array(z.string()),
|
|
474
|
+
})),
|
|
475
|
+
};
|
|
476
|
+
function articleFailure(error) {
|
|
477
|
+
const body = {
|
|
478
|
+
error: "articles_unavailable",
|
|
479
|
+
source: ARTICLES_API_URL,
|
|
480
|
+
hint: "The first-party Articles API could not be read. Retry later or use its llms-full.txt corpus directly.",
|
|
481
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
482
|
+
};
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: JSON.stringify(body, null, 2) }],
|
|
485
|
+
isError: true,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function articleNotFound(articles, slug) {
|
|
489
|
+
const available = articles.map((article) => article.slug).sort();
|
|
490
|
+
const body = {
|
|
491
|
+
error: "not_found",
|
|
492
|
+
domain: "articles",
|
|
493
|
+
slug,
|
|
494
|
+
available_count: available.length,
|
|
495
|
+
available,
|
|
496
|
+
list_tool: "list_articles",
|
|
497
|
+
hint: "Call list_articles for every valid slug; article slugs are not interchangeable with core corpus identifiers.",
|
|
498
|
+
};
|
|
499
|
+
return {
|
|
500
|
+
content: [{ type: "text", text: JSON.stringify(body, null, 2) }],
|
|
501
|
+
isError: true,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
419
504
|
export function registerTools(server, content) {
|
|
420
505
|
// ── Orientation ────────────────────────────────────────────────────────────
|
|
421
506
|
server.registerTool("get_overview", {
|
|
@@ -571,6 +656,61 @@ export function registerTools(server, content) {
|
|
|
571
656
|
const results = content.search(query, domains, limit ?? 20, (locale ?? "en"));
|
|
572
657
|
return outList(results, { query });
|
|
573
658
|
});
|
|
659
|
+
// ── First-party articles (federated) ─────────────────────────────────────
|
|
660
|
+
server.registerTool("list_articles", {
|
|
661
|
+
title: "List first-party essays",
|
|
662
|
+
annotations: READ_ONLY_REMOTE,
|
|
663
|
+
description: "List every long-form essay published on articles.santismm.com, with language, dates, topics and citable canonical URLs. Use this to browse the essay catalogue; use `search_articles` when you have a topic rather than a slug.",
|
|
664
|
+
inputSchema: z.object({
|
|
665
|
+
locale: localeSchema.describe("Restrict to en, es or pt. Omit to return every language."),
|
|
666
|
+
}),
|
|
667
|
+
outputSchema: articleListOutput,
|
|
668
|
+
}, async ({ locale }) => {
|
|
669
|
+
try {
|
|
670
|
+
const articles = articlesForLocale(await loadArticles(), locale);
|
|
671
|
+
return outList(articles.map(articleCard));
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
return articleFailure(error);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
server.registerTool("get_article", {
|
|
678
|
+
title: "Get a first-party essay",
|
|
679
|
+
annotations: READ_ONLY_REMOTE,
|
|
680
|
+
description: "Get one complete essay by slug, including its clean Markdown-like body, metadata, licence context and canonical URL. Use this after `list_articles` or `search_articles` has returned the slug you need.",
|
|
681
|
+
inputSchema: z.object({
|
|
682
|
+
slug: slugSchema.describe("Article slug, e.g. 'the-stopwatch-and-the-exam'."),
|
|
683
|
+
}),
|
|
684
|
+
outputSchema: articleUnitOutput,
|
|
685
|
+
}, async ({ slug }) => {
|
|
686
|
+
try {
|
|
687
|
+
const articles = await loadArticles();
|
|
688
|
+
const article = articles.find((candidate) => candidate.slug === slug);
|
|
689
|
+
return article ? out(article) : articleNotFound(articles, slug);
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
return articleFailure(error);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
server.registerTool("search_articles", {
|
|
696
|
+
title: "Search first-party essays",
|
|
697
|
+
annotations: READ_ONLY_REMOTE,
|
|
698
|
+
description: "Ranked, accent-insensitive full-text search over every first-party essay, including titles, summaries, topics and bodies. Use this when you need long-form analysis about a topic; follow with `get_article` for the complete essay.",
|
|
699
|
+
inputSchema: z.object({
|
|
700
|
+
query: querySchema.describe("Keyword or phrase to search for in any supported language."),
|
|
701
|
+
locale: localeSchema.describe("Restrict to en, es or pt. Omit to search every language."),
|
|
702
|
+
limit: z.number().int().positive().max(20).optional().describe("Maximum results (default 10)."),
|
|
703
|
+
}),
|
|
704
|
+
outputSchema: articleSearchOutput,
|
|
705
|
+
}, async ({ query, locale, limit }) => {
|
|
706
|
+
try {
|
|
707
|
+
const articles = articlesForLocale(await loadArticles(), locale);
|
|
708
|
+
return outList(searchArticleCorpus(articles, query, limit ?? 10), { query });
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
return articleFailure(error);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
574
714
|
// ── Graph traversal ────────────────────────────────────────────────────────
|
|
575
715
|
server.registerTool("get_related", {
|
|
576
716
|
title: "Traverse the Knowledge Graph",
|
|
@@ -655,6 +795,38 @@ export function registerTools(server, content) {
|
|
|
655
795
|
const entry = content.getHomeric("routes", slug, locale);
|
|
656
796
|
return entry ? out(entry) : noEncontrado(content, "homeric/routes", slug, (locale ?? "en"));
|
|
657
797
|
});
|
|
798
|
+
// ── Claims (ADR 0003) ────────────────────────────────────────────────────
|
|
799
|
+
// Everything else in this server answers "what does the corpus say?". These
|
|
800
|
+
// two answer "how strongly, and on what?" — which of the corpus's statements
|
|
801
|
+
// are observed fact, which are a reading of the industry, which are our own
|
|
802
|
+
// position and which are a bet. Without them an agent has to infer the
|
|
803
|
+
// epistemic status from prose, and prose does not distinguish those.
|
|
804
|
+
server.registerTool("list_claims", {
|
|
805
|
+
title: "List the corpus claims and their epistemic status",
|
|
806
|
+
annotations: READ_ONLY,
|
|
807
|
+
description: "List the load-bearing claims of the corpus, each tagged as observed_fact, industry_synthesis, santismm_thesis or strategic_hypothesis, with its confidence and the units it underpins. Use this before quoting the handbook to know whether a statement is evidence, a reading of the industry, or a position taken. Filter by `claim_type` to get only what is checkable.",
|
|
808
|
+
inputSchema: z.object({
|
|
809
|
+
claim_type: z
|
|
810
|
+
.enum(["observed_fact", "industry_synthesis", "santismm_thesis", "strategic_hypothesis"])
|
|
811
|
+
.optional()
|
|
812
|
+
.describe("Restrict to one rung of the ladder. Omit for all."),
|
|
813
|
+
locale: localeSchema,
|
|
814
|
+
}),
|
|
815
|
+
outputSchema: claimListOutput,
|
|
816
|
+
}, async ({ claim_type, locale }) => outList(content.listClaims(claim_type, (locale ?? "en"))));
|
|
817
|
+
server.registerTool("get_claim", {
|
|
818
|
+
title: "Get one claim with its limits and what would refute it",
|
|
819
|
+
annotations: READ_ONLY,
|
|
820
|
+
description: "Get one claim by id (HE-CLAIM-001) or slug: the statement, what it rests on, its structured sources, and — always present — what it does NOT establish and the observation that would retire it. Use this to cite the corpus honestly, or to check whether a result you have just measured confirms or falsifies a claim it makes.",
|
|
821
|
+
inputSchema: z.object({
|
|
822
|
+
id: z.string().describe("Claim id (e.g. 'HE-CLAIM-001') or slug."),
|
|
823
|
+
locale: localeSchema,
|
|
824
|
+
}),
|
|
825
|
+
outputSchema: claimUnitOutput,
|
|
826
|
+
}, async ({ id, locale }) => {
|
|
827
|
+
const c = content.getClaim(id, locale);
|
|
828
|
+
return c ? out(c) : noEncontrado(content, "claims", id, (locale ?? "en"));
|
|
829
|
+
});
|
|
658
830
|
}
|
|
659
831
|
/**
|
|
660
832
|
* The tools this server exposes, as advertised metadata (name + description).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "santismm-knowledge-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server for the Santismm Knowledge Platform — harness engineering, agentic AI patterns, reference architectures and AI governance. Ships the corpus; the hosted endpoint at https://santismm.com/mcp is the always-fresh alternative.",
|
|
6
6
|
"type": "module",
|