Skip to content

Commit 55d4fca

Browse files
committed
Share MCP browser config and response helpers
1 parent 692e156 commit 55d4fca

7 files changed

Lines changed: 227 additions & 498 deletions

File tree

src/lib/mcp/browser-config.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
export type BrowserProfileParams = {
2+
profile_name?: string;
3+
profile_id?: string;
4+
save_profile_changes?: boolean;
5+
};
6+
7+
export type BrowserExtensionParams = {
8+
extension_id?: string;
9+
extension_name?: string;
10+
};
11+
12+
export type BrowserViewportParams = {
13+
viewport_width?: number;
14+
viewport_height?: number;
15+
viewport_refresh_rate?: number;
16+
};
17+
18+
export type BrowserViewportUpdateParams = BrowserViewportParams & {
19+
viewport_force?: boolean;
20+
};
21+
22+
export function buildBrowserProfile(params: BrowserProfileParams) {
23+
if (params.profile_name && params.profile_id) {
24+
throw new Error("Cannot specify both profile_name and profile_id.");
25+
}
26+
if (
27+
params.save_profile_changes !== undefined &&
28+
!params.profile_name &&
29+
!params.profile_id
30+
) {
31+
throw new Error(
32+
"profile_name or profile_id is required when save_profile_changes is set.",
33+
);
34+
}
35+
if (!params.profile_name && !params.profile_id) return undefined;
36+
return {
37+
...(params.profile_name && { name: params.profile_name }),
38+
...(params.profile_id && { id: params.profile_id }),
39+
...(params.save_profile_changes !== undefined && {
40+
save_changes: params.save_profile_changes,
41+
}),
42+
};
43+
}
44+
45+
export function buildBrowserExtensions(params: BrowserExtensionParams) {
46+
if (params.extension_id && params.extension_name) {
47+
throw new Error("Cannot specify both extension_id and extension_name.");
48+
}
49+
if (!params.extension_id && !params.extension_name) return undefined;
50+
return [
51+
{
52+
...(params.extension_id && { id: params.extension_id }),
53+
...(params.extension_name && { name: params.extension_name }),
54+
},
55+
];
56+
}
57+
58+
export function buildBrowserViewport(params: BrowserViewportParams) {
59+
const width = params.viewport_width;
60+
const height = params.viewport_height;
61+
const hasViewportOptions =
62+
width !== undefined ||
63+
height !== undefined ||
64+
params.viewport_refresh_rate !== undefined;
65+
66+
if (!hasViewportOptions) return undefined;
67+
if (width === undefined || height === undefined) {
68+
throw new Error(
69+
"viewport_width and viewport_height must be provided together.",
70+
);
71+
}
72+
73+
return {
74+
width,
75+
height,
76+
...(params.viewport_refresh_rate !== undefined && {
77+
refresh_rate: params.viewport_refresh_rate,
78+
}),
79+
};
80+
}
81+
82+
export function buildBrowserViewportUpdate(
83+
params: BrowserViewportUpdateParams,
84+
) {
85+
const viewport = buildBrowserViewport(params);
86+
87+
if (!viewport) {
88+
if (params.viewport_force !== undefined) {
89+
throw new Error(
90+
"viewport_width and viewport_height must be provided when viewport_force is set.",
91+
);
92+
}
93+
return undefined;
94+
}
95+
96+
return {
97+
...viewport,
98+
...(params.viewport_force !== undefined && {
99+
force: params.viewport_force,
100+
}),
101+
};
102+
}

src/lib/mcp/responses.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function textResponse(text: string) {
2+
return { content: [{ type: "text" as const, text }] };
3+
}
4+
5+
export function jsonResponse(value: unknown) {
6+
return textResponse(JSON.stringify(value, null, 2));
7+
}
8+
9+
export function errorMessage(error: unknown) {
10+
return error instanceof Error ? error.message : String(error);
11+
}

src/lib/mcp/tools/api-keys.ts

Lines changed: 18 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z } from "zod";
33
import { createKernelClient } from "@/lib/mcp/kernel-client";
4+
import { errorMessage, jsonResponse, textResponse } from "@/lib/mcp/responses";
45

