@stratta/mcp 0.1.0 → 0.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/README.md CHANGED
@@ -15,27 +15,46 @@ MCP server exposing Swiss engineering norms (SIA / Eurocodes) to Claude clients
15
15
 
16
16
  ### Claude Code (recommended)
17
17
 
18
- Export your key once, then register the server at **user** scope (so the key
19
- never lands in a committable project `.mcp.json`):
18
+ Add the server — no key needed up front:
20
19
 
21
20
  ```bash
22
- export STRATTA_API_KEY=sk_strt_xxx # or set it in your shell profile
23
- claude mcp add stratta --scope user --command "npx -y @stratta/mcp" --env STRATTA_API_KEY="$STRATTA_API_KEY"
21
+ claude mcp add stratta -- npx -y @stratta/mcp
22
+ ```
23
+
24
+ On the first call, Claude Code prompts you to paste your Stratta API key. It's
25
+ validated and saved to `~/.stratta/config.json` (owner-only, `0600`), so you
26
+ only do this once.
27
+
28
+ Prefer to set it up ahead of time? Log in via the CLI:
29
+
30
+ ```bash
31
+ npx -y @stratta/mcp login
32
+ ```
33
+
34
+ …or pass the key explicitly as an environment variable (it then takes
35
+ precedence over the saved key):
36
+
37
+ ```bash
38
+ claude mcp add stratta --scope user --env STRATTA_API_KEY=sk_strt_xxx -- npx -y @stratta/mcp
24
39
  ```
25
40
 
26
41
  ### Claude Desktop
27
42
 
28
- Edit your `claude_desktop_config.json` (Settings → Developer → Edit Config):
43
+ Save your key once via the CLI:
44
+
45
+ ```bash
46
+ npx -y @stratta/mcp login
47
+ ```
48
+
49
+ Then add the server to `claude_desktop_config.json` (Settings → Developer →
50
+ Edit Config) — no key needed in the file:
29
51
 
