OS Apps: Poznote and Joplin

Published: Jan 29, 2026 by Isaac Johnson

I came across PozNote from a Marius Post about installing to a NAS. As we just looked at Alexandrie, I wanted to compare and contrast this similar self-hosted document suite.

Joplin is another on my list of note taking suites to check out. This one is a bit more native-app driven. I’ll explore launching in docker and using on Android.

Poznote

The initial setup is really quite easy as Tim Poznanski provides a Docker-compose file

Let’s start with a docker instance.

I’ll clone down the repo

builder@DESKTOP-QADGF36:~/Workspaces$ git clone https://github.com/timothepoznanski/poznote.git
Cloning into 'poznote'...
remote: Enumerating objects: 21194, done.
remote: Counting objects: 100% (363/363), done.
remote: Compressing objects: 100% (165/165), done.
remote: Total 21194 (delta 211), reused 207 (delta 197), pack-reused 20831 (from 3)
Receiving objects: 100% (21194/21194), 108.25 MiB | 10.26 MiB/s, done.
Resolving deltas: 100% (12995/12995), done.
builder@DESKTOP-QADGF36:~/Workspaces$ cd poznote/

We then need an .env file.

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ cp .env.example .env

Then fire it up

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ docker compose up
[+] Running 23/23
 ✔ webserver Pulled                                                                                                          15.3s
   ✔ 2d35ebdb57d9 Already exists                                                                                              0.0s
   ✔ 2bef9cb3f990 Pull complete                                                                                               8.0s
   ✔ a4c72d6e07f1 Pull complete                                                                                               8.1s
   ✔ 4bcd12be3e1e Pull complete                                                                                               8.1s
   ✔ c9d33dc5a138 Pull complete                                                                                               8.4s
   ✔ 32e14496102d Pull complete                                                                                               8.4s
   ✔ ae0ebd5abde8 Pull complete                                                                                               9.8s
   ✔ 15366323bdc0 Pull complete                                                                                               9.8s
   ✔ d1a32e3112b7 Pull complete                                                                                               9.9s
   ✔ 2e24563ec23a Pull complete                                                                                               9.9s
   ✔ 4f4fb700ef54 Pull complete                                                                                              10.0s
   ✔ 9596dcfb2908 Pull complete                                                                                              10.0s
   ✔ 69bac53097be Pull complete                                                                                              12.3s
   ✔ baa2f7aa6c46 Pull complete                                                                                              12.3s
   ✔ 05d21884c3c5 Pull complete                                                                                              12.3s
   ✔ 6dd61dfa7ea5 Pull complete                                                                                              12.4s
   ✔ 466e82c97eae Pull complete                                                                                              12.4s
   ✔ b32021bc1bca Pull complete                                                                                              12.4s
   ✔ 4604bebe4b7a Pull complete                                                                                              12.5s
   ✔ 9ac6d8b845a0 Pull complete                                                                                              13.3s
   ✔ 3af4a35860c1 Pull complete                                                                                              13.3s
   ✔ e6368db40c84 Pull complete                                                                                              14.2s
[+] Running 2/2
 ✔ Network poznote_default        Created                                                                                     0.1s
 ✔ Container poznote-webserver-1  Created                                                                                     0.5s
