@stratta/mcp 0.9.1 → 0.9.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/README.md CHANGED
@@ -4,7 +4,7 @@ package.json exactly, and it must be present in the *published* tarball —
4
4
  the registry reads the README from npm, not from this repository. Without it
5
5
  `mcp-publisher publish` rejects the server as unverified.
6
6
 
7
- mcp-name: io.github.hugogebel-boop/stratta
7
+ mcp-name: ch.stratta/mcp
8
8
  -->
9
9
 
10
10
  # @stratta/mcp
package/dist/login.js CHANGED
@@ -52,15 +52,15 @@ function openBrowser(url) {
52
52
  }
53
53
  }
54
54
  function page(title, body) {
55
- return `<!doctype html><html lang="fr"><meta charset="utf-8">
56
- <title>${title}</title>
57
- <style>
58
- body{font:16px/1.6 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
59
- background:#12110f;color:#f5f1e8;display:grid;place-items:center;
60
- min-height:100vh;margin:0;padding:2rem;text-align:center}
61
- .c{max-width:26rem} h1{font-size:1.25rem;margin:0 0 .5rem}
62
- p{margin:0;color:#a8a196}
63
- </style>
55
+ return `<!doctype html><html lang="fr"><meta charset="utf-8">
56
+ <title>${title}</title>
57
+ <style>
58
+ body{font:16px/1.6 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
59
+ background:#12110f;color:#f5f1e8;display:grid;place-items:center;
60
+ min-height:100vh;margin:0;padding:2rem;text-align:center}
61
+ .c{max-width:26rem} h1{font-size:1.25rem;margin:0 0 .5rem}
62
+ p{margin:0;color:#a8a196}
63
+ </style>
64
64
  <div class="c"><h1>${title}</h1><p>${body}</p></div>`;
65
65
  }
66
66
  /**
@@ -59,16 +59,16 @@ export const openDossier = defineTool({
59
59
  export const saveFinding = defineTool({
60
60
  name: 'save_finding',
61
61
  title: 'Save a finding to a dossier',
62
- description: `Record ONE decision in a dossier. Call it as you work, not in a batch at the end — a finding saved when it is made carries the reasoning that produced it.
63
-
64
- WHAT TO RECORD, by kind:
65
- - reference: what a norm says, that the project relies on. ALWAYS fill normCode + sectionPath (+ page). If you cannot cite it, it is not a reference.
66
- - hypothesis: a value the engineer RETAINS, and why. This is the one that matters most in geotechnics, where a retained value is a judgement between disagreeing measurements rather than the output of a formula. Put the number in \`value\` and the reasoning in \`detail\`.
67
- - observation: what the site, a borehole, or a survey showed. Include the date in \`detail\` when known.
68
- - question: something not settled. These surface FIRST when the dossier is reloaded, so record them even when you cannot answer — especially then.
69
-
70
- WHAT NOT TO RECORD: your own prose, intermediate steps, anything the user did not treat as a decision. A dossier of forty entries where three mattered is worse than a dossier of three.
71
-
62
+ description: `Record ONE decision in a dossier. Call it as you work, not in a batch at the end — a finding saved when it is made carries the reasoning that produced it.
63
+
64
+ WHAT TO RECORD, by kind:
65
+ - reference: what a norm says, that the project relies on. ALWAYS fill normCode + sectionPath (+ page). If you cannot cite it, it is not a reference.
66
+ - hypothesis: a value the engineer RETAINS, and why. This is the one that matters most in geotechnics, where a retained value is a judgement between disagreeing measurements rather than the output of a formula. Put the number in \`value\` and the reasoning in \`detail\`.
67
+ - observation: what the site, a borehole, or a survey showed. Include the date in \`detail\` when known.
68
+ - question: something not settled. These surface FIRST when the dossier is reloaded, so record them even when you cannot answer — especially then.
69
+
70
+ WHAT NOT TO RECORD: your own prose, intermediate steps, anything the user did not treat as a decision. A dossier of forty entries where three mattered is worse than a dossier of three.
71
+
72
72
  Set \`confidence\` whenever the entry is a value: established (computed or read directly), judgement (the engineer chose it), to_confirm (provisional, needs a test or a check). Confusing those three is the professional fault this field exists to prevent.`,
73
73
  inputSchema: {
74
74
  dossierId,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stratta/mcp",
3
- "mcpName": "io.github.hugogebel-boop/stratta",
4
- "version": "0.9.1",
3
+ "mcpName": "ch.stratta/mcp",
4
+ "version": "0.9.2",
5
5
  "description": "MCP server exposing the engineering norms your firm is licensed for (SIA / Eurocodes) to any MCP client, via Stratta TreeRAG.",
6
6
  "license": "UNLICENSED",
7
7
  "author": "SmartFlow <hello@stratta.ch>",
@@ -0,0 +1,99 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Publish @stratta/mcp to npm, then register it with the official MCP registry.
4
+
5
+ .DESCRIPTION
6
+ The two publications are ordered, not independent: the registry reads
7
+ `mcpName` from the tarball npm serves, so a version that is not on npm yet
8
+ cannot be registered. Doing it by hand in the wrong order fails with
9
+
10
+ NPM package '@stratta/mcp' exists, but version 'x.y.z' was not found
11
+
12
+ which reads like a transient error and is not one.
13
+
14
+ Run this from an interactive terminal. `npm publish` triggers a 2FA prompt
15
+ that opens a browser, and `npm login` needs one too if the session has
16
+ lapsed. Neither works from a non-interactive shell -- by design, so that
17
+ access to a machine is not access to the package.
18
+
19
+ ASCII only, on purpose: Windows PowerShell 5.1 reads a UTF-8 file without a
20
+ BOM as ANSI, and a single arrow or em dash in a comment breaks the parser
21
+ with an error that points at the wrong line.
22
+
23
+ .PARAMETER PrivateKey
24
+ The Ed25519 key proving control of stratta.ch, 64 hex characters. Lives in
25
+ Doppler under MCP_REGISTRY_PRIVATE_KEY. Its public half is served from
26
+ /.well-known/mcp-registry-auth -- deleting that file breaks publishing.
27
+
28
+ .PARAMETER PublisherPath
29
+ Path to mcp-publisher. Download it from the registry releases if absent:
30
+ https://github.com/modelcontextprotocol/registry/releases
31
+
32
+ .EXAMPLE
33
+ .\publish-release.ps1 -PrivateKey (doppler secrets get MCP_REGISTRY_PRIVATE_KEY --plain)
34
+ #>
35
+ param(
36
+ [Parameter(Mandatory = $true)][string]$PrivateKey,
37
+ [string]$PublisherPath = "mcp-publisher"
38
+ )
39
+
40
+ $ErrorActionPreference = "Stop"
41
+ Push-Location (Join-Path $PSScriptRoot "..")
42
+
43
+ try {
44
+ $pkg = Get-Content package.json -Raw | ConvertFrom-Json
45
+ $srv = Get-Content server.json -Raw | ConvertFrom-Json
46
+
47
+ Write-Host "-> @stratta/mcp $($pkg.version) as $($pkg.mcpName)" -ForegroundColor Cyan
48
+
49
+ # Fail before publishing, not after. The three names and the three versions
50
+ # have to agree, or the registry rejects the server as unverified once npm
51
+ # has already gone out, and a version number is burnt.
52
+ if ($pkg.mcpName -ne $srv.name) {
53
+ throw "package.json mcpName ($($pkg.mcpName)) does not match server.json name ($($srv.name))"
54
+ }
55
+ if ($pkg.version -ne $srv.version -or $pkg.version -ne $srv.packages[0].version) {
56
+ throw "version mismatch: package.json $($pkg.version), server.json $($srv.version), packages[0] $($srv.packages[0].version)"
57
+ }
58
+ if (-not (Select-String -Path README.md -Pattern ("mcp-name: " + [regex]::Escape($pkg.mcpName)) -Quiet)) {
59
+ throw "README.md is missing the marker 'mcp-name: $($pkg.mcpName)'"
60
+ }
61
+ Write-Host " manifest is coherent" -ForegroundColor DarkGray
62
+
63
+ Write-Host "-> npm whoami" -ForegroundColor Cyan
64
+ npm whoami
65
+ if ($LASTEXITCODE -ne 0) { throw "Not logged in to npm. Run 'npm login' and retry." }
66
+
67
+ Write-Host "-> npm publish (a 2FA prompt may open a browser)" -ForegroundColor Cyan
68
+ npm publish
69
+ if ($LASTEXITCODE -ne 0) { throw "npm publish failed" }
70
+
71
+ # npm's CDN needs a moment before the registry can see the new version.
72
+ Write-Host "-> waiting for npm to serve $($pkg.version)" -ForegroundColor Cyan
73
+ $seen = $false
74
+ foreach ($i in 1..20) {
75
+ Start-Sleep -Seconds 3
76
+ try {
77
+ $served = Invoke-RestMethod "https://registry.npmjs.org/@stratta/mcp/$($pkg.version)"
78
+ if ($served.version -eq $pkg.version) { $seen = $true; break }
79
+ } catch { }
80
+ }
81
+ if (-not $seen) { throw "npm has not served $($pkg.version) after 60s; rerun the registry steps by hand" }
82
+ Write-Host " npm serves $($pkg.version)" -ForegroundColor DarkGray
83
+
84
+ # Domain proof rather than the GitHub device flow: nothing waits on a human
85
+ # typing a code. The JWT it returns expires in under an hour, which is why
86
+ # login and publish run back to back with nothing in between.
87
+ Write-Host "-> mcp-publisher login (domain proof)" -ForegroundColor Cyan
88
+ & $PublisherPath login http --domain stratta.ch --private-key $PrivateKey
89
+ if ($LASTEXITCODE -ne 0) { throw "registry login failed. Check that https://stratta.ch/.well-known/mcp-registry-auth is still served." }
90
+
91
+ Write-Host "-> mcp-publisher publish" -ForegroundColor Cyan
92
+ & $PublisherPath publish
93
+ if ($LASTEXITCODE -ne 0) { throw "registry publish failed" }
94
+
95
+ Write-Host "OK $($pkg.version) published to npm and to registry.modelcontextprotocol.io" -ForegroundColor Green
96
+ }
97
+ finally {
98
+ Pop-Location
99
+ }
@@ -16,6 +16,7 @@ writes go through the Stratta MCP `ingest_*` tools, scoped to your org.
16
16
  > for. You are responsible for your usage rights (see Stratta's Terms).
17
17
 
18
18
  ## Prerequisites
19
+
19
20
  - The Stratta MCP server is installed and your `STRATTA_API_KEY` resolves
20
21
  (via env, `~/.stratta/config.json`, or first-call elicitation).
21
22
  - **Python ≥ 3.10 with PyMuPDF**. Install once:
@@ -23,12 +24,14 @@ writes go through the Stratta MCP `ingest_*` tools, scoped to your org.
23
24
  - The norm PDF is available locally.
24
25
 
25
26
  ## Tools used (all scoped to your workspace)
27
+
26
28
  `ingest_status` · `ingest_create_document` · `ingest_create_sections` ·
27
29
  `ingest_attach_formula` · `ingest_attach_table` · `ingest_attach_cross_ref` ·
28
30
  `ingest_upload_figure` · `ingest_normalize_cross_refs` · `ingest_publish` ·
29
31
  `ingest_delete`.
30
32
 
31
33
  ## Quotas — read before you start
34
+
32
35
  The workspace has server-enforced limits on norms, sections and figures. A
33
36
  `QUOTA_EXCEEDED` error names the dimension, the current count and the limit.
34
37
 
@@ -43,29 +46,37 @@ half-ingested document behind, which the user then has to delete by hand.
43
46
  ## Workflow
44
47
 
45
48
  ### 1. Locate the pre-pass script
49
+
46
50
  It ships inside this package at `scripts/ingest-prepass.py`. Resolve its path:
51
+
47
52
  ```bash
48
53
  node -e "console.log(require.resolve('@stratta/mcp/package.json'))"
49
54
  # → <root>/package.json → <root>/scripts/ingest-prepass.py
50
55
  ```
56
+
51
57
  If the user is working in the Stratta monorepo, the script also lives at
52
58
  `packages/mcp/scripts/ingest-prepass.py`.
53
59
 
54
60
  ### 2. Check for an existing copy
61
+
55
62
  `ingest_status { code }` (e.g. `"SIA 261"`). To re-ingest, call
56
63
  `ingest_delete { documentId }` first.
57
64
 
58
65
  ### 3. Run the pre-pass
66
+
59
67
  ```bash
60
68
  python <pkg-root>/scripts/ingest-prepass.py \
61
69
  --pdf <path-to-pdf> \
62
70
  --output .stratta-ingest/<code-slug>
63
71
  ```
72
+
64
73
  Output under `.stratta-ingest/<code-slug>/`:
74
+
65
75
  - `prepass.json` — full manifest (see below).
66
76
  - `figures/page-NNN.png` — one PNG per page that contains a `Figure N` caption.
67
77
 
68
78
  `prepass.json` structure:
79
+
69
80
  - `doc` — `pageCount`, detected `language`, `tocSource`, raw `metadata`.
70
81
  - `stats` — `sectionCount`, `byDepth`, `figureCount`.
71
82
  - `sections[]` — full hierarchical tree (depth **0 = chapter**, 1+ = sub-sections),
@@ -79,12 +90,15 @@ The pre-pass is **exhaustive** (e.g. ~550 nodes on SIA 261). You decide what
79
90
  to keep in the next step.
80
91
 
81
92
  ### 4. Create the document
93
+
82
94
  Read `prepass.json`, then:
83
95
  `ingest_create_document { code, year, title, language: <doc.language>, totalPages: <doc.pageCount> }`
84
96
  → returns `documentId`. Keep it for every subsequent call.
85
97
 
86
98
  ### 5. Decide section granularity + generate summaries
99
+
87
100
  Iterate `sections[]` and decide what to keep. Two viable strategies:
101
+
88
102
  - **Keep all** — most faithful, ~500 sections on a typical SIA norm. Great
89
103
  for fine-grained navigation but verbose.
90
104
  - **Aggregate trivial leaves** — fold paragraph-level nodes (`6.1.1`...`6.1.11`)
@@ -92,6 +106,7 @@ Iterate `sections[]` and decide what to keep. Two viable strategies:
92
106
  100-150 sections. Recommended unless the user asks for max granularity.
93
107
 
94
108
  For each kept section, prepare:
109
+
95
110
  - `summary` — 1-3 sentences derived from `rawText` (mention formulas/values).
96
111
  - `content` — enriched text with LaTeX inline where the source has math
97
112
  (e.g. `$\sigma_d = f_{yd} \cdot \gamma$`). Open the PDF visually for pages
@@ -102,18 +117,22 @@ Keep the `nodeId` / `parentNodeId` / `path` / `pageStart` / `pageEnd` /
102
117
  `orderIndex` / `depth` from the pre-pass — those are deterministic.
103
118
 
104
119
  ### 6. Insert sections (batched)
120
+
105
121
  `ingest_create_sections { documentId, sections: [...] }` in batches of 30-50.
106
122
  Parent links resolve via `parentNodeId` within the batch and across prior
107
123
  batches. The call returns a `nodeId → sectionId` map — **use those `sectionId`s**
108
124
  for every enrichment call below.
109
125
 
110
126
  ### 7. Enrich
127
+
111
128
  - `ingest_attach_formula { sectionId, latex, description, formulaNumber }`
112
129
  - `ingest_attach_table { sectionId, data: { headers, rows }, caption, tableNumber }`
113
130
  - `ingest_attach_cross_ref { sourceSectionId, targetDocumentCode, targetSectionPath?, refText, refType }`
114
131
 
115
132
  ### 8. Upload figures
133
+
116
134
  For each figure in `prepass.json#figures`:
135
+
117
136
  - Read `.stratta-ingest/<code>/<fileName>` and base64-encode the bytes.
118
137
  - Find the owning section: the kept section whose `pageStart..pageEnd`
119
138
  range includes the figure's `page`.
@@ -121,15 +140,18 @@ For each figure in `prepass.json#figures`:
121
140
  (≤ 8 MB per image).
122
141
 
123
142
  ### 9. Auto cross-references (optional but recommended)
143
+
124
144
  `ingest_normalize_cross_refs { documentId }` scans every section's text for
125
145
  references to other norms (SIA / SN EN / EN / ISO / DIN …) and rebuilds the
126
146
  cross-ref index. Idempotent.
127
147
 
128
148
  ### 10. Publish
149
+
129
150
  `ingest_publish { documentId }`. The norm is now queryable in your workspace
130
151
  via `list_norms`, `get_toc`, `get_section`, `search_in_norm`, `get_figure`, etc.
131
152
 
132
153
  ## Quality bar
154
+
133
155
  - Trust the pre-pass for `path` + `pageStart`/`pageEnd` — it's deterministic
134
156
  and verified against the PDF bookmarks.
135
157
  - Summaries drive navigation — be specific (mention key formulas/values).