mcpbox 0.2.2 → 0.3.1

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 CHANGED
@@ -11,7 +11,7 @@
11
11
  - Supports Tools, Resources & Prompts
12
12
  - Namespaces with `servername__` prefix to avoid collisions
13
13
  - Per-server tool filtering to limit AI access and reduce context usage
14
- - OAuth or API key authentication
14
+ - OAuth 2.1, API key, or no auth
15
15
 
16
16
  <picture>
17
17
  <source media="(prefers-color-scheme: dark)" srcset="assets/diagram-dark.excalidraw.png">
@@ -28,28 +28,22 @@ Create `mcpbox.json`:
28
28
  "memory": {
29
29
  "command": "npx",
30
30
  "args": ["-y", "@modelcontextprotocol/server-memory"]
31
+ },
32
+ "sequential-thinking": {
33
+ "command": "npx",
34
+ "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
31
35
  }
32
36
  }
33
37
  }
34
38
  ```
35
39
 
36
- Run with:
37
-
38
- **npx**
40
+ Run:
39
41
 
40
42
  ```bash
41
43
  npx mcpbox
42
44
  ```
43
- *MCP server commands (e.g., `uvx`, `docker`) must be available where the box runs.*
44
-
45
- **Docker**
46
-
47
- ```bash
48
- docker run -v ./mcpbox.json:/config/config.json -p 8080:8080 ghcr.io/kandobyte/mcpbox
49
- ```
50
- *The Docker image includes Node.js and Python, supporting MCP servers launched via `npx` and `uvx`.*
51
45
 
52
- The box starts on http://localhost:8080. Connect an agent by adding this to your MCP client config:
46
+ Add to your MCP client config:
53
47
 
54
48
  ```json
55
49
  {
@@ -62,103 +56,6 @@ The box starts on http://localhost:8080. Connect an agent by adding this to your
62
56
  }
