@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.
@@ -40,7 +40,8 @@ export declare class NycApiClient {
40
40
  zipcode?: string;
41
41
  }): Promise<unknown>;
42
42
  violationsProfile(params: {
43
- address: string;
43
+ address?: string;
44
+ bin?: string;
44
45
  borough?: string;
45
46
  zipcode?: string;
46
47
  block?: string;
@@ -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
- if (rec.bin || rec.address || rec.bbl)
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
- if (Array.isArray(rec.data) && rec.data.length > 0) {
49
- return rec.data[0];
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
- return val != null ? String(val) : "";
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
- function buildSourceCoverage(data) {
66
- return {
67
- dob: Boolean(data.dob_violations != null ||
68
- data.dob_violation_count != null ||
69
- data.dob_open != null),
70
- hpd: Boolean(data.hpd_violations != null ||
71
- data.hpd_violation_count != null ||
72
- data.hpd_open != null),
73
- complaints_311: Boolean(data.complaints != null ||
74
- data.complaint_count != null ||
75
- data.complaints_311 != null),
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
- // violationsProfile requires address — pass address or bin as address fallback
94
- const profileParams = {
95
- address: address || bin || "",
96
- borough,
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
- const sourceCoverage = buildSourceCoverage(data);
115
- const dobCount = num(data.dob_violations ?? data.dob_violation_count ?? data.dob_count);
116
- const hpdCount = num(data.hpd_violations ?? data.hpd_violation_count ?? data.hpd_count);
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
- total: num(data.total_violations ??
121
- data.violation_count ??
122
- (dobCount ?? 0) + (hpdCount ?? 0)),
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: str(data.normalized_address || data.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
- open_violations: openViolations,
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 || data.complaints || data.complaint_history;
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.0",
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.matchuplabs/nyc-api",
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": ["mcp", "nyc", "property", "violations", "restaurant", "real-estate", "api", "ai-agent"],
17
- "files": ["dist", "server.json", "README.md"],
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
- "name": "io.github.matchuplabs/nyc-api",
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 unified, agent-ready MCP tools.",
5
- "version": "0.1.0",
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
- "name": "@matchuplabs/nyc-api-mcp"
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
  }