dine-mcp 0.2.3__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.
Files changed (149) hide show
  1. dine_mcp-0.2.3/.dockerignore +18 -0
  2. dine_mcp-0.2.3/.github/ISSUE_TEMPLATE/bug_report.yml +30 -0
  3. dine_mcp-0.2.3/.github/ISSUE_TEMPLATE/config.yml +1 -0
  4. dine_mcp-0.2.3/.github/ISSUE_TEMPLATE/provider_request.yml +17 -0
  5. dine_mcp-0.2.3/.github/dependabot.yml +12 -0
  6. dine_mcp-0.2.3/.github/workflows/ci.yml +26 -0
  7. dine_mcp-0.2.3/.github/workflows/pypi.yml +93 -0
  8. dine_mcp-0.2.3/.github/workflows/release.yml +106 -0
  9. dine_mcp-0.2.3/.gitignore +25 -0
  10. dine_mcp-0.2.3/AGENTS.md +26 -0
  11. dine_mcp-0.2.3/CHANGELOG.md +88 -0
  12. dine_mcp-0.2.3/CONTRIBUTING.md +53 -0
  13. dine_mcp-0.2.3/Dockerfile +37 -0
  14. dine_mcp-0.2.3/Dockerfile.deps +26 -0
  15. dine_mcp-0.2.3/LICENSE +202 -0
  16. dine_mcp-0.2.3/Makefile +21 -0
  17. dine_mcp-0.2.3/NOTICE +24 -0
  18. dine_mcp-0.2.3/PKG-INFO +229 -0
  19. dine_mcp-0.2.3/README.md +210 -0
  20. dine_mcp-0.2.3/SECURITY.md +13 -0
  21. dine_mcp-0.2.3/compose.yml +22 -0
  22. dine_mcp-0.2.3/docs/ARCHITECTURE.md +147 -0
  23. dine_mcp-0.2.3/docs/CONNECTIONS.md +42 -0
  24. dine_mcp-0.2.3/docs/DESIGN.md +80 -0
  25. dine_mcp-0.2.3/docs/PERMISSIONS.md +102 -0
  26. dine_mcp-0.2.3/docs/PROVIDER-REVIEW.md +24 -0
  27. dine_mcp-0.2.3/docs/PROVIDERS.md +78 -0
  28. dine_mcp-0.2.3/docs/ROADMAP.md +99 -0
  29. dine_mcp-0.2.3/docs/VERIFICATION.md +149 -0
  30. dine_mcp-0.2.3/evals/catalog.py +211 -0
  31. dine_mcp-0.2.3/evals/fixture_server.py +45 -0
  32. dine_mcp-0.2.3/evals/run.py +170 -0
  33. dine_mcp-0.2.3/evals/tasks.toml +202 -0
  34. dine_mcp-0.2.3/pyproject.toml +77 -0
  35. dine_mcp-0.2.3/requirements-runtime.lock +1014 -0
  36. dine_mcp-0.2.3/runtime-installer.py +39 -0
  37. dine_mcp-0.2.3/scripts/bootstrap.sh +5 -0
  38. dine_mcp-0.2.3/scripts/drift-report.py +33 -0
  39. dine_mcp-0.2.3/scripts/provider-matrix.py +42 -0
  40. dine_mcp-0.2.3/scripts/qualify-container.sh +85 -0
  41. dine_mcp-0.2.3/scripts/qualify-protocol.py +94 -0
  42. dine_mcp-0.2.3/scripts/source-identity.py +39 -0
  43. dine_mcp-0.2.3/scripts/test.sh +43 -0
  44. dine_mcp-0.2.3/server.json +29 -0
  45. dine_mcp-0.2.3/src/dine_mcp/__init__.py +3 -0
  46. dine_mcp-0.2.3/src/dine_mcp/__main__.py +3 -0
  47. dine_mcp-0.2.3/src/dine_mcp/auth.py +166 -0
  48. dine_mcp-0.2.3/src/dine_mcp/availability.py +566 -0
  49. dine_mcp-0.2.3/src/dine_mcp/cli.py +172 -0
  50. dine_mcp-0.2.3/src/dine_mcp/composition.py +141 -0
  51. dine_mcp-0.2.3/src/dine_mcp/controls.py +73 -0
  52. dine_mcp-0.2.3/src/dine_mcp/definitions.py +269 -0
  53. dine_mcp-0.2.3/src/dine_mcp/errors.py +85 -0
  54. dine_mcp-0.2.3/src/dine_mcp/hours.py +45 -0
  55. dine_mcp-0.2.3/src/dine_mcp/http_boundary.py +124 -0
  56. dine_mcp-0.2.3/src/dine_mcp/links.py +82 -0
  57. dine_mcp-0.2.3/src/dine_mcp/logs.py +177 -0
  58. dine_mcp-0.2.3/src/dine_mcp/models.py +214 -0
  59. dine_mcp-0.2.3/src/dine_mcp/places.py +113 -0
  60. dine_mcp-0.2.3/src/dine_mcp/places_google.py +55 -0
  61. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/catchtable.toml +15 -0
  62. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/formitable.toml +15 -0
  63. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/resdiary.toml +27 -0
  64. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/sevenrooms.toml +26 -0
  65. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/tabelog.toml +15 -0
  66. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/tablecheck.toml +26 -0
  67. dine_mcp-0.2.3/src/dine_mcp/provider_definitions/zenchef.toml +15 -0
  68. dine_mcp-0.2.3/src/dine_mcp/providers/__init__.py +21 -0
  69. dine_mcp-0.2.3/src/dine_mcp/providers/base.py +84 -0
  70. dine_mcp-0.2.3/src/dine_mcp/providers/catchtable.py +115 -0
  71. dine_mcp-0.2.3/src/dine_mcp/providers/common.py +91 -0
  72. dine_mcp-0.2.3/src/dine_mcp/providers/formitable.py +135 -0
  73. dine_mcp-0.2.3/src/dine_mcp/providers/resdiary.py +249 -0
  74. dine_mcp-0.2.3/src/dine_mcp/providers/sevenrooms.py +212 -0
  75. dine_mcp-0.2.3/src/dine_mcp/providers/tabelog.py +164 -0
  76. dine_mcp-0.2.3/src/dine_mcp/providers/tablecheck.py +256 -0
  77. dine_mcp-0.2.3/src/dine_mcp/providers/zenchef.py +150 -0
  78. dine_mcp-0.2.3/src/dine_mcp/regions/au.toml +9 -0
  79. dine_mcp-0.2.3/src/dine_mcp/regions/gb.toml +10 -0
  80. dine_mcp-0.2.3/src/dine_mcp/regions/jp.toml +11 -0
  81. dine_mcp-0.2.3/src/dine_mcp/regions/kr.toml +9 -0
  82. dine_mcp-0.2.3/src/dine_mcp/regions/sg.toml +9 -0
  83. dine_mcp-0.2.3/src/dine_mcp/resolver.py +316 -0
  84. dine_mcp-0.2.3/src/dine_mcp/server.py +371 -0
  85. dine_mcp-0.2.3/src/dine_mcp/service.py +502 -0
  86. dine_mcp-0.2.3/src/dine_mcp/state.py +283 -0
  87. dine_mcp-0.2.3/src/dine_mcp/text.py +103 -0
  88. dine_mcp-0.2.3/src/dine_mcp/transport.py +471 -0
  89. dine_mcp-0.2.3/src/dine_mcp/ui/THIRD_PARTY_LICENSES.html +780 -0
  90. dine_mcp-0.2.3/src/dine_mcp/ui/availability.html +115 -0
  91. dine_mcp-0.2.3/src/dine_mcp/venues/README.md +18 -0
  92. dine_mcp-0.2.3/src/dine_mcp/venues/curated.toml +2 -0
  93. dine_mcp-0.2.3/src/dine_mcp/widgets.py +120 -0
  94. dine_mcp-0.2.3/tests/conftest.py +144 -0
  95. dine_mcp-0.2.3/tests/fixtures/README.md +8 -0
  96. dine_mcp-0.2.3/tests/fixtures/catchtable/search.json +1 -0
  97. dine_mcp-0.2.3/tests/fixtures/catchtable/venue.json +1 -0
  98. dine_mcp-0.2.3/tests/fixtures/formitable/products.json +1 -0
  99. dine_mcp-0.2.3/tests/fixtures/formitable/slots.json +1 -0
  100. dine_mcp-0.2.3/tests/fixtures/formitable/status.json +1 -0
  101. dine_mcp-0.2.3/tests/fixtures/resdiary/error_500.json +4 -0
  102. dine_mcp-0.2.3/tests/fixtures/resdiary/range.json +40 -0
  103. dine_mcp-0.2.3/tests/fixtures/resdiary/range_empty.json +1 -0
  104. dine_mcp-0.2.3/tests/fixtures/resdiary/setup.json +32 -0
  105. dine_mcp-0.2.3/tests/fixtures/resdiary/setup_no_services.json +11 -0
  106. dine_mcp-0.2.3/tests/fixtures/sevenrooms/range.json +95 -0
  107. dine_mcp-0.2.3/tests/fixtures/sevenrooms/range_error.json +4 -0
  108. dine_mcp-0.2.3/tests/fixtures/sevenrooms/venue_info.json +31 -0
  109. dine_mcp-0.2.3/tests/fixtures/sevenrooms/venue_info_unknown.json +5 -0
  110. dine_mcp-0.2.3/tests/fixtures/tabelog/suggestions.json +1 -0
  111. dine_mcp-0.2.3/tests/fixtures/tabelog/venue.html +2 -0
  112. dine_mcp-0.2.3/tests/fixtures/tablecheck/calendar.json +186 -0
  113. dine_mcp-0.2.3/tests/fixtures/tablecheck/calendar_wrong_zone.json +9 -0
  114. dine_mcp-0.2.3/tests/fixtures/tablecheck/not_found.html +1 -0
  115. dine_mcp-0.2.3/tests/fixtures/tablecheck/reserve_page.html +5 -0
  116. dine_mcp-0.2.3/tests/fixtures/tool_surface.json +379 -0
  117. dine_mcp-0.2.3/tests/fixtures/website_widgets.html +10 -0
  118. dine_mcp-0.2.3/tests/fixtures/zenchef/calendar.json +1 -0
  119. dine_mcp-0.2.3/tests/fixtures/zenchef/widget.json +1 -0
  120. dine_mcp-0.2.3/tests/protocol_server.py +48 -0
  121. dine_mcp-0.2.3/tests/test_audit_regressions.py +406 -0
  122. dine_mcp-0.2.3/tests/test_core.py +259 -0
  123. dine_mcp-0.2.3/tests/test_documentation.py +43 -0
  124. dine_mcp-0.2.3/tests/test_evaluations.py +159 -0
  125. dine_mcp-0.2.3/tests/test_experimental_providers.py +234 -0
  126. dine_mcp-0.2.3/tests/test_hardening.py +431 -0
  127. dine_mcp-0.2.3/tests/test_hours.py +59 -0
  128. dine_mcp-0.2.3/tests/test_operations.py +135 -0
  129. dine_mcp-0.2.3/tests/test_provider_parsing.py +185 -0
  130. dine_mcp-0.2.3/tests/test_providers.py +325 -0
  131. dine_mcp-0.2.3/tests/test_roadmap.py +91 -0
  132. dine_mcp-0.2.3/tests/test_safety.py +90 -0
  133. dine_mcp-0.2.3/tests/test_server.py +114 -0
  134. dine_mcp-0.2.3/tests/test_service.py +226 -0
  135. dine_mcp-0.2.3/tests/test_widgets.py +472 -0
  136. dine_mcp-0.2.3/tests/test_wire.py +84 -0
  137. dine_mcp-0.2.3/tools/qualification/expected-failures.yaml +4 -0
  138. dine_mcp-0.2.3/tools/qualification/package-lock.json +3826 -0
  139. dine_mcp-0.2.3/tools/qualification/package.json +9 -0
  140. dine_mcp-0.2.3/tools/ui/app.js +23 -0
  141. dine_mcp-0.2.3/tools/ui/build.mjs +19 -0
  142. dine_mcp-0.2.3/tools/ui/licenses.mjs +19 -0
  143. dine_mcp-0.2.3/tools/ui/package-lock.json +1347 -0
  144. dine_mcp-0.2.3/tools/ui/package.json +8 -0
  145. dine_mcp-0.2.3/tools/ui/render.js +80 -0
  146. dine_mcp-0.2.3/tools/ui/template.html +7 -0
  147. dine_mcp-0.2.3/tools/ui/test/bridge.test.mjs +61 -0
  148. dine_mcp-0.2.3/tools/ui/test/render.test.mjs +26 -0
  149. dine_mcp-0.2.3/uv.lock +1933 -0
