contractor-license-mcp-server 0.1.0 → 0.2.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/dist/api.d.ts +2 -1
- package/dist/api.js +11 -0
- package/dist/format.d.ts +2 -1
- package/dist/format.js +24 -0
- package/dist/index.js +12 -1
- package/dist/schemas.d.ts +19 -0
- package/dist/schemas.js +25 -0
- package/dist/tools/search.d.ts +12 -0
- package/dist/tools/search.js +23 -0
- package/dist/types.d.ts +20 -0
- package/package.json +6 -4
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LicenseResult } from "./types.js";
|
|
1
|
+
import type { LicenseResult, SearchResponse } from "./types.js";
|
|
2
2
|
export declare class ApiError extends Error {
|
|
3
3
|
statusCode: number;
|
|
4
4
|
retryAfter?: number | undefined;
|
|
@@ -8,6 +8,7 @@ export declare class ApiClient {
|
|
|
8
8
|
private http;
|
|
9
9
|
constructor(baseURL: string, apiKey: string);
|
|
10
10
|
verify(state: string, licenseNumber: string, trade: string): Promise<LicenseResult>;
|
|
11
|
+
search(state: string, name: string, trade: string, limit: number): Promise<SearchResponse>;
|
|
11
12
|
health(): Promise<{
|
|
12
13
|
status: string;
|
|
13
14
|
api: string;
|
package/dist/api.js
CHANGED
|
@@ -30,6 +30,17 @@ export class ApiClient {
|
|
|
30
30
|
throw this.wrapError(err);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
async search(state, name, trade, limit) {
|
|
34
|
+
try {
|
|
35
|
+
const { data } = await this.http.get("/search", {
|
|
36
|
+
params: { state, name, trade, limit },
|
|
37
|
+
});
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
throw this.wrapError(err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
33
44
|
async health() {
|
|
34
45
|
try {
|
|
35
46
|
const { data } = await this.http.get("/health");
|
package/dist/format.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { LicenseResult, StateInfo, BatchResponse } from "./types.js";
|
|
1
|
+
import type { LicenseResult, StateInfo, BatchResponse, SearchResponse } from "./types.js";
|
|
2
2
|
export declare function formatLicenseResult(result: LicenseResult, format: "markdown" | "json"): string;
|
|
3
3
|
export declare function formatStatesList(states: StateInfo[], format: "markdown" | "json"): string;
|
|
4
4
|
export declare function formatBatchResponse(batch: BatchResponse, format: "markdown" | "json"): string;
|
|
5
|
+
export declare function formatSearchResults(response: SearchResponse, format: "markdown" | "json"): string;
|
package/dist/format.js
CHANGED
|
@@ -74,3 +74,27 @@ export function formatBatchResponse(batch, format) {
|
|
|
74
74
|
}
|
|
75
75
|
return lines.join("\n");
|
|
76
76
|
}
|
|
77
|
+
export function formatSearchResults(response, format) {
|
|
78
|
+
if (format === "json") {
|
|
79
|
+
return JSON.stringify(response, null, 2);
|
|
80
|
+
}
|
|
81
|
+
const { query, total_results, results, cached, checked_at } = response;
|
|
82
|
+
const lines = [
|
|
83
|
+
`## Name Search: "${query.name}" in ${query.state}`,
|
|
84
|
+
"",
|
|
85
|
+
`**${total_results} result${total_results === 1 ? "" : "s"} found** | Trade: ${query.trade} | Cached: ${cached ? "Yes" : "No"} | Checked: ${checked_at}`,
|
|
86
|
+
"",
|
|
87
|
+
];
|
|
88
|
+
if (results.length === 0) {
|
|
89
|
+
lines.push("No matching contractors found.");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
lines.push("| # | Name | License # | Trade | Status | Confidence |", "|---|------|-----------|-------|--------|------------|");
|
|
93
|
+
for (let i = 0; i < results.length; i++) {
|
|
94
|
+
const r = results[i];
|
|
95
|
+
const conf = `${Math.round(r.confidence * 100)}%`;
|
|
96
|
+
lines.push(`| ${i + 1} | ${r.name} | ${r.license_number} | ${r.trade} | ${r.status ?? "N/A"} | ${conf} |`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return lines.join("\n");
|
|
100
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ApiClient } from "./api.js";
|
|
5
|
-
import { VerifyInputSchema, BatchInputSchema, StatesInputSchema } from "./schemas.js";
|
|
5
|
+
import { VerifyInputSchema, BatchInputSchema, StatesInputSchema, SearchInputSchema } from "./schemas.js";
|
|
6
6
|
import { handleVerify } from "./tools/verify.js";
|
|
7
7
|
import { handleBatchVerify } from "./tools/batch.js";
|
|
8
8
|
import { handleListStates } from "./tools/states.js";
|
|
9
|
+
import { handleSearchByName } from "./tools/search.js";
|
|
9
10
|
function getConfig() {
|
|
10
11
|
const apiUrl = process.env.CLV_API_URL;
|
|
11
12
|
const apiKey = process.env.CLV_API_KEY;
|
|
@@ -58,6 +59,16 @@ async function main() {
|
|
|
58
59
|
idempotentHint: true,
|
|
59
60
|
},
|
|
60
61
|
}, async (args) => handleListStates(args));
|
|
62
|
+
server.registerTool("clv_search_by_name", {
|
|
63
|
+
description: "Search for contractors by business or individual name in a state licensing database. " +
|
|
64
|
+
"Returns matching contractors with license numbers, status, and confidence scores.",
|
|
65
|
+
inputSchema: SearchInputSchema,
|
|
66
|
+
annotations: {
|
|
67
|
+
readOnlyHint: true,
|
|
68
|
+
destructiveHint: false,
|
|
69
|
+
idempotentHint: true,
|
|
70
|
+
},
|
|
71
|
+
}, async (args) => handleSearchByName(client, args));
|
|
61
72
|
const transport = new StdioServerTransport();
|
|
62
73
|
await server.connect(transport);
|
|
63
74
|
}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -48,6 +48,25 @@ export declare const BatchInputSchema: z.ZodObject<{
|
|
|
48
48
|
}[];
|
|
49
49
|
response_format?: "markdown" | "json" | undefined;
|
|
50
50
|
}>;
|
|
51
|
+
export declare const SearchInputSchema: z.ZodObject<{
|
|
52
|
+
state: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
53
|
+
name: z.ZodString;
|
|
54
|
+
trade: z.ZodDefault<z.ZodString>;
|
|
55
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
56
|
+
response_format: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
name: string;
|
|
59
|
+
state: string;
|
|
60
|
+
trade: string;
|
|
61
|
+
response_format: "markdown" | "json";
|
|
62
|
+
limit: number;
|
|
63
|
+
}, {
|
|
64
|
+
name: string;
|
|
65
|
+
state: string;
|
|
66
|
+
trade?: string | undefined;
|
|
67
|
+
response_format?: "markdown" | "json" | undefined;
|
|
68
|
+
limit?: number | undefined;
|
|
69
|
+
}>;
|
|
51
70
|
export declare const StatesInputSchema: z.ZodObject<{
|
|
52
71
|
response_format: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
53
72
|
}, "strip", z.ZodTypeAny, {
|
package/dist/schemas.js
CHANGED
|
@@ -43,6 +43,31 @@ export const BatchInputSchema = z.object({
|
|
|
43
43
|
.default("markdown")
|
|
44
44
|
.describe("Response format."),
|
|
45
45
|
});
|
|
46
|
+
export const SearchInputSchema = z.object({
|
|
47
|
+
state: stateField,
|
|
48
|
+
name: z
|
|
49
|
+
.string()
|
|
50
|
+
.min(2)
|
|
51
|
+
.max(200)
|
|
52
|
+
.describe("Business or individual name to search for in the state licensing database."),
|
|
53
|
+
trade: z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1)
|
|
56
|
+
.max(50)
|
|
57
|
+
.default("general")
|
|
58
|
+
.describe("The trade/contractor type to filter by (e.g. 'General Contractor', 'Electrical'). Use clv_list_supported_states to see valid values per state."),
|
|
59
|
+
limit: z
|
|
60
|
+
.number()
|
|
61
|
+
.int()
|
|
62
|
+
.min(1)
|
|
63
|
+
.max(50)
|
|
64
|
+
.default(20)
|
|
65
|
+
.describe("Maximum number of results to return."),
|
|
66
|
+
response_format: z
|
|
67
|
+
.enum(["markdown", "json"])
|
|
68
|
+
.default("markdown")
|
|
69
|
+
.describe("Response format."),
|
|
70
|
+
});
|
|
46
71
|
export const StatesInputSchema = z.object({
|
|
47
72
|
response_format: z
|
|
48
73
|
.enum(["markdown", "json"])
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { ApiClient } from "../api.js";
|
|
3
|
+
import type { SearchInputSchema } from "../schemas.js";
|
|
4
|
+
type SearchInput = z.output<typeof SearchInputSchema>;
|
|
5
|
+
export declare function handleSearchByName(client: ApiClient, args: SearchInput): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
isError?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { formatSearchResults } from "../format.js";
|
|
2
|
+
import { CHARACTER_LIMIT } from "../constants.js";
|
|
3
|
+
export async function handleSearchByName(client, args) {
|
|
4
|
+
const { state, name, trade, limit, response_format } = args;
|
|
5
|
+
try {
|
|
6
|
+
const response = await client.search(state, name, trade, limit);
|
|
7
|
+
let text = formatSearchResults(response, response_format);
|
|
8
|
+
if (text.length > CHARACTER_LIMIT) {
|
|
9
|
+
text =
|
|
10
|
+
text.slice(0, CHARACTER_LIMIT) +
|
|
11
|
+
"\n\n[Output truncated — too many results]";
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: err.message ?? "Name search failed" }],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -35,3 +35,23 @@ export interface BatchResponse {
|
|
|
35
35
|
};
|
|
36
36
|
results: BatchResult[];
|
|
37
37
|
}
|
|
38
|
+
export interface SearchResultItem {
|
|
39
|
+
name: string;
|
|
40
|
+
license_number: string;
|
|
41
|
+
trade: string;
|
|
42
|
+
status: string | null;
|
|
43
|
+
state: string;
|
|
44
|
+
confidence: number;
|
|
45
|
+
source_url: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface SearchResponse {
|
|
48
|
+
query: {
|
|
49
|
+
state: string;
|
|
50
|
+
name: string;
|
|
51
|
+
trade: string;
|
|
52
|
+
};
|
|
53
|
+
total_results: number;
|
|
54
|
+
results: SearchResultItem[];
|
|
55
|
+
cached: boolean;
|
|
56
|
+
checked_at: string;
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contractor-license-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for contractor license verification — verify licenses across US state licensing portals via AI agents like Claude Desktop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"contractor-license-mcp-server": "
|
|
7
|
+
"contractor-license-mcp-server": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
10
12
|
"scripts": {
|
|
11
13
|
"build": "tsc",
|
|
12
14
|
"dev": "tsc --watch",
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
"license": "MIT",
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/jackunderwood/Contractor-License-Verification.git",
|
|
34
|
+
"url": "git+https://github.com/jackunderwood/Contractor-License-Verification.git",
|
|
33
35
|
"directory": "mcp-server"
|
|
34
36
|
},
|
|
35
37
|
"homepage": "https://github.com/jackunderwood/Contractor-License-Verification/tree/main/mcp-server#readme",
|