Skip to main content
Glama
README.md
# nc-forms-mcp

**MCP server for the Nextcloud Forms API v3.** Create, update, clone, and validate
forms β€” without ever leaking an app password into the agent's context.

[![Python](https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square&logo=python)](https://python.org)
[![MCP](https://img.shields.io/badge/MCP-1.28%2B-purple?style=flat-square)](https://github.com/modelcontextprotocol)
[![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)

---

<p align="center">
  <strong>Push Forms API operations into a sealed MCP server.</strong><br>
  <em>Your agent calls tools, not curl. The password never leaves the env.</em>
</p>

---

## 🀨 The Problem

Nextcloud Forms has a REST API at `/ocs/v2.php/apps/forms/api/v3/` that uses
**HTTP Basic Auth with an app password**. When an AI agent calls this API directly:

| Issue | What happens | Consequence |
|-------|-------------|-------------|
| Credential leakage | The password passes through the LLM's context | Stored in logs, prompt caches, conversation history |
| Tool sprawl | Every Forms operation is a raw `curl` invocation | Inconsistent error handling, no validation |
| No isolation | The same credential gets reused for WebDAV, shares, etc. | Scope creep β€” operations outside Forms API |
| Repetition | Auth headers, error parsing, and retry logic in every call | Wasteful tokens and fragile code |

**Bottom line:** credentials in agent context = risk. Raw API calls in conversation = tech debt.

---

## βœ… The Solution

Wrap the Forms API in an **MCP server** that:

1. Reads credentials from **environment variables only** β€” the agent never sees them
2. Exposes **16 focused tools** β€” `forms_get`, `questions_add`, `options_update`, etc.
3. Runs as a **stdio subprocess** managed by the Hermes MCP client
4. Returns structured JSON β€” no parsing needed
5. Validates form health with a single tool call

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   MCP stdio    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   HTTPS Basic Auth   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              β”‚ ──────────────→│                  β”‚ ────────────────────→│                  β”‚
β”‚  Hermes      β”‚ ←──────────────│  nc-forms-mcp    β”‚ ←────────────────────│  Nextcloud Forms  β”‚
β”‚  Agent       β”‚                β”‚  (Python process) β”‚                     β”‚  API v3          β”‚
β”‚              β”‚                β”‚  NC_FORMS_PASSWORDβ”‚                     β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚  (env only,       β”‚                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚   never in chat)  β”‚
                                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## πŸ“¦ Installation

### Prerequisites

- Python 3.10+
- Nextcloud instance with **Forms** app (v5+)
- Nextcloud **app password** (Settings β†’ Security β†’ Devices & sessions)

### Install

```bash
git clone https://github.com/erniomaldo/nc-forms-mcp.git ~/Proyectos/nc-forms-mcp
cd ~/Proyectos/nc-forms-mcp

# Hermes uses its own venv β€” install there:
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python -e .
```

Or use your project's venv:
```bash
uv venv && uv pip install -e .
```

---

## πŸ”§ Hermes Configuration

Add to `~/.hermes/config.yaml`:

```yaml
mcp_servers:
  nc-forms:
    command: "uv"
    args:
      - "run"
      - "--directory"
      - "/home/ernesto-personal/Proyectos/nc-forms-mcp"
      - "nc-forms-mcp"
    env:
      NC_FORMS_USER: "erniomaldo"
      NC_FORMS_PASSWORD: "your-app-password-here"
      NC_FORMS_BASE: "https://base.agendasencilla.com"
    timeout: 30
```

**Restart Hermes** after adding. On startup it auto-discovers 16 tools prefixed
`mcp_nc_forms_*`.

---

## πŸ› οΈ Tools

| Tool | What it does | Use case |
|------|-------------|----------|
| `forms_list` | List all owned forms | Dashboard / overview |
| `forms_get` | Get full form with questions, options, shares | Read current state |
| `forms_create` | Create an empty form | Start a new brief/survey |
| `forms_update` | Update title, description, settings | Iterate on form metadata |
| `forms_clone` | Clone a form (no submissions) | V1 β†’ V2 iteration |
| `forms_delete` | Permanently delete a form | Cleanup |
| `questions_list` | List all questions in a form | Overview |
| `questions_get` | Get a single question by ID | Check specific question |
| `questions_add` | Add a question (multiple choice, text, etc.) | Build form content |
| `questions_update` | Update question text, description, isRequired | Refine wording |
| `questions_delete` | Remove a question | Prune sections |
| `questions_reorder` | Reorder questions by ID array | Reorganize sections |
| `options_add` | Add options to a choice question | Populate answer choices |
| `options_update` | Update a single option's text | Fix typos |
| `options_delete` | Remove an option | Cleanup |
| `forms_validate` | Check all questions have valid optionTypes | QA before publishing |

### Tool naming

MCP prefix: `mcp_nc_forms_` + tool name (underscores replace hyphens).

Example: `mcp_nc_forms_forms_get` β†’ get form details.

---

## πŸš€ Usage (after Hermes integration)

```python
# List all forms
mcp_nc_forms_forms_list()

# Get form 8 with all questions + options
mcp_nc_forms_forms_get({ "form_id": 8 })

# Add a question to form 8
mcp_nc_forms_questions_add({
    "form_id": 8,
    "type": "multiple_unique",
    "text": "🎨 1.2 ¿Colores definidos?"
})

# Add options
mcp_nc_forms_options_add({
    "form_id": 8,
    "question_id": 74,
    "option_texts": ["βœ… De acuerdo", "🟑 Cerca pero ajustes", "❌ No se parece"],
    "option_type": "choice"
})

# Validate before publishing
mcp_nc_forms_forms_validate({ "form_id": 8 })
# β†’ {"healthy": true, "total_questions": 32, "issues": []}
```

---

## πŸ§ͺ CLI Testing (without Hermes)

```bash
export NC_FORMS_USER="erniomaldo"
export NC_FORMS_PASSWORD="your-app-password"
export NC_FORMS_BASE="https://base.agendasencilla.com"

nc-forms-mcp forms_list '{}'
nc-forms-mcp forms_get '{"form_id": 8}'
nc-forms-mcp forms_validate '{"form_id": 8}'
```

---

## πŸ—οΈ Project Structure

```
nc-forms-mcp/
β”œβ”€β”€ pyproject.toml          # Project metadata + dependencies
β”œβ”€β”€ README.md               # This file
β”œβ”€β”€ LICENSE
β”œβ”€β”€ nc_forms_mcp/
β”‚   β”œβ”€β”€ __init__.py         # Package init (re-exports server)
β”‚   β”œβ”€β”€ server.py           # MCP server: auth, route, tools, entry points
└── tests/                  # Coming soon
```

---

## πŸ” Security

- **Credentials live in `env` config** β€” the agent never sees `NC_FORMS_PASSWORD`
- The app password is **scoped to Forms API only** β€” WebDAV, Shares, Calendar,
  Collectives, and other Nextcloud features use their own MCP tools with their
  own auth
- Error messages redact credential-like patterns
- The MCP server process inherits a **filtered environment** (Hermes strips
  secrets from the subprocess env except what you explicitly set in `env`)

### What NOT to do

```yaml
# ❌ WRONG β€” password leaks into agent context
mcp_servers:
  nc-forms:
    command: "python3"
    args: ["server.py", "--password", "TEZiJ-..."]
```

```yaml
# βœ… CORRECT β€” password stays in env
mcp_servers:
  nc-forms:
    command: "uv"
    args: ["run", "--directory", "~/Proyectos/nc-forms-mcp", "nc-forms-mcp"]
    env:
      NC_FORMS_PASSWORD: "TEZiJ-..."
```

---

## πŸ’‘ Why not just use curl?

| Approach | Secret leaks? | Inconsistent errors? | Token waste? | Reusable? |
|----------|:---:|:---:|:---:|:---:|
| Raw curl in conversation | βœ… Always | βœ… Every call | βœ… Headers + parsing | ❌ No |
| Python script in /tmp | βœ… In code | βœ… Per script | βœ… Duplicated logic | ❌ Per task |
| **MCP server** | ❌ Never | ❌ Unified | ❌ One setup | βœ… All tasks |

---

## πŸ“‹ Comparison with alternative approaches

| | nc-forms-mcp | Forms web UI | Direct REST API (curl) | Custom script |
|---|---|---|---|---|
| **Setup** | 5 lines in config.yaml | Zero (browser) | Zero (curl installed) | Python + requests |
| **Speed** | Instant (stdio) | Point-and-click | Fast | Depends on quality |
| **Repeatability** | βœ… Always same tools | ❌ Manual clicks | ❌ Varies per call | βœ… If committed |
| **Validation** | βœ… `forms_validate` built-in | ❌ Manual review | ❌ Never | ❌ Rarely |
| **CI/CD ready** | βœ… Yes | ❌ No | ⚠️ Needs auth wrapper | βœ… If committed |
| **Secret safety** | βœ… In env only | ❌ Browser session | ❌ In command args | ⚠️ In code or env |
| **Token cost** | Low (one discovery) | N/A | High (per call) | Low (per script) |

---

## πŸ”„ Related Projects

- **[agentcheckpoint](https://github.com/erniomaldo/agentcheckpoint)** β€” atomic state store for multi-agent coordination (SQLite + MCP)
- **[Hermes Agent](https://hermes-agent.nousresearch.com/)** β€” the personal AI agent that runs this MCP server

---

## πŸ“„ License

MIT