# Stratta (npm · @stratta/mcp)

Read the SIA and Eurocode norms your bureau licensed, cited to the section and page.

- Trust score: 76/100 (medium)
- Change this week: +3
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-25

## Components

- npm · `@stratta/mcp`: 76/100 (this document), [markdown](https://verifymcp.io/servers/ch-stratta-mcp/stratta-mcp.md), [page](https://verifymcp.io/servers/ch-stratta-mcp/stratta-mcp)

## Channel facts

- Registry: `npm`
- Package: `@stratta/mcp`
- Version: `1.16.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-25.

- **Supply Chain Security**: 99/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - 31 of 125 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 6/100
  - Repository check failed: the declared repository URL returned HTTP 404.
  - Provenance check failed: no build-provenance attestation is published.
  - License check failed: the license (UNLICENSED) isn't a recognized OSI-approved license.
  - Actively maintained (last published 4 days ago).
  - Security-disclosure policy not yet verified: we couldn't inspect the source repository.
- **Schema Quality & AI Usability**: 84/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 9313 tokens (~152/item across 61 items; 59 tools + 2 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 85/100
  - Stability check failed: the tool surface changed between 0.9.2 and 1.16.0: 0 tool removals, 7 breaking changes, 36 additions.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 4 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 61 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Stratta MCP server?

Stratta runs locally as an npm package, launched with npx -y @stratta/mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add ch-stratta-mcp -- npx -y @stratta/mcp
```

### Cursor

```json
{
  "mcpServers": {
    "ch-stratta-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@stratta/mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "ch-stratta-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@stratta/mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add ch-stratta-mcp -- npx -y @stratta/mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ch-stratta-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "@stratta/mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add ch-stratta-mcp --command npx --arg -y --arg @stratta/mcp
```

### Hermes

```yaml
mcp_servers:
  ch-stratta-mcp:
    command: "npx"
    args: ["-y", "@stratta/mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "ch-stratta-mcp": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "@stratta/mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add ch-stratta-mcp -t stdio -c npx -a -y @stratta/mcp
```

### Other

```json
{
  "mcpServers": {
    "ch-stratta-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@stratta/mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-25 (score 76, +1)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-09-22 (score 75, +1)

No change was recorded against any check on this day. Stability & Change Management went from 71 to 75.

### 2026-09-20 (score 74, +1)

- [security regression] Stability: fail → unverified
- [security regression] Tool safety: pass → unverified
- [functional regression] Schema quality: 8460 → 9313
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional] Package version: 1.15.0 → 1.16.0

### 2026-09-18 (score 73, +1)

No change was recorded against any check on this day. Stability & Change Management went from 58 to 61.

### 2026-09-16 (score 72, +1)

No change was recorded against any check on this day. Stability & Change Management went from 51 to 55.

### 2026-09-15 (score 71, +15)

- [security regression] Stability: fail → unverified
- [security regression] Tool safety: pass → unverified
- [security improvement] Malware scan: unverified → pass
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional] Package version: 0.14.0 → 1.15.0

### 2026-09-14 (score 56, −15)

- [security regression] Malware scan: pass → unverified
- [security regression] Stability: fail → unverified
- [security regression] Tool safety: pass → unverified
- [functional regression] Schema quality: 5470 → 8396
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional] Package version: 0.13.1 → 0.14.0

### 2026-09-13 (score 71, +1)

No change was recorded against any check on this day. Stability & Change Management went from 41 to 45.

## MCP tools (59)

### `get_methodology` (~76 tokens)

Working method

Read the working contract this server expects an agent to follow: how to walk a norm from its table of contents down to a clause, how to cite what it reads, and when to record something in a project dossier. Call it once, before the first technical answer of a conversation, and follow it for the rest of the conversation.

### `whoami` (~55 tokens)

Connected account

Report which organization this connection reads from, the signed-in role, the plan name and how many norms are available. Use it to confirm the connection points at the intended workspace before starting, or when a norm the user expects is missing.

### `list_norms` (~85 tokens)

Available norms

List the norms published in the signed-in organization, with code, year, title, language and coverage (how much of the norm was ingested). This is the first call of any search: a norm absent from this list cannot be read and must not be answered from memory. A demo entry is fictional and never a technical reference; a sparse entry carries a notice to relay.

### `get_toc` (~185 tokens)

Table of contents

Return the table of contents of one norm as a tree of path, title, summary and page range. Pass maxDepth 1 for the chapter list, which is enough to choose where to descend; deeper levels return the whole outline and fill the context. Descend with get_subtree, read with get_section.

Input parameters:

- `code` (string, required): Norm code exactly as list_norms returns it, e.g. "SIA 261", "SIA 267", "EN 1997-1".
- `edition` (string): Edition of the norm when the organization holds several ("2013", "2020"). Default: the most recent published edition; list_norms names them.
- `maxDepth` (integer): Nesting depth to include. 1 (default) returns chapters only; 2 adds X.Y sections. Large norms grow fast beyond 2.

### `get_subtree` (~240 tokens)

Section tree

Return the section tree below one path, each node with its summary and page range. This is the step between the table of contents and reading: it shows what a chapter contains without loading its text. Use maxDepth to bound it; a tree larger than maxNodes loses its deepest levels and says so with truncated: true.

Input parameters:

- `code` (string, required): Norm code exactly as list_norms returns it, e.g. "SIA 261", "SIA 267", "EN 1997-1".
- `edition` (string): Edition of the norm when the organization holds several ("2013", "2020"). Default: the most recent published edition; list_norms names them.
- `maxDepth` (integer): Depth relative to the root. 1 returns the root and its direct children; omit for the whole subtree.
- `maxNodes` (integer): Cap on the nodes returned, 20 to 2000. Default 400.
- `path` (string, required): Path to root the subtree at, e.g. "9" or "9.5". A clause number below the last heading resolves to that heading.

### `get_section` (~306 tokens)

Section

Return one section as a Markdown document: its text with the LaTeX formulas and tables where the norm prints them, its figures, the cross-references it carries and the sections below it, with the exact path and page range. This is what gets quoted: call it before stating any technical claim taken from a norm, and cite [CODE section path, p. N]. A clause number that is not a node (9.5.2.1) resolves to its nearest heading, whose text carries the clause. A long section comes in windows of maxChars; when truncated is true, call again with the offset given.

Input parameters:

- `code` (string, required): Norm code exactly as list_norms returns it, e.g. "SIA 261", "SIA 267", "EN 1997-1".
- `edition` (string): Edition of the norm when the organization holds several ("2013", "2020"). Default: the most recent published edition; list_norms names them.
- `maxChars` (integer): Characters of text per call, 500 to 40000. Default 12000, which fits any client. Raise it only when one section is needed whole.
- `offset` (integer): Where to resume in the text, from the nextOffset of a truncated call.
- `path` (string, required): Section path as it appears in the table of contents, e.g. "4.2.1" or "Annexe A".

### `search_in_norm` (~234 tokens)

Search a norm

Full-text search inside one norm, ranked, a hit in a heading outranking a hit in the body. Use it when the table of contents suggests no obvious section. Matching is per term, not per substring, in the language of the norm: search "charges" in a French norm, not "loads"; accents and case do not matter. A clause number as keyword returns that clause. Returns paths, titles and snippets; read the promising ones with get_section.

Input parameters:

- `code` (string, required): Norm code exactly as list_norms returns it, e.g. "SIA 261", "SIA 267", "EN 1997-1".
- `edition` (string): Edition of the norm when the organization holds several ("2013", "2020"). Default: the most recent published edition; list_norms names them.
- `keyword` (string, required): Search terms in the language of the norm, accents optional. Several words find sections containing all of them; a clause number finds that clause.
- `limit` (integer): Maximum number of hits, 1 to 50. Default 20.

### `search_corpus` (~145 tokens)

Search all norms

Full-text search across every norm the organization can read, grouped by norm, heading hits from any norm ahead of body hits. Use it first when the question does not name its norm: it tells which norms speak of the subject before any table of contents is opened. Same matching as search_in_norm. Returns, per norm, its code and title and a few hits; continue with get_section on the paths that look relevant.

Input parameters:

- `keyword` (string, required): Search terms in the language of the norms, accents optional. Several words find sections containing all of them.
- `limit` (integer): Maximum number of hits across all norms, 1 to 40. Default 20.

### `get_cross_refs` (~210 tokens)

Cross-references

Return the references one section makes: to other clauses, annexes, figures and tables of the same norm, and to other norms. SIA norms deliberately spread the rule, the coefficient and the action across separate clauses and norms, so following a reference is often what separates a correct answer from an incomplete one. Each reference says whether it is resolvable in this corpus (listed first) and names the target section; read each relevant target with get_section.

Input parameters:

- `code` (string, required): Norm code exactly as list_norms returns it, e.g. "SIA 261", "SIA 267", "EN 1997-1".
- `edition` (string): Edition of the norm when the organization holds several ("2013", "2020"). Default: the most recent published edition; list_norms names them.
- `path` (string, required): Section path as it appears in the table of contents, e.g. "4.2.1" or "Annexe A".

### `get_figure` (~153 tokens)

Figure

Return one figure of a norm as an image: load diagrams, design charts, standard cross-sections. Call it when the figure carries the rule and describing it in words would lose the content. The figure identifier comes from the figures array of get_section.

Input parameters:

- `code` (string, required): Norm code exactly as list_norms returns it, e.g. "SIA 261", "SIA 267", "EN 1997-1".
- `edition` (string): Edition of the norm when the organization holds several ("2013", "2020"). Default: the most recent published edition; list_norms names them.
- `figureId` (string, required): Figure identifier from the figures array of get_section.

### `scan_site` (~204 tokens)

Scan a site

Collect what public Swiss registers know about a building site: municipality, parcel, ground elevation, geology, nearby boreholes with their strata, polluted sites, groundwater protection, natural hazards, noise sensitivity. Give a Swiss address; it is geocoded server-side against swisstopo, so do not pass coordinates you inferred. Open the dossier first with open_dossier: one site per dossier. The scan runs in the background and takes a few seconds; read the result with get_site_context. It is a survey of public registers, never a geotechnical study.

Input parameters:

- `address` (string, required): The street address in Switzerland, as the user wrote it: "Av. de Rhodanie 58, Lausanne".
- `dossierId` (string, required): Dossier id returned by open_dossier or list_dossiers.
- `radius` (integer): How far around the site to look for boreholes and constraints, in metres (50 to 1000, default 300).

### `get_site_context` (~123 tokens)

Read a site scan

Read what a site scan found, grouped by topic, each value with its source and the date THE DATA was current. Returns two separate lists: what the registers settled, and what they could NOT settle, with the reason. Report the second list to the user in full: it is what a bid has to budget for. Never derive a design value (friction angle, bearing capacity, modulus) from a geological description returned here; state what the register says and that a local investigation is required.

Input parameters:

- `siteId` (string, required): Site id returned by scan_site.

### `get_boreholes` (~205 tokens)

Read the boreholes of a site

Read the boreholes a site scan found, nearest first, with their logged strata (depth interval, description, period), depth, year, SIA 261 ground class when logged, water table when logged, and links to the cantonal documents. Call it after get_site_context when the question turns on what the ground is actually made of. Each borehole is one observation at one point: quote it with its distance, never interpolate between two of them, and never derive a design value from a layer description.

Input parameters:

- `limit` (integer): How many boreholes to return, nearest first (1 to 40, default 8). Ask for more only when the nearest ones did not answer.
- `siteId` (string, required): Site id returned by scan_site.
- `withStrataOnly` (boolean): Return only the boreholes whose strata were read. Use it when the layers are the point; the count of what was left out still travels in `total`.

### `list_sites` (~116 tokens)

Site sheets

List the site sheets of the organization, most recently touched first, each with the dossier it belongs to, the municipality and canton, the search radius and the status of its current scan. Call it when the user names a site or an address rather than a dossier, or to find the siteId of a sheet opened from the app; a dossier row from list_dossiers also carries its siteId.

Input parameters:

- `limit` (integer): How many sites to return, 1 to 200. Default 50; truncated says when more exist.

### `get_site` (~139 tokens)

Read a site sheet

Read a site sheet as the app shows it: every fact with its stable key, its status (established, indicative, absent, uncovered), its source with the date the data was current, the official notice and attribution when the source carries one, and the scan with each source and whether it answered. get_site_context is the summary shaped for an answer; call this when you need to quote a source line, cite a fact key in save_finding, or tell the user which source failed. Facts are registers, never a geotechnical study.

Input parameters:

- `siteId` (string, required): Site id from scan_site, list_sites or a dossier row.

### `get_site_section` (~162 tokens)

Read the section

Read the geological section drawn on a site sheet: the axis from A to B, the corridor width, the measured terrain line, the modelled bedrock top, the geology bands sampled along the axis, and the boreholes inside the corridor with their logged strata, projected on the axis. Call it when the question is about what lies along a line rather than at a point. Without an axis drawn in the app, it says so and returns no lines. Nothing is interpolated between boreholes.

Input parameters:

- `maxPoints` (integer): How many points of the terrain and bedrock lines to return, evenly spaced, 2 to 200. Default 60.
- `siteId` (string, required): Site id from scan_site, list_sites or a dossier row.

### `list_site_overlays` (~144 tokens)

Map areas around a site

List the areas the site map draws around the point: polluted sites, natural hazard zones and water protection sectors read from the canton, each with whether it covers the site point or how many metres away it stops, its area and its layer. Coordinates are left out unless asked for. Call it after get_site_context when a constraint is near and the user asks how near; the sheet answers at the point, this answers in the radius.

Input parameters:

- `includeGeometry` (boolean): Return the polygon rings in LV95 as well. Default false: rings run to hundreds of points.
- `siteId` (string, required): Site id from scan_site, list_sites or a dossier row.

### `get_groundwater_contours` (~151 tokens)

Piezometric contours

Read the cantonal piezometric contours the scan stored around a site: for each contour the aquifer name, the water table elevation in metres above sea level and the source layer. Cantons that publish none return an empty list and say whether the reading happened. A contour is the canton's interpretation of an aquifer, not a measurement at the site: prefer a water table logged in a nearby borehole (get_boreholes) when one exists, and quote a contour with its name.

Input parameters:

- `includeGeometry` (boolean): Return the contour vertices in LV95 as well. Default false.
- `siteId` (string, required): Site id from scan_site, list_sites or a dossier row.

### `list_site_photos` (~113 tokens)

Site photos

List the photos the engineer uploaded on a site sheet: name, position in LV95 when the photo carries one (from its EXIF data or placed by hand), shooting direction, the instant the camera recorded, and the note written on it. No image is returned and no link: the photo is looked at in the app. Call it to know what was observed on site and when, before writing an observation.

Input parameters:

- `siteId` (string, required): Site id from scan_site, list_sites or a dossier row.

### `list_site_neighbours` (~149 tokens)

Neighbouring projects

List the other site sheets of the organization within a radius of this one, nearest first, with the distance, the geological unit their own scan established and whether it is the same as here, and the dossier that carries them with its counts of open and decided questions. This is what the public registers cannot say: the office already worked three hundred metres away. Nothing is interpolated: these are links to dossiers to reload, not values.

Input parameters:

- `radius` (integer): How far to look, in metres, 50 to 5000. Default 500. At most twelve neighbours are returned.
- `siteId` (string, required): Site id from scan_site, list_sites or a dossier row.

### `list_dossiers` (~60 tokens)

Project dossiers

List the project dossiers of the organization, most recently touched first, with the count of entries and of open questions. Call it when the user names a project or asks what they were working on: a dossier that already exists is reloaded, never recreated.

### `open_dossier` (~128 tokens)

Open a dossier

Open a project dossier, or return the one that already carries that name: calling it twice with the same name never creates a duplicate. Open one when the user works on a named project and there is something worth keeping (a question to settle, a retained value, an assumption, a site observation); not for a passing lookup. Returns dossierId and created.

Input parameters:

- `name` (string, required): How the user refers to the project, in their own words: this is the name they will search for.
- `reference` (string): The office's own project number, if the user gave one.

### `open_question` (~200 tokens)

Open a question

Open one question to settle in a dossier, before gathering evidence for it: "can pile P38 be kept?", "which friction angle do we retain?". A question is the unit of work; the evidence saved with save_finding and the eventual record_decision hang off it. One question per thing to settle, never a list of eight in one body. Idempotent on the title: the same wording returns the question already open. Name the options on the table when there are several. Returns questionId and created.

Input parameters:

- `body` (string): What is at stake and why it is not settled, in two or three sentences. Not a list of sub-questions.
- `dossierId` (string, required): Dossier id returned by open_dossier or list_dossiers.
- `options` (array): The ways this could be settled, one short name each.
- `title` (string, required): The question in one line, phrased as a question.

### `save_finding` (~493 tokens)

Record evidence

Record one piece of evidence in a dossier, as you work rather than in a batch at the end. Kinds: reference (what a norm says; always give normCode, sectionPath and page, or it is not a reference), hypothesis (the value the engineer retains and why, value in value and reasoning in detail), observation (what the site, a borehole or a survey showed, with the date). One clause, one value or one fact per call, in one or two sentences. Pass questionId to file it under the question it serves; without it the entry waits unclassified. Set confidence whenever a value is at stake: established, judgement or to_confirm. Questions go through open_question and decisions through record_decision, not here.

Input parameters:

- `attachmentId` (string): The project attachment the evidence comes from, when it is not a norm.
- `attachmentPage` (integer): The page of that attachment.
- `confidence` (string): established (computed or read directly), judgement (the engineer chose it), to_confirm (provisional, pending a test).
- `detail` (string): The reasoning in one or two sentences: why this value, what it rests on, what disagreed. Expected for a hypothesis.
- `dossierId` (string, required): Dossier id returned by open_dossier or list_dossiers.
- `kind` (string, required): reference, hypothesis or observation: pick by what the entry is.
- `normCode` (string): Norm this comes from, e.g. "SIA 267".
- `normEdition` (string): The edition cited, e.g. "2013" or "2020 + rect. 2022", when list_norms gives it.
- `page` (integer): Page in the norm, so a human can verify it in their PDF.
- `questionId` (string): The question this evidence serves, from open_question or load_dossier. Omit only when you do not know yet.
- `sectionPath` (string): Exact section path, e.g. "9.5.2.1".
- `title` (string, required): The fact in one line, as an engineer would write it in a report. One clause, one value or one observation; not a theme.
- `value` (string): The retained value with its unit, e.g. "30 deg", "1.25 kN/m2".

### `record_decision` (~397 tokens)

Record a decision

Record what the engineer retained: the decision in one line, why, and the clause it rests on. Pass questionId when the question already exists; otherwise pass dossierId and question (what is settled, in one line) and the question is opened on the spot, already decided. Name the retained option when the question listed some; put the number in value and set confidence when the decision is a value. Only after the user confirmed the decision, never on your own reasoning. Returns entryId.

Input parameters:

- `confidence` (string): established, judgement or to_confirm.
- `confirmed` (boolean): true when the user has already confirmed this decision in the conversation. Otherwise the user is asked through the client before anything is written.
- `detail` (string): Why, in one or two sentences, and what it rests on.
- `dossierId` (string): Without questionId: the dossier the decision belongs to (open_dossier, load_dossier).
- `normCode` (string): Norm the decision rests on, e.g. "SIA 267".
- `normEdition` (string): The edition cited, when known.
- `page` (integer): Page in the norm.
- `question` (string): Without questionId: what is settled, in one line ("Friction angle of the moraine"). The same words find the question already opened.
- `questionId` (string): Question id returned by open_question or load_dossier, when the question exists.
- `retainedOption` (string): The option that was retained, by the name it was given. An option not listed yet is created.
- `sectionPath` (string): Exact section path, e.g. "9.5.2.1".
- `title` (string, required): The decision in one line, as it would be written in a report.
- `value` (string): The decided value with its unit, when the decision is a value.

### `load_dossier` (~121 tokens)

Load a dossier

Reload a dossier: its questions with their evidence, options and decisions, open questions first, then the evidence not yet filed under a question, then the comments a colleague left. Do this before answering whenever the user returns to a project already named, otherwise a decision already taken gets taken again. The name is enough; the id is optional.

Input parameters:

- `dossierId` (string): Dossier id, preferred when you have it.
- `name` (string): The project name as the user says it. Matched exactly; call list_dossiers first if unsure.

### `resolve_question` (~133 tokens)

Close a question

Close a question that stopped mattering without a decision (the variant was abandoned, the request withdrawn), or reopen one with resolved false. The question stays in the dossier with its evidence. To settle a question with an answer, call record_decision instead. Only when the user says so.

Input parameters:

- `confirmed` (boolean): true when the user has already confirmed in the conversation that the question is settled. Otherwise the user is asked through the client first.
- `questionId` (string, required): Question id returned by open_question or load_dossier.
- `resolved` (boolean): Defaults to true (close). Pass false to reopen.

### `list_attachments` (~94 tokens)

Project attachments

List the project attachments of one dossier: the site reports, borehole logs, meeting minutes and data sheets the engineer uploaded. Returns for each its id, name, type, page count and extraction status. Read one with read_attachment before answering on the project; a piece is evidence like a clause and is cited the same way.

Input parameters:

- `dossierId` (string, required): The dossier, from list_dossiers or open_dossier.

### `read_attachment` (~162 tokens)

Read an attachment

Read the text of a project attachment as a Markdown document, page by page. from and to bound the pages, maxChars the characters; when truncated is true, call again with from set to nextPage. A scan without a text layer says so. Cite what you read as [name, p. N] and file it with save_finding using attachmentId and attachmentPage.

Input parameters:

- `attachmentId` (string, required): The attachment, from list_attachments or search_in_dossier.
- `from` (integer): First page to read. Default 1.
- `maxChars` (integer): Characters of text per call, 500 to 40000. Default 12000.
- `to` (integer): Last page to read. Default: the last page.

### `search_in_dossier` (~102 tokens)

Search the attachments

Full-text search over the attachments of one dossier, accents and case ignored, matching per term. Returns the attachment, the page and a snippet for each hit; read the page with read_attachment before quoting it.

Input parameters:

- `dossierId` (string, required): The dossier whose attachments to search.
- `keyword` (string, required): Search terms in the language of the documents.
- `limit` (integer): Maximum number of hits, 1 to 30. Default 10.

### `add_attachment` (~112 tokens)

Add an attachment

Upload a file from the user's machine to a dossier as a project attachment: PDF, DOCX, XLSX, PNG, JPEG, TXT, MD or CSV, 25 MB at most, 20 per dossier. Its text is extracted for read_attachment and search_in_dossier. Only upload a file the user named; never look for one.

Input parameters:

- `dossierId` (string, required): The dossier to attach the file to.
- `filePath` (string, required): Absolute path of the file on the machine running this server.

### `list_templates` (~55 tokens)

Dossier templates

List the checklists the organization wrote for its types of structure: each is a set of questions with the clauses they rest on. When a new project matches one, apply it with apply_template instead of opening the questions one by one.

### `apply_template` (~102 tokens)

Apply a template

Open the questions of a template in a dossier, with their options, and file every clause that resolves in the corpus as a reference under its question. A clause the organization cannot read is written into the question as something to establish. Applying twice opens nothing twice. Tell the user which questions were opened and which clauses are missing.

Input parameters:

- `dossierId` (string, required): The dossier to apply the template to.
- `templateId` (string, required): The template, from list_templates.

### `draft_deliverable` (~240 tokens)

Open a deliverable

Open the document a dossier produces (project basis, use agreement, geotechnical report, verification note) and get its plan: one section per chapter, each with a path, a title, a hint of what belongs there, its state (gap, current, stale: a source moved, the reason says which) and an excerpt of what is already written. The plan is the office template of that kind when the organization validated one, otherwise the plan Stratta proposes. Also returns the decisions of the dossier with their ids, so write_section can cite them, and the site id when there is one. Calling it again on the same dossier and kind returns the same document, never a second one. Then read the skill of that kind (get_skill) and fill the sections with write_section.

Input parameters:

- `dossierId` (string, required): The dossier the document belongs to (open_dossier, load_dossier).
- `kind` (string, required): project_basis, use_agreement, geotechnical_report, verification_note or other.
- `title` (string): The document title, when the user named it. Default: the kind and the dossier name.

### `write_section` (~329 tokens)

Write a section

Write one section of a deliverable: the text as it would be printed, and what it rests on. Every sentence of substance must come from a source the section lists: a decision or piece of evidence of the dossier (kind entry, ref the entry id), a fact of the site (kind fact, ref the fact key such as groundwater.level), a clause read in a norm (kind clause, ref the norm code and the section path as in "SIA 267 s. 9.5.2"), a page of an attachment (kind attachment, ref the id with #p12), or a reading recorded by the dossier (kind reading, ref the reading id). A section without a source is refused; when the dossier has nothing for it, pass gap true with a body that says what is missing, and the section is printed as something to establish. Replaces what the section held. Never invent a value: a section written from memory is worse than a gap.

Input parameters:

- `body` (string, required): The text of the section, plain prose or Markdown, up to 12000 characters.
- `deliverableId` (string, required): The deliverable id from draft_deliverable.
- `gap` (boolean): true when the dossier holds nothing for this section; the body then says what is missing.
- `path` (string, required): The section path from the plan, e.g. "2" or "3.1".
- `sources` (array, required): What the text rests on: a list of {kind, ref, label?}. At least one unless gap is true.

### `list_questions` (~189 tokens)

Questions of a dossier

List the questions of one dossier, one page at a time, with what the dossier page shows and load_dossier leaves out: status, assignee, due date, author, dates, the options with their ids and the title of the decision when there is one. Filter by status to read only what is still open. Use load_dossier to resume a project; use this to work through the questions one by one, then get_question on the one that matters. When nextCursor is present, call again with it as cursor.

Input parameters:

- `cursor` (string): The nextCursor of the previous page.
- `dossierId` (string, required): Dossier id from list_dossiers, open_dossier or load_dossier.
- `limit` (integer): Questions per page, 1 to 200. Default 50.
- `status` (string): open, decided or closed. Omit for all.

### `get_question` (~173 tokens)

Read a question

Read one question in full: its wording, status, assignee and due date, its options with their ids and descriptions, its decision, its evidence with every citation field (norm, section path, page, edition, attachment and page) and the option each piece argues for, and the comments left on the question and on those entries, each with its author. Evidence comes one page at a time; when nextCursor is present, call again with it as cursor. Call it before adding to a question, so nothing already filed is filed twice.

Input parameters:

- `cursor` (string): The nextCursor of the previous page.
- `limit` (integer): Evidence entries per page, 1 to 200. Default 50.
- `questionId` (string, required): Question id from list_questions, load_dossier or open_question.

### `get_dossier_activity` (~159 tokens)

Dossier history

Read the history of a dossier, most recent first: who did what and when, from the app or from an agent, each line with the question or the entry it concerns. Call it when the user asks what changed since they last looked, or who took a decision. One page at a time; when nextCursor is present, call again with it as cursor. Lines are written in French at the time of the act, as the app shows them.

Input parameters:

- `cursor` (string): The nextCursor of the previous page.
- `dossierId` (string, required): Dossier id from list_dossiers, open_dossier or load_dossier.
- `limit` (integer): Lines per page, 1 to 200. Default 50.

### `list_exports` (~106 tokens)

Dossier exports

List the verification notes and journals exported from a dossier, most recent first, each with its kind, its SHA-256 hash, its size, the trusted timestamp when one was obtained, the methodology version in force and who exported it. The file itself is downloaded from the dossier page; this tells the user whether a note exists and lets them check a hash they were sent.

Input parameters:

- `dossierId` (string, required): Dossier id from list_dossiers, open_dossier or load_dossier.

### `update_question` (~194 tokens)

Edit a question

Reword a question, rewrite what is at stake, assign it to a member of the organization by e-mail address, or set its due date. Pass only the fields to change; pass null to clear the assignee or the due date. Status changes go through record_decision and resolve_question, not here. Only when the user asked for the change.

Input parameters:

- `assigneeEmail`: E-mail address of the member to assign the question to. null removes the assignee.
- `body` (string): What is at stake and why it is not settled, in two or three sentences.
- `dueAt`: Due date as an ISO day, e.g. "2026-10-15". null removes it.
- `questionId` (string, required): Question id from list_questions, load_dossier or open_question.
- `title` (string): The new wording, in one line, phrased as a question.

### `add_option` (~151 tokens)

Add an option

Add one way of settling a question, when a new variant comes up after the question was opened: "keep the pile and test it", "redistribute on P37 and P39". Idempotent on the name: the same words return the option already listed. Ten options per question at most. Returns optionId and created; the option is retained later by record_decision or update_option.

Input parameters:

- `description` (string): What the option implies, in one or two sentences, when the name is not enough.
- `name` (string, required): The option in a few words, as it will be listed.
- `questionId` (string, required): Question id from list_questions, load_dossier or open_question.

### `update_option` (~144 tokens)

Edit an option

Rename or describe an option of a question, or mark it as the one retained. Retaining one option releases the others: a choice has one answer. Retaining here does not settle the question; call record_decision for that, which names the retained option itself. Pass only the fields to change. Only when the user asked for it.

Input parameters:

- `description` (string): The new description.
- `name` (string): The new name of the option.
- `optionId` (string, required): Option id from get_question, list_questions or load_dossier.
- `retained` (boolean): true to mark this option as retained (the others are released), false to release it.

### `delete_option` (~118 tokens)

Remove an option

Remove an option from a question. The evidence filed under it stays on the question, unfiled from the option. Irreversible: the user is asked to confirm through the client before anything is removed, unless confirmed is true because they already did in the conversation. Only when the user asked for it.

Input parameters:

- `confirmed` (boolean): true when the user already confirmed the removal in the conversation. Otherwise the user is asked through the client first.
- `optionId` (string, required): Option id from get_question, list_questions or load_dossier.

### `delete_question` (~148 tokens)

Delete a question

Delete a question from a dossier. Its evidence stays in the dossier, unfiled; its options, its comments and its decision entry go with it. Irreversible: the user is asked to confirm through the client before anything is deleted, unless confirmed is true because they already did in the conversation. A question that stopped mattering is closed with resolve_question, not deleted; delete only a question opened by mistake, and only when the user says so.

Input parameters:

- `confirmed` (boolean): true when the user already confirmed the deletion in the conversation. Otherwise the user is asked through the client first.
- `questionId` (string, required): Question id from list_questions, load_dossier or open_question.

### `add_comment` (~172 tokens)

Leave a comment

Leave a remark on a dossier, on one of its questions or on one entry: a doubt about a value, a note for a colleague, the reason a piece of evidence was set aside. A comment is signed by the connected user and is not evidence: what the norm says goes through save_finding. A signed-off dossier still takes comments. Write it in the user's own words, only when they asked to leave one.

Input parameters:

- `body` (string, required): The comment, one to a few sentences.
- `dossierId` (string, required): Dossier id from list_dossiers, open_dossier or load_dossier.
- `entryId` (string): The entry the comment is about, when it is about one.
- `questionId` (string): The question the comment is about, when it is about one.

### `update_entry` (~259 tokens)

Correct an entry

Correct a piece of evidence or a decision in place: its wording, its value, its confidence, or the citation (norm, section path, page, edition) when the wrong clause was cited. Pass only the fields to change; pass page null to clear it. The kind and the question do not change here (attach_entry moves an entry). Only when the user asked for the correction; never rewrite what a colleague wrote without saying so.

Input parameters:

- `confidence` (string): established, judgement or to_confirm.
- `detail` (string): The reasoning in one or two sentences.
- `entryId` (string, required): Entry id from get_question or load_dossier.
- `normCode` (string): Norm the entry comes from, e.g. "SIA 267".
- `normEdition` (string): The edition cited, e.g. "2013".
- `page`: Page in the norm. null clears it.
- `sectionPath` (string): Exact section path, e.g. "9.5.2.1".
- `title` (string): The fact in one line, as an engineer would write it in a report.
- `value` (string): The retained value with its unit, e.g. "30 deg".

### `attach_entry` (~139 tokens)

File an entry under a question

File a piece of evidence under a question, and optionally under one of its options, or take it out with questionId null. This is how the unclassified evidence of load_dossier gets sorted: read it, find the question it answers, file it. A decision cannot be moved: it belongs to its question. Filing where it already is changes nothing.

Input parameters:

- `entryId` (string, required): Entry id from get_question or load_dossier.
- `optionId`: The option of that question the entry argues for, or null for none.
- `questionId` (required): The question to file the entry under, or null to leave it unfiled.

### `list_skills` (~102 tokens)

Skills of this workspace

List the procedures an agent follows for a repeatable task: drafting the project basis or the use agreement, writing the terrain chapter of a report, instructing a type of structure, preparing a review, the verification note. Two sources: the skills Stratta ships and the ones this organization wrote; an organization skill with the same name replaces the Stratta one. Call it before drafting a document or starting such a task, then get_skill on the one that fits.

### `get_skill` (~95 tokens)

Read a skill

Read one skill in full: Markdown with its front matter (name, description) and its steps, rules and output shape. Follow it for the task at hand. The organization version wins over the Stratta version of the same name, because it carries the office plan and wording. Returns null when no skill has that name.

Input parameters:

- `name` (string, required): The skill name from list_skills, e.g. "project-basis".

### `ingest_status` (~71 tokens)

Check ingestion status

Check whether a norm (by code) already exists in the workspace, how many sections it has and its coverage score (how much of the norm actually made it in). Call it before creating a document, to avoid duplicates.

Input parameters:

- `code` (string, required): Norm code, e.g. "SIA 261".

### `ingest_create_document` (~190 tokens)

Create norm document

Create a draft norm document in the workspace. Returns the documentId every later ingest_* call needs. The organization must hold a licence for the norm it ingests.

Input parameters:

- `code` (string, required): Norm code as printed on the cover, e.g. "SIA 118/262".
- `edition` (string): Edition label as printed on the cover ("2013", "2020+C1"). Default: the year. Lets several editions of one code coexist.
- `language` (string, required): fr, de, it or en.
- `scope` (string): One sentence on what the norm covers, shown in the served methodology, e.g. "Geotechnical design: foundations, piles, anchors, retaining structures".
- `title` (string, required): Full title of the norm.
- `totalPages` (integer, required): Page count of the source PDF.
- `year` (integer, required): Edition year.

### `ingest_create_sections` (~96 tokens)

Create sections

Insert sections into a draft document, up to 200 per call. Parent links resolve through parentNodeId within the batch and the sections already inserted. Returns a map of nodeId to sectionId; use those ids to attach formulas, tables, figures and cross-references.

Input parameters:

- `documentId` (string, required): Document id from ingest_create_document.
- `sections` (array, required): The sections to insert, as the pre-pass produced them.

### `ingest_attach_formula` (~114 tokens)

Attach formula

Attach a LaTeX formula to a section. Idempotent on the formula number: attaching the same number twice updates the formula instead of doubling it.

Input parameters:

- `description` (string, required): What the formula computes, in one sentence.
- `formulaNumber` (string, required): The number as printed, e.g. "(9.12)". Empty when the norm prints none.
- `latex` (string, required): The formula in LaTeX, without dollar signs.
- `sectionId` (string, required): Section id from ingest_create_sections.

### `ingest_attach_table` (~96 tokens)

Attach table

Attach a structured table to a section: headers plus rows of cells. Idempotent on the table number.

Input parameters:

- `caption` (string, required): The caption as printed.
- `data` (object, required): An object with headers (string[]) and rows (string[][]).
- `sectionId` (string, required): Section id from ingest_create_sections.
- `tableNumber` (string, required): The number as printed, e.g. "Tableau 3".

### `ingest_attach_cross_ref` (~128 tokens)

Attach cross-reference

Attach a cross-reference from a section to another norm or clause. Idempotent on the target and the reference text.

Input parameters:

- `refText` (string, required): The reference as written in the text.
- `refType` (string, required): explicit when the text names the norm, implicit otherwise.
- `sourceSectionId` (string, required): Section id the reference is made from.
- `targetDocumentCode` (string, required): Code of the norm referred to, e.g. "SIA 263".
- `targetSectionPath` (string): Section path in the target norm, when the text names one.

### `ingest_upload_figure` (~124 tokens)

Upload figure

Upload a figure image (base64) and attach it to a section in one call. PNG, JPEG or WebP, 8 MB at most.

Input parameters:

- `base64` (string, required): Base64-encoded image bytes; a data: prefix is tolerated.
- `caption` (string, required): The caption as printed.
- `figureNumber` (string, required): The number as printed, e.g. "Figure 12".
- `mimeType` (string, required): image/png, image/jpeg or image/webp.
- `sectionId` (string, required): Section id from ingest_create_sections.

### `ingest_normalize_cross_refs` (~57 tokens)

Normalize cross-references

Scan every section of a document and rebuild the cross-references to other norms found in its text. Idempotent; run it after the sections are in.

Input parameters:

- `documentId` (string, required): Document id from ingest_create_document.

### `ingest_publish` (~131 tokens)

Publish document

Publish a draft document so the read tools can serve it. The server first scores how much of the norm was ingested (text per page, pages under a chapter, empty leaves, enrichment) and refuses below 30/100 with the reasons: fix the ingestion rather than retry. Pass force true only when the user confirms the document really is that short. Returns the score and its flags; show them to the user.

Input parameters:

- `documentId` (string, required): Document id from ingest_create_document.
- `force` (boolean): Publish even below the coverage floor. Only after the user confirmed the document is legitimately thin.

### `ingest_delete` (~117 tokens)

Delete document

Delete a document of your own organization with all its sections, figures, tables, formulas and cross-references. Irreversible; use it before re-ingesting a norm. The user is asked to confirm through the client before anything is deleted, unless confirmed is true because they already did in the conversation. Reserved to owners and administrators.

Input parameters:

- `confirmed` (boolean): true when the user already confirmed the deletion in the conversation. Otherwise the user is asked through the client first.
- `documentId` (string, required): Document id from ingest_create_document.

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/ch-stratta-mcp/stratta-mcp#diagnostics

## Score history

- 2026-09-25: 76
- 2026-09-24: 75
- 2026-09-23: 75
- 2026-09-22: 75
- 2026-09-21: 74
- 2026-09-20: 74
- 2026-09-19: 73
- 2026-09-18: 73
- 2026-09-17: 72
- 2026-09-16: 72
- 2026-09-15: 71
- 2026-09-14: 56
- 2026-09-13: 71
- 2026-09-12: 70
- 2026-09-11: 55
- 2026-09-10: 54
- 2026-09-09: 69
- 2026-09-08: 68
- 2026-09-07: 68
- 2026-09-06: 67
- 2026-09-05: 41
- 2026-09-04: 65
- 2026-09-03: 65
- 2026-09-02: 64
- 2026-09-01: 64
- 2026-08-31: 63
- 2026-08-30: 63
- 2026-08-29: 62
- 2026-08-28: 62
- 2026-08-27: 36

## Common questions

### What is the Stratta MCP server?

Stratta is an MCP server listed in the public MCP registry as ch.stratta/mcp. Read the SIA and Eurocode norms your bureau licensed, cited to the section and page. This page covers its npm package (@stratta/mcp).

### Is the Stratta MCP server safe to use?

Stratta scores 76 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 25 September 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Stratta MCP server expose?

Stratta exposes 59 tools: get_methodology, whoami, list_norms, get_toc, get_subtree, and 54 more. Their descriptions and schemas cost roughly 9,196 tokens of context every time the server is loaded.

### Is the Stratta MCP server still maintained?

Stratta is still listed as active in the MCP registry. We last reached this channel on 25 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Stratta MCP server under?

Stratta declares the UNLICENSED licence, which is not on the OSI-approved list. Read the terms before using it at work, and note this covers the source only, not the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/@stratta/mcp
- Socket report: https://socket.dev/npm/package/@stratta/mcp
- Website: https://stratta.ch/
- Changelog RSS feed: https://verifymcp.io/servers/ch-stratta-mcp/stratta-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/ch-stratta-mcp/stratta-mcp.json
- HTML version of this page: https://verifymcp.io/servers/ch-stratta-mcp/stratta-mcp