56
export function registerAPIKeyCapabilities(server: McpServer) {
67
// manage_api_keys -- Create, list, get, update, and delete Kernel API keys
@@ -44,11 +45,7 @@ export function registerAPIKeyCapabilities(server: McpServer) {
4445
switch (params.action) {
4546
case "create": {
4647
if (!params.name) {
47-
return {
48-
content: [
49-
{ type: "text", text: "Error: name is required for create." },
50-
],
51-
};
48+
return textResponse("Error: name is required for create.");
5249
}
5350
const createParams: Parameters<typeof client.apiKeys.create>[0] = {
5451
name: params.name,
@@ -60,106 +57,51 @@ export function registerAPIKeyCapabilities(server: McpServer) {
6057
createParams.days_to_expire = params.days_to_expire;
6158
}
6259
const apiKey = await client.apiKeys.create(createParams);
63-
return {
64-
content: [
65-
{ type: "text", text: JSON.stringify(apiKey, null, 2) },
66-
],
67-
};
60+
return jsonResponse(apiKey);
6861
}
6962
case "list": {
7063
const page = await client.apiKeys.list({
7164
...(params.limit !== undefined && { limit: params.limit }),
7265
...(params.offset !== undefined && { offset: params.offset }),
7366
});
7467
const items = page.getPaginatedItems();
75-
return {
76-
content: [
77-
{
78-
type: "text",
79-
text: JSON.stringify(
80-
{
81-
items,
82-
has_more: page.has_more,
83-
next_offset: page.next_offset,
84-
},
85-
null,
86-
2,
87-
),
88-
},
89-
],
90-
};
68+
return jsonResponse({
69+
items,
70+
has_more: page.has_more,
71+
next_offset: page.next_offset,
72+
});
9173
}
9274
case "get": {
9375
if (!params.api_key_id) {
94-
return {
95-
content: [
96-
{
97-
type: "text",
98-
text: "Error: api_key_id is required for get.",
99-
},
100-
],
101-
};
76+
return textResponse("Error: api_key_id is required for get.");
10277
}
10378
const apiKey = await client.apiKeys.retrieve(params.api_key_id);
104-
return {
105-
content: [
106-
{ type: "text", text: JSON.stringify(apiKey, null, 2) },
107-
],
108-
};
79+
return jsonResponse(apiKey);
10980
}
11081
case "update": {
11182
if (!params.api_key_id) {
112-
return {
113-
content: [
114-
{
115-
type: "text",
116-
text: "Error: api_key_id is required for update.",
117-
},
118-
],
119-
};
83+
return textResponse("Error: api_key_id is required for update.");
12084
}
12185
if (!params.name) {
122-
return {
123-
content: [
124-
{ type: "text", text: "Error: name is required for update." },
125-
],
126-
};
86+
return textResponse("Error: name is required for update.");
12787
}
12888
const apiKey = await client.apiKeys.update(params.api_key_id, {
12989
name: params.name,
13090
});
131-
return {
132-
content: [
133-
{ type: "text", text: JSON.stringify(apiKey, null, 2) },
134-
],
135-
};
91+
return jsonResponse(apiKey);
13692
}
13793
case "delete": {
13894
if (!params.api_key_id) {
139-
return {
140-
content: [
141-
{
142-
type: "text",
143-
text: "Error: api_key_id is required for delete.",
144-
},
145-
],
146-
};
95+
return textResponse("Error: api_key_id is required for delete.");
14796
}
14897
await client.apiKeys.delete(params.api_key_id);
149-
return {
150-
content: [{ type: "text", text: "API key deleted successfully" }],
151-
};
98+
return textResponse("API key deleted successfully");
15299
}
153100
}
154101
} catch (error) {
155-
return {
156-
content: [
157-
{
158-
type: "text",
159-
text: `Error in manage_api_keys (${params.action}): ${error instanceof Error ? error.message : String(error)}`,
160-
},
161-
],
162-
};
102+
return textResponse(
103+
`Error in manage_api_keys (${params.action}): ${errorMessage(error)}`,
104+
);
163105
}
164106
},
165107
);

0 commit comments

Comments
 (0)