@matchuplabs/nyc-api-mcp 0.1.0 → 0.1.2
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/lib/nyc-api-client.d.ts +2 -1
- package/dist/tools/get-building-violations.js +79 -60
- package/package.json +17 -4
- package/server.json +28 -4
|
@@ -36,25 +36,29 @@ function isApiError(result) {
|
|
|
36
36
|
"error" in result &&
|
|
37
37
|
typeof result.error === "object");
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Unwrap the REST API envelope: { data: { ... }, meta: { ... } }
|
|
41
|
+
* Returns the inner `data` object (the BuildingViolationProfile).
|
|
42
|
+
*/
|
|
39
43
|
function unwrapData(raw) {
|
|
40
44
|
if (!raw || typeof raw !== "object")
|
|
41
45
|
return null;
|
|
42
46
|
const rec = raw;
|
|
43
|
-
|
|
44
|
-
return rec;
|
|
47
|
+
// REST API wraps in { data: profile, meta: {...} }
|
|
45
48
|
if (rec.data && typeof rec.data === "object" && !Array.isArray(rec.data)) {
|
|
46
49
|
return rec.data;
|
|
47
50
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (Array.isArray(rec) && rec.length > 0) {
|
|
52
|
-
return rec[0];
|
|
53
|
-
}
|
|
51
|
+
// If it's already the profile (has address object or totals)
|
|
52
|
+
if (rec.address || rec.totals || rec.dob_violations)
|
|
53
|
+
return rec;
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
56
|
function str(val) {
|
|
57
|
-
|
|
57
|
+
if (val == null)
|
|
58
|
+
return "";
|
|
59
|
+
if (typeof val === "object")
|
|
60
|
+
return JSON.stringify(val);
|
|
61
|
+
return String(val);
|
|
58
62
|
}
|
|
59
63
|
function num(val) {
|
|
60
64
|
if (val == null)
|
|
@@ -62,18 +66,17 @@ function num(val) {
|
|
|
62
66
|
const n = Number(val);
|
|
63
67
|
return Number.isNaN(n) ? null : n;
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
69
|
+
/**
|
|
70
|
+
* Build a normalized address string from the REST API's address object.
|
|
71
|
+
* API returns: { house_number, street, borough, zipcode, block, lot }
|
|
72
|
+
*/
|
|
73
|
+
function buildAddressString(addr) {
|
|
74
|
+
if (!addr || typeof addr !== "object")
|
|
75
|
+
return String(addr ?? "");
|
|
76
|
+
const a = addr;
|
|
77
|
+
const parts = [a.house_number, a.street].filter(Boolean);
|
|
78
|
+
const loc = [a.borough, a.zipcode].filter(Boolean);
|
|
79
|
+
return parts.join(" ") + (loc.length ? `, ${loc.join(" ")}` : "");
|
|
77
80
|
}
|
|
78
81
|
export async function getBuildingViolations(args) {
|
|
79
82
|
const address = args.address?.trim() || undefined;
|
|
@@ -90,11 +93,13 @@ export async function getBuildingViolations(args) {
|
|
|
90
93
|
lookupParams.bin = bin;
|
|
91
94
|
if (borough)
|
|
92
95
|
lookupParams.borough = borough;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
address
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const profileParams = {};
|
|
97
|
+
if (address)
|
|
98
|
+
profileParams.address = address;
|
|
99
|
+
if (bin)
|
|
100
|
+
profileParams.bin = bin;
|
|
101
|
+
if (borough)
|
|
102
|
+
profileParams.borough = borough;
|
|
98
103
|
let raw;
|
|
99
104
|
try {
|
|
100
105
|
raw = await client.violationsProfile(profileParams);
|
|
@@ -111,50 +116,64 @@ export async function getBuildingViolations(args) {
|
|
|
111
116
|
const notFound = mcpError(ErrorCodes.NOT_FOUND, `No violation data returned for ${bin ? `BIN ${bin}` : `address "${address}"`}.`);
|
|
112
117
|
return JSON.stringify(notFound);
|
|
113
118
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
// --- Extract from the REST API's BuildingViolationProfile shape ---
|
|
120
|
+
// API returns: { address: {house_number, street, borough, ...}, dob_violations: [...],
|
|
121
|
+
// hpd_violations: [...], complaints_311: [...], totals: {dob, hpd, complaints_311, total} }
|
|
122
|
+
const addressObj = data.address;
|
|
123
|
+
const totalsObj = data.totals;
|
|
124
|
+
// DOB violations array
|
|
125
|
+
const dobViolationsRaw = Array.isArray(data.dob_violations) ? data.dob_violations : [];
|
|
126
|
+
const dobViolations = dobViolationsRaw.map((v) => ({
|
|
127
|
+
type: str(v.type || v.violation_type),
|
|
128
|
+
agency: "DOB",
|
|
129
|
+
description: str(v.description || v.violation_description),
|
|
130
|
+
date: str(v.date || v.issue_date),
|
|
131
|
+
status: str(v.disposition || v.status || ""),
|
|
132
|
+
category: str(v.category || ""),
|
|
133
|
+
violation_number: str(v.violation_number || ""),
|
|
134
|
+
}));
|
|
135
|
+
// HPD violations array
|
|
136
|
+
const hpdViolationsRaw = Array.isArray(data.hpd_violations) ? data.hpd_violations : [];
|
|
137
|
+
const hpdViolations = hpdViolationsRaw.map((v) => ({
|
|
138
|
+
type: str(v.class || v.nov_type || "HPD"),
|
|
139
|
+
agency: "HPD",
|
|
140
|
+
description: str(v.description || v.novdescription),
|
|
141
|
+
date: str(v.date || v.inspectiondate),
|
|
142
|
+
status: str(v.status || v.currentstatus || ""),
|
|
143
|
+
violation_id: str(v.violation_id || v.violationid || ""),
|
|
144
|
+
rent_impairing: v.rent_impairing ?? false,
|
|
145
|
+
}));
|
|
146
|
+
// Totals — prefer the totals object from the API, fall back to array lengths
|
|
147
|
+
const dobCount = num(totalsObj?.dob) ?? dobViolations.length;
|
|
148
|
+
const hpdCount = num(totalsObj?.hpd) ?? hpdViolations.length;
|
|
149
|
+
const complaintsCount = num(totalsObj?.complaints_311) ?? 0;
|
|
117
150
|
const totals = {
|
|
118
151
|
dob_count: dobCount,
|
|
119
152
|
hpd_count: hpdCount,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
153
|
+
complaints_311_count: complaintsCount,
|
|
154
|
+
total: num(totalsObj?.total) ?? (dobCount + hpdCount + complaintsCount),
|
|
155
|
+
};
|
|
156
|
+
// Source coverage
|
|
157
|
+
const sourceCoverage = {
|
|
158
|
+
dob: dobViolations.length > 0 || (totalsObj?.dob != null),
|
|
159
|
+
hpd: hpdViolations.length > 0 || (totalsObj?.hpd != null),
|
|
160
|
+
complaints_311: Array.isArray(data.complaints_311) || (totalsObj?.complaints_311 != null),
|
|
123
161
|
};
|
|
124
|
-
// Extract open violations array
|
|
125
|
-
const rawOpen = data.open_violations || data.violations || data.open;
|
|
126
|
-
const openViolations = Array.isArray(rawOpen)
|
|
127
|
-
? rawOpen.map((v) => ({
|
|
128
|
-
type: str(v.violation_type || v.type),
|
|
129
|
-
agency: str(v.agency || v.source),
|
|
130
|
-
description: str(v.description || v.violation_description),
|
|
131
|
-
date: str(v.date || v.violation_date || v.issued_date),
|
|
132
|
-
status: str(v.status || v.disposition || "open"),
|
|
133
|
-
...v,
|
|
134
|
-
}))
|
|
135
|
-
: [];
|
|
136
162
|
const profile = {
|
|
137
|
-
normalized_address:
|
|
138
|
-
bin: str(data.bin || bin),
|
|
139
|
-
bbl: str(data.bbl),
|
|
140
|
-
borough: str(data.borough),
|
|
163
|
+
normalized_address: buildAddressString(addressObj),
|
|
164
|
+
bin: str(addressObj?.bin || data.bin || bin),
|
|
165
|
+
bbl: str(addressObj?.bbl || data.bbl),
|
|
166
|
+
borough: str(addressObj?.borough || data.borough || borough),
|
|
167
|
+
block: str(addressObj?.block || data.block),
|
|
168
|
+
lot: str(addressObj?.lot || data.lot),
|
|
141
169
|
totals,
|
|
142
|
-
|
|
170
|
+
dob_violations: dobViolations,
|
|
171
|
+
hpd_violations: hpdViolations,
|
|
143
172
|
source_coverage: sourceCoverage,
|
|
144
173
|
};
|
|
145
174
|
if (args.include_complaints) {
|
|
146
|
-
const complaints = data.complaints_311
|
|
175
|
+
const complaints = data.complaints_311;
|
|
147
176
|
profile.complaints_311 = Array.isArray(complaints) ? complaints : [];
|
|
148
177
|
}
|
|
149
|
-
if (args.include_resolved_counts) {
|
|
150
|
-
const dobResolved = num(data.dob_resolved ?? data.dob_resolved_count);
|
|
151
|
-
const hpdResolved = num(data.hpd_resolved ?? data.hpd_resolved_count);
|
|
152
|
-
profile.resolved_counts = {
|
|
153
|
-
dob: dobResolved,
|
|
154
|
-
hpd: hpdResolved,
|
|
155
|
-
total: num(data.total_resolved ??
|
|
156
|
-
(dobResolved ?? 0) + (hpdResolved ?? 0)),
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
178
|
return JSON.stringify(profile);
|
|
160
179
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matchuplabs/nyc-api-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "MCP server for NYC property intelligence, building violations, and restaurant/venue compliance data via nycapi.app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"nyc-api-mcp": "dist/index.js"
|
|
9
9
|
},
|
|
10
|
-
"mcpName": "io.github.
|
|
10
|
+
"mcpName": "io.github.MatchupLabs/nyc-api",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "https://github.com/MATCHUP-LABS/nycapi.git"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"nyc",
|
|
19
|
+
"property",
|
|
20
|
+
"violations",
|
|
21
|
+
"restaurant",
|
|
22
|
+
"real-estate",
|
|
23
|
+
"api",
|
|
24
|
+
"ai-agent"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"server.json",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
18
31
|
"scripts": {
|
|
19
32
|
"build": "tsc",
|
|
20
33
|
"dev": "tsx src/index.ts",
|
package/server.json
CHANGED
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.MatchupLabs/nyc-api",
|
|
3
4
|
"title": "NYC Property & Venue API",
|
|
4
|
-
"description": "NYC property intelligence, building violations, and restaurant/venue compliance
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"description": "NYC property intelligence, building violations, and restaurant/venue compliance via MCP.",
|
|
6
|
+
"version": "0.1.2",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/MATCHUP-LABS/nycapi",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
6
11
|
"packages": [
|
|
7
12
|
{
|
|
8
13
|
"registryType": "npm",
|
|
9
|
-
"
|
|
14
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
|
+
"identifier": "@matchuplabs/nyc-api-mcp",
|
|
16
|
+
"version": "0.1.2",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
},
|
|
20
|
+
"environmentVariables": [
|
|
21
|
+
{
|
|
22
|
+
"name": "NYC_API_KEY",
|
|
23
|
+
"description": "API key from nycapi.app",
|
|
24
|
+
"isRequired": true,
|
|
25
|
+
"isSecret": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "NYC_API_BASE_URL",
|
|
29
|
+
"description": "API base URL (default: https://nycapi.app)",
|
|
30
|
+
"isRequired": false,
|
|
31
|
+
"isSecret": false
|
|
32
|
+
}
|
|
33
|
+
]
|
|
10
34
|
}
|
|
11
35
|
]
|
|
12
36
|
}
|