@toknbase/mcp-server 1.0.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 +84 -0
- package/index.js +283 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @toknbase/mcp-server
|
|
2
|
+
|
|
3
|
+
Zero-knowledge secrets management for AI agents via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
4
|
+
|
|
5
|
+
Toknbase is the only secrets manager where the host never sees your plaintext secrets. This MCP server connects your AI coding assistant to your Toknbase vault -- read, create, update, and delete secrets directly from Cursor, Windsurf, Claude Code, VS Code, Zed, and Cline.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### 1. Create an Agent Token
|
|
10
|
+
|
|
11
|
+
Log in to your Toknbase dashboard and navigate to **API Tokens → Agent Tokens**. Create a new token with the scope you need:
|
|
12
|
+
|
|
13
|
+
- `read_only` -- list and reveal secrets
|
|
14
|
+
- `read_write` -- list, reveal, create, and update secrets
|
|
15
|
+
- `full_access` -- all of the above plus delete and folder management
|
|
16
|
+
|
|
17
|
+
Copy the `agt_` token value -- it is shown only once.
|
|
18
|
+
|
|
19
|
+
### 2. Add to Your Editor
|
|
20
|
+
|
|
21
|
+
Replace `agt_your_token_here` with your token and `YOUR_CANISTER_ID` with your Toknbase canister ID (visible in the dashboard).
|
|
22
|
+
|
|
23
|
+
**Cursor** (`~/.cursor/mcp.json`)
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"toknbase": {
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": ["-y", "@toknbase/mcp-server"],
|
|
30
|
+
"env": {
|
|
31
|
+
"TOKNBASE_AGENT_TOKEN": "agt_your_token_here",
|
|
32
|
+
"TOKNBASE_CANISTER_ID": "YOUR_CANISTER_ID"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Claude Code** (`~/.claude.json`, mcpServers section)
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"toknbase": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["-y", "@toknbase/mcp-server"],
|
|
46
|
+
"env": {
|
|
47
|
+
"TOKNBASE_AGENT_TOKEN": "agt_your_token_here",
|
|
48
|
+
"TOKNBASE_CANISTER_ID": "YOUR_CANISTER_ID"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Available Tools
|
|
56
|
+
|
|
57
|
+
| Tool | Description | Required Scope |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `toknbase_list_secrets` | List all secrets (names, descriptions, environments -- no values) | read_only |
|
|
60
|
+
| `toknbase_reveal_secret` | Retrieve the value of a specific secret by name | read_only |
|
|
61
|
+
| `toknbase_create_secret` | Add a new secret to the vault | read_write |
|
|
62
|
+
| `toknbase_update_secret` | Update a secret's value | read_write |
|
|
63
|
+
| `toknbase_delete_secret` | Permanently delete a secret | full_access |
|
|
64
|
+
| `toknbase_list_folders` | List all folders | read_only |
|
|
65
|
+
| `toknbase_assign_folder` | Assign a secret to a folder | full_access |
|
|
66
|
+
|
|
67
|
+
## Environment Variables
|
|
68
|
+
|
|
69
|
+
| Variable | Required | Description |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `TOKNBASE_AGENT_TOKEN` | Yes | Your `agt_` agent token from the dashboard |
|
|
72
|
+
| `TOKNBASE_CANISTER_ID` | Yes | Your Toknbase canister ID |
|
|
73
|
+
| `TOKNBASE_IC_HOST` | No | IC host URL (default: `https://ic0.app`) |
|
|
74
|
+
|
|
75
|
+
## Security
|
|
76
|
+
|
|
77
|
+
Toknbase is zero-knowledge -- your plaintext secrets never leave your device. The MCP server calls the Toknbase canister which returns encrypted values. For `reveal_secret`, the value returned is the stored encrypted blob; decryption happens in your local environment using your client-side key.
|
|
78
|
+
|
|
79
|
+
Every action taken by an agent token is recorded in your Toknbase cryptographic audit log under the token's name.
|
|
80
|
+
|
|
81
|
+
## Requirements
|
|
82
|
+
|
|
83
|
+
- Node.js 18+
|
|
84
|
+
- A Toknbase account with an active agent token
|
package/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @toknbase/mcp-server
|
|
4
|
+
* Zero-knowledge secrets management for AI agents via Model Context Protocol.
|
|
5
|
+
*
|
|
6
|
+
* Required env vars:
|
|
7
|
+
* TOKNBASE_AGENT_TOKEN -- your agt_ scoped agent token from the Toknbase dashboard
|
|
8
|
+
* TOKNBASE_CANISTER_ID -- your Toknbase canister ID (e.g. xxxxx-xxxxx-cai)
|
|
9
|
+
*
|
|
10
|
+
* Optional:
|
|
11
|
+
* TOKNBASE_IC_HOST -- IC host (default: https://ic0.app)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
+
import {
|
|
17
|
+
CallToolRequestSchema,
|
|
18
|
+
ListToolsRequestSchema,
|
|
19
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
20
|
+
import { Actor, HttpAgent } from "@dfinity/agent";
|
|
21
|
+
import { IDL } from "@dfinity/candid";
|
|
22
|
+
|
|
23
|
+
// ── Config ──────────────────────────────────────────────────────────────────
|
|
24
|
+
const TOKEN = process.env.TOKNBASE_AGENT_TOKEN;
|
|
25
|
+
const CANISTER_ID = process.env.TOKNBASE_CANISTER_ID;
|
|
26
|
+
const IC_HOST = process.env.TOKNBASE_IC_HOST ?? "https://ic0.app";
|
|
27
|
+
|
|
28
|
+
if (!TOKEN) {
|
|
29
|
+
console.error("[toknbase-mcp] Missing TOKNBASE_AGENT_TOKEN environment variable.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
if (!CANISTER_ID) {
|
|
33
|
+
console.error("[toknbase-mcp] Missing TOKNBASE_CANISTER_ID environment variable.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Candid IDL (agent-token surface only) ───────────────────────────────────
|
|
38
|
+
const idlFactory = ({ IDL: I }) => {
|
|
39
|
+
const SecretMeta = I.Record({
|
|
40
|
+
name: I.Text,
|
|
41
|
+
description: I.Text,
|
|
42
|
+
environment: I.Text,
|
|
43
|
+
});
|
|
44
|
+
const FolderMeta = I.Record({
|
|
45
|
+
id: I.Nat,
|
|
46
|
+
name: I.Text,
|
|
47
|
+
});
|
|
48
|
+
return I.Service({
|
|
49
|
+
listSecretsByAgentToken: I.Func([I.Text], [I.Vec(SecretMeta)], ["query"]),
|
|
50
|
+
createSecretByAgentToken: I.Func(
|
|
51
|
+
[I.Text, I.Text, I.Text, I.Text, I.Text],
|
|
52
|
+
[I.Bool],
|
|
53
|
+
[]
|
|
54
|
+
),
|
|
55
|
+
updateSecretByAgentToken: I.Func([I.Text, I.Text, I.Text], [I.Bool], []),
|
|
56
|
+
deleteSecretByAgentToken: I.Func([I.Text, I.Text], [I.Bool], []),
|
|
57
|
+
listFoldersByAgentToken: I.Func([I.Text], [I.Vec(FolderMeta)], ["query"]),
|
|
58
|
+
assignFolderByAgentToken: I.Func([I.Text, I.Nat, I.Text], [I.Bool], []),
|
|
59
|
+
getSecretByAgentToken: I.Func([I.Text, I.Text], [I.Opt(I.Text)], ["query"]),
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ── IC Actor ─────────────────────────────────────────────────────────────────
|
|
64
|
+
const agent = new HttpAgent({ host: IC_HOST });
|
|
65
|
+
// Fetch root key only on local replica
|
|
66
|
+
if (IC_HOST !== "https://ic0.app") {
|
|
67
|
+
await agent.fetchRootKey();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const backend = Actor.createActor(idlFactory, { agent, canisterId: CANISTER_ID });
|
|
71
|
+
|
|
72
|
+
// ── MCP Server ───────────────────────────────────────────────────────────────
|
|
73
|
+
const server = new Server(
|
|
74
|
+
{ name: "toknbase", version: "1.0.0" },
|
|
75
|
+
{ capabilities: { tools: {} } }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Tool definitions
|
|
79
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
80
|
+
tools: [
|
|
81
|
+
{
|
|
82
|
+
name: "toknbase_list_secrets",
|
|
83
|
+
description: "List all secrets you have access to. Returns names, descriptions, and environments -- never plaintext values.",
|
|
84
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "toknbase_reveal_secret",
|
|
88
|
+
description: "Reveal the encrypted value of a specific secret by name. Use this only when you need the actual value.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
name: { type: "string", description: "The exact secret name" },
|
|
93
|
+
},
|
|
94
|
+
required: ["name"],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "toknbase_create_secret",
|
|
99
|
+
description: "Add a new secret to the vault. Requires read_write or full_access scope.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
name: { type: "string", description: "Secret name (e.g. STRIPE_SECRET_KEY)" },
|
|
104
|
+
value: { type: "string", description: "The secret value to store" },
|
|
105
|
+
description: { type: "string", description: "Optional description" },
|
|
106
|
+
environment: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["production", "staging", "development"],
|
|
109
|
+
description: "Environment tag",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ["name", "value"],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "toknbase_update_secret",
|
|
117
|
+
description: "Update the value of an existing secret. Requires read_write or full_access scope.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
name: { type: "string", description: "The exact secret name to update" },
|
|
122
|
+
value: { type: "string", description: "The new secret value" },
|
|
123
|
+
},
|
|
124
|
+
required: ["name", "value"],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "toknbase_delete_secret",
|
|
129
|
+
description: "Permanently delete a secret from the vault. Requires full_access scope. This cannot be undone.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
name: { type: "string", description: "The exact secret name to delete" },
|
|
134
|
+
},
|
|
135
|
+
required: ["name"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "toknbase_list_folders",
|
|
140
|
+
description: "List all folders in the vault.",
|
|
141
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "toknbase_assign_folder",
|
|
145
|
+
description: "Assign a secret to a folder. Requires full_access scope.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
secret_name: { type: "string", description: "The secret name to assign" },
|
|
150
|
+
folder_id: { type: "number", description: "The numeric folder ID" },
|
|
151
|
+
},
|
|
152
|
+
required: ["secret_name", "folder_id"],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
// Tool handlers
|
|
159
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
160
|
+
const { name, arguments: args } = request.params;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
switch (name) {
|
|
164
|
+
case "toknbase_list_secrets": {
|
|
165
|
+
const secrets = await backend.listSecretsByAgentToken(TOKEN);
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: JSON.stringify(secrets, null, 2),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case "toknbase_reveal_secret": {
|
|
177
|
+
const result = await backend.getSecretByAgentToken(args.name, TOKEN);
|
|
178
|
+
if (result.length === 0 || result[0] === undefined) {
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: `Secret '${args.name}' not found or access denied.` }],
|
|
181
|
+
isError: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: result[0] }],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case "toknbase_create_secret": {
|
|
190
|
+
const ok = await backend.createSecretByAgentToken(
|
|
191
|
+
args.name,
|
|
192
|
+
args.value,
|
|
193
|
+
args.description ?? "",
|
|
194
|
+
args.environment ?? "development",
|
|
195
|
+
TOKEN
|
|
196
|
+
);
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: "text",
|
|
201
|
+
text: ok
|
|
202
|
+
? `Secret '${args.name}' created successfully.`
|
|
203
|
+
: `Failed to create secret '${args.name}'. Check your token scope (requires read_write or full_access).`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
isError: !ok,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
case "toknbase_update_secret": {
|
|
211
|
+
const ok = await backend.updateSecretByAgentToken(args.name, args.value, TOKEN);
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: ok
|
|
217
|
+
? `Secret '${args.name}' updated successfully.`
|
|
218
|
+
: `Failed to update secret '${args.name}'. Check that it exists and your token scope (requires read_write or full_access).`,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
isError: !ok,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
case "toknbase_delete_secret": {
|
|
226
|
+
const ok = await backend.deleteSecretByAgentToken(args.name, TOKEN);
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: ok
|
|
232
|
+
? `Secret '${args.name}' deleted.`
|
|
233
|
+
: `Failed to delete secret '${args.name}'. Check that it exists and your token scope (requires full_access).`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
isError: !ok,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case "toknbase_list_folders": {
|
|
241
|
+
const folders = await backend.listFoldersByAgentToken(TOKEN);
|
|
242
|
+
return {
|
|
243
|
+
content: [{ type: "text", text: JSON.stringify(folders, null, 2) }],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case "toknbase_assign_folder": {
|
|
248
|
+
const ok = await backend.assignFolderByAgentToken(
|
|
249
|
+
args.secret_name,
|
|
250
|
+
BigInt(args.folder_id),
|
|
251
|
+
TOKEN
|
|
252
|
+
);
|
|
253
|
+
return {
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
type: "text",
|
|
257
|
+
text: ok
|
|
258
|
+
? `Secret '${args.secret_name}' assigned to folder ${args.folder_id}.`
|
|
259
|
+
: `Failed to assign folder. Check token scope (requires full_access).`,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
isError: !ok,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
default:
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
269
|
+
isError: true,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
275
|
+
isError: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ── Start ────────────────────────────────────────────────────────────────────
|
|
281
|
+
const transport = new StdioServerTransport();
|
|
282
|
+
await server.connect(transport);
|
|
283
|
+
console.error("[toknbase-mcp] Server running. Canister:", CANISTER_ID);
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toknbase/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Toknbase -- zero-knowledge secrets management for AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"toknbase-mcp-server": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
15
|
+
"@dfinity/agent": "^2.1.3",
|
|
16
|
+
"@dfinity/candid": "^2.1.3",
|
|
17
|
+
"@dfinity/principal": "^2.1.3"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["mcp", "secrets", "toknbase", "icp", "zero-knowledge", "ai-agent"],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|