Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
1184
mcp/package-lock.json
generated
Normal file
1184
mcp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
41
mcp/package.json
Normal file
41
mcp/package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "@performancewest/mcp-server",
|
||||
"version": "0.1.0",
|
||||
"description": "MCP server for Performance West — business formation in all 50 US states, compliance consulting, contractor classification, CCPA audits, and more. AI agents can walk users through the formation questionnaire and place orders.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"performancewest-mcp": "dist/index.js"
|
||||
},
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"zod": "^3.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"business-formation",
|
||||
"llc",
|
||||
"incorporation",
|
||||
"compliance",
|
||||
"performancewest"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/performancewest/mcp-server.git"
|
||||
},
|
||||
"homepage": "https://performancewest.net"
|
||||
}
|
||||
72
mcp/src/client.ts
Normal file
72
mcp/src/client.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* HTTP client for the Performance West API.
|
||||
* All MCP tools delegate to the API — this keeps the MCP server
|
||||
* stateless and avoids duplicating business logic.
|
||||
*/
|
||||
|
||||
const DEFAULT_BASE_URL = "https://api.performancewest.net";
|
||||
|
||||
const baseUrl = process.env.PW_API_URL ?? DEFAULT_BASE_URL;
|
||||
const apiKey = process.env.PW_API_KEY ?? "";
|
||||
|
||||
function buildUrl(
|
||||
path: string,
|
||||
params?: Record<string, string | number | undefined>,
|
||||
): string {
|
||||
const url = new URL(path, baseUrl);
|
||||
if (params) {
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (v !== undefined && v !== "") {
|
||||
url.searchParams.set(k, String(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = unknown> {
|
||||
ok: boolean;
|
||||
status: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export async function apiGet<T = unknown>(
|
||||
path: string,
|
||||
params?: Record<string, string | number | undefined>,
|
||||
): Promise<ApiResponse<T>> {
|
||||
const url = buildUrl(path, params);
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
"User-Agent": "performancewest-mcp-server/0.1.0",
|
||||
};
|
||||
if (apiKey) {
|
||||
headers["Authorization"] = `Bearer ${apiKey}`;
|
||||
}
|
||||
|
||||
const res = await fetch(url, { headers });
|
||||
const data = (await res.json()) as T;
|
||||
return { ok: res.ok, status: res.status, data };
|
||||
}
|
||||
|
||||
export async function apiPost<T = unknown>(
|
||||
path: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<ApiResponse<T>> {
|
||||
const url = buildUrl(path);
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "performancewest-mcp-server/0.1.0",
|
||||
};
|
||||
if (apiKey) {
|
||||
headers["Authorization"] = `Bearer ${apiKey}`;
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = (await res.json()) as T;
|
||||
return { ok: res.ok, status: res.status, data };
|
||||
}
|
||||
45
mcp/src/index.ts
Normal file
45
mcp/src/index.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Performance West MCP Server
|
||||
*
|
||||
* Exposes business formation (all 50 US states) and compliance consulting
|
||||
* services as MCP tools for AI assistants (Claude, ChatGPT, Cursor, etc.).
|
||||
*
|
||||
* Tools:
|
||||
* formation_guide — Interactive questionnaire → entity + state recommendation
|
||||
* search_name — Check business name availability
|
||||
* create_formation_order — Place a formation order ($179 basic / $399 complete)
|
||||
* check_order_status — Check order status by order number
|
||||
* get_states — List all 51 jurisdictions with fees
|
||||
* get_state_info — Detailed info for a specific state
|
||||
* get_pricing — Calculate total formation cost
|
||||
* list_services — List all 20+ compliance services
|
||||
* get_service_info — Detailed service info by slug
|
||||
* validate_discount — Validate a discount/referral code
|
||||
*
|
||||
* Transport: stdio (spawned by MCP client)
|
||||
* Data source: Performance West API (HTTP)
|
||||
* Config: PW_API_URL, PW_API_KEY env vars
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
|
||||
import { registerFormationTools } from "./tools/formation.js";
|
||||
import { registerStateTools } from "./tools/states.js";
|
||||
import { registerServiceTools } from "./tools/services.js";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "performancewest",
|
||||
version: "0.1.0",
|
||||
});
|
||||
|
||||
// Register all tools
|
||||
registerFormationTools(server);
|
||||
registerStateTools(server);
|
||||
registerServiceTools(server);
|
||||
|
||||
// Connect via stdio
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
381
mcp/src/tools/formation.ts
Normal file
381
mcp/src/tools/formation.ts
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
/**
|
||||
* Formation tools — the core business formation workflow.
|
||||
*
|
||||
* Tools:
|
||||
* formation_guide — Interactive questionnaire → entity + state recommendation
|
||||
* search_name — Check business name availability in a state
|
||||
* create_formation_order — Place a complete formation order
|
||||
* check_order_status — Check status of an existing order
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { apiGet, apiPost } from "../client.js";
|
||||
|
||||
// Recommendation logic (mirrors the website formation guide)
|
||||
interface Recommendation {
|
||||
entity_type: string;
|
||||
entity_reason: string;
|
||||
state: string;
|
||||
state_name: string;
|
||||
state_reason: string;
|
||||
cost_basic: number;
|
||||
cost_complete: number;
|
||||
annual_cost: number;
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
const STATE_NAMES: Record<string, string> = {
|
||||
AL:"Alabama",AK:"Alaska",AZ:"Arizona",AR:"Arkansas",CA:"California",CO:"Colorado",
|
||||
CT:"Connecticut",DE:"Delaware",FL:"Florida",GA:"Georgia",HI:"Hawaii",ID:"Idaho",
|
||||
IL:"Illinois",IN:"Indiana",IA:"Iowa",KS:"Kansas",KY:"Kentucky",LA:"Louisiana",
|
||||
ME:"Maine",MD:"Maryland",MA:"Massachusetts",MI:"Michigan",MN:"Minnesota",
|
||||
MS:"Mississippi",MO:"Missouri",MT:"Montana",NE:"Nebraska",NV:"Nevada",
|
||||
NH:"New Hampshire",NJ:"New Jersey",NM:"New Mexico",NY:"New York",NC:"North Carolina",
|
||||
ND:"North Dakota",OH:"Ohio",OK:"Oklahoma",OR:"Oregon",PA:"Pennsylvania",
|
||||
RI:"Rhode Island",SC:"South Carolina",SD:"South Dakota",TN:"Tennessee",TX:"Texas",
|
||||
UT:"Utah",VT:"Vermont",VA:"Virginia",WA:"Washington",WV:"West Virginia",
|
||||
WI:"Wisconsin",WY:"Wyoming",DC:"District of Columbia",
|
||||
};
|
||||
|
||||
const STATE_FEES: Record<string, { llc: number; corp: number; annual: number }> = {
|
||||
WY:{llc:100,corp:100,annual:60},DE:{llc:110,corp:89,annual:300},NV:{llc:425,corp:725,annual:350},
|
||||
TX:{llc:300,corp:300,annual:0},FL:{llc:125,corp:70,annual:139},CA:{llc:70,corp:100,annual:800},
|
||||
CO:{llc:50,corp:50,annual:25},MT:{llc:35,corp:70,annual:20},NM:{llc:50,corp:125,annual:0},
|
||||
OH:{llc:99,corp:99,annual:0},UT:{llc:59,corp:59,annual:18},MA:{llc:500,corp:275,annual:500},
|
||||
NY:{llc:200,corp:125,annual:9},SD:{llc:150,corp:150,annual:55},TN:{llc:300,corp:100,annual:300},
|
||||
};
|
||||
|
||||
function recommend(answers: {
|
||||
situation: string;
|
||||
tax_id: string;
|
||||
owners: string;
|
||||
revenue: string;
|
||||
operations: string;
|
||||
home_state: string;
|
||||
priorities: string[];
|
||||
}): Recommendation {
|
||||
let entity = "LLC";
|
||||
let entityReason = "";
|
||||
let state = "WY";
|
||||
let stateReason = "";
|
||||
const notes: string[] = [];
|
||||
const p = answers.priorities || [];
|
||||
|
||||
// Entity type
|
||||
if (answers.owners === "investors") {
|
||||
entity = "C-Corporation";
|
||||
entityReason = "C-Corporations are standard for raising investment capital. Investors expect the familiar stock structure.";
|
||||
} else if (answers.revenue === "over_500k" && answers.owners === "single") {
|
||||
entity = "LLC with S-Corp election";
|
||||
entityReason = "At your revenue level, an S-Corp election saves significant self-employment taxes while keeping LLC flexibility.";
|
||||
} else if (answers.revenue === "100k_500k") {
|
||||
entity = "LLC (consider S-Corp later)";
|
||||
entityReason = "An LLC provides liability protection and pass-through taxation. Consider S-Corp election as revenue grows.";
|
||||
} else {
|
||||
entity = "LLC";
|
||||
entityReason = "An LLC provides personal liability protection, pass-through taxation, and flexible management. Best choice for most small businesses.";
|
||||
}
|
||||
|
||||
// State
|
||||
if (answers.owners === "investors") {
|
||||
state = "DE";
|
||||
stateReason = "Delaware's Court of Chancery and established corporate law make it standard for investor-backed companies.";
|
||||
} else if (p.includes("privacy") && p.includes("no_income_tax")) {
|
||||
state = "WY";
|
||||
stateReason = "Wyoming: no income tax, strongest privacy (no public member disclosure), excellent asset protection, low cost.";
|
||||
} else if (p.includes("low_cost") && answers.home_state) {
|
||||
const hf = STATE_FEES[answers.home_state];
|
||||
if (hf && hf.llc >= 175) {
|
||||
state = "WY";
|
||||
stateReason = `Your home state (${STATE_NAMES[answers.home_state]}) charges $${hf.llc} for LLC formation. Wyoming ($100) + foreign qualification is more cost-effective plus you get privacy and no income tax.`;
|
||||
} else {
|
||||
state = answers.home_state;
|
||||
stateReason = `${STATE_NAMES[answers.home_state]} has reasonable formation costs. Forming in your home state avoids foreign qualification fees.`;
|
||||
}
|
||||
} else if (answers.home_state === "TX") {
|
||||
state = p.includes("privacy") ? "WY" : "TX";
|
||||
stateReason = state === "WY"
|
||||
? "Wyoming adds privacy protections over Texas. Foreign qualification in TX is a one-time $750 fee."
|
||||
: "Texas: no income tax, large economy, fast processing. No franchise tax unless revenue exceeds $2.47M.";
|
||||
} else if (answers.home_state === "FL") {
|
||||
state = p.includes("privacy") ? "WY" : "FL";
|
||||
stateReason = state === "WY"
|
||||
? "Wyoming adds privacy over Florida. FL foreign qualification is only $125 one-time."
|
||||
: "Florida: no personal income tax. Corporate income tax only applies to C-Corps, not LLCs/S-Corps.";
|
||||
} else if (answers.operations === "single_state" && answers.home_state) {
|
||||
state = answers.home_state;
|
||||
stateReason = `Operating in one state — forming in your home state (${STATE_NAMES[answers.home_state]}) avoids foreign qualification.`;
|
||||
} else {
|
||||
state = "WY";
|
||||
stateReason = "Wyoming: no income tax, no franchise tax, strong privacy, excellent asset protection, fast processing, low cost.";
|
||||
}
|
||||
|
||||
const sf = STATE_FEES[state] || { llc: 100, corp: 100, annual: 60 };
|
||||
const isLLC = !entity.includes("C-Corp");
|
||||
const stateFee = isLLC ? sf.llc : sf.corp;
|
||||
|
||||
return {
|
||||
entity_type: entity,
|
||||
entity_reason: entityReason,
|
||||
state,
|
||||
state_name: STATE_NAMES[state] || state,
|
||||
state_reason: stateReason,
|
||||
cost_basic: stateFee + 179,
|
||||
cost_complete: stateFee + 399,
|
||||
annual_cost: sf.annual,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function registerFormationTools(server: McpServer): void {
|
||||
|
||||
// ── Formation Guide ──────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"formation_guide",
|
||||
{
|
||||
title: "Business Formation Guide",
|
||||
description:
|
||||
"Interactive questionnaire that recommends the best entity type (LLC, Corporation, S-Corp) " +
|
||||
"and formation state based on the user's situation. Provide answers to get a personalized recommendation. " +
|
||||
"Performance West offers business formation in all 50 US states from $179 (basic) or $399 (complete).",
|
||||
inputSchema: {
|
||||
situation: z.enum(["new_business", "restructure", "holding", "ecommerce"])
|
||||
.describe("Business situation: new_business, restructure, holding, or ecommerce"),
|
||||
tax_id: z.enum(["us_ssn", "has_itin", "needs_itin"])
|
||||
.describe("Tax ID status: us_ssn (US citizen with SSN), has_itin (foreign with ITIN), needs_itin (foreign without ITIN)"),
|
||||
owners: z.enum(["single", "multi", "investors"])
|
||||
.describe("Number of owners: single, multi (multiple partners), or investors (raising VC/angel)"),
|
||||
revenue: z.enum(["under_100k", "100k_500k", "over_500k"])
|
||||
.describe("Expected annual revenue range"),
|
||||
operations: z.enum(["single_state", "multi_state", "online_only"])
|
||||
.describe("Where the business will operate"),
|
||||
home_state: z.string().length(2)
|
||||
.describe("Two-letter state code where the owner lives (e.g., TX, CA, FL)"),
|
||||
priorities: z.array(z.enum(["low_cost", "privacy", "no_income_tax", "asset_protection", "corp_law", "simplicity"]))
|
||||
.describe("What matters most (select all that apply)"),
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
if (args.tax_id === "needs_itin") {
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
recommendation: "ITIN Required First",
|
||||
message: "To form a US business and obtain an EIN, you need either an SSN or ITIN. " +
|
||||
"Visit the IRS Acceptance Agent Program to find an authorized agent in your country: " +
|
||||
"https://www.irs.gov/individuals/international-taxpayers/acceptance-agent-program",
|
||||
next_step: "Once you have your ITIN, come back to form your business.",
|
||||
performancewest_url: "https://performancewest.net/tools/formation-guide",
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
const rec = recommend(args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
recommendation: {
|
||||
entity_type: rec.entity_type,
|
||||
entity_reason: rec.entity_reason,
|
||||
formation_state: rec.state,
|
||||
formation_state_name: rec.state_name,
|
||||
state_reason: rec.state_reason,
|
||||
estimated_cost_basic: `$${rec.cost_basic}`,
|
||||
estimated_cost_complete: `$${rec.cost_complete}`,
|
||||
annual_ongoing_cost: `$${rec.annual_cost}/year`,
|
||||
notes: rec.notes,
|
||||
},
|
||||
pricing: {
|
||||
basic: "$179 — Formation filing only",
|
||||
complete: "$399 — Formation + EIN ($49) + Operating Agreement ($99) + 1st year Registered Agent ($99, $49 WY)",
|
||||
},
|
||||
next_steps: [
|
||||
`Search for name availability in ${rec.state_name} using the search_name tool`,
|
||||
"Place your order using the create_formation_order tool",
|
||||
"Or visit https://performancewest.net/order/formation",
|
||||
],
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Name Search ──────────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"search_name",
|
||||
{
|
||||
title: "Search Business Name Availability",
|
||||
description:
|
||||
"Check if a business name is available in a specific US state. " +
|
||||
"Returns availability status and similar existing names.",
|
||||
inputSchema: {
|
||||
state: z.string().length(2).describe("Two-letter state code (e.g., WY, DE, TX)"),
|
||||
name: z.string().min(2).describe("Desired business name (e.g., 'Acme Holdings LLC')"),
|
||||
},
|
||||
},
|
||||
async ({ state, name }) => {
|
||||
const res = await apiGet<{ state_code: string; name: string; available: boolean | null; message: string; similar_names: string[] }>(
|
||||
`/api/v1/states/${state.toUpperCase()}/name-search`,
|
||||
{ name },
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return { content: [{ type: "text" as const, text: `Name search failed: ${JSON.stringify(res.data)}` }], isError: true };
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
state: res.data.state_code,
|
||||
name: res.data.name,
|
||||
available: res.data.available,
|
||||
message: res.data.message,
|
||||
similar_names: res.data.similar_names,
|
||||
next_step: res.data.available
|
||||
? "Name is available! Use create_formation_order to place your order."
|
||||
: "Name is taken. Try a different name.",
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Create Formation Order ───────────────────────────────────────
|
||||
server.registerTool(
|
||||
"create_formation_order",
|
||||
{
|
||||
title: "Create Business Formation Order",
|
||||
description:
|
||||
"Place a complete business formation order. Creates an LLC, Corporation, or S-Corp in any US state. " +
|
||||
"Basic tier: $179 (formation only). Complete tier: $399 (includes EIN + Operating Agreement + 1st year Registered Agent). " +
|
||||
"State filing fees are additional and vary by state.",
|
||||
inputSchema: {
|
||||
customer_name: z.string().min(2).describe("Full name of the person placing the order"),
|
||||
customer_email: z.string().email().describe("Email address for order confirmations and document delivery"),
|
||||
customer_phone: z.string().optional().describe("Phone number (optional)"),
|
||||
state_code: z.string().length(2).describe("Two-letter state code for formation (e.g., WY, DE, TX)"),
|
||||
entity_type: z.enum(["llc", "corporation", "s_corp"]).describe("Entity type to form"),
|
||||
entity_name: z.string().min(3).describe("Desired entity name (e.g., 'Acme Holdings LLC')"),
|
||||
entity_name_alt: z.string().optional().describe("Backup name if primary is unavailable"),
|
||||
management_type: z.enum(["member_managed", "manager_managed"]).optional()
|
||||
.describe("LLC management type (default: member_managed)"),
|
||||
principal_address: z.string().describe("Principal business address (street)"),
|
||||
principal_city: z.string().describe("City"),
|
||||
principal_state: z.string().length(2).describe("State code"),
|
||||
principal_zip: z.string().describe("ZIP code"),
|
||||
members: z.array(z.object({
|
||||
name: z.string(),
|
||||
address: z.string(),
|
||||
city: z.string(),
|
||||
state: z.string(),
|
||||
zip: z.string(),
|
||||
title: z.string().optional(),
|
||||
ownership_pct: z.number().optional(),
|
||||
})).min(1).describe("At least one member/officer with name, address, and ownership %"),
|
||||
tier: z.enum(["basic", "complete"]).default("basic")
|
||||
.describe("Pricing tier: basic ($179) or complete ($399 with EIN + OA + RA)"),
|
||||
discount_code: z.string().optional().describe("Discount or referral code (optional)"),
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const isComplete = args.tier === "complete";
|
||||
const serviceFee = isComplete ? 39900 : 17900;
|
||||
const sf = STATE_FEES[args.state_code.toUpperCase()] || { llc: 100, corp: 100 };
|
||||
const stateFee = (args.entity_type === "corporation" ? sf.corp : sf.llc) * 100;
|
||||
|
||||
const payload = {
|
||||
customer_name: args.customer_name,
|
||||
customer_email: args.customer_email,
|
||||
customer_phone: args.customer_phone,
|
||||
state_code: args.state_code.toUpperCase(),
|
||||
entity_type: args.entity_type,
|
||||
entity_name: args.entity_name,
|
||||
entity_name_alt: args.entity_name_alt,
|
||||
management_type: args.management_type || "member_managed",
|
||||
principal_address: args.principal_address,
|
||||
principal_city: args.principal_city,
|
||||
principal_state: args.principal_state,
|
||||
principal_zip: args.principal_zip,
|
||||
members: args.members,
|
||||
include_ra_service: isComplete,
|
||||
include_ein: isComplete,
|
||||
include_operating_agreement: isComplete,
|
||||
expedited: false,
|
||||
state_fee_cents: stateFee,
|
||||
service_fee_cents: serviceFee,
|
||||
expedited_fee_cents: 0,
|
||||
total_cents: stateFee + serviceFee,
|
||||
discount_code: args.discount_code,
|
||||
tier: args.tier,
|
||||
};
|
||||
|
||||
const res = await apiPost<{ success: boolean; order_number?: string; message?: string; error?: string; discount_applied?: unknown }>(
|
||||
"/api/v1/formations",
|
||||
payload,
|
||||
);
|
||||
|
||||
if (!res.ok || !res.data.success) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Order failed: ${res.data.error || JSON.stringify(res.data)}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
order_number: res.data.order_number,
|
||||
message: res.data.message,
|
||||
discount_applied: res.data.discount_applied,
|
||||
next_steps: [
|
||||
`Order ${res.data.order_number} has been placed.`,
|
||||
`An email will be sent to ${args.customer_email} with a link to set up a portal account and track progress.`,
|
||||
"Formation typically takes 1-5 business days depending on the state.",
|
||||
`Use check_order_status with order number "${res.data.order_number}" to track progress.`,
|
||||
],
|
||||
portal_url: "https://performancewest.net/portal",
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Check Order Status ───────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"check_order_status",
|
||||
{
|
||||
title: "Check Formation Order Status",
|
||||
description: "Check the current status of a business formation order by order number.",
|
||||
inputSchema: {
|
||||
order_number: z.string().describe("Order number (e.g., PW-2026-A1B2C3)"),
|
||||
},
|
||||
},
|
||||
async ({ order_number }) => {
|
||||
const res = await apiGet<{ order?: Record<string, unknown>; error?: string }>(
|
||||
`/api/v1/formations/${encodeURIComponent(order_number)}`,
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Order not found: ${res.data.error || "Unknown error"}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(res.data.order, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
145
mcp/src/tools/services.ts
Normal file
145
mcp/src/tools/services.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* Compliance services tools — list and describe our services.
|
||||
*
|
||||
* Tools:
|
||||
* list_services — List all 20+ compliance services with pricing
|
||||
* get_service_info — Detailed info about a specific service
|
||||
* validate_discount — Check if a discount code is valid
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { apiGet } from "../client.js";
|
||||
|
||||
// Service catalog (embedded so MCP server works without API for basic info)
|
||||
const SERVICES = [
|
||||
{ slug: "flsa-audit", category: "Employment", name: "FLSA / Wage & Hour Audit", price: "$1,499", turnaround: "5-7 days" },
|
||||
{ slug: "contractor-classification", category: "Employment", name: "Contractor Classification Review", price: "$499/worker", turnaround: "3-5 days" },
|
||||
{ slug: "handbook-review", category: "Employment", name: "Employee Handbook Review", price: "$999", turnaround: "5-7 days" },
|
||||
{ slug: "policy-development", category: "Employment", name: "Workplace Policy Development", price: "Custom quote", turnaround: "2-3 weeks" },
|
||||
{ slug: "ccpa-audit", category: "Privacy", name: "CCPA/CPRA Compliance Audit", price: "$2,499", turnaround: "7-10 days" },
|
||||
{ slug: "privacy-policy", category: "Privacy", name: "Privacy Policy Generation & Review", price: "$499", turnaround: "5-7 days" },
|
||||
{ slug: "data-mapping", category: "Privacy", name: "Data Mapping & Inventory", price: "Custom quote", turnaround: "1-3 weeks" },
|
||||
{ slug: "breach-response", category: "Privacy", name: "Breach Response Planning", price: "$1,999", turnaround: "2-3 weeks" },
|
||||
{ slug: "consent-audit", category: "TCPA", name: "SMS/Call Consent Audit", price: "$1,299", turnaround: "5-7 days" },
|
||||
{ slug: "dnc-compliance", category: "TCPA", name: "DNC List Compliance Review", price: "$799", turnaround: "3-5 days" },
|
||||
{ slug: "campaign-review", category: "TCPA", name: "Marketing Campaign Compliance Review", price: "$599", turnaround: "2-3 days" },
|
||||
{ slug: "fcc-499a", category: "Telecom", name: "FCC Form 499-A Filing Support", price: "$799", turnaround: "5-7 days" },
|
||||
{ slug: "stir-shaken", category: "Telecom", name: "STIR/SHAKEN Implementation", price: "Custom quote", turnaround: "2-4 weeks" },
|
||||
{ slug: "ipes-isp", category: "Telecom", name: "IPES & ISP Registrations", price: "$1,299", turnaround: "2-3 weeks" },
|
||||
{ slug: "database-management", category: "Telecom", name: "Telecom Database Management", price: "$499/quarter", turnaround: "Ongoing" },
|
||||
{ slug: "state-puc", category: "Telecom", name: "State PUC/PSC Filings", price: "$399/state", turnaround: "2-8 weeks" },
|
||||
{ slug: "formation", category: "Corporate", name: "Business Formation (LLC/Corp)", price: "$179 basic / $399 complete", turnaround: "3-5 days" },
|
||||
{ slug: "state-registration", category: "Corporate", name: "State Registration & Foreign Qualification", price: "$249/state", turnaround: "1-4 weeks" },
|
||||
{ slug: "annual-reports", category: "Corporate", name: "Annual Report Filing", price: "$99/state/year", turnaround: "Filed before deadline" },
|
||||
{ slug: "registered-agent", category: "Corporate", name: "Registered Agent Service", price: "$99/state/year ($49 WY)", turnaround: "Same-day forwarding" },
|
||||
];
|
||||
|
||||
export function registerServiceTools(server: McpServer): void {
|
||||
|
||||
// ── List Services ────────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"list_services",
|
||||
{
|
||||
title: "List All Compliance Services",
|
||||
description:
|
||||
"List all Performance West compliance services with pricing, turnaround times, and categories. " +
|
||||
"Services span: Employment (FLSA, contractor classification, handbooks), Data Privacy (CCPA, privacy policies), " +
|
||||
"TCPA (SMS consent, DNC), Telecom (FCC 499A, STIR/SHAKEN), and Corporate (formation, registrations, RA).",
|
||||
inputSchema: {
|
||||
category: z.string().optional()
|
||||
.describe("Filter by category: Employment, Privacy, TCPA, Telecom, Corporate (optional)"),
|
||||
},
|
||||
},
|
||||
async ({ category }) => {
|
||||
let filtered = SERVICES;
|
||||
if (category) {
|
||||
filtered = SERVICES.filter(s => s.category.toLowerCase() === category.toLowerCase());
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
count: filtered.length,
|
||||
services: filtered,
|
||||
website: "https://performancewest.net/services",
|
||||
disclaimer: "Performance West provides compliance consulting, not legal advice.",
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Get Service Info ─────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"get_service_info",
|
||||
{
|
||||
title: "Get Service Details",
|
||||
description: "Get detailed information about a specific compliance service by slug.",
|
||||
inputSchema: {
|
||||
slug: z.string().describe("Service slug (e.g., 'flsa-audit', 'ccpa-audit', 'formation')"),
|
||||
},
|
||||
},
|
||||
async ({ slug }) => {
|
||||
const service = SERVICES.find(s => s.slug === slug.toLowerCase());
|
||||
if (!service) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: `Service '${slug}' not found. Use list_services to see available services.`,
|
||||
}],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
...service,
|
||||
url: `https://performancewest.net/services/${service.category.toLowerCase()}/${service.slug}`,
|
||||
order_url: service.slug === "formation"
|
||||
? "https://performancewest.net/order/formation"
|
||||
: "https://performancewest.net/contact",
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Validate Discount Code ───────────────────────────────────────
|
||||
server.registerTool(
|
||||
"validate_discount",
|
||||
{
|
||||
title: "Validate Discount Code",
|
||||
description: "Check if a discount or referral code is valid and what discount it provides.",
|
||||
inputSchema: {
|
||||
code: z.string().min(2).describe("Discount or referral code"),
|
||||
service: z.string().optional().describe("Service slug to check scope (optional)"),
|
||||
amount: z.number().optional().describe("Amount in cents to calculate discount against (optional)"),
|
||||
},
|
||||
},
|
||||
async ({ code, service, amount }) => {
|
||||
const res = await apiGet<{
|
||||
valid: boolean;
|
||||
code?: string;
|
||||
discount_type?: string;
|
||||
discount_value?: number;
|
||||
discount_cents?: number;
|
||||
description?: string;
|
||||
error?: string;
|
||||
}>(
|
||||
`/api/v1/discount/${encodeURIComponent(code)}`,
|
||||
{ service, amount: amount ? String(amount) : undefined },
|
||||
);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(res.data, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
158
mcp/src/tools/states.ts
Normal file
158
mcp/src/tools/states.ts
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* State information tools — fees, tax info, requirements.
|
||||
*
|
||||
* Tools:
|
||||
* get_states — List all 51 jurisdictions with fees
|
||||
* get_state_info — Detailed info for a specific state
|
||||
* get_pricing — Calculate total price for a specific formation
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { apiGet } from "../client.js";
|
||||
|
||||
export function registerStateTools(server: McpServer): void {
|
||||
|
||||
// ── Get All States ───────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"get_states",
|
||||
{
|
||||
title: "List All US States for Formation",
|
||||
description:
|
||||
"Get a list of all 50 US states plus DC with formation fees, annual fees, " +
|
||||
"tax information, and special requirements. Useful for comparing states.",
|
||||
inputSchema: {},
|
||||
},
|
||||
async () => {
|
||||
const res = await apiGet<{ states: Record<string, unknown>[] }>("/api/v1/states");
|
||||
|
||||
if (!res.ok) {
|
||||
return { content: [{ type: "text" as const, text: "Failed to fetch state data" }], isError: true };
|
||||
}
|
||||
|
||||
// Summarize for readability
|
||||
const summary = (res.data.states || []).map((s: any) => ({
|
||||
code: s.state_code,
|
||||
name: s.state_name,
|
||||
llc_fee: `$${(s.llc_formation_fee / 100).toFixed(0)}`,
|
||||
corp_fee: `$${(s.corp_formation_fee / 100).toFixed(0)}`,
|
||||
annual: s.llc_annual_fee ? `$${(s.llc_annual_fee / 100).toFixed(0)}/${s.llc_annual_period || "yr"}` : "None",
|
||||
notes: s.notes || "",
|
||||
}));
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
count: summary.length,
|
||||
states: summary,
|
||||
pricing_note: "State fees are pass-through. Our service fee: $179 (basic) or $399 (complete).",
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Get State Info ───────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"get_state_info",
|
||||
{
|
||||
title: "Get State Formation Details",
|
||||
description: "Get detailed formation information for a specific US state including fees, tax structure, processing time, and special requirements.",
|
||||
inputSchema: {
|
||||
state: z.string().length(2).describe("Two-letter state code (e.g., WY, DE, TX, CA)"),
|
||||
},
|
||||
},
|
||||
async ({ state }) => {
|
||||
const res = await apiGet<{ states: Record<string, unknown>[] }>(
|
||||
"/api/v1/states",
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return { content: [{ type: "text" as const, text: "Failed to fetch state data" }], isError: true };
|
||||
}
|
||||
|
||||
const stateData = (res.data.states || []).find((s: any) => s.state_code === state.toUpperCase());
|
||||
if (!stateData) {
|
||||
return { content: [{ type: "text" as const, text: `State ${state} not found` }], isError: true };
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(stateData, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ── Get Pricing ──────────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"get_pricing",
|
||||
{
|
||||
title: "Calculate Formation Pricing",
|
||||
description: "Calculate the total cost for a business formation including state fees, service fee, and optional add-ons.",
|
||||
inputSchema: {
|
||||
state: z.string().length(2).describe("Two-letter state code"),
|
||||
entity_type: z.enum(["llc", "corporation", "s_corp"]).describe("Entity type"),
|
||||
tier: z.enum(["basic", "complete"]).default("basic").describe("basic ($179) or complete ($399)"),
|
||||
expedited: z.boolean().default(false).describe("Add expedited processing"),
|
||||
discount_code: z.string().optional().describe("Discount code to apply"),
|
||||
},
|
||||
},
|
||||
async ({ state, entity_type, tier, expedited, discount_code }) => {
|
||||
const res = await apiGet<{ states: Record<string, unknown>[] }>("/api/v1/states");
|
||||
if (!res.ok) {
|
||||
return { content: [{ type: "text" as const, text: "Failed to fetch pricing data" }], isError: true };
|
||||
}
|
||||
|
||||
const sd = (res.data.states || []).find((s: any) => s.state_code === state.toUpperCase()) as any;
|
||||
if (!sd) {
|
||||
return { content: [{ type: "text" as const, text: `State ${state} not found` }], isError: true };
|
||||
}
|
||||
|
||||
const stateFee = entity_type === "corporation" ? sd.corp_formation_fee : sd.llc_formation_fee;
|
||||
const serviceFee = tier === "complete" ? 39900 : 17900;
|
||||
const expFee = expedited && sd.expedited_fee ? sd.expedited_fee : 0;
|
||||
let discountCents = 0;
|
||||
|
||||
if (discount_code) {
|
||||
const dcRes = await apiGet<{ valid: boolean; discount_cents?: number; description?: string }>(
|
||||
`/api/v1/discount/${encodeURIComponent(discount_code)}`,
|
||||
{ service: "formation", amount: String(serviceFee) },
|
||||
);
|
||||
if (dcRes.ok && dcRes.data.valid) {
|
||||
discountCents = dcRes.data.discount_cents || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const total = stateFee + serviceFee + expFee - discountCents;
|
||||
const isComplete = tier === "complete";
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
state: sd.state_name,
|
||||
entity_type,
|
||||
tier,
|
||||
breakdown: {
|
||||
state_filing_fee: `$${(stateFee / 100).toFixed(2)}`,
|
||||
service_fee: `$${(serviceFee / 100).toFixed(2)}` + (isComplete ? " (includes EIN + Operating Agreement + 1st yr RA)" : " (formation only)"),
|
||||
expedited_fee: expedited ? `$${(expFee / 100).toFixed(2)}` : "Not selected",
|
||||
discount: discountCents > 0 ? `-$${(discountCents / 100).toFixed(2)}` : "None",
|
||||
total: `$${(total / 100).toFixed(2)}`,
|
||||
},
|
||||
ongoing_annual: `$${(sd.llc_annual_fee ? sd.llc_annual_fee / 100 : 0).toFixed(0)}/year`,
|
||||
processing_time: `${sd.typical_processing_days || "3-7"} business days`,
|
||||
special_requirements: [
|
||||
sd.publication_required ? `Publication required (~$${(sd.publication_est_cost || 0) / 100})` : null,
|
||||
sd.franchise_tax_required ? `Franchise tax: ${sd.franchise_tax_notes || "applies"}` : null,
|
||||
sd.business_license_required ? `State business license: $${(sd.business_license_fee || 0) / 100}` : null,
|
||||
].filter(Boolean),
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
18
mcp/tsconfig.json
Normal file
18
mcp/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue