3GPP FTP Remote MCP Server
by Kamu1403
README.md
# 3GPP FTP Remote MCP Server
A remote MCP server for browsing public files from the [3GPP FTP service](https://www.3gpp.org/ftp/). It runs on Cloudflare Workers using TypeScript, Wrangler, and Streamable HTTP transport.
The MCP endpoint is `/mcp`. The server is stateless and does not require 3GPP credentials. All 3GPP source access is read-only; one separate, explicitly invoked tool can save a selected public file to the user's ChatGPT Library.
## Tools
### `search(query: string)`
Uses the official 3GPP filename conventions to locate public files before falling back to a bounded directory crawl. Supported query forms include:
- TS/TR references such as `TS 23.501` or `TR 21.900 v15.1.1`
- Versioned spec archives such as `23501-k20.zip`
- Multipart specs such as `29.198-08` or `29198-08-651.zip`
- TDoc identifiers such as `S2-2601720`, `SP-200062`, and modern RAN/SA/CT WG identifiers
- Conventional CR member filenames such as `23501_CR1668r4_(Rel-16)_S2-2001278_....docx`
- Exact paths or URLs under the configured public 3GPP FTP origin
Each result contains:
- `id`: canonical 3GPP FTP path used by `fetch`
- `title`: filename
- `url`: public 3GPP URL
- `metadata`: path, strategy, parsed identifiers, modified date, size, and extension when available
Exact files and direct paths are validated with a one-byte ranged `GET`. The 3GPP server rejects many `HEAD` requests even when the same public file is downloadable.
The response can also contain `warnings`. A CR number alone does not determine a public FTP path; CR lookup without a WG/TSG TDoc requires a future portal adapter or CR database index, so the server reports that limitation instead of guessing a link.
### `fetch(id: string)`
Fetches one result by its canonical path. Text and HTML content is returned as text, up to the configured character limit. Binary formats such as ZIP, RAR, DOCX, XLSX, and PDF return metadata, a Worker relay URL, and an MCP `resource_link`. The file is streamed only when a client follows that link.
### `download_file(id: string)`
Returns a read-only MCP `resource_link` for an ID returned by `search`. The resource points to `/download?id=<artifact-id>` on the same Worker. This explicit tool is useful for clients that do not automatically act on the resource link included by `fetch`.
### `import_to_chatgpt(id: string)`
Renders a versioned MCP Apps widget that streams one result through `/download`, constructs a browser `File`, and calls ChatGPT's optional `window.openai.uploadFile(file, { library: true })` API. This is the only write-capable tool because it creates a ChatGPT-hosted file. Its MCP annotations mark it non-read-only and non-idempotent, and its description requires an explicit user request before invocation.
The widget:
- auto-starts only after the explicit `import_to_chatgpt` tool call
- saves to ChatGPT Library when that feature is available to the user
- persists a private import key to avoid duplicate uploads when the widget rerenders
- enforces `THREEGPP_IMPORT_MAX_BYTES` before upload, including for responses without a declared size
- falls back to opening the streamed file when the ChatGPT upload API is unavailable
- does not automatically attach the saved file to the current chat or a project
### Streaming Relay
The `/download` HTTP route:
- accepts `GET`, `HEAD`, `OPTIONS`, and one HTTP byte range
- permits only the configured 3GPP origin and `/ftp` root
- streams the upstream response body without reading it into Worker memory
- adds download, MIME, CORS, and `nosniff` headers
- implements `HEAD` with a one-byte upstream `GET`, because the 3GPP service commonly rejects direct `HEAD` requests
- does not persist files in Worker storage
## Project Layout
```text
src/index.ts Remote MCP server, resource, and tool registration
src/conventions.ts Official TS/TR, TDoc, CR parsing and group routing
src/import-to-chatgpt.ts ChatGPT Library import preparation and widget
src/relay.ts Restricted, zero-storage streaming download relay
src/threegpp.ts Public 3GPP listing, search, and fetch logic
src/types.ts Worker environment and result types
tests/ Deterministic convention, resolver, and relay tests
wrangler.jsonc Cloudflare Worker configuration
chatgpt-plugin/ ChatGPT registration metadata and setup guide
legacy-python/ Original Python MCP prototype
```
## Prerequisites
- Node.js and npm
- A Cloudflare account for deployment
Verify the local tools:
```bash
node --version
npm --version
```
## Install
```bash
cd 3gpp-ftp-connector
npm install
npm test
npm run typecheck
```
## Run Locally
Start the Worker development server:
```bash
npm run dev
```
Wrangler normally serves it at `http://localhost:8787`. Confirm the Worker is running:
```bash
curl http://localhost:8787/
```
The response should advertise `/mcp`, Streamable HTTP, and the `search`, `fetch`, `download_file`, and `import_to_chatgpt` tools.
Test the relay metadata and a small byte range without downloading the whole archive:
```bash
curl --head "http://localhost:8787/download?id=%2Fftp%2FSpecs%2Farchive%2F23_series%2F23.501%2F23501-k20.zip"
curl --range 0-31 "http://localhost:8787/download?id=%2Fftp%2FSpecs%2Farchive%2F23_series%2F23.501%2F23501-k20.zip" --output relay-sample.bin
```
## Test with MCP Inspector
Keep `npm run dev` running and open a second terminal:
```bash
npx @modelcontextprotocol/inspector@latest
```
In Inspector:
1. Select **Streamable HTTP**.
2. Enter `http://localhost:8787/mcp`.
3. Connect and list the available tools.
4. Call `search` with a TS/TR reference, filename, TDoc, CR filename, path, or URL.
5. Pass a returned `id` to `fetch`.
6. Call `download_file` with the same `id` and inspect its `resource_link`.
7. Call `import_to_chatgpt` and inspect its structured result and `ui://threegpp/import-to-chatgpt-v1.html` resource.
MCP Inspector can verify tool discovery, annotations, structured output, resource MIME type, and widget HTML. It does not provide ChatGPT's optional `window.openai.uploadFile` host API, so the actual Library write must be tested in ChatGPT after deployment. The automated test suite uses a mocked bridge to verify the complete widget upload and deduplication flow.
Example search input:
```json
{
"query": "S2-2601720"
}
```
Example fetch input:
```json
{
"id": "/ftp/tsg_sa/WG2_Arch/TSGS2_174_Malta_2026-04/Docs/S2-2601720.zip"
}
```
## Deploy
Authenticate Wrangler and deploy:
```bash
npx wrangler login
npm run deploy
```
With the default Worker name, the deployed endpoint is:
```text
https://threegpp-ftp-mcp.<cloudflare-subdomain>.workers.dev/mcp
```
Streamed files use the same origin:
```text
https://threegpp-ftp-mcp.<cloudflare-subdomain>.workers.dev/download?id=<artifact-id>
```
Wrangler prints the exact URL after a successful deployment.
## Connect to ChatGPT
The maintained ChatGPT app/plugin name, description, registration values, and test prompts are in [`chatgpt-plugin/`](chatgpt-plugin/README.md). Update that metadata when the public endpoint or tool behavior changes.
## Configuration
Public configuration is stored in `wrangler.jsonc`:
- `THREEGPP_BASE_URL`: 3GPP website origin
- `THREEGPP_FTP_ROOT`: allowed FTP path root
- `THREEGPP_SEARCH_ROOTS`: comma-separated directories searched by default
- `THREEGPP_SEARCH_MAX_DIRECTORIES`: maximum directories visited only by the generic fallback
- `THREEGPP_SEARCH_MAX_RESULTS`: maximum results returned per search
- `THREEGPP_FETCH_TEXT_LIMIT`: maximum text characters returned by `fetch`
- `THREEGPP_IMPORT_MAX_BYTES`: maximum bytes the browser widget may buffer for one ChatGPT Library import; defaults to 100 MiB and is capped at 512 MiB
The Worker uses `nodejs_compat` because the Cloudflare Agents SDK currently includes dependencies that import Node built-in modules.
## Current Scope
- Public 3GPP FTP access only
- Read-only `search`, `fetch`, and `download_file` tools
- Explicit `import_to_chatgpt` write tool for ChatGPT Library
- No portal authentication
- Original files are relayed on demand with no persistent downloads or storage
- No ZIP, RAR, Office, or PDF extraction inside the Worker
Cloudflare Workers can natively decompress deflate streams, so a strictly capped ZIP/DOCX proof of concept is technically possible. It is not enabled because the free plan allows only 10 ms of CPU per request, while RAR requires JavaScript/Wasm archive processing and comment/revision-aware Office extraction requires additional ZIP/XML work. That combination would be unreliable for real meeting packages. The relay therefore transfers original bytes without parsing or storing them. A parser service can be added later without changing the `search`, `fetch`, or `download_file` contracts.
## Legacy Python Prototype
The original Python/stdin implementation is retained under [`legacy-python/`](legacy-python/README.md). It is not used by the Cloudflare Worker or required for deployment.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues