lovieme-mcp 0.0.0-stage → 0.1.1

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 CHANGED
@@ -1,3 +1,103 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # 🤖 LovieMe MCP Server (`lovieme-mcp`)
2
+
3
+ The official **Model Context Protocol (MCP)** server for [LovieMe](https://www.lovie.me) — enabling AI coding agents (Cursor, Claude Code, Antigravity, Windsurf, Devin) to autonomously check availability, claim subdomains, configure Anycast DNS records, and apply 1-click cloud hosting presets directly from chat.
4
+
5
+ ---
6
+
7
+ ## ⚡ Quickstart
8
+
9
+ ### Run with `npx` (Zero Installation)
10
+ ```bash
11
+ npx lovieme-mcp
12
+ ```
13
+
14
+ ---
15
+
16
+ ## 🔑 Authentication
17
+
18
+ Set your LovieMe API key as an environment variable:
19
+
20
+ ```bash
21
+ export LOVIE_API_KEY="lovie_live_your_api_key_here"
22
+ ```
23
+
24
+ > **Tip:** You can generate a dedicated AI Agent API token in your [LovieMe Dashboard](https://www.lovie.me/dashboard/api-keys) with granular least-privilege permissions (`subdomains:read/write`, `dns:read/write`, `profile:read`).
25
+
26
+ ---
27
+
28
+ ## 🛠️ Configuration for AI Agents
29
+
30
+ ### 1. Cursor (`.cursor/mcp.json`)
31
+ Add this to your project or global Cursor MCP configuration:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "lovieme": {
37
+ "command": "npx",
38
+ "args": ["-y", "lovieme-mcp"],
39
+ "env": {
40
+ "LOVIE_API_KEY": "lovie_live_your_key_here"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### 2. Claude Code (`claude.json`)
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "lovieme": {
52
+ "command": "npx",
53
+ "args": ["-y", "lovieme-mcp"],
54
+ "env": {
55
+ "LOVIE_API_KEY": "lovie_live_your_key_here"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### 3. Claude Desktop App (`claude_desktop_config.json`)
63
+ * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
64
+ * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "lovieme": {
70
+ "command": "npx",
71
+ "args": ["-y", "lovieme-mcp"],
72
+ "env": {
73
+ "LOVIE_API_KEY": "lovie_live_your_key_here"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 📋 Available MCP Tools
83
+
84
+ | Tool | Description | Required Scopes |
85
+ | :--- | :--- | :---: |
86
+ | `lovie_check_subdomain` | Check if a `*.lovie.me` prefix is available | Public (None) |
87
+ | `lovie_claim_subdomain` | Claim a new subdomain under your account | `subdomains:write` |
88
+ | `lovie_list_subdomains` | List all owned subdomains and DNS record counts | `subdomains:read` |
89
+ | `lovie_get_subdomain` | Inspect details and active DNS records of a subdomain | `subdomains:read` |
90
+ | `lovie_delete_subdomain`| Release a subdomain and purge edge records | `subdomains:write` |
91
+ | `lovie_list_dns_records`| List active DNS records for a given subdomain | `dns:read` |
92
+ | `lovie_create_dns_record`| Create A, AAAA, CNAME, TXT, or MX records | `dns:write` |
93
+ | `lovie_delete_dns_record`| Remove an existing DNS record by ID | `dns:write` |
94
+ | `lovie_list_presets` | List 1-click presets (Vercel, Railway, Render, etc.) | Public (None) |
95
+ | `lovie_apply_preset` | Apply a 1-click cloud hosting preset to a subdomain | `dns:write` |
96
+ | `lovie_verify_doh` | Test propagation across Google, Cloudflare, and Quad9 | Public (None) |
97
+ | `lovie_get_profile` | Check account tier and remaining subdomain quota | `profile:read` |
98
+
99
+ ---
100
+
101
+ ## 📄 License
102
+ MIT © Sagar Kewat (Sagarithm)
103
+
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../dist/index.js";
4
+
5
+ main().catch((error) => {
6
+ console.error("Fatal error in LovieMe MCP Server:", error);
7
+ process.exit(1);
8
+ });
9
+
@@ -0,0 +1,67 @@
1
+ import type { LovieMeProfile, LovieMeSubdomain, LovieMeDnsRecord, CloudPreset, DoHResolutionResult } from "./types.js";
2
+ export declare class LovieMeClient {
3
+ private baseUrl;
4
+ private apiKey;
5
+ constructor(apiKey?: string, baseUrl?: string);
6
+ setApiKey(key: string): void;
7
+ hasApiKey(): boolean;
8
+ private request;
9
+ checkSubdomain(name: string): Promise<{
10
+ available: boolean;
11
+ name: string;
12
+ fqdn: string;
13
+ reason?: string;
14
+ }>;
15
+ getProfile(): Promise<{
16
+ profile: LovieMeProfile;
17
+ }>;
18
+ listSubdomains(): Promise<{
19
+ subdomains: LovieMeSubdomain[];
20
+ }>;
21
+ getSubdomain(name: string): Promise<{
22
+ subdomain: LovieMeSubdomain;
23
+ dns_records: LovieMeDnsRecord[];
24
+ }>;
25
+ claimSubdomain(name: string): Promise<{
26
+ success: boolean;
27
+ message: string;
28
+ subdomain: LovieMeSubdomain;
29
+ }>;
30
+ deleteSubdomain(name: string): Promise<{
31
+ success: boolean;
32
+ message: string;
33
+ }>;
34
+ listDnsRecords(subdomain: string): Promise<{
35
+ dns_records: LovieMeDnsRecord[];
36
+ }>;
37
+ createDnsRecord(data: {
38
+ subdomain: string;
39
+ type: "A" | "AAAA" | "CNAME" | "TXT" | "MX";
40
+ name: string;
41
+ content: string;
42
+ ttl?: number;
43
+ priority?: number;
44
+ }): Promise<{
45
+ success: boolean;
46
+ message: string;
47
+ record: LovieMeDnsRecord;
48
+ }>;
49
+ deleteDnsRecord(id: string): Promise<{
50
+ success: boolean;
51
+ message: string;
52
+ }>;
53
+ listPresets(): Promise<{
54
+ presets: CloudPreset[];
55
+ }>;
56
+ applyPreset(data: {
57
+ subdomain: string;
58
+ preset: string;
59
+ customTarget?: string;
60
+ }): Promise<{
61
+ success: boolean;
62
+ message: string;
63
+ created_records: LovieMeDnsRecord[];
64
+ }>;
65
+ verifyDoH(domain: string, type?: string): Promise<DoHResolutionResult[]>;
66
+ private ensureAuth;
67
+ }
package/dist/client.js ADDED
@@ -0,0 +1,151 @@
1
+ import dns from "node:dns";
2
+ try {
3
+ dns.setDefaultResultOrder?.("ipv4first");
4
+ }
5
+ catch {
6
+ // Ignore in environments where setDefaultResultOrder is unsupported
7
+ }
8
+ export class LovieMeClient {
9
+ baseUrl;
10
+ apiKey;
11
+ constructor(apiKey, baseUrl) {
12
+ this.apiKey = apiKey || process.env.LOVIE_API_KEY || "";
13
+ this.baseUrl = (baseUrl || process.env.LOVIE_API_URL || "https://www.lovie.me").replace(/\/+$/, "");
14
+ }
15
+ setApiKey(key) {
16
+ this.apiKey = key;
17
+ }
18
+ hasApiKey() {
19
+ return Boolean(this.apiKey && this.apiKey.trim().length > 0);
20
+ }
21
+ async request(path, options = {}) {
22
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
23
+ const headers = {
24
+ "Content-Type": "application/json",
25
+ "User-Agent": "LovieMe-MCP-Server/0.1.1",
26
+ ...(options.headers || {}),
27
+ };
28
+ if (this.apiKey) {
29
+ headers["Authorization"] = `Bearer ${this.apiKey.trim()}`;
30
+ }
31
+ const response = await fetch(url, {
32
+ ...options,
33
+ headers,
34
+ });
35
+ const contentType = response.headers.get("content-type") || "";
36
+ const isJson = contentType.includes("application/json");
37
+ const payload = isJson ? await response.json() : await response.text();
38
+ if (!response.ok) {
39
+ const errorMsg = (typeof payload === "object" && payload !== null && (payload.error || payload.message)) ||
40
+ `HTTP ${response.status}: ${response.statusText}`;
41
+ throw new Error(String(errorMsg));
42
+ }
43
+ return payload;
44
+ }
45
+ // 1. Availability check (public endpoint, works without API key)
46
+ async checkSubdomain(name) {
47
+ const cleanName = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
48
+ return this.request(`/api/v1/subdomains/check?name=${encodeURIComponent(cleanName)}`);
49
+ }
50
+ // 2. Profile & quota
51
+ async getProfile() {
52
+ this.ensureAuth();
53
+ return this.request("/api/v1/me");
54
+ }
55
+ // 3. Subdomains
56
+ async listSubdomains() {
57
+ this.ensureAuth();
58
+ return this.request("/api/v1/subdomains");
59
+ }
60
+ async getSubdomain(name) {
61
+ this.ensureAuth();
62
+ const cleanName = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
63
+ return this.request(`/api/v1/subdomains/${encodeURIComponent(cleanName)}`);
64
+ }
65
+ async claimSubdomain(name) {
66
+ this.ensureAuth();
67
+ const cleanName = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
68
+ return this.request("/api/v1/subdomains", {
69
+ method: "POST",
70
+ body: JSON.stringify({ name: cleanName }),
71
+ });
72
+ }
73
+ async deleteSubdomain(name) {
74
+ this.ensureAuth();
75
+ const cleanName = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
76
+ return this.request(`/api/v1/subdomains/${encodeURIComponent(cleanName)}`, {
77
+ method: "DELETE",
78
+ });
79
+ }
80
+ // 4. DNS Records
81
+ async listDnsRecords(subdomain) {
82
+ this.ensureAuth();
83
+ const cleanSub = subdomain.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
84
+ return this.request(`/api/v1/dns/records?subdomain=${encodeURIComponent(cleanSub)}`);
85
+ }
86
+ async createDnsRecord(data) {
87
+ this.ensureAuth();
88
+ return this.request("/api/v1/dns/records", {
89
+ method: "POST",
90
+ body: JSON.stringify(data),
91
+ });
92
+ }
93
+ async deleteDnsRecord(id) {
94
+ this.ensureAuth();
95
+ return this.request(`/api/v1/dns/records/${encodeURIComponent(id)}`, {
96
+ method: "DELETE",
97
+ });
98
+ }
99
+ // 5. Presets
100
+ async listPresets() {
101
+ return this.request("/api/v1/dns/presets");
102
+ }
103
+ async applyPreset(data) {
104
+ this.ensureAuth();
105
+ return this.request("/api/v1/dns/presets", {
106
+ method: "POST",
107
+ body: JSON.stringify(data),
108
+ });
109
+ }
110
+ // 6. Global DoH Multi-Resolver Verification
111
+ async verifyDoH(domain, type = "A") {
112
+ const clean = domain.trim().toLowerCase();
113
+ const fqdn = clean === "lovie.me" || clean.endsWith(".lovie.me") ? clean : `${clean}.lovie.me`;
114
+ const resolvers = [
115
+ { name: "Google Public DNS", url: `https://dns.google/resolve?name=${encodeURIComponent(fqdn)}&type=${type}` },
116
+ { name: "Cloudflare (1.1.1.1)", url: `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(fqdn)}&type=${type}` },
117
+ { name: "AliDNS Global", url: `https://dns.alidns.com/resolve?name=${encodeURIComponent(fqdn)}&type=${type}` },
118
+ ];
119
+ const results = await Promise.allSettled(resolvers.map(async ({ name, url }) => {
120
+ const start = Date.now();
121
+ const res = await fetch(url, {
122
+ headers: { Accept: "application/dns-json" },
123
+ });
124
+ const latency = Date.now() - start;
125
+ if (!res.ok)
126
+ throw new Error(`HTTP ${res.status}`);
127
+ const data = (await res.json());
128
+ return {
129
+ resolver: name,
130
+ status: (data.Answer && data.Answer.length > 0 ? "resolved" : "not_found"),
131
+ latency_ms: latency,
132
+ answers: data.Answer || [],
133
+ };
134
+ }));
135
+ return results.map((r, i) => {
136
+ if (r.status === "fulfilled")
137
+ return r.value;
138
+ return {
139
+ resolver: resolvers[i].name,
140
+ status: "error",
141
+ latency_ms: 0,
142
+ answers: [],
143
+ };
144
+ });
145
+ }
146
+ ensureAuth() {
147
+ if (!this.apiKey) {
148
+ throw new Error("LovieMe API key required. Set the LOVIE_API_KEY environment variable or pass an API key (format: lovie_live_...).");
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,3 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ export declare function createServer(apiKey?: string, baseUrl?: string): Server;
3
+ export declare function main(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { LovieMeClient } from "./client.js";
5
+ import { LOVIE_TOOLS, handleToolCall } from "./tools.js";
6
+ export function createServer(apiKey, baseUrl) {
7
+ const client = new LovieMeClient(apiKey, baseUrl);
8
+ const server = new Server({
9
+ name: "lovieme-mcp",
10
+ version: "0.1.0",
11
+ }, {
12
+ capabilities: {
13
+ tools: {},
14
+ },
15
+ });
16
+ // 1. List available tools
17
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
18
+ return {
19
+ tools: LOVIE_TOOLS,
20
+ };
21
+ });
22
+ // 2. Call tool
23
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
24
+ const { name, arguments: args } = request.params;
25
+ return handleToolCall(name, args, client);
26
+ });
27
+ return server;
28
+ }
29
+ export async function main() {
30
+ const server = createServer();
31
+ const transport = new StdioServerTransport();
32
+ await server.connect(transport);
33
+ console.error("LovieMe MCP Server running on stdio");
34
+ }
@@ -0,0 +1,10 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { LovieMeClient } from "./client.js";
3
+ export declare const LOVIE_TOOLS: Tool[];
4
+ export declare function handleToolCall(name: string, args: Record<string, unknown> | undefined, client: LovieMeClient): Promise<{
5
+ content: Array<{
6
+ type: "text";
7
+ text: string;
8
+ }>;
9
+ isError?: boolean;
10
+ }>;
package/dist/tools.js ADDED
@@ -0,0 +1,359 @@
1
+ export const LOVIE_TOOLS = [
2
+ {
3
+ name: "lovie_check_subdomain",
4
+ description: "Check whether a *.lovie.me subdomain prefix is available for registration.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ name: {
9
+ type: "string",
10
+ description: "Subdomain prefix to check (e.g. 'my-app' for my-app.lovie.me).",
11
+ },
12
+ },
13
+ required: ["name"],
14
+ },
15
+ },
16
+ {
17
+ name: "lovie_claim_subdomain",
18
+ description: "Claim and register a new *.lovie.me subdomain. Requires LOVIE_API_KEY with 'subdomains:write' scope.",
19
+ inputSchema: {
20
+ type: "object",
21
+ properties: {
22
+ name: {
23
+ type: "string",
24
+ description: "Subdomain prefix to claim (alphanumeric and hyphens, 3-63 chars).",
25
+ },
26
+ },
27
+ required: ["name"],
28
+ },
29
+ },
30
+ {
31
+ name: "lovie_list_subdomains",
32
+ description: "List all subdomains owned by the authenticated account with their DNS records count. Requires LOVIE_API_KEY with 'subdomains:read' scope.",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {},
36
+ },
37
+ },
38
+ {
39
+ name: "lovie_get_subdomain",
40
+ description: "Inspect details of a specific subdomain including its active DNS records. Requires LOVIE_API_KEY with 'subdomains:read' scope.",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ name: {
45
+ type: "string",
46
+ description: "Subdomain name or prefix to inspect.",
47
+ },
48
+ },
49
+ required: ["name"],
50
+ },
51
+ },
52
+ {
53
+ name: "lovie_delete_subdomain",
54
+ description: "Release and permanently delete a subdomain, purging all its DNS records from Gcore Anycast nameservers. Requires LOVIE_API_KEY with 'subdomains:write' scope.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ name: {
59
+ type: "string",
60
+ description: "Subdomain prefix to release (e.g. 'test-app').",
61
+ },
62
+ },
63
+ required: ["name"],
64
+ },
65
+ },
66
+ {
67
+ name: "lovie_list_dns_records",
68
+ description: "List all active DNS records for a given subdomain. Requires LOVIE_API_KEY with 'dns:read' scope.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ subdomain: {
73
+ type: "string",
74
+ description: "Subdomain prefix (e.g. 'my-app').",
75
+ },
76
+ },
77
+ required: ["subdomain"],
78
+ },
79
+ },
80
+ {
81
+ name: "lovie_create_dns_record",
82
+ description: "Create an RFC-compliant DNS record (A, AAAA, CNAME, TXT, MX) with sub-2s Gcore Anycast propagation. Requires LOVIE_API_KEY with 'dns:write' scope.",
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ subdomain: {
87
+ type: "string",
88
+ description: "Subdomain prefix where the record belongs (e.g. 'my-app').",
89
+ },
90
+ type: {
91
+ type: "string",
92
+ enum: ["A", "AAAA", "CNAME", "TXT", "MX"],
93
+ description: "DNS record type.",
94
+ },
95
+ name: {
96
+ type: "string",
97
+ description: "Host label ('@' for apex of subdomain, or 'www', 'api', etc.).",
98
+ },
99
+ content: {
100
+ type: "string",
101
+ description: "Record destination (IPv4 for A, IPv6 for AAAA, target host for CNAME, string for TXT).",
102
+ },
103
+ ttl: {
104
+ type: "number",
105
+ description: "TTL in seconds (default: 300).",
106
+ },
107
+ priority: {
108
+ type: "number",
109
+ description: "MX record priority (required for MX, e.g. 10).",
110
+ },
111
+ },
112
+ required: ["subdomain", "type", "name", "content"],
113
+ },
114
+ },
115
+ {
116
+ name: "lovie_delete_dns_record",
117
+ description: "Delete an existing DNS record by ID, removing it from both database and Anycast edge. Requires LOVIE_API_KEY with 'dns:write' scope.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ id: {
122
+ type: "string",
123
+ description: "The unique UUID of the DNS record to delete.",
124
+ },
125
+ },
126
+ required: ["id"],
127
+ },
128
+ },
129
+ {
130
+ name: "lovie_list_presets",
131
+ description: "List all verified 1-click cloud hosting presets (Vercel, Railway, Render, GitHub Pages, Cloudflare Pages, Netlify, Fly.io).",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {},
135
+ },
136
+ },
137
+ {
138
+ name: "lovie_apply_preset",
139
+ description: "Apply a 1-click cloud hosting DNS preset to your subdomain. Automatically configures tested CNAME and A records. Requires LOVIE_API_KEY with 'dns:write' scope.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ subdomain: {
144
+ type: "string",
145
+ description: "Subdomain prefix to apply preset to (e.g. 'my-app').",
146
+ },
147
+ preset: {
148
+ type: "string",
149
+ enum: ["vercel", "railway", "render", "github-pages", "cloudflare-pages", "netlify", "flyio"],
150
+ description: "Target platform preset ID.",
151
+ },
152
+ customTarget: {
153
+ type: "string",
154
+ description: "Optional custom target required for some presets (e.g. GitHub username for github-pages, or Railway target domain).",
155
+ },
156
+ },
157
+ required: ["subdomain", "preset"],
158
+ },
159
+ },
160
+ {
161
+ name: "lovie_verify_doh",
162
+ description: "Verify live Anycast DNS propagation worldwide by parallel-querying Google (8.8.8.8), Cloudflare (1.1.1.1), and Quad9 (9.9.9.9) DoH resolvers.",
163
+ inputSchema: {
164
+ type: "object",
165
+ properties: {
166
+ domain: {
167
+ type: "string",
168
+ description: "Subdomain to test (e.g. 'my-app' or 'my-app.lovie.me').",
169
+ },
170
+ type: {
171
+ type: "string",
172
+ enum: ["A", "AAAA", "CNAME", "TXT", "MX"],
173
+ description: "Record type to verify (default: 'A').",
174
+ },
175
+ },
176
+ required: ["domain"],
177
+ },
178
+ },
179
+ {
180
+ name: "lovie_get_profile",
181
+ description: "Inspect the current user profile, plan tier (Free / Pro Lifetime / Builder Grant), and remaining subdomain quotas. Requires LOVIE_API_KEY with 'profile:read' scope.",
182
+ inputSchema: {
183
+ type: "object",
184
+ properties: {},
185
+ },
186
+ },
187
+ ];
188
+ export async function handleToolCall(name, args, client) {
189
+ try {
190
+ const p = args || {};
191
+ switch (name) {
192
+ case "lovie_check_subdomain": {
193
+ const result = await client.checkSubdomain(String(p.name));
194
+ return {
195
+ content: [
196
+ {
197
+ type: "text",
198
+ text: JSON.stringify(result, null, 2),
199
+ },
200
+ ],
201
+ };
202
+ }
203
+ case "lovie_claim_subdomain": {
204
+ const result = await client.claimSubdomain(String(p.name));
205
+ return {
206
+ content: [
207
+ {
208
+ type: "text",
209
+ text: JSON.stringify(result, null, 2),
210
+ },
211
+ ],
212
+ };
213
+ }
214
+ case "lovie_list_subdomains": {
215
+ const result = await client.listSubdomains();
216
+ return {
217
+ content: [
218
+ {
219
+ type: "text",
220
+ text: JSON.stringify(result, null, 2),
221
+ },
222
+ ],
223
+ };
224
+ }
225
+ case "lovie_get_subdomain": {
226
+ const result = await client.getSubdomain(String(p.name));
227
+ return {
228
+ content: [
229
+ {
230
+ type: "text",
231
+ text: JSON.stringify(result, null, 2),
232
+ },
233
+ ],
234
+ };
235
+ }
236
+ case "lovie_delete_subdomain": {
237
+ const result = await client.deleteSubdomain(String(p.name));
238
+ return {
239
+ content: [
240
+ {
241
+ type: "text",
242
+ text: JSON.stringify(result, null, 2),
243
+ },
244
+ ],
245
+ };
246
+ }
247
+ case "lovie_list_dns_records": {
248
+ const result = await client.listDnsRecords(String(p.subdomain));
249
+ return {
250
+ content: [
251
+ {
252
+ type: "text",
253
+ text: JSON.stringify(result, null, 2),
254
+ },
255
+ ],
256
+ };
257
+ }
258
+ case "lovie_create_dns_record": {
259
+ const result = await client.createDnsRecord({
260
+ subdomain: String(p.subdomain),
261
+ type: p.type,
262
+ name: String(p.name),
263
+ content: String(p.content),
264
+ ttl: p.ttl ? Number(p.ttl) : undefined,
265
+ priority: p.priority ? Number(p.priority) : undefined,
266
+ });
267
+ return {
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: JSON.stringify(result, null, 2),
272
+ },
273
+ ],
274
+ };
275
+ }
276
+ case "lovie_delete_dns_record": {
277
+ const result = await client.deleteDnsRecord(String(p.id));
278
+ return {
279
+ content: [
280
+ {
281
+ type: "text",
282
+ text: JSON.stringify(result, null, 2),
283
+ },
284
+ ],
285
+ };
286
+ }
287
+ case "lovie_list_presets": {
288
+ const result = await client.listPresets();
289
+ return {
290
+ content: [
291
+ {
292
+ type: "text",
293
+ text: JSON.stringify(result, null, 2),
294
+ },
295
+ ],
296
+ };
297
+ }
298
+ case "lovie_apply_preset": {
299
+ const result = await client.applyPreset({
300
+ subdomain: String(p.subdomain),
301
+ preset: String(p.preset),
302
+ customTarget: p.customTarget ? String(p.customTarget) : undefined,
303
+ });
304
+ return {
305
+ content: [
306
+ {
307
+ type: "text",
308
+ text: JSON.stringify(result, null, 2),
309
+ },
310
+ ],
311
+ };
312
+ }
313
+ case "lovie_verify_doh": {
314
+ const result = await client.verifyDoH(String(p.domain), p.type ? String(p.type) : "A");
315
+ return {
316
+ content: [
317
+ {
318
+ type: "text",
319
+ text: JSON.stringify(result, null, 2),
320
+ },
321
+ ],
322
+ };
323
+ }
324
+ case "lovie_get_profile": {
325
+ const result = await client.getProfile();
326
+ return {
327
+ content: [
328
+ {
329
+ type: "text",
330
+ text: JSON.stringify(result, null, 2),
331
+ },
332
+ ],
333
+ };
334
+ }
335
+ default:
336
+ return {
337
+ isError: true,
338
+ content: [
339
+ {
340
+ type: "text",
341
+ text: `Unknown tool: ${name}`,
342
+ },
343
+ ],
344
+ };
345
+ }
346
+ }
347
+ catch (err) {
348
+ const msg = err instanceof Error ? err.message : String(err);
349
+ return {
350
+ isError: true,
351
+ content: [
352
+ {
353
+ type: "text",
354
+ text: `Error executing ${name}: ${msg}`,
355
+ },
356
+ ],
357
+ };
358
+ }
359
+ }
@@ -0,0 +1,54 @@
1
+ export interface LovieMeProfile {
2
+ id: string;
3
+ email?: string;
4
+ name?: string;
5
+ plan: "starter" | "pro" | "grant";
6
+ quota: {
7
+ subdomains_max: number;
8
+ subdomains_used: number;
9
+ subdomains_remaining: number;
10
+ };
11
+ }
12
+ export interface LovieMeSubdomain {
13
+ id: string;
14
+ name: string;
15
+ fqdn: string;
16
+ created_at: string;
17
+ dns_records_count: number;
18
+ email_forwarder_active: boolean;
19
+ }
20
+ export interface LovieMeDnsRecord {
21
+ id: string;
22
+ subdomain_id: string;
23
+ type: "A" | "AAAA" | "CNAME" | "TXT" | "MX" | "CAA";
24
+ name: string;
25
+ content: string;
26
+ ttl: number;
27
+ priority?: number | null;
28
+ created_at: string;
29
+ }
30
+ export interface PresetTarget {
31
+ name: string;
32
+ type: "CNAME" | "A" | "TXT";
33
+ content: string;
34
+ ttl: number;
35
+ priority?: number;
36
+ }
37
+ export interface CloudPreset {
38
+ id: string;
39
+ name: string;
40
+ description: string;
41
+ records: PresetTarget[];
42
+ docs_url: string;
43
+ }
44
+ export interface DoHResolutionResult {
45
+ resolver: string;
46
+ status: "resolved" | "not_found" | "error";
47
+ latency_ms: number;
48
+ answers: Array<{
49
+ name: string;
50
+ type: number;
51
+ data: string;
52
+ TTL: number;
53
+ }>;
54
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
- {
2
- "name": "lovieme-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "lovieme-mcp",
3
+ "version": "0.1.1",
4
+ "description": "Official Model Context Protocol (MCP) server for LovieMe — claim free *.lovie.me subdomains, configure Anycast DNS, and wire up cloud presets directly from AI coding agents.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "lovieme-mcp": "bin/lovieme-mcp.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "bin",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "node dist/index.js",
19
+ "test": "vitest run"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "lovieme",
25
+ "dns",
26
+ "subdomain",
27
+ "ai-agent",
28
+ "cursor",
29
+ "claude-code",
30
+ "antigravity"
31
+ ],
32
+ "author": "Sagar Kewat (Sagarithm)",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.12.1",
36
+ "zod": "^3.24.2"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.17.0",
40
+ "typescript": "^5.8.0",
41
+ "vitest": "^4.1.7"
42
+ }
43
+ }