Attaching to webserver-1
webserver-1  | Poznote Initialization Script - Setting up data directory...
webserver-1  | Setting correct permissions recursively on /var/www/html/data...
webserver-1  | Final permissions check for /var/www/html/data:
webserver-1  | total 28
webserver-1  | drwxrwxr-x    7 www-data www-data      4096 Jan 23 13:33 .
webserver-1  | drwxr-xr-x    1 www-data www-data      4096 Jan 23 04:22 ..
webserver-1  | drwxrwxr-x    2 www-data www-data      4096 Jan 23 13:33 attachments
webserver-1  | drwxrwxr-x    2 www-data www-data      4096 Jan 23 13:33 backups
webserver-1  | drwxrwxr-x    2 www-data www-data      4096 Jan 23 13:33 database
webserver-1  | drwxrwxr-x    2 www-data www-data      4096 Jan 23 13:33 entries
webserver-1  | drwxrwxr-x    2 www-data www-data      4096 Jan 23 13:33 users
webserver-1  | /usr/lib/python3.12/site-packages/supervisor/options.py:13: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
webserver-1  |   import pkg_resources
webserver-1  | 2026-01-23 13:33:13,486 INFO Set uid to user 0 succeeded
webserver-1  | 2026-01-23 13:33:13,492 INFO RPC interface 'supervisor' initialized
webserver-1  | 2026-01-23 13:33:13,492 CRIT Server 'unix_http_server' running without any HTTP authentication checking
webserver-1  | 2026-01-23 13:33:13,493 INFO supervisord started with pid 1
webserver-1  | 2026-01-23 13:33:14,496 INFO spawned: 'nginx' with pid 17
webserver-1  | 2026-01-23 13:33:14,501 INFO spawned: 'php-fpm' with pid 18
webserver-1  | [23-Jan-2026 13:33:14] NOTICE: fpm is running, pid 18
webserver-1  | [23-Jan-2026 13:33:14] NOTICE: ready to handle connections
webserver-1  | 2026-01-23 13:33:15,539 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
webserver-1  | 2026-01-23 13:33:15,540 INFO success: php-fpm entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

I can now reach the login page at http://localhost:8040

/content/images/2026/01/poznote-01.png

We are greeted with a nice welcome note by Tim

/content/images/2026/01/poznote-02.png

The actions we can take (from left to right) are “search/replace”, “favourite”, “Share Note” (cloud), “Attachments”, “Open In New Tab”, “Duplicate”, “Move”, “Download”, “Convert to Markdown”, “Delete” and “Information”.

Information gives us some details like title, when it was created and modified and tags:

/content/images/2026/01/poznote-03.png

Let’s try using it a bit:

If we click the gear icon we can go to settings

/content/images/2026/01/poznote-05.png

it is here we can do occasional backups of all the notes

/content/images/2026/01/poznote-06.png

The restore is similar with more of a question driven wizard

/content/images/2026/01/poznote-07.png

The “Disaster Recovery” assumes the DB may have been corrupted and can rescan things to recreate user accounts and sharing links

/content/images/2026/01/poznote-08.png

User management let’s me rename users via Actions and create a new profile

/content/images/2026/01/poznote-10.png

However, passwords are shared (that is, admin users use the “admin” password from the .env file and the users use the “user” password set in .env)

This is not the case, however, if we setup SSO (in which case the federated IdP verifies passwords and we just need to have a user account created with a matching email address).

Exposing externally

