Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/application/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@
"instructions": {
"claudeCode": "Run in the terminal",
"cursor": "Add to ~/.cursor/mcp.json",
"claude": "Settings → Connectors → Add custom connector: paste the URL and add the request header"
"claude": "Customize → Connectors → Add custom connector. Enter a name, e.g. NoteX, and the server URL. In Authentication choose \"No sign-in\", then in Request headers add the \"authorization\" header with the value below",
"claudeUrl": "Server URL",
"claudeHeader": "Value of the authorization header"
},
"permission": {
"read": "Read only",
Expand Down
111 changes: 95 additions & 16 deletions src/presentation/components/mcp/McpTokens.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,38 @@
:title="instruction.title"
:subtitle="instruction.caption"
>
<template #right>
<template
v-if="instruction.snippets.length === 1"
#right
>
<Button
icon="Copy"
secondary
@click="copy(instruction.snippet)"
@click="copy(instruction.snippets[0].value)"
>
{{ t('userSettings.mcp.copy') }}
</Button>
</template>
</Row>
<pre :class="[$style['mcp__snippet'], 'text-monospaced']">{{ instruction.snippet }}</pre>
<template
v-for="snippet in instruction.snippets"
:key="snippet.value"
>
<div
v-if="instruction.snippets.length > 1"
:class="$style['mcp__snippet-label']"
>
<span class="text-ui-subtle">{{ snippet.label }}</span>
<Button
icon="Copy"
secondary
@click="copy(snippet.value)"
>
{{ t('userSettings.mcp.copy') }}
</Button>
</div>
<pre :class="[$style['mcp__snippet'], 'text-monospaced']">{{ snippet.value }}</pre>
</template>
</div>
</div>
</Section>
Expand Down Expand Up @@ -153,37 +174,87 @@ const createdToken = ref<string | null>(null);
*/
const mcpUrl = `${import.meta.env.VITE_API_URL}/mcp`;

/**
* Value to paste into MCP client settings
*/
interface SetupSnippet {
/**
* Field the value goes to, shown when the client needs several values
*/
label?: string;

/**
* Value to copy
*/
value: string;
}

/**
* Setup instruction for MCP client
*/
interface SetupInstruction {
/**
* MCP client name
*/
title: string;

/**
* Where to paste the snippets
*/
caption: string;

/**
* Values to paste
*/
snippets: SetupSnippet[];
}

/**
* Ready-made setup instructions for popular MCP clients
*/
const setupInstructions = computed(() => {
const setupInstructions = computed<SetupInstruction[]>(() => {
const token = createdToken.value ?? '';

return [
{
title: 'Claude Code',
caption: t('userSettings.mcp.instructions.claudeCode'),
snippet: `claude mcp add --transport http notex ${mcpUrl} --header "Authorization: Bearer ${token}"`,
snippets: [
{ value: `claude mcp add --transport http notex ${mcpUrl} --header "Authorization: Bearer ${token}"` },
],
},
{
title: 'Cursor',
caption: t('userSettings.mcp.instructions.cursor'),
snippet: JSON.stringify({
mcpServers: {
notex: {
url: mcpUrl,
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Bearer ${token}`,
snippets: [
{
value: JSON.stringify({
mcpServers: {
notex: {
url: mcpUrl,
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: `Bearer ${token}`,
},
},
},
},
}, null, 2),
},
}, null, 2),
],
},
{
title: 'Claude',
title: 'Claude Desktop and claude.ai',
caption: t('userSettings.mcp.instructions.claude'),
snippet: `${mcpUrl}\nAuthorization: Bearer ${token}`,
snippets: [
{
label: t('userSettings.mcp.instructions.claudeUrl'),
value: mcpUrl,
},
{
label: t('userSettings.mcp.instructions.claudeHeader'),
value: `Bearer ${token}`,
},
],
},
];
});
Expand Down Expand Up @@ -255,6 +326,14 @@ async function revokeClicked(token: McpToken): Promise<void> {
display: grid;
}

&__snippet-label {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-m);
padding: 0 var(--h-padding) var(--v-padding);
}

&__snippet {
margin: 0 var(--h-padding) var(--v-padding);
padding: var(--v-padding) var(--h-padding);
Expand Down
Loading