signafaide-mcp 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # signafaide-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The signafAIde MCP server for coding agents, over stdio. It is a separate package so that
4
+ `npm install signafaide` stays small: the SDK itself never needs the MCP SDK or zod.
5
+
6
+ `npx signafaide agent setup` installs it for you in each coding agent's configuration, pinned to
7
+ the SDK's version:
8
+
9
+ ```json
10
+ {"command": "npx", "args": ["-y", "signafaide-mcp@0.2.1"]}
11
+ ```
12
+
13
+ Tools: `signafaide_setup` (previews the install command and code edit, changes nothing),
14
+ `signafaide_verify`, `signafaide_status`, `signafaide_explain_error`, and `signafaide_init`
15
+ (creates the external sandbox; call it only after the user has agreed, with
16
+ `confirm_user_agreed: true` and `user_words`, what the user wrote; without both it creates nothing).
17
+
18
+ Apache-2.0. Source: the `js/` folder of the signafAIde SDK repository.
@@ -0,0 +1,187 @@
1
+ // src/version.ts
2
+ var VERSION = "0.2.1";
3
+
4
+ // src/telemetry.ts
5
+ var HEADER = "X-Signafaide-Client";
6
+ var SDK = "signafaide-js";
7
+ var ADAPTERS = /* @__PURE__ */ new Set();
8
+ var processEnv = () => typeof process !== "undefined" && process.env ? process.env : {};
9
+ var hasPrefix = (env, prefix, skip = []) => Object.keys(env).some((k) => k.startsWith(prefix) && !skip.some((s) => k.startsWith(s)));
10
+ var term = (env) => (env.TERM_PROGRAM ?? "").toLowerCase();
11
+ var AGENTS = [
12
+ ["claude-code", (e) => e.CLAUDECODE === "1" || hasPrefix(e, "CLAUDE_CODE_")],
13
+ ["codex", (e) => hasPrefix(e, "CODEX_", ["CODEX_COMPANION_"])],
14
+ ["gemini-cli", (e) => hasPrefix(e, "GEMINI_CLI")],
15
+ ["copilot", (e) => hasPrefix(e, "COPILOT_") || hasPrefix(e, "GITHUB_COPILOT_")],
16
+ ["windsurf", (e) => hasPrefix(e, "WINDSURF_") || term(e) === "windsurf"],
17
+ ["cursor", (e) => hasPrefix(e, "CURSOR_") || term(e) === "cursor"]
18
+ ];
19
+ function detectAgent(env = processEnv()) {
20
+ return AGENTS.find(([, test]) => test(env))?.[0] ?? "unknown";
21
+ }
22
+ function telemetryEnabled(env = processEnv()) {
23
+ return (env.SIGNAFAIDE_TELEMETRY ?? "").trim() !== "0";
24
+ }
25
+ function adapterField() {
26
+ return [...ADAPTERS].sort().join(",") || "none";
27
+ }
28
+ function runtimeName() {
29
+ const g = globalThis;
30
+ if (g.Bun?.version) return `bun ${g.Bun.version}`;
31
+ if (g.Deno?.version?.deno) return `deno ${g.Deno.version.deno}`;
32
+ if (typeof process !== "undefined" && process.versions?.node) return `node ${process.versions.node}`;
33
+ return "js unknown";
34
+ }
35
+ function clientHeader(opts = {}) {
36
+ const on = telemetryEnabled(opts.env);
37
+ const parts = [runtimeName()];
38
+ if (on) parts.push(`adapter=${opts.adapter || "none"}`, `agent=${opts.agent || "unknown"}`);
39
+ parts.push(`via=${opts.via ?? "sdk"}`);
40
+ if (on && opts.strict) parts.push("strict=1");
41
+ return `${SDK}/${VERSION} (${parts.join("; ")})`;
42
+ }
43
+
44
+ // src/http.ts
45
+ var DEFAULT_URL = "https://api.signafaide.co.uk";
46
+ var NON_RETRYABLE = /* @__PURE__ */ new Set(["quota_exceeded", "invalid_key", "plan_required", "bad_signature", "invalid_record", "content_mismatch", "chain_conflict", "unknown_agent", "key_mismatch", "bad_response"]);
47
+ var fetchTransport = async (req) => {
48
+ const ctrl = new AbortController();
49
+ const timer = setTimeout(() => ctrl.abort(new Error("timeout")), req.timeoutMs);
50
+ const onAbort = () => ctrl.abort(new Error("aborted"));
51
+ if (req.signal?.aborted) onAbort();
52
+ else req.signal?.addEventListener("abort", onAbort, { once: true });
53
+ try {
54
+ const resp = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body, signal: ctrl.signal });
55
+ return { status: resp.status, contentType: resp.headers.get("content-type") ?? "", text: await resp.text() };
56
+ } finally {
57
+ clearTimeout(timer);
58
+ req.signal?.removeEventListener("abort", onAbort);
59
+ }
60
+ };
61
+ var SignafaideError = class extends Error {
62
+ code;
63
+ status;
64
+ docs;
65
+ body;
66
+ constructor(message, opts = {}) {
67
+ super(message);
68
+ this.name = "SignafaideError";
69
+ this.code = opts.code ?? "error";
70
+ this.status = opts.status ?? 0;
71
+ this.docs = opts.docs;
72
+ this.body = opts.body ?? {};
73
+ }
74
+ get retryable() {
75
+ return this.code === "network" || this.code === "rate_limited" || this.status >= 500;
76
+ }
77
+ /** The service refused for a reason retrying cannot change (quota, key, revoked agent key...). */
78
+ get permanent() {
79
+ return !this.retryable && NON_RETRYABLE.has(this.code);
80
+ }
81
+ };
82
+ var Client = class {
83
+ constructor(url = DEFAULT_URL, apiKey, timeoutMs = 1e4, clientId, opts = {}) {
84
+ this.apiKey = apiKey;
85
+ this.timeoutMs = timeoutMs;
86
+ this.url = url.replace(/\/+$/, "");
87
+ this.clientId = clientId ?? clientHeader({});
88
+ this.transport = opts.transport ?? fetchTransport;
89
+ this.signal = opts.signal;
90
+ this.timeoutLabel = opts.timeoutLabel;
91
+ }
92
+ apiKey;
93
+ timeoutMs;
94
+ url;
95
+ clientId;
96
+ transport;
97
+ signal;
98
+ /** Said instead of "no answer within <timeout> s" when the request shares a larger limit. */
99
+ timeoutLabel;
100
+ async request(method, path, opts = {}) {
101
+ const auth = opts.auth ?? true;
102
+ let url = this.url + path;
103
+ const q = Object.entries(opts.query ?? {}).filter((e) => e[1] != null);
104
+ if (q.length) url += "?" + new URLSearchParams(q).toString();
105
+ const headers = { Accept: "application/json", [HEADER]: this.clientId };
106
+ if (typeof process !== "undefined" && process.versions?.node) headers["User-Agent"] = `signafaide-js/${VERSION}`;
107
+ if (opts.body !== void 0) headers["Content-Type"] = "application/json";
108
+ if (auth) {
109
+ if (!this.apiKey) throw new SignafaideError("No API key: run `signafaide init` or set SIGNAFAIDE_API_KEY.", { code: "invalid_key" });
110
+ headers.Authorization = `Bearer ${this.apiKey}`;
111
+ }
112
+ let resp;
113
+ try {
114
+ resp = await this.transport({ method, url, headers, body: opts.body === void 0 ? void 0 : JSON.stringify(opts.body), timeoutMs: this.timeoutMs, signal: this.signal });
115
+ } catch (e) {
116
+ const err = e;
117
+ const raw = err.cause?.code ?? err.code ?? err.message ?? String(e);
118
+ const reason = /abort|timeout/i.test(`${err.name} ${raw}`) ? this.timeoutLabel ?? `no answer within ${this.timeoutMs / 1e3} s` : raw;
119
+ throw new SignafaideError(`Cannot reach the signafAIde service at ${this.url} (${reason}); check the network or SIGNAFAIDE_URL.`, { code: "network" });
120
+ }
121
+ const { status, text, contentType: ctype } = resp;
122
+ if (status < 200 || status >= 300) throw serviceError(status, text);
123
+ return ctype.includes("json") || /^[[{]/.test(text) ? JSON.parse(text) : text;
124
+ }
125
+ sandbox() {
126
+ return this.request("POST", "/v1/sandbox", { body: {}, auth: false });
127
+ }
128
+ /** §2.4: the registration with its proof of possession (and rotation signature). */
129
+ registerAgent(name, publicKey, validFrom, proof, rotation) {
130
+ return this.request("POST", "/v1/agents", { body: { name, public_key: publicKey, valid_from: validFrom, proof, ...rotation ? { rotation } : {} } });
131
+ }
132
+ /** `{project, env}` of this API key (§6). */
133
+ project() {
134
+ return this.request("GET", "/v1/project");
135
+ }
136
+ submit(records) {
137
+ return this.request("POST", "/v1/records", { body: { records } });
138
+ }
139
+ head(agent) {
140
+ return this.request("GET", `/v1/heads/${encodeURIComponent(agent)}`);
141
+ }
142
+ /** `{record, receipt, anchor}` of one record of the key's project. */
143
+ record(recordHash) {
144
+ return this.request("GET", `/v1/records/${encodeURIComponent(recordHash)}`);
145
+ }
146
+ /** A verification pack (§7) of the whole project, or of one agent. */
147
+ export(agent, from, to) {
148
+ return this.request("GET", "/v1/export", { query: { agent, from, to } });
149
+ }
150
+ usage() {
151
+ return this.request("GET", "/v1/usage");
152
+ }
153
+ /** Usage event (`verify`, `export`). Not part of SPEC §6 yet. */
154
+ async event(name) {
155
+ await this.request("POST", "/v1/events", { body: { name }, auth: !!this.apiKey });
156
+ }
157
+ serviceKeys() {
158
+ return this.request("GET", "/.well-known/signafaide-keys.json", { auth: false });
159
+ }
160
+ };
161
+ function serviceError(status, text) {
162
+ try {
163
+ const body = JSON.parse(text);
164
+ const err = body.error;
165
+ if (typeof err?.message === "string") return new SignafaideError(err.message, { code: err.code ?? "error", status, docs: err.docs, body });
166
+ } catch {
167
+ }
168
+ return new SignafaideError(`The service answered HTTP ${status}: ${text.trim().slice(0, 200) || "no body"}.`, { code: "http_error", status });
169
+ }
170
+ async function fetchJson(url, timeoutMs = 1e4) {
171
+ const u = new URL(url);
172
+ return new Client(u.origin, void 0, timeoutMs, `signafaide-js/${VERSION}`).request("GET", u.pathname + u.search, { auth: false });
173
+ }
174
+
175
+ export {
176
+ VERSION,
177
+ detectAgent,
178
+ telemetryEnabled,
179
+ adapterField,
180
+ clientHeader,
181
+ DEFAULT_URL,
182
+ NON_RETRYABLE,
183
+ fetchTransport,
184
+ SignafaideError,
185
+ Client,
186
+ fetchJson
187
+ };