contractor-license-mcp-server 0.6.5 → 0.8.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 +55 -63
- package/dist/api.d.ts +6 -11
- package/dist/api.js +50 -22
- package/dist/format.d.ts +1 -2
- package/dist/format.js +36 -37
- package/dist/http.d.ts +9 -0
- package/dist/http.js +274 -0
- package/dist/index.js +3 -64
- package/dist/oauth/callback.d.ts +3 -0
- package/dist/oauth/callback.js +104 -0
- package/dist/oauth/consent.d.ts +17 -0
- package/dist/oauth/consent.js +77 -0
- package/dist/oauth/internal-api.d.ts +17 -0
- package/dist/oauth/internal-api.js +40 -0
- package/dist/oauth/jwt.d.ts +26 -0
- package/dist/oauth/jwt.js +90 -0
- package/dist/oauth/provider.d.ts +19 -0
- package/dist/oauth/provider.js +222 -0
- package/dist/oauth/store.d.ts +6 -0
- package/dist/oauth/store.js +28 -0
- package/dist/redis.d.ts +7 -0
- package/dist/redis.js +27 -0
- package/dist/schemas.d.ts +3 -0
- package/dist/schemas.js +30 -25
- package/dist/server.d.ts +3 -0
- package/dist/server.js +76 -0
- package/dist/tools/batch.js +16 -30
- package/dist/tools/search.js +4 -5
- package/dist/tools/states.d.ts +2 -1
- package/dist/tools/states.js +28 -49
- package/dist/tools/verify.js +6 -6
- package/dist/types.d.ts +44 -9
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# contractor-license-mcp-server
|
|
2
2
|
|
|
3
|
-
**Real-time contractor license verification across
|
|
3
|
+
**Real-time contractor license verification across all 50 US states + DC, plus 8 major-city contractor licensing portals (Chicago, NYC, Philadelphia, Detroit, Atlanta, Dallas, Las Vegas, Nashville).** An [MCP server](https://modelcontextprotocol.io) that lets Claude Desktop, Claude Code, Cursor, Windsurf, and any MCP-compatible AI agent verify a contractor's license, status, expiration, and disciplinary history directly against licensing board portals.
|
|
4
4
|
|
|
5
5
|
Send `{state, license_number, trade}` — get back validity, licensee name, expiration date, status, and any disciplinary actions on file. Results are fetched live from official state portals (no stale nightly exports) and cached for 24 hours when active.
|
|
6
6
|
|
|
7
7
|
## Why this server
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **All 50 US states + DC + 8 major cities** covered via official licensing board portals, not third-party data aggregators
|
|
10
10
|
- **Live lookups** — each verification hits the authoritative portal, so expirations and disciplinary actions are as fresh as the board's own data
|
|
11
11
|
- **Batch verification** — up to 25 licenses per call, run in parallel
|
|
12
12
|
- **Disciplinary history** — returned when the portal exposes it
|
|
@@ -14,13 +14,33 @@ Send `{state, license_number, trade}` — get back validity, licensee name, expi
|
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
17
|
-
###
|
|
17
|
+
### Hosted (recommended)
|
|
18
18
|
|
|
19
|
-
Add this to your Claude Desktop config:
|
|
19
|
+
No install required. Add this to your Claude Desktop config:
|
|
20
20
|
|
|
21
21
|
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
22
22
|
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
23
23
|
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"tradesapi": {
|
|
28
|
+
"type": "streamable-http",
|
|
29
|
+
"url": "https://www.tradesapi.com/mcp",
|
|
30
|
+
"headers": {
|
|
31
|
+
"Authorization": "Bearer YOUR_API_KEY"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Replace `YOUR_API_KEY` with the key from your dashboard and restart Claude Desktop.
|
|
39
|
+
|
|
40
|
+
### Local install (alternative)
|
|
41
|
+
|
|
42
|
+
If you prefer to run the MCP server locally via stdio:
|
|
43
|
+
|
|
24
44
|
```json
|
|
25
45
|
{
|
|
26
46
|
"mcpServers": {
|
|
@@ -54,15 +74,16 @@ npm install -g contractor-license-mcp-server
|
|
|
54
74
|
|
|
55
75
|
## Tools
|
|
56
76
|
|
|
57
|
-
### `
|
|
77
|
+
### `verify_license`
|
|
58
78
|
|
|
59
|
-
Verify a single contractor license against
|
|
79
|
+
Verify a single contractor license against the official state (or city) licensing portal.
|
|
60
80
|
|
|
61
81
|
| Parameter | Required | Description |
|
|
62
82
|
|---|---|---|
|
|
63
83
|
| `state` | yes | Two-letter state code (`CA`, `TX`, `FL`, ...) |
|
|
84
|
+
| `city` | no | Optional city slug to target a municipal portal: `chicago`, `nyc`, `philadelphia`, `detroit`, `atlanta`, `dallas`, `lasvegas`, `nashville`. Lowercase, no spaces. |
|
|
64
85
|
| `license_number` | yes | The license number to verify |
|
|
65
|
-
| `trade` | no | `general`, `electrical`, `plumbing`, `hvac`, `mechanical`, `residential`,
|
|
86
|
+
| `trade` | no | `general`, `electrical`, `plumbing`, `hvac`, `mechanical`, `roofing`, `residential`, ... (defaults to `general`) |
|
|
66
87
|
| `force_refresh` | no | Bypass the 24h cache and re-fetch from the portal |
|
|
67
88
|
| `response_format` | no | `markdown` (default) or `json` |
|
|
68
89
|
|
|
@@ -81,74 +102,45 @@ Verify a single contractor license against a state licensing board portal.
|
|
|
81
102
|
| Expiration | 05/12/2026 |
|
|
82
103
|
```
|
|
83
104
|
|
|
84
|
-
### `
|
|
105
|
+
### `batch_verify`
|
|
85
106
|
|
|
86
|
-
Verify up to 25 licenses in a single call. Each verification runs independently — partial failures do not block the batch.
|
|
107
|
+
Verify up to 25 licenses in a single call. Each verification runs independently — partial failures do not block the batch. Per-item `city` is supported.
|
|
87
108
|
|
|
88
109
|
| Parameter | Required | Description |
|
|
89
110
|
|---|---|---|
|
|
90
|
-
| `licenses` | yes | Array of `{ state, license_number, trade }` objects (1–25 items) |
|
|
111
|
+
| `licenses` | yes | Array of `{ state, city?, license_number, trade }` objects (1–25 items) |
|
|
91
112
|
| `response_format` | no | `markdown` (default) or `json` |
|
|
92
113
|
|
|
93
|
-
### `
|
|
114
|
+
### `search_by_name`
|
|
94
115
|
|
|
95
|
-
|
|
116
|
+
Fuzzy-match contractors by business or individual name within a single state (or city) database. Costs 2 credits per call.
|
|
96
117
|
|
|
97
118
|
| Parameter | Required | Description |
|
|
98
119
|
|---|---|---|
|
|
120
|
+
| `state` | yes | Two-letter state code |
|
|
121
|
+
| `city` | no | Optional city slug for municipal databases |
|
|
122
|
+
| `name` | yes | Business or individual name (case-insensitive, partial-match tolerant) |
|
|
123
|
+
| `trade` | no | Trade filter |
|
|
124
|
+
| `limit` | no | Max results (1–50, default 20) |
|
|
99
125
|
| `response_format` | no | `markdown` (default) or `json` |
|
|
100
126
|
|
|
101
|
-
|
|
127
|
+
Not every state portal supports name search — call `list_supported_states` and check `supports_name_search` per jurisdiction first.
|
|
128
|
+
|
|
129
|
+
### `list_supported_states`
|
|
102
130
|
|
|
103
|
-
|
|
131
|
+
List every supported jurisdiction with portal URLs, current health, available trades, and registered municipal scrapers nested under each state. Use this to discover what's reachable before constructing other tool calls.
|
|
132
|
+
|
|
133
|
+
| Parameter | Required | Description |
|
|
104
134
|
|---|---|---|
|
|
105
|
-
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
| FL | Florida | general, electrical, plumbing, hvac |
|
|
115
|
-
| GA | Georgia | general |
|
|
116
|
-
| HI | Hawaii | general |
|
|
117
|
-
| IA | Iowa | electrical |
|
|
118
|
-
| ID | Idaho | electrical, plumbing, hvac |
|
|
119
|
-
| IL | Illinois | general, electrical, plumbing, hvac |
|
|
120
|
-
| IN | Indiana | plumbing |
|
|
121
|
-
| KY | Kentucky | general, electrical, hvac, plumbing |
|
|
122
|
-
| LA | Louisiana | general |
|
|
123
|
-
| MA | Massachusetts | general, mechanical |
|
|
124
|
-
| MD | Maryland | general, hvac, electrical, plumbing |
|
|
125
|
-
| ME | Maine | electrical, plumbing |
|
|
126
|
-
| MI | Michigan | electrical, plumbing, hvac |
|
|
127
|
-
| MN | Minnesota | general, electrical, plumbing |
|
|
128
|
-
| MS | Mississippi | general |
|
|
129
|
-
| NC | North Carolina | general |
|
|
130
|
-
| ND | North Dakota | general, electrical |
|
|
131
|
-
| NE | Nebraska | general, electrical |
|
|
132
|
-
| NH | New Hampshire | electrical, plumbing |
|
|
133
|
-
| NJ | New Jersey | general, electrical, hvac, plumbing |
|
|
134
|
-
| NM | New Mexico | general, electrical, plumbing, hvac |
|
|
135
|
-
| NV | Nevada | general, electrical, plumbing, hvac |
|
|
136
|
-
| NY | New York | home_inspection |
|
|
137
|
-
| OH | Ohio | general, electrical, plumbing, hvac |
|
|
138
|
-
| OK | Oklahoma | electrical, plumbing, hvac |
|
|
139
|
-
| OR | Oregon | general |
|
|
140
|
-
| PA | Pennsylvania | general, electrical, hvac, plumbing |
|
|
141
|
-
| RI | Rhode Island | general |
|
|
142
|
-
| SC | South Carolina | general, electrical, plumbing, hvac |
|
|
143
|
-
| TN | Tennessee | general, electrical, plumbing |
|
|
144
|
-
| TX | Texas | hvac, electrical, plumbing |
|
|
145
|
-
| UT | Utah | general, electrical, plumbing, hvac |
|
|
146
|
-
| VA | Virginia | general, electrical, plumbing, hvac |
|
|
147
|
-
| VT | Vermont | electrical, plumbing |
|
|
148
|
-
| WA | Washington | general |
|
|
149
|
-
| WV | West Virginia | general, electrical, hvac, plumbing |
|
|
150
|
-
|
|
151
|
-
Coverage expands continuously. Run `clv_list_supported_states` from your agent for the current list, or see the live state grid at [www.tradesapi.com](https://www.tradesapi.com).
|
|
135
|
+
| `response_format` | no | `markdown` (default) or `json` |
|
|
136
|
+
|
|
137
|
+
## Coverage
|
|
138
|
+
|
|
139
|
+
All 50 US states + DC at the state level, plus 8 major-city contractor licensing portals (Chicago, NYC, Philadelphia, Detroit, Atlanta, Dallas, Las Vegas, Nashville).
|
|
140
|
+
|
|
141
|
+
Run `list_supported_states` from your agent for the live, fetched-fresh-each-call list of supported jurisdictions, available trades per jurisdiction, current portal health, and which states support name search. The MCP package no longer bundles a static state table — what comes back from `list_supported_states` is always current with prod.
|
|
142
|
+
|
|
143
|
+
You can also see the live state grid at [www.tradesapi.com](https://www.tradesapi.com).
|
|
152
144
|
|
|
153
145
|
## Configuration
|
|
154
146
|
|
|
@@ -164,8 +156,8 @@ Each license verification consumes **1 credit**, whether the result is fresh or
|
|
|
164
156
|
## Development
|
|
165
157
|
|
|
166
158
|
```bash
|
|
167
|
-
git clone https://github.com/
|
|
168
|
-
cd
|
|
159
|
+
git clone https://github.com/jackunderwood/Contractor-License-Verification.git
|
|
160
|
+
cd Contractor-License-Verification/mcp-server
|
|
169
161
|
npm install
|
|
170
162
|
npm run build
|
|
171
163
|
npm test
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LicenseResult, SearchResponse,
|
|
1
|
+
import type { BatchItem, BatchResponse, LicenseResult, SearchResponse, StatesApiResponse } from "./types.js";
|
|
2
2
|
export declare class ApiError extends Error {
|
|
3
3
|
statusCode: number;
|
|
4
4
|
retryAfter?: number | undefined;
|
|
@@ -6,21 +6,16 @@ export declare class ApiError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export declare class ApiClient {
|
|
8
8
|
private http;
|
|
9
|
-
constructor(baseURL: string, apiKey: string);
|
|
10
|
-
verify(state: string, licenseNumber: string, trade: string): Promise<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}>;
|
|
14
|
-
search(state: string, name: string, trade: string, limit: number): Promise<{
|
|
15
|
-
data: SearchResponse;
|
|
16
|
-
credits: CreditInfo;
|
|
17
|
-
}>;
|
|
9
|
+
constructor(baseURL: string, apiKey: string, extraHeaders?: Record<string, string>);
|
|
10
|
+
verify(state: string, licenseNumber: string, trade: string, city?: string, forceRefresh?: boolean): Promise<LicenseResult>;
|
|
11
|
+
batch(items: BatchItem[]): Promise<BatchResponse>;
|
|
12
|
+
search(state: string, name: string, trade: string, limit: number, city?: string): Promise<SearchResponse>;
|
|
18
13
|
health(): Promise<{
|
|
19
14
|
status: string;
|
|
20
15
|
api: string;
|
|
21
16
|
database: string;
|
|
22
17
|
redis: string;
|
|
23
18
|
}>;
|
|
24
|
-
|
|
19
|
+
states(): Promise<StatesApiResponse>;
|
|
25
20
|
private wrapError;
|
|
26
21
|
}
|
package/dist/api.js
CHANGED
|
@@ -11,30 +11,57 @@ export class ApiError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
export class ApiClient {
|
|
13
13
|
http;
|
|
14
|
-
constructor(baseURL, apiKey) {
|
|
14
|
+
constructor(baseURL, apiKey, extraHeaders = {}) {
|
|
15
|
+
const headers = { ...extraHeaders };
|
|
16
|
+
if (apiKey)
|
|
17
|
+
headers["X-API-Key"] = apiKey;
|
|
15
18
|
this.http = axios.create({
|
|
16
19
|
baseURL,
|
|
17
|
-
headers
|
|
18
|
-
timeout: 120_000,
|
|
20
|
+
headers,
|
|
21
|
+
timeout: 120_000,
|
|
19
22
|
});
|
|
20
23
|
}
|
|
21
|
-
async verify(state, licenseNumber, trade) {
|
|
24
|
+
async verify(state, licenseNumber, trade, city, forceRefresh) {
|
|
22
25
|
try {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const params = { state, license: licenseNumber, trade };
|
|
27
|
+
if (city)
|
|
28
|
+
params.city = city;
|
|
29
|
+
if (forceRefresh)
|
|
30
|
+
params.fresh = "true";
|
|
31
|
+
const { data } = await this.http.get("/verify", { params });
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw this.wrapError(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async batch(items) {
|
|
39
|
+
try {
|
|
40
|
+
const body = {
|
|
41
|
+
licenses: items.map((it) => ({
|
|
42
|
+
state: it.state,
|
|
43
|
+
...(it.city ? { city: it.city } : {}),
|
|
44
|
+
license: it.license,
|
|
45
|
+
trade: it.trade,
|
|
46
|
+
})),
|
|
47
|
+
};
|
|
48
|
+
const { data } = await this.http.post("/batch", body);
|
|
49
|
+
return {
|
|
50
|
+
summary: { total: data.total, succeeded: data.succeeded, failed: data.failed },
|
|
51
|
+
results: data.results,
|
|
52
|
+
};
|
|
27
53
|
}
|
|
28
54
|
catch (err) {
|
|
29
55
|
throw this.wrapError(err);
|
|
30
56
|
}
|
|
31
57
|
}
|
|
32
|
-
async search(state, name, trade, limit) {
|
|
58
|
+
async search(state, name, trade, limit, city) {
|
|
33
59
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
60
|
+
const params = { state, name, trade, limit };
|
|
61
|
+
if (city)
|
|
62
|
+
params.city = city;
|
|
63
|
+
const { data } = await this.http.get("/search", { params });
|
|
64
|
+
return data;
|
|
38
65
|
}
|
|
39
66
|
catch (err) {
|
|
40
67
|
throw this.wrapError(err);
|
|
@@ -49,20 +76,21 @@ export class ApiClient {
|
|
|
49
76
|
throw this.wrapError(err);
|
|
50
77
|
}
|
|
51
78
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
async states() {
|
|
80
|
+
try {
|
|
81
|
+
const { data } = await this.http.get("/states");
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
throw this.wrapError(err);
|
|
86
|
+
}
|
|
59
87
|
}
|
|
60
88
|
wrapError(err) {
|
|
61
89
|
if (err.isAxiosError && err.response) {
|
|
62
90
|
const { status, data, headers } = err.response;
|
|
63
91
|
const detail = data?.detail ?? "Unknown error";
|
|
64
92
|
if (status === 401) {
|
|
65
|
-
return new ApiError("Authentication failed — check your CLV_API_KEY environment variable
|
|
93
|
+
return new ApiError("Authentication failed — check your CLV_API_KEY environment variable", 401);
|
|
66
94
|
}
|
|
67
95
|
if (status === 429) {
|
|
68
96
|
const retryAfter = parseInt(headers?.["retry-after"] ?? "60", 10);
|
|
@@ -72,7 +100,7 @@ export class ApiClient {
|
|
|
72
100
|
return new ApiError(detail, 400);
|
|
73
101
|
}
|
|
74
102
|
if (status === 502) {
|
|
75
|
-
return new ApiError(
|
|
103
|
+
return new ApiError("Verification temporarily unavailable. Try again in a few minutes.", 502);
|
|
76
104
|
}
|
|
77
105
|
return new ApiError(detail, status);
|
|
78
106
|
}
|
package/dist/format.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { LicenseResult, StateInfo, BatchResponse, SearchResponse
|
|
2
|
-
export declare function formatCredits(credits: CreditInfo): string;
|
|
1
|
+
import type { LicenseResult, StateInfo, BatchResponse, SearchResponse } from "./types.js";
|
|
3
2
|
export declare function formatLicenseResult(result: LicenseResult, format: "markdown" | "json"): string;
|
|
4
3
|
export declare function formatStatesList(states: StateInfo[], format: "markdown" | "json"): string;
|
|
5
4
|
export declare function formatBatchResponse(batch: BatchResponse, format: "markdown" | "json"): string;
|
package/dist/format.js
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
function normalizeStatus(status) {
|
|
2
|
-
if (!status)
|
|
3
|
-
return "N/A";
|
|
4
|
-
const s = status.toLowerCase();
|
|
5
|
-
if (s === "not_found" || s === "not found")
|
|
6
|
-
return "Not Found";
|
|
7
|
-
if (s === "unknown")
|
|
8
|
-
return "Unknown (lookup may have failed)";
|
|
9
|
-
return status;
|
|
10
|
-
}
|
|
11
|
-
export function formatCredits(credits) {
|
|
12
|
-
const parts = [];
|
|
13
|
-
if (credits.charged != null)
|
|
14
|
-
parts.push(`Credits used: ${credits.charged}`);
|
|
15
|
-
if (credits.remaining != null)
|
|
16
|
-
parts.push(`Credits remaining: ${credits.remaining}`);
|
|
17
|
-
return parts.length > 0 ? `\n\n---\n${parts.join(" | ")}` : "";
|
|
18
|
-
}
|
|
19
1
|
export function formatLicenseResult(result, format) {
|
|
20
2
|
if (format === "json") {
|
|
21
3
|
return JSON.stringify(result, null, 2);
|
|
@@ -30,7 +12,7 @@ export function formatLicenseResult(result, format) {
|
|
|
30
12
|
`| License # | ${result.license_number} |`,
|
|
31
13
|
`| State | ${result.state} |`,
|
|
32
14
|
`| Trade | ${result.trade} |`,
|
|
33
|
-
`| Status | ${
|
|
15
|
+
`| Status | ${result.status ?? "N/A"} |`,
|
|
34
16
|
`| Expiration | ${result.expiration ?? "N/A"} |`,
|
|
35
17
|
`| Source | ${result.source_url ?? "N/A"} |`,
|
|
36
18
|
`| Cached | ${result.cached ? "Yes" : "No"} |`,
|
|
@@ -39,36 +21,53 @@ export function formatLicenseResult(result, format) {
|
|
|
39
21
|
if (result.disciplinary_actions.length > 0) {
|
|
40
22
|
lines.push("", "### Disciplinary Actions");
|
|
41
23
|
for (const action of result.disciplinary_actions) {
|
|
42
|
-
|
|
43
|
-
lines.push(`- ${action}`);
|
|
44
|
-
}
|
|
45
|
-
else if (action && typeof action === "object" && "description" in action) {
|
|
46
|
-
lines.push(`- ${action.description}`);
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
lines.push(`- ${JSON.stringify(action)}`);
|
|
50
|
-
}
|
|
24
|
+
lines.push(`- ${action}`);
|
|
51
25
|
}
|
|
52
26
|
}
|
|
53
27
|
return lines.join("\n");
|
|
54
28
|
}
|
|
29
|
+
function statusIcon(status) {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case "healthy":
|
|
32
|
+
return "OK";
|
|
33
|
+
case "degraded":
|
|
34
|
+
return "DEGRADED";
|
|
35
|
+
case "maintenance":
|
|
36
|
+
return "MAINTENANCE";
|
|
37
|
+
default:
|
|
38
|
+
return "DOWN";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
55
41
|
export function formatStatesList(states, format) {
|
|
42
|
+
const allMunis = states.flatMap((s) => s.municipalities ?? []);
|
|
56
43
|
if (format === "json") {
|
|
57
|
-
return JSON.stringify({
|
|
44
|
+
return JSON.stringify({
|
|
45
|
+
total_states: states.length,
|
|
46
|
+
total_municipalities: allMunis.length,
|
|
47
|
+
states,
|
|
48
|
+
}, null, 2);
|
|
58
49
|
}
|
|
50
|
+
const header = allMunis.length > 0
|
|
51
|
+
? `## Supported Jurisdictions (${states.length} states + ${allMunis.length} cities)`
|
|
52
|
+
: `## Supported States (${states.length} states)`;
|
|
59
53
|
const lines = [
|
|
60
|
-
|
|
54
|
+
header,
|
|
55
|
+
"",
|
|
56
|
+
"### States",
|
|
61
57
|
"",
|
|
62
58
|
"| State | Name | Status | Trades |",
|
|
63
59
|
"|-------|------|--------|--------|",
|
|
64
60
|
];
|
|
65
61
|
for (const s of states) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
lines.push(
|
|
62
|
+
lines.push(`| ${s.code} | ${s.name} | ${statusIcon(s.status)} | ${s.trades.join(", ")} |`);
|
|
63
|
+
}
|
|
64
|
+
if (allMunis.length > 0) {
|
|
65
|
+
lines.push("", "### Cities (municipal scrapers)", "");
|
|
66
|
+
lines.push("| Code | City | Parent State | Status | Trades |");
|
|
67
|
+
lines.push("|------|------|--------------|--------|--------|");
|
|
68
|
+
for (const m of allMunis) {
|
|
69
|
+
lines.push(`| ${m.code} | ${m.city} | ${m.parent_state} | ${statusIcon(m.status)} | ${m.trades.join(", ")} |`);
|
|
70
|
+
}
|
|
72
71
|
}
|
|
73
72
|
return lines.join("\n");
|
|
74
73
|
}
|
|
@@ -89,7 +88,7 @@ export function formatBatchResponse(batch, format) {
|
|
|
89
88
|
if (item.result.name)
|
|
90
89
|
lines.push(`Name: ${item.result.name}`);
|
|
91
90
|
if (item.result.status)
|
|
92
|
-
lines.push(`Status: ${
|
|
91
|
+
lines.push(`Status: ${item.result.status}`);
|
|
93
92
|
lines.push("");
|
|
94
93
|
}
|
|
95
94
|
else {
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AuthResult {
|
|
2
|
+
/** api_key_id when authenticated via JWT, or undefined when authenticated via raw API key */
|
|
3
|
+
apiKeyId?: string;
|
|
4
|
+
/** Raw token (either JWT or raw API key) — used to build ApiClient */
|
|
5
|
+
token: string;
|
|
6
|
+
/** True if this was a JWT (and therefore Node uses X-Internal-Secret + X-Api-Key-Id when calling FastAPI) */
|
|
7
|
+
isJwt: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function sessionIdentity(auth: AuthResult): string;
|