@matchuplabs/nycfhv-mcp 0.1.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 ADDED
@@ -0,0 +1,57 @@
1
+ # @matchuplabs/nycfhv-mcp
2
+
3
+ MCP server that exposes the [NYC FHV Intelligence API](https://fhv.matchup.dev) — TLC for-hire-vehicle license verification and renewal-window lookups — as Claude / OpenAI agent tools.
4
+
5
+ ## Tools
6
+
7
+ | Tool | What it does | Cost |
8
+ |---|---|---|
9
+ | `verify_tlc_vehicle` | Verify an FHV by TLC license, DMV plate, or VIN. Returns active status, expiration, base, WAV flag. | 1 credit |
10
+ | `list_upcoming_renewals` | Vehicles whose license expires in the next N days. Filters: base, WAV, max model year. | 5 credits / page |
11
+
12
+ ## Quickstart
13
+
14
+ 1. Get an API key at [matchuplabs.com](https://matchuplabs.com) (product: `fhv-intelligence`).
15
+ 2. Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "nycfhv": {
21
+ "command": "npx",
22
+ "args": ["-y", "@matchuplabs/nycfhv-mcp"],
23
+ "env": {
24
+ "FHV_API_KEY": "mv_live_your_key_here"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ 3. Restart Claude Desktop. The two tools appear in the tool picker.
32
+
33
+ ## Environment
34
+
35
+ | Variable | Required | Default |
36
+ |---|---|---|
37
+ | `FHV_API_KEY` | Yes | — |
38
+ | `FHV_BASE_URL` | No | `https://fhv.matchup.dev` |
39
+
40
+ ## Why this exists
41
+
42
+ Apps and AI agents that touch NYC for-hire transportation today can't verify a TLC license without scraping a manual portal. This MCP wraps a commercial-grade verification API so an agent can confirm "is this vehicle currently TLC-active?" as a single tool call before authorizing a livery rate, dispatching a Medicaid NEMT trip, or pre-binding insurance.
43
+
44
+ Data source: NYC TLC's daily-refreshed [For-Hire Vehicles - Active dataset](https://data.cityofnewyork.us/Transportation/For-Hire-Vehicles-FHV-Active/8wbx-tsch). Public record under NY State FOIL.
45
+
46
+ ## Development
47
+
48
+ ```bash
49
+ npm install
50
+ FHV_API_KEY=mv_test_xxx npm run dev # stdio server, requires a real key
51
+ npm run build
52
+ npm test
53
+ ```
54
+
55
+ ## License
56
+
57
+ MIT. © Matchup Labs.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer, checkApiKey } from "./server-core.js";
4
+ async function main() {
5
+ checkApiKey();
6
+ const server = createServer();
7
+ const transport = new StdioServerTransport();
8
+ await server.connect(transport);
9
+ console.error("nycfhv MCP server running on stdio");
10
+ }
11
+ main().catch((error) => {
12
+ console.error("Fatal error:", error);
13
+ process.exit(1);
14
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * MCP-side error envelope. Tools return JSON-stringified errors so the calling
3
+ * agent can branch on `error.code` without parsing free text.
4
+ */
5
+ export interface McpError {
6
+ error: {
7
+ code: string;
8
+ message: string;
9
+ suggested_next_step: string;
10
+ upstream_status?: number;
11
+ };
12
+ }
13
+ export declare function mcpError(code: string, message: string, suggested_next_step: string, upstream_status?: number): McpError;
14
+ export declare function mcpErrorString(code: string, message: string, suggested_next_step: string, upstream_status?: number): string;
15
+ /**
16
+ * Map an upstream RFC 7807 problem-details payload (from fhv.matchup.dev) onto
17
+ * an MCP error envelope. The API returns `{ type, title, status, detail, ... }`.
18
+ */
19
+ export declare function mapUpstreamError(status: number, body: unknown): McpError;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * MCP-side error envelope. Tools return JSON-stringified errors so the calling
3
+ * agent can branch on `error.code` without parsing free text.
4
+ */
5
+ export function mcpError(code, message, suggested_next_step, upstream_status) {
6
+ return { error: { code, message, suggested_next_step, upstream_status } };
7
+ }
8
+ export function mcpErrorString(code, message, suggested_next_step, upstream_status) {
9
+ return JSON.stringify(mcpError(code, message, suggested_next_step, upstream_status));
10
+ }
11
+ /**
12
+ * Map an upstream RFC 7807 problem-details payload (from fhv.matchup.dev) onto
13
+ * an MCP error envelope. The API returns `{ type, title, status, detail, ... }`.
14
+ */
15
+ export function mapUpstreamError(status, body) {
16
+ const detail = body && typeof body === "object" && "detail" in body
17
+ ? String(body.detail)
18
+ : `Upstream returned ${status}`;
19
+ const title = body && typeof body === "object" && "title" in body
20
+ ? String(body.title)
21
+ : "Upstream error";
22
+ switch (status) {
23
+ case 400:
24
+ return mcpError("invalid_input", detail, "Fix the input and retry.", 400);
25
+ case 401:
26
+ return mcpError("invalid_api_key", "API key missing or invalid for fhv.matchup.dev.", "Set FHV_API_KEY in your MCP server env to a valid key from matchuplabs.com.", 401);
27
+ case 403:
28
+ return mcpError("key_disabled", detail, "Generate a fresh key at matchuplabs.com or contact support.", 403);
29
+ case 404:
30
+ return mcpError("not_found", detail, "Try a different identifier or relax filters.", 404);
31
+ case 429: {
32
+ const code = body && typeof body === "object" && "type" in body && String(body.type).includes("credits_exhausted")
33
+ ? "credits_exhausted"
34
+ : "rate_limit_exceeded";
35
+ const next = code === "credits_exhausted"
36
+ ? "Upgrade your plan or wait until next billing cycle."
37
+ : "Back off briefly (default 60s) and retry.";
38
+ return mcpError(code, detail, next, 429);
39
+ }
40
+ case 502:
41
+ case 503:
42
+ case 504:
43
+ return mcpError("upstream_error", `${title}: ${detail}`, "NYC TLC dataset is currently unavailable. Retry in a minute.", status);
44
+ default:
45
+ return mcpError("upstream_error", `${title}: ${detail}`, "Retry in a minute. Contact support if the error persists.", status);
46
+ }
47
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Tiny HTTP client for fhv.matchup.dev.
3
+ *
4
+ * Reads FHV_API_KEY from env (required) and FHV_BASE_URL (optional, defaults
5
+ * to production). Returns the parsed envelope `{data, meta}` on 2xx, or an
6
+ * `{ error: ... }` shape on non-2xx that mirrors the upstream RFC 7807 body.
7
+ */
8
+ import { type McpError } from "./errors.js";
9
+ export interface FhvSuccess<T> {
10
+ data: T;
11
+ meta: {
12
+ source: string;
13
+ updated?: string;
14
+ query_ms?: number;
15
+ credits_remaining?: number;
16
+ page?: number;
17
+ page_size?: number;
18
+ total_count?: number;
19
+ };
20
+ }
21
+ export type FhvResult<T> = FhvSuccess<T> | McpError;
22
+ export interface Vehicle {
23
+ active: boolean;
24
+ vehicle_license_number: string;
25
+ name: string;
26
+ license_type: string;
27
+ expiration_date: string;
28
+ days_until_expiration: number;
29
+ permit_license_number: string | null;
30
+ dmv_license_plate_number: string | null;
31
+ vehicle_vin_number: string | null;
32
+ wheelchair_accessible: boolean;
33
+ vehicle_year: number | null;
34
+ base_number: string | null;
35
+ base_name: string | null;
36
+ base_type: string | null;
37
+ }
38
+ export declare class FhvClient {
39
+ private baseUrl;
40
+ private apiKey;
41
+ constructor(apiKey?: string, baseUrl?: string);
42
+ private retryFetch;
43
+ private request;
44
+ /** GET /api/v1/verify/{identifier} */
45
+ verify(identifier: string, type?: "auto" | "license" | "plate" | "vin"): Promise<FhvResult<Vehicle>>;
46
+ /** GET /api/v1/renewals/upcoming */
47
+ listUpcomingRenewals(params: {
48
+ days: number;
49
+ base_number?: string;
50
+ wheelchair_accessible?: boolean;
51
+ vehicle_year_max?: number;
52
+ page?: number;
53
+ page_size?: number;
54
+ }): Promise<FhvResult<Vehicle[]>>;
55
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Tiny HTTP client for fhv.matchup.dev.
3
+ *
4
+ * Reads FHV_API_KEY from env (required) and FHV_BASE_URL (optional, defaults
5
+ * to production). Returns the parsed envelope `{data, meta}` on 2xx, or an
6
+ * `{ error: ... }` shape on non-2xx that mirrors the upstream RFC 7807 body.
7
+ */
8
+ import { mapUpstreamError, mcpError } from "./errors.js";
9
+ const DEFAULT_BASE_URL = "https://fhv.matchup.dev";
10
+ const REQUEST_TIMEOUT_MS = 10_000;
11
+ const RETRYABLE = new Set([429, 500, 502, 503, 504]);
12
+ const BACKOFF_MS = [400, 1200];
13
+ export class FhvClient {
14
+ baseUrl;
15
+ apiKey;
16
+ constructor(apiKey, baseUrl) {
17
+ this.baseUrl = baseUrl || process.env.FHV_BASE_URL || DEFAULT_BASE_URL;
18
+ const key = apiKey || process.env.FHV_API_KEY;
19
+ if (!key) {
20
+ throw new Error("FHV_API_KEY is required — set it as an environment variable or pass it to the FhvClient constructor");
21
+ }
22
+ this.apiKey = key;
23
+ }
24
+ async retryFetch(url, init, maxRetries = 2) {
25
+ let lastResponse = null;
26
+ let lastError = null;
27
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
28
+ const controller = new AbortController();
29
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
30
+ try {
31
+ const response = await fetch(url, { ...init, signal: controller.signal });
32
+ clearTimeout(timeoutId);
33
+ if (response.ok || !RETRYABLE.has(response.status)) {
34
+ return response;
35
+ }
36
+ if (attempt < maxRetries) {
37
+ await new Promise((r) => setTimeout(r, BACKOFF_MS[attempt] ?? 1200));
38
+ lastResponse = response;
39
+ continue;
40
+ }
41
+ return response;
42
+ }
43
+ catch (err) {
44
+ clearTimeout(timeoutId);
45
+ lastError = err instanceof Error ? err : new Error(String(err));
46
+ if (attempt < maxRetries) {
47
+ await new Promise((r) => setTimeout(r, BACKOFF_MS[attempt] ?? 1200));
48
+ continue;
49
+ }
50
+ }
51
+ }
52
+ if (lastResponse)
53
+ return lastResponse;
54
+ throw lastError ?? new Error("retryFetch exhausted retries");
55
+ }
56
+ async request(path, params = {}) {
57
+ const url = new URL(path, this.baseUrl);
58
+ for (const [k, v] of Object.entries(params)) {
59
+ if (v !== undefined)
60
+ url.searchParams.set(k, String(v));
61
+ }
62
+ let response;
63
+ try {
64
+ response = await this.retryFetch(url.toString(), {
65
+ method: "GET",
66
+ headers: {
67
+ Authorization: `Bearer ${this.apiKey}`,
68
+ Accept: "application/json",
69
+ "User-Agent": "nycfhv-mcp/0.1.0",
70
+ },
71
+ });
72
+ }
73
+ catch (err) {
74
+ return mcpError("upstream_timeout", `Request to fhv.matchup.dev failed: ${err instanceof Error ? err.message : String(err)}`, "Check network connectivity and retry.");
75
+ }
76
+ let body;
77
+ try {
78
+ body = await response.json();
79
+ }
80
+ catch {
81
+ body = null;
82
+ }
83
+ if (!response.ok) {
84
+ return mapUpstreamError(response.status, body);
85
+ }
86
+ return body;
87
+ }
88
+ /** GET /api/v1/verify/{identifier} */
89
+ verify(identifier, type) {
90
+ const params = type && type !== "auto" ? { type } : {};
91
+ return this.request(`/api/v1/verify/${encodeURIComponent(identifier)}`, params);
92
+ }
93
+ /** GET /api/v1/renewals/upcoming */
94
+ listUpcomingRenewals(params) {
95
+ return this.request(`/api/v1/renewals/upcoming`, params);
96
+ }
97
+ }
@@ -0,0 +1,3 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ export declare function createServer(): Server;
3
+ export declare function checkApiKey(): void;
@@ -0,0 +1,67 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { TOOL_DEFINITION as verifyDef, TOOL_NAME as verifyName, verifyTlcVehicle, } from "./tools/verify-tlc-vehicle.js";
4
+ import { TOOL_DEFINITION as renewalsDef, TOOL_NAME as renewalsName, listUpcomingRenewals, } from "./tools/list-upcoming-renewals.js";
5
+ export function createServer() {
6
+ const server = new Server({ name: "nycfhv", version: "0.1.0" }, { capabilities: { tools: {} } });
7
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
8
+ tools: [verifyDef, renewalsDef],
9
+ }));
10
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
11
+ const { name, arguments: args } = request.params;
12
+ try {
13
+ switch (name) {
14
+ case verifyName: {
15
+ const text = await verifyTlcVehicle((args ?? {}));
16
+ return { content: [{ type: "text", text }] };
17
+ }
18
+ case renewalsName: {
19
+ const text = await listUpcomingRenewals((args ?? {}));
20
+ return { content: [{ type: "text", text }] };
21
+ }
22
+ default:
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text",
27
+ text: JSON.stringify({
28
+ error: {
29
+ code: "unknown_tool",
30
+ message: `Tool "${name}" is not implemented by the nycfhv MCP server.`,
31
+ suggested_next_step: "Check available tools via tools/list.",
32
+ },
33
+ }),
34
+ },
35
+ ],
36
+ isError: true,
37
+ };
38
+ }
39
+ }
40
+ catch (err) {
41
+ console.error(`Unhandled error in tool "${name}":`, err);
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: JSON.stringify({
47
+ error: {
48
+ code: "internal_error",
49
+ message: "An unexpected error occurred handling the tool call.",
50
+ suggested_next_step: "Retry in a few seconds.",
51
+ },
52
+ }),
53
+ },
54
+ ],
55
+ isError: true,
56
+ };
57
+ }
58
+ });
59
+ return server;
60
+ }
61
+ export function checkApiKey() {
62
+ if (!process.env.FHV_API_KEY) {
63
+ console.error("ERROR: FHV_API_KEY environment variable is not set. The server cannot function without it.");
64
+ console.error("Set it with: export FHV_API_KEY=mv_live_your_key_here");
65
+ process.exit(1);
66
+ }
67
+ }
@@ -0,0 +1,49 @@
1
+ export declare const TOOL_NAME = "list_upcoming_renewals";
2
+ export declare const TOOL_DEFINITION: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: "object";
7
+ properties: {
8
+ days: {
9
+ type: string;
10
+ description: string;
11
+ minimum: number;
12
+ maximum: number;
13
+ };
14
+ base_number: {
15
+ type: string;
16
+ description: string;
17
+ };
18
+ wheelchair_accessible: {
19
+ type: string;
20
+ description: string;
21
+ };
22
+ vehicle_year_max: {
23
+ type: string;
24
+ description: string;
25
+ };
26
+ page: {
27
+ type: string;
28
+ description: string;
29
+ minimum: number;
30
+ };
31
+ page_size: {
32
+ type: string;
33
+ description: string;
34
+ minimum: number;
35
+ maximum: number;
36
+ };
37
+ };
38
+ required: string[];
39
+ };
40
+ };
41
+ export interface ListUpcomingRenewalsArgs {
42
+ days: number;
43
+ base_number?: string;
44
+ wheelchair_accessible?: boolean;
45
+ vehicle_year_max?: number;
46
+ page?: number;
47
+ page_size?: number;
48
+ }
49
+ export declare function listUpcomingRenewals(args: ListUpcomingRenewalsArgs): Promise<string>;
@@ -0,0 +1,68 @@
1
+ import { FhvClient } from "../lib/fhv-client.js";
2
+ export const TOOL_NAME = "list_upcoming_renewals";
3
+ export const TOOL_DEFINITION = {
4
+ name: TOOL_NAME,
5
+ description: "List NYC TLC for-hire vehicles whose license expires within the next N days. Optional filters: TLC base (e.g., 'B03404' for Uber), wheelchair-accessible (WAV) only, and maximum vehicle model year (for fleet-aging targeting). Paginated, 50 results per page by default. Costs 5 credits per page. Use this for renewal-window lead lists (license expediters, insurance brokers, training schools, EV/hybrid dealer prospecting).",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ days: {
10
+ type: "integer",
11
+ description: "Renewal window from today, inclusive. 1–180 days. Most use cases pick 14, 30, 60, or 90.",
12
+ minimum: 1,
13
+ maximum: 180,
14
+ },
15
+ base_number: {
16
+ type: "string",
17
+ description: "Optional TLC base number filter (e.g., 'B03404' = Uber USA, 'B00477' = INTA-BORO ACRES). Case-insensitive.",
18
+ },
19
+ wheelchair_accessible: {
20
+ type: "boolean",
21
+ description: "If true, returns only WAV / WAV-pilot vehicles (~8% of fleet). If false, returns only non-WAV vehicles. Omit for both.",
22
+ },
23
+ vehicle_year_max: {
24
+ type: "integer",
25
+ description: "Optional maximum model year. Use to target older fleet for replacement-financing or pre-sales lead-gen.",
26
+ },
27
+ page: {
28
+ type: "integer",
29
+ description: "1-indexed page number. Default 1.",
30
+ minimum: 1,
31
+ },
32
+ page_size: {
33
+ type: "integer",
34
+ description: "Results per page, 10–100. Default 50.",
35
+ minimum: 10,
36
+ maximum: 100,
37
+ },
38
+ },
39
+ required: ["days"],
40
+ },
41
+ };
42
+ export async function listUpcomingRenewals(args) {
43
+ if (typeof args.days !== "number" || args.days < 1 || args.days > 180) {
44
+ return JSON.stringify({
45
+ error: {
46
+ code: "invalid_input",
47
+ message: "`days` must be an integer between 1 and 180.",
48
+ suggested_next_step: "Pick a renewal window like 30, 60, or 90.",
49
+ },
50
+ });
51
+ }
52
+ const client = new FhvClient();
53
+ const result = await client.listUpcomingRenewals(args);
54
+ if ("error" in result) {
55
+ return JSON.stringify(result);
56
+ }
57
+ return JSON.stringify({
58
+ data: result.data,
59
+ meta: {
60
+ source: result.meta.source,
61
+ updated: result.meta.updated,
62
+ page: result.meta.page,
63
+ page_size: result.meta.page_size,
64
+ total_count: result.meta.total_count,
65
+ credits_remaining: result.meta.credits_remaining,
66
+ },
67
+ });
68
+ }
@@ -0,0 +1,25 @@
1
+ export declare const TOOL_NAME = "verify_tlc_vehicle";
2
+ export declare const TOOL_DEFINITION: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: "object";
7
+ properties: {
8
+ identifier: {
9
+ type: string;
10
+ description: string;
11
+ };
12
+ type: {
13
+ type: string;
14
+ description: string;
15
+ enum: string[];
16
+ };
17
+ };
18
+ required: string[];
19
+ };
20
+ };
21
+ export interface VerifyTlcVehicleArgs {
22
+ identifier: string;
23
+ type?: "auto" | "license" | "plate" | "vin";
24
+ }
25
+ export declare function verifyTlcVehicle(args: VerifyTlcVehicleArgs): Promise<string>;
@@ -0,0 +1,45 @@
1
+ import { FhvClient } from "../lib/fhv-client.js";
2
+ export const TOOL_NAME = "verify_tlc_vehicle";
3
+ export const TOOL_DEFINITION = {
4
+ name: TOOL_NAME,
5
+ description: "Verify whether a NYC TLC for-hire vehicle is currently licensed. Accepts a TLC license number (e.g. 'C05015' or '5545596'), DMV plate (e.g. 'T438350C'), or 17-character VIN. Returns active status, expiration date, base affiliation, wheelchair-accessible (WAV) flag, and days_until_expiration. Identifier type is auto-detected by default. Costs 1 credit per call. Use this before authorizing a livery rate, dispatch, or pre-bind insurance check on a NYC for-hire vehicle.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ identifier: {
10
+ type: "string",
11
+ description: "TLC license number (legacy 'C05015' or 7-digit numeric '5545596'), DMV plate, or 17-char VIN.",
12
+ },
13
+ type: {
14
+ type: "string",
15
+ description: "Optional override for identifier auto-detection. Use when the identifier could be ambiguous (e.g., a 7-digit string that could be a license or a plate).",
16
+ enum: ["auto", "license", "plate", "vin"],
17
+ },
18
+ },
19
+ required: ["identifier"],
20
+ },
21
+ };
22
+ export async function verifyTlcVehicle(args) {
23
+ if (!args.identifier || typeof args.identifier !== "string") {
24
+ return JSON.stringify({
25
+ error: {
26
+ code: "invalid_input",
27
+ message: "`identifier` is required and must be a string.",
28
+ suggested_next_step: "Provide a TLC license number, plate, or VIN.",
29
+ },
30
+ });
31
+ }
32
+ const client = new FhvClient();
33
+ const result = await client.verify(args.identifier.trim(), args.type ?? "auto");
34
+ if ("error" in result) {
35
+ return JSON.stringify(result);
36
+ }
37
+ return JSON.stringify({
38
+ data: result.data,
39
+ meta: {
40
+ source: result.meta.source,
41
+ updated: result.meta.updated,
42
+ credits_remaining: result.meta.credits_remaining,
43
+ },
44
+ });
45
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@matchuplabs/nycfhv-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for the NYC FHV Intelligence API. Exposes TLC for-hire vehicle license verification and renewal-window lookups as agent tools backed by fhv.matchup.dev.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "nycfhv-mcp": "dist/index.js"
9
+ },
10
+ "mcpName": "io.github.MatchupLabs/nycfhv",
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/MATCHUP-LABS/nycfhv.git",
15
+ "directory": "mcp-server"
16
+ },
17
+ "homepage": "https://fhv.matchup.dev",
18
+ "keywords": [
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "nyc",
22
+ "tlc",
23
+ "for-hire-vehicle",
24
+ "fhv",
25
+ "license-verification",
26
+ "rideshare",
27
+ "ai-agent"
28
+ ],
29
+ "files": [
30
+ "dist",
31
+ "server.json",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "dev": "tsx src/index.ts",
37
+ "test": "vitest run",
38
+ "prepublishOnly": "npm run build"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "~1.29.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "tsx": "^4.19.0",
46
+ "typescript": "^5.7.0",
47
+ "vitest": "^4.1.2"
48
+ }
49
+ }
package/server.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.MatchupLabs/nycfhv",
4
+ "title": "NYC FHV Intelligence API",
5
+ "description": "Real-time NYC TLC for-hire vehicle license verification and renewal-window lead lookups via MCP.",
6
+ "version": "0.1.0",
7
+ "repository": {
8
+ "url": "https://github.com/MATCHUP-LABS/nycfhv",
9
+ "source": "github"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "registryBaseUrl": "https://registry.npmjs.org",
15
+ "identifier": "@matchuplabs/nycfhv-mcp",
16
+ "version": "0.1.0",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "environmentVariables": [
21
+ {
22
+ "name": "FHV_API_KEY",
23
+ "description": "API key from matchuplabs.com (product='fhv-intelligence')",
24
+ "isRequired": true,
25
+ "isSecret": true
26
+ },
27
+ {
28
+ "name": "FHV_BASE_URL",
29
+ "description": "API base URL (default: https://fhv.matchup.dev)",
30
+ "isRequired": false,
31
+ "isSecret": false
32
+ }
33
+ ]
34
+ }
35
+ ]
36
+ }