@ulinkly/mcp-server 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,128 @@
1
+ # @ulinkly/mcp-server
2
+
3
+ Model Context Protocol (MCP) server for [ULink](https://ulink.ly) -- manage deep links, domains, and projects from AI coding tools like Claude Code and Cursor.
4
+
5
+ ## Quick Start
6
+
7
+ Add the following to your MCP client configuration:
8
+
9
+ **Claude Code** (`~/.claude/claude_desktop_config.json`):
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "ulink": {
15
+ "command": "npx",
16
+ "args": ["-y", "@ulinkly/mcp-server"]
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ **Cursor** (`.cursor/mcp.json`):
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "ulink": {
28
+ "command": "npx",
29
+ "args": ["-y", "@ulinkly/mcp-server"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ On first use the server opens your browser for authentication. After you log in, the session persists and tokens refresh automatically.
36
+
37
+ ## Authentication
38
+
39
+ ### OAuth (default)
40
+
41
+ No configuration needed. The server launches a browser-based login flow using Supabase Auth with PKCE. Access tokens refresh automatically before they expire. If a refresh fails, the browser flow is re-triggered.
42
+
43
+ ### API Key
44
+
45
+ Set the `ULINK_API_KEY` environment variable to skip the browser flow entirely. This is useful for CI environments or headless servers.
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "ulink": {
51
+ "command": "npx",
52
+ "args": ["-y", "@ulinkly/mcp-server"],
53
+ "env": {
54
+ "ULINK_API_KEY": "your-api-key-here"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ You can generate an API key from the ULink dashboard under **Project Settings > API Keys**, or by using the `create_api_key` tool.
62
+
63
+ ## Tools
64
+
65
+ The server exposes 21 tools organized into five categories.
66
+
67
+ ### Projects (5 tools)
68
+
69
+ | Tool | Description |
70
+ |------|-------------|
71
+ | `list_projects` | List all projects owned by or shared with the authenticated user |
72
+ | `get_project` | Get detailed information about a specific project |
73
+ | `create_project` | Create a new project with a name and default fallback URL |
74
+ | `update_project` | Update the name or default URL of an existing project |
75
+ | `configure_project` | Set platform-specific config (iOS bundle ID, Android package, deeplink schemas, SHA-256 fingerprints) |
76
+
77
+ ### Links (6 tools)
78
+
79
+ | Tool | Description |
80
+ |------|-------------|
81
+ | `create_link` | Create a unified or dynamic smart link with platform-specific URLs and parameters |
82
+ | `list_links` | List all links in a project with pagination |
83
+ | `get_link` | Get detailed information about a specific link |
84
+ | `update_link` | Update a link's URLs, parameters, or metadata |
85
+ | `delete_link` | Permanently delete a link |
86
+ | `get_link_analytics` | Get click analytics for a link (total clicks, platform/country/referrer breakdowns) |
87
+
88
+ ### Domains (4 tools)
89
+
90
+ | Tool | Description |
91
+ |------|-------------|
92
+ | `list_domains` | List all domains (shared and custom) associated with a project |
93
+ | `add_domain` | Add a custom domain to a project |
94
+ | `verify_domain` | Trigger DNS verification for a custom domain |
95
+ | `delete_domain` | Remove a custom domain from a project |
96
+
97
+ ### API Keys (3 tools)
98
+
99
+ | Tool | Description |
100
+ |------|-------------|
101
+ | `list_api_keys` | List all API keys for a project (metadata only, not the key value) |
102
+ | `create_api_key` | Create a new API key (the full key is only shown once) |
103
+ | `revoke_api_key` | Permanently revoke an API key |
104
+
105
+ ### Account (3 tools)
106
+
107
+ | Tool | Description |
108
+ |------|-------------|
109
+ | `get_subscription` | Get the current subscription plan, status, and renewal date |
110
+ | `list_plans` | List all available subscription plans with pricing and limits |
111
+ | `get_usage` | Get usage statistics for the current billing period (clicks, links, API calls) |
112
+
113
+ ## Environment Variables
114
+
115
+ | Variable | Default | Description |
116
+ |----------|---------|-------------|
117
+ | `ULINK_API_KEY` | -- | API key for authentication (skips browser OAuth flow) |
118
+ | `ULINK_API_URL` | `https://api.ulink.ly` | Base URL for the ULink REST API |
119
+ | `ULINK_FRONTEND_URL` | `https://ulink.ly` | Frontend URL used for the OAuth login flow |
120
+ | `ULINK_SUPABASE_ANON_KEY` | -- | Supabase anon key for silent token refresh (without it, expired tokens trigger re-authentication via browser) |
121
+
122
+ ## Requirements
123
+
124
+ - Node.js 18 or later
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1 @@
1
+ export declare function getApiKey(): string | undefined;
@@ -0,0 +1,4 @@
1
+ export function getApiKey() {
2
+ return process.env.ULINK_API_KEY;
3
+ }
4
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.js","sourceRoot":"","sources":["../../src/auth/api-key.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AACnC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface OAuthTokens {
2
+ accessToken: string;
3
+ refreshToken: string;
4
+ expiresAt: number;
5
+ }
6
+ export declare function openBrowser(url: string): void;
7
+ export declare function browserOAuthFlow(): Promise<OAuthTokens>;
8
+ export declare function refreshAccessToken(refreshToken: string): Promise<OAuthTokens>;
@@ -0,0 +1,168 @@
1
+ import { randomUUID, randomBytes, createHash } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { execSync } from "node:child_process";
4
+ import { URL } from "node:url";
5
+ const FRONTEND_URL = process.env.ULINK_FRONTEND_URL ?? "https://ulink.ly";
6
+ const SUPABASE_URL = process.env.ULINK_SUPABASE_URL ?? "https://cjgihassfsspxivjtgoi.supabase.co";
7
+ const TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ function base64url(buf) {
12
+ return buf.toString("base64url");
13
+ }
14
+ function generateCodeVerifier() {
15
+ return base64url(randomBytes(32));
16
+ }
17
+ function generateCodeChallenge(verifier) {
18
+ return base64url(createHash("sha256").update(verifier).digest());
19
+ }
20
+ function successHtml() {
21
+ return `<!DOCTYPE html>
22
+ <html><head><meta charset="utf-8"><title>ULink CLI</title>
23
+ <style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#fafafa}
24
+ .card{text-align:center;padding:2rem;border-radius:12px;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.08)}
25
+ h1{color:#22c55e;margin:0 0 .5rem}p{color:#555}</style></head>
26
+ <body><div class="card"><h1>Authenticated</h1><p>You can close this window and return to your terminal.</p></div></body></html>`;
27
+ }
28
+ function errorHtml(message) {
29
+ return `<!DOCTYPE html>
30
+ <html><head><meta charset="utf-8"><title>ULink CLI — Error</title>
31
+ <style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#fafafa}
32
+ .card{text-align:center;padding:2rem;border-radius:12px;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.08)}
33
+ h1{color:#ef4444;margin:0 0 .5rem}p{color:#555}</style></head>
34
+ <body><div class="card"><h1>Authentication Error</h1><p>${message}</p></div></body></html>`;
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // Browser opener
38
+ // ---------------------------------------------------------------------------
39
+ export function openBrowser(url) {
40
+ const platform = process.platform;
41
+ try {
42
+ if (platform === "darwin") {
43
+ execSync(`open "${url}"`);
44
+ }
45
+ else if (platform === "win32") {
46
+ execSync(`start "" "${url}"`);
47
+ }
48
+ else {
49
+ // Linux / other
50
+ execSync(`xdg-open "${url}"`);
51
+ }
52
+ }
53
+ catch {
54
+ console.error(`Could not open browser. Please visit:\n${url}`);
55
+ }
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Browser OAuth PKCE flow
59
+ // ---------------------------------------------------------------------------
60
+ export function browserOAuthFlow() {
61
+ return new Promise((resolve, reject) => {
62
+ const sessionId = randomUUID();
63
+ const codeVerifier = generateCodeVerifier();
64
+ const codeChallenge = generateCodeChallenge(codeVerifier);
65
+ let settled = false;
66
+ const server = createServer((req, res) => {
67
+ if (!req.url) {
68
+ res.writeHead(400);
69
+ res.end();
70
+ return;
71
+ }
72
+ const parsed = new URL(req.url, `http://127.0.0.1`);
73
+ if (parsed.pathname !== "/callback") {
74
+ res.writeHead(404);
75
+ res.end("Not found");
76
+ return;
77
+ }
78
+ const error = parsed.searchParams.get("error");
79
+ if (error) {
80
+ const desc = parsed.searchParams.get("error_description") ?? "Unknown error";
81
+ res.writeHead(200, { "Content-Type": "text/html" });
82
+ res.end(errorHtml(desc));
83
+ settled = true;
84
+ server.close();
85
+ reject(new Error(`OAuth error: ${error} — ${desc}`));
86
+ return;
87
+ }
88
+ const accessToken = parsed.searchParams.get("access_token");
89
+ const refreshToken = parsed.searchParams.get("refresh_token");
90
+ const expiresIn = parsed.searchParams.get("expires_in");
91
+ if (!accessToken || !refreshToken || !expiresIn) {
92
+ const msg = "Missing token parameters in callback";
93
+ res.writeHead(200, { "Content-Type": "text/html" });
94
+ res.end(errorHtml(msg));
95
+ settled = true;
96
+ server.close();
97
+ reject(new Error(msg));
98
+ return;
99
+ }
100
+ res.writeHead(200, { "Content-Type": "text/html" });
101
+ res.end(successHtml());
102
+ const tokens = {
103
+ accessToken,
104
+ refreshToken,
105
+ expiresAt: Date.now() + parseInt(expiresIn, 10) * 1000,
106
+ };
107
+ settled = true;
108
+ server.close();
109
+ resolve(tokens);
110
+ });
111
+ // Listen on a random available port on loopback
112
+ server.listen(0, "127.0.0.1", () => {
113
+ const addr = server.address();
114
+ if (!addr || typeof addr === "string") {
115
+ reject(new Error("Failed to bind local server"));
116
+ return;
117
+ }
118
+ const port = addr.port;
119
+ const authUrl = `${FRONTEND_URL}/auth/cli` +
120
+ `?session=${encodeURIComponent(sessionId)}` +
121
+ `&code_challenge=${encodeURIComponent(codeChallenge)}` +
122
+ `&code_challenge_method=S256` +
123
+ `&callback_port=${port}` +
124
+ `&source=mcp`;
125
+ console.error(`Opening browser for authentication...\n${authUrl}`);
126
+ openBrowser(authUrl);
127
+ });
128
+ // 5-minute timeout
129
+ const timer = setTimeout(() => {
130
+ if (!settled) {
131
+ settled = true;
132
+ server.close();
133
+ reject(new Error("OAuth flow timed out after 5 minutes"));
134
+ }
135
+ }, TIMEOUT_MS);
136
+ // Don't let the timer keep the process alive if the server closes first
137
+ timer.unref();
138
+ });
139
+ }
140
+ // ---------------------------------------------------------------------------
141
+ // Token refresh
142
+ // ---------------------------------------------------------------------------
143
+ export async function refreshAccessToken(refreshToken) {
144
+ const anonKey = process.env.ULINK_SUPABASE_ANON_KEY;
145
+ if (!anonKey) {
146
+ throw new Error("ULINK_SUPABASE_ANON_KEY not set — cannot refresh token silently. Re-authenticating via browser.");
147
+ }
148
+ const url = `${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`;
149
+ const res = await fetch(url, {
150
+ method: "POST",
151
+ headers: {
152
+ "Content-Type": "application/json",
153
+ apikey: anonKey,
154
+ },
155
+ body: JSON.stringify({ refresh_token: refreshToken }),
156
+ });
157
+ if (!res.ok) {
158
+ const body = await res.text();
159
+ throw new Error(`Token refresh failed (${res.status}): ${body}`);
160
+ }
161
+ const data = (await res.json());
162
+ return {
163
+ accessToken: data.access_token,
164
+ refreshToken: data.refresh_token,
165
+ expiresAt: Date.now() + data.expires_in * 1000,
166
+ };
167
+ }
168
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare class ApiError extends Error {
2
+ readonly status: number;
3
+ constructor(status: number, message: string);
4
+ }
5
+ export declare function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T>;
@@ -0,0 +1,84 @@
1
+ import { getApiKey } from "../auth/api-key.js";
2
+ import { browserOAuthFlow, refreshAccessToken, } from "../auth/oauth.js";
3
+ // ---------------------------------------------------------------------------
4
+ // Configuration
5
+ // ---------------------------------------------------------------------------
6
+ const API_BASE = process.env.ULINK_API_URL ?? "https://api.ulink.ly";
7
+ const TOKEN_REFRESH_BUFFER_MS = 30 * 1000; // refresh 30 s before expiry
8
+ // ---------------------------------------------------------------------------
9
+ // Error type
10
+ // ---------------------------------------------------------------------------
11
+ export class ApiError extends Error {
12
+ status;
13
+ constructor(status, message) {
14
+ super(message);
15
+ this.status = status;
16
+ this.name = "ApiError";
17
+ }
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // Token state
21
+ // ---------------------------------------------------------------------------
22
+ let oauthTokens;
23
+ // ---------------------------------------------------------------------------
24
+ // Auth helper
25
+ // ---------------------------------------------------------------------------
26
+ async function ensureAuth() {
27
+ // 1. API-key takes precedence
28
+ const apiKey = getApiKey();
29
+ if (apiKey) {
30
+ return { header: "x-app-key", value: apiKey };
31
+ }
32
+ // 2. OAuth — first call triggers browser flow
33
+ if (!oauthTokens) {
34
+ oauthTokens = await browserOAuthFlow();
35
+ }
36
+ // 3. Auto-refresh if token expires within 30 s
37
+ if (oauthTokens.expiresAt - Date.now() < TOKEN_REFRESH_BUFFER_MS) {
38
+ try {
39
+ oauthTokens = await refreshAccessToken(oauthTokens.refreshToken);
40
+ }
41
+ catch {
42
+ // Refresh failed — re-authenticate via browser
43
+ console.error("Token refresh failed, re-authenticating...");
44
+ oauthTokens = await browserOAuthFlow();
45
+ }
46
+ }
47
+ return { header: "Authorization", value: `Bearer ${oauthTokens.accessToken}` };
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Generic API request
51
+ // ---------------------------------------------------------------------------
52
+ export async function apiRequest(method, path, body) {
53
+ const auth = await ensureAuth();
54
+ const headers = {
55
+ [auth.header]: auth.value,
56
+ };
57
+ if (body !== undefined) {
58
+ headers["Content-Type"] = "application/json";
59
+ }
60
+ const res = await fetch(`${API_BASE}${path}`, {
61
+ method,
62
+ headers,
63
+ body: body !== undefined ? JSON.stringify(body) : undefined,
64
+ });
65
+ // 204 No Content
66
+ if (res.status === 204) {
67
+ return undefined;
68
+ }
69
+ if (!res.ok) {
70
+ let message = `API request failed: ${res.status}`;
71
+ try {
72
+ const errorBody = (await res.json());
73
+ if (errorBody.message) {
74
+ message = errorBody.message;
75
+ }
76
+ }
77
+ catch {
78
+ // ignore JSON parse failure — use default message
79
+ }
80
+ throw new ApiError(res.status, message);
81
+ }
82
+ return (await res.json());
83
+ }
84
+ //# sourceMappingURL=ulink-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ulink-api.js","sourceRoot":"","sources":["../../src/client/ulink-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerProjectTools } from "./tools/projects.js";
5
+ import { registerLinkTools } from "./tools/links.js";
6
+ import { registerDomainTools } from "./tools/domains.js";
7
+ import { registerApiKeyTools } from "./tools/api-keys.js";
8
+ import { registerAccountTools } from "./tools/account.js";
9
+ const server = new McpServer({
10
+ name: "ulink",
11
+ version: "0.1.0",
12
+ });
13
+ registerProjectTools(server);
14
+ registerLinkTools(server);
15
+ registerDomainTools(server);
16
+ registerApiKeyTools(server);
17
+ registerAccountTools(server);
18
+ async function main() {
19
+ const transport = new StdioServerTransport();
20
+ await server.connect(transport);
21
+ console.error("ULink MCP Server running on stdio (21 tools registered)");
22
+ }
23
+ main().catch((error) => {
24
+ console.error("Fatal error:", error);
25
+ process.exit(1);
26
+ });
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAC1B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAE7B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;AAC3E,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAccountTools(server: McpServer): void;
@@ -0,0 +1,67 @@
1
+ import { apiRequest } from "../client/ulink-api.js";
2
+ export function registerAccountTools(server) {
3
+ // -----------------------------------------------------------------------
4
+ // get_subscription
5
+ // -----------------------------------------------------------------------
6
+ server.registerTool("get_subscription", {
7
+ title: "Get Subscription",
8
+ description: "Retrieve the current user's active subscription details, including plan name, billing period, status, and renewal date.",
9
+ annotations: { readOnlyHint: true },
10
+ }, async () => {
11
+ try {
12
+ const data = await apiRequest("GET", "/subscriptions/current");
13
+ return {
14
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
15
+ };
16
+ }
17
+ catch (err) {
18
+ return {
19
+ content: [{ type: "text", text: `Error: ${err.message}` }],
20
+ isError: true,
21
+ };
22
+ }
23
+ });
24
+ // -----------------------------------------------------------------------
25
+ // list_plans
26
+ // -----------------------------------------------------------------------
27
+ server.registerTool("list_plans", {
28
+ title: "List Plans",
29
+ description: "List all available ULink subscription plans with their features, limits, and pricing. Useful for comparing plans or determining upgrade options.",
30
+ annotations: { readOnlyHint: true },
31
+ }, async () => {
32
+ try {
33
+ const data = await apiRequest("GET", "/subscriptions/plans");
34
+ return {
35
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
36
+ };
37
+ }
38
+ catch (err) {
39
+ return {
40
+ content: [{ type: "text", text: `Error: ${err.message}` }],
41
+ isError: true,
42
+ };
43
+ }
44
+ });
45
+ // -----------------------------------------------------------------------
46
+ // get_usage
47
+ // -----------------------------------------------------------------------
48
+ server.registerTool("get_usage", {
49
+ title: "Get Usage",
50
+ description: "Retrieve the current user's usage statistics for the active billing period, including link clicks, links created, and API calls against plan limits.",
51
+ annotations: { readOnlyHint: true },
52
+ }, async () => {
53
+ try {
54
+ const data = await apiRequest("GET", "/subscriptions/me/usage");
55
+ return {
56
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
57
+ };
58
+ }
59
+ catch (err) {
60
+ return {
61
+ content: [{ type: "text", text: `Error: ${err.message}` }],
62
+ isError: true,
63
+ };
64
+ }
65
+ });
66
+ }
67
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/tools/account.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,0EAA0E;IAC1E,mBAAmB;IACnB,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,yHAAyH;QAC3H,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC;YAC/D,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,0EAA0E;IAC1E,aAAa;IACb,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,kJAAkJ;QACpJ,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC;YAC7D,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,0EAA0E;IAC1E,YAAY;IACZ,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,WAAW;QAClB,WAAW,EACT,sJAAsJ;QACxJ,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,yBAAyB,CAAC,CAAC;YAChE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerApiKeyTools(server: McpServer): void;
@@ -0,0 +1,82 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client/ulink-api.js";
3
+ export function registerApiKeyTools(server) {
4
+ // -----------------------------------------------------------------------
5
+ // list_api_keys
6
+ // -----------------------------------------------------------------------
7
+ server.registerTool("list_api_keys", {
8
+ title: "List API Keys",
9
+ description: "List all API keys for a ULink project. API keys are used for server-side and SDK authentication. Returns key metadata (name, prefix, creation date) but never the full key value.",
10
+ annotations: { readOnlyHint: true },
11
+ inputSchema: {
12
+ projectId: z.string().uuid().describe("The project whose API keys to list"),
13
+ },
14
+ }, async ({ projectId }) => {
15
+ try {
16
+ const data = await apiRequest("GET", `/api-keys?projectId=${projectId}`);
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
19
+ };
20
+ }
21
+ catch (err) {
22
+ return {
23
+ content: [{ type: "text", text: `Error: ${err.message}` }],
24
+ isError: true,
25
+ };
26
+ }
27
+ });
28
+ // -----------------------------------------------------------------------
29
+ // create_api_key
30
+ // -----------------------------------------------------------------------
31
+ server.registerTool("create_api_key", {
32
+ title: "Create API Key",
33
+ description: "Create a new API key for a ULink project. The full key value is only returned once in the response and cannot be retrieved again. Store it securely.",
34
+ inputSchema: {
35
+ projectId: z.string().uuid().describe("The project to create the API key for"),
36
+ name: z.string().describe("A descriptive name for the API key (e.g. 'Production Server')"),
37
+ },
38
+ }, async ({ projectId, name }) => {
39
+ try {
40
+ const data = await apiRequest("POST", `/api-keys?projectId=${encodeURIComponent(projectId)}`, { name });
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
43
+ };
44
+ }
45
+ catch (err) {
46
+ return {
47
+ content: [{ type: "text", text: `Error: ${err.message}` }],
48
+ isError: true,
49
+ };
50
+ }
51
+ });
52
+ // -----------------------------------------------------------------------
53
+ // revoke_api_key
54
+ // -----------------------------------------------------------------------
55
+ server.registerTool("revoke_api_key", {
56
+ title: "Revoke API Key",
57
+ description: "Permanently revoke an API key. Any applications using this key will immediately lose access. This action cannot be undone.",
58
+ annotations: { destructiveHint: true },
59
+ inputSchema: {
60
+ apiKeyId: z.string().uuid().describe("The unique identifier of the API key to revoke"),
61
+ },
62
+ }, async ({ apiKeyId }) => {
63
+ try {
64
+ await apiRequest("DELETE", `/api-keys/${apiKeyId}`);
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `Successfully revoked API key ${apiKeyId}`,
70
+ },
71
+ ],
72
+ };
73
+ }
74
+ catch (err) {
75
+ return {
76
+ content: [{ type: "text", text: `Error: ${err.message}` }],
77
+ isError: true,
78
+ };
79
+ }
80
+ });
81
+ }
82
+ //# sourceMappingURL=api-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../src/tools/api-keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDomainTools(server: McpServer): void;
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client/ulink-api.js";
3
+ export function registerDomainTools(server) {
4
+ // -----------------------------------------------------------------------
5
+ // list_domains
6
+ // -----------------------------------------------------------------------
7
+ server.registerTool("list_domains", {
8
+ title: "List Domains",
9
+ description: "List all domains associated with a ULink project, including shared domains and any custom domains that have been added. Shows verification status for each domain.",
10
+ annotations: { readOnlyHint: true },
11
+ inputSchema: {
12
+ projectId: z.string().uuid().describe("The project whose domains to list"),
13
+ },
14
+ }, async ({ projectId }) => {
15
+ try {
16
+ const data = await apiRequest("GET", `/domains/projects/${projectId}`);
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
19
+ };
20
+ }
21
+ catch (err) {
22
+ return {
23
+ content: [{ type: "text", text: `Error: ${err.message}` }],
24
+ isError: true,
25
+ };
26
+ }
27
+ });
28
+ // -----------------------------------------------------------------------
29
+ // add_domain
30
+ // -----------------------------------------------------------------------
31
+ server.registerTool("add_domain", {
32
+ title: "Add Domain",
33
+ description: "Add a custom domain to a ULink project. After adding, you must configure DNS records and verify the domain before it can be used for links.",
34
+ inputSchema: {
35
+ projectId: z.string().uuid().describe("The project to add the domain to"),
36
+ host: z.string().describe("The domain hostname to add (e.g. links.example.com)"),
37
+ },
38
+ }, async ({ projectId, host }) => {
39
+ try {
40
+ const data = await apiRequest("POST", "/domains", { projectId, host });
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
43
+ };
44
+ }
45
+ catch (err) {
46
+ return {
47
+ content: [{ type: "text", text: `Error: ${err.message}` }],
48
+ isError: true,
49
+ };
50
+ }
51
+ });
52
+ // -----------------------------------------------------------------------
53
+ // verify_domain
54
+ // -----------------------------------------------------------------------
55
+ server.registerTool("verify_domain", {
56
+ title: "Verify Domain",
57
+ description: "Trigger DNS verification for a custom domain. The domain's DNS records must be correctly configured before verification will succeed. Returns the current verification status.",
58
+ inputSchema: {
59
+ domainId: z.string().uuid().describe("The unique identifier of the domain to verify"),
60
+ },
61
+ }, async ({ domainId }) => {
62
+ try {
63
+ const data = await apiRequest("POST", `/domains/${domainId}/verify`);
64
+ return {
65
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
66
+ };
67
+ }
68
+ catch (err) {
69
+ return {
70
+ content: [{ type: "text", text: `Error: ${err.message}` }],
71
+ isError: true,
72
+ };
73
+ }
74
+ });
75
+ // -----------------------------------------------------------------------
76
+ // delete_domain
77
+ // -----------------------------------------------------------------------
78
+ server.registerTool("delete_domain", {
79
+ title: "Delete Domain",
80
+ description: "Remove a custom domain from a ULink project. Any links using this domain will stop working. This action is irreversible.",
81
+ annotations: { destructiveHint: true },
82
+ inputSchema: {
83
+ domainId: z.string().uuid().describe("The unique identifier of the domain to delete"),
84
+ },
85
+ }, async ({ domainId }) => {
86
+ try {
87
+ await apiRequest("DELETE", `/domains/${domainId}`);
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: `Successfully deleted domain ${domainId}`,
93
+ },
94
+ ],
95
+ };
96
+ }
97
+ catch (err) {
98
+ return {
99
+ content: [{ type: "text", text: `Error: ${err.message}` }],
100
+ isError: true,
101
+ };
102
+ }
103
+ });
104
+ }
105
+ //# sourceMappingURL=domains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domains.js","sourceRoot":"","sources":["../../src/tools/domains.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,0EAA0E;IAC1E,eAAe;IACf,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,oKAAoK;QACtK,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;QACnC,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;SAC3E;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,qBAAqB,SAAS,EAAE,CAAC,CAAC;YACvE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,0EAA0E;IAC1E,aAAa;IACb,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,6IAA6I;QAC/I,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;YACzE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;SACjF;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,0EAA0E;IAC1E,gBAAgB;IAChB,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,gLAAgL;QAClL,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SACtF;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,YAAY,QAAQ,SAAS,CAAC,CAAC;YACrE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACjE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,0EAA0E;IAC1E,gBAAgB;IAChB,0EAA0E;IAC1E,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,0HAA0H;QAC5H,WAAW,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;QACtC,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SACtF;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,QAAQ,EAAE,YAAY,QAAQ,EAAE,CAAC,CAAC;YACnD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,+BAA+B,QAAQ,EAAE;qBAChD;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,GAAW,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerLinkTools(server: McpServer): void;
@@ -0,0 +1,263 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client/ulink-api.js";
3
+ export function registerLinkTools(server) {
4
+ // -----------------------------------------------------------------------
5
+ // create_link
6
+ // -----------------------------------------------------------------------
7
+ server.registerTool("create_link", {
8
+ title: "Create Link",
9
+ description: "Create a new smart link in a ULink project. Supports unified links (single URL that routes by platform) and dynamic links (parameterised deep links). You must specify the project, domain, and link type. Optionally set platform-specific URLs, fallback URLs, custom slug, metadata, and parameters.",
10
+ inputSchema: {
11
+ projectId: z.string().uuid().describe("The project to create the link in"),
12
+ domainId: z.string().uuid().describe("The domain to host the link on"),
13
+ type: z
14
+ .enum(["unified", "dynamic"])
15
+ .describe("Link type: 'unified' for smart routing or 'dynamic' for parameterised deep links"),
16
+ slug: z.string().optional().describe("Custom slug for the short URL (auto-generated if omitted)"),
17
+ name: z.string().optional().describe("Human-readable name for the link"),
18
+ iosUrl: z.string().optional().describe("URL to open on iOS devices"),
19
+ androidUrl: z.string().optional().describe("URL to open on Android devices"),
20
+ fallbackUrl: z.string().optional().describe("Fallback URL for unsupported platforms"),
21
+ iosFallbackUrl: z
22
+ .string()
23
+ .optional()
24
+ .describe("iOS-specific fallback URL (e.g. App Store link)"),
25
+ androidFallbackUrl: z
26
+ .string()
27
+ .optional()
28
+ .describe("Android-specific fallback URL (e.g. Play Store link)"),
29
+ parameters: z
30
+ .record(z.string())
31
+ .optional()
32
+ .describe("Key-value parameters passed through the deep link"),
33
+ metadata: z
34
+ .record(z.unknown())
35
+ .optional()
36
+ .describe("Arbitrary metadata attached to the link"),
37
+ },
38
+ }, async ({ projectId, domainId, type, slug, name, iosUrl, androidUrl, fallbackUrl, iosFallbackUrl, androidFallbackUrl, parameters, metadata }) => {
39
+ try {
40
+ const body = { type };
41
+ if (slug !== undefined)
42
+ body.slug = slug;
43
+ if (name !== undefined)
44
+ body.name = name;
45
+ if (iosUrl !== undefined)
46
+ body.iosUrl = iosUrl;
47
+ if (androidUrl !== undefined)
48
+ body.androidUrl = androidUrl;
49
+ if (fallbackUrl !== undefined)
50
+ body.fallbackUrl = fallbackUrl;
51
+ if (iosFallbackUrl !== undefined)
52
+ body.iosFallbackUrl = iosFallbackUrl;
53
+ if (androidFallbackUrl !== undefined)
54
+ body.androidFallbackUrl = androidFallbackUrl;
55
+ if (parameters !== undefined)
56
+ body.parameters = parameters;
57
+ if (metadata !== undefined)
58
+ body.metadata = metadata;
59
+ const qs = domainId ? `?domainId=${encodeURIComponent(domainId)}` : "";
60
+ const data = await apiRequest("POST", `/api/v1/projects/${projectId}/links${qs}`, body);
61
+ return {
62
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
63
+ };
64
+ }
65
+ catch (err) {
66
+ return {
67
+ content: [{ type: "text", text: `Error: ${err.message}` }],
68
+ isError: true,
69
+ };
70
+ }
71
+ });
72
+ // -----------------------------------------------------------------------
73
+ // list_links
74
+ // -----------------------------------------------------------------------
75
+ server.registerTool("list_links", {
76
+ title: "List Links",
77
+ description: "List all links in a ULink project with optional pagination. Returns an array of link objects with their configuration, URLs, and metadata.",
78
+ annotations: { readOnlyHint: true },
79
+ inputSchema: {
80
+ projectId: z.string().uuid().describe("The project whose links to list"),
81
+ offset: z
82
+ .number()
83
+ .int()
84
+ .min(0)
85
+ .optional()
86
+ .describe("Number of links to skip for pagination (starts at 0)"),
87
+ limit: z
88
+ .number()
89
+ .int()
90
+ .positive()
91
+ .max(100)
92
+ .optional()
93
+ .describe("Number of links to return (max 100)"),
94
+ },
95
+ }, async ({ projectId, offset, limit }) => {
96
+ try {
97
+ const params = new URLSearchParams();
98
+ if (offset !== undefined)
99
+ params.set("offset", String(offset));
100
+ if (limit !== undefined)
101
+ params.set("limit", String(limit));
102
+ const qs = params.toString();
103
+ const path = `/api/v1/projects/${projectId}/links${qs ? `?${qs}` : ""}`;
104
+ const data = await apiRequest("GET", path);
105
+ return {
106
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
107
+ };
108
+ }
109
+ catch (err) {
110
+ return {
111
+ content: [{ type: "text", text: `Error: ${err.message}` }],
112
+ isError: true,
113
+ };
114
+ }
115
+ });
116
+ // -----------------------------------------------------------------------
117
+ // get_link
118
+ // -----------------------------------------------------------------------
119
+ server.registerTool("get_link", {
120
+ title: "Get Link",
121
+ description: "Retrieve detailed information about a specific link by its ID, including all platform URLs, parameters, metadata, and current configuration.",
122
+ annotations: { readOnlyHint: true },
123
+ inputSchema: {
124
+ linkId: z.string().uuid().describe("The unique identifier of the link"),
125
+ },
126
+ }, async ({ linkId }) => {
127
+ try {
128
+ const data = await apiRequest("GET", `/api/v1/links/${linkId}`);
129
+ return {
130
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
131
+ };
132
+ }
133
+ catch (err) {
134
+ return {
135
+ content: [{ type: "text", text: `Error: ${err.message}` }],
136
+ isError: true,
137
+ };
138
+ }
139
+ });
140
+ // -----------------------------------------------------------------------
141
+ // update_link
142
+ // -----------------------------------------------------------------------
143
+ server.registerTool("update_link", {
144
+ title: "Update Link",
145
+ description: "Update an existing link's properties. You can change the name, platform-specific URLs, fallback URLs, parameters, and metadata. Only the fields you provide will be modified.",
146
+ inputSchema: {
147
+ linkId: z.string().uuid().describe("The unique identifier of the link to update"),
148
+ name: z.string().optional().describe("New human-readable name for the link"),
149
+ iosUrl: z.string().optional().describe("New URL to open on iOS devices"),
150
+ androidUrl: z.string().optional().describe("New URL to open on Android devices"),
151
+ fallbackUrl: z.string().optional().describe("New fallback URL for unsupported platforms"),
152
+ iosFallbackUrl: z
153
+ .string()
154
+ .optional()
155
+ .describe("New iOS-specific fallback URL"),
156
+ androidFallbackUrl: z
157
+ .string()
158
+ .optional()
159
+ .describe("New Android-specific fallback URL"),
160
+ parameters: z
161
+ .record(z.string())
162
+ .optional()
163
+ .describe("New key-value parameters passed through the deep link"),
164
+ metadata: z
165
+ .record(z.unknown())
166
+ .optional()
167
+ .describe("New arbitrary metadata attached to the link"),
168
+ },
169
+ }, async ({ linkId, name, iosUrl, androidUrl, fallbackUrl, iosFallbackUrl, androidFallbackUrl, parameters, metadata }) => {
170
+ try {
171
+ const body = {};
172
+ if (name !== undefined)
173
+ body.name = name;
174
+ if (iosUrl !== undefined)
175
+ body.iosUrl = iosUrl;
176
+ if (androidUrl !== undefined)
177
+ body.androidUrl = androidUrl;
178
+ if (fallbackUrl !== undefined)
179
+ body.fallbackUrl = fallbackUrl;
180
+ if (iosFallbackUrl !== undefined)
181
+ body.iosFallbackUrl = iosFallbackUrl;
182
+ if (androidFallbackUrl !== undefined)
183
+ body.androidFallbackUrl = androidFallbackUrl;
184
+ if (parameters !== undefined)
185
+ body.parameters = parameters;
186
+ if (metadata !== undefined)
187
+ body.metadata = metadata;
188
+ const data = await apiRequest("PUT", `/api/v1/links/${linkId}`, body);
189
+ return {
190
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
191
+ };
192
+ }
193
+ catch (err) {
194
+ return {
195
+ content: [{ type: "text", text: `Error: ${err.message}` }],
196
+ isError: true,
197
+ };
198
+ }
199
+ });
200
+ // -----------------------------------------------------------------------
201
+ // delete_link
202
+ // -----------------------------------------------------------------------
203
+ server.registerTool("delete_link", {
204
+ title: "Delete Link",
205
+ description: "Permanently delete a link. This is irreversible and the short URL will stop working immediately. Use with caution.",
206
+ annotations: { destructiveHint: true },
207
+ inputSchema: {
208
+ linkId: z.string().uuid().describe("The unique identifier of the link to delete"),
209
+ },
210
+ }, async ({ linkId }) => {
211
+ try {
212
+ await apiRequest("DELETE", `/api/v1/links/${linkId}`);
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: `Successfully deleted link ${linkId}`,
218
+ },
219
+ ],
220
+ };
221
+ }
222
+ catch (err) {
223
+ return {
224
+ content: [{ type: "text", text: `Error: ${err.message}` }],
225
+ isError: true,
226
+ };
227
+ }
228
+ });
229
+ // -----------------------------------------------------------------------
230
+ // get_link_analytics
231
+ // -----------------------------------------------------------------------
232
+ server.registerTool("get_link_analytics", {
233
+ title: "Get Link Analytics",
234
+ description: "Retrieve click analytics for a specific link. Returns aggregated data such as total clicks, unique clicks, and breakdowns by platform, country, and referrer for the requested time period.",
235
+ annotations: { readOnlyHint: true },
236
+ inputSchema: {
237
+ linkId: z.string().uuid().describe("The unique identifier of the link"),
238
+ period: z
239
+ .enum(["24h", "7d", "30d", "90d"])
240
+ .optional()
241
+ .describe("Time period for analytics data (defaults to 7d)"),
242
+ },
243
+ }, async ({ linkId, period }) => {
244
+ try {
245
+ const params = new URLSearchParams();
246
+ if (period !== undefined)
247
+ params.set("period", period);
248
+ const qs = params.toString();
249
+ const path = `/api/v1/links/${linkId}/analytics${qs ? `?${qs}` : ""}`;
250
+ const data = await apiRequest("GET", path);
251
+ return {
252
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
253
+ };
254
+ }
255
+ catch (err) {
256
+ return {
257
+ content: [{ type: "text", text: `Error: ${err.message}` }],
258
+ isError: true,
259
+ };
260
+ }
261
+ });
262
+ }
263
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/tools/links.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProjectTools(server: McpServer): void;
@@ -0,0 +1,164 @@
1
+ import { z } from "zod";
2
+ import { apiRequest } from "../client/ulink-api.js";
3
+ export function registerProjectTools(server) {
4
+ // -----------------------------------------------------------------------
5
+ // list_projects
6
+ // -----------------------------------------------------------------------
7
+ server.registerTool("list_projects", {
8
+ title: "List Projects",
9
+ description: "List all ULink projects owned by or shared with the authenticated user. Returns an array of project objects including id, name, slug, and default URL.",
10
+ annotations: { readOnlyHint: true },
11
+ }, async () => {
12
+ try {
13
+ const data = await apiRequest("GET", "/projects");
14
+ return {
15
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
16
+ };
17
+ }
18
+ catch (err) {
19
+ return {
20
+ content: [{ type: "text", text: `Error: ${err.message}` }],
21
+ isError: true,
22
+ };
23
+ }
24
+ });
25
+ // -----------------------------------------------------------------------
26
+ // get_project
27
+ // -----------------------------------------------------------------------
28
+ server.registerTool("get_project", {
29
+ title: "Get Project",
30
+ description: "Retrieve detailed information about a specific ULink project by its ID, including configuration, domains, and membership details.",
31
+ annotations: { readOnlyHint: true },
32
+ inputSchema: {
33
+ projectId: z.string().uuid().describe("The unique identifier of the project"),
34
+ },
35
+ }, async ({ projectId }) => {
36
+ try {
37
+ const data = await apiRequest("GET", `/projects/${projectId}`);
38
+ return {
39
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
40
+ };
41
+ }
42
+ catch (err) {
43
+ return {
44
+ content: [{ type: "text", text: `Error: ${err.message}` }],
45
+ isError: true,
46
+ };
47
+ }
48
+ });
49
+ // -----------------------------------------------------------------------
50
+ // create_project
51
+ // -----------------------------------------------------------------------
52
+ server.registerTool("create_project", {
53
+ title: "Create Project",
54
+ description: "Create a new ULink project. A project is the top-level container for links, domains, and API keys. Requires a name and a default fallback URL.",
55
+ inputSchema: {
56
+ name: z.string().describe("Human-readable name for the project"),
57
+ defaultUrl: z
58
+ .string()
59
+ .url()
60
+ .describe("Default fallback URL used when no platform-specific URL matches"),
61
+ },
62
+ }, async ({ name, defaultUrl }) => {
63
+ try {
64
+ const data = await apiRequest("POST", "/projects", { name, default_url: defaultUrl });
65
+ return {
66
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
67
+ };
68
+ }
69
+ catch (err) {
70
+ return {
71
+ content: [{ type: "text", text: `Error: ${err.message}` }],
72
+ isError: true,
73
+ };
74
+ }
75
+ });
76
+ // -----------------------------------------------------------------------
77
+ // update_project
78
+ // -----------------------------------------------------------------------
79
+ server.registerTool("update_project", {
80
+ title: "Update Project",
81
+ description: "Update the name and/or default URL of an existing ULink project. Only the fields you provide will be changed.",
82
+ inputSchema: {
83
+ projectId: z.string().uuid().describe("The unique identifier of the project to update"),
84
+ name: z.string().optional().describe("New name for the project"),
85
+ defaultUrl: z.string().url().optional().describe("New default fallback URL"),
86
+ },
87
+ }, async ({ projectId, name, defaultUrl }) => {
88
+ try {
89
+ const body = {};
90
+ if (name !== undefined)
91
+ body.name = name;
92
+ if (defaultUrl !== undefined)
93
+ body.default_url = defaultUrl;
94
+ const data = await apiRequest("PATCH", `/projects/${projectId}`, body);
95
+ return {
96
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
97
+ };
98
+ }
99
+ catch (err) {
100
+ return {
101
+ content: [{ type: "text", text: `Error: ${err.message}` }],
102
+ isError: true,
103
+ };
104
+ }
105
+ });
106
+ // -----------------------------------------------------------------------
107
+ // configure_project
108
+ // -----------------------------------------------------------------------
109
+ server.registerTool("configure_project", {
110
+ title: "Configure Project",
111
+ description: "Update platform-specific configuration for a ULink project, such as iOS bundle identifier, Android package name, deeplink schemas, and SHA-256 fingerprints. These settings are used for deep link resolution on mobile platforms.",
112
+ inputSchema: {
113
+ projectId: z.string().uuid().describe("The unique identifier of the project to configure"),
114
+ androidPackageName: z
115
+ .string()
116
+ .optional()
117
+ .describe("Android package name (e.g. com.example.app)"),
118
+ iosBundleIdentifier: z
119
+ .string()
120
+ .optional()
121
+ .describe("iOS bundle identifier (e.g. com.example.app)"),
122
+ iosTeamId: z.string().optional().describe("Apple Developer Team ID"),
123
+ iosDeeplinkSchema: z
124
+ .string()
125
+ .optional()
126
+ .describe("iOS deeplink URI scheme (e.g. myapp://)"),
127
+ androidDeeplinkSchema: z
128
+ .string()
129
+ .optional()
130
+ .describe("Android deeplink URI scheme (e.g. myapp://)"),
131
+ androidSha256Fingerprints: z
132
+ .array(z.string())
133
+ .optional()
134
+ .describe("SHA-256 certificate fingerprints for Android App Links verification"),
135
+ },
136
+ }, async ({ projectId, androidPackageName, iosBundleIdentifier, iosTeamId, iosDeeplinkSchema, androidDeeplinkSchema, androidSha256Fingerprints, }) => {
137
+ try {
138
+ const body = {};
139
+ if (androidPackageName !== undefined)
140
+ body.android_package_name = androidPackageName;
141
+ if (iosBundleIdentifier !== undefined)
142
+ body.ios_bundle_identifier = iosBundleIdentifier;
143
+ if (iosTeamId !== undefined)
144
+ body.ios_team_id = iosTeamId;
145
+ if (iosDeeplinkSchema !== undefined)
146
+ body.ios_deeplink_schema = iosDeeplinkSchema;
147
+ if (androidDeeplinkSchema !== undefined)
148
+ body.android_deeplink_schema = androidDeeplinkSchema;
149
+ if (androidSha256Fingerprints !== undefined)
150
+ body.android_sha256_fingerprints = androidSha256Fingerprints;
151
+ const data = await apiRequest("PATCH", `/projects/${projectId}/configuration`, body);
152
+ return {
153
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
154
+ };
155
+ }
156
+ catch (err) {
157
+ return {
158
+ content: [{ type: "text", text: `Error: ${err.message}` }],
159
+ isError: true,
160
+ };
161
+ }
162
+ });
163
+ }
164
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@ulinkly/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "ULink MCP Server — Model Context Protocol server for the ULink REST API",
5
+ "type": "module",
6
+ "bin": {
7
+ "ulink-mcp-server": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc && node -e \"const fs=require('fs');const f='dist/index.js';const c=fs.readFileSync(f,'utf8');if(!c.startsWith('#!')){fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);}\" && chmod 755 dist/index.js",
15
+ "dev": "tsc --watch",
16
+ "prepare": "npm run build"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.26.0",
23
+ "zod": "^3.22.4"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22",
27
+ "typescript": "^5.5.4"
28
+ }
29
+ }