matrix-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrix_mcp-0.1.0/.github/workflows/ci.yml +51 -0
- matrix_mcp-0.1.0/.github/workflows/release.yml +24 -0
- matrix_mcp-0.1.0/.gitignore +11 -0
- matrix_mcp-0.1.0/.pre-commit-config.yaml +33 -0
- matrix_mcp-0.1.0/AGENTS.md +7 -0
- matrix_mcp-0.1.0/PKG-INFO +106 -0
- matrix_mcp-0.1.0/README.md +83 -0
- matrix_mcp-0.1.0/pyproject.toml +96 -0
- matrix_mcp-0.1.0/src/matrix_mcp/__init__.py +8 -0
- matrix_mcp-0.1.0/src/matrix_mcp/_version.py +24 -0
- matrix_mcp-0.1.0/src/matrix_mcp/auth.py +122 -0
- matrix_mcp-0.1.0/src/matrix_mcp/cli.py +138 -0
- matrix_mcp-0.1.0/src/matrix_mcp/config.py +51 -0
- matrix_mcp-0.1.0/src/matrix_mcp/matrix_client.py +157 -0
- matrix_mcp-0.1.0/src/matrix_mcp/mcp_server.py +56 -0
- matrix_mcp-0.1.0/tests/__init__.py +0 -0
- matrix_mcp-0.1.0/tests/test_auth.py +26 -0
- matrix_mcp-0.1.0/tests/test_config.py +36 -0
- matrix_mcp-0.1.0/tests/test_matrix_client.py +73 -0
- matrix_mcp-0.1.0/tests/test_mcp_tools.py +59 -0
- matrix_mcp-0.1.0/tests/test_version.py +8 -0
- matrix_mcp-0.1.0/uv.lock +2220 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, macos-latest]
|
|
16
|
+
python-version: ["3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v7
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
run: uv python install ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --all-extras --dev
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: uv run pytest
|
|
32
|
+
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: uv build
|
|
35
|
+
|
|
36
|
+
lint:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v6
|
|
40
|
+
|
|
41
|
+
- name: Install uv
|
|
42
|
+
uses: astral-sh/setup-uv@v7
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
run: uv python install 3.12
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: uv sync --all-extras --dev
|
|
49
|
+
|
|
50
|
+
- name: Run pre-commit (via prek)
|
|
51
|
+
uses: j178/prek-action@v1
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Upload Python Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
deploy:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment:
|
|
11
|
+
name: pypi
|
|
12
|
+
url: https://pypi.org/p/matrix-mcp
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v7
|
|
21
|
+
- name: Build
|
|
22
|
+
run: uv build
|
|
23
|
+
- name: Publish package distributions to PyPI
|
|
24
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
- id: check-merge-conflict
|
|
10
|
+
- id: debug-statements
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
13
|
+
rev: v0.14.9
|
|
14
|
+
hooks:
|
|
15
|
+
- id: ruff-check
|
|
16
|
+
args: [--fix]
|
|
17
|
+
- id: ruff-format
|
|
18
|
+
|
|
19
|
+
- repo: local
|
|
20
|
+
hooks:
|
|
21
|
+
- id: mypy
|
|
22
|
+
name: mypy (type checker)
|
|
23
|
+
entry: uv run mypy src tests
|
|
24
|
+
language: system
|
|
25
|
+
types: [python]
|
|
26
|
+
pass_filenames: false
|
|
27
|
+
|
|
28
|
+
- id: ty
|
|
29
|
+
name: ty (type checker)
|
|
30
|
+
entry: uv run ty check
|
|
31
|
+
language: system
|
|
32
|
+
types: [python]
|
|
33
|
+
pass_filenames: false
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Matrix MCP
|
|
2
|
+
|
|
3
|
+
This repository is a generic Matrix MCP server.
|
|
4
|
+
|
|
5
|
+
Do not include deployment-specific names, internal domains, company names, private OAuth-provider details, private Matrix room IDs, private Matrix user IDs, or production logs in source, docs, tests, commit messages, issues, or pull requests.
|
|
6
|
+
|
|
7
|
+
Keep the package useful for any Matrix homeserver and any MCP client.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matrix-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Matrix MCP server for connecting coding agents to Matrix and MindRoom conversations.
|
|
5
|
+
Author: MindRoom
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: fastmcp>=2.12
|
|
9
|
+
Requires-Dist: mindroom-nio>=0.25
|
|
10
|
+
Requires-Dist: platformdirs>=4.3
|
|
11
|
+
Requires-Dist: pydantic-settings>=2.9
|
|
12
|
+
Requires-Dist: pydantic>=2.11
|
|
13
|
+
Requires-Dist: rich>=14.0
|
|
14
|
+
Requires-Dist: typer>=0.16
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
17
|
+
Requires-Dist: pre-commit>=4; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest>=8.4; extra == 'dev'
|
|
20
|
+
Requires-Dist: ruff>=0.13; extra == 'dev'
|
|
21
|
+
Requires-Dist: ty; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Matrix MCP
|
|
25
|
+
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](https://github.com/mindroom-ai/matrix-mcp/actions/workflows/ci.yml)
|
|
28
|
+
[](https://pypi.org/project/matrix-mcp/)
|
|
29
|
+
[](https://pypi.org/project/matrix-mcp/)
|
|
30
|
+
[](https://docs.mindroom.chat/plugins/)
|
|
31
|
+
[](https://modelcontextprotocol.io/)
|
|
32
|
+
|
|
33
|
+
<img src="https://media.githubusercontent.com/media/mindroom-ai/mindroom/refs/heads/main/frontend/public/logo.png" alt="MindRoom Logo" align="right" width="120" />
|
|
34
|
+
|
|
35
|
+
Local-first Matrix access for MCP clients.
|
|
36
|
+
|
|
37
|
+
Matrix MCP lets Claude Code and other MCP clients read and write Matrix rooms.
|
|
38
|
+
It is intended to make MindRoom conversations available to local coding agents without giving hosted agents access to the local filesystem.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv tool install git+https://github.com/mindroom-ai/matrix-mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For local development:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv sync --extra dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Login
|
|
53
|
+
|
|
54
|
+
Matrix SSO:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
matrix-mcp auth sso https://matrix.example.com
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Existing Matrix access token:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
matrix-mcp auth token https://matrix.example.com @alice:example.com "$MATRIX_ACCESS_TOKEN" --device-id DEVICEID
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Password auth, when enabled by the homeserver:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
matrix-mcp auth password https://matrix.example.com @alice:example.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Credentials are stored in the user config directory reported by:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
matrix-mcp config-path
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Claude Code
|
|
79
|
+
|
|
80
|
+
Add the local MCP server:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
claude mcp add matrix -- matrix-mcp serve
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The server runs over stdio. It does not expose a local HTTP port during normal MCP operation.
|
|
87
|
+
|
|
88
|
+
## Tools
|
|
89
|
+
|
|
90
|
+
- `matrix_whoami`: show the configured Matrix user/device.
|
|
91
|
+
- `matrix_list_rooms`: list rooms joined by the authenticated user.
|
|
92
|
+
- `matrix_read_room_recent`: read recent text events from a room.
|
|
93
|
+
- `matrix_send_message`: send a text message, optionally as a Matrix thread reply.
|
|
94
|
+
|
|
95
|
+
The tool instructions tell clients to prefer read tools first and only send messages when the user explicitly asks.
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv run --extra dev pytest
|
|
101
|
+
uv run --extra dev ruff check .
|
|
102
|
+
uv run --extra dev ruff format --check .
|
|
103
|
+
uv run --extra dev mypy src tests
|
|
104
|
+
uv run --extra dev ty check
|
|
105
|
+
uv build
|
|
106
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Matrix MCP
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://github.com/mindroom-ai/matrix-mcp/actions/workflows/ci.yml)
|
|
5
|
+
[](https://pypi.org/project/matrix-mcp/)
|
|
6
|
+
[](https://pypi.org/project/matrix-mcp/)
|
|
7
|
+
[](https://docs.mindroom.chat/plugins/)
|
|
8
|
+
[](https://modelcontextprotocol.io/)
|
|
9
|
+
|
|
10
|
+
<img src="https://media.githubusercontent.com/media/mindroom-ai/mindroom/refs/heads/main/frontend/public/logo.png" alt="MindRoom Logo" align="right" width="120" />
|
|
11
|
+
|
|
12
|
+
Local-first Matrix access for MCP clients.
|
|
13
|
+
|
|
14
|
+
Matrix MCP lets Claude Code and other MCP clients read and write Matrix rooms.
|
|
15
|
+
It is intended to make MindRoom conversations available to local coding agents without giving hosted agents access to the local filesystem.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install git+https://github.com/mindroom-ai/matrix-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For local development:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv sync --extra dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Login
|
|
30
|
+
|
|
31
|
+
Matrix SSO:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
matrix-mcp auth sso https://matrix.example.com
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Existing Matrix access token:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
matrix-mcp auth token https://matrix.example.com @alice:example.com "$MATRIX_ACCESS_TOKEN" --device-id DEVICEID
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Password auth, when enabled by the homeserver:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
matrix-mcp auth password https://matrix.example.com @alice:example.com
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Credentials are stored in the user config directory reported by:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
matrix-mcp config-path
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Claude Code
|
|
56
|
+
|
|
57
|
+
Add the local MCP server:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
claude mcp add matrix -- matrix-mcp serve
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The server runs over stdio. It does not expose a local HTTP port during normal MCP operation.
|
|
64
|
+
|
|
65
|
+
## Tools
|
|
66
|
+
|
|
67
|
+
- `matrix_whoami`: show the configured Matrix user/device.
|
|
68
|
+
- `matrix_list_rooms`: list rooms joined by the authenticated user.
|
|
69
|
+
- `matrix_read_room_recent`: read recent text events from a room.
|
|
70
|
+
- `matrix_send_message`: send a text message, optionally as a Matrix thread reply.
|
|
71
|
+
|
|
72
|
+
The tool instructions tell clients to prefer read tools first and only send messages when the user explicitly asks.
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv run --extra dev pytest
|
|
78
|
+
uv run --extra dev ruff check .
|
|
79
|
+
uv run --extra dev ruff format --check .
|
|
80
|
+
uv run --extra dev mypy src tests
|
|
81
|
+
uv run --extra dev ty check
|
|
82
|
+
uv build
|
|
83
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "matrix-mcp"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A Matrix MCP server for connecting coding agents to Matrix and MindRoom conversations."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "MindRoom" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"fastmcp>=2.12",
|
|
15
|
+
"mindroom-nio>=0.25",
|
|
16
|
+
"platformdirs>=4.3",
|
|
17
|
+
"pydantic>=2.11",
|
|
18
|
+
"pydantic-settings>=2.9",
|
|
19
|
+
"rich>=14.0",
|
|
20
|
+
"typer>=0.16",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"mypy>=1.14",
|
|
26
|
+
"pre-commit>=4",
|
|
27
|
+
"pytest>=8.4",
|
|
28
|
+
"pytest-asyncio>=1.0",
|
|
29
|
+
"ruff>=0.13",
|
|
30
|
+
"ty",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
matrix-mcp = "matrix_mcp.cli:app"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.version]
|
|
37
|
+
source = "vcs"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.hooks.vcs]
|
|
40
|
+
version-file = "src/matrix_mcp/_version.py"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/matrix_mcp"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
asyncio_mode = "auto"
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
51
|
+
target-version = "py312"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["ALL"]
|
|
55
|
+
ignore = [
|
|
56
|
+
"D", # pydocstyle
|
|
57
|
+
"ANN", # annotations
|
|
58
|
+
"COM812", # trailing comma
|
|
59
|
+
"ISC001", # implicit string concat
|
|
60
|
+
"T20", # print statements are acceptable for CLI output
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"src/matrix_mcp/cli.py" = [
|
|
65
|
+
"B008", # Typer uses Option/Argument defaults intentionally
|
|
66
|
+
"FBT001", # Typer boolean options are command-line flags
|
|
67
|
+
"FBT003", # Typer boolean option defaults are command-line flag defaults
|
|
68
|
+
"PLR0913", # CLI commands naturally expose several command-line options
|
|
69
|
+
"TC003", # Typer resolves annotations at runtime
|
|
70
|
+
]
|
|
71
|
+
"src/matrix_mcp/config.py" = [
|
|
72
|
+
"S105", # redaction placeholder is intentionally not a real token
|
|
73
|
+
]
|
|
74
|
+
"tests/*" = [
|
|
75
|
+
"S101", # assert usage is fine in tests
|
|
76
|
+
"PLR2004", # magic values are fine in tests
|
|
77
|
+
"S105", # test tokens
|
|
78
|
+
"S106", # test tokens
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.ruff.format]
|
|
82
|
+
quote-style = "double"
|
|
83
|
+
|
|
84
|
+
[tool.mypy]
|
|
85
|
+
python_version = "3.12"
|
|
86
|
+
strict = true
|
|
87
|
+
warn_return_any = true
|
|
88
|
+
warn_unused_configs = true
|
|
89
|
+
packages = ["matrix_mcp"]
|
|
90
|
+
|
|
91
|
+
[[tool.mypy.overrides]]
|
|
92
|
+
module = ["nio"]
|
|
93
|
+
ignore_missing_imports = true
|
|
94
|
+
|
|
95
|
+
[tool.ty.rules]
|
|
96
|
+
unused-ignore-comment = "ignore"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
4
|
+
from urllib.parse import parse_qs, quote, urlencode, urlsplit
|
|
5
|
+
|
|
6
|
+
from nio import AsyncClient, LoginResponse
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from matrix_mcp.config import MatrixMCPConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LoginResult(BaseModel):
|
|
13
|
+
homeserver: str
|
|
14
|
+
user_id: str
|
|
15
|
+
device_id: str | None
|
|
16
|
+
access_token: str
|
|
17
|
+
|
|
18
|
+
def to_config(self) -> MatrixMCPConfig:
|
|
19
|
+
return MatrixMCPConfig(
|
|
20
|
+
homeserver=self.homeserver,
|
|
21
|
+
user_id=self.user_id,
|
|
22
|
+
device_id=self.device_id,
|
|
23
|
+
access_token=self.access_token,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_sso_redirect_url(*, homeserver: str, redirect_url: str, idp_id: str | None = None) -> str:
|
|
28
|
+
base = homeserver.rstrip("/")
|
|
29
|
+
provider = f"/{quote(idp_id, safe='')}" if idp_id else ""
|
|
30
|
+
query = urlencode({"redirectUrl": redirect_url})
|
|
31
|
+
return f"{base}/_matrix/client/v3/login/sso/redirect{provider}?{query}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def extract_login_token(query: str) -> str:
|
|
35
|
+
values = parse_qs(query, keep_blank_values=False)
|
|
36
|
+
token = values.get("loginToken", [None])[0]
|
|
37
|
+
if not token:
|
|
38
|
+
msg = "Matrix SSO callback did not include loginToken"
|
|
39
|
+
raise ValueError(msg)
|
|
40
|
+
return token
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SSOCallbackServer:
|
|
44
|
+
def __init__(self, *, host: str = "127.0.0.1", port: int = 8767) -> None:
|
|
45
|
+
self.token: str | None = None
|
|
46
|
+
self.error: Exception | None = None
|
|
47
|
+
|
|
48
|
+
owner = self
|
|
49
|
+
|
|
50
|
+
class CallbackHandler(BaseHTTPRequestHandler):
|
|
51
|
+
def do_GET(self) -> None:
|
|
52
|
+
try:
|
|
53
|
+
owner.token = extract_login_token(urlsplit(self.path).query)
|
|
54
|
+
self.send_response(200)
|
|
55
|
+
self.end_headers()
|
|
56
|
+
self.wfile.write(b"Matrix MCP login complete. You can close this tab.")
|
|
57
|
+
except Exception as exc: # noqa: BLE001
|
|
58
|
+
owner.error = exc
|
|
59
|
+
self.send_response(400)
|
|
60
|
+
self.end_headers()
|
|
61
|
+
self.wfile.write(b"Matrix MCP login failed. Return to the terminal.")
|
|
62
|
+
|
|
63
|
+
def log_message(self, format: str, *_args: object) -> None: # noqa: A002
|
|
64
|
+
del format, _args
|
|
65
|
+
|
|
66
|
+
self._server = HTTPServer((host, port), CallbackHandler)
|
|
67
|
+
self.redirect_url = f"http://{host}:{self._server.server_port}/callback"
|
|
68
|
+
|
|
69
|
+
def wait_for_token(self) -> str:
|
|
70
|
+
self._server.handle_request()
|
|
71
|
+
self._server.server_close()
|
|
72
|
+
if self.error is not None:
|
|
73
|
+
raise self.error
|
|
74
|
+
if self.token is None:
|
|
75
|
+
msg = "Matrix SSO callback did not complete"
|
|
76
|
+
raise RuntimeError(msg)
|
|
77
|
+
return self.token
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def login_with_password(
|
|
81
|
+
*,
|
|
82
|
+
homeserver: str,
|
|
83
|
+
user: str,
|
|
84
|
+
password: str,
|
|
85
|
+
device_name: str = "matrix-mcp",
|
|
86
|
+
) -> LoginResult:
|
|
87
|
+
client = AsyncClient(homeserver.rstrip("/"), user)
|
|
88
|
+
try:
|
|
89
|
+
response = await client.login(password=password, device_name=device_name)
|
|
90
|
+
finally:
|
|
91
|
+
await client.close()
|
|
92
|
+
if isinstance(response, LoginResponse):
|
|
93
|
+
return LoginResult(
|
|
94
|
+
homeserver=homeserver.rstrip("/"),
|
|
95
|
+
user_id=response.user_id,
|
|
96
|
+
device_id=response.device_id,
|
|
97
|
+
access_token=response.access_token,
|
|
98
|
+
)
|
|
99
|
+
msg = f"Matrix password login failed: {response}"
|
|
100
|
+
raise RuntimeError(msg)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def login_with_token(
|
|
104
|
+
*,
|
|
105
|
+
homeserver: str,
|
|
106
|
+
login_token: str,
|
|
107
|
+
device_name: str = "matrix-mcp",
|
|
108
|
+
) -> LoginResult:
|
|
109
|
+
client = AsyncClient(homeserver.rstrip("/"))
|
|
110
|
+
try:
|
|
111
|
+
response = await client.login(token=login_token, device_name=device_name)
|
|
112
|
+
finally:
|
|
113
|
+
await client.close()
|
|
114
|
+
if isinstance(response, LoginResponse):
|
|
115
|
+
return LoginResult(
|
|
116
|
+
homeserver=homeserver.rstrip("/"),
|
|
117
|
+
user_id=response.user_id,
|
|
118
|
+
device_id=response.device_id,
|
|
119
|
+
access_token=response.access_token,
|
|
120
|
+
)
|
|
121
|
+
msg = f"Matrix token login failed: {response}"
|
|
122
|
+
raise RuntimeError(msg)
|