Nettskjema MCP Server
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| NETTSKJEMA_CLIENT_ID | Yes | Your Nettskjema API client ID | |
| NETTSKJEMA_CLIENT_SECRET | Yes | Your Nettskjema API client secret |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| extensions | {
"io.modelcontextprotocol/ui": {}
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| activate_tsdB | Activate TSD (Services for Sensitive Data) for a form. The token parameter is a credential that authorizes activating TSD for the named project. It must be kept as secure as the OAuth2 access token and is never logged or included in error messages. The API returns 200 with no response body on success. |
| activate_educloudA | Activate Educloud for a form. The token parameter is a credential that authorizes activating Educloud for the named project. It must be kept as secure as the OAuth2 access token and is never logged or included in error messages. The API returns 200 with no response body on success. |
| upload_imageB | Upload an image to the API, returning a response with string keys and values. The entire file is read into memory, base64-encoded, and sent as a JSON string in the request body, per the published OpenAPI schema ({"type": "string", "format": "byte"} under application/json). The filename is derived from the file itself and sent as a query parameter. The body format is most likely a Spring Boot serialisation artefact where a byte[] parameter was rendered as format: byte. The actual server may expect raw bytes; whether it accepts the JSON string can only be settled end-to-end. |
| get_imageA | Download an image to a file, written to disk. The image is streamed to disk and never inlined. The returned path is absolute and tilde-expanded. An existing file at the output path is silently overwritten, and its mode is preserved. A new file (whether from output_path or a temp file) is created with mode 0600 (readable only by the current user). When output_path is omitted, a temporary file is created in the system temp directory with a suffix derived from the response's media type. The temporary file persists after this call returns and is left on disk for the caller to use or delete. The spec declares the response as / (any media type), so the actual content type is reported as received. |
| get_form_answersA | Stream all answers for a form, as newline-delimited JSON. This exports every answer on the form, one JSON object per line (NDJSON), so a large form's answers are never held whole in memory. Filter by element_ids to export only specific questions. The result is capped at max_rows (default 1000) to bound context size: check the returned truncated field to see if more data exists. When truncated is true, the reason field distinguishes a hit row cap ("rowCap") from a connection that closed mid-line ("incompleteFinalLine"). |
| get_answer_attachmentA | Download an answer attachment to a file, written to disk. The attachment is streamed to disk and never inlined: a multi-megabyte audio recording as base64 is useless to a model. The returned path is absolute and tilde-expanded. An existing file at the output path is silently overwritten, and its mode is preserved. A new file (whether from output_path or a temp file) is created with mode 0600 (readable only by the current user). When output_path is omitted, a temporary file is created in the system temp directory with a suffix derived from the response's media type. The temporary file persists after this call returns and is left on disk for the caller to use or delete. |
| upload_submission_attachmentA | Upload an attachment to a submission question, optionally requesting transcription. The entire file is read into memory before uploading. The filename and fileSize are derived from the file itself rather than accepted as parameters, so they cannot drift out of sync. An audio recording awaiting transcription can be tens of megabytes; the whole file is held in RAM during the upload. The attachment token comes from the attachmentToken field in the response body of POST /form/{formId}/submission. It authorizes writing to someone else's submission and must be kept as secure as the access token itself. The spec does not document what happens when the named questionId already has an attachment. An upload may silently replace a respondent's file. Transcription options apply only when the uploaded file is audio or video. The six supported models are: model_auto, nb_whisper_large_semantic, nb_whisper_large_verbatim, openai_whisper_large_v3, openai_whisper_large_v2, and KBLab-large. The language parameter accepts 100+ language codes; passing an unknown code raises BadRequestError naming the allowed values. The diarize flag requests speaker identification, and translate_to_english requests translation to English after transcription. |
| get_attachment_transcriptionA | Get the transcription of an attachment in JSON, plain text, or WebVTT format. The attachment must have been uploaded with transcription options for this to return meaningful results. A file uploaded without transcription, or a file that is not audio or video, has no transcription to retrieve. For txt and vtt formats, the result is capped at max_chars (default 1,000,000) to bound context size. Check the truncated field to see if the full text was cut. The caller may raise this limit arbitrarily by passing a larger value. |
| retranscribe_attachmentA | Retranscribe an existing attachment with new transcription options. The API overwrites the existing transcription. The attachment must be audio or video; retranscribing a non-media file or a file that was never transcribed has undefined effect. The six supported models are: model_auto, nb_whisper_large_semantic, nb_whisper_large_verbatim, openai_whisper_large_v3, openai_whisper_large_v2, and KBLab-large. The language parameter accepts 100+ language codes; passing an unknown code raises BadRequestError naming the allowed values. The diarize flag requests speaker identification, and translate_to_english requests translation to English after transcription. |
| get_form_elementsA | Get the form's elements: its questions, headings, images and page breaks. Use this before any element edit. The only write endpoint replaces the whole list, so the array returned here is the starting point for a modified list. |
| replace_form_elementsA | Replace the form's ENTIRE element list. Every element you leave out is DELETED. This is not an "add an element" tool. Whatever you send becomes the form's
complete content: any question, heading, image or page break that is not in
Nothing locks the form in between: an edit made by someone else after your get_form_elements call is overwritten by this one without warning. The endpoint offers no version field, ETag or conditional header to detect that. Each element is checked against the vendored ElementDTO schema before the request is sent: elementType is required on every element and must be one of the known types, and every field the schema declares is checked for type and range. A field the schema does not declare is passed through to the API rather than rejected here, so an element read back from a newer API than the vendored snapshot can still be written back unchanged. |
| update_form_elementA | Change one element, by rewriting the form's ENTIRE element list. The API has no single-element update, so this tool reads every element, applies
If no element in the form has the given element_id, nothing is written: a PUT
of the unchanged list would report success while changing nothing. For the
same reason |
| export_form_csvA | Export a form's submission data as CSV. The spec's summary describes the response as a CSV text file with semicolon (;) as the delimiter. When output_path is provided, the file is written to disk (mode 0600 for a new file) and the result reports the path, byte count and media type. When output_path is omitted, the text is returned as JSON with a size signal and truncation flag. The result is capped at max_chars (default 1,000,000) to bound context size: check the truncated field to see if the full export was cut. |
| export_form_excelA | Export a form's submission data to a file, written to disk. The spec documents this route's response as an Excel file. The file is streamed to disk and never inlined: a multi-megabyte spreadsheet as base64 is useless to a model. The returned path is absolute and tilde-expanded. An existing file at the output path is silently overwritten, and its mode is preserved. A new file (whether from output_path or a temp file) is created with mode 0600 (readable only by the current user). When output_path is omitted, a temporary file is created in the system temp directory with a suffix derived from the response's media type. The temporary file persists after this call returns and is left on disk for the caller to use or delete. |
| get_spss_syntaxA | Get SPSS syntax for a form's submission data. The spec documents this route's response as an SPSS syntax file. The syntax is returned as JSON with a size signal and truncation flag. The result is capped at max_chars (default 1,000,000) to bound context size: check the truncated field to see if the full syntax was cut. |
| upload_codebookA | Upload a codebook mapping form elements to external identifiers. The spec does not document whether omitted entries are preserved or deleted. Send the complete mapping you want the form to have: include every element, sub-element and answer option whose external id you want to keep or set. Omitting an entry has undefined effect and may delete that mapping. Each map is validated against the CodebookDTO schema before the request is sent. All three maps are required by the schema: send an empty dict for any map you do not need to populate. |
| list_my_formsB | List all forms owned by the authenticated API client. Returns: An array of form summary objects as JSON. Each object includes formId, title, open (boolean), modifiedDate, lastSubmissionDate, numberOfDeliveredSubmissions, deliveryDestination, and myFormsFormListingGroup. |
| lookup_form_by_titleB | Look up a form by its short title (the URL-safe identifier). |
| get_form_infoB | Get operational metadata about a form: submission counts, open/closed status, and edit permissions. |
| get_form_settingsB | Get the form's configuration: all 51 settings controlling form behavior and permissions. |
| get_form_settings_validationC | Get validation errors or constraints for the form's settings. |
| get_form_definitionC | Get the form's structure and creation history: the elements array and authorship metadata. |
| list_forms_collecting_personal_dataC | List forms that collect personal data, with optional filters. |
| create_formA | Create a new form with the given settings and optional elements. The six required settings are taken as named parameters so a model cannot omit them. Additional settings can be provided via extra_settings. If a required setting appears in extra_settings, the named parameter takes precedence. |
| delete_formsA | Delete one or more forms by their IDs. Deleted forms are moved to the trash and can be restored for a limited time. This operation is destructive and cannot be undone after the trash retention period expires. |
| update_form_settingsA | Update one or more settings on an existing form. This endpoint accepts partial updates: only the settings included in the settings dict will be updated. A 207 Multi-Status response means some settings were applied and others were rejected; this is reported as an error listing the failed settings. The settings dict is validated as a partial FormSettingsDTO before the request is sent. Required-field constraints are relaxed for partial updates, but unknown keys are rejected locally to catch typos and flag fields that are newer than the vendored spec snapshot. |
| remind_all_pending_invitationsA | Send reminder emails to every pending invitation on a form. This operation is all-or-nothing: every sent invitation on the form receives a reminder. There is no endpoint to remind a subset of invitations. Whether it also reaches invitations created through the newer batch interface is not documented anywhere; they may share the same storage, but that is an inference and not something the API states. Creating, viewing and deleting invitations are not available through this MCP server. Those operations are public Nettskjema features — supported, documented, production-ready — but are managed through the web frontend instead. Use the form management interface to handle invitations: https://nettskjema.no/user/form/{formId}/collect-responses/invited/create and https://nettskjema.no/user/form/{formId}/collect-responses/invited/overview |
| get_user_infoB | Get the Nettskjema user record for the authenticated API client. Returns: The user record as JSON. |
| check_auth_statusA | Report which Nettskjema credentials and URLs this server is configured with. Makes no request, so it still answers when authentication is the thing that is broken. The client secret is never included: only whether it is set. Returns: A JSON object describing the current configuration, the names of any missing environment variables, and whether the server is ready to call the API. |
| get_submissionB | Get a single submission with its answers and metadata. |
| list_my_submissionsB | List all submissions made by the authenticated API client. Returns: An array of submission summary objects as JSON. Each object includes submissionId, form (with title), createdDate, modifiedDate, and delivered (boolean). |
| get_submission_pdfA | Download a submission as a PDF file. The PDF is written to disk, not returned inline: a multi-megabyte PDF as base64 is not useful to a model. The returned path is absolute and tilde-expanded. An existing file at the output path is silently overwritten, and its mode is preserved. A new file (whether from output_path or a temp file) is created with mode 0600 (readable only by the current user). When output_path is omitted, a temporary file is created in the system temp directory with a .pdf suffix. The temporary file persists after this call returns and is left on disk for the caller to use or delete. |
| get_submission_metadataA | Stream all submission metadata for a form, as newline-delimited JSON. Returns metadata without answers: use this for bulk export of submission-level fields or to find which submissions exist before fetching their answers. This streams NDJSON (one JSON object per line), so a large form's metadata is never held whole in memory. The result is capped at max_rows to bound context size: check the returned truncated field to see if more data exists. Distinct from get_postponed_submission_metadata, which returns postponed submission metadata only. The relationship between the two result sets (whether this endpoint includes or excludes postponed submissions) is not specified by the API documentation. |
| get_postponed_submission_metadataA | Stream postponed submission metadata for a form, as newline-delimited JSON. This endpoint returns the same metadata structure as get_submission_metadata, but only for postponed submissions. The result is streamed as NDJSON and capped at max_rows to bound context size. Distinct from get_submission_metadata: this endpoint returns postponed submission metadata only, while that one returns all submission metadata for the form. |
| submit_formA | Deliver a response to a form, as the authenticated API client. This submits a real answer to a real form: it adds a row to the form's collected data, and the form's owner sees it alongside human responses. There is no draft or preview mode here. Use submit_form_for_invitation instead when answering on behalf of a specific invitation. Each answer is checked against the vendored ClientAnswerDTO schema before the request is sent, so a bad enum value or a mistyped field fails without reaching the form. A field the snapshot does not declare is passed through to the API, which has the final say. |
| submit_form_for_invitationA | Deliver a response to the form a given invitation was issued for. Same delivery as submit_form, addressed by invitation rather than by form: the response is recorded against that invitation, so the form's owner can see which invited respondent answered. The invitation is identified by its own numeric id, not by the form's. This submits a real answer to a real form; there is no draft or preview mode. Each answer is checked against the vendored ClientAnswerDTO schema before the request is sent. A field the snapshot does not declare is passed through to the API, which has the final say. |
| delete_submissionsA | Delete ONLY the listed responses to a form. Other responses are left alone. This is the scoped delete, and the one to use whenever specific responses are meant: it removes exactly the submissions whose ids are given, and nothing else. Do not confuse it with delete_all_submissions, which deletes every response the form has and takes no id list. Deleted responses are gone; the API offers no undo for them. Get submission ids from get_submission_metadata or list_my_submissions. An empty list is refused rather than sent, because what the API would do with an empty body here is not specified and the cost of guessing wrong is a form's data. |
| delete_all_submissionsA | Delete EVERY response the form has ever collected. This CANNOT be undone. There is no id list and no partial form of this call: the whole dataset goes, for every respondent, and the API offers no undo and no trash can for submissions. Confirm with the user before calling it. To remove particular responses, use delete_submissions, which takes submission ids and leaves the rest of the form's data in place. The spec's plural operation accepts an optional array body, but documents the operation as deleting all submissions and never says the ids narrow it. This tool therefore sends no body and offers no id parameter, rather than a parameter that looks like it limits the damage and may not. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 38 tools
Despite 38 tools, purposes are sharply distinct and descriptions actively call out near-neighbors (e.g. delete_submissions vs delete_all_submissions, get_submission_metadata vs get_postponed_submission_metadata, replace_form_elements vs update_form_element). The cluster of get_form_info/settings/settings_validation/definition tools targets distinct facets (metadata, config, validation, structure) and is well differentiated in prose.
Near-perfect snake_case verb_noun convention throughout: get_*, list_*, create_*, update_*, delete_*, upload_*, export_*, submit_*, activate_*. Pluralization and scoping (delete_forms, delete_submissions, delete_all_submissions) is used meaningfully and consistently.
38 tools is heavy and exceeds the comfortable 15-tool band, spanning forms, elements, settings, submissions, attachments, transcription, exports, invitations, users, auth, and codebook. The domain is genuinely broad and most tools map to distinct endpoints, but the surface is large enough that consolidation (e.g. the several get_form_* readers) would help.
Core lifecycle is well covered: form create/read/delete, element read/replace/update, settings read/update/validate, submission create/get/list/bulk-export/delete, attachment upload/download/transcribe, and multiple export formats. Gaps remain in invitation management (only remind_all_pending_invitations; create/view/delete are acknowledged as unavailable) and codebook is upload-only with no update/delete or form rename.