Skip to main content

dvm-contextlayer

A personal context layer for AI assistants: one Python package that bundles several Model Context Protocol (MCP) servers, each exposing a slice of your own working context. Run them together or separately, over stdio or HTTP, from any MCP client such as Claude Desktop or Claude Code.

Server What it exposes Backend
obsidian Full-text search, notes, tags, backlinks and daily notes Local Obsidian vaults + SQLite FTS5
outlook Mail search, folders, messages, calendar events Microsoft Graph (read-only)
planner Plans, buckets, tasks, my tasks Microsoft Graph (read-only)
todo Microsoft To Do lists and tasks Microsoft Graph (read-only)
files OneDrive and SharePoint search, browsing and text content Microsoft Graph (read-only)
teams Chats, channels, messages and message search Microsoft Graph (read-only)
graph A personal knowledge graph: entities, relations, observations, Cypher Local Kuzu database
memory Mem0-style long-term memories with full-text recall and history Local SQLite FTS5

Settings live in ~/.mycontextlayer/config.json. Secrets (the Microsoft token cache) never touch that file: they are stored in the operating system's secure store through the keyring library (Windows Credential Manager, macOS Keychain, or Secret Service on Linux).

Using GitHub Copilot on Windows? Follow the step-by-step walkthrough.

Install

Python 3.11 or newer.

# recommended: an isolated tool install
uv tool install dvm-contextlayer
# or
pipx install dvm-contextlayer
# or plain pip
pip install dvm-contextlayer

This installs the mycontextlayer command.

Quick start

mycontextlayer list                 # what is bundled and what is configured
mycontextlayer configure obsidian   # answer the prompts; settings are saved
mycontextlayer serve obsidian       # run it over stdio (what MCP clients launch)
mycontextlayer client-config        # JSON / commands to paste into your MCP client

A server refuses to start until it is configured and tells you which command to run:

$ mycontextlayer serve outlook
The outlook server is not configured. Run: mycontextlayer configure outlook

Connecting a client

mycontextlayer client-config prints ready-made snippets. For Claude Desktop add to claude_desktop_config.json:

{
  "mcpServers": {
    "obsidian": { "command": "mycontextlayer", "args": ["serve", "obsidian"] },
    "outlook":  { "command": "mycontextlayer", "args": ["serve", "outlook"] }
  }
}

For Claude Code:

claude mcp add obsidian -- mycontextlayer serve obsidian
claude mcp add memory   -- mycontextlayer serve memory

HTTP mode (all servers in one process)

mycontextlayer configure http        # optional: host and port, default 127.0.0.1:8765
mycontextlayer serve --all           # every configured server, Streamable HTTP
mycontextlayer serve outlook -t http # or a single server

With --all each server is mounted under its own path, for example http://127.0.0.1:8765/outlook/mcp; a single server is served at /mcp. GET / lists the endpoints and GET /health is a public liveness probe. Use mycontextlayer client-config --transport http for the matching client snippets.

HTTP authentication

HTTP mode is authenticated by default. configure http lets you pick one of three modes:

http.auth Who can connect
api_key Anyone presenting the random key generated during configure http (default)
oauth Anyone presenting a Microsoft Entra access token issued for your app registration
none Everyone on the network (the server warns loudly)

API key mode. configure http generates a random key, stores it in the OS keyring (never in config.json) and prints it once. Every request except /health must carry it, otherwise the server answers 401:

Authorization: Bearer mcl_...      # or
X-API-Key: mcl_...
mycontextlayer apikey            # show the key again
mycontextlayer apikey --rotate   # replace it (old key stops working immediately)
claude mcp add --transport http outlook http://127.0.0.1:8765/outlook/mcp \
  --header "Authorization: Bearer $(mycontextlayer apikey)"

serve refuses to start in HTTP mode when auth is on but no key is stored. In containers without a keyring, pass the key through the MYCONTEXTLAYER_HTTP_API_KEY environment variable instead.

