@rapidthoughtlabs/heku 0.3.0 → 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.
Files changed (3) hide show
  1. package/README.md +361 -62
  2. package/dist/cli.js +105 -56
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # heku
2
2
 
3
- > One server. Any REST API. Any LLM.
3
+ > One server. Any API. Any LLM.
4
4
 
5
5
  **heku** is a single dynamic [Model Context Protocol](https://modelcontextprotocol.io) server that turns JSON config files into working API tools. No code to write — drop a config, and your LLM gets the tools instantly.
6
6
 
@@ -8,13 +8,21 @@ Stop building one MCP server per API. Build one config.
8
8
 
9
9
  ---
10
10
 
11
+ ## Try it now
12
+
13
+ **[console.rapidthoughtlabs.space](https://console.rapidthoughtlabs.space)** — hosted console you can point at any running heku instance. Connect, browse configs, chat with your tools, and inspect the system prompt — no local build needed.
14
+
15
+ **[app.rapidthoughtlabs.space](https://app.rapidthoughtlabs.space)** — **heku hub**, the online registry for browsing, installing, and publishing heku configs. Find community-built connectors for GitHub, Slack, Linear, and more — or publish your own.
16
+
17
+ ---
18
+
11
19
  ## Features
12
20
 
13
- - **7 connector types** — HTTP, gRPC, GraphQL, CLI, File, child-MCP, and Internal (self-management)
21
+ - **8 connector types** — 4 standard (HTTP, GraphQL, gRPC, child-MCP) + 4 experimental (CLI, File, SQL, MongoDB)
14
22
  - **Hot-reload** — add or edit a config, tools update live without restart
15
23
  - **Auto-discovery** — gRPC reflection, GraphQL introspection, and child MCP tool listing fill in tools automatically
16
24
  - **Built-in console UI** — React dashboard for chat, config editing, and registry browsing
17
- - **Registry** — publish and install community configs from [app.rapidthoughtlabs.space](https://app.rapidthoughtlabs.space)
25
+ - **heku hub** — publish and install community configs from [app.rapidthoughtlabs.space](https://app.rapidthoughtlabs.space)
18
26
  - **Auth handled** — bearer, basic, API key, and OAuth2 with `.env`-based credential management
19
27
  - **Self-managing** — the server can create and edit its own configs via internal tools
20
28
 
@@ -39,12 +47,13 @@ heku start
39
47
 
40
48
  ## Quick start
41
49
 
42
- Create a config in `mcp-configs/mcp.github.json`:
50
+ Create `mcp-configs/mcp.github.json`:
43
51
 
44
52
  ```json
45
53
  {
46
- "id": "github",
54
+ "id": "github-http",
47
55
  "name": "GitHub API",
56
+ "description": "Manage GitHub repos, issues, and pull requests",
48
57
  "connector": {
49
58
  "type": "http",
50
59
  "base_url": "https://api.github.com",
@@ -64,33 +73,327 @@ Create a config in `mcp-configs/mcp.github.json`:
64
73
  }
65
74
  ```
66
75
 
67
- Set your token:
68
-
69
76
  ```bash
70
- heku auth setup github
77
+ heku auth setup github-http # writes GITHUB_TOKEN to .env
78
+ heku start
71
79
  ```
72
80
 
73
- Start the server:
81
+ Your LLM now has a `github-http.list_repos` tool.
74
82
 
75
- ```bash
76
- heku start
83
+ ---
84
+
85
+ ## Connectors
86
+
87
+ Tool names follow the pattern `config_id.tool_name` — e.g. `github-http.list_repos`, `linear-graphql.create_issue`.
88
+
89
+ ### Standard
90
+
91
+ #### `http` — REST API
92
+
93
+ Define each endpoint as a tool. Supports `path`, `query`, `body`, and `header` params.
94
+
95
+ ```json
96
+ {
97
+ "id": "stripe-http",
98
+ "name": "Stripe",
99
+ "connector": {
100
+ "type": "http",
101
+ "base_url": "https://api.stripe.com/v1",
102
+ "auth": { "type": "bearer", "token_env": "STRIPE_API_KEY" }
103
+ },
104
+ "tools": [
105
+ {
106
+ "name": "list_customers",
107
+ "description": "List Stripe customers with optional filters",
108
+ "method": "GET",
109
+ "path": "/customers",
110
+ "params": [
111
+ { "name": "limit", "type": "number", "required": false, "location": "query", "description": "Max results (1–100)" },
112
+ { "name": "email", "type": "string", "required": false, "location": "query", "description": "Filter by email address" }
113
+ ]
114
+ },
115
+ {
116
+ "name": "create_customer",
117
+ "description": "Create a new Stripe customer",
118
+ "method": "POST",
119
+ "path": "/customers",
120
+ "params": [
121
+ { "name": "email", "type": "string", "required": true, "location": "body", "description": "Customer email" },
122
+ { "name": "name", "type": "string", "required": false, "location": "body", "description": "Full name" }
123
+ ]
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ **Tool fields:** `name`, `description`, `method` (`GET`/`POST`/`PUT`/`PATCH`/`DELETE`), `path` (supports `{{param}}` placeholders), `params[]`, `body_template?`, `response_map?`, `error_map?`
130
+
131
+ **Param locations:** `path` · `query` · `body` · `header`
132
+
133
+ ---
134
+
135
+ #### `graphql` — GraphQL API
136
+
137
+ Tools are auto-discovered via introspection. Set `tools: []`.
138
+
139
+ ```json
140
+ {
141
+ "id": "linear-graphql",
142
+ "name": "Linear",
143
+ "connector": {
144
+ "type": "graphql",
145
+ "endpoint": "https://api.linear.app/graphql",
146
+ "auth": { "type": "bearer", "token_env": "LINEAR_API_KEY" },
147
+ "include_mutations": true,
148
+ "include_queries": true
149
+ },
150
+ "tools": []
151
+ }
152
+ ```
153
+
154
+ **Connector fields:** `endpoint`, `auth?`, `introspect?` (default `true`), `include_mutations?` (default `true`), `include_queries?` (default `true`), `headers?`, `timeout_ms?`
155
+
156
+ ---
157
+
158
+ #### `grpc` — gRPC service
159
+
160
+ Tools are auto-discovered via server reflection or a `.proto` file. Set `tools: []`.
161
+
162
+ ```json
163
+ {
164
+ "id": "myservice-grpc",
165
+ "name": "My gRPC Service",
166
+ "connector": {
167
+ "type": "grpc",
168
+ "endpoint": "localhost:50051",
169
+ "reflection": true,
170
+ "tls": false
171
+ },
172
+ "tools": []
173
+ }
174
+ ```
175
+
176
+ Or with a proto file:
177
+
178
+ ```json
179
+ {
180
+ "connector": {
181
+ "type": "grpc",
182
+ "endpoint": "grpc.example.com:443",
183
+ "proto_path": "./protos/service.proto",
184
+ "tls": true,
185
+ "auth": { "type": "bearer", "token_env": "GRPC_TOKEN" }
186
+ }
187
+ }
188
+ ```
189
+
190
+ **Connector fields:** `endpoint`, `reflection?` or `proto_path?` (one required), `tls?` (`true`/`false` or cert object), `auth?`, `metadata?`, `service_filter?`, `timeout_ms?`
191
+
192
+ ---
193
+
194
+ #### `mcp` — child MCP server
195
+
196
+ Spawn any existing MCP server (stdio or SSE) and proxy its tools through heku. Tools are auto-discovered. Set `tools: []`.
197
+
198
+ ```json
199
+ {
200
+ "id": "filesystem-mcp",
201
+ "name": "Filesystem MCP",
202
+ "connector": {
203
+ "type": "mcp",
204
+ "transport": "stdio",
205
+ "command": "npx",
206
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
207
+ "install_command": "npm",
208
+ "install_args": ["install", "-g", "@modelcontextprotocol/server-filesystem"]
209
+ },
210
+ "tools": []
211
+ }
212
+ ```
213
+
214
+ SSE transport:
215
+
216
+ ```json
217
+ {
218
+ "connector": {
219
+ "type": "mcp",
220
+ "transport": "sse",
221
+ "url": "http://localhost:8080/sse"
222
+ }
223
+ }
224
+ ```
225
+
226
+ **Connector fields:** `transport` (`stdio`/`sse`), `command?` + `args?` + `env?` (stdio), `url?` (sse), `install_command?`, `install_args?`, `install_cwd?`, `install_env?`, `install_timeout_ms?`, `active?`
227
+
228
+ > **Note:** `mcp` configs cannot be published to the registry — they reference local processes.
229
+
230
+ ---
231
+
232
+ ### Experimental
233
+
234
+ > These connector types are functional but their config schema and behaviour may change in future releases.
235
+
236
+ ---
237
+
238
+ #### `cli` — shell command
239
+
240
+ Wrap any CLI tool as an MCP tool. Use `args_template` for positional args or `stdin_template` to pipe input.
241
+
242
+ ```json
243
+ {
244
+ "id": "git-cli",
245
+ "name": "Git",
246
+ "connector": { "type": "cli" },
247
+ "tools": [
248
+ {
249
+ "name": "log",
250
+ "description": "Show recent git commits",
251
+ "args_template": ["git", "log", "--oneline", "-{{limit}}"],
252
+ "params": [
253
+ { "name": "limit", "type": "number", "required": false, "description": "Number of commits to show" }
254
+ ],
255
+ "output_as": "text"
256
+ },
257
+ {
258
+ "name": "diff",
259
+ "description": "Show unstaged changes",
260
+ "command": "git diff",
261
+ "params": [],
262
+ "output_as": "text"
263
+ }
264
+ ]
265
+ }
266
+ ```
267
+
268
+ **Tool fields:** `name`, `description`, `params[]`, `command?` (string) or `args_template?` (array), `stdin_template?`, `output_as?` (`"text"` | `"json"`)
269
+
270
+ ---
271
+
272
+ #### `file` — filesystem
273
+
274
+ Read, write, append, delete, or list files. `path_template` supports `{{param}}` placeholders.
275
+
276
+ ```json
277
+ {
278
+ "id": "notes-file",
279
+ "name": "Notes",
280
+ "connector": { "type": "file" },
281
+ "tools": [
282
+ {
283
+ "name": "read_note",
284
+ "description": "Read a note by name",
285
+ "operation": "read",
286
+ "path_template": "/home/user/notes/{{name}}.md",
287
+ "params": [
288
+ { "name": "name", "type": "string", "required": true, "description": "Note filename without extension" }
289
+ ]
290
+ },
291
+ {
292
+ "name": "save_note",
293
+ "description": "Save or overwrite a note",
294
+ "operation": "write",
295
+ "path_template": "/home/user/notes/{{name}}.md",
296
+ "content_template": "{{content}}",
297
+ "params": [
298
+ { "name": "name", "type": "string", "required": true, "description": "Note filename without extension" },
299
+ { "name": "content", "type": "string", "required": true, "description": "Note content" }
300
+ ]
301
+ }
302
+ ]
303
+ }
304
+ ```
305
+
306
+ **Tool fields:** `name`, `description`, `params[]`, `operation` (`read`/`write`/`append`/`delete`/`list`), `path_template`, `content_template?` (required for `write`/`append`)
307
+
308
+ ---
309
+
310
+ #### `sql` — relational database
311
+
312
+ Named SQL queries with `:param` placeholders. Supports PostgreSQL, MySQL, and SQLite.
313
+
314
+ ```json
315
+ {
316
+ "id": "analytics-sql",
317
+ "name": "Analytics DB",
318
+ "connector": {
319
+ "type": "sql",
320
+ "dialect": "postgres",
321
+ "connection_string_env": "DATABASE_URL"
322
+ },
323
+ "tools": [
324
+ {
325
+ "name": "active_users",
326
+ "description": "Count active users in a date range",
327
+ "sql": "SELECT COUNT(*) as count FROM users WHERE created_at BETWEEN :from AND :to AND active = true",
328
+ "params": [
329
+ { "name": "from", "type": "string", "required": true, "description": "Start date (ISO 8601)" },
330
+ { "name": "to", "type": "string", "required": true, "description": "End date (ISO 8601)" }
331
+ ],
332
+ "max_rows": 1
333
+ }
334
+ ]
335
+ }
77
336
  ```
78
337
 
79
- Your LLM now has a `github.list_repos` tool. That's it.
338
+ **Connector fields:** `dialect` (`postgres`/`mysql`/`sqlite`), `connection_string_env?` or field-based (`host`, `port`, `database`, `username_env`, `password_env`), `ssl?`, `pool_max?`
339
+
340
+ **Tool fields:** `name`, `description`, `params[]`, `sql` (`:name` placeholders only — no `{{}}`, no `?`, no `$N`), `max_rows?` (1–10000), `timeout_ms?`
80
341
 
81
342
  ---
82
343
 
83
- ## Connector types
344
+ #### `mongodb` — MongoDB
345
+
346
+ Document operations with JSON templates. Placeholders use `{{param}}` in templates.
347
+
348
+ ```json
349
+ {
350
+ "id": "catalog-mongo",
351
+ "name": "Product Catalog",
352
+ "connector": {
353
+ "type": "mongodb",
354
+ "database": "catalog",
355
+ "connection_string_env": "MONGO_URI"
356
+ },
357
+ "tools": [
358
+ {
359
+ "name": "find_products",
360
+ "description": "Search products by category and price range",
361
+ "collection": "products",
362
+ "operation": "find",
363
+ "filter_template": { "category": "{{category}}", "price": { "$lte": "{{max_price}}" } },
364
+ "params": [
365
+ { "name": "category", "type": "string", "required": true, "description": "Product category" },
366
+ { "name": "max_price", "type": "number", "required": false, "description": "Maximum price" }
367
+ ],
368
+ "max_rows": 50
369
+ }
370
+ ]
371
+ }
372
+ ```
373
+
374
+ **Connector fields:** `database`, `connection_string_env?` or `host?`+`port?`, `auth_source?`, `tls?`
375
+
376
+ **Tool fields:** `name`, `description`, `params[]`, `collection`, `operation` (`find`/`findOne`/`aggregate`/`insertOne`/`insertMany`/`updateOne`/`updateMany`/`deleteOne`/`deleteMany`/`countDocuments`/`distinct`), plus operation-specific templates: `filter_template`, `update_template`, `document_template`, `documents_template`, `pipeline_template`, `projection?`, `sort?`, `max_rows?`, `limit?`, `timeout_ms?`
377
+
378
+ ---
84
379
 
85
- | Type | Use case |
380
+ ## Auth types
381
+
382
+ All credentials read from environment variables — `heku auth setup` writes them to `.env`:
383
+
384
+ | Type | Header |
86
385
  |---|---|
87
- | `http` | REST APIs define method, path, params, response mapping |
88
- | `grpc` | gRPC services — load via `.proto` file or server reflection; tools auto-discovered |
89
- | `graphql` | GraphQL APIs introspection-based auto-discovery, or define operations manually |
90
- | `cli` | Wrap any shell command as a tool with templated args/stdin |
91
- | `file` | Filesystem operations: read, write, append, delete, list |
92
- | `mcp` | Spawn another MCP server (stdio or SSE) and proxy its tools through heku |
93
- | `internal` | heku's own management surface — create configs, install from registry, set auth |
386
+ | `bearer` | `Authorization: Bearer {token}` |
387
+ | `basic` | `Authorization: Basic base64(user:token)` |
388
+ | `api_key` | Custom header, e.g. `X-API-Key` |
389
+ | `oauth2_static` | Pre-acquired OAuth2 access token |
390
+
391
+ ```json
392
+ { "type": "bearer", "token_env": "GITHUB_TOKEN" }
393
+ { "type": "api_key", "key_env": "MY_KEY", "header_name": "X-Api-Key" }
394
+ { "type": "basic", "username_env": "MY_USER", "token_env": "MY_PASS" }
395
+ { "type": "oauth2_static","token_env": "MY_OAUTH_TOKEN" }
396
+ ```
94
397
 
95
398
  ---
96
399
 
@@ -119,43 +422,17 @@ heku update Update heku to the latest version
119
422
  heku help Show usage
120
423
  ```
121
424
 
122
- Run with `--http` to start the console UI alongside the stdio server:
425
+ Start with the console UI:
123
426
 
124
427
  ```bash
125
428
  heku start --http --port 3456
126
429
  ```
127
430
 
128
- ---
129
-
130
- ## Configuration
131
-
132
- ### Config file shape
133
-
134
- Configs live in `mcp-configs/mcp.{id}.json`:
135
-
136
- ```typescript
137
- {
138
- id: string; // becomes the tool namespace prefix
139
- name: string;
140
- description?: string; // shown to the LLM
141
- connector: ConnectorConfig; // one of 7 types
142
- tools: ToolDef[]; // empty for auto-discovery (grpc/graphql/mcp)
143
- overlays?: { // override tool descriptions without editing the config
144
- [toolName: string]: { description?: string }
145
- };
146
- }
147
- ```
148
-
149
- ### Auth types
150
-
151
- All credentials read from environment variables — `heku auth setup` writes them to `.env`:
431
+ Then open **[console.rapidthoughtlabs.space](https://console.rapidthoughtlabs.space)** and connect to `http://localhost:3456`.
152
432
 
153
- - **`bearer`** — `Authorization: Bearer {token}`
154
- - **`basic`** — base64(username:token)
155
- - **`api_key`** — custom header (e.g. `X-API-Key`)
156
- - **`oauth2_static`** — pre-acquired OAuth2 access token
433
+ ---
157
434
 
158
- ### System config (optional)
435
+ ## System config (optional)
159
436
 
160
437
  Drop `heku.config.json` in your config directory:
161
438
 
@@ -163,7 +440,7 @@ Drop `heku.config.json` in your config directory:
163
440
  {
164
441
  "log_level": "info",
165
442
  "rate_limits": {
166
- "github": { "requests_per_minute": 60 }
443
+ "github-http": { "requests_per_minute": 60 }
167
444
  },
168
445
  "self_config": true
169
446
  }
@@ -173,28 +450,27 @@ Drop `heku.config.json` in your config directory:
173
450
 
174
451
  ## Console UI
175
452
 
176
- The dashboard is a React + Vite app that talks to the heku server:
453
+ The dashboard is a React + Vite app available hosted at **[console.rapidthoughtlabs.space](https://console.rapidthoughtlabs.space)** or embedded when you run `heku start --http`.
177
454
 
178
- - **Chat** — test tools through a model of your choice
455
+ - **Chat** — test tools through a model of your choice (OpenAI, Together AI)
179
456
  - **Configs** — visual editor for connector and tool definitions
457
+ - **Prompts** — inspect the system prompt layers and token counts
180
458
  - **Registry** — browse, install, and publish configs
181
- - **Auth** — see credential status across all configs at a glance
182
-
183
- Built with React 19, TailwindCSS v4, Zustand, and the MCP SDK.
459
+ - **Auth** — credential status across all configs at a glance
184
460
 
185
461
  ---
186
462
 
187
- ## Registry
463
+ ## heku hub
188
464
 
189
- [**app.rapidthoughtlabs.space**](https://app.rapidthoughtlabs.space) is the default registry for sharing configs.
465
+ **[app.rapidthoughtlabs.space](https://app.rapidthoughtlabs.space)** is the default registry for sharing configs — browse community connectors, install with one command, and publish your own.
190
466
 
191
467
  ```bash
192
468
  heku install @rtl/github
193
469
  heku install @rtl/slack@1.2.0
194
- heku publish my-config.json
470
+ heku publish mcp-configs/mcp.stripe-http.json
195
471
  ```
196
472
 
197
- Use `--registry` to point at a different one.
473
+ Use `--registry <url>` to point at a self-hosted registry.
198
474
 
199
475
  ---
200
476
 
@@ -227,7 +503,30 @@ mcp-configs/ Local config files (gitignored)
227
503
 
228
504
  ## Tech stack
229
505
 
230
- TypeScript · Node.js (ESM) · `@modelcontextprotocol/sdk` · Express · React 19 · Vite · TailwindCSS · Zustand · `@grpc/grpc-js` · GraphQL · tsup · Vitest
506
+ TypeScript · Node.js (ESM) · `@modelcontextprotocol/sdk` · Express · React 19 · Vite · Zustand · `@grpc/grpc-js` · GraphQL · tsup · Vitest
507
+
508
+ ---
509
+
510
+ ## Changelog
511
+
512
+ ### 0.3.1
513
+ - Renamed meta-tool namespace from `mcp.one.*` to `heku.*` across all connectors, prompts, and client code
514
+ - Fixed deployed console manifest style switcher (settings API calls now use the correct bridge base URL)
515
+ - Fixed prompt page config catalog not refreshing when heku connects after page load
516
+ - Markdown rendering in the demo chat — assistant responses now render formatted text
517
+ - Config catalog descriptions now show in composed prompt preview; falls back to display name when description is absent
518
+ - Dual manifest preview in Prompts page — flat and namespaced styles with separate token counts
519
+ - heku server version now reads from `package.json` at runtime in dev mode instead of showing `0.0.0-dev`
520
+
521
+ ### 0.3.0
522
+ - Registry versioning overhaul — semver-based publish/install flow
523
+ - CLI registry commands: `install`, `uninstall`, `fork`, `publish`
524
+ - Console registry browser tab
525
+
526
+ ### 0.2.x
527
+ - SQL and MongoDB connector types (experimental)
528
+ - Config write lock — block LLM agents from mutating configs
529
+ - Hot-reload watcher improvements
231
530
 
232
531
  ---
233
532
 
package/dist/cli.js CHANGED
@@ -26827,10 +26827,10 @@ var require_view = __commonJS({
26827
26827
  var debug = require_src()("express:view");
26828
26828
  var path24 = __require("path");
26829
26829
  var fs23 = __require("fs");
26830
- var dirname3 = path24.dirname;
26830
+ var dirname4 = path24.dirname;
26831
26831
  var basename3 = path24.basename;
26832
26832
  var extname2 = path24.extname;
26833
- var join3 = path24.join;
26833
+ var join4 = path24.join;
26834
26834
  var resolve3 = path24.resolve;
26835
26835
  module2.exports = View;
26836
26836
  function View(name, options) {
@@ -26866,7 +26866,7 @@ var require_view = __commonJS({
26866
26866
  for (var i = 0; i < roots.length && !path25; i++) {
26867
26867
  var root = roots[i];
26868
26868
  var loc = resolve3(root, name);
26869
- var dir = dirname3(loc);
26869
+ var dir = dirname4(loc);
26870
26870
  var file2 = basename3(loc);
26871
26871
  path25 = this.resolve(dir, file2);
26872
26872
  }
@@ -26892,12 +26892,12 @@ var require_view = __commonJS({
26892
26892
  };
26893
26893
  View.prototype.resolve = function resolve4(dir, file2) {
26894
26894
  var ext = this.ext;
26895
- var path25 = join3(dir, file2);
26895
+ var path25 = join4(dir, file2);
26896
26896
  var stat4 = tryStat(path25);
26897
26897
  if (stat4 && stat4.isFile()) {
26898
26898
  return path25;
26899
26899
  }
26900
- path25 = join3(dir, basename3(file2, ext), "index" + ext);
26900
+ path25 = join4(dir, basename3(file2, ext), "index" + ext);
26901
26901
  stat4 = tryStat(path25);
26902
26902
  if (stat4 && stat4.isFile()) {
26903
26903
  return path25;
@@ -30573,7 +30573,7 @@ var require_send = __commonJS({
30573
30573
  var Stream = __require("stream");
30574
30574
  var util2 = __require("util");
30575
30575
  var extname2 = path24.extname;
30576
- var join3 = path24.join;
30576
+ var join4 = path24.join;
30577
30577
  var normalize2 = path24.normalize;
30578
30578
  var resolve3 = path24.resolve;
30579
30579
  var sep = path24.sep;
@@ -30745,7 +30745,7 @@ var require_send = __commonJS({
30745
30745
  return res;
30746
30746
  }
30747
30747
  parts = path25.split(sep);
30748
- path25 = normalize2(join3(root, path25));
30748
+ path25 = normalize2(join4(root, path25));
30749
30749
  } else {
30750
30750
  if (UP_PATH_REGEXP.test(path25)) {
30751
30751
  debug('malicious path "%s"', path25);
@@ -30878,7 +30878,7 @@ var require_send = __commonJS({
30878
30878
  if (err) return self2.onStatError(err);
30879
30879
  return self2.error(404);
30880
30880
  }
30881
- var p = join3(path25, self2._index[i]);
30881
+ var p = join4(path25, self2._index[i]);
30882
30882
  debug('stat "%s"', p);
30883
30883
  fs23.stat(p, function(err2, stat4) {
30884
30884
  if (err2) return next(err2);
@@ -79142,7 +79142,7 @@ var require_named_placeholders = __commonJS({
79142
79142
  }
79143
79143
  return s;
79144
79144
  }
79145
- function join3(tree) {
79145
+ function join4(tree) {
79146
79146
  if (tree.length === 1) {
79147
79147
  return tree;
79148
79148
  }
@@ -79168,7 +79168,7 @@ var require_named_placeholders = __commonJS({
79168
79168
  if (cache2 && (tree = cache2.get(query))) {
79169
79169
  return toArrayParams(tree, paramsObj);
79170
79170
  }
79171
- tree = join3(parse3(query));
79171
+ tree = join4(parse3(query));
79172
79172
  if (cache2) {
79173
79173
  cache2.set(query, tree);
79174
79174
  }
@@ -81773,9 +81773,9 @@ var require_bindings = __commonJS({
81773
81773
  init_esm_shims();
81774
81774
  var fs23 = __require("fs");
81775
81775
  var path24 = __require("path");
81776
- var fileURLToPath3 = require_file_uri_to_path();
81777
- var join3 = path24.join;
81778
- var dirname3 = path24.dirname;
81776
+ var fileURLToPath4 = require_file_uri_to_path();
81777
+ var join4 = path24.join;
81778
+ var dirname4 = path24.dirname;
81779
81779
  var exists = fs23.accessSync && function(path25) {
81780
81780
  try {
81781
81781
  fs23.accessSync(path25);
@@ -81834,7 +81834,7 @@ var require_bindings = __commonJS({
81834
81834
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
81835
81835
  var tries = [], i = 0, l = opts.try.length, n, b, err;
81836
81836
  for (; i < l; i++) {
81837
- n = join3.apply(
81837
+ n = join4.apply(
81838
81838
  null,
81839
81839
  opts.try[i].map(function(p) {
81840
81840
  return opts[p] || p;
@@ -81885,17 +81885,17 @@ var require_bindings = __commonJS({
81885
81885
  Error.stackTraceLimit = origSTL;
81886
81886
  var fileSchema = "file://";
81887
81887
  if (fileName.indexOf(fileSchema) === 0) {
81888
- fileName = fileURLToPath3(fileName);
81888
+ fileName = fileURLToPath4(fileName);
81889
81889
  }
81890
81890
  return fileName;
81891
81891
  };
81892
81892
  exports2.getRoot = function getRoot(file2) {
81893
- var dir = dirname3(file2), prev;
81893
+ var dir = dirname4(file2), prev;
81894
81894
  while (true) {
81895
81895
  if (dir === ".") {
81896
81896
  dir = process.cwd();
81897
81897
  }
81898
- if (exists(join3(dir, "package.json")) || exists(join3(dir, "node_modules"))) {
81898
+ if (exists(join4(dir, "package.json")) || exists(join4(dir, "node_modules"))) {
81899
81899
  return dir;
81900
81900
  }
81901
81901
  if (prev === dir) {
@@ -81904,7 +81904,7 @@ var require_bindings = __commonJS({
81904
81904
  );
81905
81905
  }
81906
81906
  prev = dir;
81907
- dir = join3(dir, "..");
81907
+ dir = join4(dir, "..");
81908
81908
  }
81909
81909
  };
81910
81910
  }
@@ -130795,7 +130795,7 @@ function isConnectorType(value) {
130795
130795
  // src/lib/config-rules.ts
130796
130796
  init_esm_shims();
130797
130797
  init_env_store();
130798
- var RESERVED_IDS = ["one"];
130798
+ var RESERVED_IDS = ["heku"];
130799
130799
  function validateBaseId(id) {
130800
130800
  if (!id || typeof id !== "string" || id.trim() === "") {
130801
130801
  return "id must be a non-empty string";
@@ -131059,7 +131059,10 @@ function getInstalledEntry(slug, registry2 = "default") {
131059
131059
 
131060
131060
  // src/lib/version.ts
131061
131061
  init_esm_shims();
131062
- var VERSION = true ? "0.3.0" : "0.0.0-dev";
131062
+ import { readFileSync } from "fs";
131063
+ import { fileURLToPath as fileURLToPath2 } from "url";
131064
+ import { join, dirname } from "path";
131065
+ var VERSION = true ? "0.3.1" : readPkgVersion();
131063
131066
 
131064
131067
  // src/connectors/mcp.ts
131065
131068
  init_esm_shims();
@@ -133923,8 +133926,40 @@ var serverSettings = {
133923
133926
  function getServerSettings() {
133924
133927
  return { ...serverSettings };
133925
133928
  }
133929
+ var DISCOVERY_FLAT_SET = /* @__PURE__ */ new Set(["heku.search", "heku.list_tools", "heku.list_configs", "heku.invoke"]);
133930
+ var DISCOVERY_TRIO_SET = /* @__PURE__ */ new Set(["heku.search", "heku.list_tools", "heku.list_configs"]);
133926
133931
  function createAdminRouter(ctx) {
133927
133932
  const router = (0, import_express.Router)();
133933
+ router.get("/tools-manifest", (req, res) => {
133934
+ const styleParam = req.query.style;
133935
+ const style = styleParam === "namespaced" || styleParam === "flat" ? styleParam : serverSettings.manifestStyle;
133936
+ const tools = ctx.registry.list();
133937
+ if (style === "namespaced") {
133938
+ const visible2 = tools.filter(
133939
+ (rt) => DISCOVERY_TRIO_SET.has(`${rt.configId}.${rt.tool.name}`)
133940
+ );
133941
+ res.json(
133942
+ visible2.map((rt) => ({
133943
+ name: `${rt.configId}.${rt.tool.name}`,
133944
+ description: rt.tool.description,
133945
+ inputSchema: buildInputSchema2(rt.tool.params ?? []),
133946
+ configId: rt.configId
133947
+ }))
133948
+ );
133949
+ return;
133950
+ }
133951
+ const visible = tools.filter(
133952
+ (rt) => DISCOVERY_FLAT_SET.has(`${rt.configId}.${rt.tool.name}`)
133953
+ );
133954
+ res.json(
133955
+ visible.map((rt) => ({
133956
+ name: rt.tool.name,
133957
+ description: rt.tool.description,
133958
+ inputSchema: buildInputSchema2(rt.tool.params ?? []),
133959
+ configId: rt.configId
133960
+ }))
133961
+ );
133962
+ });
133928
133963
  router.get("/server-settings", (_req, res) => {
133929
133964
  res.json({
133930
133965
  hotReload: ctx.watcher ? !ctx.watcher.isPaused() : serverSettings.hotReload,
@@ -134350,8 +134385,8 @@ var ToolRegistry = class {
134350
134385
  return this.tools.size;
134351
134386
  }
134352
134387
  };
134353
- var DISCOVERY_FLAT = /* @__PURE__ */ new Set(["one.search", "one.list_tools", "one.list_configs", "one.invoke"]);
134354
- var DISCOVERY_TRIO = /* @__PURE__ */ new Set(["one.search", "one.list_tools", "one.list_configs"]);
134388
+ var DISCOVERY_FLAT = /* @__PURE__ */ new Set(["heku.search", "heku.list_tools", "heku.list_configs", "heku.invoke"]);
134389
+ var DISCOVERY_TRIO = /* @__PURE__ */ new Set(["heku.search", "heku.list_tools", "heku.list_configs"]);
134355
134390
  function buildManifestView(style, registry2) {
134356
134391
  if (style === "namespaced") {
134357
134392
  return {
@@ -134362,24 +134397,24 @@ function buildManifestView(style, registry2) {
134362
134397
  }
134363
134398
  return {
134364
134399
  visible: registry2.list().filter((rt) => DISCOVERY_FLAT.has(`${rt.configId}.${rt.tool.name}`)),
134365
- rewrite: (qualified) => qualified.replace(/^one\./, ""),
134366
- resolve: (incoming) => incoming.includes(".") ? incoming : `one.${incoming}`
134400
+ rewrite: (qualified) => qualified.replace(/^heku\./, ""),
134401
+ resolve: (incoming) => incoming.includes(".") ? incoming : `heku.${incoming}`
134367
134402
  };
134368
134403
  }
134369
134404
  var BLOCKED_WHEN_LOCKED = /* @__PURE__ */ new Set([
134370
- "one.create_config",
134371
- "one.update_config",
134372
- "one.delete_config",
134373
- "one.add_tool",
134374
- "one.remove_tool",
134375
- "one.update_tool",
134376
- "one.registry_install",
134377
- "one.registry_update",
134378
- "one.auth_set"
134405
+ "heku.create_config",
134406
+ "heku.update_config",
134407
+ "heku.delete_config",
134408
+ "heku.add_tool",
134409
+ "heku.remove_tool",
134410
+ "heku.update_tool",
134411
+ "heku.registry_install",
134412
+ "heku.registry_update",
134413
+ "heku.auth_set"
134379
134414
  ]);
134380
134415
  function blockedTool(qualifiedName, args2) {
134381
134416
  if (BLOCKED_WHEN_LOCKED.has(qualifiedName)) return qualifiedName;
134382
- if (qualifiedName === "one.invoke") {
134417
+ if (qualifiedName === "heku.invoke") {
134383
134418
  const target = args2["tool"];
134384
134419
  if (typeof target === "string" && BLOCKED_WHEN_LOCKED.has(target)) return target;
134385
134420
  }
@@ -135373,9 +135408,9 @@ var NodeFsHandler = class {
135373
135408
  if (this.fsw.closed) {
135374
135409
  return;
135375
135410
  }
135376
- const dirname3 = sp.dirname(file2);
135411
+ const dirname4 = sp.dirname(file2);
135377
135412
  const basename3 = sp.basename(file2);
135378
- const parent = this.fsw._getWatchedDir(dirname3);
135413
+ const parent = this.fsw._getWatchedDir(dirname4);
135379
135414
  let prevStats = stats;
135380
135415
  if (parent.has(basename3))
135381
135416
  return;
@@ -135402,7 +135437,7 @@ var NodeFsHandler = class {
135402
135437
  prevStats = newStats2;
135403
135438
  }
135404
135439
  } catch (error3) {
135405
- this.fsw._remove(dirname3, basename3);
135440
+ this.fsw._remove(dirname4, basename3);
135406
135441
  }
135407
135442
  } else if (parent.has(basename3)) {
135408
135443
  const at = newStats.atimeMs;
@@ -136365,7 +136400,7 @@ init_loader();
136365
136400
  var DEBOUNCE_MS = 300;
136366
136401
  function isMcpConfigFile(filePath) {
136367
136402
  const base2 = path11.basename(filePath);
136368
- return base2.startsWith("mcp.") && base2.endsWith(".json") && base2 !== "mcp.one.json";
136403
+ return base2.startsWith("mcp.") && base2.endsWith(".json") && base2 !== "mcp.heku.json";
136369
136404
  }
136370
136405
  function isMcpEnvFile(filePath) {
136371
136406
  const base2 = path11.basename(filePath);
@@ -136687,7 +136722,7 @@ function resolveConfigDir(cliOverride, systemConfig) {
136687
136722
  init_esm_shims();
136688
136723
  var import_express5 = __toESM(require_express2(), 1);
136689
136724
  var import_cors = __toESM(require_lib3(), 1);
136690
- import { fileURLToPath as fileURLToPath2 } from "url";
136725
+ import { fileURLToPath as fileURLToPath3 } from "url";
136691
136726
 
136692
136727
  // server/mcp-client.ts
136693
136728
  init_esm_shims();
@@ -137614,7 +137649,21 @@ function createApiRouter(mcp) {
137614
137649
  }
137615
137650
  }
137616
137651
  });
137617
- router.get("/tools/manifest", (_req, res) => {
137652
+ router.get("/tools/manifest", async (req, res) => {
137653
+ const style = req.query.style;
137654
+ if (style === "flat" || style === "namespaced") {
137655
+ try {
137656
+ const tools = await admin.get(`/tools-manifest?style=${style}`);
137657
+ res.json(tools);
137658
+ } catch (err) {
137659
+ if (err instanceof AdminUnavailableError) {
137660
+ res.json([]);
137661
+ return;
137662
+ }
137663
+ res.status(500).json({ error: err.message });
137664
+ }
137665
+ return;
137666
+ }
137618
137667
  res.json(mcp.listTools());
137619
137668
  });
137620
137669
  router.post("/tools/call", async (req, res) => {
@@ -138163,7 +138212,7 @@ async function startBridge(options) {
138163
138212
  }
138164
138213
  };
138165
138214
  }
138166
- var isMain = process.argv[1] === fileURLToPath2(import.meta.url);
138215
+ var isMain = process.argv[1] === fileURLToPath3(import.meta.url);
138167
138216
  if (isMain) {
138168
138217
  const port = Number(process.env["PORT"] ?? 3456);
138169
138218
  const bridge = await startBridge({ port });
@@ -138191,7 +138240,7 @@ init_env_store();
138191
138240
  // src/internal-config.ts
138192
138241
  init_esm_shims();
138193
138242
  var INTERNAL_CONFIG = {
138194
- id: "one",
138243
+ id: "heku",
138195
138244
  name: "heku Self-Management",
138196
138245
  description: "Create configs, add tools, install from registry, manage auth \u2014 heku's own management interface for LLM agents.",
138197
138246
  connector: { type: "internal" },
@@ -138222,7 +138271,7 @@ var INTERNAL_CONFIG = {
138222
138271
  },
138223
138272
  {
138224
138273
  name: "search",
138225
- description: "Find tools by name or intent across all configs (or within a specific one). Returns matching tools with full schemas grouped by quality: exact \u2192 partial \u2192 description \u2192 related. Covers both native `one.*` self-management tools and all loaded service tools. Narrow by config: `github-http` returns only github-http tools; `github` matches github-http and github-cli. Once you have a tool name, call `invoke` with `config_id.tool_name` to execute it.",
138274
+ description: "Find tools by name or intent across all configs (or within a specific one). Returns matching tools with full schemas grouped by quality: exact \u2192 partial \u2192 description \u2192 related. Covers both native `heku.*` self-management tools and all loaded service tools. Narrow by config: `github-http` returns only github-http tools; `github` matches github-http and github-cli. Once you have a tool name, call `invoke` with `config_id.tool_name` to execute it.",
138226
138275
  params: [
138227
138276
  {
138228
138277
  name: "query",
@@ -138240,13 +138289,13 @@ var INTERNAL_CONFIG = {
138240
138289
  },
138241
138290
  {
138242
138291
  name: "invoke",
138243
- description: "Execute any registered tool by its qualified name (`config_id.tool_name`). Workflow: (1) list_configs \u2192 pick a config id, (2) search or list_tools \u2192 get the tool name and required args, (3) invoke \u2192 run it. Examples: `github-http.create_issue`, `linear-graphql.create_issue`, `one.server_status`.",
138292
+ description: "Execute any registered tool by its qualified name (`config_id.tool_name`). Workflow: (1) list_configs \u2192 pick a config id, (2) search or list_tools \u2192 get the tool name and required args, (3) invoke \u2192 run it. Examples: `github-http.create_issue`, `linear-graphql.create_issue`, `heku.server_status`.",
138244
138293
  params: [
138245
138294
  {
138246
138295
  name: "tool",
138247
138296
  type: "string",
138248
138297
  required: true,
138249
- description: "Qualified tool name in the format config_id.tool_name (e.g. 'open-meteo-http.get_forecast', 'github-http.create_issue', 'one.server_status')"
138298
+ description: "Qualified tool name in the format config_id.tool_name (e.g. 'open-meteo-http.get_forecast', 'github-http.create_issue', 'heku.server_status')"
138250
138299
  },
138251
138300
  {
138252
138301
  name: "args",
@@ -138268,7 +138317,7 @@ var INTERNAL_CONFIG = {
138268
138317
  },
138269
138318
  {
138270
138319
  name: "delete_config",
138271
- description: "Delete a config file and unregister all its tools. Cannot delete the 'one' self-management config \u2014 use self_config: false in heku.config.json instead.",
138320
+ description: "Delete a config file and unregister all its tools. Cannot delete the 'heku' self-management config \u2014 use self_config: false in heku.config.json instead.",
138272
138321
  params: [
138273
138322
  { name: "config_id", type: "string", required: true, description: "Config ID to delete" }
138274
138323
  ]
@@ -138307,9 +138356,9 @@ var INTERNAL_CONFIG = {
138307
138356
  },
138308
138357
  {
138309
138358
  name: "list_tools",
138310
- description: "Returns tools with full schemas ready to call. No args: returns only the native `one.*` self-management surface. With config_id: returns all tools in that config. For targeted lookup by name or intent, use `one.search` instead.",
138359
+ description: "Returns tools with full schemas ready to call. No args: returns only the native `heku.*` self-management surface. With config_id: returns all tools in that config. For targeted lookup by name or intent, use `heku.search` instead.",
138311
138360
  params: [
138312
- { name: "config_id", type: "string", required: false, description: "Config ID to list tools for. Omit to list only native one.* tools." }
138361
+ { name: "config_id", type: "string", required: false, description: "Config ID to list tools for. Omit to list only native heku.* tools." }
138313
138362
  ]
138314
138363
  },
138315
138364
  {
@@ -141441,7 +141490,7 @@ async function handleUpdateTool(ctx, args2) {
141441
141490
  }
141442
141491
  async function handleListTools(ctx, args2) {
141443
141492
  const filterById = args2.config_id;
141444
- const scopeId = filterById ?? "one";
141493
+ const scopeId = filterById ?? "heku";
141445
141494
  const tools = ctx.registry.list().filter((rt) => rt.configId === scopeId).map((rt) => ({
141446
141495
  qualified_name: `${rt.configId}.${rt.tool.name}`,
141447
141496
  config_id: rt.configId,
@@ -142009,7 +142058,7 @@ async function handleInvoke(ctx, args2) {
142009
142058
  success: false,
142010
142059
  data: {
142011
142060
  error: `Tool "${toolName}" not found`,
142012
- hint: "Use one.search or one.list_tools to find available tool names",
142061
+ hint: "Use heku.search or heku.list_tools to find available tool names",
142013
142062
  available_count: available.length
142014
142063
  }
142015
142064
  };
@@ -142555,8 +142604,8 @@ async function run(args2) {
142555
142604
  }
142556
142605
  log.info("server", `Loading configs from: ${configDir}`);
142557
142606
  const handAuthored = loadConfigs(configDir).filter((c) => {
142558
- if (c.id === "one") {
142559
- log.warn("server", `mcp.one.json in ${configDir} is ignored \u2014 internal config is built-in`);
142607
+ if (c.id === "heku") {
142608
+ log.warn("server", `mcp.heku.json in ${configDir} is ignored \u2014 internal config is built-in`);
142560
142609
  return false;
142561
142610
  }
142562
142611
  return true;
@@ -142566,7 +142615,7 @@ async function run(args2) {
142566
142615
  ...handAuthored
142567
142616
  ];
142568
142617
  if (handAuthored.length === 0) {
142569
- log.info("server", `No user configs yet \u2014 drop mcp.*.json files in ${configDir} or call one.registry_install`);
142618
+ log.info("server", `No user configs yet \u2014 drop mcp.*.json files in ${configDir} or call heku.registry_install`);
142570
142619
  }
142571
142620
  for (const config2 of allConfigs) {
142572
142621
  if (config2.connector.type === "mcp") {
@@ -142699,13 +142748,13 @@ async function run(args2) {
142699
142748
  }
142700
142749
  if (systemConfig.self_config === false) {
142701
142750
  const before = allConfigs.length;
142702
- allConfigs.splice(0, allConfigs.length, ...allConfigs.filter((c) => c.id !== "one"));
142751
+ allConfigs.splice(0, allConfigs.length, ...allConfigs.filter((c) => c.id !== "heku"));
142703
142752
  if (allConfigs.length < before) {
142704
142753
  log.info("server", "self_config: false \u2014 heku self-management disabled");
142705
142754
  }
142706
142755
  } else if (systemConfig.self_config && typeof systemConfig.self_config === "object") {
142707
142756
  const sc = systemConfig.self_config;
142708
- const oneConfig = allConfigs.find((c) => c.id === "one");
142757
+ const oneConfig = allConfigs.find((c) => c.id === "heku");
142709
142758
  if (oneConfig) {
142710
142759
  if (sc.allow) {
142711
142760
  oneConfig.tools = oneConfig.tools.filter((t) => sc.allow.includes(t.name));
@@ -143494,7 +143543,7 @@ async function run5(args2) {
143494
143543
  } else {
143495
143544
  let candidates = [];
143496
143545
  if (fs18.existsSync(configDir)) {
143497
- candidates = fs18.readdirSync(configDir).filter((f) => f.startsWith("mcp.") && f.endsWith(".json") && f !== "mcp.one.json").map((f) => path20.join(configDir, f));
143546
+ candidates = fs18.readdirSync(configDir).filter((f) => f.startsWith("mcp.") && f.endsWith(".json") && f !== "mcp.heku.json").map((f) => path20.join(configDir, f));
143498
143547
  }
143499
143548
  if (candidates.length === 0) {
143500
143549
  console.error(red("\u2717") + ` No config file found. Try: ${bold("heku publish <name>")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidthoughtlabs/heku",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A single dynamic MCP server that turns JSON configs into working API tools",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",