@viewert/mcp 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,95 @@
1
+ # @viewert/mcp
2
+
3
+ MCP server for [Viewert](https://viewert.com) — expose your Librams (AI context collections) to any MCP-compatible AI client.
4
+
5
+ ## What it does
6
+
7
+ Connects Claude Desktop, Cursor, Windsurf, or any MCP client to your Viewert Librams. Each Libram is a curated collection of Vellums (notes/documents) that you've marked as available for AI context.
8
+
9
+ ## Setup
10
+
11
+ ### 1. Get an API key
12
+
13
+ Go to **Settings → API Keys** on Viewert and create a key. Copy it — it's shown only once.
14
+
15
+ ### 2. Add to your MCP client config
16
+
17
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "viewert": {
23
+ "command": "npx",
24
+ "args": ["-y", "@viewert/mcp"],
25
+ "env": {
26
+ "VIEWERT_API_KEY": "vwt_your_key_here"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ **Cursor / Windsurf** (`.cursor/mcp.json` or `.windsurf/mcp.json` in your project, or global config):
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "viewert": {
39
+ "command": "npx",
40
+ "args": ["-y", "@viewert/mcp"],
41
+ "env": {
42
+ "VIEWERT_API_KEY": "vwt_your_key_here"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### 3. Restart your AI client
50
+
51
+ The Viewert tools will appear automatically.
52
+
53
+ ---
54
+
55
+ ## Tools
56
+
57
+ | Tool | Description |
58
+ |------|-------------|
59
+ | `list_librams` | List all Librams in your account with names and IDs |
60
+ | `get_libram_context` | Fetch all AI-enabled Vellums from a Libram as **markdown** |
61
+ | `get_libram_context_json` | Fetch AI-enabled Vellums as **structured JSON** (id, title, content) |
62
+
63
+ ## Resources
64
+
65
+ | URI | Description |
66
+ |-----|-------------|
67
+ | `librams://list` | JSON list of all your Librams |
68
+ | `librams://{id}/context` | Markdown context for a specific Libram |
69
+
70
+ ---
71
+
72
+ ## Environment variables
73
+
74
+ | Variable | Required | Default |
75
+ |----------|----------|---------|
76
+ | `VIEWERT_API_KEY` | ✅ Yes | — |
77
+ | `VIEWERT_API_URL` | No | `https://viewert.com/api` |
78
+
79
+ ---
80
+
81
+ ## Example usage in Claude
82
+
83
+ > "Load my **Project Notes** Libram and summarise the key points."
84
+
85
+ Claude will call `list_librams` to find the ID, then `get_libram_context` to pull the content — all inline in the conversation.
86
+
87
+ ---
88
+
89
+ ## Local development
90
+
91
+ ```bash
92
+ cd mcp
93
+ pnpm install
94
+ VIEWERT_API_KEY=vwt_... pnpm dev
95
+ ```
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Viewert MCP Server
4
+ *
5
+ * Exposes Viewert Librams as context resources and tools to any MCP-compatible
6
+ * AI client (Claude Desktop, Cursor, Windsurf, etc.).
7
+ *
8
+ * Required env vars:
9
+ * VIEWERT_API_KEY — your vwt_... API key from viewert.com/settings
10
+ *
11
+ * Optional env vars:
12
+ * VIEWERT_API_URL — defaults to https://viewert.com/api
13
+ */
14
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Viewert MCP Server
4
+ *
5
+ * Exposes Viewert Librams as context resources and tools to any MCP-compatible
6
+ * AI client (Claude Desktop, Cursor, Windsurf, etc.).
7
+ *
8
+ * Required env vars:
9
+ * VIEWERT_API_KEY — your vwt_... API key from viewert.com/settings
10
+ *
11
+ * Optional env vars:
12
+ * VIEWERT_API_URL — defaults to https://viewert.com/api
13
+ */
14
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { z } from 'zod';
17
+ const API_KEY = process.env.VIEWERT_API_KEY;
18
+ const API_BASE = (process.env.VIEWERT_API_URL ?? 'https://viewert.com/api').replace(/\/$/, '');
19
+ if (!API_KEY) {
20
+ process.stderr.write('[viewert-mcp] ERROR: VIEWERT_API_KEY is not set.\n' +
21
+ 'Generate a key at https://viewert.com/settings and add it to your MCP config.\n');
22
+ process.exit(1);
23
+ }
24
+ // ── HTTP helpers ──────────────────────────────────────────────────────────────
25
+ async function apiFetch(path, opts = {}) {
26
+ return fetch(`${API_BASE}${path}`, {
27
+ ...opts,
28
+ headers: {
29
+ Authorization: `Bearer ${API_KEY}`,
30
+ 'Content-Type': 'application/json',
31
+ ...(opts.headers ?? {}),
32
+ },
33
+ });
34
+ }
35
+ async function apiJSON(path) {
36
+ const res = await apiFetch(path);
37
+ if (!res.ok) {
38
+ const body = await res.text().catch(() => '');
39
+ throw new Error(`Viewert API ${res.status}: ${body}`);
40
+ }
41
+ return res.json();
42
+ }
43
+ async function apiText(path) {
44
+ const res = await apiFetch(path);
45
+ if (!res.ok) {
46
+ const body = await res.text().catch(() => '');
47
+ throw new Error(`Viewert API ${res.status}: ${body}`);
48
+ }
49
+ return res.text();
50
+ }
51
+ // ── Server ────────────────────────────────────────────────────────────────────
52
+ const server = new McpServer({
53
+ name: 'viewert',
54
+ version: '0.1.0',
55
+ });
56
+ // ── Tool: list_librams ────────────────────────────────────────────────────────
57
+ server.tool('list_librams', 'List all Librams (AI context collections) in your Viewert account.', {}, async () => {
58
+ const data = await apiJSON('/librams/mine');
59
+ const librams = data.librams ?? [];
60
+ if (librams.length === 0) {
61
+ return {
62
+ content: [{ type: 'text', text: 'You have no Librams yet. Create one at https://viewert.com/librams' }],
63
+ };
64
+ }
65
+ const lines = librams.map((l) => `• ${l.emoji || '📁'} **${l.name}** (id: \`${l.id}\`)` +
66
+ ` — ${l.ai_count} AI vellums` +
67
+ (l.description ? ` — ${l.description}` : ''));
68
+ return {
69
+ content: [
70
+ {
71
+ type: 'text',
72
+ text: `**Your Viewert Librams (${librams.length})**\n\n${lines.join('\n')}\n\nUse \`get_libram_context\` with a libram id to load its content.`,
73
+ },
74
+ ],
75
+ };
76
+ });
77
+ // ── Tool: get_libram_context ──────────────────────────────────────────────────
78
+ server.tool('get_libram_context', 'Fetch the full AI-enabled Vellum contents of a Viewert Libram as markdown. ' +
79
+ 'Use this to inject knowledge from your Libram into the current conversation.', {
80
+ libram_id: z.string().describe('The ID of the Libram to fetch. Use list_librams to find IDs.'),
81
+ }, async ({ libram_id }) => {
82
+ const markdown = await apiText(`/librams/${libram_id}/context?format=markdown`);
83
+ return {
84
+ content: [{ type: 'text', text: markdown }],
85
+ };
86
+ });
87
+ // ── Tool: get_libram_context_json ─────────────────────────────────────────────
88
+ server.tool('get_libram_context_json', 'Fetch AI-enabled Vellums from a Libram as structured JSON (id, title, content per vellum). ' +
89
+ 'Use this when you need to reference individual vellums by title or process them separately.', {
90
+ libram_id: z.string().describe('The ID of the Libram to fetch.'),
91
+ }, async ({ libram_id }) => {
92
+ const data = await apiJSON(`/librams/${libram_id}/context?format=json`);
93
+ return {
94
+ content: [
95
+ {
96
+ type: 'text',
97
+ text: JSON.stringify(data, null, 2),
98
+ },
99
+ ],
100
+ };
101
+ });
102
+ // ── Resource: librams://list ──────────────────────────────────────────────────
103
+ server.resource('librams', 'librams://list', { mimeType: 'application/json', description: 'All Librams in your Viewert account' }, async () => {
104
+ const data = await apiJSON('/librams/mine');
105
+ return {
106
+ contents: [
107
+ {
108
+ uri: 'librams://list',
109
+ mimeType: 'application/json',
110
+ text: JSON.stringify(data.librams ?? [], null, 2),
111
+ },
112
+ ],
113
+ };
114
+ });
115
+ // ── Resource: librams://{id}/context ─────────────────────────────────────────
116
+ server.resource('libram-context', new ResourceTemplate('librams://{id}/context', { list: undefined }), { mimeType: 'text/markdown', description: 'AI-enabled Vellum contents of a Libram as markdown' }, async (uri, variables) => {
117
+ const libramId = variables['id'];
118
+ if (!libramId)
119
+ throw new Error(`Missing libram id in URI: ${uri.href}`);
120
+ const markdown = await apiText(`/librams/${libramId}/context?format=markdown`);
121
+ return {
122
+ contents: [
123
+ {
124
+ uri: uri.href,
125
+ mimeType: 'text/markdown',
126
+ text: markdown,
127
+ },
128
+ ],
129
+ };
130
+ });
131
+ // ── Start ─────────────────────────────────────────────────────────────────────
132
+ const transport = new StdioServerTransport();
133
+ await server.connect(transport);
134
+ process.stderr.write('[viewert-mcp] Server started. Waiting for MCP client...\n');
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@viewert/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Viewert Librams — expose AI-enabled Vellums as context to any MCP-compatible AI client",
5
+ "keywords": ["mcp", "viewert", "libram", "ai-context", "model-context-protocol"],
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "bin": {
10
+ "viewert-mcp": "dist/index.js"
11
+ },
12
+ "packageManager": "pnpm@9.0.0",
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsx src/index.ts",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "pnpm run build"
18
+ },
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.8.0",
21
+ "zod": "^3.24.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "tsx": "^4.19.0",
26
+ "typescript": "^5.7.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Viewert MCP Server
4
+ *
5
+ * Exposes Viewert Librams as context resources and tools to any MCP-compatible
6
+ * AI client (Claude Desktop, Cursor, Windsurf, etc.).
7
+ *
8
+ * Required env vars:
9
+ * VIEWERT_API_KEY — your vwt_... API key from viewert.com/settings
10
+ *
11
+ * Optional env vars:
12
+ * VIEWERT_API_URL — defaults to https://viewert.com/api
13
+ */
14
+
15
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
16
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
17
+ import { z } from 'zod'
18
+
19
+ const API_KEY = process.env.VIEWERT_API_KEY
20
+ const API_BASE = (process.env.VIEWERT_API_URL ?? 'https://viewert.com/api').replace(/\/$/, '')
21
+
22
+ if (!API_KEY) {
23
+ process.stderr.write(
24
+ '[viewert-mcp] ERROR: VIEWERT_API_KEY is not set.\n' +
25
+ 'Generate a key at https://viewert.com/settings and add it to your MCP config.\n'
26
+ )
27
+ process.exit(1)
28
+ }
29
+
30
+ // ── HTTP helpers ──────────────────────────────────────────────────────────────
31
+
32
+ async function apiFetch(path: string, opts: RequestInit = {}): Promise<Response> {
33
+ return fetch(`${API_BASE}${path}`, {
34
+ ...opts,
35
+ headers: {
36
+ Authorization: `Bearer ${API_KEY}`,
37
+ 'Content-Type': 'application/json',
38
+ ...(opts.headers as Record<string, string> ?? {}),
39
+ },
40
+ })
41
+ }
42
+
43
+ async function apiJSON<T>(path: string): Promise<T> {
44
+ const res = await apiFetch(path)
45
+ if (!res.ok) {
46
+ const body = await res.text().catch(() => '')
47
+ throw new Error(`Viewert API ${res.status}: ${body}`)
48
+ }
49
+ return res.json() as Promise<T>
50
+ }
51
+
52
+ async function apiText(path: string): Promise<string> {
53
+ const res = await apiFetch(path)
54
+ if (!res.ok) {
55
+ const body = await res.text().catch(() => '')
56
+ throw new Error(`Viewert API ${res.status}: ${body}`)
57
+ }
58
+ return res.text()
59
+ }
60
+
61
+ // ── Types ─────────────────────────────────────────────────────────────────────
62
+
63
+ interface Libram {
64
+ id: string
65
+ name: string
66
+ description: string
67
+ emoji: string
68
+ is_public: boolean
69
+ vellum_count: number
70
+ ai_count: number
71
+ updated_at: string
72
+ }
73
+
74
+ // ── Server ────────────────────────────────────────────────────────────────────
75
+
76
+ const server = new McpServer({
77
+ name: 'viewert',
78
+ version: '0.1.0',
79
+ })
80
+
81
+ // ── Tool: list_librams ────────────────────────────────────────────────────────
82
+
83
+ server.tool(
84
+ 'list_librams',
85
+ 'List all Librams (AI context collections) in your Viewert account.',
86
+ {},
87
+ async () => {
88
+ const data = await apiJSON<{ librams: Libram[] }>('/librams/mine')
89
+ const librams = data.librams ?? []
90
+
91
+ if (librams.length === 0) {
92
+ return {
93
+ content: [{ type: 'text', text: 'You have no Librams yet. Create one at https://viewert.com/librams' }],
94
+ }
95
+ }
96
+
97
+ const lines = librams.map((l) =>
98
+ `• ${l.emoji || '📁'} **${l.name}** (id: \`${l.id}\`)` +
99
+ ` — ${l.ai_count} AI vellums` +
100
+ (l.description ? ` — ${l.description}` : '')
101
+ )
102
+
103
+ return {
104
+ content: [
105
+ {
106
+ type: 'text',
107
+ text: `**Your Viewert Librams (${librams.length})**\n\n${lines.join('\n')}\n\nUse \`get_libram_context\` with a libram id to load its content.`,
108
+ },
109
+ ],
110
+ }
111
+ }
112
+ )
113
+
114
+ // ── Tool: get_libram_context ──────────────────────────────────────────────────
115
+
116
+ server.tool(
117
+ 'get_libram_context',
118
+ 'Fetch the full AI-enabled Vellum contents of a Viewert Libram as markdown. ' +
119
+ 'Use this to inject knowledge from your Libram into the current conversation.',
120
+ {
121
+ libram_id: z.string().describe('The ID of the Libram to fetch. Use list_librams to find IDs.'),
122
+ },
123
+ async ({ libram_id }) => {
124
+ const markdown = await apiText(`/librams/${libram_id}/context?format=markdown`)
125
+ return {
126
+ content: [{ type: 'text', text: markdown }],
127
+ }
128
+ }
129
+ )
130
+
131
+ // ── Tool: get_libram_context_json ─────────────────────────────────────────────
132
+
133
+ server.tool(
134
+ 'get_libram_context_json',
135
+ 'Fetch AI-enabled Vellums from a Libram as structured JSON (id, title, content per vellum). ' +
136
+ 'Use this when you need to reference individual vellums by title or process them separately.',
137
+ {
138
+ libram_id: z.string().describe('The ID of the Libram to fetch.'),
139
+ },
140
+ async ({ libram_id }) => {
141
+ const data = await apiJSON<{
142
+ libram_name: string
143
+ vellums: Array<{ id: string; title: string; content: string }>
144
+ }>(`/librams/${libram_id}/context?format=json`)
145
+
146
+ return {
147
+ content: [
148
+ {
149
+ type: 'text',
150
+ text: JSON.stringify(data, null, 2),
151
+ },
152
+ ],
153
+ }
154
+ }
155
+ )
156
+
157
+ // ── Resource: librams://list ──────────────────────────────────────────────────
158
+
159
+ server.resource(
160
+ 'librams',
161
+ 'librams://list',
162
+ { mimeType: 'application/json', description: 'All Librams in your Viewert account' },
163
+ async () => {
164
+ const data = await apiJSON<{ librams: Libram[] }>('/librams/mine')
165
+ return {
166
+ contents: [
167
+ {
168
+ uri: 'librams://list',
169
+ mimeType: 'application/json',
170
+ text: JSON.stringify(data.librams ?? [], null, 2),
171
+ },
172
+ ],
173
+ }
174
+ }
175
+ )
176
+
177
+ // ── Resource: librams://{id}/context ─────────────────────────────────────────
178
+
179
+ server.resource(
180
+ 'libram-context',
181
+ new ResourceTemplate('librams://{id}/context', { list: undefined }),
182
+ { mimeType: 'text/markdown', description: 'AI-enabled Vellum contents of a Libram as markdown' },
183
+ async (uri: URL, variables: Record<string, string | string[]>) => {
184
+ const libramId = variables['id'] as string
185
+ if (!libramId) throw new Error(`Missing libram id in URI: ${uri.href}`)
186
+ const markdown = await apiText(`/librams/${libramId}/context?format=markdown`)
187
+ return {
188
+ contents: [
189
+ {
190
+ uri: uri.href,
191
+ mimeType: 'text/markdown',
192
+ text: markdown,
193
+ },
194
+ ],
195
+ }
196
+ }
197
+ )
198
+
199
+ // ── Start ─────────────────────────────────────────────────────────────────────
200
+
201
+ const transport = new StdioServerTransport()
202
+ await server.connect(transport)
203
+ process.stderr.write('[viewert-mcp] Server started. Waiting for MCP client...\n')
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src"]
14
+ }