OAuth mode (offered once a Microsoft app registration is configured). The HTTP host becomes an OAuth 2.0 protected resource for that same app registration: clients obtain an access token from Microsoft Entra for your API scope and send it as a bearer token. Tokens are validated locally (RS256 signature against the tenant's published keys, expiry, audience, issuer, tenant and scope), so no round trip to Microsoft is needed per request.

One-time setup on the app registration in the Entra admin center:

  1. Expose an API → set the Application ID URI to api://<client id>.
  2. Add a scope, e.g. access_as_user (admins and users can consent).
  3. Optionally add the calling client ids under Authorized client applications so users are not prompted for consent.
  4. Optionally set requestedAccessTokenVersion to 2 in the manifest. Both v1.0 and v2.0 token formats are accepted.

Then run mycontextlayer configure http, choose oauth and enter the scope name. The host serves RFC 9728 metadata at /.well-known/oauth-protected-resource (and per endpoint, e.g. /.well-known/oauth-protected-resource/outlook/mcp) naming https://login.microsoftonline.com/<tenant>/v2.0 as the authorization server, and answers 401 with a WWW-Authenticate: Bearer resource_metadata=... challenge, which is how MCP clients discover where to authenticate. Entra does not support dynamic client registration, so the MCP client must be able to use a pre-registered client id (or run its own OAuth flow for the scope api://<client id>/<scope>). Tokens missing the scope get 403.

config.json options under http.oauth: required_scopes (default ["access_as_user"]), audiences (extra accepted aud values; the client id and api://<client id> are always accepted) and allowed_tenants (defaults to the configured tenant, or any tenant when the tenant is common/organizations). For a quick test:

mycontextlayer oauth-token          # device-code sign-in for the API scope, prints a token
curl -H "Authorization: Bearer $(mycontextlayer oauth-token)" http://127.0.0.1:8765/

Setting "auth": "none" in the http section (or choosing none during configure http) disables authentication; the server then warns loudly, so only do that on a trusted, local-only interface. Whatever the mode, put a TLS-terminating reverse proxy in front of the server before exposing it beyond localhost, since credentials travel in a header.

Microsoft 365 setup

The outlook, planner, todo, files and teams servers share one sign-in. You sign in once with a device code; the refresh token is kept in the OS keyring and renewed automatically.

1. Create an app registration (once)

In the Microsoft Entra admin center go to App registrations → New registration:

  • Name: anything, e.g. mycontextlayer
  • Supported account types: Accounts in this organizational directory only (or multi-tenant if you want to use the same app in several tenants)
  • Redirect URI: leave empty
  • After creating: Authentication → Advanced settings → Allow public client flows: Yes
  • Add the Microsoft Graph delegated permissions below (see Adding API permissions for the exact clicks). Add only the rows for the servers you intend to use; sign-in requests just the scopes of the servers you have configured, so an unconsented permission for an unused server never blocks you.
Permission Used by Admin consent Notes
User.Read all no Always required
Mail.Read outlook no Your own mailbox
Calendars.Read outlook no Your own calendars
MailboxSettings.Read outlook no Time zone and working hours; get_mailbox_settings only
Tasks.Read planner, todo no Your Planner tasks, plans you can open, your To Do lists
Group.Read.All planner yes Optional. Enumerates plans of every group you belong to
Files.Read.All files no Your OneDrive plus files shared with you and site libraries
Sites.Read.All files no SharePoint site search and site drives
Chat.Read teams no Your 1:1 and group chats
Team.ReadBasic.All teams no Names of teams you joined
Channel.ReadBasic.All teams no Channel names
ChannelMessage.Read.All teams yes Reading channel posts and replies

Minimum set to read your own data without an administrator: User.Read, Mail.Read, Calendars.Read, MailboxSettings.Read, Tasks.Read, Files.Read.All, Sites.Read.All, Chat.Read, Team.ReadBasic.All, Channel.ReadBasic.All. Without Group.Read.All the planner server still lists plans shared with you and plans referenced by your tasks. Without ChannelMessage.Read.All the teams server can list teams, channels and chats but channel messages return a permission error. If you only need OneDrive and no SharePoint, Files.Read is enough in place of Files.Read.All and Sites.Read.All (set it via microsoft.extra_scopes and remove the files defaults from the app registration). Some tenants require admin consent for every permission; in that case ask an admin to grant consent once for the app.

Only read permissions are requested; the servers never send mail, create tasks or modify anything in Microsoft 365. Note the Application (client) ID and Directory (tenant) ID.

Adding API permissions

In the portal (works on any browser, no tooling needed):

  1. Open entra.microsoft.comIdentity → Applications → App registrations → All applications and click your app (e.g. mycontextlayer).
  2. In the left menu under Manage click API permissions. You will see User.Read already listed; it is added to every new registration.
  3. Click + Add a permission. A panel opens on the right.
  4. Under Microsoft APIs click the big Microsoft Graph tile.
  5. Click Delegated permissions (not Application permissions: delegated means "act as the signed-in user", which is what the device-code sign-in does).
  6. Type a permission name in the Select permissions search box, e.g. Mail.Read. The list is grouped by resource; expand the group (Mail) and tick the checkbox next to Mail.Read. Repeat the search for each permission you need from the table above; the ticks are kept while you search.
  7. Click Add permissions at the bottom. The panel closes and the new rows appear in the Configured permissions table.
  8. Look at the Admin consent required column. Rows saying No are consented by you when you first sign in. Rows saying Yes (Group.Read.All, ChannelMessage.Read.All) need an administrator: either click Grant admin consent for above the table if you are one, or send the app name and the permission names to your admin and ask them to press that button. Until then the Status column shows Not granted.
  9. Nothing else to save; the permissions take effect immediately. Removing a permission is the menu at the end of its row → Remove permission.

Your sign-in can only include permissions that are listed here; if mycontextlayer login fails with AADSTS65001 or "needs admin approval", the permission named in the message is either missing from this page or still waiting for admin consent.

From a script instead (Azure CLI, signed in with az login), the permission ids are looked up from the Microsoft Graph service principal so nothing is hard-coded:

APP_ID="<your Application (client) ID>"
GRAPH="00000003-0000-0000-c000-000000000000"   # Microsoft Graph
for perm in User.Read Mail.Read Calendars.Read MailboxSettings.Read Tasks.Read \
            Files.Read.All Sites.Read.All Chat.Read Team.ReadBasic.All Channel.ReadBasic.All; do
  id=$(az ad sp show --id "$GRAPH" --query "oauth2PermissionScopes[?value=='$perm'].id" -o tsv)
  az ad app permission add --id "$APP_ID" --api "$GRAPH" --api-permissions "$id=Scope"
done
az ad app permission admin-consent --id "$APP_ID"   # only if you are an administrator

2. Configure and sign in

mycontextlayer configure outlook   # asks for client ID and tenant ID, then offers to sign in
# or explicitly
mycontextlayer configure microsoft
mycontextlayer login
mycontextlayer whoami
mycontextlayer logout              # removes the cached sign-in from the keyring

login prints a URL and a code; open the URL, enter the code and sign in. The scopes of every configured Microsoft server are requested in one go so you consent once; configuring another Microsoft server later checks whether the existing sign-in covers it and offers to sign in again if not. Extra scopes can be added to microsoft.extra_scopes in config.json.

Configuration file

~/.mycontextlayer/config.json (override the folder with the MYCONTEXTLAYER_HOME environment variable). Example:

{
  "version": 1,
  "microsoft": {
    "client_id": "00000000-0000-0000-0000-000000000000",
    "tenant_id": "common",
    "account": "you@example.com",
    "extra_scopes": []
  },
  "servers": {
    "obsidian": { "vaults": [{ "name": "Work", "path": "/Users/you/Obsidian/Work" }] },
    "outlook":  { "timezone": "Europe/Amsterdam", "default_page_size": 25 },
    "planner":  { "include_group_plans": true },
    "todo":     {},
    "files":    { "max_content_bytes": 200000 },
    "teams":    {},
    "graph":    { "database_path": "" },
    "memory":   { "database_path": "", "default_user_id": "default" }
  },
  "http": {
    "host": "127.0.0.1",
    "port": 8765,
    "auth": "api_key",
    "oauth": { "required_scopes": ["access_as_user"], "audiences": [], "allowed_tenants": [] }
  }
}

A server is "configured" when its section exists. Empty database paths default to ~/.mycontextlayer/data/. The Obsidian index lives in ~/.mycontextlayer/index/. The file contains no secrets and is written with mode 0600.

Secrets

The secrets are the MSAL token cache and the HTTP API key. Both are stored under the service name dvm-contextlayer in the OS keyring, split into chunks small enough for Windows Credential Manager. If no keyring backend is available (typical on a headless Linux box without a Secret Service), configure and login fail with an explanation instead of falling back to plain files. On Linux desktops install GNOME Keyring or KWallet; on servers, run the Microsoft servers on a machine with a keyring instead.

The servers in more detail

obsidian indexes .md and .txt files (ignoring .obsidian, .trash, hidden folders) into SQLite FTS5 with YAML frontmatter, inline #tags and [[wikilinks]]. Tools: list_vaults, search_notes (FTS5 syntax), read_note, list_notes, recent_notes, get_backlinks, list_tags, get_daily_note, reindex. Notes are also exposed as obsidian://<vault>/<path> resources. The index refreshes incrementally when it is older than a minute.

graph stores Entity(name, type, observations[]) nodes and RELATED(relation) edges in an embedded Kuzu database. Tools mirror the reference MCP memory server (create_entities, create_relations, add_observations, search_nodes, open_nodes, read_graph, delete variants) plus get_neighbors, graph_stats and a read-only query_graph for Cypher.

memory keeps one short fact per row, scoped by user_id / agent_id / run_id, with BM25 full-text search, duplicate detection and an audit history (add_memory, add_memories, search_memories, list_memories, get_memory, update_memory, delete_memory, delete_all_memories, memory_history, memory_stats). Extraction of facts is left to the MCP client, which keeps everything local and model-free.

The Microsoft servers return compact summaries (recipients, dates, previews) and convert HTML bodies to plain text. See each tool's description in your client for parameters.

Development

git clone https://github.com/divyavanmahajan/mycontextlayer
cd mycontextlayer
uv sync --group dev
uv run pytest
uv run ruff check src tests && uv run ruff format --check src tests
uv run mycontextlayer list

Point MYCONTEXTLAYER_HOME at a scratch folder while developing so your real config is left alone. The Microsoft servers are tested against a mocked Graph API; there are no live tests.

Releasing

Versions come from git tags via hatch-vcs; there is no version number in the source.

git tag v0.1.0
git push origin v0.1.0

The Publish to PyPI workflow runs the tests, builds the sdist and wheel, checks that the built version equals the tag, publishes to PyPI through Trusted Publishing and creates a GitHub release with the artifacts. One-time setup on PyPI: under the dvm-contextlayer project (or as a pending publisher before the first release) add a GitHub publisher with owner divyavanmahajan, repository mycontextlayer, workflow publish.yml, environment pypi. Also create the pypi environment in the GitHub repository settings (optionally with required reviewers). No PyPI token is needed.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dvm_contextlayer-1.0.0.tar.gz (60.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dvm_contextlayer-1.0.0-py3-none-any.whl (66.1 kB view details)

Uploaded Python 3

File details

Details for the file dvm_contextlayer-1.0.0.tar.gz.

File metadata

  • Download URL: dvm_contextlayer-1.0.0.tar.gz
  • Upload date:
  • Size: 60.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dvm_contextlayer-1.0.0.tar.gz
Algorithm Hash digest
SHA256 34287ce82188931f49e452be84910711348dbefc3310523f1b372e046811def7
MD5 74d8f88554e0fcfcd9e6aa0dd5e3389a
BLAKE2b-256 9509425fae8d2b47970844137bf3988bfe499a6e1916a7adc8e38d53dd623c20

See more details on using hashes here.

Provenance

The following attestation bundles were made for dvm_contextlayer-1.0.0.tar.gz:

Publisher: publish.yml on divyavanmahajan/mycontextlayer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dvm_contextlayer-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for dvm_contextlayer-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6e0678451762ecfdb180eb73723bda537bf10ee341591efe975a9fafd56f7937
MD5 a3cffccc7a3cbb7f9800d86a42a3b0fc
BLAKE2b-256 a5990e56dcb4073404556a9029659460c93201cc7e8de3c2e2b00d4d5c6db3c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for dvm_contextlayer-1.0.0-py3-none-any.whl:

Publisher: publish.yml on divyavanmahajan/mycontextlayer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page