@husar.ai/cli 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mcp.ts CHANGED
@@ -7,7 +7,7 @@ import { ConfigMaker } from 'config-maker';
7
7
  import { parser } from './functions/parser.js';
8
8
  import { generateCms } from './functions/generate.js';
9
9
  import path from 'path';
10
- import { HusarConfigType } from '@/types/config.js';
10
+ import { HusarConfigType, getAdminToken } from '@/types/config.js';
11
11
  import {
12
12
  buildClientSchema,
13
13
  printSchema,
@@ -227,8 +227,9 @@ const createCmsParseFileHandler = ({ timeoutMs = 300_000 }: { timeoutMs?: number
227
227
  if (!raw.host) {
228
228
  throw new Error('Please implement host in husar.json');
229
229
  }
230
- if (!raw.adminToken) {
231
- throw new Error('Please implement adminToken in husar.json');
230
+ const adminToken = getAdminToken(raw);
231
+ if (!adminToken) {
232
+ throw new Error('Admin token not configured. Set HUSAR_ADMIN_TOKEN env var, or adminTokenEnv in husar.json');
232
233
  }
233
234
  const result = await withTimeout(
234
235
  parser({
@@ -237,7 +238,7 @@ const createCmsParseFileHandler = ({ timeoutMs = 300_000 }: { timeoutMs?: number
237
238
  opts: { type: (type ?? 'shape') as 'model' | 'shape', name },
238
239
  authentication: {
239
240
  HUSAR_MCP_HOST: raw.host,
240
- HUSAR_MCP_ADMIN_TOKEN: raw.adminToken,
241
+ HUSAR_MCP_ADMIN_TOKEN: adminToken,
241
242
  },
242
243
  }),
243
244
  timeoutMs,
@@ -333,9 +334,10 @@ export const startMcpServer = async () => {
333
334
  const cfg = await getConfig(workspaceRoot);
334
335
  const raw = cfg.get();
335
336
  const host = raw.host;
336
- const token = raw.adminToken;
337
+ const token = getAdminToken(raw);
337
338
  if (!host) throw new Error('Missing host in husar.json');
338
- if (!token) throw new Error('Missing adminToken in husar.json');
339
+ if (!token)
340
+ throw new Error('Admin token not configured. Set HUSAR_ADMIN_TOKEN env var, or adminTokenEnv in husar.json');
339
341
  const url = new URL(endpointPath || 'api/graphql', host).toString();
340
342
  const vars =
341
343
  typeof variables === 'string' && variables.trim().length ? JSON.parse(variables) : (variables ?? {});
@@ -375,9 +377,10 @@ export const startMcpServer = async () => {
375
377
  const cfg = await getConfig(workspaceRoot);
376
378
  const raw = cfg.get();
377
379
  const host = raw.host;
378
- const token = raw.adminToken;
380
+ const token = getAdminToken(raw);
379
381
  if (!host) throw new Error('Missing host in husar.json');
380
- if (!token) throw new Error('Missing adminToken in husar.json');
382
+ if (!token)
383
+ throw new Error('Admin token not configured. Set HUSAR_ADMIN_TOKEN env var, or adminTokenEnv in husar.json');
381
384
  const url = new URL(endpointPath || 'api/graphql', host).toString();
382
385
  const res = await fetch(url, {
383
386
  method: 'POST',
@@ -403,9 +406,10 @@ export const startMcpServer = async () => {
403
406
  const cfg = await getConfig(workspaceRoot);
404
407
  const raw = cfg.get();
405
408
  const host = raw.host;
406
- const token = raw.adminToken;
409
+ const token = getAdminToken(raw);
407
410
  if (!host) throw new Error('Missing host in husar.json');
408
- if (!token) throw new Error('Missing adminToken in husar.json');
411
+ if (!token)
412
+ throw new Error('Admin token not configured. Set HUSAR_ADMIN_TOKEN env var, or adminTokenEnv in husar.json');
409
413
  const url = new URL(endpointPath || 'api/graphql', host).toString();
410
414
  const res = await fetch(url, {
411
415
  method: 'POST',
@@ -603,9 +607,10 @@ export const startMcpServer = async () => {
603
607
  const cfg = await getConfig(workspaceRoot);
604
608
  const raw = cfg.get();
605
609
  const host = raw.host;
606
- const token = raw.adminToken;
610
+ const token = getAdminToken(raw);
607
611
  if (!host) throw new Error('Missing host in husar.json');
608
- if (!token) throw new Error('Missing adminToken in husar.json');
612
+ if (!token)
613
+ throw new Error('Admin token not configured. Set HUSAR_ADMIN_TOKEN env var, or adminTokenEnv in husar.json');
609
614
  const { query, variables: tmpl } = suggestQueryFromSDL({ sdl, task, rootType: 'Query' });
610
615
  const overrides =
611
616
  typeof variables === 'string' && variables.trim().length ? JSON.parse(variables) : (variables ?? {});
@@ -689,9 +694,10 @@ export const startMcpServer = async () => {
689
694
  const cfg = await getConfig(undefined);
690
695
  const raw = cfg.get();
691
696
  const host = raw.host;
692
- const token = raw.adminToken;
697
+ const token = getAdminToken(raw);
693
698
  if (!host) throw new Error('Missing host in husar.json');
694
- if (!token) throw new Error('Missing adminToken in husar.json');
699
+ if (!token)
700
+ throw new Error('Admin token not configured. Set HUSAR_ADMIN_TOKEN env var, or adminTokenEnv in husar.json');
695
701
  const url = new URL('api/graphql', host).toString();
696
702
  const input = { name, args };
697
703
  const res = await fetch(url, {
@@ -718,20 +724,34 @@ export const startMcpServer = async () => {
718
724
  },
719
725
  });
720
726
 
727
+ // Create a passthrough Zod schema that accepts any object properties.
728
+ // This allows the MCP SDK to properly pass arguments to handlers while
729
+ // letting the backend handle actual validation. We use z.object({}).passthrough()
730
+ // which accepts and preserves all properties.
731
+ const passthroughZodSchema = z.object({}).passthrough();
732
+
721
733
  for (const toolDef of ormTools) {
722
- // We pass raw JSON Schema parameters to MCP server; SDK will forward as-is.
723
- server.tool(toolDef.name, toolDef.description, toolDef.parameters, (async (args: unknown) => {
724
- try {
725
- const outcome = await toolDef.run(args);
726
- const payload =
727
- typeof outcome.result !== 'undefined' ? outcome.result : { ok: outcome.ok, error: outcome.error };
728
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
729
- } catch (err) {
730
- const message = err instanceof Error ? err.message : String(err);
731
- const stack = err instanceof Error && err.stack ? `\nStack: ${err.stack}` : '';
732
- return { isError: true, content: [{ type: 'text', text: `Error: ${message}${stack}` }] };
733
- }
734
- }) as any);
734
+ // Register ORM tools with the passthrough schema.
735
+ // The description includes the actual JSON Schema for AI context.
736
+ server.registerTool(
737
+ toolDef.name,
738
+ {
739
+ description: `${toolDef.description}\n\nExpected parameters: ${JSON.stringify(toolDef.parameters, null, 2)}`,
740
+ inputSchema: passthroughZodSchema,
741
+ },
742
+ (async (args: unknown, _extra: unknown) => {
743
+ try {
744
+ const outcome = await toolDef.run(args);
745
+ const payload =
746
+ typeof outcome.result !== 'undefined' ? outcome.result : { ok: outcome.ok, error: outcome.error };
747
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
748
+ } catch (err) {
749
+ const message = err instanceof Error ? err.message : String(err);
750
+ const stack = err instanceof Error && err.stack ? `\nStack: ${err.stack}` : '';
751
+ return { isError: true, content: [{ type: 'text', text: `Error: ${message}${stack}` }] };
752
+ }
753
+ }) as any,
754
+ );
735
755
  }
736
756
  } catch (e) {
737
757
  // In case tool spec import fails, keep MCP operational for other tools
@@ -3,5 +3,36 @@ export type HusarConfigType = {
3
3
  hostEnvironmentVariable: string;
4
4
  authenticationEnvironmentVariable: string;
5
5
  overrideHost: boolean;
6
- adminToken: string;
6
+ /** @deprecated Use adminTokenEnv instead for security. This field is kept for backward compatibility. */
7
+ adminToken?: string;
8
+ /** Environment variable name containing the admin token (recommended) */
9
+ adminTokenEnv?: string;
7
10
  };
11
+
12
+ /**
13
+ * Get the admin token from config, checking env vars first
14
+ * Priority:
15
+ * 1. HUSAR_MCP_ADMIN_TOKEN env var (for MCP override)
16
+ * 2. ENV var specified by adminTokenEnv in config
17
+ * 3. HUSAR_ADMIN_TOKEN env var (default)
18
+ * 4. adminToken field in config (deprecated, for backward compatibility)
19
+ */
20
+ export function getAdminToken(config: Partial<HusarConfigType>): string | undefined {
21
+ // 1. MCP-specific override
22
+ if (process.env.HUSAR_MCP_ADMIN_TOKEN) {
23
+ return process.env.HUSAR_MCP_ADMIN_TOKEN;
24
+ }
25
+
26
+ // 2. Config-specified env var
27
+ if (config.adminTokenEnv && process.env[config.adminTokenEnv]) {
28
+ return process.env[config.adminTokenEnv];
29
+ }
30
+
31
+ // 3. Default env var
32
+ if (process.env.HUSAR_ADMIN_TOKEN) {
33
+ return process.env.HUSAR_ADMIN_TOKEN;
34
+ }
35
+
36
+ // 4. Deprecated: direct token in config (backward compatibility)
37
+ return config.adminToken;
38
+ }