ApiDocs
by alfaizmac
README.md
# MCP Resources Server Demo
A Laravel ([`laravel/mcp`](https://github.com/laravel/mcp)) demo of an MCP
server that exposes **exactly one resource** — `api-docs.json` — behind three
independent security controls:
1. **mTLS** — a local Caddy reverse proxy requires and verifies a client
certificate before any request reaches Laravel.
2. **OAuth 2.1 + PKCE** — Laravel Passport (embedded Authorization Server)
issues access tokens via the Authorization Code grant with mandatory PKCE
(S256), using `laravel/mcp`'s built-in OAuth discovery and dynamic client
registration.
3. **Granular scope control** — the resource only appears in `resources/list`
(and only serves content in `resources/read`) for tokens carrying the
`mcp:read-api-docs` scope. A token without it sees an empty resource list —
it can't discover the resource exists, let alone read it.
This is a precursor demo for a larger MCP Resources Server project — see
[docs/MCP_DEMO.md](docs/MCP_DEMO.md) for the full architecture writeup.
## Setup
```bash
composer install
npm install && npm run build
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan passport:keys
php artisan db:seed
```
`php artisan db:seed` creates the demo user used to complete the OAuth
consent step: `test@example.com` / `password`.
Replace `storage/app/private/api-docs.json` with the real file you want the
resource to serve, if you haven't already.
## Testing the demo
You need three terminals.
**Terminal 1 — Laravel:**
```bash
php artisan serve --port=8000
```
**Terminal 2 — Caddy (mTLS-terminating proxy, `localhost:8443` → `127.0.0.1:8000`):**
```bash
cd deploy/mtls
./bin/caddy.exe run --config Caddyfile --adapter caddyfile
```
**Terminal 3 — run the scripted end-to-end client:**
```bash
php deploy/mtls/demo-client.php
```
This plays the role of a real MCP client: registers itself via
`POST /oauth/register`, generates a PKCE `code_verifier`/`code_challenge`,
logs in as the seeded demo user, submits the consent screen, exchanges the
code for a token, then calls the MCP endpoint over the mTLS connection with
`resources/list` and `resources/read`. You should see `api-docs.json` (and
only that) listed, with its contents returned.
Run it again with the flag below to request only the base `mcp:use` scope
(no `mcp:read-api-docs`) and watch the resource disappear from the list —
proof the scope gate hides it rather than just rejecting reads:
```bash
php deploy/mtls/demo-client.php --no-scope
```
For negative tests (no client cert, no bearer token) and a manual
browser-based walkthrough of the consent screen, see
[docs/MCP_DEMO.md](docs/MCP_DEMO.md).
## Key files
| File | Purpose |
|---|---|
| [routes/ai.php](routes/ai.php) | Registers `Mcp::oauthRoutes()` and the protected `/mcp/api-docs` endpoint |
| [app/Mcp/Servers/ApiDocsServer.php](app/Mcp/Servers/ApiDocsServer.php) | The MCP server — only registers `ApiDocsResource` |
| [app/Mcp/Resources/ApiDocsResource.php](app/Mcp/Resources/ApiDocsResource.php) | The scope gate (`shouldRegister()`) and file read |
| [app/Http/Middleware/EnsureMutualTlsVerified.php](app/Http/Middleware/EnsureMutualTlsVerified.php) | App-level mTLS check |
| [deploy/mtls/Caddyfile](deploy/mtls/Caddyfile) | The actual mTLS enforcement |
| [deploy/mtls/demo-client.php](deploy/mtls/demo-client.php) | Scripted end-to-end test client |
## Status
This is demo-grade, not production-grade: self-signed local certs, Passport
running as an embedded Authorization Server (a real deployment would more
likely delegate to a standalone IdP), and no automated PHPUnit coverage for
the new mTLS/OAuth/scope wiring — it's been verified by actually running the
flow, not by a test suite. See the "Notes for the real project" section of
[docs/MCP_DEMO.md](docs/MCP_DEMO.md) for what carries over.
---
Built on the [Laravel](https://laravel.com) framework, licensed
[MIT](https://opensource.org/licenses/MIT).
# MCP-Demo
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues