Skip to main content
Glama
sn0bored

Permissioned MCP Server

by sn0bored
README.md
# Permissioned MCP Server

[![CI](https://github.com/sn0bored/permissioned-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/sn0bored/permissioned-mcp-server/actions/workflows/ci.yml)

A compact Model Context Protocol server that demonstrates the parts usually missing from quickstarts: explicit tool boundaries, least-privilege discovery, execution-time authorization, destructive-action confirmation, and metadata-only audit logs.

The domain is deliberately boring: a local note store. The reference is about designing a safe boundary between an AI agent and real side effects.

## What it demonstrates

- JavaScript on Node.js with the official MCP SDK v2
- Narrow read, write, and destructive tools
- Separate `notes:read`, `notes:write`, and `notes:admin` scopes
- Unauthorized tools omitted from `tools/list` and rejected again at execution
- Identifier-bound confirmation for destructive actions
- Structured tool results and MCP behavior annotations
- Audit events that never log arguments, content, or credentials
- Atomic local persistence with restrictive file permissions
- Tests for policy, handlers, persistence, and failure cases

## Run it

Requires Node.js twenty or newer.

```bash
npm install
cp .env.example .env
MCP_SCOPES=notes:read,notes:write npm start
```

Example client configuration:

```json
{
  "mcpServers": {
    "permissioned-notes": {
      "command": "node",
      "args": ["/absolute/path/to/permissioned-mcp-server/src/server.js"],
      "env": {
        "MCP_SCOPES": "notes:read,notes:write",
        "MCP_DATA_FILE": "/absolute/path/to/notes.json",
        "MCP_ACTOR": "local-agent"
      }
    }
  }
}
```

Start read-only. Grant `notes:write` only when mutation is necessary. Keep `notes:admin` out of the default configuration.

## Tool boundary

| Tool | Scope | Side effect |
| --- | --- | --- |
| `notes.list` | `notes:read` | Returns metadata only |
| `notes.get` | `notes:read` | Reads one note |
| `notes.create` | `notes:write` | Creates one note |
| `notes.delete` | `notes:admin` | Permanently deletes one note |

See [the decision record](docs/DECISIONS.md) for the reasoning behind discovery filtering, double authorization, confirmation design, audit redaction, transport choice, and storage isolation.

## Verify it

```bash
npm run check
npm test
```

The test suite never starts a model or calls a paid API.

## Production notes

This is a local stdio reference, not a turnkey hosted authorization server. Before exposing an MCP server over Streamable HTTP, add OAuth-based authorization, token audience validation, HTTPS, rate limits, tenant isolation, durable audit storage, and client-specific consent.

## License

MIT

---

Built by [Lanier](https://lanierdev.com), an applied AI studio. More free
tools at [lanierdev.com/tools](https://lanierdev.com/tools).

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: list returns metadata, get returns a full note, and create adds a new note. No overlap in functionality.

Naming Consistency5/5

All tools follow the consistent pattern 'notes.<verb>', using simple, descriptive verbs (list, get, create). No naming style deviations.

Tool Count5/5

Three tools is a reasonable scope for a basic note management server. It covers core operations without being too sparse or excessive.

Completeness3/5

The set supports listing, reading, and creating notes but lacks update and delete operations. While a minimal viable set, these missing operations are notable gaps for typical CRUD coverage.

Maintenance

ActivitySlowing
ResponsivenessUnresponsive