@cyanheads/sanctions-screening-mcp-server 0.1.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.
Files changed (110) hide show
  1. package/AGENTS.md +452 -0
  2. package/CLAUDE.md +452 -0
  3. package/Dockerfile +126 -0
  4. package/LICENSE +201 -0
  5. package/README.md +354 -0
  6. package/changelog/0.1.x/0.1.0.md +26 -0
  7. package/changelog/template.md +127 -0
  8. package/dist/config/server-config.d.ts +37 -0
  9. package/dist/config/server-config.d.ts.map +1 -0
  10. package/dist/config/server-config.js +87 -0
  11. package/dist/config/server-config.js.map +1 -0
  12. package/dist/index.d.ts +11 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +70 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/mcp-server/prompts/definitions/index.d.ts +12 -0
  17. package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
  18. package/dist/mcp-server/prompts/definitions/index.js +9 -0
  19. package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
  20. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.d.ts +14 -0
  21. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.d.ts.map +1 -0
  22. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.js +42 -0
  23. package/dist/mcp-server/prompts/definitions/vet-counterparty.prompt.js.map +1 -0
  24. package/dist/mcp-server/resources/definitions/designation.resource.d.ts +25 -0
  25. package/dist/mcp-server/resources/definitions/designation.resource.d.ts.map +1 -0
  26. package/dist/mcp-server/resources/definitions/designation.resource.js +57 -0
  27. package/dist/mcp-server/resources/definitions/designation.resource.js.map +1 -0
  28. package/dist/mcp-server/resources/definitions/entity.resource.d.ts +17 -0
  29. package/dist/mcp-server/resources/definitions/entity.resource.d.ts.map +1 -0
  30. package/dist/mcp-server/resources/definitions/entity.resource.js +40 -0
  31. package/dist/mcp-server/resources/definitions/entity.resource.js.map +1 -0
  32. package/dist/mcp-server/resources/definitions/index.d.ts +32 -0
  33. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
  34. package/dist/mcp-server/resources/definitions/index.js +11 -0
  35. package/dist/mcp-server/resources/definitions/index.js.map +1 -0
  36. package/dist/mcp-server/resources/definitions/sources.resource.d.ts +9 -0
  37. package/dist/mcp-server/resources/definitions/sources.resource.d.ts.map +1 -0
  38. package/dist/mcp-server/resources/definitions/sources.resource.js +50 -0
  39. package/dist/mcp-server/resources/definitions/sources.resource.js.map +1 -0
  40. package/dist/mcp-server/tools/definitions/_shared.d.ts +13 -0
  41. package/dist/mcp-server/tools/definitions/_shared.d.ts.map +1 -0
  42. package/dist/mcp-server/tools/definitions/_shared.js +13 -0
  43. package/dist/mcp-server/tools/definitions/_shared.js.map +1 -0
  44. package/dist/mcp-server/tools/definitions/get-designation.tool.d.ts +78 -0
  45. package/dist/mcp-server/tools/definitions/get-designation.tool.d.ts.map +1 -0
  46. package/dist/mcp-server/tools/definitions/get-designation.tool.js +168 -0
  47. package/dist/mcp-server/tools/definitions/get-designation.tool.js.map +1 -0
  48. package/dist/mcp-server/tools/definitions/get-entity.tool.d.ts +55 -0
  49. package/dist/mcp-server/tools/definitions/get-entity.tool.d.ts.map +1 -0
  50. package/dist/mcp-server/tools/definitions/get-entity.tool.js +176 -0
  51. package/dist/mcp-server/tools/definitions/get-entity.tool.js.map +1 -0
  52. package/dist/mcp-server/tools/definitions/index.d.ts +306 -0
  53. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  54. package/dist/mcp-server/tools/definitions/index.js +21 -0
  55. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  56. package/dist/mcp-server/tools/definitions/list-sources.tool.d.ts +23 -0
  57. package/dist/mcp-server/tools/definitions/list-sources.tool.d.ts.map +1 -0
  58. package/dist/mcp-server/tools/definitions/list-sources.tool.js +106 -0
  59. package/dist/mcp-server/tools/definitions/list-sources.tool.js.map +1 -0
  60. package/dist/mcp-server/tools/definitions/resolve-entity.tool.d.ts +51 -0
  61. package/dist/mcp-server/tools/definitions/resolve-entity.tool.d.ts.map +1 -0
  62. package/dist/mcp-server/tools/definitions/resolve-entity.tool.js +148 -0
  63. package/dist/mcp-server/tools/definitions/resolve-entity.tool.js.map +1 -0
  64. package/dist/mcp-server/tools/definitions/screen-name.tool.d.ts +82 -0
  65. package/dist/mcp-server/tools/definitions/screen-name.tool.d.ts.map +1 -0
  66. package/dist/mcp-server/tools/definitions/screen-name.tool.js +172 -0
  67. package/dist/mcp-server/tools/definitions/screen-name.tool.js.map +1 -0
  68. package/dist/mcp-server/tools/definitions/trace-ownership.tool.d.ts +74 -0
  69. package/dist/mcp-server/tools/definitions/trace-ownership.tool.d.ts.map +1 -0
  70. package/dist/mcp-server/tools/definitions/trace-ownership.tool.js +273 -0
  71. package/dist/mcp-server/tools/definitions/trace-ownership.tool.js.map +1 -0
  72. package/dist/services/screening/fixtures.d.ts +17 -0
  73. package/dist/services/screening/fixtures.d.ts.map +1 -0
  74. package/dist/services/screening/fixtures.js +162 -0
  75. package/dist/services/screening/fixtures.js.map +1 -0
  76. package/dist/services/screening/gleif-ingest.d.ts +68 -0
  77. package/dist/services/screening/gleif-ingest.d.ts.map +1 -0
  78. package/dist/services/screening/gleif-ingest.js +251 -0
  79. package/dist/services/screening/gleif-ingest.js.map +1 -0
  80. package/dist/services/screening/sanctions-ingest.d.ts +46 -0
  81. package/dist/services/screening/sanctions-ingest.d.ts.map +1 -0
  82. package/dist/services/screening/sanctions-ingest.js +688 -0
  83. package/dist/services/screening/sanctions-ingest.js.map +1 -0
  84. package/dist/services/screening/schema.d.ts +52 -0
  85. package/dist/services/screening/schema.d.ts.map +1 -0
  86. package/dist/services/screening/schema.js +125 -0
  87. package/dist/services/screening/schema.js.map +1 -0
  88. package/dist/services/screening/screening-service.d.ts +203 -0
  89. package/dist/services/screening/screening-service.d.ts.map +1 -0
  90. package/dist/services/screening/screening-service.js +702 -0
  91. package/dist/services/screening/screening-service.js.map +1 -0
  92. package/dist/services/screening/text-matching.d.ts +53 -0
  93. package/dist/services/screening/text-matching.d.ts.map +1 -0
  94. package/dist/services/screening/text-matching.js +514 -0
  95. package/dist/services/screening/text-matching.js.map +1 -0
  96. package/dist/services/screening/types.d.ts +154 -0
  97. package/dist/services/screening/types.d.ts.map +1 -0
  98. package/dist/services/screening/types.js +24 -0
  99. package/dist/services/screening/types.js.map +1 -0
  100. package/dist/services/screening/xml.d.ts +29 -0
  101. package/dist/services/screening/xml.d.ts.map +1 -0
  102. package/dist/services/screening/xml.js +46 -0
  103. package/dist/services/screening/xml.js.map +1 -0
  104. package/package.json +119 -0
  105. package/scripts/_mirror-context.ts +21 -0
  106. package/scripts/mirror-init.ts +66 -0
  107. package/scripts/mirror-refresh.ts +56 -0
  108. package/scripts/mirror-seed.ts +36 -0
  109. package/scripts/mirror-verify.ts +44 -0
  110. package/server.json +148 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @fileoverview Common normalized schema for sanctions designations and GLEIF
