@attrkit/mcp 1.0.0 → 1.2.0
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/CHANGELOG.md +24 -0
- package/README.md +19 -5
- package/dist/cli.js +281 -13
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0 (2026-10-03)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `attrkit-mcp login` (macOS): asks for the management key with echo off, checks it against the
|
|
8
|
+
API, and stores it in the Keychain item `attrkit-mcp` through `security -i` with the key on
|
|
9
|
+
stdin, so it never appears in argv. `attrkit-mcp logout` removes the item.
|
|
10
|
+
- The server reads the key from that Keychain item on macOS when `ATTRKIT_API_KEY` is unset, empty,
|
|
11
|
+
or an unexpanded client placeholder (`$ATTRKIT_API_KEY`, `${ATTRKIT_API_KEY}`,
|
|
12
|
+
`${ATTRKIT_API_KEY:-}`, `${env:ATTRKIT_API_KEY}`, `${input:attrkit-key}`). A real
|
|
13
|
+
`ATTRKIT_API_KEY` still wins and the Keychain is not consulted, so existing configurations behave
|
|
14
|
+
exactly as before.
|
|
15
|
+
- The missing-key error now mentions `npx -y @attrkit/mcp login` on macOS.
|
|
16
|
+
|
|
17
|
+
## 1.1.0 (2026-10-01)
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `send_server_event` accepts an optional `consent.dma` object: `eea`, and optionally
|
|
22
|
+
`ad_user_data` and `ad_personalization`, the user's answers to Google's EU consent questions. It
|
|
23
|
+
is forwarded to the API as `consent.dma` with `source: "manual"`, and only when you give it; a
|
|
24
|
+
call without it sends exactly what 1.0.0 sent. A key the object does not define (`adUserData`
|
|
25
|
+
for `ad_user_data`) is refused before any request, as the API refuses it.
|
|
26
|
+
|
|
3
27
|
## 1.0.0 (2026-09-30)
|
|
4
28
|
|
|
5
29
|
Major version release with input contract formalization, bug fixes, and security hardening.
|
package/README.md
CHANGED
|
@@ -45,15 +45,29 @@ Use a read-only or app-scoped key when the client needs less access. App-scoped
|
|
|
45
45
|
keys cannot read or change the workspace usage cap, create sibling apps, or
|
|
46
46
|
manage workspace-wide integrations.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
### Give the server the key
|
|
49
|
+
|
|
50
|
+
On macOS, store the key once in the Keychain:
|
|
49
51
|
|
|
50
52
|
```bash
|
|
51
|
-
|
|
52
|
-
IFS= read -r -s ATTRKIT_API_KEY
|
|
53
|
-
export ATTRKIT_API_KEY
|
|
54
|
-
printf "\n"
|
|
53
|
+
npx -y @attrkit/mcp login
|
|
55
54
|
```
|
|
56
55
|
|
|
56
|
+
It asks for the key with echo off, checks it against AttriKit (and prints the plan and scopes),
|
|
57
|
+
then stores it in the Keychain item `attrkit-mcp`. The key never appears in a process listing.
|
|
58
|
+
`npx -y @attrkit/mcp logout` removes the item.
|
|
59
|
+
|
|
60
|
+
On any system you can instead set `ATTRKIT_API_KEY` in the environment of the process that starts
|
|
61
|
+
your MCP client, or in the client's own secret store.
|
|
62
|
+
|
|
63
|
+
At start the server resolves the key in this order:
|
|
64
|
+
|
|
65
|
+
1. `ATTRKIT_API_KEY`, when it holds a real value.
|
|
66
|
+
2. On macOS, the Keychain item `attrkit-mcp`. An empty `ATTRKIT_API_KEY`, or one a client left
|
|
67
|
+
unexpanded (`$ATTRKIT_API_KEY`, `${ATTRKIT_API_KEY}`, `${ATTRKIT_API_KEY:-}`,
|
|
68
|
+
`${env:ATTRKIT_API_KEY}`, `${input:attrkit-key}`), counts as unset.
|
|
69
|
+
3. Otherwise it exits with an error that names the fix.
|
|
70
|
+
|
|
57
71
|
## Codex
|
|
58
72
|
|
|
59
73
|
Add this server to `~/.codex/config.toml`:
|
package/dist/cli.js
CHANGED
|
@@ -13876,6 +13876,13 @@ var consentStateSchema = z3.object({
|
|
|
13876
13876
|
policy_version: z3.number().int().positive(),
|
|
13877
13877
|
framework_string: z3.string().max(512).optional()
|
|
13878
13878
|
}).strict();
|
|
13879
|
+
var dmaConsentSchema = z3.object({
|
|
13880
|
+
eea: z3.boolean(),
|
|
13881
|
+
ad_user_data: z3.boolean().optional(),
|
|
13882
|
+
ad_personalization: z3.boolean().optional(),
|
|
13883
|
+
source: z3.enum(["tcf", "manual"])
|
|
13884
|
+
}).strict();
|
|
13885
|
+
var firstOpenConsentSchema = consentStateSchema.extend({ dma: dmaConsentSchema.optional() }).strict();
|
|
13879
13886
|
var coarseContextSchema = z3.object({
|
|
13880
13887
|
country_code: z3.string().regex(/^[A-Z]{2}$/).optional(),
|
|
13881
13888
|
region_code: z3.string().max(16).optional(),
|
|
@@ -13940,7 +13947,7 @@ var firstOpenEnvelopeSchema = z3.object({
|
|
|
13940
13947
|
occurred_at: z3.string().datetime({ offset: true }),
|
|
13941
13948
|
app_version: z3.string().min(1).max(128),
|
|
13942
13949
|
coarse_context: coarseContextSchema,
|
|
13943
|
-
consent:
|
|
13950
|
+
consent: firstOpenConsentSchema,
|
|
13944
13951
|
app_transaction_jws: z3.string().min(16).max(32768).optional(),
|
|
13945
13952
|
asa_token: z3.string().min(8).max(8192).optional(),
|
|
13946
13953
|
exact_token_ref: exactTokenReferenceSchema.optional(),
|
|
@@ -13969,7 +13976,8 @@ var identifyEnvelopeSchema = z3.object({
|
|
|
13969
13976
|
var eventConsentSchema = z3.object({
|
|
13970
13977
|
measurement: z3.enum(["granted", "denied", "unknown"]),
|
|
13971
13978
|
tracking: z3.enum(["granted", "denied", "unknown"]),
|
|
13972
|
-
policy_version: z3.number().int().positive()
|
|
13979
|
+
policy_version: z3.number().int().positive(),
|
|
13980
|
+
dma: dmaConsentSchema.optional()
|
|
13973
13981
|
}).strict();
|
|
13974
13982
|
var propertyScalarSchema = z3.union([
|
|
13975
13983
|
z3.string().max(1024),
|
|
@@ -14783,7 +14791,7 @@ function validatedApiBaseUrl(value) {
|
|
|
14783
14791
|
function readConfig(env = process.env) {
|
|
14784
14792
|
const apiKey = env.ATTRKIT_API_KEY;
|
|
14785
14793
|
if (!apiKey) {
|
|
14786
|
-
throw new Error("ATTRKIT_API_KEY is required (create one in AttriKit → Settings → API keys).");
|
|
14794
|
+
throw new Error("ATTRKIT_API_KEY is required (create one in AttriKit → Settings → API keys). On macOS, run `npx -y @attrkit/mcp login` to store it in the Keychain.");
|
|
14787
14795
|
}
|
|
14788
14796
|
const apiUrl = (env.ATTRKIT_API_URL ?? "https://attrikit.io").replace(/\/+$/, "");
|
|
14789
14797
|
validatedApiBaseUrl(apiUrl);
|
|
@@ -15047,7 +15055,7 @@ ${body}`
|
|
|
15047
15055
|
structuredContent: structured
|
|
15048
15056
|
}, result.nextCursor);
|
|
15049
15057
|
}
|
|
15050
|
-
var SERVER_VERSION = "1.
|
|
15058
|
+
var SERVER_VERSION = "1.2.0";
|
|
15051
15059
|
var periodSchema = z5.enum(["7d", "30d", "90d", "all"]).describe("Reporting window. Omit to use the endpoint's documented default.").optional();
|
|
15052
15060
|
var appIdSchema = z5.string().describe("Optional app id to scope the result to one app.").optional();
|
|
15053
15061
|
var requiredAppIdSchema = z5.string().min(1).describe("The AttriKit app id.");
|
|
@@ -15802,13 +15810,31 @@ function createServer(config, fetchImpl) {
|
|
|
15802
15810
|
consent: z5.object({
|
|
15803
15811
|
measurement: z5.enum(["granted", "denied", "unknown"]),
|
|
15804
15812
|
tracking: z5.enum(["granted", "denied", "unknown"]),
|
|
15805
|
-
policy_version: z5.number().int().positive()
|
|
15813
|
+
policy_version: z5.number().int().positive(),
|
|
15814
|
+
dma: z5.object({
|
|
15815
|
+
eea: z5.boolean().describe("Whether EEA, UK or Swiss rules apply to this user."),
|
|
15816
|
+
ad_user_data: z5.boolean().optional().describe("The user's ad_user_data consent."),
|
|
15817
|
+
ad_personalization: z5.boolean().optional().describe("The user's ad_personalization consent.")
|
|
15818
|
+
}).strict().optional().describe("Optional. The user's answers to Google's EU consent questions, sent to Google with this event. A value left out is derived from tracking; an unknown key is refused, as the API refuses it.")
|
|
15806
15819
|
}).describe("The per-event consent SDK events carry. An event whose measurement is not granted is not collected."),
|
|
15807
15820
|
properties: z5.record(z5.string(), z5.union([z5.string().max(1024), z5.number(), z5.boolean(), z5.null()])).optional().describe("Scalar values only, at most 64 keys. Revenue is value (major units) or price_minor_units with currency, an ISO 4217 code."),
|
|
15808
15821
|
idempotency_key: idempotencyKeySchema
|
|
15809
15822
|
}
|
|
15810
15823
|
}, async ({ app_id, customer_user_id, event_id, event_name, event_version, occurred_at, consent, properties, idempotency_key }) => {
|
|
15811
|
-
const
|
|
15824
|
+
const forwardedConsent = {
|
|
15825
|
+
measurement: consent.measurement,
|
|
15826
|
+
tracking: consent.tracking,
|
|
15827
|
+
policy_version: consent.policy_version
|
|
15828
|
+
};
|
|
15829
|
+
if (consent.dma !== undefined) {
|
|
15830
|
+
const dma = { eea: consent.dma.eea, source: "manual" };
|
|
15831
|
+
if (consent.dma.ad_user_data !== undefined)
|
|
15832
|
+
dma.ad_user_data = consent.dma.ad_user_data;
|
|
15833
|
+
if (consent.dma.ad_personalization !== undefined)
|
|
15834
|
+
dma.ad_personalization = consent.dma.ad_personalization;
|
|
15835
|
+
forwardedConsent.dma = dma;
|
|
15836
|
+
}
|
|
15837
|
+
const body = { customer_user_id, event_id, event_name, event_version, occurred_at, consent: forwardedConsent };
|
|
15812
15838
|
if (properties !== undefined)
|
|
15813
15839
|
body.properties = properties;
|
|
15814
15840
|
return toToolResult(await call(`/api/v1/apps/${encodeOpaquePathSegment(app_id)}/events`, {
|
|
@@ -15829,23 +15855,265 @@ function createServer(config, fetchImpl) {
|
|
|
15829
15855
|
return server;
|
|
15830
15856
|
}
|
|
15831
15857
|
|
|
15832
|
-
// src/
|
|
15833
|
-
|
|
15858
|
+
// src/key.ts
|
|
15859
|
+
import { execFile } from "node:child_process";
|
|
15860
|
+
var KEYCHAIN_SERVICE = "attrkit-mcp";
|
|
15861
|
+
var SECURITY_BIN = "/usr/bin/security";
|
|
15862
|
+
var KEY_REQUIRED_MESSAGE = "ATTRKIT_API_KEY is required (create one in AttriKit → Settings → API keys). On macOS, run `npx -y @attrkit/mcp login` to store it in the Keychain.";
|
|
15863
|
+
var KEY_REQUIRED_MESSAGE_OTHER_PLATFORM = "ATTRKIT_API_KEY is required (create one in AttriKit → Settings → API keys). Set it in the environment or in your MCP client's secret store.";
|
|
15864
|
+
var UNEXPANDED_PLACEHOLDERS = new Set([
|
|
15865
|
+
"$ATTRKIT_API_KEY",
|
|
15866
|
+
"${ATTRKIT_API_KEY}",
|
|
15867
|
+
"${ATTRKIT_API_KEY:-}",
|
|
15868
|
+
"${env:ATTRKIT_API_KEY}",
|
|
15869
|
+
"${input:attrkit-key}"
|
|
15870
|
+
]);
|
|
15871
|
+
function realEnvKey(env) {
|
|
15872
|
+
const value = env.ATTRKIT_API_KEY;
|
|
15873
|
+
if (value === undefined || value.trim() === "")
|
|
15874
|
+
return;
|
|
15875
|
+
if (UNEXPANDED_PLACEHOLDERS.has(value.trim()))
|
|
15876
|
+
return;
|
|
15877
|
+
return value;
|
|
15878
|
+
}
|
|
15879
|
+
var realExecFile = (file, args, options, done) => execFile(file, args, options, done);
|
|
15880
|
+
function createSecurityRunner(execFileImpl = realExecFile) {
|
|
15881
|
+
return (args, stdin) => new Promise((resolve) => {
|
|
15882
|
+
let writeFailed = false;
|
|
15883
|
+
const child = execFileImpl(SECURITY_BIN, [...args], { encoding: "utf8", timeout: 15000 }, (error, stdout, stderr) => {
|
|
15884
|
+
const code = error === null ? 0 : Number.isInteger(error.code) ? Number(error.code) : 1;
|
|
15885
|
+
resolve(writeFailed ? { code: code === 0 ? 1 : code, stdout, stderr: `${stderr}could not write to security` } : { code, stdout, stderr });
|
|
15886
|
+
});
|
|
15887
|
+
child.stdin?.on("error", () => {
|
|
15888
|
+
writeFailed = true;
|
|
15889
|
+
});
|
|
15890
|
+
if (stdin !== undefined)
|
|
15891
|
+
child.stdin?.end(stdin);
|
|
15892
|
+
else
|
|
15893
|
+
child.stdin?.end();
|
|
15894
|
+
});
|
|
15895
|
+
}
|
|
15896
|
+
var defaultSecurityRunner = createSecurityRunner();
|
|
15897
|
+
async function keychainKey(runSecurity) {
|
|
15898
|
+
const result = await runSecurity(["find-generic-password", "-s", KEYCHAIN_SERVICE, "-w"]);
|
|
15899
|
+
if (result.code !== 0)
|
|
15900
|
+
return;
|
|
15901
|
+
const key = result.stdout.replace(/\r?\n$/, "");
|
|
15902
|
+
return key.trim() === "" ? undefined : key;
|
|
15903
|
+
}
|
|
15904
|
+
async function resolveApiKey(options = {}) {
|
|
15905
|
+
const env = options.env ?? process.env;
|
|
15906
|
+
const platform = options.platform ?? process.platform;
|
|
15907
|
+
const fromEnv = realEnvKey(env);
|
|
15908
|
+
if (fromEnv !== undefined)
|
|
15909
|
+
return fromEnv;
|
|
15910
|
+
if (platform !== "darwin")
|
|
15911
|
+
throw new Error(KEY_REQUIRED_MESSAGE_OTHER_PLATFORM);
|
|
15912
|
+
const fromKeychain = await keychainKey(options.runSecurity ?? defaultSecurityRunner);
|
|
15913
|
+
if (fromKeychain !== undefined)
|
|
15914
|
+
return fromKeychain;
|
|
15915
|
+
throw new Error(KEY_REQUIRED_MESSAGE);
|
|
15916
|
+
}
|
|
15917
|
+
|
|
15918
|
+
// src/login.ts
|
|
15919
|
+
import { userInfo } from "node:os";
|
|
15920
|
+
import { z as z6 } from "zod";
|
|
15921
|
+
function quotedForSecurity(key) {
|
|
15922
|
+
if (key === "" || Array.from(key).some(isUnquotable))
|
|
15923
|
+
return null;
|
|
15924
|
+
return `"${key}"`;
|
|
15925
|
+
}
|
|
15926
|
+
function isUnquotable(character) {
|
|
15927
|
+
const code = character.charCodeAt(0);
|
|
15928
|
+
return character === '"' || character === "\\" || code <= 31 || code === 127;
|
|
15929
|
+
}
|
|
15930
|
+
function addKeyCommand(username, quotedKey) {
|
|
15931
|
+
return `add-generic-password -U -s ${KEYCHAIN_SERVICE} -a "${Array.from(username).filter((character) => !isUnquotable(character)).join("")}" -w ${quotedKey}
|
|
15932
|
+
`;
|
|
15933
|
+
}
|
|
15934
|
+
function readHidden(input, prompt, write, idleMs = 50) {
|
|
15935
|
+
return new Promise((resolve) => {
|
|
15936
|
+
write(prompt);
|
|
15937
|
+
input.setRawMode(true);
|
|
15938
|
+
input.resume();
|
|
15939
|
+
input.setEncoding("utf8");
|
|
15940
|
+
let value = "";
|
|
15941
|
+
let mode = "text";
|
|
15942
|
+
let idle;
|
|
15943
|
+
const finish = (result) => {
|
|
15944
|
+
if (idle !== undefined)
|
|
15945
|
+
clearTimeout(idle);
|
|
15946
|
+
input.removeListener("data", onData);
|
|
15947
|
+
input.setRawMode(false);
|
|
15948
|
+
input.pause();
|
|
15949
|
+
write(`
|
|
15950
|
+
`);
|
|
15951
|
+
resolve(result);
|
|
15952
|
+
};
|
|
15953
|
+
const armIdle = () => {
|
|
15954
|
+
if (idle !== undefined)
|
|
15955
|
+
clearTimeout(idle);
|
|
15956
|
+
idle = setTimeout(() => finish(value), idleMs);
|
|
15957
|
+
};
|
|
15958
|
+
const onData = (chunk) => {
|
|
15959
|
+
for (const character of chunk) {
|
|
15960
|
+
if (mode === "drain")
|
|
15961
|
+
continue;
|
|
15962
|
+
if (mode === "escape") {
|
|
15963
|
+
mode = character === "[" ? "csi" : character === "O" ? "ss3" : "text";
|
|
15964
|
+
} else if (mode === "csi") {
|
|
15965
|
+
if (character >= "@" && character <= "~")
|
|
15966
|
+
mode = "text";
|
|
15967
|
+
} else if (mode === "ss3") {
|
|
15968
|
+
mode = "text";
|
|
15969
|
+
} else if (character === "\x03") {
|
|
15970
|
+
return finish(null);
|
|
15971
|
+
} else if (character === "\x1B") {
|
|
15972
|
+
mode = "escape";
|
|
15973
|
+
} else if (character === "\r" || character === `
|
|
15974
|
+
`) {
|
|
15975
|
+
mode = "drain";
|
|
15976
|
+
} else if (character === "" || character === "\b") {
|
|
15977
|
+
value = value.slice(0, -1);
|
|
15978
|
+
} else if (character >= " ") {
|
|
15979
|
+
value += character;
|
|
15980
|
+
}
|
|
15981
|
+
}
|
|
15982
|
+
if (mode === "drain")
|
|
15983
|
+
armIdle();
|
|
15984
|
+
};
|
|
15985
|
+
input.on("data", onData);
|
|
15986
|
+
});
|
|
15987
|
+
}
|
|
15988
|
+
function readHiddenFromTty(prompt) {
|
|
15989
|
+
return readHidden(process.stdin, prompt, (text) => void process.stderr.write(text));
|
|
15990
|
+
}
|
|
15991
|
+
var whoamiSchema = z6.object({
|
|
15992
|
+
data: z6.object({
|
|
15993
|
+
plan: z6.string().optional(),
|
|
15994
|
+
scope: z6.object({
|
|
15995
|
+
app_id: z6.string().nullish(),
|
|
15996
|
+
scopes: z6.array(z6.string()).optional()
|
|
15997
|
+
}).optional()
|
|
15998
|
+
})
|
|
15999
|
+
});
|
|
16000
|
+
function describeWhoami(json) {
|
|
16001
|
+
const parsed = whoamiSchema.safeParse(json);
|
|
16002
|
+
if (!parsed.success)
|
|
16003
|
+
return "Key accepted.";
|
|
16004
|
+
const { plan, scope } = parsed.data.data;
|
|
16005
|
+
const app = scope?.app_id ? `app ${scope.app_id}` : "all apps";
|
|
16006
|
+
return `Key accepted. Plan: ${plan ?? "unknown"}. Scopes: ${scope?.scopes?.join(", ") ?? "unknown"}. Restriction: ${app}.`;
|
|
16007
|
+
}
|
|
16008
|
+
async function runLogin(options = {}) {
|
|
16009
|
+
const write = options.write ?? ((text) => void process.stderr.write(text));
|
|
16010
|
+
const platform = options.platform ?? process.platform;
|
|
16011
|
+
if (platform !== "darwin") {
|
|
16012
|
+
write(`attrkit-mcp login stores the key in the macOS Keychain. On this system set ATTRKIT_API_KEY in the environment or in your MCP client's secret store.
|
|
16013
|
+
`);
|
|
16014
|
+
return 1;
|
|
16015
|
+
}
|
|
16016
|
+
const isTTY = options.isTTY ?? Boolean(process.stdin.isTTY);
|
|
16017
|
+
if (!isTTY) {
|
|
16018
|
+
write(`attrkit-mcp login needs an interactive terminal to ask for the key.
|
|
16019
|
+
`);
|
|
16020
|
+
return 1;
|
|
16021
|
+
}
|
|
16022
|
+
const readSecret = options.readSecret ?? readHiddenFromTty;
|
|
16023
|
+
const entered = await readSecret("AttriKit management key: ");
|
|
16024
|
+
if (entered === null)
|
|
16025
|
+
return 130;
|
|
16026
|
+
const key = entered.trim();
|
|
16027
|
+
const quoted = quotedForSecurity(key);
|
|
16028
|
+
if (quoted === null) {
|
|
16029
|
+
write(`That key was not accepted: it is empty or holds a quote, backslash or control character. Nothing was stored.
|
|
16030
|
+
`);
|
|
16031
|
+
return 1;
|
|
16032
|
+
}
|
|
15834
16033
|
let config;
|
|
15835
16034
|
try {
|
|
15836
|
-
config = readConfig();
|
|
16035
|
+
config = readConfig({ ...options.env ?? process.env, ATTRKIT_API_KEY: key });
|
|
15837
16036
|
} catch (error) {
|
|
15838
|
-
|
|
15839
|
-
|
|
16037
|
+
write(`${error instanceof Error ? error.message : String(error)}
|
|
16038
|
+
`);
|
|
16039
|
+
return 1;
|
|
16040
|
+
}
|
|
16041
|
+
const result = await callApi(config, "/api/v1/whoami", options.fetchImpl ? { fetchImpl: options.fetchImpl } : {});
|
|
16042
|
+
if (result.status === 401 || result.status === 403) {
|
|
16043
|
+
write(`AttriKit refused that key. Nothing was stored.
|
|
16044
|
+
`);
|
|
16045
|
+
return 1;
|
|
16046
|
+
}
|
|
16047
|
+
if (!result.ok) {
|
|
16048
|
+
write(result.status === 0 ? `Could not reach ${new URL(config.apiUrl).origin} to check the key. Nothing was stored.
|
|
16049
|
+
` : `Could not check the key (HTTP ${result.status}). Nothing was stored.
|
|
16050
|
+
`);
|
|
16051
|
+
return 1;
|
|
16052
|
+
}
|
|
16053
|
+
write(`${describeWhoami(result.json)}
|
|
16054
|
+
`);
|
|
16055
|
+
const runSecurity = options.runSecurity ?? defaultSecurityRunner;
|
|
16056
|
+
const stored = await runSecurity(["-i"], addKeyCommand(options.username ?? userInfo().username, quoted));
|
|
16057
|
+
if (stored.code !== 0 || stored.stderr.trim() !== "") {
|
|
16058
|
+
write(`The Keychain refused the item (exit ${stored.code}). Nothing was stored.
|
|
16059
|
+
`);
|
|
16060
|
+
return 1;
|
|
16061
|
+
}
|
|
16062
|
+
write(`Stored in the macOS Keychain as attrkit-mcp. Your MCP client can now start the server without ATTRKIT_API_KEY.
|
|
16063
|
+
`);
|
|
16064
|
+
return 0;
|
|
16065
|
+
}
|
|
16066
|
+
async function runLogout(options = {}) {
|
|
16067
|
+
const write = options.write ?? ((text) => void process.stderr.write(text));
|
|
16068
|
+
if ((options.platform ?? process.platform) !== "darwin") {
|
|
16069
|
+
write(`attrkit-mcp logout removes the macOS Keychain item; there is nothing to remove on this system.
|
|
16070
|
+
`);
|
|
16071
|
+
return 1;
|
|
16072
|
+
}
|
|
16073
|
+
const result = await (options.runSecurity ?? defaultSecurityRunner)(["delete-generic-password", "-s", KEYCHAIN_SERVICE]);
|
|
16074
|
+
write(result.code === 0 ? `Removed the attrkit-mcp item from the macOS Keychain.
|
|
16075
|
+
` : `Nothing was stored in the Keychain for attrkit-mcp.
|
|
16076
|
+
`);
|
|
16077
|
+
return 0;
|
|
16078
|
+
}
|
|
16079
|
+
|
|
16080
|
+
// src/main.ts
|
|
16081
|
+
function defaultDeps(startServer) {
|
|
16082
|
+
return {
|
|
16083
|
+
runLogin,
|
|
16084
|
+
runLogout,
|
|
16085
|
+
resolveApiKey,
|
|
16086
|
+
startServer,
|
|
16087
|
+
fail: (message, code) => void process.stderr.write(`${message}
|
|
16088
|
+
`, () => process.exit(code)),
|
|
16089
|
+
exit: (code) => process.exit(code)
|
|
16090
|
+
};
|
|
16091
|
+
}
|
|
16092
|
+
async function runCli(argv, deps) {
|
|
16093
|
+
const command = argv[2];
|
|
16094
|
+
if (command === "login")
|
|
16095
|
+
return deps.exit(await deps.runLogin());
|
|
16096
|
+
if (command === "logout")
|
|
16097
|
+
return deps.exit(await deps.runLogout());
|
|
16098
|
+
let config;
|
|
16099
|
+
try {
|
|
16100
|
+
const apiKey = await deps.resolveApiKey();
|
|
16101
|
+
config = readConfig({ ...process.env, ATTRKIT_API_KEY: apiKey });
|
|
16102
|
+
} catch (error) {
|
|
16103
|
+
deps.fail(error instanceof Error ? error.message : String(error), 1);
|
|
15840
16104
|
return;
|
|
15841
16105
|
}
|
|
16106
|
+
await deps.startServer(config);
|
|
16107
|
+
}
|
|
16108
|
+
|
|
16109
|
+
// src/cli.ts
|
|
16110
|
+
runCli(process.argv, defaultDeps(async (config) => {
|
|
15842
16111
|
const server = createServer(config);
|
|
15843
16112
|
const transport = new StdioServerTransport;
|
|
15844
16113
|
await server.connect(transport);
|
|
15845
16114
|
process.stderr.write(`attrkit-mcp connected (api=${config.apiUrl})
|
|
15846
16115
|
`);
|
|
15847
|
-
}
|
|
15848
|
-
main().catch((error) => {
|
|
16116
|
+
})).catch((error) => {
|
|
15849
16117
|
process.stderr.write(`attrkit-mcp failed to start: ${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
15850
16118
|
`, () => process.exit(1));
|
|
15851
16119
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attrkit/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "AttriKit's stdio MCP server for its public Management API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"attrkit",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepack": "bun run build",
|
|
35
35
|
"typecheck": "../../node_modules/typescript-7/bin/tsc --noEmit",
|
|
36
36
|
"lint": "eslint .",
|
|
37
|
-
"test": "cd ../.. && vitest run packages/mcp/src
|
|
37
|
+
"test": "cd ../.. && vitest run packages/mcp/src"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "1.30.0",
|