Personal MCP — connect your AI tools
Kroov hosts a personal MCP server at /mcp that exposes your
durable memory to any MCP-compatible client. /api/personal-mcp remains a dual-mounted
alias (no redirect). Once connected, Claude Desktop, Cursor,
VS Code, Cline or a custom script can search, read, list, write and retire facts in
your memory — the same memory your Kroov assistant and agents use.
Your memory stays yours: it lives in your Kroov account, is scoped to you alone, and you can revoke any connected tool at any time from Settings → Connected applications.
This guide walks through connecting the most common clients. The developer/admin
reference lives in docs/PERSONAL-MCP.md at the repository root.
Before you start
1. Enable the personal MCP server (required)
The route /mcp (and the /api/personal-mcp alias) is not registered until PersonalMcp:Enabled=true.
If the flag is off, Cursor (and your browser) get HTTP 404 — including when you
click Authenticate.
Local dev — pick one:
Option A — environment variable (quick test, Windows PowerShell):
$env:PersonalMcp__Enabled = "true"
cd Kroov.Api
dotnet run --launch-profile http
Option B — appsettings.Development.json (persistent; merge into your local file
under src/Kroov.Api/, not committed):
"PersonalMcp": {
"Enabled": true,
"Audience": "kroov-mcp",
"AccessTokenMinutes": 15,
"RefreshTokenDays": 30,
"AuthorizationCodeMinutes": 10,
"RateLimitPerMinute": 60
},
"OAuth": {
"PublicBaseUrl": "https://localhost:7003",
"ExternalIssuers": []
}
Restart the API after changing config.
Verify the feature is on (do not open /mcp in a browser — see below):
curl -s https://localhost:7003/api/features
Look for "personalMcp": true. If it is false, the endpoint is still disabled.
Apply the OAuth migration once if you have not already:
dotnet ef database update \
--project src/Kroov.Infrastructure.Persistence/Kroov.Infrastructure.Persistence.csproj \
--startup-project src/Kroov.Api/Kroov.Api.csproj
2. Other prerequisites
- You have a Kroov account and can sign in to the SPA (
https://localhost:3007/app). - You know your API base URL:
https://localhost:7003(local) orhttps://kroov.example.com(deployed).
OAuth metadata and token endpoints: <base>/api/oauth (e.g.
https://localhost:7003/api/oauth/token). The MCP server URL for clients:
<base>/mcp.
Do not test with a browser tab
/mcp is a Streamable HTTP MCP endpoint (JSON-RPC over POST).
Opening https://localhost:7003/mcp in Chrome or Edge often shows
404 or an empty response even when the server is healthy. Use GET /api/features
or connect from Cursor/Claude instead.
What you get after connecting
Five tools, all scoped to you:
| Tool | What it does |
|---|---|
personal_search_memory | Search your memory for facts matching a query. |
personal_get_memory | Read one memory by id. |
personal_list_memory | List your memories, most recent first. |
personal_remember | Write a fact to your memory. The origin is the connected app (Claude, Cursor, …), not a tool argument. |
personal_forget | Retire a memory (does not delete it). |
The client asks for two scopes at consent time:
memory:read— search, get, listmemory:write— remember, forget
You can grant read-only if you prefer the client to never write.
Connect Claude Desktop
Claude Desktop supports Streamable HTTP MCP servers with OAuth.
Step 1 — register the client
From a terminal (Claude Desktop does this for you on first connect, but doing it once by hand makes errors easier to read):
curl -s -X POST https://kroov.example.com/api/oauth/register \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Claude Desktop",
"grant_types": ["authorization_code", "refresh_token"],
"scopes": ["memory:read", "memory:write"],
"token_endpoint_auth_method": "none"
}'
Save the returned client_id (no client_secret — this is a public client).
Step 2 — add the server to Claude Desktop
Open Claude Desktop → Settings → Developer → Edit Config, and add to
claude_desktop_config.json:
{
"mcpServers": {
"kroov": {
"url": "https://localhost:7003/mcp"
}
}
}
Restart Claude Desktop.
Step 3 — consent
The first time Claude calls the server, it runs the OAuth flow:
- Claude opens a browser window at
https://kroov.example.com/api/oauth/authorize?response_type=code&client_id=<your client_id>&redirect_uri=http://127.0.0.1:port/callback&code_challenge=<PKCE>&code_challenge_method=S256&scope=memory:read%20memory:write&state=<state>. - Sign in to Kroov if you are not already.
- The consent page shows Claude Desktop wants
memory:read,memory:write. Pick Allow. - Claude exchanges the code for an access token and stores it.
You should now see personal_search_memory, personal_remember, etc. in
Claude's tool list. Ask Claude "What's in my memory?" to verify.
Connect Claude.ai (custom connector)
Claude.ai custom connectors run OAuth from Anthropic's servers, not from your laptop.
That is why Cursor can connect to localhost and Claude.ai cannot.
Use the public HTTPS personal MCP URL — never /api/internal-mcp (that server is only
for the in-product assistant) and never http://localhost:…:
https://kroov.example.com/mcp
Add it under Settings → Connectors → Add custom connector. Claude registers itself via Dynamic Client Registration and opens the Kroov consent page in the browser.
If you see Authorization with kroov failed with an ofid_… reference after clicking
Allow, the OAuth dance usually succeeded and Claude then rejected the JWT (iss/aud
must match the well-known metadata). Rebuild/redeploy the API with the aligned issuer
and audience, then remove and re-add the connector.
Connect Cursor
Cursor supports HTTP MCP servers with OAuth. Configuration goes in your MCP settings
(project .cursor/mcp.json or Cursor → Settings → MCP → Edit config).
Step 1 — enable the server on the API
See Before you start. Without PersonalMcp:Enabled=true, Cursor
gets 404 when it tries to reach the URL or start OAuth.
Step 2 — add the server (local dev)
Use this exact shape (only url — Cursor discovers OAuth from the server):
{
"mcpServers": {
"kroov": {
"url": "https://localhost:7003/mcp"
}
}
}
For a deployed host, replace the host only:
{
"mcpServers": {
"kroov": {
"url": "https://kroov.example.com/mcp"
}
}
}
Save the file and reload Cursor (or disable/re-enable the server in MCP settings).
Step 3 — authenticate
- In Cursor MCP settings, open the kroov server and click Authenticate (or use the server on first tool call).
- A browser opens the consent page on the SPA, not on the API — the
authorization endpoint redirects to
{OAuth:PublicWebAppBaseUrl}/app/oauth/consent. Sign in to the SPA if prompted (admin/adminwhen local login is enabled). - On the consent page, review scopes (
memory:read,memory:write) and click Allow. - Return to Cursor. The server should show connected;
personal_*tools are available in chat.
Cursor registers its own OAuth client via Dynamic Client Registration on first
connect — you do not need a manual POST /api/oauth/register step for Cursor.
It registers three redirect URIs at once (http://localhost:8787/callback,
https://www.cursor.com/agents/mcp/oauth/callback and a cursor:// fallback);
all three are accepted.
:::caution Local development: the SPA must be running
Consent is collected by the SPA because the product JWT lives there, not in a
cookie the API can read. In development the SPA is a separate Vite server, so
npm start in apps/legacy-web must be running on https://localhost:3007 or the
consent page will not load. Override the origin with
OAuth__PublicWebAppBaseUrl when the SPA is served elsewhere.
:::
Step 4 — verify
Ask the model to use your memory, e.g. "Search my Kroov memory for diet". Check Settings → Connected applications in the Kroov SPA — Cursor (or the registered client name) should appear after a successful consent.
Connect VS Code (GitHub Copilot Chat)
VS Code's MCP support accepts HTTP servers.
Step 1 — register the client
Same POST /api/oauth/register call, with "client_name": "VS Code".
Step 2 — add the server
Open Command Palette → Preferences: Open User Settings (JSON) and add to
mcp.servers:
{
"kroov": {
"type": "http",
"url": "https://kroov.example.com/mcp"
}
}
Reload the window. The first invocation triggers the OAuth consent in a browser.
Connect Cline / Roo Code / other stdio MCP clients
Stdio-only clients (older Cline builds, Roo) cannot speak Streamable HTTP
directly. Use the mcp-remote shim:
- Register the client as above (
"client_name": "Cline"). - Add to the client's MCP config:
{
"mcpServers": {
"kroov": {
"command": "npx",
"args": [
"mcp-remote",
"https://kroov.example.com/mcp"
]
}
}
}
mcp-remote opens a browser for the OAuth consent on first call, then caches
the token.
Connect a generic client (curl / script)
Useful for testing or for a custom automation.
Step 1 — register the client
curl -s -X POST https://kroov.example.com/api/oauth/register \
-H 'Content-Type: application/json' \
-d '{
"client_name": "my-script",
"grant_types": ["authorization_code", "refresh_token"],
"scopes": ["memory:read"],
"token_endpoint_auth_method": "none"
}'
Step 2 — generate a PKCE pair and open the consent URL
# PKCE verifier and challenge (S256)
VERIFIER="$(openssl rand -hex 32)"
CHALLENGE="$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')"
# Open in a browser (sign in first if needed):
# https://kroov.example.com/api/oauth/authorize?response_type=code&client_id=<client_id>&redirect_uri=http://127.0.0.1:8080/callback&code_challenge=<CHALLENGE>&code_challenge_method=S256&scope=memory:read&state=xyz
After Allow, the redirect lands at http://127.0.0.1:8080/callback?code=<CODE>&state=xyz.
Capture <CODE>.
Step 3 — exchange the code for tokens
curl -s -X POST https://kroov.example.com/api/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "grant_type=authorization_code" \
-d "code=<CODE>" \
-d "code_verifier=$VERIFIER" \
-d "client_id=<client_id>" \
-d "redirect_uri=http://127.0.0.1:8080/callback"
Save access_token and refresh_token from the response.
Step 4 — call the MCP server
curl -s -X POST https://kroov.example.com/mcp \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "personal_search_memory", "arguments": { "query": "diet" } }
}'
Step 5 — refresh when the token expires
curl -s -X POST https://kroov.example.com/api/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "grant_type=refresh_token" \
-d "refresh_token=<refresh_token>" \
-d "client_id=<client_id>"
The old refresh token is rotated; store the new one.
Manage connected applications
Open Settings → Connected applications. Every client you authorized is listed with its scopes, grant date, and last access. Revoke any client instantly — its in-flight tokens stop working within 5 minutes (revocation cache), even though tokens are stateless.
Revoking is the right move when:
- you no longer use a client,
- a token leaked,
- you want to reduce a client's scopes (revoke and re-authorize with fewer scopes).
Export and import your AI data
Open Settings → My data.
- Export downloads a signed zip of your memory, agents, approvals, actions,
and optionally conversations and embeddings. The zip carries a
signature.sigthat proves it came from your account. - Import loads a signed zip into another Kroov instance — useful when
moving from self-hosted to SaaS or back. Memory lands as
Suggested(pending your review) and agents land inactive, so nothing imported starts acting on its own.
The same page exposes the Journal of my AI's actions — a signed, exportable audit of everything your AI did on your behalf, plus monthly summary reports.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
404 on https://localhost:7003/mcp (browser or Cursor Authenticate) | PersonalMcp:Enabled=false — route never mounted. | Set PersonalMcp:Enabled=true in env or appsettings.Development.json, restart API. Confirm GET /api/features → "personalMcp": true. |
Browser tab shows 405 Method Not Allowed on /mcp but features says personalMcp: true | Normal — MCP uses POST JSON-RPC only, and the server offers no GET event stream. | Use Cursor/Claude, or probe with POST (expect 401 with WWW-Authenticate when unauthenticated). |
Cursor Authenticate fails though personalMcp: true and POST returns 401 | OAuth discovery — client needs RFC 9728 metadata at /mcp/.well-known/oauth-protected-resource. | Update to a build that includes the discovery controller; restart API; retry Authenticate. |
Log: Invalid OAuth error response: Unexpected end of JSON input on 401 | MCP 401 returned an empty body; Cursor expects OAuth JSON (error / error_description). | Rebuild/restart API with the JSON-body fix; retry Authenticate. |
Log: Failed to open SSE stream: Not Found, then Tombstoning streamable HTTP transport after 5 consecutive session HTTP 404 responses | The client opens a GET stream for server-initiated messages. The server does not offer one, and older builds answered 404, which the client reads as a lost session and retries until it disables the server. | Rebuild/restart the API: GET /mcp now answers 405 with Allow: POST, DELETE, which clients accept as final. Then re-enable the server in Cursor (a tombstoned transport does not retry on its own). |
Log: ECONNREFUSED 127.0.0.1:7003 | API is not running (or wrong port). | Start API on :7003 (dotnet run from Kroov.Api). |
401 unauthenticated | No token, expired token, or client revoked. | Click Authenticate in Cursor MCP settings when status shows needs auth; sign in to Kroov in the browser if prompted. |
403 insufficient_scope | Token lacks memory:write (or memory:read). | Re-authenticate; grant the missing scope on the consent screen. |
429 rate_limited | Per-client limit hit (default 60/min). | Wait for Retry-After; raise PersonalMcp:RateLimitPerMinute if needed. |
409 memory_disabled | Memory turned off in assistant settings. | Re-enable memory under assistant settings. |
Authorization with kroov failed (ofid_…) in Claude.ai after the Kroov login page | After login the SPA used to drop the consent URL because redirect_uri=https://… failed an open-redirect check, so Claude never received an authorization code. | Deploy the post-login redirect fix. After signing in you must land on Authorize access (Allow / Deny), not the product home. Then re-add the connector. |
invalid_grant on refresh | Refresh token rotated or revoked. | Click Authenticate again in Cursor. |
Tips
- Read-only first. Authorize a new client with
memory:readonly, try it for a few days, then re-authorize withmemory:writeif you trust it. - One client per device. Register a separate client per device/editor so you can revoke one without nuking the others.
- Audit your memory. Periodically open
/app/knowledge/memoryand review what your connected clients remembered on your behalf. - Export before you leave. The signed export is the trust signal: you can take your AI with you, no lock-in.