63
57
  ```
64
58
 
65
- For remote access with authentication, see [Deployment](#deployment) and [Connect Your AI](#connect-your-ai).
66
-
67
- ## Configuration
68
-
69
- See [`mcpbox.example.jsonc`](mcpbox.example.jsonc) for all options. All string values support `${VAR_NAME}` environment variable substitution.
70
-
71
- **[Authentication](docs/authentication.md)** — none (default), API key, or OAuth.
72
-
73
- ## Deployment
74
-
75
- To expose MCPBox remotely, put it behind a TLS-terminating reverse proxy.
76
-
77
- Before deploying with OAuth:
78
- - [ ] Use sqlite storage for persistence across restarts
79
- - [ ] Set issuer to your public URL
80
- - [ ] Use bcrypt hashes for local passwords
81
-
82
- > [!NOTE]
83
- > MCPBox is single-instance only — don't run multiple instances behind a load balancer.
84
-
85
- ### Quick remote access
86
-
87
- Use [cloudflared](https://github.com/cloudflare/cloudflared) to expose a local instance (no account required):
88
-
89
- ```bash
90
- cloudflared tunnel --url http://localhost:8080
91
- ```
92
-
93
- Then update your config with the generated public URL:
94
-
95
- ```json
96
- {
97
- "auth": {
98
- "type": "oauth",
99
- "issuer": "https://<tunnel-id>.trycloudflare.com",
100
- "identityProviders": [
101
- { "type": "local", "users": [{ "username": "admin", "password": "${MCPBOX_PASSWORD}" }] }
102
- ],
103
- "dynamicRegistration": true
104
- },
105
- "storage": {
106
- "type": "sqlite",
107
- "path": "/data/mcpbox.db"
108
- },
109
- "mcpServers": { ... }
110
- }
111
- ```
112
-
113
- Run with a persistent data volume:
114
-
115
- ```bash
116
- docker run -v ./mcpbox.json:/config/config.json -v ./data:/data -p 8080:8080 ghcr.io/kandobyte/mcpbox
117
- ```
118
-
119
- ## Connect Your AI
120
-
121
- ### Claude Web & Mobile
59
+ ## Documentation
122
60
 
123
- Settings Connectors Add Custom Connector enter your URL → Connect
124
-
125
- Requires `dynamicRegistration: true` in your config.
126
-
127
- ### Claude Code
128
-
129
- ```bash
130
- claude mcp add --transport http mcpbox https://your-mcpbox-url.com
131
- ```
132
-
133
- Requires `dynamicRegistration: true` in your config.
134
-
135
- ### Other MCP clients
136
-
137
- **With dynamic registration (OAuth)** — just provide the URL:
138
-
139
- ```json
140
- {
141
- "mcpServers": {
142
- "mcpbox": {
143
- "type": "http",
144
- "url": "https://your-mcpbox-url.com"
145
- }
146
- }
147
- }
148
- ```
149
-
150
- **With API key:**
151
-
152
- ```json
153
- {
154
- "mcpServers": {
155
- "mcpbox": {
156
- "type": "http",
157
- "url": "https://your-mcpbox-url.com",
158
- "headers": {
159
- "Authorization": "Bearer YOUR_API_KEY"
160
- }
161
- }
162
- }
163
- }
164
- ```
61
+ See the [documentation](https://kandobyte.github.io/mcpbox/quick-start) for configuration, authentication, deployment, and connecting clients.
@@ -2,7 +2,13 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { RawConfigSchema, } from "./schema.js";
3
3
  function substituteEnvVars(obj) {
4
4
  if (typeof obj === "string") {
5
- return obj.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "");
5
+ return obj.replace(/\$\{(\w+)\}/g, (match, name) => {
6
+ const value = process.env[name];
7
+ if (value === undefined) {
8
+ throw new Error(`Environment variable ${name} is not set (referenced as ${match})`);
9
+ }
10
+ return value;
11
+ });
6
12
  }
7
13
  if (Array.isArray(obj)) {
8
14
  return obj.map(substituteEnvVars);
@@ -25,6 +31,8 @@ function parseMcpServers(mcpServers) {
25
31
  args: entry.args,
26
32
  env: entry.env,
27
33
  tools: entry.tools,
34
+ resources: entry.resources,
35
+ prompts: entry.prompts,
28
36
  });
29
37
  }
30
38
  return mcps;
@@ -8,6 +8,8 @@ export declare const McpServerEntrySchema: z.ZodObject<{
8
8
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
9
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
10
10
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
+ resources: z.ZodOptional<z.ZodBoolean>;
12
+ prompts: z.ZodOptional<z.ZodBoolean>;
11
13
  }, z.core.$strict>;
12
14
  /**
13
15
  * OAuth user credentials
@@ -204,6 +206,8 @@ export declare const RawConfigSchema: z.ZodObject<{
204
206
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
205
207
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
206
208
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
209
+ resources: z.ZodOptional<z.ZodBoolean>;
210
+ prompts: z.ZodOptional<z.ZodBoolean>;
207
211
  }, z.core.$strict>>>;
208
212
  }, z.core.$strict>;
209
213
  /**
@@ -216,6 +220,8 @@ export declare const McpConfigSchema: z.ZodObject<{
216
220
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
217
221
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
218
222
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
223
+ resources: z.ZodOptional<z.ZodBoolean>;
224
+ prompts: z.ZodOptional<z.ZodBoolean>;
219
225
  }, z.core.$strip>;
220
226
  /**
221
227
  * Processed config (after loader adds defaults and resolves mcpServers)
@@ -282,6 +288,8 @@ export declare const ConfigSchema: z.ZodObject<{
282
288
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
283
289
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
284
290
  tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
291
+ resources: z.ZodOptional<z.ZodBoolean>;
292
+ prompts: z.ZodOptional<z.ZodBoolean>;
285
293
  }, z.core.$strip>>;
286
294
  }, z.core.$strip>;
287
295
  export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
@@ -17,6 +17,8 @@ export const McpServerEntrySchema = z
17
17
  args: z.array(z.string()).optional(),
18
18
  env: z.record(z.string(), z.string()).optional(),
19
19
  tools: z.array(z.string()).optional(),
20
+ resources: z.boolean().optional(),
21
+ prompts: z.boolean().optional(),
20
22
  })
21
23
  .strict();
22
24
  /**
@@ -201,6 +203,8 @@ export const McpConfigSchema = z.object({
201
203
  args: z.array(z.string()).optional(),
202
204
  env: z.record(z.string(), z.string()).optional(),
203
205
  tools: z.array(z.string()).optional(),
206
+ resources: z.boolean().optional(),
207
+ prompts: z.boolean().optional(),
204
208
  });
205
209
  /**
206
210
  * Processed config (after loader adds defaults and resolves mcpServers)
@@ -106,37 +106,47 @@ export class McpManager {
106
106
  }
107
107
  // Get resources from this MCP (namespace them if multiple servers)
108
108
  let resources = [];
109
- try {
110
- const { resources: rawResources } = await client.listResources();
111
- resources = this.useNamespacing
112
- ? rawResources.map((resource) => ({
113
- ...resource,
114
- uri: namespaceName(config.name, resource.uri),
115
- }))
116
- : rawResources;
117
- for (const resource of resources) {
118
- this.resourceToMcp.set(resource.uri, config.name);
109
+ if (config.resources !== false) {
110
+ try {
111
+ const { resources: rawResources } = await client.listResources();
112
+ resources = this.useNamespacing
113
+ ? rawResources.map((resource) => ({
114
+ ...resource,
115
+ uri: namespaceName(config.name, resource.uri),
116
+ }))
117
+ : rawResources;
118
+ for (const resource of resources) {
119
+ this.resourceToMcp.set(resource.uri, config.name);
120
+ }
121
+ }
122
+ catch {
123
+ logger.debug({ mcp: config.name }, "Server doesn't support resources");
119
124
  }
120
125
  }
121
- catch {
122
- logger.debug({ mcp: config.name }, "Server doesn't support resources");
126
+ else {
127
+ logger.debug({ mcp: config.name }, "Resources disabled by config");
123
128
  }
124
129
  // Get prompts from this MCP (namespace them if multiple servers)
125
130
  let prompts = [];
126
- try {
127
- const { prompts: rawPrompts } = await client.listPrompts();
128
- prompts = this.useNamespacing
129
- ? rawPrompts.map((prompt) => ({
130
- ...prompt,
131
- name: namespaceName(config.name, prompt.name),
132
- }))
133
- : rawPrompts;
134
- for (const prompt of prompts) {
135
- this.promptToMcp.set(prompt.name, config.name);
131
+ if (config.prompts !== false) {
132
+ try {
133
+ const { prompts: rawPrompts } = await client.listPrompts();
134
+ prompts = this.useNamespacing
135
+ ? rawPrompts.map((prompt) => ({
136
+ ...prompt,
137
+ name: namespaceName(config.name, prompt.name),
138
+ }))
139
+ : rawPrompts;
140
+ for (const prompt of prompts) {
141
+ this.promptToMcp.set(prompt.name, config.name);
142
+ }
143
+ }
144
+ catch {
145
+ logger.debug({ mcp: config.name }, "Server doesn't support prompts");
136
146
  }
137
147
  }
138
- catch {
139
- logger.debug({ mcp: config.name }, "Server doesn't support prompts");
148
+ else {
149
+ logger.debug({ mcp: config.name }, "Prompts disabled by config");
140
150
  }
141
151
  this.mcps.set(config.name, {
142
152
  name: config.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpbox",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "A lightweight gateway that exposes local stdio-based MCP servers via Streamable HTTP",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -18,7 +18,10 @@
18
18
  "test:conformance:oauth": "node --disable-warning=ExperimentalWarning --import tsx --test --test-concurrency=1 'test/conformance-oauth/**/*.test.ts'",
19
19
  "test:coverage": "node --experimental-test-coverage --disable-warning=ExperimentalWarning --import tsx --test --test-concurrency=1 'test/unit/**/*.test.ts' 'test/integration/**/*.test.ts' 'test/conformance-oauth/**/*.test.ts'",
20
20
  "format": "biome check --write .",
21
- "check": "biome check ."
21
+ "check": "biome check .",
22
+ "docs:dev": "vitepress dev docs",
23
+ "docs:build": "vitepress build docs",
24
+ "docs:preview": "vitepress preview docs"
22
25
  },
23
26
  "keywords": [
24
27
  "mcp",
@@ -61,6 +64,7 @@
61
64
  "@biomejs/biome": "^2.3.14",
62
65
  "@types/node": "^25.1.0",
63
66
  "tsx": "^4.21.0",
64
- "typescript": "^5.9.3"
67
+ "typescript": "^5.9.3",
68
+ "vitepress": "^1.6.4"
65
69
  }
66
70
  }