Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
groups:
go-deps:
patterns: ["*"]

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
groups:
actions:
patterns: ["*"]
79 changes: 79 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- run: go vet ./...
- name: Run tests with coverage
run: go test -race -count=1 -covermode=atomic -coverprofile=coverage.out -v ./...
- name: Coverage summary
run: go tool cover -func=coverage.out | tail -n 1

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
with:
version: v2.12.2

smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- name: Run end-to-end MCP smoke test (uses Python fake PDP)
run: make smoke

vuln:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
- name: govulncheck (Go module CVE scan)
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: osv-scanner (OSV.dev cross-ecosystem scan)
uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
with:
scan-args: |-
--recursive
./

actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- name: Lint GitHub Actions workflows
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8 # v2.1.2
39 changes: 39 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: release

on:
push:
tags:
- 'v*'

permissions:
contents: write
# Required for cosign keyless signing via GitHub OIDC
id-token: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod

- name: Install syft
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # v0.20.6

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57 changes: 57 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
version: 2

project_name: mcp-authzen

before:
hooks:
# `verify` checks checksums without mutating go.mod / go.sum, so a
# release stays bit-reproducible. CI / dev should run `go mod tidy`.
- go mod verify

builds:
- id: mcp-authzen
binary: mcp-authzen
main: ./
goos:
- darwin
- linux
Comment thread
kanywst marked this conversation as resolved.
- windows
goarch:
- amd64
- arm64
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X main.version={{.Version}}

archives:
- format_overrides:
- goos: windows
formats: [zip]
formats:
- tar.gz
name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}"
files:
- LICENSE
- README.md

checksum:
name_template: "{{ .ProjectName }}-{{ .Version }}-checksums.txt"

release:
prerelease: auto

sboms:
- artifacts: archive

signs:
- cmd: cosign
certificate: "${artifact}.pem"
args:
- "sign-blob"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes"
artifacts: checksum
output: true
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BIN := mcp-authzen
PKG := ./...

.PHONY: build test vet lint smoke clean fmt tidy

build:
go build -trimpath -ldflags "-s -w" -o $(BIN) .

test:
go test -race -count=1 -v $(PKG)

vet:
go vet $(PKG)

lint:
golangci-lint run $(PKG)

smoke: build
./scripts/smoke.sh ./$(BIN)

fmt:
gofmt -s -w .

tidy:
go mod tidy

clean:
rm -f $(BIN)
rm -rf dist/
93 changes: 59 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,38 @@

