@oga-mcp/server 1.0.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 (56) hide show
  1. package/README.md +137 -0
  2. package/dist/api-client.d.ts +255 -0
  3. package/dist/api-client.js +260 -0
  4. package/dist/api-client.js.map +1 -0
  5. package/dist/methodology-data.d.ts +40 -0
  6. package/dist/methodology-data.js +179 -0
  7. package/dist/methodology-data.js.map +1 -0
  8. package/dist/server.d.ts +28 -0
  9. package/dist/server.js +173 -0
  10. package/dist/server.js.map +1 -0
  11. package/dist/tools/area-brief-audiences.d.ts +40 -0
  12. package/dist/tools/area-brief-audiences.js +131 -0
  13. package/dist/tools/area-brief-audiences.js.map +1 -0
  14. package/dist/tools/area-brief-format.d.ts +15 -0
  15. package/dist/tools/area-brief-format.js +142 -0
  16. package/dist/tools/area-brief-format.js.map +1 -0
  17. package/dist/tools/area-brief.d.ts +48 -0
  18. package/dist/tools/area-brief.js +82 -0
  19. package/dist/tools/area-brief.js.map +1 -0
  20. package/dist/tools/compare-postcodes.d.ts +57 -0
  21. package/dist/tools/compare-postcodes.js +148 -0
  22. package/dist/tools/compare-postcodes.js.map +1 -0
  23. package/dist/tools/engine-version.d.ts +23 -0
  24. package/dist/tools/engine-version.js +35 -0
  25. package/dist/tools/engine-version.js.map +1 -0
  26. package/dist/tools/find-areas.d.ts +38 -0
  27. package/dist/tools/find-areas.js +58 -0
  28. package/dist/tools/find-areas.js.map +1 -0
  29. package/dist/tools/find-peers.d.ts +44 -0
  30. package/dist/tools/find-peers.js +77 -0
  31. package/dist/tools/find-peers.js.map +1 -0
  32. package/dist/tools/get-area-signals.d.ts +39 -0
  33. package/dist/tools/get-area-signals.js +60 -0
  34. package/dist/tools/get-area-signals.js.map +1 -0
  35. package/dist/tools/get-portfolio-changes.d.ts +58 -0
  36. package/dist/tools/get-portfolio-changes.js +121 -0
  37. package/dist/tools/get-portfolio-changes.js.map +1 -0
  38. package/dist/tools/get-signals-by-category.d.ts +44 -0
  39. package/dist/tools/get-signals-by-category.js +67 -0
  40. package/dist/tools/get-signals-by-category.js.map +1 -0
  41. package/dist/tools/intelligence-format.d.ts +28 -0
  42. package/dist/tools/intelligence-format.js +197 -0
  43. package/dist/tools/intelligence-format.js.map +1 -0
  44. package/dist/tools/methodology-for.d.ts +34 -0
  45. package/dist/tools/methodology-for.js +71 -0
  46. package/dist/tools/methodology-for.js.map +1 -0
  47. package/dist/tools/score-postcode.d.ts +48 -0
  48. package/dist/tools/score-postcode.js +108 -0
  49. package/dist/tools/score-postcode.js.map +1 -0
  50. package/dist/tools/signals-format.d.ts +11 -0
  51. package/dist/tools/signals-format.js +116 -0
  52. package/dist/tools/signals-format.js.map +1 -0
  53. package/dist/tools/watch-portfolio.d.ts +50 -0
  54. package/dist/tools/watch-portfolio.js +126 -0
  55. package/dist/tools/watch-portfolio.js.map +1 -0
  56. package/package.json +48 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * AR-369: Render an audience-shaped brief from a real AreaProfile +
