nxvet-mcp 0.1.0 → 0.3.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/README.md CHANGED
@@ -41,8 +41,17 @@ claude mcp add nxvet --transport http https://mcp.nx.vet/mcp \
41
41
  }
42
42
  ```
43
43
 
44
- > claude.ai custom connectors currently require OAuth or unauthenticated URLs; header-based
45
- > API-key auth works in Claude Code, Cursor, and the MCP Inspector. OAuth support is a future step.
44
+ claude.ai / Claude Desktop / mobile add a **custom connector** with URL `https://mcp.nx.vet/mcp`;
45
+ the OAuth flow opens a NxVET page where you paste your `nxvet_sk_` key once and approve.
46
+
47
+ ### OAuth design (stateless)
48
+
49
+ The server is its own minimal OAuth 2.1 authorization server (dynamic client registration + PKCE,
50
+ discovery at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`).
51
+ There is no token database: client ids, auth codes, and access/refresh tokens are AES-256-GCM-sealed
52
+ blobs (secret from `OAUTH_TOKEN_SECRET`, injected via Secrets Manager) that encrypt the approved API
53
+ key. Any instance can verify any token, and revoking the API key upstream instantly invalidates every
54
+ token minted from it. Raw `Authorization: Bearer nxvet_sk_...` headers keep working alongside OAuth.
46
55
 
47
56
  Claude Desktop (`claude_desktop_config.json`) or any stdio-only client — uses the npm package:
48
57
 
package/dist/dates.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Date ergonomics for LLM clients. The NxVET API speaks Unix epoch
3
+ * milliseconds; models are bad at epoch arithmetic, so tools accept ISO 8601
4
+ * inputs and every ms timestamp in responses gains an ISO sibling field.
5
+ */
6
+ /** Accept epoch ms (number) or an ISO 8601 date/datetime string; return epoch ms. */
7
+ export function parseTimeInput(value) {
8
+ if (typeof value === "number")
9
+ return value;
10
+ const trimmed = value.trim();
11
+ if (/^\d{11,}$/.test(trimmed))
12
+ return Number(trimmed); // epoch ms sent as a string
13
+ // Bare dates are treated as UTC midnight (JS already does this for YYYY-MM-DD).
14
+ const ms = Date.parse(trimmed);
15
+ if (Number.isNaN(ms)) {
16
+ throw new Error(`Could not parse time "${value}" — use epoch milliseconds or ISO 8601 (e.g. "2026-07-06" or "2026-07-06T14:00:00Z")`);
17
+ }
18
+ return ms;
19
+ }
20
+ const MS_KEY_PATTERN = /(Ms|Time|At|timestamp)$/;
21
+ // Epoch-ms plausibility window: 2001-09-09 .. 2286-11-20.
22
+ const MIN_MS = 1e12;
23
+ const MAX_MS = 1e13;
24
+ function isEpochMsField(key, value) {
25
+ return (typeof value === "number" &&
26
+ Number.isFinite(value) &&
27
+ value >= MIN_MS &&
28
+ value < MAX_MS &&
29
+ MS_KEY_PATTERN.test(key));
30
+ }
31
+ /**
32
+ * Recursively add `<field>Iso` siblings for every epoch-ms timestamp field,
33
+ * so clients never do calendar math on raw milliseconds.
34
+ */
35
+ export function enrichTimestamps(data) {
36
+ if (Array.isArray(data))
37
+ return data.map(enrichTimestamps);
38
+ if (data === null || typeof data !== "object")
39
+ return data;
40
+ const out = {};
41
+ for (const [key, value] of Object.entries(data)) {
42
+ out[key] = enrichTimestamps(value);
43
+ if (isEpochMsField(key, value)) {
44
+ out[`${key}Iso`] = new Date(value).toISOString();
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ //# sourceMappingURL=dates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.js","sourceRoot":"","sources":["../src/dates.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B;IACnF,gFAAgF;IAChF,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,yBAAyB,KAAK,sFAAsF,CACrH,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,cAAc,GAAG,yBAAyB,CAAC;AACjD,0DAA0D;AAC1D,MAAM,MAAM,GAAG,IAAI,CAAC;AACpB,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,SAAS,cAAc,CAAC,GAAW,EAAE,KAAc;IACjD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACtB,KAAK,IAAI,MAAM;QACf,KAAK,GAAG,MAAM;QACd,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CACzB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC3D,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3D,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;YAC/B,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
package/dist/format.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { NxVetApiError } from "./client.js";
2
+ import { enrichTimestamps } from "./dates.js";
2
3
  /** Cap tool output so a huge transcript list can't blow out the client context. */
3
4
  const MAX_RESULT_CHARS = 100_000;
4
5
  export function jsonResult(data, note) {
5
- let text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
6
+ let text = typeof data === "string" ? data : JSON.stringify(enrichTimestamps(data), null, 2);
6
7
  if (text.length > MAX_RESULT_CHARS) {
7
8
  text =
8
9
  text.slice(0, MAX_RESULT_CHARS) +
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,mFAAmF;AACnF,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEjC,MAAM,UAAU,UAAU,CAAC,IAAa,EAAE,IAAa;IACrD,IAAI,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3E,IAAI,IAAI,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACnC,IAAI;YACF,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC;gBAC/B,qBAAqB,gBAAgB,iGAAiG,CAAC;IAC3I,CAAC;IACD,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,GAAG,GAAG,IAAI,OAAO,IAAI,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,IAAY,CAAC;IACjB,IAAI,KAAK,YAAY,aAAa,EAAE,CAAC;QACnC,MAAM,KAAK,GAA2B;YACpC,GAAG,EAAE,oFAAoF;YACzF,GAAG,EAAE,2EAA2E;YAChF,GAAG,EAAE,oCAAoC;YACzC,GAAG,EAAE,yDAAyD;SAC/D,CAAC;QACF,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,IAAI,oBAAoB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACrF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,EAAiC;IACzD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,mFAAmF;AACnF,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEjC,MAAM,UAAU,UAAU,CAAC,IAAa,EAAE,IAAa;IACrD,IAAI,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7F,IAAI,IAAI,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACnC,IAAI;YACF,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC;gBAC/B,qBAAqB,gBAAgB,iGAAiG,CAAC;IAC3I,CAAC;IACD,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,GAAG,GAAG,IAAI,OAAO,IAAI,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,IAAY,CAAC;IACjB,IAAI,KAAK,YAAY,aAAa,EAAE,CAAC;QACnC,MAAM,KAAK,GAA2B;YACpC,GAAG,EAAE,oFAAoF;YACzF,GAAG,EAAE,2EAA2E;YAChF,GAAG,EAAE,oCAAoC;YACzC,GAAG,EAAE,yDAAyD;SAC/D,CAAC;QACF,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,IAAI,oBAAoB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACrF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,EAAiC;IACzD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -1,36 +1,84 @@
1
1
  import express from "express";
2
2
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
3
  import { buildServer, SERVER_NAME, SERVER_VERSION } from "./server.js";
4
+ import { oauthEnabled } from "./oauth/crypto.js";
5
+ import { buildOAuthRouter, wellKnownMetadata } from "./oauth/router.js";
6
+ import { isAccessToken, parseAccessToken } from "./oauth/tokens.js";
4
7
  const PORT = Number(process.env.PORT ?? 3000);
8
+ const PUBLIC_URL = (process.env.PUBLIC_URL ?? "https://mcp.nx.vet").replace(/\/+$/, "");
5
9
  const app = express();
6
10
  app.disable("x-powered-by");
7
11
  app.use(express.json({ limit: "1mb" }));
12
+ app.use(express.urlencoded({ extended: false, limit: "64kb" }));
13
+ // CORS so browser-based MCP clients (e.g. the Inspector) can connect directly.
14
+ app.use((req, res, next) => {
15
+ res.set("Access-Control-Allow-Origin", "*");
16
+ res.set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
17
+ res.set("Access-Control-Allow-Headers", "Authorization, Content-Type, Mcp-Protocol-Version, Mcp-Session-Id");
18
+ res.set("Access-Control-Expose-Headers", "Mcp-Session-Id, WWW-Authenticate");
19
+ if (req.method === "OPTIONS") {
20
+ res.sendStatus(204);
21
+ return;
22
+ }
23
+ next();
24
+ });
8
25
  app.get("/health", (_req, res) => {
9
- res.json({ status: "ok", name: SERVER_NAME, version: SERVER_VERSION });
26
+ res.json({ status: "ok", name: SERVER_NAME, version: SERVER_VERSION, oauth: oauthEnabled() });
10
27
  });
28
+ // OAuth discovery + endpoints (active only when OAUTH_TOKEN_SECRET is set).
29
+ if (oauthEnabled()) {
30
+ const meta = wellKnownMetadata(PUBLIC_URL);
31
+ for (const path of ["/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp"]) {
32
+ app.get(path, (_req, res) => {
33
+ res.json(meta.protectedResource);
34
+ });
35
+ }
36
+ app.get("/.well-known/oauth-authorization-server", (_req, res) => {
37
+ res.json(meta.authorizationServer);
38
+ });
39
+ app.use("/oauth", buildOAuthRouter());
40
+ }
41
+ function unauthorized(res, message, error) {
42
+ const challenge = [
43
+ 'Bearer realm="nxvet-mcp"',
44
+ ...(error ? [`error="${error}"`] : []),
45
+ ...(oauthEnabled()
46
+ ? [`resource_metadata="${PUBLIC_URL}/.well-known/oauth-protected-resource"`]
47
+ : []),
48
+ ].join(", ");
49
+ res.status(401).set("WWW-Authenticate", challenge).json({
50
+ jsonrpc: "2.0",
51
+ error: { code: -32001, message },
52
+ id: null,
53
+ });
54
+ }
55
+ /** Resolve the Authorization header to an NxVET API key (raw key or OAuth access token). */
56
+ function resolveApiKey(authHeader) {
57
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
58
+ return {
59
+ failure: "Missing credentials. Send 'Authorization: Bearer nxvet_sk_...' (create a key at https://app.nx.vet/integrations) or connect via OAuth.",
60
+ };
61
+ }
62
+ const credential = authHeader.slice("Bearer ".length).trim();
63
+ if (isAccessToken(credential)) {
64
+ const payload = parseAccessToken(credential);
65
+ if (!payload) {
66
+ return { failure: "OAuth access token is invalid or expired — refresh or re-authorize." };
67
+ }
68
+ return { apiKey: payload.k };
69
+ }
70
+ return { apiKey: credential };
71
+ }
11
72
  /**
12
- * Stateless Streamable HTTP MCP endpoint.
13
- * Every POST carries the full JSON-RPC message plus the caller's NxVET API key
14
- * as `Authorization: Bearer nxvet_sk_...`; a fresh server + transport pair is
73
+ * Stateless Streamable HTTP MCP endpoint. A fresh server + transport pair is
15
74
  * created per request, so any instance behind a load balancer can serve it.
16
75
  */
17
76
  app.post("/mcp", async (req, res) => {
18
- const auth = req.headers.authorization;
19
- if (!auth || !auth.startsWith("Bearer ")) {
20
- res
21
- .status(401)
22
- .set("WWW-Authenticate", 'Bearer realm="nxvet-mcp"')
23
- .json({
24
- jsonrpc: "2.0",
25
- error: {
26
- code: -32001,
27
- message: "Missing API key. Send 'Authorization: Bearer nxvet_sk_...' — create a key at https://app.nx.vet/integrations",
28
- },
29
- id: null,
30
- });
77
+ const { apiKey, failure } = resolveApiKey(req.headers.authorization);
78
+ if (!apiKey) {
79
+ unauthorized(res, failure ?? "Unauthorized", "invalid_token");
31
80
  return;
32
81
  }
33
- const apiKey = auth.slice("Bearer ".length).trim();
34
82
  const server = buildServer(apiKey);
35
83
  const transport = new StreamableHTTPServerTransport({
36
84
  sessionIdGenerator: undefined, // stateless mode
@@ -65,6 +113,6 @@ const methodNotAllowed = (_req, res) => {
65
113
  app.get("/mcp", methodNotAllowed);
66
114
  app.delete("/mcp", methodNotAllowed);
67
115
  app.listen(PORT, () => {
68
- console.log(`${SERVER_NAME} v${SERVER_VERSION} listening on :${PORT} (POST /mcp)`);
116
+ console.log(`${SERVER_NAME} v${SERVER_VERSION} listening on :${PORT} (POST /mcp, OAuth ${oauthEnabled() ? "enabled" : "disabled"})`);
69
117
  });
70
118
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Sealed-payload crypto for the stateless OAuth layer.
3
+ * Every OAuth artifact (client id, auth code, access/refresh token) is an
4
+ * AES-256-GCM-encrypted JSON blob — no database, any instance can mint or
5
+ * verify, and forgery is impossible without OAUTH_TOKEN_SECRET.
6
+ */
7
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
8
+ let cachedKey = null;
9
+ function key() {
10
+ if (cachedKey)
11
+ return cachedKey;
12
+ const secret = process.env.OAUTH_TOKEN_SECRET;
13
+ if (!secret || secret.length < 32) {
14
+ throw new Error("OAUTH_TOKEN_SECRET env var (>= 32 chars) is required for OAuth support");
15
+ }
16
+ cachedKey = createHash("sha256").update(secret).digest();
17
+ return cachedKey;
18
+ }
19
+ export function oauthEnabled() {
20
+ return (process.env.OAUTH_TOKEN_SECRET ?? "").length >= 32;
21
+ }
22
+ export function seal(payload) {
23
+ const iv = randomBytes(12);
24
+ const cipher = createCipheriv("aes-256-gcm", key(), iv);
25
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(payload), "utf8"), cipher.final()]);
26
+ return Buffer.concat([iv, cipher.getAuthTag(), ciphertext]).toString("base64url");
27
+ }
28
+ export function unseal(sealed) {
29
+ try {
30
+ const buf = Buffer.from(sealed, "base64url");
31
+ const decipher = createDecipheriv("aes-256-gcm", key(), buf.subarray(0, 12));
32
+ decipher.setAuthTag(buf.subarray(12, 28));
33
+ const plaintext = Buffer.concat([decipher.update(buf.subarray(28)), decipher.final()]);
34
+ return JSON.parse(plaintext.toString("utf8"));
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ export function sha256url(value) {
41
+ return createHash("sha256").update(value).digest("base64url");
42
+ }
43
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/oauth/crypto.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAExF,IAAI,SAAS,GAAkB,IAAI,CAAC;AAEpC,SAAS,GAAG;IACV,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC9C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;IAC5F,CAAC;IACD,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IACzD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,OAAe;IAClC,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnG,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,MAAM,CAAI,MAAc;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC7E,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAM,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,58 @@
1
+ /** Consent page for the paste-key OAuth flow. */
2
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3
+ export function renderConsentPage(params) {
4
+ const redirectHost = (() => {
5
+ try {
6
+ return new URL(params.redirectUri).hostname;
7
+ }
8
+ catch {
9
+ return "unknown";
10
+ }
11
+ })();
12
+ return `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
+ <title>Connect to NxVET</title>
18
+ <style>
19
+ body { font-family: 'Nunito', -apple-system, system-ui, sans-serif; background: #F3F4F6; margin: 0;
20
+ display: flex; align-items: center; justify-content: center; min-height: 100vh; }
21
+ .card { background: #fff; border-radius: 18px; box-shadow: 0 4px 24px rgba(0,0,0,.08);
22
+ padding: 2.5rem; max-width: 430px; width: calc(100% - 2rem); }
23
+ h1 { font-size: 1.4rem; margin: 0 0 .5rem; color: #111827; }
24
+ p { color: #4B5563; font-size: .95rem; line-height: 1.5; }
25
+ code { background: #F3F4F6; padding: .1rem .35rem; border-radius: 4px; font-size: .85em; }
26
+ label { display: block; font-weight: 700; color: #111827; margin: 1.25rem 0 .4rem; font-size: .9rem; }
27
+ input[type=password] { width: 100%; box-sizing: border-box; padding: .7rem .9rem; border: 1px solid #D1D5DB;
28
+ border-radius: 10px; font-size: .95rem; font-family: monospace; }
29
+ button { width: 100%; margin-top: 1.25rem; padding: .8rem; background: #7C3AED; color: #fff; border: none;
30
+ border-radius: 10px; font-size: 1rem; font-weight: 700; cursor: pointer; }
31
+ button:hover { background: #6D28D9; }
32
+ .error { background: #FEF2F2; border: 1px solid #FECACA; color: #B91C1C; padding: .7rem .9rem;
33
+ border-radius: 10px; font-size: .9rem; margin-top: 1rem; }
34
+ .hint { font-size: .82rem; color: #6B7280; margin-top: 1rem; }
35
+ .client { background: #F5F3FF; border-radius: 10px; padding: .7rem .9rem; font-size: .9rem; color: #4B5563; }
36
+ </style>
37
+ </head>
38
+ <body>
39
+ <div class="card">
40
+ <h1>Connect to NxVET</h1>
41
+ <div class="client"><strong>${escapeHtml(redirectHost)}</strong> is requesting access to your NxVET data.</div>
42
+ <p>Paste an NxVET API key to authorize. The key determines which organization's data the connection can access, with exactly the same permissions as the key itself.</p>
43
+ ${params.error ? `<div class="error">${escapeHtml(params.error)}</div>` : ""}
44
+ <form method="POST" action="/oauth/authorize">
45
+ <input type="hidden" name="client_id" value="${escapeHtml(params.clientId)}">
46
+ <input type="hidden" name="redirect_uri" value="${escapeHtml(params.redirectUri)}">
47
+ <input type="hidden" name="state" value="${escapeHtml(params.state)}">
48
+ <input type="hidden" name="code_challenge" value="${escapeHtml(params.codeChallenge)}">
49
+ <label for="api_key">NxVET API key</label>
50
+ <input type="password" id="api_key" name="api_key" placeholder="nxvet_sk_..." autocomplete="off" required>
51
+ <button type="submit">Verify &amp; Connect</button>
52
+ </form>
53
+ <p class="hint">Create or revoke keys at <a href="https://app.nx.vet/integrations" target="_blank" rel="noopener">app.nx.vet/integrations</a> &rarr; API Keys. Revoking the key instantly disconnects this integration. The key is never stored by the MCP server.</p>
54
+ </div>
55
+ </body>
56
+ </html>`;
57
+ }
58
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/oauth/page.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,EAAE,CAC/B,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAU/F,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE;QACzB,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCA6BuB,UAAU,CAAC,YAAY,CAAC;;IAEpD,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,sBAAsB,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;;mDAE3B,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;sDACxB,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC;+CACrC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC;wDACf,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC;;;;;;;;QAQhF,CAAC;AACT,CAAC"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Minimal OAuth 2.1 authorization server for claude.ai and other MCP clients
3
+ * that require OAuth. Fully stateless: client ids, auth codes, and tokens are
4
+ * sealed blobs (see tokens.ts). The "login" step is pasting an NxVET API key,
5
+ * which is validated live against the NxVET API and sealed into the tokens.
6
+ */
7
+ import { Router } from "express";
8
+ import { sha256url } from "./crypto.js";
9
+ import { renderConsentPage } from "./page.js";
10
+ import { ACCESS_TOKEN_TTL_S, mintAccessToken, mintAuthCode, mintClientId, mintRefreshToken, parseAuthCode, parseClientId, parseRefreshToken, } from "./tokens.js";
11
+ const NXVET_API_BASE = process.env.NXVET_API_BASE ?? "https://app.nx.vet";
12
+ function isAcceptableRedirectUri(uri) {
13
+ try {
14
+ const url = new URL(uri);
15
+ if (url.hash)
16
+ return false;
17
+ if (url.protocol === "https:")
18
+ return true;
19
+ // Loopback redirects are allowed for local development clients.
20
+ return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ async function validateApiKey(apiKey) {
27
+ if (!apiKey.startsWith("nxvet_sk_"))
28
+ return { ok: false };
29
+ try {
30
+ const res = await fetch(`${NXVET_API_BASE}/api/auth/me`, {
31
+ headers: { Authorization: `Bearer ${apiKey}` },
32
+ });
33
+ if (!res.ok)
34
+ return { ok: false };
35
+ const body = (await res.json());
36
+ return { ok: true, orgName: body.organizationName };
37
+ }
38
+ catch {
39
+ return { ok: false };
40
+ }
41
+ }
42
+ function oauthError(res, status, error, description) {
43
+ res.status(status).json({ error, error_description: description });
44
+ }
45
+ export function wellKnownMetadata(publicUrl) {
46
+ return {
47
+ protectedResource: {
48
+ resource: `${publicUrl}/mcp`,
49
+ authorization_servers: [publicUrl],
50
+ bearer_methods_supported: ["header"],
51
+ resource_name: "NxVET MCP Server",
52
+ resource_documentation: "https://api.nx.vet/mcp.html",
53
+ },
54
+ authorizationServer: {
55
+ issuer: publicUrl,
56
+ authorization_endpoint: `${publicUrl}/oauth/authorize`,
57
+ token_endpoint: `${publicUrl}/oauth/token`,
58
+ registration_endpoint: `${publicUrl}/oauth/register`,
59
+ response_types_supported: ["code"],
60
+ grant_types_supported: ["authorization_code", "refresh_token"],
61
+ code_challenge_methods_supported: ["S256"],
62
+ token_endpoint_auth_methods_supported: ["none"],
63
+ scopes_supported: ["nxvet"],
64
+ service_documentation: "https://api.nx.vet/mcp.html",
65
+ },
66
+ };
67
+ }
68
+ export function buildOAuthRouter() {
69
+ const router = Router();
70
+ // RFC 7591 dynamic client registration (public clients, PKCE-only).
71
+ router.post("/register", (req, res) => {
72
+ const redirectUris = req.body?.redirect_uris;
73
+ if (!Array.isArray(redirectUris) ||
74
+ redirectUris.length === 0 ||
75
+ !redirectUris.every((u) => typeof u === "string" && isAcceptableRedirectUri(u))) {
76
+ oauthError(res, 400, "invalid_redirect_uri", "redirect_uris must be HTTPS (or loopback) URLs without fragments");
77
+ return;
78
+ }
79
+ const clientId = mintClientId({ ru: redirectUris });
80
+ res.status(201).json({
81
+ client_id: clientId,
82
+ redirect_uris: redirectUris,
83
+ token_endpoint_auth_method: "none",
84
+ grant_types: ["authorization_code", "refresh_token"],
85
+ response_types: ["code"],
86
+ client_name: typeof req.body?.client_name === "string" ? req.body.client_name : undefined,
87
+ });
88
+ });
89
+ // Authorization endpoint: GET renders the paste-key consent page.
90
+ router.get("/authorize", (req, res) => {
91
+ const q = (name) => (typeof req.query[name] === "string" ? req.query[name] : "");
92
+ const clientId = q("client_id");
93
+ const redirectUri = q("redirect_uri");
94
+ const client = parseClientId(clientId);
95
+ if (!client || !client.ru.includes(redirectUri)) {
96
+ res.status(400).send("Invalid client_id or unregistered redirect_uri.");
97
+ return;
98
+ }
99
+ if (q("response_type") !== "code") {
100
+ res.redirect(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}error=unsupported_response_type&state=${encodeURIComponent(q("state"))}`);
101
+ return;
102
+ }
103
+ if (!q("code_challenge") || (q("code_challenge_method") || "S256") !== "S256") {
104
+ res.redirect(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}error=invalid_request&error_description=PKCE+S256+required&state=${encodeURIComponent(q("state"))}`);
105
+ return;
106
+ }
107
+ res
108
+ .type("html")
109
+ .send(renderConsentPage({
110
+ clientId,
111
+ redirectUri,
112
+ state: q("state"),
113
+ codeChallenge: q("code_challenge"),
114
+ }));
115
+ });
116
+ // Consent form submission: validate the pasted key, issue the auth code.
117
+ router.post("/authorize", async (req, res) => {
118
+ const f = (name) => (typeof req.body?.[name] === "string" ? req.body[name] : "");
119
+ const clientId = f("client_id");
120
+ const redirectUri = f("redirect_uri");
121
+ const client = parseClientId(clientId);
122
+ if (!client || !client.ru.includes(redirectUri) || !f("code_challenge")) {
123
+ res.status(400).send("Invalid or expired authorization request. Restart the connection from your MCP client.");
124
+ return;
125
+ }
126
+ const apiKey = f("api_key").trim();
127
+ const validation = await validateApiKey(apiKey);
128
+ if (!validation.ok) {
129
+ res.status(401).type("html").send(renderConsentPage({
130
+ clientId,
131
+ redirectUri,
132
+ state: f("state"),
133
+ codeChallenge: f("code_challenge"),
134
+ error: "That API key was rejected by NxVET — check it is a valid, unrevoked nxvet_sk_ key.",
135
+ }));
136
+ return;
137
+ }
138
+ const code = mintAuthCode({
139
+ k: apiKey,
140
+ ch: f("code_challenge"),
141
+ ru: redirectUri,
142
+ ci: sha256url(clientId),
143
+ });
144
+ const url = new URL(redirectUri);
145
+ url.searchParams.set("code", code);
146
+ if (f("state"))
147
+ url.searchParams.set("state", f("state"));
148
+ res.redirect(url.toString());
149
+ });
150
+ // Token endpoint: authorization_code (with PKCE) and refresh_token grants.
151
+ router.post("/token", (req, res) => {
152
+ const f = (name) => (typeof req.body?.[name] === "string" ? req.body[name] : "");
153
+ const grantType = f("grant_type");
154
+ if (grantType === "authorization_code") {
155
+ const code = parseAuthCode(f("code"));
156
+ if (!code) {
157
+ oauthError(res, 400, "invalid_grant", "Authorization code is invalid or expired");
158
+ return;
159
+ }
160
+ if (code.ci !== sha256url(f("client_id")) || code.ru !== f("redirect_uri")) {
161
+ oauthError(res, 400, "invalid_grant", "client_id or redirect_uri mismatch");
162
+ return;
163
+ }
164
+ if (!f("code_verifier") || sha256url(f("code_verifier")) !== code.ch) {
165
+ oauthError(res, 400, "invalid_grant", "PKCE verification failed");
166
+ return;
167
+ }
168
+ res.json({
169
+ access_token: mintAccessToken({ k: code.k }),
170
+ token_type: "Bearer",
171
+ expires_in: ACCESS_TOKEN_TTL_S,
172
+ refresh_token: mintRefreshToken({ k: code.k, ci: code.ci }),
173
+ scope: "nxvet",
174
+ });
175
+ return;
176
+ }
177
+ if (grantType === "refresh_token") {
178
+ const refresh = parseRefreshToken(f("refresh_token"));
179
+ if (!refresh || refresh.ci !== sha256url(f("client_id"))) {
180
+ oauthError(res, 400, "invalid_grant", "Refresh token is invalid or expired");
181
+ return;
182
+ }
183
+ res.json({
184
+ access_token: mintAccessToken({ k: refresh.k }),
185
+ token_type: "Bearer",
186
+ expires_in: ACCESS_TOKEN_TTL_S,
187
+ refresh_token: mintRefreshToken({ k: refresh.k, ci: refresh.ci }),
188
+ scope: "nxvet",
189
+ });
190
+ return;
191
+ }
192
+ oauthError(res, 400, "unsupported_grant_type", "Use authorization_code or refresh_token");
193
+ });
194
+ return router;
195
+ }
196
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/oauth/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Stateless OAuth artifacts. Each is a prefixed sealed blob with a type tag
3
+ * and expiry baked in. Revoking the underlying NxVET API key invalidates
4
+ * every token minted from it (the upstream API rejects the key).
5
+ */
6
+ import { seal, unseal } from "./crypto.js";
7
+ export const ACCESS_TOKEN_TTL_S = 3600; // 1 hour
8
+ const AUTH_CODE_TTL_S = 60;
9
+ const REFRESH_TOKEN_TTL_S = 30 * 24 * 3600; // 30 days
10
+ const PREFIXES = {
11
+ client: "nxv_c_",
12
+ code: "nxv_ac_",
13
+ access: "nxv_at_",
14
+ refresh: "nxv_rt_",
15
+ };
16
+ const now = () => Math.floor(Date.now() / 1000);
17
+ function mint(type, payload, ttlSeconds) {
18
+ const envelope = { t: type, exp: ttlSeconds > 0 ? now() + ttlSeconds : 0, p: payload };
19
+ return PREFIXES[type] + seal(envelope);
20
+ }
21
+ function parse(type, token) {
22
+ const prefix = PREFIXES[type];
23
+ if (!token.startsWith(prefix))
24
+ return null;
25
+ const envelope = unseal(token.slice(prefix.length));
26
+ if (!envelope || envelope.t !== type)
27
+ return null;
28
+ if (envelope.exp !== 0 && envelope.exp < now())
29
+ return null;
30
+ return envelope.p;
31
+ }
32
+ export const mintClientId = (p) => mint("client", p, 0);
33
+ export const parseClientId = (id) => parse("client", id);
34
+ export const mintAuthCode = (p) => mint("code", p, AUTH_CODE_TTL_S);
35
+ export const parseAuthCode = (code) => parse("code", code);
36
+ export const mintAccessToken = (p) => mint("access", p, ACCESS_TOKEN_TTL_S);
37
+ export const parseAccessToken = (t) => parse("access", t);
38
+ export const mintRefreshToken = (p) => mint("refresh", p, REFRESH_TOKEN_TTL_S);
39
+ export const parseRefreshToken = (t) => parse("refresh", t);
40
+ export const isAccessToken = (t) => t.startsWith(PREFIXES.access);
41
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/oauth/tokens.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import { registerConversationTools } from "./tools/conversations.js";
6
6
  import { registerWebhookTools } from "./tools/webhooks.js";
7
7
  import { registerApiKeyTools } from "./tools/apiKeys.js";
8
8
  export const SERVER_NAME = "nxvet-mcp";
9
- export const SERVER_VERSION = "0.1.0";
9
+ export const SERVER_VERSION = "0.3.0";
10
10
  /**
11
11
  * Build a fresh MCP server bound to one caller's API key.
12
12
  * The server is stateless — one instance is created per HTTP request and
@@ -17,7 +17,10 @@ export function buildServer(apiKey) {
17
17
  instructions: "Tools for the NxVET veterinary platform API (app.nx.vet): consultation labels with transcripts and " +
18
18
  "SOAP notes, NxHub ambient-recording conversations and device timelines, webhook management, and API keys. " +
19
19
  "Start with get_identity to discover the caller's organizationId. " +
20
- "All *Ms timestamps are Unix epoch milliseconds; label metadata and SOAP content are JSON strings that need parsing.",
20
+ `The current UTC time is ${new Date().toISOString()} use it for "today"/"yesterday" style questions instead of guessing. ` +
21
+ "Time-range tool inputs accept ISO 8601 strings directly (no epoch math needed), and every epoch-ms timestamp " +
22
+ "in responses has an ...Iso sibling field with the ISO 8601 equivalent — always read the Iso fields. " +
23
+ "Label metadata and SOAP content are JSON strings that need parsing.",
21
24
  });
22
25
  const client = new NxVetApiClient(apiKey);
23
26
  registerIdentityTools(server, client);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC;AACvC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,EAC9C;QACE,YAAY,EACV,qGAAqG;YACrG,4GAA4G;YAC5G,mEAAmE;YACnE,qHAAqH;KACxH,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1C,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpC,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC;AACvC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,EAC9C;QACE,YAAY,EACV,qGAAqG;YACrG,4GAA4G;YAC5G,mEAAmE;YACnE,2BAA2B,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,yEAAyE;YAC5H,+GAA+G;YAC/G,sGAAsG;YACtG,qEAAqE;KACxE,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1C,qBAAqB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { jsonResult, run } from "../format.js";
3
+ import { parseTimeInput } from "../dates.js";
3
4
  // Device and conversation IDs are interpolated into URL paths — restrict them
4
5
  // to safe characters (defense-in-depth alongside encodeURIComponent below).
5
6
  const SAFE_ID = /^[A-Za-z0-9._~-]+$/;
@@ -12,6 +13,9 @@ const conversationIdSchema = z
12
13
  .regex(SAFE_ID, "conversationId may only contain letters, digits, and . _ ~ -")
13
14
  .describe("Conversation ID");
14
15
  const enc = encodeURIComponent;
16
+ const timeSchema = z
17
+ .union([z.number().int(), z.string().min(4)])
18
+ .describe('Epoch milliseconds OR ISO 8601, e.g. "2026-07-06" (UTC midnight) or "2026-07-06T14:00:00Z"');
15
19
  export function registerConversationTools(server, client) {
16
20
  server.registerTool("list_conversations", {
17
21
  title: "List NxHub conversations",
@@ -44,12 +48,12 @@ export function registerConversationTools(server, client) {
44
48
  "Time range must not exceed 6 hours. startMs/endMs are Unix epoch milliseconds.",
45
49
  inputSchema: {
46
50
  deviceId: deviceIdSchema,
47
- startMs: z.number().int().describe("Range start, epoch milliseconds"),
48
- endMs: z.number().int().describe("Range end, epoch milliseconds (max 6h after startMs)"),
51
+ startMs: timeSchema.describe("Range start (epoch ms or ISO 8601)"),
52
+ endMs: timeSchema.describe("Range end (epoch ms or ISO 8601, max 6h after start)"),
49
53
  },
50
54
  }, async ({ deviceId, startMs, endMs }) => run(async () => jsonResult((await client.get(`/api/nxhub/conversations/timeline/${enc(deviceId)}`, {
51
- startMs,
52
- endMs,
55
+ startMs: parseTimeInput(startMs),
56
+ endMs: parseTimeInput(endMs),
53
57
  })).body)));
54
58
  server.registerTool("preview_conversation", {
55
59
  title: "Preview conversation creation",
@@ -57,10 +61,14 @@ export function registerConversationTools(server, client) {
57
61
  "Time range must not exceed 90 minutes.",
58
62
  inputSchema: {
59
63
  deviceId: deviceIdSchema,
60
- startMs: z.number().int().describe("Range start, epoch milliseconds"),
61
- endMs: z.number().int().describe("Range end, epoch milliseconds (max 90 min after startMs)"),
64
+ startMs: timeSchema.describe("Range start (epoch ms or ISO 8601)"),
65
+ endMs: timeSchema.describe("Range end (epoch ms or ISO 8601, max 90 min after start)"),
62
66
  },
63
- }, async (args) => run(async () => jsonResult((await client.get("/api/nxhub/conversations/create/preview", args)).body)));
67
+ }, async ({ deviceId, startMs, endMs }) => run(async () => jsonResult((await client.get("/api/nxhub/conversations/create/preview", {
68
+ deviceId,
69
+ startMs: parseTimeInput(startMs),
70
+ endMs: parseTimeInput(endMs),
71
+ })).body)));
64
72
  server.registerTool("create_conversation", {
65
73
  title: "Create conversation from time range",
66
74
  description: "Create a new NxHub conversation from a device time range (max 90 minutes). " +
@@ -68,10 +76,12 @@ export function registerConversationTools(server, client) {
68
76
  "Creation queues transcript processing; the conversation moves through COMPLETED → PROCESSING → UPLOADED.",
69
77
  inputSchema: {
70
78
  deviceId: deviceIdSchema,
71
- startMs: z.number().int().describe("Range start, epoch milliseconds"),
72
- endMs: z.number().int().describe("Range end, epoch milliseconds (max 90 min after startMs)"),
79
+ startMs: timeSchema.describe("Range start (epoch ms or ISO 8601)"),
80
+ endMs: timeSchema.describe("Range end (epoch ms or ISO 8601, max 90 min after start)"),
73
81
  },
74
- }, async (args) => run(async () => jsonResult((await client.post("/api/nxhub/conversations/create", { body: args })).body)));
82
+ }, async ({ deviceId, startMs, endMs }) => run(async () => jsonResult((await client.post("/api/nxhub/conversations/create", {
83
+ body: { deviceId, startMs: parseTimeInput(startMs), endMs: parseTimeInput(endMs) },
84
+ })).body)));
75
85
  server.registerTool("complete_conversation", {
76
86
  title: "Force-complete active conversation",
77
87
  description: "Mark an ACTIVE NxHub conversation as completed, triggering transcript processing immediately " +
@@ -1 +1 @@
1
- {"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../src/tools/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAE/C,8EAA8E;AAC9E,4EAA4E;AAC5E,MAAM,OAAO,GAAG,oBAAoB,CAAC;AACrC,MAAM,cAAc,GAAG,CAAC;KACrB,MAAM,EAAE;KACR,KAAK,CAAC,OAAO,EAAE,wDAAwD,CAAC;KACxE,QAAQ,CAAC,yCAAyC,CAAC,CAAC;AACvD,MAAM,oBAAoB,GAAG,CAAC;KAC3B,MAAM,EAAE;KACR,KAAK,CAAC,OAAO,EAAE,8DAA8D,CAAC;KAC9E,QAAQ,CAAC,iBAAiB,CAAC,CAAC;AAC/B,MAAM,GAAG,GAAG,kBAAkB,CAAC;AAE/B,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,MAAsB;IACjF,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,2EAA2E;YAC3E,8FAA8F;YAC9F,qFAAqF;YACrF,sGAAsG;QACxG,WAAW,EAAE;YACX,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;YAC3F,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YACnE,MAAM,EAAE,CAAC;iBACN,IAAI,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;iBAC7E,QAAQ,EAAE;iBACV,QAAQ,CAAC,+BAA+B,CAAC;YAC5C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAClF;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CACb,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CACzF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,gCAAgC;QACvC,WAAW,EACT,0HAA0H;QAC5H,WAAW,EAAE;YACX,QAAQ,EAAE,cAAc;YACxB,cAAc,EAAE,oBAAoB;SACrC;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,CACrC,GAAG,CAAC,KAAK,IAAI,EAAE,CACb,UAAU,CACR,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,4BAA4B,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAC5F,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,gHAAgH;YAChH,gFAAgF;QAClF,WAAW,EAAE;YACX,QAAQ,EAAE,cAAc;YACxB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YACrE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,sDAAsD,CAAC;SACzF;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CACrC,GAAG,CAAC,KAAK,IAAI,EAAE,CACb,UAAU,CACR,CACE,MAAM,MAAM,CAAC,GAAG,CAAC,qCAAqC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE;QACrE,OAAO;QACP,KAAK;KACN,CAAC,CACH,CAAC,IAAI,CACP,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,+BAA+B;QACtC,WAAW,EACT,uGAAuG;YACvG,wCAAwC;QAC1C,WAAW,EAAE;YACX,QAAQ,EAAE,cAAc;YACxB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YACrE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;SAC7F;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CACb,GAAG,CAAC,KAAK,IAAI,EAAE,CACb,UAAU,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,yCAAyC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CACrF,CACJ,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,qCAAqC;QAC5C,WAAW,EACT,6EAA6E;YAC7E,qEAAqE;YACrE,0GAA0G;QAC5G,WAAW,EAAE;YACX,QAAQ,EAAE,cAAc;YACxB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YACrE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;SAC7F;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CACb,GAAG,CAAC,KAAK,IAAI,EAAE,CACb,UAAU,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CACxF,CACJ,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,oCAAoC;QAC3C,WAAW,EACT,+FAA+F;YAC/F,gEAAgE;QAClE,WAAW,EAAE;YACX,QAAQ,EAAE,cAAc;YACxB,cAAc,EAAE,oBAAoB;SACrC;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,CACrC,GAAG,CAAC,KAAK,IAAI,EAAE,CACb,UAAU,CACR,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,4BAA4B,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;SAC7F,IAAI,CACR,CACF,CACJ,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../src/tools/conversations.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nxvet-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for the NxVET API (labels, transcripts, NxHub conversations, webhooks, API keys) — hosted at https://mcp.nx.vet/mcp or run locally via npx",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",