Let’s fire up an A Record first as we’ll need that soon

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 174.53.161.33 -n notes
{
  "ARecords": [
    {
      "ipv4Address": "174.53.161.33"
    }
  ],
  "TTL": 3600,
  "etag": "0182c2a8-df28-4ef7-bb84-53132c995ca6",
  "fqdn": "notes.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/notes",
  "name": "notes",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

I’m going to use Helm to sort this out.

I built out charts with Gemini CLI based on the Dockerfile. Then created a proper values file

autoscaling:
  enabled: true
  maxReplicas: 2
  minReplicas: 1
  targetCPUUtilizationPercentage: 80

config:
  POZNOTE_PASSWORD: "sampleadmin"
  POZNOTE_PASSWORD_USER: "sampleuser"

persistence:
  enabled: true
  storageClass: "local-path"
  accessMode: ReadWriteOnce
  size: 5Gi

ingress:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  className: nginx
  enabled: true
  hosts:
  - host: notes.tpk.pw
    paths:
    - path: /
      pathType: ImplementationSpecific
  tls:
  - hosts:
    - notes.tpk.pw
    secretName: notestpkpw-tls

I can now use the values to install

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ helm install poznote -f ./values.yml ./charts/poznote/
NAME: poznote
LAST DEPLOYED: Fri Jan 23 11:09:06 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

The first issue I encountered was the helm charts neglected to create an SA so the RS was blocked

$ kubectl describe rs poznote-5568d5bcb7 | tail -n1
  Warning  FailedCreate  51s (x16 over 3m35s)  replicaset-controller  Error creating: pods "poznote-5568d5bcb7-" is forbidden: error looking up service account default/poznote: serviceaccount "poznote" not found

I had Gemini CLI help sort that out:

/content/images/2026/01/poznote-11.png

This time it created a pod that started to launch

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ kubectl get po | grep poz
poznote-5568d5bcb7-f4rgv                             0/1     ContainerCreating   0                   12s
builder@DESKTOP-QADGF36:~/Workspaces/poznote$ kubectl get po | grep poz
poznote-5568d5bcb7-f4rgv                             1/1     Running     0                   110s

Once the cert was satisfied I could access the site

/content/images/2026/01/poznote-12.png

The first step I’ll do is to rename the admin user

/content/images/2026/01/poznote-13.png

SSO

I’ll use GCP Oauth Federated identity. I start with going to the API and Services section, OAuth consent screen

/content/images/2026/01/poznote-14.png

From there I’ll use “Create Client”

/content/images/2026/01/poznote-15.png

I’ll want to use “/oidc_callback.php” for the callback for this app

/content/images/2026/01/poznote-16.png

We can also edit this later (for instance, fix the Redirect URI)

/content/images/2026/01/poznote-20.png

This will give us a Client ID and Secret we’ll need for the SSO section

/content/images/2026/01/poznote-17.png

I’ll now add them as a block to the values file

config:
  POZNOTE_PASSWORD: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  POZNOTE_PASSWORD_USER: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  POZNOTE_OIDC_ENABLED: "true"
  POZNOTE_OIDC_PROVIDER_NAME: "Google"
  POZNOTE_OIDC_ISSUER: "https://accounts.google.com"
  POZNOTE_OIDC_CLIENT_ID: "511842454269-iugi6qdj6p9qlel9n0qklgejdckc6a12.apps.googleusercontent.com"
  POZNOTE_OIDC_CLIENT_SECRET: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  POZNOTE_OIDC_REDIRECT_URI: "https://notes.tpk.pw/oidc_callback.php"
  POZNOTE_OIDC_SCOPES: "openid profile email"

/content/images/2026/01/poznote-18.png

I’ll upgrade with helm, but because this really just changes the Configmap, I need to forcibly bounce the pod

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ !2086
helm upgrade poznote -f ./values.yml ./charts/poznote/
Release "poznote" has been upgraded. Happy Helming!
NAME: poznote
LAST DEPLOYED: Fri Jan 23 13:48:39 2026
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
builder@DESKTOP-QADGF36:~/Workspaces/poznote$ kubectl get po | grep poz
poznote-5568d5bcb7-f4rgv                             1/1     Running            0                   152m
builder@DESKTOP-QADGF36:~/Workspaces/poznote$ kubectl get po | grep poz
poznote-5568d5bcb7-f4rgv                             1/1     Running            0                   152m
builder@DESKTOP-QADGF36:~/Workspaces/poznote$ kubectl delete po poznote-5568d5bcb7-f4rgv
pod "poznote-5568d5bcb7-f4rgv" deleted
builder@DESKTOP-QADGF36:~/Workspaces/poznote$ kubectl get po | grep poz
poznote-5568d5bcb7-zhdzg                             1/1     Running            0                   5s

We can now see SSO as an option, but to use it, I’ll need to add users

/content/images/2026/01/poznote-19.png

Here we can see me adding a user, verifying it works and then logging out to show the admin user cannot see the new users files (My New Note)

API

We can use the REST API to get things like notes associated to a user:

For instance, the admin users

$ curl -u 'builder:xxxxxxxxxxxxx' -H "X-User-ID: 1" https://notes.tpk.pw/api/v1/notes
{"success":true,"notes":[{"id":1,"heading":"Welcome to Poznote","type":"note","tags":null,"folder":"Getting Started","folder_id":1,"workspace":"Poznote","updated":"2026-01-23 17:28:56","created":"2026-01-23 17:28:56"}]}

I can fetch my federated user as well if I know the key ID

$ curl -u 'builder:xxxxxxxxxxxxxxxxxxx' -H "X-User-ID: 3" https://notes.tpk.pw/apq/v1/notes | j
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   407    0   407    0     0   3662      0 --:--:-- --:--:-- --:--:--  3700
{
  "success": true,
  "notes": [
    {
      "id": 2,
      "heading": "My New Note",
      "type": "markdown",
      "tags": "",
      "folder": "Getting Started",
      "folder_id": 1,
      "workspace": "Poznote",
      "updated": "2026-01-23 20:02:09",
      "created": "2026-01-23 20:01:59"
    },
    {
      "id": 1,
      "heading": "Welcome to Poznote",
      "type": "note",
      "tags": null,
      "folder": "Getting Started",
      "folder_id": 1,
      "workspace": "Poznote",
      "updated": "2026-01-23 20:01:41",
      "created": "2026-01-23 20:01:41"
    }
  ]
}

MCP Server

We can see the documentation for the MCP server included here

There is a way to run this as an httpStreamable with just python and a virtual env

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ cd mcp-server/
builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ python3 -m venv venv
builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ source venv/bin/activate
(venv) builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ pip install --upgrade pip
Requirement already satisfied: pip in ./venv/lib/python3.11/site-packages (23.3.1)
Collecting pip
  Downloading pip-25.3-py3-none-any.whl.metadata (4.7 kB)
Downloading pip-25.3-py3-none-any.whl (1.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 15.7 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 23.3.1
    Uninstalling pip-23.3.1:
      Successfully uninstalled pip-23.3.1
Successfully installed pip-25.3

Then install the libs and use the import command to verify we have all the required libraries installed

(venv) builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ pip install -e .
Obtaining file:///home/builder/Workspaces/poznote/mcp-server
  Installing build dependencies ... done
  Checking if build backend supports build_editable ... done
  Getting requirements to build editable ... done
  Installing backend dependencies ... done
  Preparing editable metadata (pyproject.toml) ... done
Collecting httpx>=0.27.0 (from poznote-mcp-server==2.0.0)
  Using cached httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
Collecting mcp>=1.0.0 (from poznote-mcp-server==2.0.0)
  Downloading mcp-1.25.0-py3-none-any.whl.metadata (89 kB)
Collecting uvicorn>=0.30.0 (from poznote-mcp-server==2.0.0)
  Downloading uvicorn-0.40.0-py3-none-any.whl.metadata (6.7 kB)
Collecting anyio (from httpx>=0.27.0->poznote-mcp-server==2.0.0)
  Downloading anyio-4.12.1-py3-none-any.whl.metadata (4.3 kB)
Collecting certifi (from httpx>=0.27.0->poznote-mcp-server==2.0.0)
  Downloading certifi-2026.1.4-py3-none-any.whl.metadata (2.5 kB)
Collecting httpcore==1.* (from httpx>=0.27.0->poznote-mcp-server==2.0.0)
  Using cached httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)
Collecting idna (from httpx>=0.27.0->poznote-mcp-server==2.0.0)
  Using cached idna-3.11-py3-none-any.whl.metadata (8.4 kB)
Collecting h11>=0.16 (from httpcore==1.*->httpx>=0.27.0->poznote-mcp-server==2.0.0)
  Using cached h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
Collecting httpx-sse>=0.4 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading httpx_sse-0.4.3-py3-none-any.whl.metadata (9.7 kB)
Collecting jsonschema>=4.20.0 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading jsonschema-4.26.0-py3-none-any.whl.metadata (7.6 kB)
Collecting pydantic-settings>=2.5.2 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading pydantic_settings-2.12.0-py3-none-any.whl.metadata (3.4 kB)
Collecting pydantic<3.0.0,>=2.11.0 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Using cached pydantic-2.12.5-py3-none-any.whl.metadata (90 kB)
Collecting pyjwt>=2.10.1 (from pyjwt[crypto]>=2.10.1->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Using cached PyJWT-2.10.1-py3-none-any.whl.metadata (4.0 kB)
Collecting python-multipart>=0.0.9 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading python_multipart-0.0.21-py3-none-any.whl.metadata (1.8 kB)
Collecting sse-starlette>=1.6.1 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading sse_starlette-3.2.0-py3-none-any.whl.metadata (12 kB)
Collecting starlette>=0.27 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading starlette-0.52.1-py3-none-any.whl.metadata (6.3 kB)
Collecting typing-extensions>=4.9.0 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Collecting typing-inspection>=0.4.1 (from mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Using cached typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)
Collecting annotated-types>=0.6.0 (from pydantic<3.0.0,>=2.11.0->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.41.5 (from pydantic<3.0.0,>=2.11.0->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Using cached pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.3 kB)
Collecting attrs>=22.2.0 (from jsonschema>=4.20.0->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading attrs-25.4.0-py3-none-any.whl.metadata (10 kB)
Collecting jsonschema-specifications>=2023.03.6 (from jsonschema>=4.20.0->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading jsonschema_specifications-2025.9.1-py3-none-any.whl.metadata (2.9 kB)
Collecting referencing>=0.28.4 (from jsonschema>=4.20.0->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading referencing-0.37.0-py3-none-any.whl.metadata (2.8 kB)
Collecting rpds-py>=0.25.0 (from jsonschema>=4.20.0->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.1 kB)
Collecting python-dotenv>=0.21.0 (from pydantic-settings>=2.5.2->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)
Collecting cryptography>=3.4.0 (from pyjwt[crypto]>=2.10.1->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl.metadata (5.7 kB)
Collecting cffi>=2.0.0 (from cryptography>=3.4.0->pyjwt[crypto]>=2.10.1->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB)
Collecting pycparser (from cffi>=2.0.0->cryptography>=3.4.0->pyjwt[crypto]>=2.10.1->mcp>=1.0.0->poznote-mcp-server==2.0.0)
  Downloading pycparser-3.0-py3-none-any.whl.metadata (8.2 kB)
Collecting click>=7.0 (from uvicorn>=0.30.0->poznote-mcp-server==2.0.0)
  Using cached click-8.3.1-py3-none-any.whl.metadata (2.6 kB)
Using cached httpx-0.28.1-py3-none-any.whl (73 kB)
Using cached httpcore-1.0.9-py3-none-any.whl (78 kB)
Using cached h11-0.16.0-py3-none-any.whl (37 kB)
Downloading mcp-1.25.0-py3-none-any.whl (233 kB)
Downloading pydantic-2.12.5-py3-none-any.whl (463 kB)
Downloading pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 7.4 MB/s  0:00:00
Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)
Downloading anyio-4.12.1-py3-none-any.whl (113 kB)
Downloading httpx_sse-0.4.3-py3-none-any.whl (9.0 kB)
Downloading idna-3.11-py3-none-any.whl (71 kB)
Downloading jsonschema-4.26.0-py3-none-any.whl (90 kB)
Downloading attrs-25.4.0-py3-none-any.whl (67 kB)
Downloading jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
Downloading pydantic_settings-2.12.0-py3-none-any.whl (51 kB)
Using cached PyJWT-2.10.1-py3-none-any.whl (22 kB)
Downloading cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl (4.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 12.0 MB/s  0:00:00
Downloading cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (215 kB)
Downloading python_dotenv-1.2.1-py3-none-any.whl (21 kB)
Downloading python_multipart-0.0.21-py3-none-any.whl (24 kB)
Downloading referencing-0.37.0-py3-none-any.whl (26 kB)
Downloading rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (390 kB)
Downloading sse_starlette-3.2.0-py3-none-any.whl (12 kB)
Downloading starlette-0.52.1-py3-none-any.whl (74 kB)
Downloading typing_extensions-4.15.0-py3-none-any.whl (44 kB)
Downloading typing_inspection-0.4.2-py3-none-any.whl (14 kB)
Downloading uvicorn-0.40.0-py3-none-any.whl (68 kB)
Downloading click-8.3.1-py3-none-any.whl (108 kB)
Downloading certifi-2026.1.4-py3-none-any.whl (152 kB)
Downloading pycparser-3.0-py3-none-any.whl (48 kB)
Building wheels for collected packages: poznote-mcp-server
  Building editable for poznote-mcp-server (pyproject.toml) ... done
  Created wheel for poznote-mcp-server: filename=poznote_mcp_server-2.0.0-py3-none-any.whl size=3798 sha256=35c6bfcb8265f881c4c122a4b4ae72323cb13a1e18fadf82f70da30c833bd49d
  Stored in directory: /tmp/pip-ephem-wheel-cache-exv8wnh5/wheels/ce/d8/23/efd3bf9165997d0dd03cdfd2b3953ff6a0fa97cf0a5315dbd9
Successfully built poznote-mcp-server
Installing collected packages: typing-extensions, rpds-py, python-multipart, python-dotenv, pyjwt, pycparser, idna, httpx-sse, h11, click, certifi, attrs, annotated-types, uvicorn, typing-inspection, referencing, pydantic-core, httpcore, cffi, anyio, starlette, pydantic, jsonschema-specifications, httpx, cryptography, sse-starlette, pydantic-settings, jsonschema, mcp, poznote-mcp-server
Successfully installed annotated-types-0.7.0 anyio-4.12.1 attrs-25.4.0 certifi-2026.1.4 cffi-2.0.0 click-8.3.1 cryptography-46.0.3 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 httpx-sse-0.4.3 idna-3.11 jsonschema-4.26.0 jsonschema-specifications-2025.9.1 mcp-1.25.0 poznote-mcp-server-2.0.0 pycparser-3.0 pydantic-2.12.5 pydantic-core-2.41.5 pydantic-settings-2.12.0 pyjwt-2.10.1 python-dotenv-1.2.1 python-multipart-0.0.21 referencing-0.37.0 rpds-py-0.30.0 sse-starlette-3.2.0 starlette-0.52.1 typing-extensions-4.15.0 typing-inspection-0.4.2 uvicorn-0.40.0
(venv) builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ python -c "import poznote_mcp; print('poznote_mcp OK')"
poznote_mcp OK

We can now run it

(venv) builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ export POZNOTE_API_URL=https://notes.tpk.pw/api/v1
export POZNOTE_USERNAME=builder
export POZNOTE_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxx
export POZNOTE_USER_ID=3
export POZNOTE_DEFAULT_WORKSPACE=Poznote
(venv) builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$
(venv) builder@DESKTOP-QADGF36:~/Workspaces/poznote/mcp-server$ poznote-mcp serve --host=0.0.0.0 --port=8877
2026-01-23 20:44:59,963 - poznote-mcp - INFO - Starting Poznote MCP Server (HTTP mode on 0.0.0.0:8877)...
INFO:     Started server process [87870]
INFO:     Waiting for application startup.
2026-01-23 20:44:59,988 - mcp.server.streamable_http_manager - INFO - StreamableHTTP session manager started
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8877 (Press CTRL+C to quit)

Testing with Gemini CLI

Gemini CLI

Let’s add it to Gemini CLI

builder@DESKTOP-QADGF36:~/Workspaces/poznote$ nvm use lts/jod
Now using node v22.22.0 (npm v10.9.4)
builder@DESKTOP-QADGF36:~/Workspaces/poznote$ gemini mcp add --transport http poznote http://localhost:8877/mcp
MCP server "poznote" added to project settings. (http)

We can now see the poznote MCP server with tools listed upon firing up Gemini CLI and using /mcp list

/content/images/2026/01/poznote-21.png

Creating some AI notes

I first used Gemini CLI

Which took a few to work out

/content/images/2026/01/poznote-25.png

But it did want to pivot to using Python3 for setting it up

/content/images/2026/01/poznote-26.png

However, that did work

/content/images/2026/01/poznote-27.png

and we can see the output

/content/images/2026/01/poznote-28.png

Copilot

I set this up in VS Code for Copilot

/content/images/2026/01/poznote-23.png

and asked it to create Camping notes

Which worked great

/content/images/2026/01/poznote-24.png

Solving a problem? Creating a doc?

I wanted to solve a real problem/challenge, not just a made up test.

Years ago, my buddy created and sent out a word doc for BWCA trips which I’ve had imported into Google Docs.

I can export it as markdown

/content/images/2026/01/poznote-30.png

I’ll now fire up the MCP server and copy the doc locally

/content/images/2026/01/poznote-31.png

I verified Gemini could see the MCP server and it was running

/content/images/2026/01/poznote-32.png

I can now use Gemini CLI to not only load it to Poznote, but reformat it for warm vs cold weather:

which create a pretty good file (though it might still need some refinement)

/content/images/2026/01/poznote-34.png

Now I have yet to get video to work - and maybe that will just not happen - but I did find it renders Mermaid diagrams natively:

/content/images/2026/01/joplin-16.png

using just the “mermaid” tag:

/content/images/2026/01/poznote-35.png

Joplin

We explored Poznote in detail, but there was another app that seemed very similar, Joplin which I had noted from an older Marius post.

While the Website for Joplin doesn’t really cover installing the server, we can look to the Docker compose file

I can use docker compose up on the dev file to get started

/content/images/2026/01/joplin-01.png

I can now reach the server on port 22300

/content/images/2026/01/joplin-02.png

We can now login with admin@localhost/admin

/content/images/2026/01/joplin-03.png

Once I logged in and changed the admin password, I could see the main dashboard

/content/images/2026/01/joplin-04.png

The next step would likely be to add users

/content/images/2026/01/joplin-05.png

However, the Joplin “server” is really just a backend. All the functionality lives in native apps

Android App

So let’s install the android app

/content/images/2026/01/joplin-06.png

To set a sync server, we have to Configuration and then click “Synchronisation” (sic)

/content/images/2026/01/joplin-07.png

From there we have a variety of options including their own Cloud option as well as our server

/content/images/2026/01/joplin-08.png

It would be there that I could set values to my ingress server, or if in the same network, the local IP of my laptop running Joplin

/content/images/2026/01/joplin-09.png

Using Joplin

We can create a variety of options from the “+” new menu option

/content/images/2026/01/joplin-10.png

For instance, I unfolded and quickly made some diagrams with text

/content/images/2026/01/joplin-11.png

Those diagrams can then be added to a document as a local reference

/content/images/2026/01/joplin-12.png

which renders fine, locally

/content/images/2026/01/joplin-13.png

But now is where we get stuck. I can’t really export it in any real way.

If I “email” the document, this is what they see:

/content/images/2026/01/joplin-14.png

I synced to OneDrive, which did back things up, but as you can see, not in a usable way

/content/images/2026/01/joplin-15.png

Summary

Today we started with Poznote by Tim Poznanski. In many ways, it is similar to Alexandrie, but it has some key features that Alexandrie lacks: namely a built-in MCP server and easy to setup MFA. It’s a nice extra that we have native Mermaid diagrams as I often use them in my Github and Azure repos.

I was just thinking of starting to use it for an app and leveraging the “tasks” list (to compare with Vikunja)

/content/images/2026/01/poznote-36.png

The other suite+app we explored was Joplin by Laurent Cozic. It has a lot of interesting features in the android app, but the lack of a usable web back end means I’m unlikely to move forward with it. I do as much work on my laptops and desktops as I do on my phone and cannot live in a mobile-app-only system. That said, it was very easy to use and if you want to use their backend, they have a reasonable SaaS offering:

/content/images/2026/01/poznote-37.png

And for many, a key feature is that it is not using a US-based cloud provider. From their FAQ:

Joplin Cloud data and servers are all located in France (Paris), thus your data benefits from strong data protection regulations including GDPR

poznote joplin mcp geminicli docker kubernetes opensource

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes