frankfurtermcp 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.
- frankfurtermcp-0.1.0/.env.template +7 -0
- frankfurtermcp-0.1.0/.github/dependabot.yml +11 -0
- frankfurtermcp-0.1.0/.github/workflows/pypi-publish.yml +30 -0
- frankfurtermcp-0.1.0/.github/workflows/uv-pytest.yml +31 -0
- frankfurtermcp-0.1.0/.gitignore +162 -0
- frankfurtermcp-0.1.0/.pre-commit-config.yaml +34 -0
- frankfurtermcp-0.1.0/LICENSE +21 -0
- frankfurtermcp-0.1.0/PKG-INFO +102 -0
- frankfurtermcp-0.1.0/README.md +90 -0
- frankfurtermcp-0.1.0/pyproject.toml +29 -0
- frankfurtermcp-0.1.0/src/frankfurtermcp/__init__.py +14 -0
- frankfurtermcp-0.1.0/src/frankfurtermcp/common.py +25 -0
- frankfurtermcp-0.1.0/src/frankfurtermcp/composition.py +61 -0
- frankfurtermcp-0.1.0/src/frankfurtermcp/server.py +196 -0
- frankfurtermcp-0.1.0/src/frankfurtermcp/utils.py +54 -0
- frankfurtermcp-0.1.0/tests/__init__.py +0 -0
- frankfurtermcp-0.1.0/tests/test_composition.py +125 -0
- frankfurtermcp-0.1.0/tests/test_server.py +190 -0
- frankfurtermcp-0.1.0/uv.lock +589 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "uv" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: pypi-publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release-build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Install the project
|
|
21
|
+
run: uv sync --locked --all-extras --dev
|
|
22
|
+
|
|
23
|
+
- name: Build release distributions
|
|
24
|
+
run: |
|
|
25
|
+
uv build
|
|
26
|
+
|
|
27
|
+
- name: Upload distributions
|
|
28
|
+
run: |
|
|
29
|
+
# Requires UV_PUBLISH_TOKEN to be set
|
|
30
|
+
uv publish
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
on:
|
|
2
|
+
push:
|
|
3
|
+
branches: [master]
|
|
4
|
+
paths:
|
|
5
|
+
- "**.py"
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
paths:
|
|
9
|
+
- "**.py"
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
name: pytest
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
uv-pytest:
|
|
18
|
+
name: python
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v5
|
|
26
|
+
|
|
27
|
+
- name: Install the project
|
|
28
|
+
run: uv sync --locked --all-extras --dev
|
|
29
|
+
|
|
30
|
+
- name: Run tests using pytest
|
|
31
|
+
run: uv run --group test pytest tests/
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
110
|
+
.pdm.toml
|
|
111
|
+
.pdm-python
|
|
112
|
+
.pdm-build/
|
|
113
|
+
|
|
114
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
115
|
+
__pypackages__/
|
|
116
|
+
|
|
117
|
+
# Celery stuff
|
|
118
|
+
celerybeat-schedule
|
|
119
|
+
celerybeat.pid
|
|
120
|
+
|
|
121
|
+
# SageMath parsed files
|
|
122
|
+
*.sage.py
|
|
123
|
+
|
|
124
|
+
# Environments
|
|
125
|
+
.env
|
|
126
|
+
.venv
|
|
127
|
+
env/
|
|
128
|
+
venv/
|
|
129
|
+
ENV/
|
|
130
|
+
env.bak/
|
|
131
|
+
venv.bak/
|
|
132
|
+
|
|
133
|
+
# Spyder project settings
|
|
134
|
+
.spyderproject
|
|
135
|
+
.spyproject
|
|
136
|
+
|
|
137
|
+
# Rope project settings
|
|
138
|
+
.ropeproject
|
|
139
|
+
|
|
140
|
+
# mypy
|
|
141
|
+
.mypy_cache/
|
|
142
|
+
.dmypy.json
|
|
143
|
+
dmypy.json
|
|
144
|
+
|
|
145
|
+
# Pyre type checker
|
|
146
|
+
.pyre/
|
|
147
|
+
|
|
148
|
+
# pytype static type analyzer
|
|
149
|
+
.pytype/
|
|
150
|
+
|
|
151
|
+
# Cython debug symbols
|
|
152
|
+
cython_debug/
|
|
153
|
+
|
|
154
|
+
# Visual Studio Code workspace settings
|
|
155
|
+
# .vscode/
|
|
156
|
+
|
|
157
|
+
# PyCharm
|
|
158
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
159
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
160
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
161
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
162
|
+
#.idea/
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
default_language_version:
|
|
2
|
+
python: python3
|
|
3
|
+
|
|
4
|
+
repos:
|
|
5
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
6
|
+
rev: v5.0.0
|
|
7
|
+
hooks:
|
|
8
|
+
- id: trailing-whitespace
|
|
9
|
+
exclude: tests
|
|
10
|
+
- id: end-of-file-fixer
|
|
11
|
+
- id: check-merge-conflict
|
|
12
|
+
- id: check-case-conflict
|
|
13
|
+
- id: check-json
|
|
14
|
+
- id: check-toml
|
|
15
|
+
exclude: tests/fixtures/invalid_lock/uv\.lock
|
|
16
|
+
- id: check-yaml
|
|
17
|
+
- id: pretty-format-json
|
|
18
|
+
args: [--autofix, --no-ensure-ascii, --no-sort-keys]
|
|
19
|
+
- id: check-ast
|
|
20
|
+
- id: debug-statements
|
|
21
|
+
- id: check-docstring-first
|
|
22
|
+
- id: detect-private-key
|
|
23
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
24
|
+
# Ruff version.
|
|
25
|
+
rev: v0.11.13
|
|
26
|
+
hooks:
|
|
27
|
+
# Run the linter.
|
|
28
|
+
- id: ruff
|
|
29
|
+
args: [--fix, --exit-non-zero-on-fix, --respect-gitignore]
|
|
30
|
+
exclude: ".*uv.lock|.*_static"
|
|
31
|
+
# Run the formatter.
|
|
32
|
+
- id: ruff-format
|
|
33
|
+
args: [--respect-gitignore]
|
|
34
|
+
exclude: ".*uv.lock|.*_static"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025--present Anirban Basu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: frankfurtermcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A MCP server for the Frankfurter API for currency exchange rates.
|
|
5
|
+
Author-email: Anirban Basu <anirbanbasu@users.noreply.github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: fastmcp>=2.7.0
|
|
10
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
[](https://www.python.org/downloads/release/python-3120/)
|
|
14
|
+
[](#)
|
|
15
|
+
|
|
16
|
+
[](https://github.com/anirbanbasu/frankfurtermcp/actions/workflows/dependabot/dependabot-updates) [](https://github.com/anirbanbasu/frankfurtermcp/actions/workflows/uv-pytest.yml)
|
|
17
|
+
# Frankfurter MCP
|
|
18
|
+
|
|
19
|
+
[Frankfurter](https://frankfurter.dev/) is a useful API for latest currency exchange rates, historical data, or time series published by sources such as the European Central Bank. Should you need to access the Frankfurter API as tools for language model agents exposed over the Model Context Protocol (MCP), Frankfurter MCP is what you need.
|
|
20
|
+
|
|
21
|
+
## Project status
|
|
22
|
+
|
|
23
|
+
Following is a table of some updates regarding the project status. Note that these do not correspond to specific commits or milestones.
|
|
24
|
+
|
|
25
|
+
| Date | Status | Notes or observations |
|
|
26
|
+
|----------|:-------------:|----------------------|
|
|
27
|
+
| June 8, 2025 | active | Added dynamic composition.<br/>**TODO**: Exception handling; outgoing proxy and self-signed certificate configuration; Dockerisation. |
|
|
28
|
+
| June 7, 2025 | active | Added tools to cover all the functionalities of the Frankfurter API. |
|
|
29
|
+
| June 7, 2025 | active | Project started. |
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
The directory where you clone this repository will be referred to as the _working directory_ or _WD_ hereinafter.
|
|
34
|
+
|
|
35
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/). To install the project with its essential dependencies in a virtual environment, run the following in the _WD_. To install all non-essential dependencies (_which are required for developing and testing_), add the `--all-extras` flag to the following command.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv sync
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Environment variables
|
|
43
|
+
|
|
44
|
+
Following is a list of environment variables that can be used to configure the application. A template of environment variables is provided in the file `.env.template`.
|
|
45
|
+
|
|
46
|
+
The following environment variables can be specified, prefixed with `FASTMCP_SERVER_`: `HOST`, `PORT`, `DEBUG` and `LOG_LEVEL`. See [key configuration options](https://gofastmcp.com/servers/fastmcp#key-configuration-options) for FastMCP. Note that `on_duplicate_` prefixed options specified as environment variables _will be ignored_.
|
|
47
|
+
|
|
48
|
+
| Variable | [Default value] and description |
|
|
49
|
+
|--------------|----------------|
|
|
50
|
+
| `MCP_SERVER_TRANSPORT` | [streamable-http] The acceptable options are `stdio`, `sse` or `streamable-http`. Given the use-case of running this MCP server as a remotely accessible endpoint, there is no real reason to choose `stdio`. |
|
|
51
|
+
| `FRANKFURTER_API_URL` | [https://api.frankfurter.dev/v1] If you are [self-hosting the Frankfurter API](https://hub.docker.com/r/lineofflight/frankfurter), you should change this to the API endpoint address of your deployment. |
|
|
52
|
+
|
|
53
|
+
## Usage (self-hosted server using `uv`)
|
|
54
|
+
|
|
55
|
+
Copy the `.env.template` file to a `.env` file in the _WD_, to modify the aforementioned environment variables, if you want to use anything other than the default settings.
|
|
56
|
+
|
|
57
|
+
Run the following in the _WD_ to start the MCP server.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv run frankfurtermcp
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If you want to run it without `uv`, assuming that the appropriate virtual environment has been created in the `.venv` within the _WD_, you can start the server calling the following.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
./.venv/bin/python -m frankfurtermcp.server
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If you are installing this packag using `pip` in a virtual environment (possibly managed by `conda` or `pyenv`), then running `python -m frankfurtermcp.server` in that virtual environment.
|
|
70
|
+
|
|
71
|
+
The MCP endpoint will be available over HTTP at [http://localhost:8000/sse](http://localhost:8000/sse) for the Server Sent Events (SSE) transport, or [http://localhost:8000/mcp](http://localhost:8000/mcp) for the streamable HTTP transport. To exit the server, use the Ctrl+C key combination.
|
|
72
|
+
|
|
73
|
+
## Usage (self-hosted server using Docker)
|
|
74
|
+
|
|
75
|
+
**TODO:** To be added, eventually.
|
|
76
|
+
|
|
77
|
+
## Usage (dynamic mounting with FastMCP)
|
|
78
|
+
|
|
79
|
+
To see how to use the MCP server by mounting it dynamically with [FastMCP](https://gofastmcp.com/), check the file `src/frankfurtermcp/composition.py`.
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
|
|
83
|
+
Install [`pre-commit`](https://pre-commit.com/) for Git and [`ruff`](https://docs.astral.sh/ruff/installation/). Then enable `pre-commit` by running the following in the _WD_.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pre-commit install
|
|
87
|
+
```
|
|
88
|
+
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
|
89
|
+
|
|
90
|
+
## Testing
|
|
91
|
+
|
|
92
|
+
To run the provided test cases, execute the following. Add the flag `--capture=tee-sys` to the command to display further console output.
|
|
93
|
+
|
|
94
|
+
_Note that for the tests to succeed, the environment variable `MCP_SERVER_TRANSPORT` must be set to either `sse` or `streamable-http`, or not set at all, in which case it will default to `streamable-http`_.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uv run --group test pytest -q tests/
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
[MIT](https://choosealicense.com/licenses/mit/).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[](https://www.python.org/downloads/release/python-3120/)
|
|
2
|
+
[](#)
|
|
3
|
+
|
|
4
|
+
[](https://github.com/anirbanbasu/frankfurtermcp/actions/workflows/dependabot/dependabot-updates) [](https://github.com/anirbanbasu/frankfurtermcp/actions/workflows/uv-pytest.yml)
|
|
5
|
+
# Frankfurter MCP
|
|
6
|
+
|
|
7
|
+
[Frankfurter](https://frankfurter.dev/) is a useful API for latest currency exchange rates, historical data, or time series published by sources such as the European Central Bank. Should you need to access the Frankfurter API as tools for language model agents exposed over the Model Context Protocol (MCP), Frankfurter MCP is what you need.
|
|
8
|
+
|
|
9
|
+
## Project status
|
|
10
|
+
|
|
11
|
+
Following is a table of some updates regarding the project status. Note that these do not correspond to specific commits or milestones.
|
|
12
|
+
|
|
13
|
+
| Date | Status | Notes or observations |
|
|
14
|
+
|----------|:-------------:|----------------------|
|
|
15
|
+
| June 8, 2025 | active | Added dynamic composition.<br/>**TODO**: Exception handling; outgoing proxy and self-signed certificate configuration; Dockerisation. |
|
|
16
|
+
| June 7, 2025 | active | Added tools to cover all the functionalities of the Frankfurter API. |
|
|
17
|
+
| June 7, 2025 | active | Project started. |
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
The directory where you clone this repository will be referred to as the _working directory_ or _WD_ hereinafter.
|
|
22
|
+
|
|
23
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/). To install the project with its essential dependencies in a virtual environment, run the following in the _WD_. To install all non-essential dependencies (_which are required for developing and testing_), add the `--all-extras` flag to the following command.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv sync
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environment variables
|
|
31
|
+
|
|
32
|
+
Following is a list of environment variables that can be used to configure the application. A template of environment variables is provided in the file `.env.template`.
|
|
33
|
+
|
|
34
|
+
The following environment variables can be specified, prefixed with `FASTMCP_SERVER_`: `HOST`, `PORT`, `DEBUG` and `LOG_LEVEL`. See [key configuration options](https://gofastmcp.com/servers/fastmcp#key-configuration-options) for FastMCP. Note that `on_duplicate_` prefixed options specified as environment variables _will be ignored_.
|
|
35
|
+
|
|
36
|
+
| Variable | [Default value] and description |
|
|
37
|
+
|--------------|----------------|
|
|
38
|
+
| `MCP_SERVER_TRANSPORT` | [streamable-http] The acceptable options are `stdio`, `sse` or `streamable-http`. Given the use-case of running this MCP server as a remotely accessible endpoint, there is no real reason to choose `stdio`. |
|
|
39
|
+
| `FRANKFURTER_API_URL` | [https://api.frankfurter.dev/v1] If you are [self-hosting the Frankfurter API](https://hub.docker.com/r/lineofflight/frankfurter), you should change this to the API endpoint address of your deployment. |
|
|
40
|
+
|
|
41
|
+
## Usage (self-hosted server using `uv`)
|
|
42
|
+
|
|
43
|
+
Copy the `.env.template` file to a `.env` file in the _WD_, to modify the aforementioned environment variables, if you want to use anything other than the default settings.
|
|
44
|
+
|
|
45
|
+
Run the following in the _WD_ to start the MCP server.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv run frankfurtermcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If you want to run it without `uv`, assuming that the appropriate virtual environment has been created in the `.venv` within the _WD_, you can start the server calling the following.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
./.venv/bin/python -m frankfurtermcp.server
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If you are installing this packag using `pip` in a virtual environment (possibly managed by `conda` or `pyenv`), then running `python -m frankfurtermcp.server` in that virtual environment.
|
|
58
|
+
|
|
59
|
+
The MCP endpoint will be available over HTTP at [http://localhost:8000/sse](http://localhost:8000/sse) for the Server Sent Events (SSE) transport, or [http://localhost:8000/mcp](http://localhost:8000/mcp) for the streamable HTTP transport. To exit the server, use the Ctrl+C key combination.
|
|
60
|
+
|
|
61
|
+
## Usage (self-hosted server using Docker)
|
|
62
|
+
|
|
63
|
+
**TODO:** To be added, eventually.
|
|
64
|
+
|
|
65
|
+
## Usage (dynamic mounting with FastMCP)
|
|
66
|
+
|
|
67
|
+
To see how to use the MCP server by mounting it dynamically with [FastMCP](https://gofastmcp.com/), check the file `src/frankfurtermcp/composition.py`.
|
|
68
|
+
|
|
69
|
+
## Contributing
|
|
70
|
+
|
|
71
|
+
Install [`pre-commit`](https://pre-commit.com/) for Git and [`ruff`](https://docs.astral.sh/ruff/installation/). Then enable `pre-commit` by running the following in the _WD_.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pre-commit install
|
|
75
|
+
```
|
|
76
|
+
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
|
77
|
+
|
|
78
|
+
## Testing
|
|
79
|
+
|
|
80
|
+
To run the provided test cases, execute the following. Add the flag `--capture=tee-sys` to the command to display further console output.
|
|
81
|
+
|
|
82
|
+
_Note that for the tests to succeed, the environment variable `MCP_SERVER_TRANSPORT` must be set to either `sse` or `streamable-http`, or not set at all, in which case it will default to `streamable-http`_.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv run --group test pytest -q tests/
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
[MIT](https://choosealicense.com/licenses/mit/).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "frankfurtermcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A MCP server for the Frankfurter API for currency exchange rates."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Anirban Basu", email = "anirbanbasu@users.noreply.github.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"fastmcp>=2.7.0",
|
|
13
|
+
"python-dotenv>=1.1.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
frankfurtermcp = "frankfurtermcp.server:main"
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["hatchling"]
|
|
21
|
+
build-backend = "hatchling.build"
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"icecream>=2.1.4",
|
|
26
|
+
]
|
|
27
|
+
test = [
|
|
28
|
+
"pytest>=8.4.0",
|
|
29
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import metadata
|
|
4
|
+
from frankfurtermcp.common import AppMetadata, EnvironmentVariables, ic
|
|
5
|
+
from frankfurtermcp.utils import parse_env
|
|
6
|
+
|
|
7
|
+
package_metadata = metadata(AppMetadata.PACKAGE_NAME)
|
|
8
|
+
|
|
9
|
+
frankfurter_api_url = parse_env(
|
|
10
|
+
EnvironmentVariables.FRANKFURTER_API_URL,
|
|
11
|
+
default_value=EnvironmentVariables.DEFAULT__FRANKFURTER_API_URL,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
ic(load_dotenv())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from icecream import ic
|
|
3
|
+
except ImportError: # Graceful fallback if IceCream isn't installed.
|
|
4
|
+
ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a) # noqa
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnvironmentVariables:
|
|
8
|
+
"""
|
|
9
|
+
List of environment variables used in this project.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
MCP_SERVER_TRANSPORT = "MCP_SERVER_TRANSPORT"
|
|
13
|
+
DEFAULT__MCP_SERVER_TRANSPORT = "streamable-http"
|
|
14
|
+
ALLOWED__MCP_SERVER_TRANSPORT = ["stdio", "sse", "streamable-http"]
|
|
15
|
+
|
|
16
|
+
FRANKFURTER_API_URL = "FRANKFURTER_API_URL"
|
|
17
|
+
DEFAULT__FRANKFURTER_API_URL = "https://api.frankfurter.dev/v1"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AppMetadata:
|
|
21
|
+
"""
|
|
22
|
+
Metadata for the application.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
PACKAGE_NAME = "frankfurtermcp"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import signal
|
|
2
|
+
import sys
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
from frankfurtermcp.common import EnvironmentVariables
|
|
5
|
+
from frankfurtermcp.server import app as frankfurtermcp
|
|
6
|
+
from frankfurtermcp.utils import parse_env
|
|
7
|
+
|
|
8
|
+
app = FastMCP(
|
|
9
|
+
name="test_composition",
|
|
10
|
+
description="This is a MCP server to test dynamic composition of MCP.",
|
|
11
|
+
tags=["frankfurtermcp", "mcp", "composition"],
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
COMPOSITION_PREFIX = "composition_"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.tool(
|
|
18
|
+
description="The quintessential hello world tool",
|
|
19
|
+
tags=["hello", "world"],
|
|
20
|
+
name="hello_world",
|
|
21
|
+
)
|
|
22
|
+
def hello_world(name: str = None) -> str:
|
|
23
|
+
"""
|
|
24
|
+
A simple tool that returns a greeting message.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name (str): The name to greet.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: A greeting message.
|
|
31
|
+
"""
|
|
32
|
+
suffix = "This is the MCP server to test dynamic composition."
|
|
33
|
+
return f"Hello, {name}! {suffix}" if name else f"Hello World! {suffix}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main():
|
|
37
|
+
"""
|
|
38
|
+
Main function to run the MCP server.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def sigterm_handler(signal, frame):
|
|
42
|
+
"""
|
|
43
|
+
Signal handler to shut down the server gracefully.
|
|
44
|
+
"""
|
|
45
|
+
# This is absolutely necessary to exit the program
|
|
46
|
+
sys.exit(0)
|
|
47
|
+
|
|
48
|
+
signal.signal(signal.SIGTERM, sigterm_handler)
|
|
49
|
+
|
|
50
|
+
app.mount(prefix=COMPOSITION_PREFIX, server=frankfurtermcp, as_proxy=False)
|
|
51
|
+
app.run(
|
|
52
|
+
transport=parse_env(
|
|
53
|
+
EnvironmentVariables.MCP_SERVER_TRANSPORT,
|
|
54
|
+
default_value=EnvironmentVariables.DEFAULT__MCP_SERVER_TRANSPORT,
|
|
55
|
+
allowed_values=EnvironmentVariables.ALLOWED__MCP_SERVER_TRANSPORT,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|