@nuxtjs/mcp-toolkit 0.4.1 → 0.5.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 +3 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +14 -2
- package/dist/runtime/components/InstallButton.d.vue.ts +38 -0
- package/dist/runtime/components/InstallButton.vue +172 -0
- package/dist/runtime/components/InstallButton.vue.d.ts +38 -0
- package/dist/runtime/server/mcp/badge-image.d.ts +3 -0
- package/dist/runtime/server/mcp/badge-image.js +154 -0
- package/dist/runtime/server/mcp/deeplink.d.ts +3 -0
- package/dist/runtime/server/mcp/deeplink.js +62 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
+
[](https://mcp-toolkit.nuxt.dev/mcp/deeplink)
|
|
4
|
+
[](https://mcp-toolkit.nuxt.dev/mcp/deeplink?ide=vscode)
|
|
5
|
+
|
|
3
6
|
# Nuxt MCP Toolkit
|
|
4
7
|
|
|
5
8
|
<!-- automd:badges color="black" license name="@nuxtjs/mcp-toolkit" -->
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger, createResolver, defineNuxtModule, addServerHandler, addServerImports } from '@nuxt/kit';
|
|
1
|
+
import { logger, createResolver, defineNuxtModule, addComponent, addServerHandler, addServerImports } from '@nuxt/kit';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
3
|
import { loadAllDefinitions } from '../dist/runtime/server/mcp/loaders/index.js';
|
|
4
4
|
import { defaultMcpConfig } from '../dist/runtime/server/mcp/config.js';
|
|
@@ -6,7 +6,7 @@ import { ROUTES } from '../dist/runtime/server/mcp/constants.js';
|
|
|
6
6
|
import { addDevToolsCustomTabs } from '../dist/runtime/server/mcp/devtools/index.js';
|
|
7
7
|
|
|
8
8
|
const name = "@nuxtjs/mcp-toolkit";
|
|
9
|
-
const version = "0.
|
|
9
|
+
const version = "0.5.0";
|
|
10
10
|
|
|
11
11
|
const log = logger.withTag("@nuxtjs/mcp-toolkit");
|
|
12
12
|
const { resolve } = createResolver(import.meta.url);
|
|
@@ -35,6 +35,10 @@ const module$1 = defineNuxtModule({
|
|
|
35
35
|
if (!options.enabled) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
addComponent({
|
|
39
|
+
name: "InstallButton",
|
|
40
|
+
filePath: resolver.resolve("runtime/components/InstallButton.vue")
|
|
41
|
+
});
|
|
38
42
|
const mcpDir = options.dir ?? defaultMcpConfig.dir;
|
|
39
43
|
const paths = {
|
|
40
44
|
tools: [`${mcpDir}/tools`],
|
|
@@ -102,6 +106,14 @@ const module$1 = defineNuxtModule({
|
|
|
102
106
|
route: options.route,
|
|
103
107
|
handler: resolver.resolve("runtime/server/mcp/handler")
|
|
104
108
|
});
|
|
109
|
+
addServerHandler({
|
|
110
|
+
route: `${options.route}/deeplink`,
|
|
111
|
+
handler: resolver.resolve("runtime/server/mcp/deeplink")
|
|
112
|
+
});
|
|
113
|
+
addServerHandler({
|
|
114
|
+
route: `${options.route}/badge.svg`,
|
|
115
|
+
handler: resolver.resolve("runtime/server/mcp/badge-image")
|
|
116
|
+
});
|
|
105
117
|
addDevToolsCustomTabs(nuxt, options);
|
|
106
118
|
}
|
|
107
119
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type SupportedIDE = 'cursor' | 'vscode';
|
|
2
|
+
export interface InstallButtonProps {
|
|
3
|
+
/**
|
|
4
|
+
* URL of the MCP server endpoint (e.g., 'https://example.com/mcp')
|
|
5
|
+
* The deeplink will be derived as '{url}/deeplink?ide=xxx'
|
|
6
|
+
*/
|
|
7
|
+
url: string;
|
|
8
|
+
/**
|
|
9
|
+
* Target IDE
|
|
10
|
+
* @default 'cursor'
|
|
11
|
+
*/
|
|
12
|
+
ide?: SupportedIDE;
|
|
13
|
+
/**
|
|
14
|
+
* Button label (auto-generated based on IDE if not provided)
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Show the IDE icon
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
showIcon?: boolean;
|
|
22
|
+
}
|
|
23
|
+
declare var __VLS_1: {};
|
|
24
|
+
type __VLS_Slots = {} & {
|
|
25
|
+
default?: (props: typeof __VLS_1) => any;
|
|
26
|
+
};
|
|
27
|
+
declare const __VLS_base: import("@vue/runtime-core").DefineComponent<InstallButtonProps, {}, {}, {}, {}, import("@vue/runtime-core").ComponentOptionsMixin, import("@vue/runtime-core").ComponentOptionsMixin, {}, string, import("@vue/runtime-core").PublicProps, Readonly<InstallButtonProps> & Readonly<{}>, {
|
|
28
|
+
ide: SupportedIDE;
|
|
29
|
+
showIcon: boolean;
|
|
30
|
+
}, {}, {}, {}, string, import("@vue/runtime-core").ComponentProvideOptions, false, {}, any>;
|
|
31
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
32
|
+
declare const _default: typeof __VLS_export;
|
|
33
|
+
export default _default;
|
|
34
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
35
|
+
new (): {
|
|
36
|
+
$slots: S;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
const IDE_CONFIG = {
|
|
4
|
+
cursor: {
|
|
5
|
+
name: "Cursor",
|
|
6
|
+
defaultLabel: "Install MCP in Cursor"
|
|
7
|
+
},
|
|
8
|
+
vscode: {
|
|
9
|
+
name: "VS Code",
|
|
10
|
+
defaultLabel: "Install MCP in VS Code"
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
url: { type: String, required: true },
|
|
15
|
+
ide: { type: String, required: false, default: "cursor" },
|
|
16
|
+
label: { type: String, required: false },
|
|
17
|
+
showIcon: { type: Boolean, required: false, default: true }
|
|
18
|
+
});
|
|
19
|
+
const ideConfig = computed(() => IDE_CONFIG[props.ide]);
|
|
20
|
+
const buttonLabel = computed(() => props.label ?? ideConfig.value.defaultLabel);
|
|
21
|
+
const deeplink = computed(() => {
|
|
22
|
+
const baseUrl = props.url.replace(/\/$/, "");
|
|
23
|
+
return `${baseUrl}/deeplink?ide=${props.ide}`;
|
|
24
|
+
});
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<a
|
|
29
|
+
:href="deeplink"
|
|
30
|
+
class="mcp-install-button"
|
|
31
|
+
v-bind="$attrs"
|
|
32
|
+
>
|
|
33
|
+
<!-- Cursor Icon -->
|
|
34
|
+
<svg
|
|
35
|
+
v-if="showIcon && ide === 'cursor'"
|
|
36
|
+
class="mcp-install-button-icon"
|
|
37
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
+
width="1em"
|
|
39
|
+
height="1em"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
>
|
|
43
|
+
<path
|
|
44
|
+
fill="url(#mcp-cursor-fill-0)"
|
|
45
|
+
d="M11.925 24l10.425-6-10.425-6L1.5 18l10.425 6z"
|
|
46
|
+
/>
|
|
47
|
+
<path
|
|
48
|
+
fill="url(#mcp-cursor-fill-1)"
|
|
49
|
+
d="M22.35 18V6L11.925 0v12l10.425 6z"
|
|
50
|
+
/>
|
|
51
|
+
<path
|
|
52
|
+
fill="url(#mcp-cursor-fill-2)"
|
|
53
|
+
d="M11.925 0L1.5 6v12l10.425-6V0z"
|
|
54
|
+
/>
|
|
55
|
+
<path
|
|
56
|
+
fill="#555"
|
|
57
|
+
d="M22.35 6L11.925 24V12L22.35 6z"
|
|
58
|
+
/>
|
|
59
|
+
<path
|
|
60
|
+
fill="#000"
|
|
61
|
+
d="M22.35 6l-10.425 6L1.5 6h20.85z"
|
|
62
|
+
/>
|
|
63
|
+
<defs>
|
|
64
|
+
<linearGradient
|
|
65
|
+
id="mcp-cursor-fill-0"
|
|
66
|
+
x1="11.925"
|
|
67
|
+
x2="11.925"
|
|
68
|
+
y1="12"
|
|
69
|
+
y2="24"
|
|
70
|
+
gradientUnits="userSpaceOnUse"
|
|
71
|
+
>
|
|
72
|
+
<stop
|
|
73
|
+
offset=".16"
|
|
74
|
+
stop-color="#000"
|
|
75
|
+
stop-opacity=".39"
|
|
76
|
+
/>
|
|
77
|
+
<stop
|
|
78
|
+
offset=".658"
|
|
79
|
+
stop-color="#000"
|
|
80
|
+
stop-opacity=".8"
|
|
81
|
+
/>
|
|
82
|
+
</linearGradient>
|
|
83
|
+
<linearGradient
|
|
84
|
+
id="mcp-cursor-fill-1"
|
|
85
|
+
x1="22.35"
|
|
86
|
+
x2="11.925"
|
|
87
|
+
y1="6.037"
|
|
88
|
+
y2="12.15"
|
|
89
|
+
gradientUnits="userSpaceOnUse"
|
|
90
|
+
>
|
|
91
|
+
<stop
|
|
92
|
+
offset=".182"
|
|
93
|
+
stop-color="#000"
|
|
94
|
+
stop-opacity=".31"
|
|
95
|
+
/>
|
|
96
|
+
<stop
|
|
97
|
+
offset=".715"
|
|
98
|
+
stop-color="#000"
|
|
99
|
+
stop-opacity="0"
|
|
100
|
+
/>
|
|
101
|
+
</linearGradient>
|
|
102
|
+
<linearGradient
|
|
103
|
+
id="mcp-cursor-fill-2"
|
|
104
|
+
x1="11.925"
|
|
105
|
+
x2="1.5"
|
|
106
|
+
y1="0"
|
|
107
|
+
y2="18"
|
|
108
|
+
gradientUnits="userSpaceOnUse"
|
|
109
|
+
>
|
|
110
|
+
<stop
|
|
111
|
+
stop-color="#000"
|
|
112
|
+
stop-opacity=".6"
|
|
113
|
+
/>
|
|
114
|
+
<stop
|
|
115
|
+
offset=".667"
|
|
116
|
+
stop-color="#000"
|
|
117
|
+
stop-opacity=".22"
|
|
118
|
+
/>
|
|
119
|
+
</linearGradient>
|
|
120
|
+
</defs>
|
|
121
|
+
</svg>
|
|
122
|
+
|
|
123
|
+
<!-- VS Code Icon -->
|
|
124
|
+
<svg
|
|
125
|
+
v-if="showIcon && ide === 'vscode'"
|
|
126
|
+
class="mcp-install-button-icon mcp-install-button-icon--vscode"
|
|
127
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
128
|
+
width="1em"
|
|
129
|
+
height="1em"
|
|
130
|
+
viewBox="0 0 100 100"
|
|
131
|
+
aria-hidden="true"
|
|
132
|
+
>
|
|
133
|
+
<mask
|
|
134
|
+
id="mcp-vscode-mask"
|
|
135
|
+
width="100"
|
|
136
|
+
height="100"
|
|
137
|
+
x="0"
|
|
138
|
+
y="0"
|
|
139
|
+
maskUnits="userSpaceOnUse"
|
|
140
|
+
>
|
|
141
|
+
<path
|
|
142
|
+
fill="#fff"
|
|
143
|
+
fill-rule="evenodd"
|
|
144
|
+
d="M70.912 99.317a6.223 6.223 0 0 0 4.96-.19l20.589-9.907A6.25 6.25 0 0 0 100 83.587V16.413a6.25 6.25 0 0 0-3.54-5.632L75.874.874a6.226 6.226 0 0 0-7.104 1.21L29.355 38.04 12.187 25.01a4.162 4.162 0 0 0-5.318.236l-5.506 5.009a4.168 4.168 0 0 0-.004 6.162L16.247 50 1.36 63.583a4.168 4.168 0 0 0 .004 6.162l5.506 5.01a4.162 4.162 0 0 0 5.318.236l17.168-13.032L68.77 97.917a6.217 6.217 0 0 0 2.143 1.4ZM75.015 27.3 45.11 50l29.906 22.701V27.3Z"
|
|
145
|
+
clip-rule="evenodd"
|
|
146
|
+
/>
|
|
147
|
+
</mask>
|
|
148
|
+
<g mask="url(#mcp-vscode-mask)">
|
|
149
|
+
<path
|
|
150
|
+
fill="#0065A9"
|
|
151
|
+
d="M96.461 10.796 75.857.876a6.23 6.23 0 0 0-7.107 1.207l-67.451 61.5a4.167 4.167 0 0 0 .004 6.162l5.51 5.009a4.167 4.167 0 0 0 5.32.236l81.228-61.62c2.725-2.067 6.639-.124 6.639 3.297v-.24a6.25 6.25 0 0 0-3.539-5.63Z"
|
|
152
|
+
/>
|
|
153
|
+
<path
|
|
154
|
+
fill="#007ACC"
|
|
155
|
+
d="m96.461 89.204-20.604 9.92a6.229 6.229 0 0 1-7.107-1.207l-67.451-61.5a4.167 4.167 0 0 1 .004-6.162l5.51-5.009a4.167 4.167 0 0 1 5.32-.236l81.228 61.62c2.725 2.067 6.639.124 6.639-3.297v.24a6.25 6.25 0 0 1-3.539 5.63Z"
|
|
156
|
+
/>
|
|
157
|
+
<path
|
|
158
|
+
fill="#1F9CF0"
|
|
159
|
+
d="M75.858 99.126a6.232 6.232 0 0 1-7.108-1.21c2.306 2.307 6.25.674 6.25-2.588V4.672c0-3.262-3.944-4.895-6.25-2.589a6.232 6.232 0 0 1 7.108-1.21l20.6 9.908A6.25 6.25 0 0 1 100 16.413v67.174a6.25 6.25 0 0 1-3.541 5.633l-20.601 9.906Z"
|
|
160
|
+
/>
|
|
161
|
+
</g>
|
|
162
|
+
</svg>
|
|
163
|
+
|
|
164
|
+
<span class="mcp-install-button-label">
|
|
165
|
+
<slot>{{ buttonLabel }}</slot>
|
|
166
|
+
</span>
|
|
167
|
+
</a>
|
|
168
|
+
</template>
|
|
169
|
+
|
|
170
|
+
<style>
|
|
171
|
+
.mcp-install-button{align-items:center;background-color:#171717;border:1px solid #404040;color:#fff;cursor:pointer;display:inline-flex;font-size:.875rem;font-weight:500;gap:.5rem;line-height:1.25rem;padding:.4rem .5rem;text-decoration:none;transition:background-color .15s,border-color .15s}.mcp-install-button:hover{background-color:#262626;border-color:#525252}.mcp-install-button:focus-visible{outline:2px solid #3b82f6;outline-offset:2px}.mcp-install-button-icon{filter:invert(1);flex-shrink:0;height:1.25rem;width:1.25rem}.mcp-install-button-icon--vscode{filter:none}.mcp-install-button-label{white-space:nowrap}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type SupportedIDE = 'cursor' | 'vscode';
|
|
2
|
+
export interface InstallButtonProps {
|
|
3
|
+
/**
|
|
4
|
+
* URL of the MCP server endpoint (e.g., 'https://example.com/mcp')
|
|
5
|
+
* The deeplink will be derived as '{url}/deeplink?ide=xxx'
|
|
6
|
+
*/
|
|
7
|
+
url: string;
|
|
8
|
+
/**
|
|
9
|
+
* Target IDE
|
|
10
|
+
* @default 'cursor'
|
|
11
|
+
*/
|
|
12
|
+
ide?: SupportedIDE;
|
|
13
|
+
/**
|
|
14
|
+
* Button label (auto-generated based on IDE if not provided)
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Show the IDE icon
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
showIcon?: boolean;
|
|
22
|
+
}
|
|
23
|
+
declare var __VLS_1: {};
|
|
24
|
+
type __VLS_Slots = {} & {
|
|
25
|
+
default?: (props: typeof __VLS_1) => any;
|
|
26
|
+
};
|
|
27
|
+
declare const __VLS_base: import("@vue/runtime-core").DefineComponent<InstallButtonProps, {}, {}, {}, {}, import("@vue/runtime-core").ComponentOptionsMixin, import("@vue/runtime-core").ComponentOptionsMixin, {}, string, import("@vue/runtime-core").PublicProps, Readonly<InstallButtonProps> & Readonly<{}>, {
|
|
28
|
+
ide: SupportedIDE;
|
|
29
|
+
showIcon: boolean;
|
|
30
|
+
}, {}, {}, {}, string, import("@vue/runtime-core").ComponentProvideOptions, false, {}, any>;
|
|
31
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
32
|
+
declare const _default: typeof __VLS_export;
|
|
33
|
+
export default _default;
|
|
34
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
35
|
+
new (): {
|
|
36
|
+
$slots: S;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import satori from "satori";
|
|
3
|
+
const IDE_CONFIG = {
|
|
4
|
+
cursor: {
|
|
5
|
+
defaultLabel: "Install MCP in Cursor"
|
|
6
|
+
},
|
|
7
|
+
vscode: {
|
|
8
|
+
defaultLabel: "Install MCP in VS Code"
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function CursorIcon() {
|
|
12
|
+
return {
|
|
13
|
+
type: "svg",
|
|
14
|
+
props: {
|
|
15
|
+
width: 18,
|
|
16
|
+
height: 18,
|
|
17
|
+
viewBox: "0 0 24 24",
|
|
18
|
+
style: { filter: "invert(1)" },
|
|
19
|
+
children: [
|
|
20
|
+
{
|
|
21
|
+
type: "path",
|
|
22
|
+
props: {
|
|
23
|
+
fill: "#666",
|
|
24
|
+
d: "M11.925 24l10.425-6-10.425-6L1.5 18l10.425 6z"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "path",
|
|
29
|
+
props: {
|
|
30
|
+
fill: "#888",
|
|
31
|
+
d: "M22.35 18V6L11.925 0v12l10.425 6z"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "path",
|
|
36
|
+
props: {
|
|
37
|
+
fill: "#777",
|
|
38
|
+
d: "M11.925 0L1.5 6v12l10.425-6V0z"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: "path",
|
|
43
|
+
props: {
|
|
44
|
+
fill: "#555",
|
|
45
|
+
d: "M22.35 6L11.925 24V12L22.35 6z"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: "path",
|
|
50
|
+
props: {
|
|
51
|
+
fill: "#333",
|
|
52
|
+
d: "M22.35 6l-10.425 6L1.5 6h20.85z"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function VSCodeIconSimple() {
|
|
60
|
+
return {
|
|
61
|
+
type: "svg",
|
|
62
|
+
props: {
|
|
63
|
+
width: 18,
|
|
64
|
+
height: 18,
|
|
65
|
+
viewBox: "0 0 24 24",
|
|
66
|
+
children: [
|
|
67
|
+
{
|
|
68
|
+
type: "path",
|
|
69
|
+
props: {
|
|
70
|
+
fill: "#007ACC",
|
|
71
|
+
d: "M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63l-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12L.326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128l9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function getIcon(ide) {
|
|
79
|
+
return ide === "vscode" ? VSCodeIconSimple() : CursorIcon();
|
|
80
|
+
}
|
|
81
|
+
async function generateBadgeSVG(options) {
|
|
82
|
+
const { label, color, textColor, borderColor, showIcon, ide } = options;
|
|
83
|
+
const icon = getIcon(ide);
|
|
84
|
+
const element = {
|
|
85
|
+
type: "div",
|
|
86
|
+
props: {
|
|
87
|
+
style: {
|
|
88
|
+
display: "flex",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
gap: "8px",
|
|
91
|
+
padding: "6px 8px",
|
|
92
|
+
fontSize: "14px",
|
|
93
|
+
fontWeight: 500,
|
|
94
|
+
color: `#${textColor}`,
|
|
95
|
+
backgroundColor: `#${color}`,
|
|
96
|
+
border: `1px solid #${borderColor}`
|
|
97
|
+
},
|
|
98
|
+
children: showIcon ? [icon, { type: "span", props: { children: label } }] : [{ type: "span", props: { children: label } }]
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const iconWidth = showIcon ? 26 : 0;
|
|
102
|
+
const textWidth = label.length * 8;
|
|
103
|
+
const padding = 20;
|
|
104
|
+
const width = Math.max(Math.ceil(iconWidth + textWidth + padding), 140);
|
|
105
|
+
const height = 32;
|
|
106
|
+
const svg = await satori(element, {
|
|
107
|
+
width,
|
|
108
|
+
height,
|
|
109
|
+
fonts: [
|
|
110
|
+
{
|
|
111
|
+
name: "Inter",
|
|
112
|
+
data: await loadFont(),
|
|
113
|
+
weight: 500,
|
|
114
|
+
style: "normal"
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
});
|
|
118
|
+
return svg;
|
|
119
|
+
}
|
|
120
|
+
async function loadFont() {
|
|
121
|
+
const response = await fetch(
|
|
122
|
+
"https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fAZ9hjp-Ek-_EeA.woff"
|
|
123
|
+
);
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
throw new Error(`Failed to load font: ${response.status} ${response.statusText}`);
|
|
126
|
+
}
|
|
127
|
+
return response.arrayBuffer();
|
|
128
|
+
}
|
|
129
|
+
export default defineEventHandler(async (event) => {
|
|
130
|
+
const query = getQuery(event);
|
|
131
|
+
const ide = query.ide || "cursor";
|
|
132
|
+
const ideConfig = IDE_CONFIG[ide] || IDE_CONFIG.cursor;
|
|
133
|
+
const options = {
|
|
134
|
+
ide,
|
|
135
|
+
label: query.label || ideConfig.defaultLabel,
|
|
136
|
+
color: query.color || "171717",
|
|
137
|
+
textColor: query.textColor || "ffffff",
|
|
138
|
+
borderColor: query.borderColor || "404040",
|
|
139
|
+
showIcon: query.icon !== "false"
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
const svg = await generateBadgeSVG(options);
|
|
143
|
+
setHeader(event, "Content-Type", "image/svg+xml");
|
|
144
|
+
setHeader(event, "Cache-Control", "public, max-age=86400");
|
|
145
|
+
return svg;
|
|
146
|
+
} catch {
|
|
147
|
+
setHeader(event, "Content-Type", "image/svg+xml");
|
|
148
|
+
setHeader(event, "Cache-Control", "no-cache");
|
|
149
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="32">
|
|
150
|
+
<rect width="140" height="32" fill="#171717" stroke="#404040"/>
|
|
151
|
+
<text x="70" y="20" fill="#fff" font-size="12" text-anchor="middle">${options.label}</text>
|
|
152
|
+
</svg>`;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { defineEventHandler, getRequestURL, getQuery, setHeader } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
const IDE_CONFIGS = {
|
|
4
|
+
cursor: {
|
|
5
|
+
name: "Cursor",
|
|
6
|
+
generateDeeplink: (serverName, mcpUrl) => {
|
|
7
|
+
const config = { type: "http", url: mcpUrl };
|
|
8
|
+
const configBase64 = Buffer.from(JSON.stringify(config)).toString("base64");
|
|
9
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(serverName)}&config=${encodeURIComponent(configBase64)}`;
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
vscode: {
|
|
13
|
+
name: "VS Code",
|
|
14
|
+
generateDeeplink: (serverName, mcpUrl) => {
|
|
15
|
+
const config = { name: serverName, type: "http", url: mcpUrl };
|
|
16
|
+
return `vscode:mcp/install?${encodeURIComponent(JSON.stringify(config))}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function escapeHtmlAttr(str) {
|
|
21
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
22
|
+
}
|
|
23
|
+
function escapeJs(str) {
|
|
24
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
|
|
25
|
+
}
|
|
26
|
+
export default defineEventHandler((event) => {
|
|
27
|
+
const runtimeConfig = useRuntimeConfig(event).mcp;
|
|
28
|
+
const requestUrl = getRequestURL(event);
|
|
29
|
+
const query = getQuery(event);
|
|
30
|
+
const ide = query.ide || "cursor";
|
|
31
|
+
const ideConfig = IDE_CONFIGS[ide];
|
|
32
|
+
if (!ideConfig) {
|
|
33
|
+
setHeader(event, "Location", "/");
|
|
34
|
+
return new Response(null, { status: 302 });
|
|
35
|
+
}
|
|
36
|
+
const serverName = runtimeConfig.name || "mcp-server";
|
|
37
|
+
const mcpUrl = `${requestUrl.origin}${runtimeConfig.route || "/mcp"}`;
|
|
38
|
+
const deeplink = ideConfig.generateDeeplink(serverName, mcpUrl);
|
|
39
|
+
const htmlDeeplink = escapeHtmlAttr(deeplink);
|
|
40
|
+
const jsDeeplink = escapeJs(deeplink);
|
|
41
|
+
setHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
42
|
+
return `<!DOCTYPE html>
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head>
|
|
45
|
+
<meta charset="utf-8">
|
|
46
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
47
|
+
<title>Opening ${ideConfig.name}...</title>
|
|
48
|
+
<style>
|
|
49
|
+
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #fff; }
|
|
50
|
+
.container { text-align: center; padding: 2rem; }
|
|
51
|
+
a { color: #3b82f6; }
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<div class="container">
|
|
56
|
+
<p>Opening ${ideConfig.name}...</p>
|
|
57
|
+
<p>If nothing happens, <a href="${htmlDeeplink}">click here to install</a>.</p>
|
|
58
|
+
</div>
|
|
59
|
+
<script>window.location.href = "${jsDeeplink}";<\/script>
|
|
60
|
+
</body>
|
|
61
|
+
</html>`;
|
|
62
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjs/mcp-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Create MCP servers directly in your Nuxt application. Define tools, resources, and prompts with a simple and intuitive API.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"defu": "^6.1.4",
|
|
41
41
|
"ms": "^2.1.3",
|
|
42
42
|
"pathe": "^2.0.3",
|
|
43
|
+
"satori": "^0.18.3",
|
|
43
44
|
"scule": "^1.3.0",
|
|
44
45
|
"tinyglobby": "^0.2.15"
|
|
45
46
|
},
|