3
+ * ScoreResponse (?explain=true). Server-side-narrative policy:
4
+ *
5
+ * - The `summary`, `dimensions[].reasoning`, `dimensions[].confidence_reason`,
6
+ * `recommendations[]`, and `data_sources[]` come straight from
7
+ * /v1/score?explain=true (server-composed in AR-363).
8
+ * - The per-signal `confidence_reason`, `value`, `unit`, `percentile`,
9
+ * `source`, `observed_period` come straight from /v1/area (engine output).
10
+ * - This module SELECTS which of those fields render in which section
11
+ * based on the AudienceConfig. It does NOT invent prose.
12
+ */
13
+ /* ── value/unit formatting (mirrors signals-format.ts) ──────────── */
14
+ function formatValue(s) {
15
+ if (s.value === null)
16
+ return "—";
17
+ if (typeof s.value === "string")
18
+ return s.value;
19
+ const n = s.value;
20
+ if (s.unit === "GBP")
21
+ return `£${Math.round(n).toLocaleString("en-GB")}`;
22
+ if (s.unit === "pct")
23
+ return `${n.toFixed(1)}%`;
24
+ if (s.unit === "count" || s.unit === "rank" || s.unit === "decile")
25
+ return Math.round(n).toLocaleString("en-GB");
26
+ if (s.unit === "per_month")
27
+ return `${n.toFixed(1)} / mo`;
28
+ return n.toLocaleString("en-GB");
29
+ }
30
+ /* ── per-section renderers ──────────────────────────────────────── */
31
+ function renderDimensionLine(d) {
32
+ const lines = [];
33
+ const conf = ` · confidence ${(d.confidence * 100).toFixed(0)}%`;
34
+ lines.push(`- **${d.label}**: ${d.score}/100 (weight ${d.weight}%${conf})`);
35
+ if (d.reasoning)
36
+ lines.push(` ${d.reasoning}`);
37
+ if (d.confidence_reason)
38
+ lines.push(` _${d.confidence_reason}_`);
39
+ return lines;
40
+ }
41
+ function renderSignalLine(s) {
42
+ const lines = [];
43
+ const unit = s.unit && !["GBP", "pct"].includes(s.unit) ? ` ${s.unit}` : "";
44
+ lines.push(`- **${s.label}**: ${formatValue(s)}${s.value !== null ? unit : ""}`);
45
+ if (typeof s.percentile === "number") {
46
+ lines.push(` ${s.percentile}th percentile · direction: ${s.direction.replace(/_/g, " ")}`);
47
+ }
48
+ lines.push(` Confidence ${(s.confidence * 100).toFixed(0)}% · ${s.confidence_reason}`);
49
+ lines.push(` Source: ${s.source} · ${s.observed_period}`);
50
+ return lines;
51
+ }
52
+ /* ── section selector ───────────────────────────────────────────── */
53
+ function pickDimensions(allDims, labels) {
54
+ /* Match by exact label (case-insensitive). The engine evolves dimension
55
+ names across versions; the audience config lists known aliases so we
56
+ don't drop a section just because "Safety" became "Safety & Crime". */
57
+ const wanted = new Set(labels.map((l) => l.toLowerCase()));
58
+ return allDims.filter((d) => wanted.has(d.label.toLowerCase()));
59
+ }
60
+ function pickSignals(allSignals, keys) {
61
+ const wanted = new Set(keys);
62
+ /* Preserve the audience-defined order, not the catalog order. */
63
+ const byKey = new Map(allSignals.map((s) => [s.key, s]));
64
+ const out = [];
65
+ for (const k of keys) {
66
+ const s = byKey.get(k);
67
+ if (s && wanted.has(k))
68
+ out.push(s);
69
+ }
70
+ return out;
71
+ }
72
+ function renderSection(section, score, profile) {
73
+ const dims = pickDimensions(score.dimensions, section.dimensions);
74
+ const signals = pickSignals(profile.signals, section.signals);
75
+ if (dims.length === 0 && signals.length === 0) {
76
+ /* Drop empty sections rather than render an empty header — the
77
+ audience config can over-specify and that's fine. */
78
+ return [];
79
+ }
80
+ const lines = [];
81
+ lines.push(`## ${section.title}`);
82
+ for (const d of dims)
83
+ for (const line of renderDimensionLine(d))
84
+ lines.push(line);
85
+ for (const s of signals)
86
+ for (const line of renderSignalLine(s))
87
+ lines.push(line);
88
+ lines.push("");
89
+ return lines;
90
+ }
91
+ /* ── brief assembly ─────────────────────────────────────────────── */
92
+ export function formatAreaBriefAsText(audience, profile, score) {
93
+ const lines = [];
94
+ const geo = profile.geo;
95
+ /* Headline. */
96
+ const headlineLoc = geo.postcode && geo.postcode !== geo.query
97
+ ? `${geo.query} (${geo.postcode})`
98
+ : geo.query;
99
+ lines.push(`# ${audience.label} · ${headlineLoc}`);
100
+ lines.push(`${audience.framing}`);
101
+ lines.push("");
102
+ /* Geo summary line. */
103
+ const geoBits = [];
104
+ if (geo.admin_district)
105
+ geoBits.push(geo.admin_district);
106
+ if (geo.region)
107
+ geoBits.push(geo.region);
108
+ geoBits.push(geo.country);
109
+ geoBits.push(`${geo.area_type} area`);
110
+ if (geo.lsoa)
111
+ geoBits.push(`LSOA ${geo.lsoa}`);
112
+ lines.push(`**Area:** ${geoBits.join(" · ")}`);
113
+ lines.push(`**Engine version:** ${score.engine_version} · **Preset:** ${score.preset} · **Fetch mode:** ${profile.meta.fetch_mode}`);
114
+ lines.push("");
115
+ /* Overall verdict block — score + server-composed summary. */
116
+ lines.push(`## Overall verdict`);
117
+ lines.push(`**Score:** ${score.score}/100 · **Aggregate confidence:** ${(score.confidence * 100).toFixed(0)}%`);
118
+ if (score.summary) {
119
+ lines.push("");
120
+ lines.push(score.summary);
121
+ }
122
+ lines.push("");
123
+ /* Audience-shaped sections. Each pulls real fields per the config. */
124
+ for (const section of audience.sections) {
125
+ for (const line of renderSection(section, score, profile))
126
+ lines.push(line);
127
+ }
128
+ /* Recommendations (server-composed, AR-363). */
129
+ if (score.recommendations && score.recommendations.length > 0) {
130
+ lines.push(`## Notes & risks`);
131
+ for (const r of score.recommendations)
132
+ lines.push(`- ${r}`);
133
+ lines.push("");
134
+ }
135
+ /* Data sources (server-composed, AR-363). */
136
+ if (score.data_sources && score.data_sources.length > 0) {
137
+ lines.push(`## Data sources`);
138
+ lines.push(score.data_sources.join(" · "));
139
+ }
140
+ return lines.join("\n").trimEnd();
141
+ }
142
+ //# sourceMappingURL=area-brief-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"area-brief-format.js","sourceRoot":"","sources":["../../src/tools/area-brief-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * MCP tool: area_brief (AR-369) — the marquee composite.
3
+ *
4
+ * Given an area + audience, returns a fully-formatted audience-shaped
5
+ * brief on that area, composed from real engine state. Internally calls
6
+ * GET /v1/area (full signal catalog) and POST /v1/score?explain=true
7
+ * (audience's preset, with server-composed summary + recommendations +
8
+ * data sources), then renders an audience-specific markdown layout.
9
+ *
10
+ * Brief-shape policy: no client-side text synthesis. The MCP only
11
+ * SELECTS which real fields render in which section per the audience
12
+ * config. Every word of prose comes from the engine.
13
+ */
14
+ import type { OogaApiClient } from "../api-client.js";
15
+ import { type Audience } from "./area-brief-audiences.js";
16
+ export declare const areaBriefToolName = "area_brief";
17
+ export declare const areaBriefToolDef: {
18
+ readonly name: "area_brief";
19
+ readonly description: string;
20
+ readonly inputSchema: {
21
+ readonly type: "object";
22
+ readonly properties: {
23
+ readonly area: {
24
+ readonly type: "string";
25
+ readonly description: "UK postcode (e.g. 'SW1A 1AA') or place name (e.g. 'Manchester city centre'). Max 100 characters.";
26
+ };
27
+ readonly audience: {
28
+ readonly type: "string";
29
+ readonly enum: Audience[];
30
+ readonly description: "Brief audience. One of: lender, insurer, retailer, investor.";
31
+ };
32
+ };
33
+ readonly required: readonly ["area", "audience"];
34
+ readonly additionalProperties: false;
35
+ };
36
+ };
37
+ export interface AreaBriefArgs {
38
+ area: string;
39
+ audience: Audience;
40
+ }
41
+ export declare function parseAreaBriefArgs(raw: unknown): AreaBriefArgs;
42
+ export declare function executeAreaBrief(client: OogaApiClient, args: AreaBriefArgs): Promise<{
43
+ content: Array<{
44
+ type: "text";
45
+ text: string;
46
+ }>;
47
+ isError?: boolean;
48
+ }>;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * MCP tool: area_brief (AR-369) — the marquee composite.
3
+ *
4
+ * Given an area + audience, returns a fully-formatted audience-shaped
5
+ * brief on that area, composed from real engine state. Internally calls
6
+ * GET /v1/area (full signal catalog) and POST /v1/score?explain=true
7
+ * (audience's preset, with server-composed summary + recommendations +
8
+ * data sources), then renders an audience-specific markdown layout.
9
+ *
10
+ * Brief-shape policy: no client-side text synthesis. The MCP only
11
+ * SELECTS which real fields render in which section per the audience
12
+ * config. Every word of prose comes from the engine.
13
+ */
14
+ import { OogaApiError } from "../api-client.js";
15
+ import { AUDIENCES, getAudienceConfig } from "./area-brief-audiences.js";
16
+ import { formatAreaBriefAsText } from "./area-brief-format.js";
17
+ export const areaBriefToolName = "area_brief";
18
+ export const areaBriefToolDef = {
19
+ name: areaBriefToolName,
20
+ description: "Produce an audience-shaped brief on a UK area for one of four buyers: lender (residential mortgage origination), insurer (property risk underwriting), retailer (commercial site selection), investor (residential investment). " +
21
+ "Internally combines the full signals catalog with the audience's scoring preset (with explain mode) and renders an audience-specific markdown document: overall verdict, audience-relevant dimensions, audience-relevant signals with provenance, recommendations, and data sources. " +
22
+ "Every value is real engine output — no client-side prose synthesis. Costs two API calls per invocation (one /v1/area, one /v1/score?explain=true).",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ area: {
27
+ type: "string",
28
+ description: "UK postcode (e.g. 'SW1A 1AA') or place name (e.g. 'Manchester city centre'). Max 100 characters.",
29
+ },
30
+ audience: {
31
+ type: "string",
32
+ enum: AUDIENCES,
33
+ description: "Brief audience. One of: lender, insurer, retailer, investor.",
34
+ },
35
+ },
36
+ required: ["area", "audience"],
37
+ additionalProperties: false,
38
+ },
39
+ };
40
+ export function parseAreaBriefArgs(raw) {
41
+ if (typeof raw !== "object" || raw === null) {
42
+ throw new Error("area_brief arguments must be an object");
43
+ }
44
+ const obj = raw;
45
+ const area = obj.area;
46
+ const audience = obj.audience;
47
+ if (typeof area !== "string" || area.trim().length === 0) {
48
+ throw new Error("area must be a non-empty string");
49
+ }
50
+ if (area.length > 100) {
51
+ throw new Error("area must be 100 characters or fewer");
52
+ }
53
+ if (typeof audience !== "string" || !AUDIENCES.includes(audience)) {
54
+ throw new Error(`audience must be one of: ${AUDIENCES.join(", ")}`);
55
+ }
56
+ return { area: area.trim(), audience: audience };
57
+ }
58
+ export async function executeAreaBrief(client, args) {
59
+ const config = getAudienceConfig(args.audience);
60
+ try {
61
+ /* Two parallel calls — the brief needs both responses to render
62
+ and the network round-trip is the dominant cost. /v1/score is
63
+ called with explain=true (AR-363) so summary + recommendations
64
+ + data_sources come back server-composed. */
65
+ const [profile, score] = await Promise.all([
66
+ client.getAreaSignals(args.area),
67
+ client.scoreArea(args.area, config.preset),
68
+ ]);
69
+ return {
70
+ content: [{ type: "text", text: formatAreaBriefAsText(config, profile, score) }],
71
+ };
72
+ }
73
+ catch (err) {
74
+ if (err instanceof OogaApiError) {
75
+ const msg = `OneGoodArea API error (HTTP ${err.status ?? "?"}): ${err.message}`;
76
+ return { content: [{ type: "text", text: msg }], isError: true };
77
+ }
78
+ const msg = err instanceof Error ? err.message : String(err);
79
+ return { content: [{ type: "text", text: `Tool error: ${msg}` }], isError: true };
80
+ }
81
+ }
82
+ //# sourceMappingURL=area-brief.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"area-brief.js","sourceRoot":"","sources":["../../src/tools/area-brief.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * MCP tool: compare_postcodes
3
+ *
4
+ * Scores N areas (max 8) for a single preset in parallel and returns a
5
+ * comparison table sorted by overall score. Uses the existing scoreArea
6
+ * client method — Promise.allSettled in parallel, individual failures are
7
+ * captured inline rather than failing the whole call. Each per-area
8
+ * summary uses the server-composed `summary` from /v1/score?explain=true;
9
+ * no client-side narrative synthesis.
10
+ */
11
+ import type { OogaApiClient, Preset, OogaScoreResponse } from "../api-client.js";
12
+ export declare const comparePostcodesToolName = "compare_postcodes";
13
+ export declare const comparePostcodesToolDef: {
14
+ readonly name: "compare_postcodes";
15
+ readonly description: string;
16
+ readonly inputSchema: {
17
+ readonly type: "object";
18
+ readonly properties: {
19
+ readonly areas: {
20
+ readonly type: "array";
21
+ readonly items: {
22
+ readonly type: "string";
23
+ };
24
+ readonly minItems: 2;
25
+ readonly maxItems: 8;
26
+ readonly description: "Array of UK postcodes or place names to compare. Between 2 and 8 entries.";
27
+ };
28
+ readonly preset: {
29
+ readonly type: "string";
30
+ readonly enum: readonly ["moving", "business", "investing", "research"];
31
+ readonly description: "Same preset applied to every area. Use 'moving' for origination, 'business' for site selection, 'investing' for yield, 'research' for neutral baseline.";
32
+ };
33
+ };
34
+ readonly required: readonly ["areas", "preset"];
35
+ readonly additionalProperties: false;
36
+ };
37
+ };
38
+ export interface ComparePostcodesArgs {
39
+ areas: string[];
40
+ preset: Preset;
41
+ }
42
+ export declare function parseComparePostcodesArgs(raw: unknown): ComparePostcodesArgs;
43
+ interface ComparisonRow {
44
+ area: string;
45
+ score: number | null;
46
+ result: OogaScoreResponse | null;
47
+ error: string | null;
48
+ }
49
+ export declare function formatComparisonAsText(rows: ComparisonRow[], preset: Preset): string;
50
+ export declare function executeComparePostcodes(client: OogaApiClient, args: ComparePostcodesArgs): Promise<{
51
+ content: Array<{
52
+ type: "text";
53
+ text: string;
54
+ }>;
55
+ isError?: boolean;
56
+ }>;
57
+ export {};
@@ -0,0 +1,148 @@
1
+ /**
2
+ * MCP tool: compare_postcodes
3
+ *
4
+ * Scores N areas (max 8) for a single preset in parallel and returns a
5
+ * comparison table sorted by overall score. Uses the existing scoreArea
6
+ * client method — Promise.allSettled in parallel, individual failures are
7
+ * captured inline rather than failing the whole call. Each per-area
8
+ * summary uses the server-composed `summary` from /v1/score?explain=true;
9
+ * no client-side narrative synthesis.
10
+ */
11
+ import { OogaApiError } from "../api-client.js";
12
+ const MAX_AREAS = 8;
13
+ export const comparePostcodesToolName = "compare_postcodes";
14
+ export const comparePostcodesToolDef = {
15
+ name: comparePostcodesToolName,
16
+ description: "Compare multiple UK postcodes (max 8) side-by-side for the same decision preset. " +
17
+ "Returns a sorted table showing each area's overall score, area type, top dimension, and the server-composed per-area summary. " +
18
+ "Each request hits POST /v1/score?explain=true independently — partial failures are inline, not fatal.",
19
+ inputSchema: {
20
+ type: "object",
21
+ properties: {
22
+ areas: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ minItems: 2,
26
+ maxItems: MAX_AREAS,
27
+ description: `Array of UK postcodes or place names to compare. Between 2 and ${MAX_AREAS} entries.`,
28
+ },
29
+ preset: {
30
+ type: "string",
31
+ enum: ["moving", "business", "investing", "research"],
32
+ description: "Same preset applied to every area. Use 'moving' for origination, 'business' for site selection, 'investing' for yield, 'research' for neutral baseline.",
33
+ },
34
+ },
35
+ required: ["areas", "preset"],
36
+ additionalProperties: false,
37
+ },
38
+ };
39
+ export function parseComparePostcodesArgs(raw) {
40
+ if (typeof raw !== "object" || raw === null) {
41
+ throw new Error("compare_postcodes arguments must be an object");
42
+ }
43
+ const obj = raw;
44
+ const areas = obj.areas;
45
+ const preset = obj.preset;
46
+ if (!Array.isArray(areas)) {
47
+ throw new Error("areas must be an array");
48
+ }
49
+ if (areas.length < 2) {
50
+ throw new Error("areas must contain at least 2 entries");
51
+ }
52
+ if (areas.length > MAX_AREAS) {
53
+ throw new Error(`areas must contain at most ${MAX_AREAS} entries`);
54
+ }
55
+ const cleaned = [];
56
+ for (const a of areas) {
57
+ if (typeof a !== "string" || a.trim().length === 0) {
58
+ throw new Error("every area must be a non-empty string");
59
+ }
60
+ if (a.length > 100) {
61
+ throw new Error("each area must be 100 characters or fewer");
62
+ }
63
+ cleaned.push(a.trim());
64
+ }
65
+ const presets = ["moving", "business", "investing", "research"];
66
+ if (typeof preset !== "string" || !presets.includes(preset)) {
67
+ throw new Error(`preset must be one of: ${presets.join(", ")}`);
68
+ }
69
+ return { areas: cleaned, preset: preset };
70
+ }
71
+ export function formatComparisonAsText(rows, preset) {
72
+ // Sort: successful results by descending score, errors at the bottom
73
+ const sorted = [...rows].sort((a, b) => {
74
+ if (a.score === null && b.score === null)
75
+ return 0;
76
+ if (a.score === null)
77
+ return 1;
78
+ if (b.score === null)
79
+ return -1;
80
+ return b.score - a.score;
81
+ });
82
+ const lines = [];
83
+ lines.push(`# Comparison · ${rows.length} areas · preset: ${preset}`);
84
+ lines.push("");
85
+ // Headline table — score + area type per area
86
+ lines.push("| Rank | Area | Score | Area type | Top dimension |");
87
+ lines.push("|---|---|---|---|---|");
88
+ let rank = 1;
89
+ for (const r of sorted) {
90
+ if (r.score === null) {
91
+ lines.push(`| — | ${r.area} | ERROR | — | ${r.error ?? "Unknown"} |`);
92
+ continue;
93
+ }
94
+ const top = r.result?.dimensions?.length
95
+ ? r.result.dimensions.reduce((a, b) => (a.score > b.score ? a : b))
96
+ : null;
97
+ const topStr = top ? `${top.label} (${top.score}/100)` : "—";
98
+ const areaType = r.result?.area_type ?? "—";
99
+ lines.push(`| ${rank} | ${r.area} | ${r.score}/100 | ${areaType} | ${topStr} |`);
100
+ rank++;
101
+ }
102
+ lines.push("");
103
+ // Per-area summary
104
+ lines.push("## Summaries");
105
+ for (const r of sorted) {
106
+ if (r.score === null) {
107
+ lines.push(`- **${r.area}**: error — ${r.error ?? "unknown failure"}`);
108
+ continue;
109
+ }
110
+ /* Summary is server-composed from explain=true. Falls back to a
111
+ minimal score line only if explain mode somehow returned without it. */
112
+ const summary = r.result?.summary ?? `${r.score}/100`;
113
+ lines.push(`- **${r.area}**: ${summary}`);
114
+ }
115
+ // Footer with engine version if available
116
+ const firstSuccess = sorted.find((r) => r.result !== null)?.result;
117
+ if (firstSuccess?.engine_version) {
118
+ lines.push("");
119
+ lines.push(`Engine version: ${firstSuccess.engine_version}`);
120
+ }
121
+ return lines.join("\n");
122
+ }
123
+ export async function executeComparePostcodes(client, args) {
124
+ const settled = await Promise.allSettled(args.areas.map((a) => client.scoreArea(a, args.preset)));
125
+ const rows = settled.map((s, i) => {
126
+ if (s.status === "fulfilled") {
127
+ return {
128
+ area: args.areas[i],
129
+ score: s.value.score,
130
+ result: s.value,
131
+ error: null,
132
+ };
133
+ }
134
+ const reason = s.reason instanceof OogaApiError
135
+ ? `HTTP ${s.reason.status ?? "?"}: ${s.reason.message}`
136
+ : s.reason instanceof Error
137
+ ? s.reason.message
138
+ : String(s.reason);
139
+ return { area: args.areas[i], score: null, result: null, error: reason };
140
+ });
141
+ // If EVERY call failed, surface that as isError so the LLM can act
142
+ const allFailed = rows.every((r) => r.error !== null);
143
+ return {
144
+ content: [{ type: "text", text: formatComparisonAsText(rows, args.preset) }],
145
+ ...(allFailed ? { isError: true } : {}),
146
+ };
147
+ }
148
+ //# sourceMappingURL=compare-postcodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-postcodes.js","sourceRoot":"","sources":["../../src/tools/compare-postcodes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * MCP tool: engine_version
3
+ *
4
+ * Returns the current OneGoodArea engine version + changelog. Useful for
5
+ * procurement / model-risk teams documenting which engine version is in
6
+ * production, and for confirming version pinning when that ships.
7
+ */
8
+ export declare const engineVersionToolName = "engine_version";
9
+ export declare const engineVersionToolDef: {
10
+ readonly name: "engine_version";
11
+ readonly description: string;
12
+ readonly inputSchema: {
13
+ readonly type: "object";
14
+ readonly properties: {};
15
+ readonly additionalProperties: false;
16
+ };
17
+ };
18
+ export declare function executeEngineVersion(): {
19
+ content: Array<{
20
+ type: "text";
21
+ text: string;
22
+ }>;
23
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * MCP tool: engine_version
3
+ *
4
+ * Returns the current OneGoodArea engine version + changelog. Useful for
5
+ * procurement / model-risk teams documenting which engine version is in
6
+ * production, and for confirming version pinning when that ships.
7
+ */
8
+ import { ENGINE } from "../methodology-data.js";
9
+ export const engineVersionToolName = "engine_version";
10
+ export const engineVersionToolDef = {
11
+ name: engineVersionToolName,
12
+ description: "Return the current OneGoodArea engine version (e.g. '2.0.0'), release date, and changelog. " +
13
+ "Use this when a customer asks 'what version are we on' or for procurement / model-risk documentation. " +
14
+ "The engine version is also stamped on every score_postcode response for audit trails.",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {},
18
+ additionalProperties: false,
19
+ },
20
+ };
21
+ export function executeEngineVersion() {
22
+ const lines = [];
23
+ lines.push(`# OneGoodArea engine ${ENGINE.version}`);
24
+ lines.push(`Released: ${ENGINE.released}`);
25
+ lines.push("");
26
+ lines.push(`## Changelog`);
27
+ for (const entry of ENGINE.changelog) {
28
+ lines.push(`### ${entry.version} — ${entry.date}`);
29
+ lines.push(entry.summary);
30
+ lines.push("");
31
+ }
32
+ lines.push(`Full methodology: https://www.onegoodarea.com/methodology`);
33
+ return { content: [{ type: "text", text: lines.join("\n") }] };
34
+ }
35
+ //# sourceMappingURL=engine-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../src/tools/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEhD,MAAM,CAAC,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AAEtD,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACT,6FAA6F;QAC7F,wGAAwG;QACxG,uFAAuF;IACzF,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE;QACd,oBAAoB,EAAE,KAAK;KAC5B;CACO,CAAC;AAEX,MAAM,UAAU,oBAAoB;IAGlC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACrD,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAExE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AACjE,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * MCP tool: find_areas (AR-367)
3
+ *
4
+ * Natural-language interface to the OneGoodArea Intelligence query plane.
5
+ * The planner translates the question into a typed plan (one of 7 ops);
6
+ * the database executes it; the response carries plan + plan_source +
7
+ * results so every answer is reproducible.
8
+ *
9
+ * Wraps POST /v1/query with {question}.
10
+ */
11
+ import type { OogaApiClient } from "../api-client.js";
12
+ export declare const findAreasToolName = "find_areas";
13
+ export declare const findAreasToolDef: {
14
+ readonly name: "find_areas";
15
+ readonly description: string;
16
+ readonly inputSchema: {
17
+ readonly type: "object";
18
+ readonly properties: {
19
+ readonly question: {
20
+ readonly type: "string";
21
+ readonly description: "Free-form question about UK areas. Examples: 'areas under £250k median price and rising YoY in England', 'compare M1 1AE, SW4 0LG, EH1 1BB for site selection', 'LSOAs like E01034129 with similar crime + amenity profile', 'forecast median price in M1 1AE over the next 12 months'. Max 500 characters.";
22
+ };
23
+ };
24
+ readonly required: readonly ["question"];
25
+ readonly additionalProperties: false;
26
+ };
27
+ };
28
+ export interface FindAreasArgs {
29
+ question: string;
30
+ }
31
+ export declare function parseFindAreasArgs(raw: unknown): FindAreasArgs;
32
+ export declare function executeFindAreas(client: OogaApiClient, args: FindAreasArgs): Promise<{
33
+ content: Array<{
34
+ type: "text";
35
+ text: string;
36
+ }>;
37
+ isError?: boolean;
38
+ }>;