30
52
  ```json
31
53
  {
32
54
  "mcpServers": {
33
55
  "stratta": {
34
56
  "command": "npx",
35
- "args": ["-y", "@stratta/mcp"],
36
- "env": {
37
- "STRATTA_API_KEY": "sk_strt_xxx"
38
- }
57
+ "args": ["-y", "@stratta/mcp"]
39
58
  }
40
59
  }
41
60
  }
@@ -43,8 +62,9 @@ Edit your `claude_desktop_config.json` (Settings → Developer → Edit Config):
43
62
 
44
63
  Restart Claude Desktop. The Stratta tools should appear in the MCP indicator.
45
64
 
46
- > `claude_desktop_config.json` stores the key in plaintext on your machine —
47
- > keep the file private and out of any synced/backed-up repo.
65
+ > Prefer to keep the key in the config instead of `~/.stratta/config.json`? Add
66
+ > an `"env": { "STRATTA_API_KEY": "sk_strt_xxx" }` block to the server entry —
67
+ > but that file then stores the key in plaintext, so keep it private and unsynced.
48
68
 
49
69
  ### Global install (optional)
50
70
 
@@ -56,11 +76,13 @@ Then use `"command": "stratta-mcp"` instead of `npx`.
56
76
 
57
77
  ## Configuration
58
78
 
59
- The server reads its config from environment variables. See [`.env.example`](./.env.example) for a template.
79
+ The API key is resolved in this order: the `STRATTA_API_KEY` env var, then
80
+ `~/.stratta/config.json` (written by `login` or the first-run prompt). Other
81
+ settings come from environment variables — see [`.env.example`](./.env.example).
60
82
 
61
83
  | Variable | Required | Default | Purpose |
62
84
  |---|---|---|---|
63
- | `STRATTA_API_KEY` | yes | – | API key from https://stratta.ch/api-keys. |
85
+ | `STRATTA_API_KEY` | no¹ | – | API key from https://stratta.ch. ¹If unset, the server falls back to `~/.stratta/config.json`, or prompts you on first use (clients that support elicitation). |
64
86
  | `STRATTA_CONVEX_URL` | no | Stratta prod backend | Override only if you self-host. |
65
87
 
66
88
  ## Tools exposed
package/dist/auth.d.ts CHANGED
@@ -4,4 +4,6 @@ export type AuthContext = {
4
4
  apiKeyId: string;
5
5
  organizationId: string | null;
6
6
  };
7
- export declare function ensureAuthenticated(client: ConvexHttpClient): Promise<AuthContext>;
7
+ export declare function resolveApiKey(): string | null;
8
+ export declare function requireApiKey(): string;
9
+ export declare function ensureAuthenticated(client: ConvexHttpClient, plaintext: string): Promise<AuthContext>;
package/dist/auth.js CHANGED
@@ -1,12 +1,25 @@
1
1
  import { api } from './client.js';
2
+ import { readStoredApiKey } from './config.js';
2
3
  const CACHE_TTL_MS = 60 * 60 * 1000; // 1h
3
4
  let state = { kind: 'unvalidated' };
4
- export async function ensureAuthenticated(client) {
5
- const plaintext = process.env.STRATTA_API_KEY;
6
- if (!plaintext) {
7
- throw new Error('STRATTA_API_KEY environment variable is not set. Generate a key at https://stratta.ch/api-keys');
5
+ // Resolve the API key from the environment first (explicit, ephemeral), then
6
+ // fall back to the persisted config file (written by `login` or elicitation).
7
+ export function resolveApiKey() {
8
+ const fromEnv = process.env.STRATTA_API_KEY?.trim();
9
+ if (fromEnv)
10
+ return fromEnv;
11
+ return readStoredApiKey();
12
+ }
13
+ export function requireApiKey() {
14
+ const key = resolveApiKey();
15
+ if (!key) {
16
+ throw new Error('No Stratta API key found. Set STRATTA_API_KEY or run: npx @stratta/mcp login');
8
17
  }
18
+ return key;
19
+ }
20
+ export async function ensureAuthenticated(client, plaintext) {
9
21
  if (state.kind === 'valid' &&
22
+ state.plaintext === plaintext &&
10
23
  Date.now() - state.validatedAt < CACHE_TTL_MS) {
11
24
  return {
12
25
  userId: state.userId,
@@ -14,7 +27,7 @@ export async function ensureAuthenticated(client) {
14
27
  organizationId: state.organizationId,
15
28
  };
16
29
  }
17
- if (state.kind === 'invalid') {
30
+ if (state.kind === 'invalid' && state.plaintext === plaintext) {
18
31
  throw new Error(state.error);
19
32
  }
20
33
  const result = (await client.action(api.apiKeys.validateApiKey, {
@@ -23,12 +36,14 @@ export async function ensureAuthenticated(client) {
23
36
  if (!result.valid || !result.userId || !result.apiKeyId) {
24
37
  state = {
25
38
  kind: 'invalid',
39
+ plaintext,
26
40
  error: 'Invalid STRATTA_API_KEY. Generate a new one at https://stratta.ch/api-keys',
27
41
  };
28
42
  throw new Error(state.error);
29
43
  }
30
44
  state = {
31
45
  kind: 'valid',
46
+ plaintext,
32
47
  userId: result.userId,
33
48
  apiKeyId: result.apiKeyId,
34
49
  organizationId: result.organizationId,
@@ -0,0 +1,3 @@
1
+ export declare const CONFIG_PATH: string;
2
+ export declare function readStoredApiKey(path?: string): string | null;
3
+ export declare function writeStoredApiKey(apiKey: string, path?: string): void;
package/dist/config.js ADDED
@@ -0,0 +1,23 @@
1
+ import { homedir } from 'node:os';
2
+ import { dirname, join } from 'node:path';
3
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ // The API key persists here so a user only enters it once. chmod 0600 on the
5
+ // file (best-effort on Windows) keeps it readable by the owner only. This is
6
+ // the same pattern used by gh / aws / auth0 CLIs.
7
+ export const CONFIG_PATH = join(homedir(), '.stratta', 'config.json');
8
+ export function readStoredApiKey(path = CONFIG_PATH) {
9
+ try {
10
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
11
+ const key = parsed.apiKey?.trim();
12
+ return key && key.length > 0 ? key : null;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function writeStoredApiKey(apiKey, path = CONFIG_PATH) {
19
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
20
+ writeFileSync(path, JSON.stringify({ apiKey }, null, 2) + '\n', {
21
+ mode: 0o600,
22
+ });
23
+ }
@@ -0,0 +1,3 @@
1
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import type { ConvexHttpClient } from 'convex/browser';
3
+ export declare function elicitApiKey(server: Server, client: ConvexHttpClient): Promise<string>;
package/dist/elicit.js ADDED
@@ -0,0 +1,42 @@
1
+ import { api } from './client.js';
2
+ import { writeStoredApiKey } from './config.js';
3
+ // Ask the user for their API key through the MCP client (an interactive
4
+ // dialog shown by Claude), validate it, and persist it so they only do this
5
+ // once. The key travels over the client<->user channel, not through the LLM.
6
+ export async function elicitApiKey(server, client) {
7
+ const caps = server.getClientCapabilities();
8
+ if (!caps?.elicitation) {
9
+ throw new Error('No Stratta API key found, and this MCP client cannot prompt for one. ' +
10
+ 'Set STRATTA_API_KEY in the server env, or run: npx @stratta/mcp login');
11
+ }
12
+ const result = await server.elicitInput({
13
+ mode: 'form',
14
+ message: 'Stratta needs your API key. Generate one at https://stratta.ch (API keys page) and paste it below.',
15
+ requestedSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ apiKey: {
19
+ type: 'string',
20
+ title: 'Stratta API key',
21
+ description: 'Starts with sk_strt_',
22
+ },
23
+ },
24
+ required: ['apiKey'],
25
+ },
26
+ });
27
+ if (result.action !== 'accept' || !result.content) {
28
+ throw new Error('API key entry was cancelled. Set STRATTA_API_KEY or run: npx @stratta/mcp login');
29
+ }
30
+ const apiKey = String(result.content.apiKey ?? '').trim();
31
+ if (!apiKey) {
32
+ throw new Error('No API key was provided.');
33
+ }
34
+ const res = (await client.action(api.apiKeys.validateApiKey, {
35
+ plaintext: apiKey,
36
+ }));
37
+ if (!res?.valid) {
38
+ throw new Error('That API key is invalid. Generate a new one at https://stratta.ch and try again.');
39
+ }
40
+ writeStoredApiKey(apiKey);
41
+ return apiKey;
42
+ }
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { createConvexClient, api } from './client.js';
6
- import { ensureAuthenticated } from './auth.js';
6
+ import { ensureAuthenticated, resolveApiKey } from './auth.js';
7
+ import { elicitApiKey } from './elicit.js';
7
8
  import { getMethodologyTool, handleGetMethodology } from './tools/get-methodology.js';
8
9
  import { listNormsTool, handleListNorms } from './tools/list-norms.js';
9
10
  import { getTocTool, handleGetToc } from './tools/get-toc.js';
@@ -24,7 +25,7 @@ const tools = [
24
25
  getCrossRefsTool,
25
26
  ...ingestTools,
26
27
  ];
27
- const server = new Server({ name: 'stratta-mcp', version: '0.1.0' }, { capabilities: { tools: {} } });
28
+ const server = new Server({ name: 'stratta-mcp', version: '0.2.0' }, { capabilities: { tools: {} } });
28
29
  const client = createConvexClient();
29
30
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
30
31
  tools,
@@ -33,17 +34,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
33
34
  const { name, arguments: args } = request.params;
34
35
  const startedAt = Date.now();
35
36
  let apiKeyId = null;
37
+ let resolvedKey = null;
36
38
  let status = 'success';
37
39
  const recordUsage = () => {
38
40
  // Only record once authenticated. Send the plaintext key (proof of
39
41
  // possession) — the server derives the identity, so usage can't be forged
40
42
  // for another user's key.
41
- const apiKey = process.env.STRATTA_API_KEY;
42
- if (!apiKeyId || !apiKey)
43
+ if (!apiKeyId || !resolvedKey)
43
44
  return;
44
45
  void client
45
46
  .action(api.apiKeys.recordToolUsage, {
46
- apiKey,
47
+ apiKey: resolvedKey,
47
48
  toolName: name,
48
49
  status,
49
50
  durationMs: Date.now() - startedAt,
@@ -51,7 +52,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
51
52
  .catch((e) => console.error('[stratta-mcp] track failed:', e));
52
53
  };
53
54
  try {
54
- const auth = await ensureAuthenticated(client);
55
+ resolvedKey = resolveApiKey();
56
+ if (!resolvedKey) {
57
+ resolvedKey = await elicitApiKey(server, client);
58
+ }
59
+ const auth = await ensureAuthenticated(client, resolvedKey);
55
60
  apiKeyId = auth.apiKeyId;
56
61
  let result;
57
62
  if (isIngestTool(name)) {
@@ -131,6 +136,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
131
136
  }
132
137
  });
133
138
  async function main() {
139
+ if (process.argv[2] === 'login') {
140
+ const { runLogin } = await import('./login.js');
141
+ await runLogin();
142
+ return;
143
+ }
134
144
  const transport = new StdioServerTransport();
135
145
  await server.connect(transport);
136
146
  // Stderr-only logging; stdout is reserved for MCP protocol.
@@ -0,0 +1 @@
1
+ export declare function runLogin(): Promise<void>;
package/dist/login.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { createConvexClient, api } from './client.js';
3
+ import { writeStoredApiKey, CONFIG_PATH } from './config.js';
4
+ // Prompt for a secret on the TTY without echoing it back.
5
+ function promptSecret(query) {
6
+ return new Promise((resolve) => {
7
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8
+ let muted = false;
9
+ rl._writeToOutput =
10
+ (s) => {
11
+ if (!muted)
12
+ process.stdout.write(s);
13
+ };
14
+ rl.question(query, (answer) => {
15
+ rl.close();
16
+ process.stdout.write('\n');
17
+ resolve(answer.trim());
18
+ });
19
+ muted = true;
20
+ });
21
+ }
22
+ export async function runLogin() {
23
+ const apiKey = await promptSecret('Paste your Stratta API key (from https://stratta.ch): ');
24
+ if (!apiKey) {
25
+ console.error('No key entered. Aborting.');
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+ const client = createConvexClient();
30
+ const res = (await client.action(api.apiKeys.validateApiKey, {
31
+ plaintext: apiKey,
32
+ }));
33
+ if (!res?.valid) {
34
+ console.error('That API key is invalid. Generate a new one at https://stratta.ch and try again.');
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ writeStoredApiKey(apiKey);
39
+ console.log(`Saved. Stratta MCP will use this key (stored at ${CONFIG_PATH}).`);
40
+ }
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const getCrossRefsTool = {
4
4
  name: 'get_cross_refs',
5
5
  description: "List outgoing cross-references from a section to other norms (e.g. SIA 261 §4.2 → SIA 263). For compound questions you MUST follow these refs: fetch each target via get_section before concluding. Skipping cross-refs is the #1 cause of incomplete answers — SIA norms intentionally distribute the rule, the coefficient, and the action across separate norms (260 / 261 / domain).",
@@ -14,9 +14,8 @@ export const getCrossRefsTool = {
14
14
  },
15
15
  };
16
16
  export async function handleGetCrossRefs(client, args) {
17
- await ensureAuthenticated(client);
18
17
  const result = await client.action(api._mcp.getCrossRefs, {
19
- apiKey: process.env.STRATTA_API_KEY,
18
+ apiKey: requireApiKey(),
20
19
  code: args.norm,
21
20
  path: args.path,
22
21
  });
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const getFigureTool = {
4
4
  name: 'get_figure',
5
5
  description: 'Retrieve a figure (image) referenced in a section. Returns the image inline (base64) so you can see and reason about diagrams, charts, and technical drawings. Use the `id` returned by get_section in its `figures` array.',
@@ -17,8 +17,7 @@ export const getFigureTool = {
17
17
  },
18
18
  };
19
19
  export async function handleGetFigure(client, args) {
20
- await ensureAuthenticated(client);
21
- const apiKey = process.env.STRATTA_API_KEY;
20
+ const apiKey = requireApiKey();
22
21
  const meta = (await client.action(api._mcp.getFigureMeta, {
23
22
  apiKey,
24
23
  code: args.norm,
@@ -1,5 +1,4 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
3
2
  export const getMethodologyTool = {
4
3
  name: 'get_methodology',
5
4
  description: "MANDATORY FIRST CALL when answering any technical question about Swiss civil engineering norms via Stratta. Returns the canonical persona, navigation workflow, meta-routing hints (which SIA norms cover which topics), tree-navigation rules (where to look for formulas vs coefficients vs definitions), citation format and answer rules. Adopt these rules verbatim for the rest of the consultation. If you skip this call you WILL produce lower-quality answers (wrong citation format, missing cross-norm dependencies, hallucinated values).",
@@ -15,7 +14,6 @@ export const getMethodologyTool = {
15
14
  },
16
15
  };
17
16
  export async function handleGetMethodology(client, args) {
18
- await ensureAuthenticated(client);
19
17
  const result = await client.query(api._mcp.getMethodology, {
20
18
  norm: args.norm,
21
19
  });
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const getSectionTool = {
4
4
  name: 'get_section',
5
5
  description: "Fetch the full enriched content of a section: markdown text with formulas in LaTeX and tables inline, pageStart/pageEnd, figures (call get_figure for the image), attached tables/formulas, and cross-references to other norms. ALL technical claims in your answer MUST be backed by a [<norm> <path>, p. <pageStart>] citation pointing to a section you actually fetched via get_section — never cite from memory. When a section's crossRefs list non-empty targets, follow them with another get_section call if the answer depends on them.",
@@ -20,9 +20,8 @@ export const getSectionTool = {
20
20
  },
21
21
  };
22
22
  export async function handleGetSection(client, args) {
23
- await ensureAuthenticated(client);
24
23
  const result = await client.action(api._mcp.getSection, {
25
- apiKey: process.env.STRATTA_API_KEY,
24
+ apiKey: requireApiKey(),
26
25
  code: args.norm,
27
26
  path: args.path,
28
27
  });
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const getSubtreeTool = {
4
4
  name: 'get_subtree',
5
5
  description: 'Drill down into a specific chapter or section. Returns the subtree rooted at `path` with optional depth limit (relative to the root). Use this after get_toc to explore one chapter in detail without fetching the entire TOC. Each node has nodeId, path, title, summary, depth, pageStart, pageEnd, and recursive children.',
@@ -24,9 +24,8 @@ export const getSubtreeTool = {
24
24
  },
25
25
  };
26
26
  export async function handleGetSubtree(client, args) {
27
- await ensureAuthenticated(client);
28
27
  const result = await client.action(api._mcp.getSubtree, {
29
- apiKey: process.env.STRATTA_API_KEY,
28
+ apiKey: requireApiKey(),
30
29
  code: args.norm,
31
30
  path: args.path,
32
31
  maxDepth: args.maxDepth,
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const getTocTool = {
4
4
  name: 'get_toc',
5
5
  description: "Get the high-level table of contents for a norm. By default returns only top-level chapters (depth=1) to stay light. Call get_subtree on a specific chapter's path to drill into sections + subsections. Increase maxDepth if you need a wider overview (cost: response size grows fast). Each node has nodeId, path, title, summary, pageStart, pageEnd, depth, and children (empty at the maxDepth boundary).",
@@ -20,9 +20,8 @@ export const getTocTool = {
20
20
  },
21
21
  };
22
22
  export async function handleGetToc(client, args) {
23
- await ensureAuthenticated(client);
24
23
  const result = await client.action(api._mcp.getDocumentToc, {
25
- apiKey: process.env.STRATTA_API_KEY,
24
+ apiKey: requireApiKey(),
26
25
  code: args.norm,
27
26
  maxDepth: args.maxDepth,
28
27
  });
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  // Ingestion tools — let an engineering office add ITS OWN licensed norms to its
4
4
  // Stratta workspace, agentically, from the user's own Claude. Every call is
5
5
  // authenticated by the user's STRATTA_API_KEY and scoped server-side to their
@@ -159,8 +159,7 @@ export function isIngestTool(name) {
159
159
  return ingestTools.some((t) => t.name === name);
160
160
  }
161
161
  export async function handleIngestTool(client, name, args) {
162
- await ensureAuthenticated(client);
163
- const apiKey = process.env.STRATTA_API_KEY;
162
+ const apiKey = requireApiKey();
164
163
  switch (name) {
165
164
  case 'ingest_status':
166
165
  return await client.action(api.ingestionApi.ingestStatus, {
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const listNormsTool = {
4
4
  name: 'list_norms',
5
5
  description: "List all engineering norms (SIA, Eurocodes, etc.) currently available in Stratta. Returns code, year, title, and language for each. Workflow: call get_methodology FIRST (persona + rules), then list_norms to know what is queryable, then meta-route the user's question to the relevant norms before drilling in.",
@@ -10,9 +10,8 @@ export const listNormsTool = {
10
10
  },
11
11
  };
12
12
  export async function handleListNorms(client) {
13
- await ensureAuthenticated(client);
14
13
  const result = await client.action(api._mcp.listPublishedNorms, {
15
- apiKey: process.env.STRATTA_API_KEY,
14
+ apiKey: requireApiKey(),
16
15
  });
17
16
  return { norms: result };
18
17
  }
@@ -1,5 +1,5 @@
1
1
  import { api } from '../client.js';
2
- import { ensureAuthenticated } from '../auth.js';
2
+ import { requireApiKey } from '../auth.js';
3
3
  export const searchInNormTool = {
4
4
  name: 'search_in_norm',
5
5
  description: 'Search for sections within a norm by keyword (case-insensitive). Returns up to `limit` matches with path, title, and a snippet of matching content. Use this when you do not yet know which section to read — e.g. "charges variables" or "béton armé".',
@@ -21,9 +21,8 @@ export const searchInNormTool = {
21
21
  },
22
22
  };
23
23
  export async function handleSearchInNorm(client, args) {
24
- await ensureAuthenticated(client);
25
24
  const result = await client.action(api._mcp.searchInNorm, {
26
- apiKey: process.env.STRATTA_API_KEY,
25
+ apiKey: requireApiKey(),
27
26
  code: args.norm,
28
27
  keyword: args.keyword,
29
28
  limit: args.limit,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stratta/mcp",
3
3
  "mcpName": "io.github.hugogebel-boop/stratta",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "MCP server exposing Swiss engineering norms (SIA / Eurocodes) to Claude clients via Stratta TreeRAG.",
6
6
  "license": "UNLICENSED",
7
7
  "author": "Hugo Gebel <hugo.gebel@epfl.ch>",