3
+ * legal-entity records, plus the matching-engine vocabulary. Every upstream
4
+ * source (OFAC, EU, UK, UN, GLEIF) collapses onto these shapes so the matching
5
+ * engine and tools never see a source-specific structure.
6
+ * @module services/screening/types
7
+ */
8
+ /** Source list codes — the value stored in `designation.source`. */
9
+ export type SourceCode = 'ofac_sdn' | 'ofac_consolidated' | 'eu' | 'uk' | 'un';
10
+ /** All sanctions source codes, in display order. */
11
+ export declare const SOURCE_CODES: readonly SourceCode[];
12
+ /** Human-facing label per source, used in provenance and `sanctions_list_sources`. */
13
+ export declare const SOURCE_LABELS: Record<SourceCode, string>;
14
+ /** Coarse entity classification shared across all sources. */
15
+ export type EntityType = 'person' | 'organization' | 'vessel' | 'aircraft' | 'unknown';
16
+ /** Name-record provenance within a designation. */
17
+ export type NameType = 'primary' | 'aka' | 'fka' | 'low-quality-aka';
18
+ /** One name or alias attached to a designation. */
19
+ export interface NameRecord {
20
+ /** The name as published. */
21
+ name: string;
22
+ /** Provenance of this name. */
23
+ nameType: NameType;
24
+ }
25
+ /** A structured identifier (passport, national ID, tax ID, registration number, …). */
26
+ export interface IdentifierRecord {
27
+ /** Issuing country/authority, when published. */
28
+ country?: string;
29
+ /** Identifier category as published (e.g. "Passport", "National ID"). */
30
+ type: string;
31
+ /** The identifier value. */
32
+ value: string;
33
+ }
34
+ /** A published address (free-form components — sources vary widely). */
35
+ export interface AddressRecord {
36
+ /** ISO country or country name, when published. */
37
+ country?: string;
38
+ /** Single-line rendering of the address, joined from whatever components were published. */
39
+ full: string;
40
+ }
41
+ /** Date + place of birth (persons only). */
42
+ export interface DobRecord {
43
+ /** Date string as published (ISO 8601 where the source provides a clean date). */
44
+ date?: string;
45
+ /** Place of birth, when published. */
46
+ place?: string;
47
+ }
48
+ /**
49
+ * The full normalized record for one designation, stored as JSON in
50
+ * `designation.payload` and surfaced by `sanctions_get_designation`.
51
+ */
52
+ export interface DesignationPayload {
53
+ addresses: AddressRecord[];
54
+ aliases: NameRecord[];
55
+ datesOfBirth: DobRecord[];
56
+ identifiers: IdentifierRecord[];
57
+ nationalities: string[];
58
+ /** Free-form remarks/title published by the source, when present. */
59
+ remarks?: string;
60
+ }
61
+ /**
62
+ * One normalized designation — the unit an ingester yields and the row stored
63
+ * in the primary `designation` table (with `payload` JSON-stringified).
64
+ */
65
+ export interface NormalizedDesignation {
66
+ /** Designation date, ISO 8601 where available. */
67
+ designationDate?: string;
68
+ entityType: EntityType;
69
+ /** `{source}:{sourceEntryId}` composite primary key. */
70
+ id: string;
71
+ /** Statutory / regulatory basis, when published. */
72
+ legalBasis?: string;
73
+ /** Full normalized detail. */
74
+ payload: DesignationPayload;
75
+ /** Primary name as published. */
76
+ primaryName: string;
77
+ /** Sanctioning program / regime, when published. */
78
+ program?: string;
79
+ source: SourceCode;
80
+ /** The list's own entry ID (for `get_designation`). */
81
+ sourceEntryId: string;
82
+ }
83
+ /** A GLEIF Level 1 entity record (who-is-who). */
84
+ export interface NormalizedLeiEntity {
85
+ /** Single-line headquarters address. */
86
+ headquartersAddress?: string;
87
+ /** ISO 3166-1 alpha-2 jurisdiction, when published. */
88
+ jurisdiction?: string;
89
+ /** ISO 8601 last-update timestamp from the LEI record. */
90
+ lastUpdate?: string;
91
+ /** Single-line legal address. */
92
+ legalAddress?: string;
93
+ legalName: string;
94
+ lei: string;
95
+ /** Trading / other names published in the LEI record. */
96
+ otherNames: string[];
97
+ /** The entity's ID at its registration authority. */
98
+ registrationAuthorityEntityId?: string;
99
+ /** Registration authority identifier (RA code). */
100
+ registrationAuthorityId?: string;
101
+ /** Registration status (e.g. ISSUED, LAPSED). */
102
+ status?: string;
103
+ }
104
+ /** A GLEIF Level 2 relationship record (who-owns-whom). */
105
+ export interface NormalizedLeiRelationship {
106
+ childLei: string;
107
+ parentLei: string;
108
+ /** Relationship period summary, when published. */
109
+ relationshipPeriod?: string;
110
+ /** Accounting/relationship status (e.g. ACTIVE, INACTIVE). */
111
+ relationshipStatus?: string;
112
+ /** e.g. IS_DIRECTLY_CONSOLIDATED_BY, IS_ULTIMATELY_CONSOLIDATED_BY. */
113
+ relationshipType: string;
114
+ }
115
+ /** Match classification, in descending confidence. */
116
+ export type MatchType = 'exact' | 'strong' | 'approximate';
117
+ /** The two screening match modes. */
118
+ export type MatchMode = 'strict' | 'fuzzy';
119
+ /** A scored screening hit returned by the matching engine. */
120
+ export interface ScreeningHit {
121
+ designationDate?: string;
122
+ /** `{source}:{sourceEntryId}` of the matched designation. */
123
+ designationId: string;
124
+ entityType: EntityType;
125
+ /** The specific name/alias string that matched the query. */
126
+ matchedName: string;
127
+ /** Provenance of the matched name (primary / aka / fka / low-quality-aka). */
128
+ matchedNameType: NameType;
129
+ matchType: MatchType;
130
+ /** Primary published name of the matched designation. */
131
+ primaryName: string;
132
+ program?: string;
133
+ /**
134
+ * Raw Jaro-Winkler similarity (0–1) for `approximate` hits — a real
135
+ * measurement, never a fabricated composite. Omitted for exact/strong hits,
136
+ * which are deterministic and not scored.
137
+ */
138
+ score?: number;
139
+ source: SourceCode;
140
+ sourceEntryId: string;
141
+ }
142
+ /** A scored LEI resolution candidate. */
143
+ export interface LeiMatch {
144
+ jurisdiction?: string;
145
+ legalName: string;
146
+ lei: string;
147
+ /** The name (legal or other) that matched the query. */
148
+ matchedName: string;
149
+ matchType: MatchType;
150
+ /** Raw Jaro-Winkler similarity for `approximate` hits only. */
151
+ score?: number;
152
+ status?: string;
153
+ }
154
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/screening/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @fileoverview Common normalized schema for sanctions designations and GLEIF
3
+ * legal-entity records, plus the matching-engine vocabulary. Every upstream
4
+ * source (OFAC, EU, UK, UN, GLEIF) collapses onto these shapes so the matching
5
+ * engine and tools never see a source-specific structure.
6
+ * @module services/screening/types
7
+ */
8
+ /** All sanctions source codes, in display order. */
9
+ export const SOURCE_CODES = [
10
+ 'ofac_sdn',
11
+ 'ofac_consolidated',
12
+ 'eu',
13
+ 'uk',
14
+ 'un',
15
+ ];
16
+ /** Human-facing label per source, used in provenance and `sanctions_list_sources`. */
17
+ export const SOURCE_LABELS = {
18
+ ofac_sdn: 'OFAC Specially Designated Nationals (SDN) List',
19
+ ofac_consolidated: 'OFAC Consolidated Sanctions List',
20
+ eu: 'EU Consolidated Financial Sanctions List',
21
+ uk: 'UK Sanctions List (FCDO)',
22
+ un: 'UN Security Council Consolidated List',
23
+ };
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/screening/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,oDAAoD;AACpD,MAAM,CAAC,MAAM,YAAY,GAA0B;IACjD,UAAU;IACV,mBAAmB;IACnB,IAAI;IACJ,IAAI;IACJ,IAAI;CACI,CAAC;AAEX,sFAAsF;AACtF,MAAM,CAAC,MAAM,aAAa,GAA+B;IACvD,QAAQ,EAAE,gDAAgD;IAC1D,iBAAiB,EAAE,kCAAkC;IACrD,EAAE,EAAE,0CAA0C;IAC9C,EAAE,EAAE,0BAA0B;IAC9B,EAAE,EAAE,uCAAuC;CAC5C,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @fileoverview Server-local XML parser for the attribute-bearing sanctions and
3
+ * GLEIF data feeds. The framework's shared `xmlParser`
4
+ * (`@cyanheads/mcp-ts-core/utils`) is tuned for LLM structured output and
5
+ * constructs `fast-xml-parser` with its default `ignoreAttributes: true` — every
6
+ * XML attribute is dropped. That is fatal here: the OFAC advanced schema, the EU
7
+ * `xmlFullSanctionsList_1_1`, and parts of the GLEIF golden copy carry their
8
+ * load-bearing data (stable entry IDs, name strings, type codes, programmes,
9
+ * publication dates) in XML *attributes*, not element text. With attributes
10
+ * ignored the EU list parses to zero designations and OFAC loses its entry IDs,
11
+ * entity types, programmes, and dates.
12
+ *
13
+ * This parser turns attributes on with the `@_` prefix the ingesters read
14
+ * (`@_FixedRef`, `@_wholeName`, `@_code`, `@_publicationDate`, …) and is the only
15
+ * XML entry point the screening ingesters use. `processEntities: false` matches
16
+ * the framework's safe default (no entity expansion on untrusted bulk input).
17
+ * @module services/screening/xml
18
+ */
19
+ /**
20
+ * Parse an XML document string into a plain object, preserving attributes under
21
+ * the `@_` prefix. Synchronous — `fast-xml-parser` is a direct dependency, so
22
+ * there is no lazy-load step.
23
+ *
24
+ * @template T Expected shape of the parsed document.
25
+ * @param xml Raw XML string.
26
+ * @returns The parsed document.
27
+ */
28
+ export declare function parseXml<T = Record<string, unknown>>(xml: string): T;
29
+ //# sourceMappingURL=xml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../../src/services/screening/xml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAmBH;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAEpE"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @fileoverview Server-local XML parser for the attribute-bearing sanctions and
3
+ * GLEIF data feeds. The framework's shared `xmlParser`
4
+ * (`@cyanheads/mcp-ts-core/utils`) is tuned for LLM structured output and
5
+ * constructs `fast-xml-parser` with its default `ignoreAttributes: true` — every
6
+ * XML attribute is dropped. That is fatal here: the OFAC advanced schema, the EU
7
+ * `xmlFullSanctionsList_1_1`, and parts of the GLEIF golden copy carry their
8
+ * load-bearing data (stable entry IDs, name strings, type codes, programmes,
9
+ * publication dates) in XML *attributes*, not element text. With attributes
10
+ * ignored the EU list parses to zero designations and OFAC loses its entry IDs,
11
+ * entity types, programmes, and dates.
12
+ *
13
+ * This parser turns attributes on with the `@_` prefix the ingesters read
14
+ * (`@_FixedRef`, `@_wholeName`, `@_code`, `@_publicationDate`, …) and is the only
15
+ * XML entry point the screening ingesters use. `processEntities: false` matches
16
+ * the framework's safe default (no entity expansion on untrusted bulk input).
17
+ * @module services/screening/xml
18
+ */
19
+ import { XMLParser } from 'fast-xml-parser';
20
+ /**
21
+ * Shared parser instance. `fast-xml-parser` is stateless across `parse` calls,
22
+ * so one instance is reused for every source.
23
+ */
24
+ const parser = new XMLParser({
25
+ ignoreAttributes: false,
26
+ attributeNamePrefix: '@_',
27
+ processEntities: false,
28
+ // Keep numeric-looking ids (LEIs, entry ids) as strings — they are opaque
29
+ // identifiers, not numbers, and downstream code treats them as text.
30
+ parseAttributeValue: false,
31
+ parseTagValue: false,
32
+ trimValues: true,
33
+ });
34
+ /**
35
+ * Parse an XML document string into a plain object, preserving attributes under
36
+ * the `@_` prefix. Synchronous — `fast-xml-parser` is a direct dependency, so
37
+ * there is no lazy-load step.
38
+ *
39
+ * @template T Expected shape of the parsed document.
40
+ * @param xml Raw XML string.
41
+ * @returns The parsed document.
42
+ */
43
+ export function parseXml(xml) {
44
+ return parser.parse(xml);
45
+ }
46
+ //# sourceMappingURL=xml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.js","sourceRoot":"","sources":["../../../src/services/screening/xml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,gBAAgB,EAAE,KAAK;IACvB,mBAAmB,EAAE,IAAI;IACzB,eAAe,EAAE,KAAK;IACtB,0EAA0E;IAC1E,qEAAqE;IACrE,mBAAmB,EAAE,KAAK;IAC1B,aAAa,EAAE,KAAK;IACpB,UAAU,EAAE,IAAI;CACjB,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAA8B,GAAW;IAC/D,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;AAChC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,119 @@
1
+ {
2
+ "name": "@cyanheads/sanctions-screening-mcp-server",
3
+ "version": "0.1.0",
4
+ "mcpName": "io.github.cyanheads/sanctions-screening-mcp-server",
5
+ "description": "Screen names against the consolidated OFAC, EU, UK, and UN sanctions lists and resolve legal entities against GLEIF, fuzzy-matched offline over a local SQLite + FTS5 mirror. A screening aid, not a compliance determination.",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "sanctions-screening-mcp-server": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "changelog/",
14
+ "dist/",
15
+ "scripts/_mirror-context.ts",
16
+ "scripts/mirror-init.ts",
17
+ "scripts/mirror-refresh.ts",
18
+ "scripts/mirror-verify.ts",
19
+ "scripts/mirror-seed.ts",
20
+ "README.md",
21
+ "LICENSE",
22
+ "CLAUDE.md",
23
+ "AGENTS.md",
24
+ "Dockerfile",
25
+ "server.json"
26
+ ],
27
+ "scripts": {
28
+ "build": "bun run scripts/build.ts",
29
+ "rebuild": "bun run scripts/clean.ts && bun run scripts/build.ts",
30
+ "clean": "bun run scripts/clean.ts",
31
+ "devcheck": "bun run scripts/devcheck.ts",
32
+ "audit:refresh": "rm -f bun.lock && bun install && bun audit",
33
+ "tree": "bun run scripts/tree.ts",
34
+ "list-skills": "bun run scripts/list-skills.ts",
35
+ "format": "biome check --write .",
36
+ "format:unsafe": "biome check --write --unsafe .",
37
+ "lint:mcp": "bun run scripts/lint-mcp.ts",
38
+ "lint:packaging": "bun run scripts/lint-packaging.ts",
39
+ "bundle": "bun run build && npx -y @anthropic-ai/mcpb pack . dist/sanctions-screening-mcp-server.mcpb && bun run scripts/clean-mcpb.ts dist/sanctions-screening-mcp-server.mcpb",
40
+ "changelog:build": "bun run scripts/build-changelog.ts",
41
+ "changelog:check": "bun run scripts/build-changelog.ts --check",
42
+ "release:github": "bun run scripts/release-github.ts",
43
+ "publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish",
44
+ "test": "vitest run",
45
+ "start": "node dist/index.js",
46
+ "start:stdio": "MCP_TRANSPORT_TYPE=stdio node dist/index.js",
47
+ "start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js",
48
+ "mirror:init": "bun run scripts/mirror-init.ts",
49
+ "mirror:refresh": "bun run scripts/mirror-refresh.ts",
50
+ "mirror:verify": "bun run scripts/mirror-verify.ts",
51
+ "mirror:seed": "bun run scripts/mirror-seed.ts"
52
+ },
53
+ "keywords": [
54
+ "sanctions",
55
+ "sanctions-screening",
56
+ "ofac",
57
+ "ofac-sdn",
58
+ "aml",
59
+ "kyc",
60
+ "compliance",
61
+ "watchlist",
62
+ "gleif",
63
+ "lei",
64
+ "beneficial-ownership",
65
+ "due-diligence",
66
+ "mcp",
67
+ "mcp-server",
68
+ "model-context-protocol",
69
+ "typescript",
70
+ "bun",
71
+ "stdio",
72
+ "streamable-http",
73
+ "ai-agent"
74
+ ],
75
+ "repository": {
76
+ "type": "git",
77
+ "url": "git+https://github.com/cyanheads/sanctions-screening-mcp-server.git"
78
+ },
79
+ "bugs": {
80
+ "url": "https://github.com/cyanheads/sanctions-screening-mcp-server/issues"
81
+ },
82
+ "homepage": "https://github.com/cyanheads/sanctions-screening-mcp-server#readme",
83
+ "author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads)",
84
+ "funding": [
85
+ {
86
+ "type": "github",
87
+ "url": "https://github.com/sponsors/cyanheads"
88
+ },
89
+ {
90
+ "type": "buy_me_a_coffee",
91
+ "url": "https://www.buymeacoffee.com/cyanheads"
92
+ }
93
+ ],
94
+ "license": "Apache-2.0",
95
+ "packageManager": "bun@1.3.11",
96
+ "engines": {
97
+ "bun": ">=1.3.0",
98
+ "node": ">=24.0.0"
99
+ },
100
+ "publishConfig": {
101
+ "access": "public"
102
+ },
103
+ "dependencies": {
104
+ "@cyanheads/mcp-ts-core": "^0.10.6",
105
+ "better-sqlite3": "^12.4.1",
106
+ "fast-xml-parser": "^5.8.0",
107
+ "pino-pretty": "^13.1.3",
108
+ "zod": "^4.4.3"
109
+ },
110
+ "devDependencies": {
111
+ "@biomejs/biome": "2.5.0",
112
+ "@types/node": "25.9.3",
113
+ "depcheck": "^1.4.7",
114
+ "ignore": "^7.0.5",
115
+ "tsc-alias": "^1.8.17",
116
+ "typescript": "^6.0.3",
117
+ "vitest": "^4.1.8"
118
+ }
119
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview Shared bootstrap for the mirror lifecycle scripts
3
+ * (`mirror:init`, `mirror:refresh`, `mirror:verify`, `mirror:seed`). Builds a
4
+ * standalone ScreeningService outside the MCP request pipeline and exposes the
5
+ * framework logger. Imported by the three named scripts, so it must travel with
6
+ * them in the npm tarball / Docker image.
7
+ * @module scripts/_mirror-context
8
+ */
9
+
10
+ import { logger } from '@cyanheads/mcp-ts-core/utils';
11
+ import { buildScreeningService } from '@/services/screening/screening-service.js';
12
+
13
+ /** Build a fresh, standalone screening service for a lifecycle script. */
14
+ export function bootstrap() {
15
+ return { service: buildScreeningService(), log: logger };
16
+ }
17
+
18
+ /** A long, abortable signal for hours-long init runs (caps a runaway harvest). */
19
+ export function longRunSignal(hours = 6): AbortSignal {
20
+ return AbortSignal.timeout(hours * 60 * 60 * 1000);
21
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @fileoverview `mirror:init` — full out-of-band initialization of the local
3
+ * mirror from the live upstream sources. Harvests all four sanctions lists in
4
+ * full (via the MirrorService `init` sync), rebuilds the per-alias name index,
5
+ * then streams the GLEIF golden copy (Level 1 entities + Level 2 relationships).
6
+ * Hours-long and resumable; never run on the request path. Set
7
+ * `SANCTIONS_INIT_SKIP_GLEIF=1` to load only the (small) sanctions lists.
8
+ *
9
+ * Usage: `bun run mirror:init`
10
+ * @module scripts/mirror-init
11
+ */
12
+
13
+ import {
14
+ harvestLeiLevel1,
15
+ harvestLeiLevel2,
16
+ resolveGleifFileUrl,
17
+ } from '@/services/screening/gleif-ingest.js';
18
+ import { bootstrap, longRunSignal } from './_mirror-context.js';
19
+
20
+ async function main(): Promise<void> {
21
+ const { service, log } = bootstrap();
22
+ const signal = longRunSignal(8);
23
+
24
+ log.info('mirror:init — harvesting sanctions lists (full)');
25
+ const sanctions = await service.designations.runSync({ mode: 'init', signal });
26
+ log.info('mirror:init — sanctions harvest complete', {
27
+ records: sanctions.recordsApplied,
28
+ total: sanctions.total,
29
+ });
30
+ await service.rebuildNameIndex();
31
+ log.info('mirror:init — name index rebuilt');
32
+
33
+ if (process.env.SANCTIONS_INIT_SKIP_GLEIF === '1') {
34
+ log.notice(
35
+ 'mirror:init — SANCTIONS_INIT_SKIP_GLEIF set; skipping GLEIF (sanctions-only mirror)',
36
+ );
37
+ await service.close();
38
+ return;
39
+ }
40
+
41
+ log.info('mirror:init — resolving GLEIF golden-copy URLs');
42
+ const [l1Url, l2Url] = await Promise.all([
43
+ resolveGleifFileUrl('lei2-full', signal),
44
+ resolveGleifFileUrl('rr-full', signal),
45
+ ]);
46
+
47
+ log.info('mirror:init — streaming GLEIF Level 1 (who-is-who, ~3.3M records)');
48
+ const entities = await harvestLeiLevel1(l1Url, signal);
49
+ await service.ingestLeiEntities(entities);
50
+ log.info('mirror:init — GLEIF Level 1 loaded', { entities: entities.length });
51
+
52
+ log.info('mirror:init — streaming GLEIF Level 2 (who-owns-whom)');
53
+ const relationships = await harvestLeiLevel2(l2Url, signal);
54
+ await service.ingestLeiRelationships(relationships);
55
+ log.info('mirror:init — GLEIF Level 2 loaded', { relationships: relationships.length });
56
+
57
+ await service.markLeiReady(entities.length);
58
+ log.info('mirror:init — complete');
59
+ await service.close();
60
+ }
61
+
62
+ main().catch((err) => {
63
+ // eslint-disable-next-line no-console
64
+ console.error('mirror:init failed:', err);
65
+ process.exit(1);
66
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview `mirror:refresh` — incremental out-of-band refresh. Re-harvests
3
+ * the (small) sanctions lists in full and rebuilds the name index, then applies
4
+ * the GLEIF 8-hour deltas. The HTTP server runs the sanctions half of this on a
5
+ * cron automatically; stdio operators run this manually. Set
6
+ * `SANCTIONS_REFRESH_SKIP_GLEIF=1` to refresh only the sanctions lists.
7
+ *
8
+ * Usage: `bun run mirror:refresh`
9
+ * @module scripts/mirror-refresh
10
+ */
11
+
12
+ import {
13
+ harvestLeiLevel1,
14
+ harvestLeiLevel2,
15
+ resolveGleifFileUrl,
16
+ } from '@/services/screening/gleif-ingest.js';
17
+ import { bootstrap, longRunSignal } from './_mirror-context.js';
18
+
19
+ async function main(): Promise<void> {
20
+ const { service, log } = bootstrap();
21
+ const signal = longRunSignal(4);
22
+
23
+ log.info('mirror:refresh — re-harvesting sanctions lists');
24
+ const sanctions = await service.designations.runSync({ mode: 'refresh', signal });
25
+ await service.rebuildNameIndex();
26
+ log.info('mirror:refresh — sanctions refreshed', { records: sanctions.recordsApplied });
27
+
28
+ if (process.env.SANCTIONS_REFRESH_SKIP_GLEIF === '1') {
29
+ log.notice('mirror:refresh — SANCTIONS_REFRESH_SKIP_GLEIF set; skipping GLEIF deltas');
30
+ await service.close();
31
+ return;
32
+ }
33
+
34
+ log.info('mirror:refresh — applying GLEIF deltas (LastDay)');
35
+ const [l1Url, l2Url] = await Promise.all([
36
+ resolveGleifFileUrl('lei2-delta', signal, 'LastDay'),
37
+ resolveGleifFileUrl('rr-delta', signal, 'LastDay'),
38
+ ]);
39
+ const entities = await harvestLeiLevel1(l1Url, signal);
40
+ await service.ingestLeiEntities(entities);
41
+ const relationships = await harvestLeiLevel2(l2Url, signal);
42
+ await service.ingestLeiRelationships(relationships);
43
+ log.info('mirror:refresh — GLEIF deltas applied', {
44
+ entities: entities.length,
45
+ relationships: relationships.length,
46
+ });
47
+
48
+ log.info('mirror:refresh — complete');
49
+ await service.close();
50
+ }
51
+
52
+ main().catch((err) => {
53
+ // eslint-disable-next-line no-console
54
+ console.error('mirror:refresh failed:', err);
55
+ process.exit(1);
56
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @fileoverview `mirror:seed` — load the small synthetic fixture into the local
3
+ * mirror and mark it ready, for a quick local smoke run without downloading the
4
+ * real corpus. The fixture names are invented, NOT real sanctions designations.
5
+ * For production data use `mirror:init`.
6
+ *
7
+ * Usage: `bun run mirror:seed`
8
+ * @module scripts/mirror-seed
9
+ */
10
+
11
+ import {
12
+ FIXTURE_DESIGNATIONS,
13
+ FIXTURE_LEI_ENTITIES,
14
+ FIXTURE_LEI_RELATIONSHIPS,
15
+ } from '@/services/screening/fixtures.js';
16
+ import { bootstrap } from './_mirror-context.js';
17
+
18
+ async function main(): Promise<void> {
19
+ const { service, log } = bootstrap();
20
+ await service.seedFixtures({
21
+ designations: FIXTURE_DESIGNATIONS,
22
+ leiEntities: FIXTURE_LEI_ENTITIES,
23
+ leiRelationships: FIXTURE_LEI_RELATIONSHIPS,
24
+ });
25
+ log.notice('mirror:seed — synthetic fixture loaded (NOT real sanctions data)', {
26
+ designations: FIXTURE_DESIGNATIONS.length,
27
+ leiEntities: FIXTURE_LEI_ENTITIES.length,
28
+ });
29
+ await service.close();
30
+ }
31
+
32
+ main().catch((err) => {
33
+ // eslint-disable-next-line no-console
34
+ console.error('mirror:seed failed:', err);
35
+ process.exit(1);
36
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @fileoverview `mirror:verify` — readiness + freshness report for both mirrors.
3
+ * Prints per-source record counts, the sanctions/GLEIF readiness flags, and the
4
+ * last-completed timestamps. Read-only; safe to run anytime.
5
+ *
6
+ * Usage: `bun run mirror:verify`
7
+ * @module scripts/mirror-verify
8
+ */
9
+
10
+ import { bootstrap } from './_mirror-context.js';
11
+
12
+ async function main(): Promise<void> {
13
+ const { service, log } = bootstrap();
14
+ const [counts, sanctions, lei] = await Promise.all([
15
+ service.sourceCounts(),
16
+ service.sanctionsReadiness(),
17
+ service.leiReadiness(),
18
+ ]);
19
+
20
+ log.info('mirror:verify — sanctions mirror', {
21
+ ready: sanctions.ready,
22
+ total: sanctions.total,
23
+ completedAt: sanctions.completedAt ?? 'never',
24
+ status: sanctions.status,
25
+ });
26
+ for (const s of counts) {
27
+ log.info(` source ${s.code}`, { records: s.recordCount });
28
+ }
29
+ log.info('mirror:verify — GLEIF mirror', {
30
+ ready: lei.ready,
31
+ entities: lei.entityCount,
32
+ relationships: lei.relationshipCount,
33
+ completedAt: lei.completedAt ?? 'never',
34
+ status: lei.status,
35
+ });
36
+
37
+ await service.close();
38
+ }
39
+
40
+ main().catch((err) => {
41
+ // eslint-disable-next-line no-console
42
+ console.error('mirror:verify failed:', err);
43
+ process.exit(1);
44
+ });