Skip to main content

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) or https://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:

ToolWhat it does
personal_search_memorySearch your memory for facts matching a query.
personal_get_memoryRead one memory by id.
personal_list_memoryList your memories, most recent first.
personal_rememberWrite a fact to your memory. The origin is the connected app (Claude, Cursor, …), not a tool argument.
personal_forgetRetire a memory (does not delete it).

The client asks for two scopes at consent time:

  • memory:read — search, get, list
  • memory: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.

The first time Claude calls the server, it runs the OAuth flow:

  1. 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>.
  2. Sign in to Kroov if you are not already.
  3. The consent page shows Claude Desktop wants memory:read, memory:write. Pick Allow.
  4. 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

  1. In Cursor MCP settings, open the kroov server and click Authenticate (or use the server on first tool call).
  2. 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 / admin when local login is enabled).
  3. On the consent page, review scopes (memory:read, memory:write) and click Allow.
  4. 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:

  1. Register the client as above ("client_name": "Cline").
  2. 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"
}'
# 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.sig that 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

SymptomLikely causeFix
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: trueNormal — 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 401OAuth 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 401MCP 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 responsesThe 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:7003API is not running (or wrong port).Start API on :7003 (dotnet run from Kroov.Api).
401 unauthenticatedNo 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_scopeToken lacks memory:write (or memory:read).Re-authenticate; grant the missing scope on the consent screen.
429 rate_limitedPer-client limit hit (default 60/min).Wait for Retry-After; raise PersonalMcp:RateLimitPerMinute if needed.
409 memory_disabledMemory turned off in assistant settings.Re-enable memory under assistant settings.
Authorization with kroov failed (ofid_…) in Claude.ai after the Kroov login pageAfter 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 refreshRefresh token rotated or revoked.Click Authenticate again in Cursor.

Tips

  • Read-only first. Authorize a new client with memory:read only, try it for a few days, then re-authorize with memory:write if 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/memory and 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.