> For the complete documentation index, see [llms.txt](https://docs.datasentinel.io/manual/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.datasentinel.io/manual/ai-connector/server-setup/mcp-server-setup.md).

# MCP Server Setup

How the Datasentinel MCP server is started, configured and secured with OAuth 2.1 in production, plus manual setup steps for sites upgrading from an older release.

This guide explains how to run the **Datasentinel MCP server** in production: how it is started, how it is configured, how clients authenticate, and (for sites upgrading from an older release) how to set it up by hand.

It is operations-focused. It covers how to run the server, not the tools it exposes. For what AI Connector does end to end, see the [AI Connector overview](/manual/ai-connector/readme.md). For what users experience when they sign in, see [How authentication works](/manual/ai-connector/server-setup/how-authentication-works.md).

## At a glance

<table><thead><tr><th width="233">Concern</th><th>Value</th></tr></thead><tbody><tr><td>Entrypoint</td><td><code>datasentinel_mcp.py</code> → <code>mcp.run(transport=...)</code></td></tr><tr><td>Transport in production</td><td><strong><code>streamable-http</code></strong> (set via <code>DS_MCP_TRANSPORT</code>)</td></tr><tr><td>Internal bind</td><td><code>DS_MCP_HOST</code> (default <code>0.0.0.0</code>) : <code>DS_MCP_PORT</code> (default <strong><code>14311</code></strong>)</td></tr><tr><td>Public endpoint</td><td>nginx <code>location /mcp</code> → <code>http://127.0.0.1:14311</code>,<br><br>plus the OAuth locations (<code>/.well-known/oauth-*</code>, <code>/authorize</code>, <code>/token</code>, <code>/register</code>, <code>/revoke</code>, <code>/oauth/login</code>)</td></tr><tr><td>Public URL</td><td><code>https://&#x3C;host name></code> by default;<br><br><code>DS_MCP_PUBLIC_URL</code>, set on the <code>datasentinel_mcp</code> service, overrides it when the public name differs</td></tr><tr><td>Authentication</td><td><strong>OAuth 2.1</strong>: the server is its own authorization server, users sign in with their platform login</td></tr><tr><td>OAuth state</td><td><code>/datasentinel/data/mcp_oauth/oauth.db</code> (SQLite), must persist across restarts</td></tr><tr><td>systemd unit</td><td><code>/usr/lib/systemd/system/datasentinel_mcp.service</code></td></tr><tr><td>Start/stop scripts</td><td><code>scripts/backend_mcp_start.sh</code>, <code>scripts/backend_mcp_stop.sh</code></td></tr></tbody></table>

## Manual setup on upgrade

If you are upgrading from a release that predates the MCP server (2026.10), the systemd unit, the service-control sudoers rules, and the nginx routes do not yet exist on your machine.

Set them up with the steps below. All paths use location `/datasentinel/soft/datasentinel_backend`.

Run the steps as a user with `sudo`. The service itself runs as the `datasentinel` user.

{% stepper %}
{% step %}

#### Grant the datasentinel user permission to manage the service

So the platform can stop, start, restart, and check the MCP service without a password prompt, grant the `datasentinel` group the matching `systemctl` rights. Add the lines below to `/etc/sudoers.d/99-datasentinel-users`. Edit the file with `visudo` so the syntax is validated before it is saved:

```bash
sudo visudo -f /etc/sudoers.d/99-datasentinel-users
```

```
%datasentinel ALL=NOPASSWD: /bin/systemctl stop datasentinel_mcp
%datasentinel ALL=NOPASSWD: /bin/systemctl start datasentinel_mcp
%datasentinel ALL=NOPASSWD: /bin/systemctl status datasentinel_mcp
%datasentinel ALL=NOPASSWD: /bin/systemctl status datasentinel_mcp -l
%datasentinel ALL=NOPASSWD: /bin/systemctl restart datasentinel_mcp
```

These entries scope the `datasentinel` group to exactly the `systemctl` verbs it needs for the `datasentinel_mcp` service and nothing else.
{% endstep %}

{% step %}

#### Install the systemd service

Create `/usr/lib/systemd/system/datasentinel_mcp.service`:

```ini
[Unit]
Description=Datasentinel MCP Server
After=datasentinel_backend.service

[Service]
User=datasentinel

# MCP settings: see "How it is configured". Change them with a drop-in, not in this file.
Environment=DS_MCP_TRANSPORT=streamable-http
Environment=DS_MCP_HOST=0.0.0.0
Environment=DS_MCP_PORT=14311
Environment=DS_MCP_PUBLIC_URL=
Environment=DS_MCP_OAUTH_DIR=/datasentinel/data/mcp_oauth
Environment=DS_MCP_MAX_CONCURRENT_TOOLS=16
Environment=DS_API_VERIFY_TLS=

ExecStart=/datasentinel/soft/datasentinel_backend/scripts/backend_mcp_start.sh
ExecStop=/datasentinel/soft/datasentinel_backend/scripts/backend_mcp_stop.sh
KillSignal=SIGQUIT
Type=simple
Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target
```

Reload systemd, enable at boot, and start:

```bash
sudo systemctl daemon-reload
sudo systemctl enable datasentinel_mcp
sudo systemctl start  datasentinel_mcp
sudo systemctl status datasentinel_mcp
```

At this point the server is listening on `127.0.0.1:14311` but is not yet reachable through nginx.
{% endstep %}

{% step %}

#### Add the nginx location blocks

Edit the Datasentinel site file `/etc/nginx/conf.d/datasentinel.conf`.<br>

Two things are added **before** the catch-all `location /` directive: the `/mcp` block, and the OAuth locations (the discovery documents, `/authorize`, `/token`, `/register`, `/revoke`, `/oauth/login`).

The OAuth routes are served at the root of the MCP application and cannot be moved under a prefix, which is why they need their own locations. None of them collides with a Grafana route.

The complete file, with the existing directives kept as they are and the new parts marked:

```nginx
map $request_method $loggable {
    default 1;
    POST    0;
}

# --- NEW 2026.10: brute-force protection for the login form and the token endpoint -------------
limit_req_zone $binary_remote_addr zone=ds_oauth:1m rate=10r/m;
# --- END NEW 2026.10: brute-force protection -------------

server {
    listen 443 ssl;
    server_name default_server;
    ssl_certificate     /home/datasentinel/datasentinel/config/cert_datasentinel.pem;
    ssl_certificate_key /home/datasentinel/datasentinel/config/key_datasentinel.pem;
    access_log off;
    client_max_body_size 100M;
    proxy_connect_timeout 600;
    proxy_send_timeout 600;
    proxy_read_timeout 600;
    send_timeout 600;

    # --- existing Datasentinel services -------------------------------------------------
    location /ds-api/pool {
        proxy_set_header Host $remote_addr;
        proxy_pass http://127.0.0.1:13300$request_uri;
    }
    location /ds-api {
        proxy_set_header Host $remote_addr;
        proxy_pass http://127.0.0.1:12324$request_uri;
    }

    # --- NEW 2026.10: MCP streamable-http endpoint and OAuth 2.1 discovery and authorization-server endpoints --------------------
    location /mcp {
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_cache off;
        proxy_set_header Connection '';
        proxy_set_header Host $host;
        proxy_set_header Mcp-Session-Id $http_mcp_session_id;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server.
    # ^~: a regex location on /.well-known (an ACME one, for example) must not take these requests.
    location ^~ /.well-known/oauth- {
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    location = /authorize {                       # GET: starts the flow
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    location = /register {                        # POST: dynamic client registration
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    location = /revoke {                          # POST: optional
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    location = /oauth/login {                     # GET + POST: Datasentinel login form
        limit_req zone=ds_oauth burst=5 nodelay;
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    location = /token {                           # POST: code / refresh exchange
        limit_req zone=ds_oauth burst=5 nodelay;
        proxy_pass http://127.0.0.1:14311;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    # --- END NEW 2026.10: MCP streamable-http endpoint and OAuth 2.1 
    
    
    # --- Grafana frontend (catch-all) -----------------------
    location / {
        proxy_set_header Host $remote_addr;
        proxy_pass http://127.0.0.1:3000$request_uri;
    }
}

server {
    listen 80;
    server_name default_server;
    access_log off;
    return 301 https://$http_host$request_uri;
}
```

{% hint style="warning" %}
**Remove the old `/.well-known` block if present.** Files installed by previous Datasentinel versions may contain this block:

```nginx
location ~ /.well-known {
    allow all;
    root /tmp;
}
```

Remove it. It is a regular-expression location, so in the `listen 443` server it takes the OAuth discovery requests before the MCP server sees them, and assistants fail to sign in. It is not part of the file above.

If you renew a Let's Encrypt certificate with the HTTP challenge in `/tmp`, keep the block in the `listen 80` server only.
{% endhint %}

**Notes on the directives:**

* `/mcp`: `proxy_buffering off` / `proxy_cache off` let streamed tool-call chunks reach the client immediately; `proxy_set_header Connection ''` preserves the upstream keep-alive stream; the one-hour timeouts exist because tool calls (reports, long queries) can run for minutes.
* The OAuth locations are ordinary short requests: no buffering or timeout overrides, and no URI part on `proxy_pass`, so the request path reaches the MCP application unchanged.
* `limit_req` on `/oauth/login` and `/token` is the brute-force protection of the login form and the token endpoint (10 requests per minute per client address, burst of 5). It is not optional: the application has no lockout of its own.
* `Authorization` is forwarded by nginx by default; do not strip it. `/token` must never answer a redirect, so keep it proxied directly.

If the public name of the platform is not the host name (a load balancer, a NAT, a DNS alias), set `DS_MCP_PUBLIC_URL` on the service with a systemd drop-in:

```bash
sudo systemctl edit datasentinel_mcp
```

In the editor, add:

```ini
[Service]
Environment=DS_MCP_PUBLIC_URL=https://<public-host>
```

Do not set it in `scripts/backend_mcp_start.sh`: each upgrade replaces that script. See [Change a setting](#change-a-setting).

Validate and reload:

```bash
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart datasentinel_mcp
```

{% endstep %}

{% step %}

#### Verify

Look at [Verify the install](#verify-the-install).

To look at the answers yourself:

```bash
# service up
sudo systemctl status datasentinel_mcp

# internal listener: 401 with a WWW-Authenticate challenge is the healthy answer
curl -s -i http://127.0.0.1:14311/mcp | head -3

# end-to-end through nginx: the same challenge, then the two OAuth discovery documents
curl -s -i https://<public-host>/mcp | head -3
curl -s https://<public-host>/.well-known/oauth-protected-resource/mcp
curl -s https://<public-host>/.well-known/oauth-authorization-server
```

Every URL in the two discovery documents, and the `resource_metadata` URL in the challenge, must start with the public origin clients use.
{% endstep %}
{% endstepper %}

## Fresh install

On a fresh Datasentinel install, the MCP server is provisioned automatically.\
You do not need to create anything by hand. This section just explains **how it is started**, **how it is configured**, and **how it is secured** so you can operate and troubleshoot it.

## Verify the install

A health-check helper ships in `scripts/mcp_check.sh`. Run it on the platform host after a fresh install, after the [manual setup on upgrade](#manual-setup-on-upgrade), and whenever you change a setting, the nginx configuration or the certificate:

```bash
/datasentinel/soft/datasentinel_backend/scripts/mcp_check.sh
```

<figure><img src="https://196776047-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FDL4xMeOs1Z7fz2LBCNSv%2Fuploads%2Fgit-blob-439b8c95cfb677515801a4bf03c87afcfca6f351%2Fmcp-check.svg?alt=media" alt="Animated illustration with example values of the mcp_check.sh script run on the platform host, with five checks and one verdict. Check 1, internal MCP server: the script connects to the listener on port 14311, finds 24 tools, and the platform status lists six services, all running. Check 2, nginx certificate: the script finds the public URL from the datasentinel_mcp service and describes the certificate: issued to datasentinel.example.com by Let&#x27;s Encrypt, 75 days left, trusted. Check 3, public access through nginx: the script connects to the public URL and finds the 24 tools. Check 4, OAuth routes on the internal MCP server: the two discovery documents, /mcp, /authorize, /register, /token, /revoke and /oauth/login all answer, 9 routes out of 9. Check 5, OAuth routes through nginx: the same nine routes answer on the public URL. Verdict: OK, the MCP server is up, reachable through nginx, and the platform is healthy; the script exits with code 0."><figcaption><p>The health check: five checks, then one verdict.</p></figcaption></figure>

It mints a token for the check user, then runs five checks and prints a readable summary:

<table><thead><tr><th width="220">Check</th><th>What it does</th></tr></thead><tbody><tr><td>Internal MCP server</td><td>Connects to the internal listener (<code>http://localhost:14311/mcp</code>), counts the tools and calls the platform status tool, which lists every platform service as running or stopped.</td></tr><tr><td>Nginx certificate</td><td>Describes the certificate nginx presents on the public URL: who it is issued to, who issued it, when it expires, and whether clients will trust it.</td></tr><tr><td>Public access through nginx</td><td>Connects to <code>&#x3C;public URL>/mcp</code> through nginx, the way an assistant does.</td></tr><tr><td>OAuth routes on the internal MCP server</td><td>Probes every OAuth route on the internal listener: the two discovery documents, <code>/mcp</code>, <code>/authorize</code>, <code>/register</code>, <code>/token</code>, <code>/revoke</code> and <code>/oauth/login</code>.</td></tr><tr><td>OAuth routes through nginx</td><td>Probes the same routes on the public URL, which shows that nginx has a location for each of them.</td></tr></tbody></table>

The script finds the public URL the way the server does: `DS_MCP_PUBLIC_URL` from the environment or from the `datasentinel_mcp` unit, else `https://<machine name>`. The `Public URL` line of the output says which one was used.

```
Internal MCP server (http://localhost:14311/mcp, streamable-http)
  Connection        : OK (server Datasentinel)
  Tools available   : 24
  Platform status   : OK - Datasentinel Backend up and running
  Datasentinel      : 2026.10
  Services          : 6/6 running
    nginx                    running  Nginx web server
    datasentinel_grafana     running  Grafana frontend
    datasentinel_postgresql  running  PostgreSQL instance
    datasentinel_backend     running  Backend service and API
    datasentinel_dispatcher  running  Agentless service
    datasentinel_influxdb    running  Influxdb database

Public URL          : https://datasentinel.example.com (from datasentinel_mcp service)

Nginx certificate (datasentinel.example.com:443)
  Issued to         : datasentinel.example.com
  Issued by         : Let's Encrypt (YE1)
  Valid until       : 2026-12-18 (75 days left)
  Trusted           : yes

Public access through nginx (https://datasentinel.example.com/mcp)
  Connection        : OK (24 tools)

OAuth routes on the internal MCP server (http://localhost:14311)
    GET /.well-known/oauth-protected-resource/mcp  OK
    GET /.well-known/oauth-authorization-server    OK
    GET /mcp                                       OK
    GET /authorize                                 OK
    POST /register                                 OK
    POST /token                                    OK
    POST /revoke                                   OK
    GET /oauth/login                               OK
    POST /oauth/login                              OK
  Routes            : 9/9 OK

OAuth routes through nginx (https://datasentinel.example.com)
    GET /.well-known/oauth-protected-resource/mcp  OK
    GET /.well-known/oauth-authorization-server    OK
    GET /mcp                                       OK
    GET /authorize                                 OK
    POST /register                                 OK
    POST /token                                    OK
    POST /revoke                                   OK
    GET /oauth/login                               OK
    POST /oauth/login                              OK
  Routes            : 9/9 OK

OK      *** MCP server is up, reachable through nginx, and the platform is healthy
```

The last line gives the verdict, and the exit code follows it:

<table><thead><tr><th width="130">Verdict</th><th width="110">Exit code</th><th>When</th></tr></thead><tbody><tr><td><code>OK</code></td><td><code>0</code></td><td>The five checks pass.</td></tr><tr><td><code>WARNING</code></td><td><code>0</code></td><td>The server works, but the certificate is not trusted (self-signed, unknown CA, or a name that does not match the public URL) or expires in less than 30 days. The output says what to do.</td></tr><tr><td><code>FAILED</code></td><td><code>1</code></td><td>The internal listener or the public URL cannot be reached, a platform service is stopped, the platform status is not <code>OK</code>, the certificate has expired, or an OAuth route does not answer as the MCP server should.</td></tr></tbody></table>

### The OAuth route probes

Each probe is an incomplete request, sent on purpose: the server refuses it with its own error, so nothing is registered, issued or revoked. Only the MCP server's own answer passes. A failing route shows the reason on its line:

<table><thead><tr><th width="330">Reason shown</th><th>Meaning</th></tr></thead><tbody><tr><td><code>HTTP 404 - not routed to the MCP server</code></td><td>The nginx location of that route is missing, or another location took the request.</td></tr><tr><td><code>redirected to ...</code></td><td>Another location caught the request, usually the Grafana catch-all <code>location /</code>.</td></tr><tr><td><code>resource is ..., expected ...</code><br><code>issuer is ..., expected ...</code></td><td>A discovery document announces another host name than the public URL: check <code>DS_MCP_PUBLIC_URL</code>.</td></tr><tr><td><code>missing ...</code></td><td>The authorization server document lacks an endpoint.</td></tr><tr><td><code>no WWW-Authenticate header with resource_metadata</code></td><td><code>/mcp</code> did not answer with the authentication challenge.</td></tr><tr><td><code>not an OAuth error answer from the MCP server</code><br><code>not the Datasentinel login page</code></td><td>Something other than the MCP server answered on that route.</td></tr></tbody></table>

The internal probes run first. A route that passes internally and fails through nginx is an nginx problem: see the nginx step of the [manual setup](#manual-setup-on-upgrade).

{% hint style="info" %}
With the default self-signed nginx certificate, the verdict is `WARNING`. The script still connects through nginx, without verifying the certificate, so the public access is tested. Browsers and AI assistants are stricter: see [Certificates](#certificates).
{% endhint %}

Add `--json` to also print the raw answer of the platform status tool:

```bash
/datasentinel/soft/datasentinel_backend/scripts/mcp_check.sh --json
```

## How it is started

The MCP server runs as a dedicated systemd service, `datasentinel_mcp.service`, which starts **after** the backend API service and stays up on failure:

The control flow is:

```
systemd (datasentinel_mcp.service)
  ├─ Environment=DS_MCP_*, DS_API_VERIFY_TLS   # the unit's lines, then any drop-in override
  └─ ExecStart → scripts/backend_mcp_start.sh
        ├─ export PYTHONPATH=/datasentinel/soft/datasentinel_backend
        └─ python datasentinel_mcp.pyc       # binds 0.0.0.0:14311, serves /mcp and the OAuth routes
```

nginx terminates client traffic and proxies the `/mcp` path and the OAuth routes to the internal port `14311` (see the nginx step under [Manual setup on upgrade](#manual-setup-on-upgrade)). Clients therefore connect to the public nginx URL, never directly to `14311`.

**Everyday operation:**

```bash
sudo systemctl status  datasentinel_mcp
sudo systemctl restart datasentinel_mcp
sudo journalctl -u datasentinel_mcp -f      # follow the service output
tail -f /datasentinel/log/mcp.log           # tool calls and authentication events
```

## How it is configured

Configuration is entirely through environment variables read at process start. They are set by the `Environment=` lines of the `datasentinel_mcp` unit, which lists all of them. The start script sets none: each upgrade replaces it. An empty value (`DS_MCP_PUBLIC_URL=`, `DS_API_VERIFY_TLS=`) means the default shown below.

<table><thead><tr><th width="259">Variable</th><th>Default</th><th>Purpose</th></tr></thead><tbody><tr><td><code>DS_MCP_TRANSPORT</code></td><td><code>streamable-http</code></td><td>Transport (<code>sse</code> only for local runs without nginx)</td></tr><tr><td><code>DS_MCP_HOST</code></td><td><code>0.0.0.0</code></td><td>Bind address for the MCP listener</td></tr><tr><td><code>DS_MCP_PORT</code></td><td><code>14311</code></td><td>Bind port for the MCP listener (nginx upstream)</td></tr><tr><td><code>DS_MCP_PUBLIC_URL</code></td><td><code>https://&#x3C;host name></code></td><td>The external HTTPS origin served by nginx, no path. Everything is derived from it: the OAuth discovery documents, the login page URL, and the server's own calls to the platform API. The default is the machine's fully qualified name, correct when nginx runs on the platform host under its own name. <strong>Set it only when the public name differs</strong> (load balancer, NAT, DNS alias).</td></tr><tr><td><code>DS_MCP_OAUTH_DIR</code></td><td><code>/datasentinel/data/mcp_oauth</code></td><td>Directory of the OAuth store (<code>oauth.db</code>), created on first start with owner-only permissions</td></tr><tr><td><code>DS_MCP_MAX_CONCURRENT_TOOLS</code></td><td><code>16</code></td><td>How many tool calls may run at once. Each one holds a database connection and builds its answer in memory. Raise it for a large fleet with many concurrent users. Lower it if the platform host is short of memory.</td></tr><tr><td><code>DS_API_VERIFY_TLS</code></td><td>empty (no verification)</td><td><code>1</code> (or <code>true</code>, <code>yes</code>) makes the server verify the nginx certificate on its own calls to the platform API. Use it only with a CA-signed certificate: the default nginx certificate is self-signed.</td></tr></tbody></table>

## Change a setting

Do not edit the unit file: a new installation rewrites it. Put the change in a systemd drop-in instead. It survives upgrades and reinstallations:

```bash
sudo systemctl edit datasentinel_mcp
```

In the editor, add only the lines to change. For example:

```ini
[Service]
Environment=DS_MCP_PUBLIC_URL=https://datasentinel.example.com
```

systemd saves the file as `/etc/systemd/system/datasentinel_mcp.service.d/override.conf` and reloads itself. Restart the service and check the values it runs with:

```bash
sudo systemctl restart datasentinel_mcp
systemctl show datasentinel_mcp -p Environment
```

A platform installed with an older release may have a unit without `Environment=` lines. The server then runs on the defaults above, and a drop-in works the same way.

## How clients authenticate

The server is protected by **OAuth 2.1** and is its own authorization server. No token is configured on the server side, and the tools take no token argument.

{% hint style="info" %}
The full user-facing description, including sign-in from a server without a browser, is in [How authentication works](/manual/ai-connector/server-setup/how-authentication-works.md).
{% endhint %}

## Certificates

OAuth exposes the nginx certificate to consumers that did not see it before:

* **The user's browser**, on the login page: a self-signed certificate shows a warning, which is survivable.
* **The assistant's own HTTP client**, which fetches the discovery documents, registers and exchanges tokens. Claude Code and the other CLIs **reject untrusted certificates**: the machine running the assistant needs a certificate it trusts, either a CA-signed certificate on nginx or the internal CA installed on that machine. This applies equally to a headless server running a CLI over SSH.
* **Cloud-hosted assistants** (Claude, ChatGPT, Microsoft Copilot) need the nginx endpoint reachable from the internet with a **publicly trusted certificate**; there is no workaround.

See [Self-signed and untrusted TLS certificates](/manual/ai-connector/troubleshooting/ssl-certificates.md) for the per-assistant options.

## Data directory

The OAuth state lives in one SQLite database, `/datasentinel/data/mcp_oauth/oauth.db`, created by the service on first start with owner-only permissions. It holds the registered clients, the hashed refresh tokens, and the sign-ins in progress.

* **It must survive restarts and upgrades.** In Docker, `/datasentinel/data` must be a persistent volume.
* **It is not part of the platform backup**: the file is rebuilt by use. If it is lost, every assistant user signs in again on the login page (one browser step, no data lost) and assistants re-register by themselves.
* Never copy `oauth.db` while the service runs: the database is in write-ahead-log mode and a raw copy of an open database is inconsistent. Stop the service first, or use `sqlite3 oauth.db ".backup <file>"`.

To list the sessions held in this store, end them, or check the store's integrity, use the `mcp_oauth_sessions.sh` script described in [Manage assistant sessions](/manual/ai-connector/server-setup/manage-assistant-sessions.md).

## Logs

`/datasentinel/log/mcp.log` records every tool call and every authentication event, one line each, with the user's email and never a token value or a password: client registration, sign-in accepted or refused, tokens issued by code exchange or by refresh, tokens revoked, bearers rejected.

Every line carries `session=<id>`, the MCP session it belongs to, and the first tool call of a session prints `session opened for <email>`. To follow one session:

```bash
grep 'session opened' /datasentinel/log/mcp.log     # which session belongs to whom
grep 'session=<id>' /datasentinel/log/mcp.log       # everything that session did
```

The HTTP access log of the OAuth routes is not in this file: it goes to the service output (`journalctl -u datasentinel_mcp`).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.datasentinel.io/manual/ai-connector/server-setup/mcp-server-setup.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
