@novalinkai/mcp 0.1.1 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +212 -37
  2. package/dist/cli.js +48 -18
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,12 +1,64 @@
1
- # @novalinkai/mcp
1
+ # Novalink MCP
2
2
 
3
- Connect Claude Code, Cursor, Antigravity, VS Code and Windsurf to [Novalink](https://novalink.live). Your coding agent can then plan AI workflows, build and test them, publish them, and wire them into your codebase.
3
+ [![npm version](https://img.shields.io/npm/v/@novalinkai/mcp.svg)](https://www.npmjs.com/package/@novalinkai/mcp)
4
+ [![CI](https://github.com/Novalink-AI/mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Novalink-AI/mcp/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ Connect coding agents to [Novalink](https://novalink.live) so they can design, build, test and ship AI workflows, then integrate them into your codebase.
8
+
9
+ This repository contains:
10
+
11
+ - **`@novalinkai/mcp`**: a command-line tool that configures Claude Code, Cursor, Antigravity, VS Code and Windsurf to use the hosted Novalink MCP server. It also includes a local stdio bridge for clients that cannot authenticate to remote servers.
12
+ - **The `novalink-workflows` skill**: guidance that teaches agents the Novalink workflow model, so the workflows they produce validate the first time.
13
+ - **A Claude Code plugin** that bundles the server configuration and the skill.
14
+
15
+ ## Contents
16
+
17
+ - [What agents can do](#what-agents-can-do)
18
+ - [Requirements](#requirements)
19
+ - [Quick start](#quick-start)
20
+ - [Installation options](#installation-options)
21
+ - [How it works](#how-it-works)
22
+ - [Tools](#tools)
23
+ - [Permissions](#permissions)
24
+ - [CLI reference](#cli-reference)
25
+ - [Security](#security)
26
+ - [Troubleshooting](#troubleshooting)
27
+ - [Development](#development)
28
+ - [License](#license)
29
+
30
+ ## What agents can do
31
+
32
+ With Novalink connected, you can ask your agent for an automation in plain language, for example "when a support email arrives, classify it and open a Linear issue". The agent then:
33
+
34
+ 1. **Discovers** the available node types and the apps, credentials, APIs and MCP servers connected to your account.
35
+ 2. **Plans** a workflow graph from your description and shows you the steps before saving anything.
36
+ 3. **Builds** the workflow as a draft and resolves validation issues.
37
+ 4. **Tests** the draft with realistic input and repairs any failing nodes.
38
+ 5. **Publishes** a version once you approve it.
39
+ 6. **Integrates** it: creates an API key limited to that workflow, stores it in your environment file, and adds the API call to your code.
40
+
41
+ Every workflow created this way is a standard Novalink workflow. You can open it on the canvas, inspect its runs, and edit it like any other.
42
+
43
+ ## Requirements
44
+
45
+ - A [Novalink](https://novalink.live) account.
46
+ - Node.js 20 or later, for the installer and the stdio bridge.
47
+ - One or more supported clients: Claude Code, Cursor, Antigravity, VS Code (agent mode) or Windsurf.
48
+
49
+ ## Quick start
50
+
51
+ Run the installer from the root of your project:
4
52
 
5
53
  ```sh
6
54
  npx @novalinkai/mcp install
7
55
  ```
8
56
 
9
- Any package runner works:
57
+ The installer detects your editors, adds the Novalink server to each one, and installs the `/novalink-workflows` guide. It writes configuration only, never credentials, so the generated files are safe to commit.
58
+
59
+ The first time your agent calls Novalink, a browser window opens. Sign in, review the permissions the agent is requesting, and approve. Then ask your agent to build a workflow, or invoke `/novalink-workflows` directly.
60
+
61
+ The installer runs with any package runner:
10
62
 
11
63
  ```sh
12
64
  pnpm dlx @novalinkai/mcp install
@@ -14,78 +66,201 @@ yarn dlx @novalinkai/mcp install
14
66
  bunx @novalinkai/mcp install
15
67
  ```
16
68
 
17
- The installer finds your editors, adds the Novalink MCP server (`https://api.novalink.live/mcp`) to each one, and adds the `/novalink-workflows` guide. The first time the agent uses Novalink, your browser opens so you can sign in and choose what it may do. The installer never writes secrets.
69
+ ## Installation options
18
70
 
19
- ### Claude Code plugin
20
-
21
- In Claude Code you can install Novalink as a plugin instead. It bundles the server and the skill, and updates with the plugin:
71
+ ### Choose editors and scope
22
72
 
23
- ```
24
- /plugin marketplace add Novalink-AI/mcp
25
- /plugin install novalink@novalink
73
+ ```sh
74
+ npx @novalinkai/mcp install --client claude-code,cursor # specific editors
75
+ npx @novalinkai/mcp install --scope user # all projects, not only this repository
76
+ npx @novalinkai/mcp install --dry-run # preview changes without writing
77
+ npx @novalinkai/mcp uninstall # remove the configuration
26
78
  ```
27
79
 
28
- ## What gets written
80
+ The installer writes to these locations:
29
81
 
30
- | Editor | Server config | Guide |
82
+ | Client | Server configuration | Guide |
31
83
  |---|---|---|
32
- | Claude Code | `.mcp.json`, or `claude mcp add` for `--scope user` | `.claude/skills/novalink-workflows/SKILL.md` |
84
+ | Claude Code | `.mcp.json`, or `claude mcp add` with `--scope user` | `.claude/skills/novalink-workflows/SKILL.md` |
33
85
  | Cursor | `.cursor/mcp.json` | `.cursor/commands/` and `.cursor/rules/` |
34
86
  | Antigravity | `~/.gemini/antigravity/mcp_config.json` | `.agent/rules/` and `.agent/workflows/` |
35
87
  | VS Code | `.vscode/mcp.json` | `.github/prompts/novalink-workflows.prompt.md` |
36
88
  | Windsurf | `~/.codeium/windsurf/mcp_config.json` | `.windsurf/rules/` |
37
89
 
38
- Existing servers in these files are kept. Files with comments are left alone, and the installer prints the entry for you to add by hand.
90
+ Existing entries in these files are preserved. If a file contains comments and cannot be parsed as plain JSON, the installer leaves it unchanged and prints the entry to add manually.
91
+
92
+ ### Claude Code plugin
39
93
 
40
- ## Options
94
+ In Claude Code, you can install Novalink as a plugin. The plugin bundles the server configuration and the skill, and updates through the plugin system:
41
95
 
42
96
  ```
43
- npx @novalinkai/mcp install --client claude-code,cursor # choose editors
44
- npx @novalinkai/mcp install --scope user # every project, not just this repo
45
- npx @novalinkai/mcp install --bridge # connect through the local stdio bridge
46
- npx @novalinkai/mcp install --dry-run # show changes only
47
- npx @novalinkai/mcp uninstall # remove them again
97
+ /plugin marketplace add Novalink-AI/mcp
98
+ /plugin install novalink@novalink
48
99
  ```
49
100
 
50
- ## The stdio bridge
101
+ ### Manual configuration
51
102
 
52
- Some editors cannot sign in to a remote MCP server themselves. For those, `npx @novalinkai/mcp serve` runs locally over stdio and relays to Novalink. It runs the OAuth sign-in in your browser and keeps tokens in `~/.config/novalink/credentials.json`, which only you can read.
103
+ The server endpoint is `https://api.novalink.live/mcp`, using the Streamable HTTP transport with OAuth 2.1.
104
+
105
+ Claude Code:
53
106
 
54
107
  ```sh
55
- npx @novalinkai/mcp login # sign in ahead of time
56
- npx @novalinkai/mcp status
57
- npx @novalinkai/mcp logout # revoke and forget the tokens
108
+ claude mcp add --transport http --scope user novalink https://api.novalink.live/mcp
58
109
  ```
59
110
 
60
- Set `NOVALINK_MCP_URL` (or `NOVALINK_API_URL`) to point at another deployment, such as `http://localhost:8000/mcp` in development.
111
+ Cursor (`.cursor/mcp.json`):
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "novalink": { "url": "https://api.novalink.live/mcp" }
117
+ }
118
+ }
119
+ ```
120
+
121
+ VS Code (`.vscode/mcp.json`):
122
+
123
+ ```json
124
+ {
125
+ "servers": {
126
+ "novalink": { "type": "http", "url": "https://api.novalink.live/mcp" }
127
+ }
128
+ }
129
+ ```
130
+
131
+ Clients without remote OAuth support can use the stdio bridge:
132
+
133
+ ```json
134
+ {
135
+ "mcpServers": {
136
+ "novalink": { "command": "npx", "args": ["-y", "@novalinkai/mcp@latest", "serve"] }
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## How it works
142
+
143
+ ```
144
+ Coding agent --- MCP over HTTPS ---------------------> api.novalink.live/mcp ---> your Novalink account
145
+ | ^
146
+ +--- stdio ---> novalink-mcp serve (local bridge) -------+
147
+ ```
148
+
149
+ - **Hosted server.** Novalink runs the MCP server. Your agent connects over HTTPS, and nothing runs on your machine besides your editor, unless you use the bridge.
150
+ - **Authentication.** The server is an OAuth 2.1 protected resource (RFC 9728).
151
+ - Clients register dynamically (RFC 7591) and sign in with the authorization code flow and PKCE.
152
+ - Access tokens expire after one hour and refresh automatically.
153
+ - Refresh tokens rotate on every use. Reusing an old refresh token revokes the connection.
154
+ - **Stdio bridge.** `novalink-mcp serve` reads JSON-RPC messages on stdin and relays them to the hosted server. It runs the OAuth flow itself through a loopback redirect and stores tokens locally.
155
+ - **Guide.** The installer puts the `novalink-workflows` skill into each editor in that editor's native format. The server also exposes it as the `design_workflow` prompt for any MCP client.
156
+
157
+ ## Tools
158
+
159
+ The agent sees only the tools allowed by the permissions you grant. Publishing, activating versions and creating API keys are marked as consequential, so clients ask for confirmation before calling them.
160
+
161
+ | Tool | Permission | Description |
162
+ |---|---|---|
163
+ | `list_node_types` | `workflows:read` | Node types available to workflow graphs |
164
+ | `get_node_type` | `workflows:read` | Ports and configuration schema for one node type |
165
+ | `list_integrations` | `workflows:read` | Connected apps and actions, credential names, custom APIs and MCP servers |
166
+ | `list_workflows` | `workflows:read` | Workflows with publish state and latest run |
167
+ | `get_workflow` | `workflows:read` | Draft graph, published versions and the active version |
168
+ | `validate_workflow` | `workflows:read` | The checks that publishing runs |
169
+ | `get_run` | `workflows:read` | Run status, output, and failing nodes |
170
+ | `get_integration_snippet` | `workflows:read` | TypeScript, JavaScript, Python or curl code for the Workflow API |
171
+ | `plan_workflow` | `workflows:write` | Drafts a graph and a readable plan without saving |
172
+ | `create_workflow` | `workflows:write` | Saves a new draft and returns validation issues |
173
+ | `update_workflow` | `workflows:write` | Replaces a draft graph and returns validation issues |
174
+ | `publish_workflow` | `workflows:publish` | Publishes the draft as a new version, optionally activating it |
175
+ | `activate_version` | `workflows:publish` | Selects the version the API runs |
176
+ | `run_workflow` | `runs:write` | Runs the draft or published version and returns the result |
177
+ | `create_api_key` | `api_keys:write` | Creates an API key limited to specific workflows |
61
178
 
62
179
  ## Permissions
63
180
 
64
- At sign-in you choose what the agent may do:
181
+ You choose the permissions on the consent screen when the agent first connects.
65
182
 
66
- - **workflows:read**: see workflows, node types, connections and credential names.
67
- - **workflows:write**: plan, create and edit drafts.
68
- - **workflows:publish**: publish versions and choose the active one.
69
- - **runs:write**: run workflows.
70
- - **api_keys:write**: create API keys limited to chosen workflows.
183
+ | Scope | Allows |
184
+ |---|---|
185
+ | `workflows:read` | Reading workflows, node types, integrations and credential names. Always granted. |
186
+ | `workflows:write` | Planning, creating and editing draft workflows. |
187
+ | `workflows:publish` | Publishing versions and changing the active version. |
188
+ | `runs:write` | Running workflows and reading their results. |
189
+ | `api_keys:write` | Creating API keys restricted to chosen workflows. |
71
190
 
72
- To disconnect an agent at any time, go to **API keys > Connected agents** in Novalink.
191
+ To review or revoke connected agents, open **API keys > Connected agents** in Novalink. Revocation takes effect immediately.
73
192
 
74
- Docs: https://novalink.live/developers/mcp
193
+ ## CLI reference
194
+
195
+ ```
196
+ novalink-mcp <command> [options]
197
+ ```
198
+
199
+ | Command | Description |
200
+ |---|---|
201
+ | `install` | Configure detected or selected editors (the default when run in a terminal) |
202
+ | `uninstall` | Remove the configuration and guides |
203
+ | `serve` | Run the stdio bridge (the default when started by an editor) |
204
+ | `login` | Sign in for the stdio bridge ahead of time |
205
+ | `logout` | Revoke the bridge's tokens and delete them locally |
206
+ | `status` | Show the bridge's sign-in state |
207
+ | `skill` | Print the workflow guide |
208
+
209
+ | Option | Description |
210
+ |---|---|
211
+ | `--client <ids>` | Comma-separated: `claude-code`, `cursor`, `antigravity`, `vscode`, `windsurf` |
212
+ | `--scope <scope>` | `project` (default) or `user` |
213
+ | `--url <url>` | MCP server URL |
214
+ | `--bridge` | Configure editors to use the stdio bridge instead of HTTP |
215
+ | `--no-skill` | Skip installing the guide |
216
+ | `--dry-run` | Show changes without writing files |
217
+ | `--yes` | Use detected editors without prompting |
218
+
219
+ | Environment variable | Description |
220
+ |---|---|
221
+ | `NOVALINK_MCP_URL` | MCP server URL, for example `http://localhost:8000/mcp` |
222
+ | `NOVALINK_API_URL` | API base URL; `/mcp` is appended |
223
+ | `NOVALINK_CONFIG_DIR` | Directory for the bridge's credentials file |
224
+
225
+ ## Security
226
+
227
+ - The installer writes server URLs and guides only. It never writes tokens or API keys.
228
+ - The bridge stores tokens in `~/.config/novalink/credentials.json`, or `%APPDATA%\novalink` on Windows, readable only by your user.
229
+ - On the server, tokens are stored as SHA-256 hashes, and you can revoke any grant from your account.
230
+ - Agents are instructed to keep API keys in untracked environment files, out of source control.
231
+
232
+ To report a vulnerability, see [SECURITY.md](SECURITY.md).
233
+
234
+ ## Troubleshooting
235
+
236
+ **The browser did not open during sign-in.** The bridge prints the authorization URL to stderr. Open it manually.
237
+
238
+ **A configuration file was not updated.** The installer does not modify files that contain comments. Add the printed entry by hand.
239
+
240
+ **The agent reports a missing permission.** Reconnect Novalink from your editor and grant the scope named in the message. With the bridge, run `npx @novalinkai/mcp logout`, then `login`.
241
+
242
+ **Connecting to a local or self-hosted deployment.** Set `NOVALINK_MCP_URL`, or pass `--url` to `install`. Server URLs must use https; plain http is accepted only for `localhost`, so tokens are never sent unencrypted.
75
243
 
76
244
  ## Development
77
245
 
78
246
  ```sh
247
+ git clone https://github.com/Novalink-AI/mcp.git
248
+ cd mcp
79
249
  npm ci
80
250
  npm test
81
251
  npm run build
82
252
  NOVALINK_MCP_URL=http://localhost:8000/mcp node dist/cli.js install --dry-run
83
253
  ```
84
254
 
85
- The skill in `skills/novalink-workflows/SKILL.md` is the source for every editor's guide, the Claude Code plugin, and the `design_workflow` prompt the Novalink server serves.
255
+ `skills/novalink-workflows/SKILL.md` is the single source for the guide. The installer, the Claude Code plugin and the Novalink server's `design_workflow` prompt all derive from it.
256
+
257
+ ### Releasing
258
+
259
+ 1. Update the version in `package.json`, `src/constants.ts`, `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`. The test suite fails if they differ.
260
+ 2. Push a matching tag, for example `v0.1.2`.
86
261
 
87
- To release, bump the version in `package.json`, `src/constants.ts` and `.claude-plugin/*.json`, then push a tag such as `v0.1.2`.
262
+ The release workflow publishes to npm through trusted publishing, with provenance.
88
263
 
89
264
  ## License
90
265
 
91
- MIT
266
+ [MIT](LICENSE)
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { spawn, spawnSync } from "child_process";
4
+ import { spawnSync } from "child_process";
5
5
  import { existsSync as existsSync3 } from "fs";
6
6
  import { homedir as homedir2 } from "os";
7
7
  import { delimiter, join as join3, relative } from "path";
@@ -17,16 +17,35 @@ import { dirname, join } from "path";
17
17
 
18
18
  // src/constants.ts
19
19
  var PACKAGE = "@novalinkai/mcp";
20
- var VERSION = "0.1.1";
20
+ var VERSION = "0.1.2";
21
21
  var SERVER_NAME = "novalink";
22
22
  var SKILL_NAME = "novalink-workflows";
23
23
  var DEFAULT_URL = "https://api.novalink.live/mcp";
24
24
  var DOCS_URL = "https://novalink.live/developers/mcp";
25
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
26
+ var SHELL_UNSAFE = /[\s"'`^|<>&;$\\]/;
27
+ function checkUrl(value, what = "URL", strict = true) {
28
+ let url;
29
+ try {
30
+ url = new URL(value);
31
+ } catch {
32
+ throw new Error(`${what} is not a valid URL: ${value}`);
33
+ }
34
+ const local = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
35
+ if (url.protocol !== "https:" && !local) {
36
+ throw new Error(`${what} must use https (http is allowed only for localhost): ${value}`);
37
+ }
38
+ if (url.username || url.password) throw new Error(`${what} must not contain credentials: ${value}`);
39
+ const checked = strict ? value : value.split(/[?#]/)[0] ?? "";
40
+ if (SHELL_UNSAFE.test(checked)) throw new Error(`${what} contains characters that are not allowed: ${value}`);
41
+ return value;
42
+ }
25
43
  function serverUrl(flag, env = process.env) {
26
- if (flag) return flag.replace(/\/$/, "");
27
- if (env.NOVALINK_MCP_URL) return env.NOVALINK_MCP_URL.replace(/\/$/, "");
28
- if (env.NOVALINK_API_URL) return `${env.NOVALINK_API_URL.replace(/\/$/, "")}/mcp`;
29
- return DEFAULT_URL;
44
+ let url = DEFAULT_URL;
45
+ if (flag) url = flag.replace(/\/$/, "");
46
+ else if (env.NOVALINK_MCP_URL) url = env.NOVALINK_MCP_URL.replace(/\/$/, "");
47
+ else if (env.NOVALINK_API_URL) url = `${env.NOVALINK_API_URL.replace(/\/$/, "")}/mcp`;
48
+ return checkUrl(url, "The Novalink server URL");
30
49
  }
31
50
  function log(message) {
32
51
  process.stderr.write(`${message}
@@ -44,7 +63,6 @@ var TokenStore = class {
44
63
  constructor(path) {
45
64
  this.path = path;
46
65
  }
47
- path;
48
66
  readAll() {
49
67
  if (!existsSync(this.path)) return {};
50
68
  try {
@@ -93,7 +111,9 @@ async function discover(url, fetchFn) {
93
111
  `${resource.origin}/.well-known/oauth-protected-resource`
94
112
  ]);
95
113
  const servers = prm?.authorization_servers;
96
- const issuer = new URL(Array.isArray(servers) && typeof servers[0] === "string" ? servers[0] : resource.origin);
114
+ const issuer = new URL(
115
+ checkUrl(Array.isArray(servers) && typeof servers[0] === "string" ? servers[0] : resource.origin, "The authorization server")
116
+ );
97
117
  const issuerPath = issuer.pathname === "/" ? "" : issuer.pathname.replace(/\/$/, "");
98
118
  const metadata = await firstJson(fetchFn, [
99
119
  `${issuer.origin}/.well-known/oauth-authorization-server${issuerPath}`,
@@ -103,6 +123,10 @@ async function discover(url, fetchFn) {
103
123
  if (!metadata?.authorization_endpoint || !metadata.token_endpoint) {
104
124
  throw new Error(`Could not find the sign-in endpoints for ${url}. Check the URL, or that the server is up.`);
105
125
  }
126
+ for (const key of ["authorization_endpoint", "token_endpoint", "registration_endpoint", "revocation_endpoint"]) {
127
+ const value = metadata[key];
128
+ if (value !== void 0) checkUrl(String(value), `The server's ${key}`);
129
+ }
106
130
  return metadata;
107
131
  }
108
132
  async function postForm(fetchFn, url, form) {
@@ -254,9 +278,6 @@ var Bridge = class {
254
278
  this.deps = deps;
255
279
  this.write = write;
256
280
  }
257
- url;
258
- deps;
259
- write;
260
281
  signingIn;
261
282
  protocolVersion;
262
283
  async token() {
@@ -331,6 +352,22 @@ async function serve(url, deps) {
331
352
  await Promise.all(pending);
332
353
  }
333
354
 
355
+ // src/browser.ts
356
+ import { spawn } from "child_process";
357
+ function browserCommand(url, platform = process.platform) {
358
+ checkUrl(url, "The sign-in URL", false);
359
+ if (platform === "darwin") return ["open", [url]];
360
+ if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
361
+ return ["xdg-open", [url]];
362
+ }
363
+ function openBrowser(url) {
364
+ try {
365
+ const [command, args] = browserCommand(url);
366
+ spawn(command, args, { stdio: "ignore", detached: true }).on("error", () => void 0).unref();
367
+ } catch {
368
+ }
369
+ }
370
+
334
371
  // src/clients.ts
335
372
  import { join as join2 } from "path";
336
373
 
@@ -549,13 +586,6 @@ function onPath(bin) {
549
586
  const extensions = process.platform === "win32" ? ["", ".cmd", ".exe", ".bat"] : [""];
550
587
  return (process.env.PATH ?? "").split(delimiter).some((dir) => dir && extensions.some((extension) => existsSync3(join3(dir, bin + extension))));
551
588
  }
552
- function openBrowser(url) {
553
- const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url.replace(/&/g, "^&")]] : ["xdg-open", [url]];
554
- try {
555
- spawn(command, args, { stdio: "ignore", detached: true }).on("error", () => void 0).unref();
556
- } catch {
557
- }
558
- }
559
589
  function authDeps() {
560
590
  return { fetch: globalThis.fetch, store: new TokenStore(defaultStorePath()), open: openBrowser, log };
561
591
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novalinkai/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Connect Claude Code, Cursor, Antigravity and other coding agents to Novalink, so they can plan, build and integrate workflows.",
5
5
  "keywords": [
6
6
  "novalink",
@@ -42,7 +42,7 @@
42
42
  "@types/node": "^22.0.0",
43
43
  "tsup": "^8.5.0",
44
44
  "typescript": "^5.9.0",
45
- "vitest": "^3.2.0"
45
+ "vitest": "^5.0.2"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public",