[![ci](https://github.com/0-draft/mcp-authzen/actions/workflows/ci.yml/badge.svg)](https://github.com/0-draft/mcp-authzen/actions/workflows/ci.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/0-draft/mcp-authzen.svg)](https://pkg.go.dev/github.com/0-draft/mcp-authzen)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

A [Model Context Protocol](https://modelcontextprotocol.io) server that fronts an [OpenID AuthZEN 1.0](https://openid.net/specs/authorization-api-1_0.html) Policy Decision Point (PDP).
[MCP](https://modelcontextprotocol.io) server that fronts an [OpenID AuthZEN 1.0](https://openid.net/specs/authorization-api-1_0.html) PDP.

Lets an LLM agent ask **"can subject S perform action A on resource R?"** and have the answer come from a real authorization decision point — your OPA-AuthZEN, your Topaz, your in-house PDP — instead of from the model's training data.
Sends a `subject + resource + action + context` bundle to a real Policy Decision Point (OPA-AuthZEN, Topaz, your own) and returns the decision. Use it when "can alice delete this?" should be answered by policy code, not by the model's guess.

## Tool
Conforms to AuthZEN 1.0 §6 (single-evaluation request/response) and §5.5 (decision entity). Batch evaluation (§7) is not yet exposed as a tool — file an issue if you need it.

### `authzen_evaluate`

POST the bundle to the configured PDP's `/access/v1/evaluation` (or equivalent) endpoint, return the decision.

| Parameter | Required | Description |
| ----------- | -------- | ----------------------------------------------------------------------------------------------- |
| `subject` | yes | JSON object: `{"type": "user", "id": "alice", "properties": {...}}` |
| `resource` | yes | JSON object: `{"type": "document", "id": "doc-1", "properties": {...}}` |
| `action` | yes | JSON object: `{"name": "read", "properties": {...}}` |
| `context` | no | Free-form JSON object with runtime context (IP, time, MFA strength, etc). |
| `pdp_url` | no | Override the default PDP URL for this call. |
## Install

Returns AuthZEN's `{"decision": <bool>, "context": {...}}` as JSON.
```bash
go install github.com/0-draft/mcp-authzen@latest
```

## Configuration
Pre-built signed binaries are on the [releases page](https://github.com/0-draft/mcp-authzen/releases).

Set the default PDP endpoint via env:
## Quickstart

```bash
# Point at a real PDP (or your local opa-authzen-plugin on :8181)
export AUTHZEN_PDP_URL=http://localhost:8181/access/v1/evaluation
```

Or pass `pdp_url` on every call. If neither is set, the tool returns an error.

## Install
# Optional: PDP behind auth. Bare values are auto-prefixed with "Bearer ";
# values starting with "Bearer " or "Basic " pass through verbatim.
# export AUTHZEN_PDP_TOKEN=eyJhbGciOi...

```bash
go install github.com/0-draft/mcp-authzen@latest
# Run the smoke test (spins up an in-process fake PDP, exercises the
# full MCP handshake, asserts the decision is forwarded correctly)
make smoke
```

Or grab a signed binary from the [releases page](https://github.com/0-draft/mcp-authzen/releases).

## Use with Claude Code
## Wire it to Claude Code

```bash
AUTHZEN_PDP_URL=http://localhost:8181/access/v1/evaluation \
Expand All @@ -52,11 +44,11 @@ Then in a session:

> Can `alice` (role=admin) `delete` `doc-42` (owner=bob)?

Claude builds the AuthZEN request, calls `authzen_evaluate`, returns the PDP's verdict — auditable, deterministic, and your policy never leaves your PDP.
The model builds the AuthZEN bundle, calls `authzen_evaluate`, returns the PDP's decision.

## Use with Cursor / other MCP clients
## Wire it to Cursor / other clients

```json
```jsonc
{
"mcpServers": {
"authzen": {
Expand All @@ -67,17 +59,50 @@ Claude builds the AuthZEN request, calls `authzen_evaluate`, returns the PDP's v
}
```

## Test against a local PDP
## Tool: `authzen_evaluate`

| Param | Required | Description |
| ---------- | -------- | -------------------------------------------------------------------------- |
| `subject` | yes | JSON object. AuthZEN §5.1, e.g. `{"type":"user","id":"alice"}`. |
| `resource` | yes | JSON object. AuthZEN §5.2, e.g. `{"type":"doc","id":"doc-1"}`. |
| `action` | yes | JSON object. AuthZEN §5.3, e.g. `{"name":"read"}`. |
| `context` | no | JSON object with runtime context. AuthZEN §5.4. |
| `pdp_url` | no | Override `AUTHZEN_PDP_URL` for this call. |

Returns AuthZEN's `{"decision": <bool>, "context": {...}}` as JSON.

## Test against opa-authzen-plugin

Spin up [`opa-authzen-plugin`](https://github.com/kanywst/opa-authzen-plugin) (or any AuthZEN PDP) on `:8181`, then:
[`kanywst/opa-authzen-plugin`](https://github.com/kanywst/opa-authzen-plugin) is a reference AuthZEN PDP built on OPA.

```bash
AUTHZEN_PDP_URL=http://localhost:8181/access/v1/evaluation mcp-authzen
# In one terminal — start the PDP on :8181
git clone https://github.com/kanywst/opa-authzen-plugin
cd opa-authzen-plugin && make run

# In another — wire mcp-authzen
export AUTHZEN_PDP_URL=http://localhost:8181/access/v1/evaluation
mcp-authzen
```

## Layout

Flat by design. A single-binary MCP server with one tool does not need
`cmd/`, `internal/`, or `pkg/`. When batch (`/access/v1/evaluations`) or
the search APIs (§8) land, they get sibling files, not subpackages.

```text
.
├── main.go # server bootstrap + tool registration
├── main_test.go # httptest-driven PDP round-trips
├── scripts/smoke.sh
├── .goreleaser.yml
└── .github/
```

## Verifying a release
## Verify a release

Each release ships a `cosign`-signed checksum file (keyless, Sigstore via GitHub OIDC) and a CycloneDX SBOM. To verify before installing:
Releases ship a `cosign`-signed checksum file (Sigstore keyless via GitHub OIDC) and a CycloneDX SBOM per archive.

```bash
TAG=v0.1.0
Expand Down
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/0-draft/mcp-authzen

go 1.26.3
Comment thread
kanywst marked this conversation as resolved.

require github.com/mark3labs/mcp-go v0.54.1

require (
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/text v0.14.0 // indirect
)
Loading
Loading