a4492d49385748974332becc65ebf7e9e8e83f44
JwtBearer 10.0.7 transitively brings in Microsoft.IdentityModel.JsonWebTokens 9.x, which calls Base64UrlEncoder.Decode(ReadOnlySpan<char>, Span<byte>) — an overload only present in Microsoft.IdentityModel.Tokens 9.x. The explicit pin to 8.9.0 forced NuGet to downgrade Tokens to 8.9, producing a runtime MissingMethodException on every JWT validation. Symptom: every incoming request gets 401, never visible at default log level. Drop the explicit pin so JwtBearer's transitive dep wins. The code only uses SymmetricSecurityKey etc., which are stable across versions.
obsidian-mcp
Read and write an Obsidian vault via MCP (Model Context Protocol), with OAuth authentication via nas-auth.
Architecture
Claude.ai / MCP client
│
│ ① GET /.well-known/oauth-authorization-server
│ ② OAuth Authorization Code + PKCE (via nas-auth)
│ ③ Bearer JWT (aud=obsidian, scope=read:obsidian | write:obsidian)
│
▼
obsidian-mcp.zhengchentao.win/mcp (this service, port 9090 → 8080)
│ JWT verify (HS256, shared key with nas-auth)
│ VaultPathResolver — chroot + blacklist
│ VaultWriteGuard — whitelist for writes
│
▼
/vault (Docker volume, backed by WebDAV share synced via Remotely Save)
MCP Tools
| Tool | Auth required | Description |
|---|---|---|
list_vault_tree |
read:obsidian | Depth-limited directory tree of the vault |
list_files |
read:obsidian | Files and subdirs in a directory |
read_file |
read:obsidian | Read file content (UTF-8), with optional byte-range params |
search |
read:obsidian | Literal substring search, glob-filterable |
get_metadata |
read:obsidian | Size, modified_at, has_frontmatter |
write_file |
write:obsidian | Overwrite a whitelisted file |
append_file |
write:obsidian | Append to a whitelisted file |
Configuration (environment variables)
| Variable | Default | Description |
|---|---|---|
Vault__Root |
./test-vault |
Vault root directory inside the container |
Vault__Blacklist__0 |
— | Extra blacklist path segments (01-Secret, .obsidian, .trash, .git are hardcoded) |
Vault__WriteWhitelist__0 |
— | Extra writable path prefixes |
Jwt__Issuer |
https://auth.zhengchentao.win |
Expected iss claim |
Jwt__Audience |
obsidian |
Expected aud claim |
Jwt__SigningKey__Current |
required | HS256 signing key (share with nas-auth) |
Jwt__SigningKey__Previous |
— | Previous key during rotation |
Mcp__OAuthDiscovery__Issuer |
https://auth.zhengchentao.win |
/.well-known issuer field |
AuditLog__Directory |
/app/logs |
Directory for audit log files |
ASPNETCORE_ENVIRONMENT |
Production |
Set to Development for verbose logs |
Local development
# 1. Create a test vault
mkdir -p test-vault/NAS test-vault/Coding
echo "# Test" > test-vault/NAS/test.md
# 2. Set a dev signing key
export Jwt__SigningKey__Current=dev-secret-key-at-least-32-chars-long
# 3. Run
dotnet run
# 4. Generate a test JWT (requires dotnet user-jwts)
dotnet user-jwts create \
--issuer https://auth.zhengchentao.win \
--audience obsidian \
--name tao \
--claim sub=tao \
--claim scope="read:obsidian write:obsidian"
# 5. Test with MCP Inspector
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP
# URL: http://localhost:5000/mcp
# Bearer Token: <paste JWT from step 4>
Write whitelist
Write tools only accept paths matching (hardcoded, extendable via env):
- Prefix
02-ShengquGames/logs/ - Prefix
Coding/ - Exact
NAS/NAS 待办清单.md
Always forbidden (any directory): AGENTS.md, PROFILE.md, README.md, CLAUDE.md, 01-Secret/
Running tests
cd obsidian-mcp.Tests
dotnet test
Related docs
Description
Languages
C#
96.6%
Dockerfile
3.4%