@ulinkly/mcp-server 0.1.14 → 0.1.16
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.
|
@@ -70,6 +70,32 @@ describe("Link tools", () => {
|
|
|
70
70
|
expect(result.isError).toBe(true);
|
|
71
71
|
expect(result.content[0].text).toContain("Quota exceeded");
|
|
72
72
|
});
|
|
73
|
+
it("forwards externalId to the API body when provided", async () => {
|
|
74
|
+
mockedApiRequest.mockResolvedValue({ id: "link-1", slug: "abc" });
|
|
75
|
+
const handler = getHandler("create_link");
|
|
76
|
+
await handler({
|
|
77
|
+
projectId: "00000000-0000-0000-0000-000000000001",
|
|
78
|
+
domainId: "00000000-0000-0000-0000-000000000002",
|
|
79
|
+
type: "unified",
|
|
80
|
+
iosUrl: "https://apps.apple.com/app/123",
|
|
81
|
+
androidUrl: "https://play.google.com/store/apps/details?id=com.example",
|
|
82
|
+
externalId: "share:user:123:post:456",
|
|
83
|
+
});
|
|
84
|
+
expect(mockedApiRequest).toHaveBeenCalledWith("POST", expect.stringContaining("/api/v1/projects/"), expect.objectContaining({ externalId: "share:user:123:post:456" }));
|
|
85
|
+
});
|
|
86
|
+
it("omits externalId from the API body when not provided (backward compat)", async () => {
|
|
87
|
+
mockedApiRequest.mockResolvedValue({ id: "link-1" });
|
|
88
|
+
const handler = getHandler("create_link");
|
|
89
|
+
await handler({
|
|
90
|
+
projectId: "00000000-0000-0000-0000-000000000001",
|
|
91
|
+
domainId: "00000000-0000-0000-0000-000000000002",
|
|
92
|
+
type: "unified",
|
|
93
|
+
iosUrl: "https://apps.apple.com/app/123",
|
|
94
|
+
androidUrl: "https://play.google.com/store/apps/details?id=com.example",
|
|
95
|
+
});
|
|
96
|
+
const [, , body] = mockedApiRequest.mock.calls[0];
|
|
97
|
+
expect(body).not.toHaveProperty("externalId");
|
|
98
|
+
});
|
|
73
99
|
});
|
|
74
100
|
// ---------------------------------------------------------------------------
|
|
75
101
|
// list_links
|
package/dist/tools/links.js
CHANGED
|
@@ -6,7 +6,7 @@ export function registerLinkTools(server) {
|
|
|
6
6
|
// -----------------------------------------------------------------------
|
|
7
7
|
server.registerTool("create_link", {
|
|
8
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.",
|
|
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. Pass externalId (any stable string from your system) to make creation idempotent on (project, externalId).",
|
|
10
10
|
inputSchema: {
|
|
11
11
|
projectId: z.string().uuid().describe("The project to create the link in"),
|
|
12
12
|
domainId: z.string().uuid().describe("The domain to host the link on"),
|
|
@@ -15,6 +15,7 @@ export function registerLinkTools(server) {
|
|
|
15
15
|
.describe("Link type: 'unified' for smart routing or 'dynamic' for parameterised deep links"),
|
|
16
16
|
slug: z.string().optional().describe("Custom slug for the short URL (auto-generated if omitted)"),
|
|
17
17
|
name: z.string().optional().describe("Human-readable name for the link"),
|
|
18
|
+
externalId: z.string().min(1).max(255).regex(/^\S+$/, "externalId must not contain whitespace").optional().describe("Optional stable identifier from your system (e.g. 'share:user-123:post-456'). Repeated calls with the same (project, externalId) return the existing link instead of creating a duplicate. Idempotent."),
|
|
18
19
|
iosUrl: z.string().url().startsWith("https://", { message: "URL must use HTTPS" }).optional().describe("URL to open on iOS devices"),
|
|
19
20
|
androidUrl: z.string().url().startsWith("https://", { message: "URL must use HTTPS" }).optional().describe("URL to open on Android devices"),
|
|
20
21
|
fallbackUrl: z.string().url().startsWith("https://", { message: "URL must use HTTPS" }).optional().describe("Fallback URL for unsupported platforms"),
|
|
@@ -35,13 +36,15 @@ export function registerLinkTools(server) {
|
|
|
35
36
|
.optional()
|
|
36
37
|
.describe("Arbitrary metadata attached to the link"),
|
|
37
38
|
},
|
|
38
|
-
}, async ({ projectId, domainId, type, slug, name, iosUrl, androidUrl, fallbackUrl, iosFallbackUrl, androidFallbackUrl, parameters, metadata }) => {
|
|
39
|
+
}, async ({ projectId, domainId, type, slug, name, externalId, iosUrl, androidUrl, fallbackUrl, iosFallbackUrl, androidFallbackUrl, parameters, metadata }) => {
|
|
39
40
|
try {
|
|
40
41
|
const body = { type };
|
|
41
42
|
if (slug !== undefined)
|
|
42
43
|
body.slug = slug;
|
|
43
44
|
if (name !== undefined)
|
|
44
45
|
body.name = name;
|
|
46
|
+
if (externalId !== undefined)
|
|
47
|
+
body.externalId = externalId;
|
|
45
48
|
if (iosUrl !== undefined)
|
|
46
49
|
body.iosUrl = iosUrl;
|
|
47
50
|
if (androidUrl !== undefined)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ulinkly/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"mcpName": "io.github.
|
|
3
|
+
"version": "0.1.16",
|
|
4
|
+
"mcpName": "io.github.FlywheelStudio/ulink",
|
|
5
5
|
"description": "ULink MCP Server — Model Context Protocol server for the ULink REST API",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|