@zackbart/connecta 0.10.0 → 0.10.2
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/AGENTS.md +113 -0
- package/CHANGELOG.md +83 -0
- package/README.md +62 -12
- package/bin/connecta.mjs +272 -0
- package/dist/access-tokens.d.ts +32 -0
- package/dist/access-tokens.d.ts.map +1 -0
- package/dist/access-tokens.js +225 -0
- package/dist/access-tokens.js.map +1 -0
- package/dist/catalog-service.d.ts +39 -1
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +133 -11
- package/dist/catalog-service.js.map +1 -1
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +113 -13
- package/dist/catalog.js.map +1 -1
- package/dist/execute.d.ts +45 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +265 -68
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +1 -5
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +1 -0
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +410 -12
- package/dist/meta-tools.js.map +1 -1
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/operator-ui/generated.d.ts.map +1 -1
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/generated.js.map +1 -1
- package/dist/operator-ui/model.d.ts +2 -0
- package/dist/operator-ui/model.d.ts.map +1 -1
- package/dist/operator-ui/model.js.map +1 -1
- package/dist/routes/access-tokens.d.ts +7 -0
- package/dist/routes/access-tokens.d.ts.map +1 -0
- package/dist/routes/access-tokens.js +84 -0
- package/dist/routes/access-tokens.js.map +1 -0
- package/dist/routes/shared.d.ts +3 -0
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/routes/ui.d.ts.map +1 -1
- package/dist/routes/ui.js +9 -1
- package/dist/routes/ui.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +1 -1
- package/dist/storage/file.d.ts.map +1 -1
- package/dist/storage/file.js +5 -0
- package/dist/storage/file.js.map +1 -1
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +8 -0
- package/dist/storage/memory.js.map +1 -1
- package/dist/tool-safety.d.ts +10 -0
- package/dist/tool-safety.d.ts.map +1 -0
- package/dist/tool-safety.js +12 -0
- package/dist/tool-safety.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +4 -4
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +44 -1
- package/dist/ui.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +7 -0
- package/documentation/auth.md +58 -0
- package/documentation/call-admission.md +7 -0
- package/documentation/code-first-exploration.md +292 -0
- package/documentation/code-mode.md +697 -0
- package/documentation/connector-guides.md +7 -0
- package/documentation/connectors.md +63 -0
- package/documentation/mcp-2026-07-28.md +46 -0
- package/documentation/meta-tools.md +167 -0
- package/documentation/operations.md +7 -0
- package/documentation/operator-ui.md +7 -0
- package/documentation/request-admission.md +7 -0
- package/documentation/storage-and-credentials.md +54 -0
- package/ethos.md +132 -0
- package/examples/node/README.md +53 -0
- package/examples/node/src/index.ts +73 -0
- package/examples/worker/README.md +160 -0
- package/examples/worker/src/cloudflare-kv.ts +43 -0
- package/examples/worker/src/d1-activity-row.ts +100 -0
- package/examples/worker/src/d1-activity.ts +144 -0
- package/examples/worker/src/index.ts +136 -0
- package/examples/worker/wrangler.jsonc +26 -0
- package/package.json +11 -1
- package/src/access-tokens.ts +289 -0
- package/src/catalog-service.ts +177 -15
- package/src/catalog.ts +143 -12
- package/src/execute.ts +372 -96
- package/src/index.ts +33 -1
- package/src/invocation.ts +1 -8
- package/src/meta-tools.ts +504 -11
- package/src/operator-ui/browser.css +63 -0
- package/src/operator-ui/browser.ts +288 -2
- package/src/operator-ui/generated.ts +2 -2
- package/src/operator-ui/model.ts +6 -0
- package/src/routes/access-tokens.ts +115 -0
- package/src/routes/shared.ts +3 -0
- package/src/routes/ui.ts +9 -0
- package/src/server.ts +5 -0
- package/src/skills.ts +1 -1
- package/src/storage/file.ts +5 -0
- package/src/storage/memory.ts +8 -0
- package/src/tool-safety.ts +15 -0
- package/src/types.ts +5 -0
- package/src/ui.ts +50 -1
- package/src/version.ts +1 -1
- package/templates/node/.env.example +5 -0
- package/templates/node/AGENTS.md +19 -0
- package/templates/node/README.md +33 -0
- package/templates/node/package.json +23 -0
- package/templates/node/src/index.ts +43 -0
- package/templates/node/tsconfig.json +12 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Working on connecta
|
|
2
|
+
|
|
3
|
+
This is the canonical instruction file for coding agents. `CLAUDE.md` is a
|
|
4
|
+
symlink to this file so every agent works from the same conventions.
|
|
5
|
+
|
|
6
|
+
A single MCP endpoint aggregating downstream connectors (remote MCP servers and
|
|
7
|
+
plain HTTP APIs) behind a small fixed set of meta-tools. A deployment with an
|
|
8
|
+
executor serves seven, `execute_code` among them, and agents reach connectors by
|
|
9
|
+
writing JavaScript against it; one without an executor serves the nine classic
|
|
10
|
+
tools. One fetch-native core, running on both Node and Cloudflare Workers.
|
|
11
|
+
|
|
12
|
+
- **[`ethos.md`](./ethos.md) is the constitution.** It states what connecta is
|
|
13
|
+
and isn't, and its decisions table carries a verdict for every shape already
|
|
14
|
+
considered — refused, removed, provisional, or gated. Check the table before
|
|
15
|
+
designing or building anything: a `refused` row is a "no" with the reason
|
|
16
|
+
attached, and a `removed` row (toolkits
|
|
17
|
+
[#178](https://github.com/zackbart/connecta/issues/178), proactive credential
|
|
18
|
+
liveness [#179](https://github.com/zackbart/connecta/issues/179)) records a
|
|
19
|
+
surface that no longer exists — do not reintroduce it without a new decision.
|
|
20
|
+
- There is no TODO.md — the roadmap lives in
|
|
21
|
+
[GitHub issues](https://github.com/zackbart/connecta/issues). When you find
|
|
22
|
+
TODO items (in code comments, docs, or conversation), don't accumulate them
|
|
23
|
+
in a file: turn each one into a GitHub issue that clearly defines what to do
|
|
24
|
+
— motivation, behavioral requirements, and acceptance criteria — without
|
|
25
|
+
prescribing the implementation.
|
|
26
|
+
|
|
27
|
+
## Verification
|
|
28
|
+
|
|
29
|
+
`npm run check` must pass before you claim anything is done — `check:docs` →
|
|
30
|
+
`check:operator-ui` → `check:lint` → `check:unused` → `typecheck` → `test`
|
|
31
|
+
(both vitest projects) → `build` → `check:examples`. It is also the `prepack`
|
|
32
|
+
hook. `npm run release:check` adds `check:security` and `check:package` and is
|
|
33
|
+
what CI runs on publish; use it when touching packaging, dependencies, or
|
|
34
|
+
exports.
|
|
35
|
+
|
|
36
|
+
## The map
|
|
37
|
+
|
|
38
|
+
- [`ethos.md`](./ethos.md) — what connecta is, what it refuses to be, the
|
|
39
|
+
decisions table, and the invariants every change must preserve. Check it
|
|
40
|
+
before building something new; "we already decided not to" is a real answer
|
|
41
|
+
there, and its removed/provisional verdicts override anything staler.
|
|
42
|
+
- [`documentation/`](./documentation/) — per-subsystem guides for agents
|
|
43
|
+
working on the repo. **Currently stubs**: the old manual (`docs/`) was
|
|
44
|
+
retired in the phase-1 docs restructure and each guide is being rewritten as
|
|
45
|
+
the ideas settle. Until a guide is filled in, the subsystem's prior manual
|
|
46
|
+
text lives in git history (`docs/<name>.md`) — consult it there when you need
|
|
47
|
+
the old rationale, but treat `ethos.md` as the authority where they disagree.
|
|
48
|
+
- [`README.md`](./README.md) — the human-facing overview.
|
|
49
|
+
- [`templates/node/`](./templates/node/) — the one standalone Node deployment
|
|
50
|
+
shape copied by `connecta init`. Keep it small and prescribed; examples may
|
|
51
|
+
demonstrate alternatives, but they must not become competing scaffolds.
|
|
52
|
+
|
|
53
|
+
**Read `ethos.md` and the subsystem's guide (or its git-history predecessor)
|
|
54
|
+
before changing a subsystem.**
|
|
55
|
+
|
|
56
|
+
## Deployment setup
|
|
57
|
+
|
|
58
|
+
`connecta init [directory]` is the golden path. It copies `templates/node/`,
|
|
59
|
+
pins the generated deployment to the CLI package's exact version, restores the
|
|
60
|
+
template `.gitignore`, and refuses to merge into an existing path.
|
|
61
|
+
`connecta doctor` verifies a running deployment's health, executor, and exact
|
|
62
|
+
seven-tool surface. Setup changes must keep the root README, template, Docker
|
|
63
|
+
example, and `scripts/check-package.mjs` smoke aligned; do not add a second
|
|
64
|
+
initializer or another “recommended” project shape.
|
|
65
|
+
|
|
66
|
+
## Where new code goes
|
|
67
|
+
|
|
68
|
+
Two boundaries CI enforces that are not obvious from reading a file:
|
|
69
|
+
|
|
70
|
+
- **Import-graph purity.** Nothing reachable from `src/index.ts` may import a
|
|
71
|
+
`node:` builtin — the core is Web-API only so it runs unchanged on Workers.
|
|
72
|
+
`src/node.ts`, `src/storage/file.ts`, and the QuickJS process-pool entry
|
|
73
|
+
(`src/executors/quickjs.ts` + child) are the Node-touching paths and must stay
|
|
74
|
+
unreachable from the root entry. `test/purity.test.ts` walks the import graph
|
|
75
|
+
and fails otherwise. Need a Node API? It goes behind an explicit Node-only
|
|
76
|
+
subpath (`/node` or `/quickjs`), never the root.
|
|
77
|
+
- **The published surface.** Platform-specific storage adapters live in
|
|
78
|
+
`examples/`, not the package. `@clerk/backend` and `quickjs-emscripten` are
|
|
79
|
+
optional peers behind the `./auth/clerk` and `./quickjs` subpaths and must
|
|
80
|
+
never become dependencies or install with core. Enforced by
|
|
81
|
+
`test/package-surface.test.ts` and `scripts/check-package.mjs`. Anything
|
|
82
|
+
heavyweight or platform-bound gets a subpath and an optional peer.
|
|
83
|
+
|
|
84
|
+
## Where new tests go
|
|
85
|
+
|
|
86
|
+
Suites live in `test/` and run as two vitest projects (`vitest.config.ts`).
|
|
87
|
+
Every `*.test.ts` belongs to exactly one explicit list: runtime-portable suites
|
|
88
|
+
in `WORKERS_SUITES`, Node-bound suites in `NODE_ONLY_SUITES` with a reason. The
|
|
89
|
+
`node` project runs both lists; the `workers` project re-runs the portable list
|
|
90
|
+
inside workerd. `test/suite-partition.test.ts` walks the directory and fails on
|
|
91
|
+
an unclassified, double-classified, stale, or reasonless entry. New behavior
|
|
92
|
+
also gets a row in the test map in
|
|
93
|
+
[`documentation/operations.md`](./documentation/operations.md) once that guide
|
|
94
|
+
is rewritten; until then the row waits with the guide.
|
|
95
|
+
|
|
96
|
+
## Conventions
|
|
97
|
+
|
|
98
|
+
- **Static analysis.** `npm run check:lint` runs Oxlint's correctness category
|
|
99
|
+
only; it does not enforce style. `npm run check:unused` runs Knip's
|
|
100
|
+
unused-export and dependency gate. Keep both clean, and prefer removing dead
|
|
101
|
+
declarations over suppressing a finding.
|
|
102
|
+
- **Style.** There is no formatter. Match the surrounding code. The docs voice
|
|
103
|
+
is precise, occasionally wry, and always explains *why* — don't flatten it
|
|
104
|
+
into boilerplate.
|
|
105
|
+
- **Commits.** Imperative summary naming the behavior change, with issue refs in
|
|
106
|
+
parens: `Normalize maxResultBytes at every intake point (#32) (#39)`.
|
|
107
|
+
- **CHANGELOG.** Each release opens with a narrative paragraph — what this
|
|
108
|
+
release is, what breaks, what a deployment can ignore — then
|
|
109
|
+
`### Added` / `### Changed` / `### Fixed`.
|
|
110
|
+
- **Releases.** `npm run release:check`, tag `v<version>` matching
|
|
111
|
+
`package.json` exactly (the publish workflow verifies this and fails
|
|
112
|
+
otherwise), and publishing fires on GitHub **Release publication**, not on the
|
|
113
|
+
tag push.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.10.2 — 2026-07-30
|
|
6
|
+
|
|
7
|
+
A prescribed deployment path for agents and operators. `connecta init` now
|
|
8
|
+
creates one small, reviewable Node project with exact dependencies and
|
|
9
|
+
auto-discoverable conventions; `connecta doctor` proves the running endpoint is
|
|
10
|
+
healthy, advertises the intended seven tools, and can actually execute a
|
|
11
|
+
harmless QuickJS program. The initializer never merges into an existing path,
|
|
12
|
+
and it stages the complete project before an atomic rename so an interrupted
|
|
13
|
+
setup leaves nothing half-created. **Existing deployments are unchanged.** The
|
|
14
|
+
new template refuses to start without an explicit bearer token, while the
|
|
15
|
+
repository Docker example now selects the same code-first surface by default.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **One command to create the prescribed deployment.**
|
|
20
|
+
`npx @zackbart/connecta init <directory>` writes only the deployment config,
|
|
21
|
+
exact package manifest, TypeScript config, environment example, ignore rules,
|
|
22
|
+
README, and canonical `AGENTS.md` with `CLAUDE.md` pointing to it. The command
|
|
23
|
+
refuses every existing destination and pins the generated project to the
|
|
24
|
+
initializer's exact Connecta version.
|
|
25
|
+
- **A live deployment doctor.** `CONNECTA_TOKEN=… connecta doctor` checks
|
|
26
|
+
`/health`, requires the exact seven-tool code-first surface, and runs
|
|
27
|
+
`async () => 42` through `execute_code`. Requests are bounded to ten seconds,
|
|
28
|
+
bearer tokens are accepted only from the environment, and remote plaintext
|
|
29
|
+
HTTP is refused.
|
|
30
|
+
- **Artifact-level setup qualification.** The package smoke now installs the
|
|
31
|
+
packed tarball, initializes a deployment, checks overwrite refusal, installs
|
|
32
|
+
and typechecks the generated project, verifies tokenless startup fails,
|
|
33
|
+
starts it with auth, and runs the live doctor through QuickJS.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **Agent instructions have one source of truth.** `AGENTS.md` is canonical in
|
|
38
|
+
the package repository and generated deployments; `CLAUDE.md` is a symlink
|
|
39
|
+
where the filesystem supports it.
|
|
40
|
+
- **The setup material ships with the package.** The standalone template,
|
|
41
|
+
agent-facing documentation, ethos, and usable Node and Worker examples are
|
|
42
|
+
available beside the installed package. Docker remains explicitly
|
|
43
|
+
repository-only because its reproducible build consumes repository inputs.
|
|
44
|
+
- **The Docker example is code-first by default.** Its entrypoint configures the
|
|
45
|
+
bounded QuickJS executor instead of leaving operators to infer and add it.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **The prescribed Node deployment no longer has a known fallback secret.**
|
|
50
|
+
Both the generated template and repository example refuse startup when
|
|
51
|
+
`CONNECTA_TOKEN` is absent.
|
|
52
|
+
- **Failed initialization no longer strands a partial destination.** Work is
|
|
53
|
+
assembled in a uniquely named sibling directory, cleaned on failure, and
|
|
54
|
+
renamed into place only when complete.
|
|
55
|
+
|
|
56
|
+
## 0.10.1 — 2026-07-30
|
|
57
|
+
|
|
58
|
+
An inbound-auth escape hatch for MCP clients whose OAuth implementations do not
|
|
59
|
+
interoperate cleanly with a Connecta deployment. Eligible Clerk operators can
|
|
60
|
+
now issue named, revocable Bearer tokens from `/tokens`; each call is attributed
|
|
61
|
+
to the token's immutable identity while activity history resolves the current
|
|
62
|
+
friendly name. The secret is shown once, only a SHA-256 digest is stored, and a
|
|
63
|
+
token can authenticate MCP without gaining operator privileges. **Deployments
|
|
64
|
+
that do not set `accessTokens: {}` are unchanged.** Enabling it requires a
|
|
65
|
+
storage adapter with `list(prefix)` and a Clerk auth provider so token lifecycle
|
|
66
|
+
operations remain behind the human operator boundary.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **Managed MCP access tokens.** `accessTokens: {}` adds a Clerk-only operator
|
|
71
|
+
ledger for creating, naming, renaming, and revoking `cta_…` Bearer tokens.
|
|
72
|
+
`maxActive` defaults to 100 and can be configured from 1 through 1,000.
|
|
73
|
+
- **Token-attributed activity.** Calls authenticated by a managed token record
|
|
74
|
+
its immutable token ID and resolve the current friendly name when an eligible
|
|
75
|
+
operator reads activity. Revoked metadata remains as a tombstone so historical
|
|
76
|
+
attribution survives rotation.
|
|
77
|
+
- **Enumerable storage.** `KVStorage.list(prefix)` is available for durable
|
|
78
|
+
metadata ledgers, with implementations in the built-in memory and file
|
|
79
|
+
adapters and the Cloudflare Workers KV example.
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
83
|
+
- **One-time secrets leave no reusable operator-UI state.** The plaintext token
|
|
84
|
+
disappears when it is dismissed, when the operator navigates away or signs
|
|
85
|
+
out, and before the document enters the browser back-forward cache. The create
|
|
86
|
+
form stays unavailable while a newly issued secret is waiting to be stored.
|
|
87
|
+
|
|
5
88
|
## 0.10.0 — 2026-07-30
|
|
6
89
|
|
|
7
90
|
**Code-first is what a model sees.** A deployment with an executor now serves
|
package/README.md
CHANGED
|
@@ -13,13 +13,17 @@ among them.
|
|
|
13
13
|
|
|
14
14
|
```mermaid
|
|
15
15
|
flowchart LR
|
|
16
|
-
Agent["AI agent"]
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Agent["AI agent"]
|
|
17
|
+
Integrations["The integrations you chose"]
|
|
18
|
+
|
|
19
|
+
subgraph Connecta["Connecta: one MCP endpoint; credentials stay here"]
|
|
19
20
|
Sandbox["execute_code<br/>server-side sandbox"]
|
|
20
21
|
Explicit["Explicit tools<br/>destructive calls, search, auth"]
|
|
21
22
|
end
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
Agent -->|"writes a program"| Sandbox
|
|
25
|
+
Agent -->|"one deliberate call"| Explicit
|
|
26
|
+
Sandbox --> Integrations
|
|
23
27
|
Explicit --> Integrations
|
|
24
28
|
```
|
|
25
29
|
|
|
@@ -31,20 +35,66 @@ flowchart LR
|
|
|
31
35
|
to be loaded up front.
|
|
32
36
|
- **Safer access.** Credentials stay server-side — the program never sees them
|
|
33
37
|
— and consequential actions remain explicit and individual.
|
|
38
|
+
- **Named client access.** A Clerk operator can issue and revoke one-time,
|
|
39
|
+
hashed Bearer tokens for MCP clients that support header authentication.
|
|
34
40
|
- **Your deployment.** Connecta runs on Node, Docker, or Cloudflare Workers,
|
|
35
41
|
with configuration you can review and version.
|
|
36
42
|
|
|
37
43
|
## Start here
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
Create the prescribed Node deployment:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npx @zackbart/connecta init my-connecta
|
|
49
|
+
cd my-connecta
|
|
50
|
+
npm install
|
|
51
|
+
CONNECTA_TOKEN=dev-token npm start
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Point an MCP client at `http://localhost:8787/mcp` with
|
|
55
|
+
`Authorization: Bearer dev-token`. The generated project is deliberately small:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
my-connecta/
|
|
59
|
+
├── src/index.ts # connectors, auth, storage, public URL
|
|
60
|
+
├── package.json # exact Connecta and QuickJS versions
|
|
61
|
+
├── tsconfig.json
|
|
62
|
+
├── .env.example
|
|
63
|
+
├── .gitignore
|
|
64
|
+
├── AGENTS.md
|
|
65
|
+
├── CLAUDE.md -> AGENTS.md
|
|
66
|
+
└── README.md
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For an agent setting this up, the contract is:
|
|
70
|
+
|
|
71
|
+
1. Edit `src/index.ts`; do not copy Connecta internals into the deployment.
|
|
72
|
+
2. Keep `executor: quickJsExecutor()` for the prescribed seven-tool surface.
|
|
73
|
+
3. Keep secrets in environment variables or a secret store, never source.
|
|
74
|
+
4. Add code only for deliberate `api()` connectors.
|
|
75
|
+
5. Run `npm run typecheck`, start the server, and run
|
|
76
|
+
`CONNECTA_TOKEN=... npm run doctor`. Doctor checks health, the executor, and
|
|
77
|
+
the exact seven-tool model-facing surface, then executes a harmless sandbox
|
|
78
|
+
program. The bearer stays in the environment rather than command history.
|
|
79
|
+
|
|
80
|
+
The template refuses to merge into an existing directory, so initialization
|
|
81
|
+
cannot overwrite another project. Its generated programs have no filesystem,
|
|
82
|
+
environment, arbitrary network, imports, or timers; only explicitly read-only
|
|
83
|
+
connector tools are reachable. Unannotated or write-capable calls stay
|
|
84
|
+
individual and cross `call_destructive_tool`, where the MCP host can ask the
|
|
85
|
+
operator for approval.
|
|
86
|
+
|
|
87
|
+
Other supported deployment shapes:
|
|
88
|
+
|
|
89
|
+
- [Prescribed Node template](./templates/node/)
|
|
90
|
+
- [Node repository example](./examples/node/)
|
|
91
|
+
- [Code-first Docker deployment (repository-only)](https://github.com/zackbart/connecta/tree/main/examples/docker)
|
|
92
|
+
- [Cloudflare Worker deployment](./examples/worker/)
|
|
93
|
+
- [Subsystem documentation](./documentation/)
|
|
43
94
|
|
|
44
|
-
Configuring a sandbox — a Dynamic Worker on Cloudflare
|
|
45
|
-
what selects the code-first surface
|
|
46
|
-
|
|
47
|
-
which stays supported.
|
|
95
|
+
Configuring a sandbox — QuickJS on Node or a Dynamic Worker on Cloudflare — is
|
|
96
|
+
what selects the code-first surface and is the assumed posture. A deployment
|
|
97
|
+
without one keeps the earlier nine-tool compatibility interface.
|
|
48
98
|
|
|
49
99
|
## Project status
|
|
50
100
|
|
package/bin/connecta.mjs
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
copyFile,
|
|
5
|
+
cp,
|
|
6
|
+
lstat,
|
|
7
|
+
mkdtemp,
|
|
8
|
+
readFile,
|
|
9
|
+
rename,
|
|
10
|
+
rm,
|
|
11
|
+
symlink,
|
|
12
|
+
writeFile,
|
|
13
|
+
} from "node:fs/promises";
|
|
14
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
18
|
+
const [command, ...args] = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
function shellCd(path) {
|
|
21
|
+
if (process.platform === "win32") {
|
|
22
|
+
return `cd /d "${path.replaceAll('"', '""')}"`;
|
|
23
|
+
}
|
|
24
|
+
return `cd '${path.replaceAll("'", "'\\''")}'`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function usage() {
|
|
28
|
+
console.log(`Usage:
|
|
29
|
+
connecta init [directory]
|
|
30
|
+
CONNECTA_TOKEN=<bearer> connecta doctor [--url http://localhost:8787]`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function init() {
|
|
34
|
+
if (args.length > 1) {
|
|
35
|
+
usage();
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const destination = args[0] ?? "connecta-deployment";
|
|
40
|
+
const target = resolve(process.cwd(), destination);
|
|
41
|
+
const parent = dirname(target);
|
|
42
|
+
try {
|
|
43
|
+
await lstat(target);
|
|
44
|
+
throw new Error(`Refusing to overwrite existing path: ${target}`);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const code =
|
|
47
|
+
error && typeof error === "object" && "code" in error
|
|
48
|
+
? String(error.code)
|
|
49
|
+
: "";
|
|
50
|
+
if (code !== "ENOENT") throw error;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Build beside the destination, then rename once complete. A failed copy or
|
|
54
|
+
// rewrite leaves no partial destination that blocks a clean retry.
|
|
55
|
+
let stage = await mkdtemp(
|
|
56
|
+
join(parent, `.${basename(target)}.connecta-init-`),
|
|
57
|
+
);
|
|
58
|
+
try {
|
|
59
|
+
await cp(join(packageRoot, "templates", "node"), stage, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// npm excludes .gitignore files and symlinks from packed dependencies.
|
|
64
|
+
// Restore both conventions explicitly in the generated project.
|
|
65
|
+
await writeFile(
|
|
66
|
+
join(stage, ".gitignore"),
|
|
67
|
+
".connecta-state.json\n.env\nnode_modules/\n",
|
|
68
|
+
);
|
|
69
|
+
await rm(join(stage, "CLAUDE.md"), { force: true });
|
|
70
|
+
try {
|
|
71
|
+
await symlink("AGENTS.md", join(stage, "CLAUDE.md"));
|
|
72
|
+
} catch {
|
|
73
|
+
// Some Windows environments disallow symlink creation. A materialized
|
|
74
|
+
// fallback preserves discovery even though AGENTS.md remains canonical.
|
|
75
|
+
await copyFile(join(stage, "AGENTS.md"), join(stage, "CLAUDE.md"));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rootPackage = JSON.parse(
|
|
79
|
+
await readFile(join(packageRoot, "package.json"), "utf8"),
|
|
80
|
+
);
|
|
81
|
+
const deploymentPath = join(stage, "package.json");
|
|
82
|
+
const deploymentPackage = JSON.parse(
|
|
83
|
+
await readFile(deploymentPath, "utf8"),
|
|
84
|
+
);
|
|
85
|
+
deploymentPackage.dependencies["@zackbart/connecta"] = rootPackage.version;
|
|
86
|
+
await writeFile(
|
|
87
|
+
deploymentPath,
|
|
88
|
+
JSON.stringify(deploymentPackage, null, 2) + "\n",
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
await rename(stage, target);
|
|
92
|
+
stage = "";
|
|
93
|
+
} finally {
|
|
94
|
+
if (stage) await rm(stage, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`Created ${target}`);
|
|
98
|
+
console.log("Next:");
|
|
99
|
+
console.log(` ${shellCd(target)}`);
|
|
100
|
+
console.log(" npm install");
|
|
101
|
+
console.log(" CONNECTA_TOKEN=dev-token npm start");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function option(name, fallback) {
|
|
105
|
+
const index = args.indexOf(name);
|
|
106
|
+
if (index < 0) return fallback;
|
|
107
|
+
const value = args[index + 1];
|
|
108
|
+
if (!value || value.startsWith("--")) {
|
|
109
|
+
throw new Error(`${name} requires a value`);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function jsonResponse(response) {
|
|
115
|
+
const text = await response.text();
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
throw new Error(`HTTP ${response.status}: ${text.slice(0, 500)}`);
|
|
118
|
+
}
|
|
119
|
+
if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
120
|
+
const line = text
|
|
121
|
+
.split("\n")
|
|
122
|
+
.filter((candidate) => candidate.startsWith("data:"))
|
|
123
|
+
.pop();
|
|
124
|
+
if (!line) throw new Error("MCP response contained no SSE data");
|
|
125
|
+
return JSON.parse(line.slice("data:".length).trim());
|
|
126
|
+
}
|
|
127
|
+
return JSON.parse(text);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const DOCTOR_TIMEOUT_MS = 10_000;
|
|
131
|
+
|
|
132
|
+
async function doctorFetch(url, init = {}) {
|
|
133
|
+
try {
|
|
134
|
+
return await fetch(url, {
|
|
135
|
+
...init,
|
|
136
|
+
signal: AbortSignal.timeout(DOCTOR_TIMEOUT_MS),
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (
|
|
140
|
+
error instanceof Error &&
|
|
141
|
+
(error.name === "TimeoutError" || error.name === "AbortError")
|
|
142
|
+
) {
|
|
143
|
+
throw new Error(`Timed out after ${DOCTOR_TIMEOUT_MS}ms contacting ${url}`);
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function doctor() {
|
|
150
|
+
const known = new Set(["--url"]);
|
|
151
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
152
|
+
if (!known.has(args[index]) || !args[index + 1]) {
|
|
153
|
+
usage();
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const requestedUrl = option("--url", "http://localhost:8787");
|
|
159
|
+
const parsedUrl = new URL(requestedUrl);
|
|
160
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
161
|
+
throw new Error("Doctor URL must not contain credentials.");
|
|
162
|
+
}
|
|
163
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
164
|
+
throw new Error("Doctor URL must use http or https.");
|
|
165
|
+
}
|
|
166
|
+
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
167
|
+
if (
|
|
168
|
+
parsedUrl.protocol === "http:" &&
|
|
169
|
+
!loopbackHosts.has(parsedUrl.hostname)
|
|
170
|
+
) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Refusing to send a bearer token over remote plaintext HTTP. Use HTTPS.",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const baseUrl = requestedUrl.replace(/\/+$/, "");
|
|
176
|
+
const token = process.env.CONNECTA_TOKEN;
|
|
177
|
+
if (!token) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"Set CONNECTA_TOKEN so doctor can inspect the MCP surface.",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const health = await jsonResponse(
|
|
184
|
+
await doctorFetch(`${baseUrl}/health`),
|
|
185
|
+
);
|
|
186
|
+
if (health.status !== "ok") {
|
|
187
|
+
throw new Error(`Unexpected health status: ${String(health.status)}`);
|
|
188
|
+
}
|
|
189
|
+
if (health.admission?.code === null) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"The server has no executor and is serving classic compatibility. " +
|
|
192
|
+
"Keep executor: quickJsExecutor() for the prescribed surface.",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let requestId = 0;
|
|
197
|
+
const mcp = async (method, params) =>
|
|
198
|
+
jsonResponse(
|
|
199
|
+
await doctorFetch(`${baseUrl}/mcp`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: {
|
|
202
|
+
Authorization: `Bearer ${token}`,
|
|
203
|
+
"Content-Type": "application/json",
|
|
204
|
+
Accept: "application/json, text/event-stream",
|
|
205
|
+
},
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
jsonrpc: "2.0",
|
|
208
|
+
id: ++requestId,
|
|
209
|
+
method,
|
|
210
|
+
params,
|
|
211
|
+
}),
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const listed = await mcp("tools/list", {});
|
|
216
|
+
if (listed.error) {
|
|
217
|
+
throw new Error(`tools/list failed: ${JSON.stringify(listed.error)}`);
|
|
218
|
+
}
|
|
219
|
+
const actual = listed.result?.tools
|
|
220
|
+
?.map((tool) => tool.name)
|
|
221
|
+
.sort();
|
|
222
|
+
const expected = [
|
|
223
|
+
"authorize_connector",
|
|
224
|
+
"call_destructive_tool",
|
|
225
|
+
"call_tool",
|
|
226
|
+
"execute_code",
|
|
227
|
+
"get_result",
|
|
228
|
+
"search_tools",
|
|
229
|
+
"skills",
|
|
230
|
+
];
|
|
231
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
|
|
234
|
+
`${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const executed = await mcp("tools/call", {
|
|
239
|
+
name: "execute_code",
|
|
240
|
+
arguments: { code: "async () => 42" },
|
|
241
|
+
});
|
|
242
|
+
if (executed.error) {
|
|
243
|
+
throw new Error(`execute_code failed: ${JSON.stringify(executed.error)}`);
|
|
244
|
+
}
|
|
245
|
+
const executionResult =
|
|
246
|
+
executed.result?.structuredContent ??
|
|
247
|
+
JSON.parse(executed.result?.content?.[0]?.text ?? "null");
|
|
248
|
+
if (executed.result?.isError || executionResult?.result !== 42) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`QuickJS execution check failed: ${JSON.stringify(executed.result)}`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(
|
|
255
|
+
`Connecta doctor passed: ${health.connectors} connector(s), ` +
|
|
256
|
+
"QuickJS executed, prescribed seven-tool surface.",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
if (command === "init") await init();
|
|
262
|
+
else if (command === "doctor") await doctor();
|
|
263
|
+
else {
|
|
264
|
+
usage();
|
|
265
|
+
process.exitCode = command === "--help" || command === "-h" ? 0 : 1;
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error(
|
|
269
|
+
`[connecta] ${error instanceof Error ? error.message : String(error)}`,
|
|
270
|
+
);
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { InboundAuth, KVStorage } from "./types.js";
|
|
2
|
+
export interface AccessTokenMetadata {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
tokenPrefix: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
revokedAt?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CreatedAccessToken {
|
|
10
|
+
token: string;
|
|
11
|
+
accessToken: AccessTokenMetadata;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Deployment-scoped personal access tokens. Secret material is never
|
|
15
|
+
* recoverable: authentication indexes a SHA-256 digest of a random 256-bit
|
|
16
|
+
* token, while separately enumerable metadata powers operator management.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AccessTokenManager {
|
|
19
|
+
private readonly storage;
|
|
20
|
+
readonly auth: InboundAuth;
|
|
21
|
+
private readonly maxActive;
|
|
22
|
+
constructor(storage: KVStorage, options?: {
|
|
23
|
+
maxActive?: number;
|
|
24
|
+
});
|
|
25
|
+
private read;
|
|
26
|
+
list(): Promise<AccessTokenMetadata[]>;
|
|
27
|
+
create(name: unknown, createdBy: string): Promise<CreatedAccessToken>;
|
|
28
|
+
rename(id: string, name: unknown): Promise<AccessTokenMetadata | null>;
|
|
29
|
+
revoke(id: string, revokedBy: string): Promise<AccessTokenMetadata | null>;
|
|
30
|
+
private authorize;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=access-tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-tokens.d.ts","sourceRoot":"","sources":["../src/access-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA6BrE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAyGD;;;;GAIG;AACH,qBAAa,kBAAkB;IAK3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGhB,OAAO,EAAE,SAAS,EACnC,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;YAgCxB,IAAI;IAKZ,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IActC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAoCrE,MAAM,CACV,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAQhC,MAAM,CACV,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAexB,SAAS;CAgBxB"}
|