contractor-license-mcp-server 0.6.3 → 0.6.4
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/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/api.js +1 -2
- package/dist/index.js +22 -9
- package/dist/schemas.js +14 -14
- package/dist/tools/batch.js +0 -2
- package/dist/tools/states.js +0 -2
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jack Underwood
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -164,8 +164,8 @@ Each license verification consumes **1 credit**, whether the result is fresh or
|
|
|
164
164
|
## Development
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
|
-
git clone https://github.com/
|
|
168
|
-
cd
|
|
167
|
+
git clone https://github.com/Noquarter6/contractor-license-mcp-server.git
|
|
168
|
+
cd contractor-license-mcp-server
|
|
169
169
|
npm install
|
|
170
170
|
npm run build
|
|
171
171
|
npm test
|
package/dist/api.js
CHANGED
|
@@ -15,11 +15,10 @@ export class ApiClient {
|
|
|
15
15
|
this.http = axios.create({
|
|
16
16
|
baseURL,
|
|
17
17
|
headers: { "X-API-Key": apiKey },
|
|
18
|
-
timeout: 120_000, //
|
|
18
|
+
timeout: 120_000, // Portal lookups can be slow
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
async verify(state, licenseNumber, trade) {
|
|
22
|
-
// TODO: Add force_refresh param once backend supports cache bypass
|
|
23
22
|
try {
|
|
24
23
|
const { data } = await this.http.get("/verify", {
|
|
25
24
|
params: { state, license: licenseNumber, trade },
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ function getConfig() {
|
|
|
12
12
|
const apiKey = process.env.CLV_API_KEY;
|
|
13
13
|
if (!apiUrl) {
|
|
14
14
|
console.error("Error: CLV_API_URL environment variable is required.\n" +
|
|
15
|
-
"Set it to
|
|
15
|
+
"Set it to https://www.tradesapi.com");
|
|
16
16
|
process.exit(1);
|
|
17
17
|
}
|
|
18
18
|
if (!apiKey) {
|
|
@@ -27,11 +27,16 @@ async function main() {
|
|
|
27
27
|
const client = new ApiClient(apiUrl, apiKey);
|
|
28
28
|
const server = new McpServer({
|
|
29
29
|
name: "contractor-license-verification",
|
|
30
|
-
version: "0.
|
|
30
|
+
version: "0.6.3",
|
|
31
31
|
});
|
|
32
32
|
server.registerTool("clv_verify_license", {
|
|
33
|
-
description: "Verify a contractor
|
|
34
|
-
"Returns
|
|
33
|
+
description: "Verify a single contractor license against a state licensing board portal. " +
|
|
34
|
+
"Returns validity, licensee name, status, expiration date, and any disciplinary actions on file. " +
|
|
35
|
+
"Use this when you have a specific license number to check. " +
|
|
36
|
+
"Use clv_search_by_name instead when you only have a contractor's name. " +
|
|
37
|
+
"Results are cached for 24 hours; set force_refresh to bypass. " +
|
|
38
|
+
"Returns an error for unsupported states (check clv_list_supported_states first). " +
|
|
39
|
+
"This is a read-only lookup that does not modify any licensing data.",
|
|
35
40
|
inputSchema: VerifyInputSchema,
|
|
36
41
|
annotations: {
|
|
37
42
|
readOnlyHint: true,
|
|
@@ -40,8 +45,11 @@ async function main() {
|
|
|
40
45
|
},
|
|
41
46
|
}, async (args) => handleVerify(client, args));
|
|
42
47
|
server.registerTool("clv_batch_verify", {
|
|
43
|
-
description: "Verify multiple contractor licenses in a single request (
|
|
44
|
-
"
|
|
48
|
+
description: "Verify multiple contractor licenses in a single request (1-25 items). " +
|
|
49
|
+
"Each license is verified independently — individual failures do not block the batch. " +
|
|
50
|
+
"Use this instead of multiple clv_verify_license calls when checking more than one license. " +
|
|
51
|
+
"Returns a summary (succeeded/failed counts) plus per-license results with the same fields as clv_verify_license. " +
|
|
52
|
+
"Returns an error if the array is empty or exceeds 25 items.",
|
|
45
53
|
inputSchema: BatchInputSchema,
|
|
46
54
|
annotations: {
|
|
47
55
|
readOnlyHint: true,
|
|
@@ -50,8 +58,10 @@ async function main() {
|
|
|
50
58
|
},
|
|
51
59
|
}, async (args) => handleBatchVerify(client, args));
|
|
52
60
|
server.registerTool("clv_list_supported_states", {
|
|
53
|
-
description: "List all US states
|
|
54
|
-
"
|
|
61
|
+
description: "List all US states supported for contractor license verification, with portal URLs, health status, and available trades per state. " +
|
|
62
|
+
"Call this before verifying to confirm a state and trade combination is supported. " +
|
|
63
|
+
"Returns 45 states. Health status is 'healthy', 'degraded', or 'down'. " +
|
|
64
|
+
"Does not make any network requests — the state list is embedded in the server.",
|
|
55
65
|
inputSchema: StatesInputSchema,
|
|
56
66
|
annotations: {
|
|
57
67
|
readOnlyHint: true,
|
|
@@ -61,7 +71,10 @@ async function main() {
|
|
|
61
71
|
}, async (args) => handleListStates(args));
|
|
62
72
|
server.registerTool("clv_search_by_name", {
|
|
63
73
|
description: "Search for contractors by business or individual name in a state licensing database. " +
|
|
64
|
-
"
|
|
74
|
+
"Use this when you have a contractor's name but not their license number. " +
|
|
75
|
+
"Use clv_verify_license instead when you already have the license number. " +
|
|
76
|
+
"Returns matching contractors with license numbers, status, and confidence scores (0-1). " +
|
|
77
|
+
"Partial name matches are supported. Results are capped by the limit parameter (default 20, max 50).",
|
|
65
78
|
inputSchema: SearchInputSchema,
|
|
66
79
|
annotations: {
|
|
67
80
|
readOnlyHint: true,
|
package/dist/schemas.js
CHANGED
|
@@ -5,43 +5,43 @@ const stateField = z
|
|
|
5
5
|
.length(2)
|
|
6
6
|
.transform((s) => s.toUpperCase())
|
|
7
7
|
.refine((s) => US_STATE_CODES.has(s), { message: "Invalid US state code" })
|
|
8
|
-
.describe("Two-letter US state code (e.g. 'CA', 'TX').
|
|
8
|
+
.describe("Two-letter US state code (e.g. 'CA', 'TX', 'FL'). 45 states supported. Call clv_list_supported_states to see the full list with available trades.");
|
|
9
9
|
export const VerifyInputSchema = z.object({
|
|
10
10
|
state: stateField,
|
|
11
11
|
license_number: z
|
|
12
12
|
.string()
|
|
13
13
|
.min(1)
|
|
14
14
|
.max(50)
|
|
15
|
-
.describe("The contractor's license number as
|
|
15
|
+
.describe("The contractor's license number exactly as issued (e.g. 'TACLA00000103C' for TX HVAC, '1098765' for CA). Format varies by state."),
|
|
16
16
|
trade: z
|
|
17
17
|
.string()
|
|
18
18
|
.min(1)
|
|
19
19
|
.max(50)
|
|
20
20
|
.default("general")
|
|
21
|
-
.describe("
|
|
21
|
+
.describe("Trade category: 'general', 'electrical', 'plumbing', 'hvac', 'mechanical', 'residential', or 'home_inspection'. Defaults to 'general'. Available trades vary by state — check clv_list_supported_states."),
|
|
22
22
|
force_refresh: z
|
|
23
23
|
.boolean()
|
|
24
24
|
.default(false)
|
|
25
|
-
.describe("Bypass cache and fetch
|
|
25
|
+
.describe("Bypass the 24-hour cache and re-fetch live from the state portal. Use when you need the most current data."),
|
|
26
26
|
response_format: z
|
|
27
27
|
.enum(["markdown", "json"])
|
|
28
28
|
.default("markdown")
|
|
29
|
-
.describe("
|
|
29
|
+
.describe("Output format. 'markdown' is optimized for chat display with tables. 'json' returns structured data for programmatic use."),
|
|
30
30
|
});
|
|
31
31
|
export const BatchInputSchema = z.object({
|
|
32
32
|
licenses: z
|
|
33
33
|
.array(z.object({
|
|
34
34
|
state: stateField,
|
|
35
|
-
license_number: z.string().min(1).max(50).describe("License number."),
|
|
36
|
-
trade: z.string().min(1).max(50).default("general").describe("Trade
|
|
35
|
+
license_number: z.string().min(1).max(50).describe("License number exactly as issued. Format varies by state."),
|
|
36
|
+
trade: z.string().min(1).max(50).default("general").describe("Trade category (e.g. 'general', 'electrical', 'plumbing', 'hvac'). Defaults to 'general'."),
|
|
37
37
|
}))
|
|
38
38
|
.min(1)
|
|
39
39
|
.max(25)
|
|
40
|
-
.describe("Array of licenses to verify (1-25 items)."),
|
|
40
|
+
.describe("Array of licenses to verify (1-25 items). Each license is verified independently — a failure on one does not affect the others."),
|
|
41
41
|
response_format: z
|
|
42
42
|
.enum(["markdown", "json"])
|
|
43
43
|
.default("markdown")
|
|
44
|
-
.describe("
|
|
44
|
+
.describe("Output format. 'markdown' is optimized for chat display. 'json' returns structured data for programmatic use."),
|
|
45
45
|
});
|
|
46
46
|
export const SearchInputSchema = z.object({
|
|
47
47
|
state: stateField,
|
|
@@ -49,28 +49,28 @@ export const SearchInputSchema = z.object({
|
|
|
49
49
|
.string()
|
|
50
50
|
.min(2)
|
|
51
51
|
.max(200)
|
|
52
|
-
.describe("Business or individual name to search for
|
|
52
|
+
.describe("Business or individual name to search for. Partial matches are supported (e.g. 'Anderson' will match 'Anderson Electric LLC')."),
|
|
53
53
|
trade: z
|
|
54
54
|
.string()
|
|
55
55
|
.min(1)
|
|
56
56
|
.max(50)
|
|
57
57
|
.default("general")
|
|
58
|
-
.describe("
|
|
58
|
+
.describe("Trade category to filter by: 'general', 'electrical', 'plumbing', 'hvac', etc. Defaults to 'general'. Check clv_list_supported_states for valid trades per state."),
|
|
59
59
|
limit: z
|
|
60
60
|
.number()
|
|
61
61
|
.int()
|
|
62
62
|
.min(1)
|
|
63
63
|
.max(50)
|
|
64
64
|
.default(20)
|
|
65
|
-
.describe("Maximum number of results to return."),
|
|
65
|
+
.describe("Maximum number of results to return. Defaults to 20, capped at 50."),
|
|
66
66
|
response_format: z
|
|
67
67
|
.enum(["markdown", "json"])
|
|
68
68
|
.default("markdown")
|
|
69
|
-
.describe("
|
|
69
|
+
.describe("Output format. 'markdown' is optimized for chat display with tables. 'json' returns structured data for programmatic use."),
|
|
70
70
|
});
|
|
71
71
|
export const StatesInputSchema = z.object({
|
|
72
72
|
response_format: z
|
|
73
73
|
.enum(["markdown", "json"])
|
|
74
74
|
.default("markdown")
|
|
75
|
-
.describe("
|
|
75
|
+
.describe("Output format. 'markdown' renders a table with state codes, names, status, and trades. 'json' returns a structured array."),
|
|
76
76
|
});
|
package/dist/tools/batch.js
CHANGED
|
@@ -3,8 +3,6 @@ import { CHARACTER_LIMIT } from "../constants.js";
|
|
|
3
3
|
export async function handleBatchVerify(client, args) {
|
|
4
4
|
const { licenses, response_format } = args;
|
|
5
5
|
const results = [];
|
|
6
|
-
// Sequential to respect backend rate limits.
|
|
7
|
-
// TODO: Switch to POST /batch when backend supports it (Phase 2B).
|
|
8
6
|
for (const item of licenses) {
|
|
9
7
|
try {
|
|
10
8
|
const result = await client.verify(item.state, item.license_number, item.trade);
|
package/dist/tools/states.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { formatStatesList } from "../format.js";
|
|
2
|
-
// Hardcoded until backend exposes a /states endpoint.
|
|
3
|
-
// Last updated: 2026-03-27 — 43 working states.
|
|
4
2
|
const SUPPORTED_STATES = [
|
|
5
3
|
{ code: "AK", name: "Alaska", portal: "https://www.commerce.alaska.gov/", status: "healthy", trades: ["general", "electrical", "mechanical"] },
|
|
6
4
|
{ code: "AL", name: "Alabama", portal: "https://genconbd.alabama.gov/", status: "healthy", trades: ["general", "electrical", "plumbing", "hvac", "residential"] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contractor-license-mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Real-time contractor license verification across 45 US states. MCP server for Claude Desktop and other AI agents — verify license status, expiration, and disciplinary history directly against state licensing board portals.",
|
|
5
5
|
"mcpName": "io.github.Noquarter6/contractor-license-mcp-server",
|
|
6
6
|
"type": "module",
|