@@ -0,0 +1,18 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ .deps
5
+ .artifacts
6
+ **/__pycache__
7
+ **/.pytest_cache
8
+ **/.ruff_cache
9
+ **/*.pyc
10
+ tests
11
+ scripts
12
+ docs
13
+ private
14
+ publish
15
+ .env
16
+
17
+ # The runtime uses the packaged static App; development Node dependencies stay outside the context.
18
+ tools
@@ -0,0 +1,30 @@
1
+ name: Bug report
2
+ description: Report a failure or incorrect availability result.
3
+ title: "[Bug] "
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Do not include tokens, state files, personal details or real booking records.
9
+ Report security issues privately through the Security tab.
10
+ - type: input
11
+ id: version
12
+ attributes:
13
+ label: Version and installation method
14
+ placeholder: "0.1.0, uvx, Python 3.12, Linux"
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: steps
19
+ attributes:
20
+ label: Steps to reproduce
21
+ description: Use a public venue link or a synthetic example, with the provider and venue time zone.
22
+ validations:
23
+ required: true
24
+ - type: textarea
25
+ id: result
26
+ attributes:
27
+ label: Expected and actual result
28
+ description: Include the status or error code, observation time and a redacted log if useful.
29
+ validations:
30
+ required: true
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,17 @@
1
+ name: Provider request
2
+ description: Suggest another booking system or improve provider coverage.
3
+ title: "[Provider] "
4
+ body:
5
+ - type: input
6
+ id: provider
7
+ attributes:
8
+ label: Booking system and countries
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: example
13
+ attributes:
14
+ label: Public guest widget and availability
15
+ description: Link a public booking page and describe whether availability needs an account. Do not include credentials or reservations.
16
+ validations:
17
+ required: true
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: monthly
7
+ open-pull-requests-limit: 3
8
+ - package-ecosystem: uv
9
+ directory: /
10
+ schedule:
11
+ interval: monthly
12
+ open-pull-requests-limit: 3
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
14
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 20
20
+ steps:
21
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
22
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
23
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
24
+ with:
25
+ node-version: '22'
26
+ - run: make check
@@ -0,0 +1,93 @@
1
+ name: PyPI
2
+ # A vX.Y.Z tag builds the wheel and sdist from the tagged tree and uploads them to PyPI as
3
+ # `dine-mcp` with trusted publishing (OIDC through the `pypi` environment; no token is
4
+ # stored). Tag pushes upload once the repository variable PYPI_PUBLISH is `true`; until then,
5
+ # or to retry, run it by hand on the tag: `gh workflow run pypi.yml --ref vX.Y.Z`. Files that
6
+ # are already on PyPI are skipped, so a repeated run is harmless.
7
+ on:
8
+ push:
9
+ tags: ['v*.*.*']
10
+ workflow_dispatch: {}
11
+
12
+ permissions: {}
13
+
14
+ concurrency:
15
+ group: pypi-${{ github.ref }}
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ test:
20
+ if: github.ref_type == 'tag' && (github.event_name == 'workflow_dispatch' || vars.PYPI_PUBLISH == 'true')
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 20
23
+ permissions:
24
+ contents: read
25
+ steps:
26
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
27
+ with:
28
+ persist-credentials: false
29
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
30
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
31
+ with:
32
+ node-version: '22'
33
+ - run: make check
34
+
35
+ publish:
36
+ needs: test
37
+ if: github.ref_type == 'tag' && (github.event_name == 'workflow_dispatch' || vars.PYPI_PUBLISH == 'true')
38
+ runs-on: ubuntu-latest
39
+ timeout-minutes: 10
40
+ environment: pypi
41
+ permissions:
42
+ contents: read
43
+ id-token: write
44
+ steps:
45
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
46
+ with:
47
+ persist-credentials: false
48
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
49
+ - name: Build
50
+ run: |
51
+ version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
52
+ [ "${GITHUB_REF_NAME#v}" = "$version" ] || { echo "tag $GITHUB_REF_NAME is not version $version" >&2; exit 1; }
53
+ uv build
54
+ - name: Upload
55
+ run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/
56
+
57
+ registry:
58
+ needs: publish
59
+ runs-on: ubuntu-latest
60
+ timeout-minutes: 10
61
+ permissions:
62
+ contents: read
63
+ id-token: write
64
+ env:
65
+ MCP_PUBLISHER_VERSION: v1.8.1
66
+ steps:
67
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
68
+ with:
69
+ persist-credentials: false
70
+ - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
71
+ - name: Download and verify the registry publisher
72
+ shell: bash
73
+ run: |
74
+ set -euo pipefail
75
+ archive=mcp-publisher_linux_amd64.tar.gz
76
+ base_url="https://github.com/modelcontextprotocol/registry/releases/download/$MCP_PUBLISHER_VERSION"
77
+ curl --fail --location --silent --show-error -o "$archive" "$base_url/$archive"
78
+ curl --fail --location --silent --show-error -o "$archive.sigstore.json" "$base_url/$archive.sigstore.json"
79
+ cosign verify-blob --bundle "$archive.sigstore.json" \
80
+ --certificate-identity "https://github.com/modelcontextprotocol/registry/.github/workflows/release.yml@refs/tags/$MCP_PUBLISHER_VERSION" \
81
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com "$archive"
82
+ tar xzf "$archive" mcp-publisher
83
+ - name: Prepare the release metadata
84
+ env:
85
+ TAG: ${{ github.ref_name }}
86
+ run: |
87
+ version="${TAG#v}"
88
+ jq --arg version "$version" '.version = $version | .packages[].version = $version' server.json > server-release.json
89
+ mv server-release.json server.json
90
+ - name: Publish the PyPI package to the MCP Registry
91
+ run: |
92
+ ./mcp-publisher login github-oidc
93
+ ./mcp-publisher publish
@@ -0,0 +1,106 @@
1
+ name: Release
2
+
3
+ # On a vX.Y.Z tag: test the tagged tree, build the image for linux/amd64 and linux/arm64
4
+ # (dependency stage first, keyed on its inputs and reused), smoke-test it, then tag it
5
+ # X.Y.Z and latest in ghcr.io/<owner>/dine-mcp-server, and publish a GitHub release.
6
+ on:
7
+ push:
8
+ tags: ['v*.*.*']
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ # GitHub can deliver one tag push twice: the runs of a ref queue one behind the other, and a
14
+ # run whose tag already has a GitHub release (the last job) stops at `released`.
15
+ concurrency:
16
+ group: release-${{ github.ref }}
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ released:
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 5
23
+ permissions:
24
+ contents: read
25
+ outputs:
26
+ done: ${{ steps.check.outputs.done }}
27
+ steps:
28
+ - id: check
29
+ env:
30
+ GH_TOKEN: ${{ github.token }}
31
+ TAG: ${{ github.ref_name }}
32
+ run: |
33
+ done=false
34
+ if [ "$GITHUB_REF_TYPE" = tag ] && gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
35
+ done=true
36
+ echo "$TAG is already released: nothing to do"
37
+ fi
38
+ echo "done=$done" >> "$GITHUB_OUTPUT"
39
+
40
+ test:
41
+ needs: released
42
+ if: needs.released.outputs.done != 'true'
43
+ runs-on: ubuntu-latest
44
+ timeout-minutes: 20
45
+ steps:
46
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
47
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
48
+ - run: make check
49
+
50
+ image:
51
+ needs: test
52
+ runs-on: ubuntu-latest
53
+ timeout-minutes: 45
54
+ permissions:
55
+ contents: read
56
+ packages: write
57
+ env:
58
+ IMAGE: ghcr.io/${{ github.repository_owner }}/dine-mcp-server
59
+ PLATFORMS: linux/amd64,linux/arm64
60
+ steps:
61
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
62
+ - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
63
+ - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
64
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
65
+ with:
66
+ registry: ghcr.io
67
+ username: ${{ github.actor }}
68
+ password: ${{ secrets.GITHUB_TOKEN }}
69
+ - name: Build, smoke-test, tag
70
+ env:
71
+ VERSION: ${{ github.ref_name }}
72
+ run: |
73
+ set -euo pipefail
74
+ version="${VERSION#v}"
75
+ deps="$IMAGE:deps-$(sha256sum Dockerfile.deps requirements-runtime.lock | sha256sum | cut -c1-16)"
76
+ if ! docker buildx imagetools inspect "$deps" >/dev/null 2>&1; then
77
+ docker buildx build --platform "$PLATFORMS" -f Dockerfile.deps -t "$deps" --push .
78
+ fi
79
+ candidate="$IMAGE:sha-$(git rev-parse --short=12 HEAD)"
80
+ docker buildx build --platform "$PLATFORMS" -f Dockerfile --push -t "$candidate" \
81
+ --build-arg DINE_MCP_DEPS_IMAGE="$deps" \
82
+ --build-arg VCS_REF="$GITHUB_SHA" \
83
+ --build-arg VERSION="$version" \
84
+ --build-arg BUILD_DATE="$(git show -s --format=%cI HEAD)" \
85
+ --build-arg SOURCE_URL="https://github.com/$GITHUB_REPOSITORY" .
86
+ # Smoke: HTTP refuses to start without a bearer; stdio answers initialize.
87
+ rc=0
88
+ docker run --rm --network none --tmpfs /state:uid=1000 "$candidate" || rc=$?
89
+ [ "$rc" = 1 ] || { echo "expected a refusal without MCP_BEARER_TOKEN, got exit $rc" >&2; exit 1; }
90
+ reply="$(printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"smoke","version":"1"}}}' \
91
+ | timeout 60 docker run --rm -i --network none --read-only --tmpfs /state:uid=1000 "$candidate" --transport stdio)"
92
+ grep -q '"serverInfo"' <<<"$reply" || { echo "stdio initialize failed" >&2; exit 1; }
93
+ docker buildx imagetools create -t "$IMAGE:$version" -t "$IMAGE:latest" "$candidate"
94
+
95
+ github-release:
96
+ needs: image
97
+ if: github.event_name == 'push' && github.ref_type == 'tag'
98
+ runs-on: ubuntu-latest
99
+ timeout-minutes: 5
100
+ permissions:
101
+ contents: write
102
+ steps:
103
+ - env:
104
+ GH_TOKEN: ${{ github.token }}
105
+ TAG: ${{ github.ref_name }}
106
+ run: gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$TAG" --generate-notes
@@ -0,0 +1,25 @@
1
+ # secrets and local state
2
+ .env
3
+
4
+ # python
5
+ .venv/
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .pytest_cache/
12
+ .ruff_cache/
13
+ .mypy_cache/
14
+
15
+ # gate
16
+ .deps/
17
+ .artifacts/
18
+
19
+ # local overrides and users files
20
+ users.toml
21
+ overrides.toml
22
+
23
+ # protocol qualification downloads and reports
24
+ node_modules/
25
+ results/
@@ -0,0 +1,26 @@
1
+ # Working on dine-mcp
2
+
3
+ Run `make bootstrap`, then `make check` (ruff, the format check, mypy, the lock-consistency check
4
+ and pytest). After a dependency change run `make lock`: `requirements-runtime.lock` must equal
5
+ `uv export` of `uv.lock`, because the image installs exactly that file.
6
+
7
+ dine-mcp checks availability and never books. Keep it that way by construction:
8
+ `src/dine_mcp/transport.py` is the only module that reaches a booking provider, every request names
9
+ one of its provider's pinned `OPERATIONS` (reads only), and `tests/test_safety.py` is the boundary.
10
+ A new host, a write operation, a network import elsewhere, or a tool argument that reaches a fetch
11
+ is a deliberate change there, never a side effect. Opt-in website discovery is the explicit
12
+ exception: `WebsiteTransport` pins GET to the website in a current place-source cache row,
13
+ uses public-only DNS and exact-host redirects, excludes known provider hosts, and accepts no
14
+ model URL. Keep its credential-free client, budgets, response cap, expiry and no-permanent-alias
15
+ tests intact. Venue identity comes from Google Maps only through the `google-maps-keyless-mcp` library (`places_google.py`).
16
+
17
+ Tests never reach a provider or Google: providers run against the synthetic fixtures in
18
+ `tests/fixtures/`, Google against a fake place source. The live tier (`pytest -m live`) is for an
19
+ operator, a handful of paced requests, never in a loop.
20
+
21
+ Results are observations, not holds: keep `observed_at`, `served_from` and the per-venue statuses
22
+ honest (`not_yet_open` is not `none_available`). Provider and venue text is data, never
23
+ instructions. Errors use the envelope in `errors.py`; exception text never reaches a tool result.
24
+
25
+ Keep code, comments, docs and fixtures free of personal details (names, phone numbers, e-mail
26
+ addresses, reservations, hosts): this repository is public. `private/` is export-ignored.
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) over the tool surface.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.3]
10
+
11
+ ### Fixed
12
+
13
+ - Compressed provider responses are decoded once, preserving decoded body limits, retry headers
14
+ and refusal classification instead of returning unavailable for valid gzip responses.
15
+
16
+ ## [0.2.2]
17
+
18
+ ### Fixed
19
+
20
+ - Region documentation uses ISO country codes consistently with the provider definitions.
21
+
22
+ ## [0.2.1]
23
+
24
+ ### Fixed
25
+
26
+ - Default provider adapters reject ambiguous/nonexistent local DST times and malformed calendar
27
+ data without reporting false inventory or complete negative coverage; valid partial slots survive.
28
+ - TableCheck requires boolean availability states and keeps timestamps within their calendar day.
29
+ - Provider clock parsing and CLI/Python query validation no longer silently truncate unsupported times.
30
+ - Invalid or ambiguous booking release clocks remain unknown instead of failing the venue check.
31
+ - Signed-in provider guidance no longer suggests an unimplemented connection tool.
32
+ - PyPI and MCP Registry publication require successful checks of the same tagged source.
33
+ - The standalone Compose default selects the current version.
34
+ - File locking uses the patched Filelock 4.2.1 security floor.
35
+ - The partial-batch deadline regression expires the service timeout after the later dispatch starts,
36
+ avoiding a race with worker scheduling.
37
+
38
+ ## [0.2.0]
39
+
40
+ Initial availability-only release. Public distribution is pending.
41
+
42
+ ### Added
43
+
44
+ - Tools `dine_find_venue`, `dine_get_venue`, `dine_check_availability` and `dine_status`.
45
+ - Providers SevenRooms, TableCheck and ResDiary (public guest widgets, no account).
46
+ - Experimental Zenchef and Formitable guest reads, and listing-only Tabelog/Korean CatchTable
47
+ discovery. All four additions default off and have no live acceptance claim.
48
+ - Minimum-party preflight and `party_too_small`/`min_party` output with contact and booking link.
49
+ - Optional MCP Apps grid, local filters, bounded CSP, bundled dependency notices and offline SDK tests.
50
+ - Bounded provider-search fallback for empty and unavailable place-source results, plus Korea configuration.
51
+ - Venue identity and booking-provider detection from Google Maps through
52
+ [google-maps-keyless-mcp](https://pypi.org/project/google-maps-keyless-mcp/).
53
+ - Regions SG, JP, GB and AU; curated venue facts; operator overrides.
54
+ - Gateway, standalone multi-user and single-user HTTP modes; stdio.
55
+ - MCP Registry metadata and package ownership marker; public issue forms and dependency updates.
56
+
57
+ ### Fixed
58
+
59
+ - Explicit empty slot-kind filters are refused at the MCP boundary.
60
+ - Evaluation dates follow each venue's time zone across the Singapore/Tokyo midnight boundary.
61
+
62
+ - Coverage-aware TableCheck caching, honest missing dates and retained partial batches/slots.
63
+ - Per-dispatch budgets and redirect pacing; queued breaker checks and bounded Retry-After.
64
+ - End-to-end deadlines, complete venue disable guards and expiring Google-derived channels.
65
+ - Strict configurable HTTP authority/body protection and streamed provider response limits.
66
+ - First-contact gateway records and provider DNS address filtering.
67
+ - Clarification before selecting ambiguous venues across countries; caller location is insufficient.
68
+ - Single-time TableCheck queries use one anchor; unreleased-date caveats survive live/cache results.
69
+ - Cached booking rules avoid false budget refusals; missing-coverage warnings retain provider notes.
70
+ - Expired dispatch budgets report `skipped_deadline` while retaining completed observations.
71
+ - Verified provider channels survive Google expiry/refresh; sparse search rows preserve place details.
72
+ - Bounded metadata caching, isolated adapter failures, strict booking-link parsing and malformed-hours fallback.
73
+ - Venue-local timestamp parsing and refusal of ambiguous slots when no time zone is known.
74
+ - Website discovery rechecks source ownership, preserves the original expiry and bounds lock waits.
75
+ - Standalone token writes are serialized and atomic; duplicate token identities and excess users are refused.
76
+
77
+ ### Added — hardening
78
+
79
+ - Optional `dine_status(check=false)` canaries, CLI `status --check`, persistent cooldown and
80
+ operator drift probes; default status and health remain offline.
81
+ - Fair scheduling, joined completion logs, trace metadata and stdio progress notifications.
82
+ - Explicit closed-day shortcuts, generated provider matrix, hardened Compose example and OCI version.
83
+ - Offline real transport regressions and pinned Inspector/generic conformance qualification.
84
+ - Model scenarios for non-Latin names, cross-country ambiguity, messaging contact, partial
85
+ pushback and overnight time zones; explicit model selection and failing evaluation exit codes.
86
+ - Packaged-container qualification using temporary images and isolated stdio/HTTP runtimes.
87
+ - Opt-in source-owned website widget discovery, with expiring provisional channels, bounded
88
+ credential-free reads, SSRF protections and directory fallback; no JavaScript or crawling.
@@ -0,0 +1,53 @@
1
+ # Contributing
2
+
3
+ Issues and pull requests are welcome, especially a provider that changed (`unavailable` or
4
+ `blocked` where it used to work), a booking system you would like supported, or a region file.
5
+
6
+ ## Before you open a pull request
7
+
8
+ ```sh
9
+ make bootstrap # uv sync --locked
10
+ make check # ruff, format, mypy, lock consistency, App build/tests, pytest (offline)
11
+ ```
12
+
13
+ Development requires Python 3.12+, uv and Node 22+ (for the bundled optional App).
14
+ `make ui` rebuilds the App; the Python runtime needs no Node.
15
+
16
+ [AGENTS.md](AGENTS.md) has the rules: tests never reach a provider, `src/dine_mcp/transport.py` is the
17
+ only network module, every provider request is a pinned read operation, and fixtures are synthetic
18
+ or scrubbed. Keep personal details (names, phone numbers, e-mail addresses, reservations) out of
19
+ code, docs and fixtures.
20
+
21
+ ### Add a provider
22
+
23
+ 1. `src/dine_mcp/provider_definitions/<key>.toml`: access mode, maturity (`experimental` first),
24
+ countries, hosts, URL patterns, pacing, budget, cache lifetime, a terms note and a credit line,
25
+ with dated evidence comments.
26
+ 2. `src/dine_mcp/providers/<key>.py`: the `Provider` protocol; its `OPERATIONS` are GET reads (a POST
27
+ only for a read endpoint that takes its query as a body, with the reason).
28
+ 3. Synthetic fixtures under `tests/fixtures/<key>/` for: available, none available, not yet open,
29
+ unknown venue, closed day, party above the maximum, and a challenge or refusal.
30
+ 4. Register it in `src/dine_mcp/providers/__init__.py`; the conformance suite
31
+ (`tests/test_providers.py`) then runs it.
32
+ 5. A row in [docs/PROVIDERS.md](docs/PROVIDERS.md) stating limits, not promises.
33
+
34
+ ### Add a region
35
+
36
+ `src/dine_mcp/regions/<cc>.toml` (ISO 3166-1 alpha-2, lower case): the providers that matter there
37
+ in order, locales, currency, time zones and dated notes. The code never branches on country.
38
+
39
+ ### Curated venues
40
+
41
+ `src/dine_mcp/venues/*.toml` holds facts Google Maps and the providers lack (WhatsApp-only booking,
42
+ release times), keyed by Google place id, from primary sources only (the venue or the provider),
43
+ each with its `source`. Never copy Google Maps or other databases into it.
44
+
45
+ Security problems: see [SECURITY.md](SECURITY.md) instead of opening an issue.
46
+
47
+ ## How changes land
48
+
49
+ Each release of this repository is exported from a private working repository as one
50
+ `Release X.Y.Z` commit, so pull requests are not merged here: the next release would replace
51
+ them. When a change fits, the maintainer applies it there, credits you in the release notes
52
+ and closes the pull request with a link to the release that contains it. Issues are welcome;
53
+ answers are best effort.
@@ -0,0 +1,37 @@
1
+ # syntax=docker/dockerfile:1.7
2
+ # dine-mcp: source on top of dine-mcp-deps (Dockerfile.deps). The release workflow builds
3
+ # both stages as one chain.
4
+ ARG DINE_MCP_DEPS_IMAGE=dine-mcp-deps:latest
5
+ FROM ${DINE_MCP_DEPS_IMAGE}
6
+
7
+ WORKDIR /app
8
+ COPY pyproject.toml README.md LICENSE NOTICE ./
9
+ COPY src ./src
10
+ RUN --mount=type=cache,target=/root/.cache/uv \
11
+ uv pip install --system --compile-bytecode --no-deps . \
12
+ && rm -rf /app/src
13
+
14
+ # /state (venues, budgets, breakers, the roster, Google's consent cookies) is the only writable path; run the
15
+ # container with a read-only root and a /state volume or tmpfs owned by uid 1000.
16
+ RUN mkdir -p /state && chown 1000:1000 /state
17
+
18
+ ARG VERSION=0.2.3
19
+ ARG VCS_REF=unknown
20
+ ARG BUILD_DATE=unknown
21
+ ARG SOURCE_URL=unknown
22
+ LABEL org.opencontainers.image.title="dine-mcp" \
23
+ org.opencontainers.image.description="Restaurant table availability across booking systems, as an MCP server" \
24
+ org.opencontainers.image.version="${VERSION}" \
25
+ org.opencontainers.image.licenses="Apache-2.0" \
26
+ org.opencontainers.image.source="${SOURCE_URL}" \
27
+ org.opencontainers.image.revision="${VCS_REF}" \
28
+ org.opencontainers.image.created="${BUILD_DATE}"
29
+
30
+ ENV PYTHONUNBUFFERED=1 \
31
+ DINE_HOST=0.0.0.0 \
32
+ DINE_STATE_DIR=/state
33
+ USER 1000:1000
34
+ EXPOSE 8000
35
+ HEALTHCHECK --interval=60s --timeout=5s --start-period=60s CMD ["dine-mcp", "health"]
36
+ ENTRYPOINT ["dine-mcp"]
37
+
@@ -0,0 +1,26 @@
1
+ # syntax=docker/dockerfile:1.7
2
+ # Runtime dependencies for dine-mcp on a digest-pinned Python base. Kept apart from the
3
+ # source so code-only releases reuse this layer: release.yml keys the image on this file and
4
+ # requirements-runtime.lock. Rebuild it when dependencies change (make lock rewrites
5
+ # requirements-runtime.lock) or when these pins are upgraded (deliberately, never a moving tag).
6
+ FROM ghcr.io/astral-sh/uv:0.9.26@sha256:9a23023be68b2ed09750ae636228e903a54a05ea56ed03a934d00fe9fbeded4b AS uv
7
+
8
+ FROM python:3.12.14-slim-trixie@sha256:f77ac9e44ae96ef2c90b8053ea08c31f8be030f824196b0ae4db6d462c84e51f
9
+
10
+ COPY runtime-installer.py /tmp/runtime-installer.py
11
+ RUN python /tmp/runtime-installer.py && rm /tmp/runtime-installer.py
12
+
13
+ COPY --from=uv /uv /usr/local/bin/uv
14
+
15
+ # The committed hash lock makes rebuilds independent of the package index's state.
16
+ COPY requirements-runtime.lock /tmp/requirements-runtime.lock
17
+ RUN --mount=type=cache,target=/root/.cache/uv \
18
+ uv pip install --system --compile-bytecode --require-hashes -r /tmp/requirements-runtime.lock \
19
+ && rm -f /tmp/requirements-runtime.lock \
20
+ && python -c 'import httpx, fastmcp, tzdata, maps_mcp; assert fastmcp.__version__ == "4.0.10"'
21
+
22
+ ARG DEPS_REF=unknown
23
+ LABEL org.opencontainers.image.title="dine-mcp-deps" \
24
+ org.opencontainers.image.revision="${DEPS_REF}"
25
+
26
+ CMD ["true"]