meta-data-mcp 2.3.0__tar.gz → 2.3.2__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.
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/PKG-INFO +2 -2
- meta_data_mcp-2.3.2/meta_data_mcp/__init__.py +1 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/oauth_provider.py +22 -1
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/sdk/remote.py +13 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/server.py +119 -16
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_news_tone_v1.html +9 -4
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/pyproject.toml +1 -1
- meta_data_mcp-2.3.0/meta_data_mcp/__init__.py +0 -1
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/.gitignore +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/AUTHORS +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/LICENSE +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/README.md +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/cli.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/client.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/discovery/__init__.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/discovery/loader.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/discovery/state.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/errors.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/fields.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/health.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/provenance.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/provider_config.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/__init__.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/__template__.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/au_data_gov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/ca_open_gov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/cern_opendata.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/ch_sbb.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/de_db.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/eu_copernicus.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/eu_ecb.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/eu_eurostat.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/fr_data_gouv.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_arxiv.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_bgpview.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_coingecko.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_crossref.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_dbnomics.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_disease_sh.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_epss.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_europepmc.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_faostat.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_frankfurter.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_gbif.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_gdelt.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_imf.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_inaturalist.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_met_museum.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_nvd_cve.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_oecd.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_open_library.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_open_meteo.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_openalex.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_openaq.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_opensanctions.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_opensky.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_osm_nominatim.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_osv_dev.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_overpass.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_pubchem.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_rcsb_pdb.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_ripe_stat.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_un_comtrade.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_unesco_heritage.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_who_gho.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_wikidata.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_wikipedia.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_world_bank.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/meta_data_mcp.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/nl_cbs.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/nl_ndov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/nl_rechtspraak.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/nl_tweedekamer.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/sg_data_gov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/uk_gov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/uk_legislation.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/uk_ons.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_arcgis_item.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_cary.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_cdc_socrata.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_census_geocoder.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_cisa_kev.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_clinicaltrials.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_courtlistener.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_data_gov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_faa_nasstatus.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_fayetteville.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_fda_openfda.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_federal_register.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_healthdata_gov.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_nasa.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_nc_onemap.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_ncdeq_gis.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_noaa_awc.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_noaa_ncei.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_noaa_tides.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_raleigh.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_sec_edgar.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_treasury_fiscal.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/us_usgs_earthquake.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/registry.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/routing.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/sdk/__init__.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/serialize.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/transport.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/__init__.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_discovery_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_discovery_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_entity_graph_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_entity_graph_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_molecular_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_molecular_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_museum_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_museum_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_network_topology_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_network_topology_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_news_tone_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_trade_flows_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_trade_flows_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_vulnerability_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_vulnerability_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_geofeatures_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_geofeatures_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_records_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_records_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_timeseries_v1.html +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_timeseries_v1.py +0 -0
- {meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meta-data-mcp
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: MCP server exposing 60+ open data providers to LLM clients via intelligent metadata routing.
|
|
5
5
|
Project-URL: Homepage, https://github.com/derekslinz/meta-data-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/derekslinz/meta-data-mcp
|
|
@@ -41,7 +41,7 @@ Requires-Python: >=3.12
|
|
|
41
41
|
Requires-Dist: anyio>=4.0.0
|
|
42
42
|
Requires-Dist: click>=8.1.7
|
|
43
43
|
Requires-Dist: httpx>=0.28.0
|
|
44
|
-
Requires-Dist: mcp>=1.
|
|
44
|
+
Requires-Dist: mcp>=1.9.0
|
|
45
45
|
Requires-Dist: pydantic>=2.0.0
|
|
46
46
|
Requires-Dist: pyyaml>=6.0
|
|
47
47
|
Requires-Dist: starlette>=1.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.3.2"
|
|
@@ -87,6 +87,22 @@ class InMemoryOAuthProvider(
|
|
|
87
87
|
}
|
|
88
88
|
return f"{self.issuer_url}/oauth/consent?session={session_token}"
|
|
89
89
|
|
|
90
|
+
def peek_session(self, session_token: str) -> dict[str, Any] | None:
|
|
91
|
+
"""Return a shallow copy of a pending consent session without consuming it.
|
|
92
|
+
|
|
93
|
+
Used by the GET consent page to display session details while leaving
|
|
94
|
+
the session intact for the POST approval step. Returns a shallow copy
|
|
95
|
+
so callers cannot accidentally mutate internal provider state.
|
|
96
|
+
Returns None for unknown or expired sessions.
|
|
97
|
+
"""
|
|
98
|
+
session = self._auth_sessions.get(session_token)
|
|
99
|
+
if session is None:
|
|
100
|
+
return None
|
|
101
|
+
if time.time() > session["expires_at"]:
|
|
102
|
+
self._auth_sessions.pop(session_token, None)
|
|
103
|
+
return None
|
|
104
|
+
return dict(session) # shallow copy — callers cannot mutate stored state
|
|
105
|
+
|
|
90
106
|
def consume_session(self, session_token: str) -> dict[str, Any] | None:
|
|
91
107
|
"""Retrieve and remove a pending consent session (one-shot)."""
|
|
92
108
|
session = self._auth_sessions.pop(session_token, None)
|
|
@@ -136,7 +152,12 @@ class InMemoryOAuthProvider(
|
|
|
136
152
|
client: OAuthClientInformationFull,
|
|
137
153
|
authorization_code: AuthorizationCode,
|
|
138
154
|
) -> OAuthToken:
|
|
139
|
-
"""
|
|
155
|
+
"""Consume the authorization code and issue access + refresh tokens.
|
|
156
|
+
|
|
157
|
+
PKCE validation (code_verifier vs code_challenge) is performed by the
|
|
158
|
+
MCP SDK's ``TokenHandler`` before calling this method — this provider
|
|
159
|
+
does not repeat that check.
|
|
160
|
+
"""
|
|
140
161
|
# Remove the used code (one-shot).
|
|
141
162
|
self._auth_codes.pop(authorization_code.code, None)
|
|
142
163
|
|
|
@@ -75,6 +75,19 @@ class RemoteClient:
|
|
|
75
75
|
timeout: float = 30.0,
|
|
76
76
|
auth: Any = None,
|
|
77
77
|
) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Args:
|
|
80
|
+
base_url: Root URL of the server (e.g. ``"https://mcp.example.com"``).
|
|
81
|
+
The ``/sse`` path is appended automatically.
|
|
82
|
+
token: Bearer token for the server's auth middleware.
|
|
83
|
+
Falls back to ``META_DATA_MCP_AUTH_TOKEN`` env var.
|
|
84
|
+
Mutually exclusive with ``auth``.
|
|
85
|
+
timeout: Connection timeout in seconds (default 30 s).
|
|
86
|
+
auth: MCP-compatible OAuth client provider for Authorization Code
|
|
87
|
+
+ PKCE flows. Accepts any object implementing the
|
|
88
|
+
``mcp.client.sse`` ``auth=`` interface (e.g. a custom
|
|
89
|
+
``OAuthClientProvider``). Mutually exclusive with ``token``.
|
|
90
|
+
"""
|
|
78
91
|
if token is not None and auth is not None:
|
|
79
92
|
raise ValueError(
|
|
80
93
|
"token and auth are mutually exclusive — use one or the other"
|
|
@@ -390,7 +390,18 @@ async def run_server(
|
|
|
390
390
|
|
|
391
391
|
oauth_issuer = os.getenv("META_DATA_MCP_OAUTH_ISSUER")
|
|
392
392
|
if oauth_issuer:
|
|
393
|
-
|
|
393
|
+
try:
|
|
394
|
+
from mcp.server.auth.routes import (
|
|
395
|
+
create_auth_routes,
|
|
396
|
+
create_protected_resource_routes,
|
|
397
|
+
)
|
|
398
|
+
except ImportError as exc:
|
|
399
|
+
raise RuntimeError(
|
|
400
|
+
"OAuth support requires an MCP SDK version that provides "
|
|
401
|
+
"`mcp.server.auth.routes.create_protected_resource_routes`. "
|
|
402
|
+
"Please upgrade the `mcp` package to a compatible version or "
|
|
403
|
+
"unset META_DATA_MCP_OAUTH_ISSUER to disable OAuth."
|
|
404
|
+
) from exc
|
|
394
405
|
from mcp.server.auth.settings import ClientRegistrationOptions
|
|
395
406
|
from pydantic import AnyHttpUrl
|
|
396
407
|
from starlette.responses import HTMLResponse
|
|
@@ -400,16 +411,46 @@ async def run_server(
|
|
|
400
411
|
|
|
401
412
|
oauth_provider = InMemoryOAuthProvider(issuer_url=oauth_issuer)
|
|
402
413
|
|
|
414
|
+
import html as _html
|
|
415
|
+
from urllib.parse import urlencode, urlsplit, urlunsplit, parse_qsl
|
|
416
|
+
|
|
417
|
+
def _add_query_params(base_url: str, params: dict[str, str]) -> str:
|
|
418
|
+
"""Append params to base_url, preserving any existing query string.
|
|
419
|
+
|
|
420
|
+
Uses list-of-tuples rather than a dict so that existing duplicate
|
|
421
|
+
or blank-value query params are never silently dropped. Only the
|
|
422
|
+
keys explicitly provided in ``params`` are added/overwritten.
|
|
423
|
+
"""
|
|
424
|
+
parts = urlsplit(base_url)
|
|
425
|
+
# Keep all existing params except any that we're explicitly setting,
|
|
426
|
+
# then append the new ones. This preserves duplicate keys and blanks.
|
|
427
|
+
existing = [
|
|
428
|
+
(k, v)
|
|
429
|
+
for k, v in parse_qsl(parts.query, keep_blank_values=True)
|
|
430
|
+
if k not in params
|
|
431
|
+
]
|
|
432
|
+
merged = existing + list(params.items())
|
|
433
|
+
return urlunsplit(parts._replace(query=urlencode(merged)))
|
|
434
|
+
|
|
403
435
|
# Consent page — GET /oauth/consent?session=<token>
|
|
404
436
|
async def consent_get(request: Request) -> HTMLResponse:
|
|
405
437
|
session_token = request.query_params.get("session", "")
|
|
406
|
-
|
|
438
|
+
# Use the provider's own peek method so expiry is enforced
|
|
439
|
+
# without consuming the session (POST /approve does that).
|
|
440
|
+
session = oauth_provider.peek_session(session_token)
|
|
407
441
|
if session is None:
|
|
408
442
|
return HTMLResponse(
|
|
409
443
|
"<h1>Session expired or invalid.</h1>", status_code=400
|
|
410
444
|
)
|
|
411
|
-
|
|
412
|
-
|
|
445
|
+
# HTML-escape all values derived from dynamic client registration
|
|
446
|
+
# to prevent XSS via a maliciously crafted client_name or scope.
|
|
447
|
+
client_name = _html.escape(
|
|
448
|
+
str(session.get("client_name", session.get("client_id", "?")))
|
|
449
|
+
)
|
|
450
|
+
scopes_html = _html.escape(
|
|
451
|
+
", ".join(session.get("scopes", [])) or "(default)"
|
|
452
|
+
)
|
|
453
|
+
session_token_escaped = _html.escape(session_token)
|
|
413
454
|
return HTMLResponse(f"""<!DOCTYPE html>
|
|
414
455
|
<html lang="en"><head><meta charset="utf-8">
|
|
415
456
|
<title>Authorize — meta-data-mcp</title>
|
|
@@ -424,7 +465,7 @@ async def run_server(
|
|
|
424
465
|
<p><strong>{client_name}</strong> is requesting access to your meta-data-mcp server.</p>
|
|
425
466
|
<p class="scope">Requested scopes: {scopes_html}</p>
|
|
426
467
|
<form method="POST" action="/oauth/consent/approve">
|
|
427
|
-
<input type="hidden" name="session" value="{
|
|
468
|
+
<input type="hidden" name="session" value="{session_token_escaped}">
|
|
428
469
|
<button type="submit" class="approve">Approve</button>
|
|
429
470
|
<button type="submit" name="deny" value="1" class="deny">Deny</button>
|
|
430
471
|
</form>
|
|
@@ -433,7 +474,6 @@ async def run_server(
|
|
|
433
474
|
# Consent approval — POST /oauth/consent/approve
|
|
434
475
|
async def consent_post(request: Request) -> HTMLResponse:
|
|
435
476
|
from starlette.responses import RedirectResponse
|
|
436
|
-
from urllib.parse import urlencode
|
|
437
477
|
|
|
438
478
|
form = await request.form()
|
|
439
479
|
session_token = str(form.get("session", ""))
|
|
@@ -444,35 +484,98 @@ async def run_server(
|
|
|
444
484
|
)
|
|
445
485
|
if form.get("deny"):
|
|
446
486
|
redirect_uri = session["redirect_uri"]
|
|
447
|
-
params = {"error": "access_denied"}
|
|
487
|
+
params: dict[str, str] = {"error": "access_denied"}
|
|
448
488
|
if session.get("state"):
|
|
449
489
|
params["state"] = session["state"]
|
|
450
490
|
return RedirectResponse(
|
|
451
|
-
|
|
491
|
+
_add_query_params(redirect_uri, params), status_code=302
|
|
452
492
|
)
|
|
453
493
|
code = oauth_provider.create_authorization_code(session)
|
|
454
494
|
params = {"code": code}
|
|
455
495
|
if session.get("state"):
|
|
456
496
|
params["state"] = session["state"]
|
|
457
497
|
return RedirectResponse(
|
|
458
|
-
|
|
498
|
+
_add_query_params(session["redirect_uri"], params), status_code=302
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
configured_resource_public_url = (
|
|
502
|
+
os.getenv("META_DATA_MCP_PUBLIC_URL", "").strip() or None
|
|
503
|
+
)
|
|
504
|
+
resource_public_url = configured_resource_public_url or oauth_issuer
|
|
505
|
+
validated_resource_url = AnyHttpUrl(resource_public_url)
|
|
506
|
+
validated_oauth_issuer = AnyHttpUrl(oauth_issuer)
|
|
507
|
+
|
|
508
|
+
if configured_resource_public_url is None:
|
|
509
|
+
log.info(
|
|
510
|
+
"META_DATA_MCP_PUBLIC_URL is not set; defaulting protected "
|
|
511
|
+
"resource URL to META_DATA_MCP_OAUTH_ISSUER (%s)",
|
|
512
|
+
oauth_issuer,
|
|
513
|
+
)
|
|
514
|
+
elif hmac.compare_digest(
|
|
515
|
+
resource_public_url.rstrip("/"), oauth_issuer.rstrip("/")
|
|
516
|
+
):
|
|
517
|
+
log.info(
|
|
518
|
+
"META_DATA_MCP_PUBLIC_URL matches META_DATA_MCP_OAUTH_ISSUER; "
|
|
519
|
+
"using %s for both protected resource URL and issuer",
|
|
520
|
+
resource_public_url,
|
|
521
|
+
)
|
|
522
|
+
else:
|
|
523
|
+
log.warning(
|
|
524
|
+
"META_DATA_MCP_PUBLIC_URL (%s) differs from "
|
|
525
|
+
"META_DATA_MCP_OAUTH_ISSUER (%s); using "
|
|
526
|
+
"META_DATA_MCP_PUBLIC_URL for the protected resource URL "
|
|
527
|
+
"and META_DATA_MCP_OAUTH_ISSUER for the authorization server "
|
|
528
|
+
"issuer",
|
|
529
|
+
resource_public_url,
|
|
530
|
+
oauth_issuer,
|
|
459
531
|
)
|
|
460
532
|
|
|
533
|
+
# Serve both RFC 9728 variants:
|
|
534
|
+
# - Base: /.well-known/oauth-protected-resource
|
|
535
|
+
# - Path: /.well-known/oauth-protected-resource/sse
|
|
536
|
+
# Clients (e.g. Claude Desktop) probe both.
|
|
537
|
+
protected_resource_routes = create_protected_resource_routes(
|
|
538
|
+
resource_url=validated_resource_url,
|
|
539
|
+
authorization_servers=[validated_oauth_issuer],
|
|
540
|
+
scopes_supported=["opendata"],
|
|
541
|
+
) + create_protected_resource_routes(
|
|
542
|
+
resource_url=AnyHttpUrl(resource_public_url.rstrip("/") + "/sse"),
|
|
543
|
+
authorization_servers=[validated_oauth_issuer],
|
|
544
|
+
scopes_supported=["opendata"],
|
|
545
|
+
)
|
|
461
546
|
oauth_routes = create_auth_routes(
|
|
462
547
|
provider=oauth_provider,
|
|
463
|
-
issuer_url=
|
|
548
|
+
issuer_url=validated_oauth_issuer,
|
|
464
549
|
client_registration_options=ClientRegistrationOptions(
|
|
465
550
|
enabled=True,
|
|
466
551
|
valid_scopes=["opendata"],
|
|
467
552
|
default_scopes=["opendata"],
|
|
468
553
|
),
|
|
469
554
|
)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
555
|
+
from starlette.responses import RedirectResponse as _RedirectResponse
|
|
556
|
+
|
|
557
|
+
async def oidc_discovery(_request):
|
|
558
|
+
return _RedirectResponse(
|
|
559
|
+
"/.well-known/oauth-authorization-server", status_code=301
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
extra_routes = (
|
|
563
|
+
protected_resource_routes
|
|
564
|
+
+ oauth_routes
|
|
565
|
+
+ [
|
|
566
|
+
Route(
|
|
567
|
+
"/.well-known/openid-configuration",
|
|
568
|
+
endpoint=oidc_discovery,
|
|
569
|
+
methods=["GET"],
|
|
570
|
+
),
|
|
571
|
+
Route("/oauth/consent", endpoint=consent_get, methods=["GET"]),
|
|
572
|
+
Route(
|
|
573
|
+
"/oauth/consent/approve",
|
|
574
|
+
endpoint=consent_post,
|
|
575
|
+
methods=["POST"],
|
|
576
|
+
),
|
|
577
|
+
]
|
|
578
|
+
)
|
|
476
579
|
log.info(f"OAuth 2.0 enabled — issuer: {oauth_issuer}")
|
|
477
580
|
|
|
478
581
|
app = Starlette(
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_news_tone_v1.html
RENAMED
|
@@ -374,6 +374,10 @@
|
|
|
374
374
|
return m ? m[1] : '';
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
function safeUrl(url) {
|
|
378
|
+
return url && /^https?:\/\//i.test(url) ? url : null;
|
|
379
|
+
}
|
|
380
|
+
|
|
377
381
|
function sortByDate(arr) {
|
|
378
382
|
return arr.slice().sort(function (a, b) {
|
|
379
383
|
return a.date < b.date ? -1 : a.date > b.date ? 1 : 0;
|
|
@@ -550,9 +554,10 @@
|
|
|
550
554
|
row('Domain', ev.domain || '-');
|
|
551
555
|
row('Language', ev.language || '-');
|
|
552
556
|
card.appendChild(dl);
|
|
553
|
-
|
|
557
|
+
var safe = safeUrl(ev.url);
|
|
558
|
+
if (safe) {
|
|
554
559
|
card.appendChild(el('a', {
|
|
555
|
-
href:
|
|
560
|
+
href: safe, target: '_blank', rel: 'noopener noreferrer',
|
|
556
561
|
text: ev.url,
|
|
557
562
|
}));
|
|
558
563
|
}
|
|
@@ -665,7 +670,7 @@
|
|
|
665
670
|
renderRightPanel();
|
|
666
671
|
});
|
|
667
672
|
g.addEventListener('click', function () {
|
|
668
|
-
|
|
673
|
+
var u = safeUrl(ev.url); if (u) window.open(u, '_blank', 'noopener,noreferrer');
|
|
669
674
|
});
|
|
670
675
|
var titleEl = svg('title');
|
|
671
676
|
titleEl.textContent = ev.date + ' - ' + (ev.country || '-') +
|
|
@@ -827,7 +832,7 @@
|
|
|
827
832
|
renderRightPanel();
|
|
828
833
|
});
|
|
829
834
|
tr.addEventListener('click', function () {
|
|
830
|
-
|
|
835
|
+
var u = safeUrl(ev.url); if (u) window.open(u, '_blank', 'noopener,noreferrer');
|
|
831
836
|
});
|
|
832
837
|
tbody.appendChild(tr);
|
|
833
838
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/providers/global_unesco_heritage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_discovery_v1.html
RENAMED
|
File without changes
|
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_entity_graph_v1.html
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_entity_graph_v1.py
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_molecular_v1.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_network_topology_v1.html
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_network_topology_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_trade_flows_v1.html
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_trade_flows_v1.py
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_vulnerability_v1.html
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/app_vulnerability_v1.py
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_geofeatures_v1.html
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_geofeatures_v1.py
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_records_v1.html
RENAMED
|
File without changes
|
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_timeseries_v1.html
RENAMED
|
File without changes
|
{meta_data_mcp-2.3.0 → meta_data_mcp-2.3.2}/meta_data_mcp/ui_resources/shape_timeseries_v1.py
RENAMED
|
File without changes
|
|
File without changes
|