Skip to main content
Glama
README.md
# mcp101

[![ci](https://github.com/1995parham-learning/mcp101/actions/workflows/ci.yaml/badge.svg)](https://github.com/1995parham-learning/mcp101/actions/workflows/ci.yaml)

A hands-on sample [MCP](https://modelcontextprotocol.io) server built with
[FastMCP](https://gofastmcp.com), used to try out the Model Context Protocol —
basic tools first, then a real-world **Google OAuth** example that calls Google
APIs on a user's behalf.

It accompanies the `MCP Tooling` learning note.

## Layout

| File | What it is |
| --- | --- |
| `src/mcp101/server.py` | Minimal **no-auth** server — `add`, `greet`, a `time://now` resource, a `summarize` prompt. Runs with zero credentials. Start here. |
| `src/mcp101/google_server.py` | **Google OAuth** server — authenticates the user with Google and lists their calendars via the Calendar API. |
| `src/mcp101/github_server.py` | **GitHub OAuth** server — authenticates the user with GitHub and lists their repos. Quickest auth demo to set up. |

## Requirements

- Python >= 3.11
- [uv](https://docs.astral.sh/uv/)
- [just](https://github.com/casey/just) (optional, for the shortcuts below)
- Node.js (only for the MCP Inspector)

## Quick start (no auth)

```bash
just install          # or: uv sync
just run              # stdio transport, for Claude Desktop / Cursor
just run-http         # streamable HTTP on http://localhost:8000/mcp
```

Test it visually with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):

```bash
just inspect          # npx @modelcontextprotocol/inspector uv run mcp101
```

Wire it into Claude Desktop (`claude_desktop_config.json`):

```json
{
    "mcpServers": {
        "mcp101": {
            "command": "uv",
            "args": ["run", "--directory", "/absolute/path/to/mcp101", "mcp101"]
        }
    }
}
```

## Google OAuth demo

This is the part worth understanding: it does not just *log you in*, it *acts as
you* against Google. FastMCP's `GoogleProvider` is an `OAuthProxy` (Google has no
Dynamic Client Registration), so FastMCP brokers the OAuth flow, encrypts and
stores the upstream Google token server-side, and hands the MCP client only a
reference token. Inside a tool, `get_access_token().token` gives you the real
Google access token to call Google with — and the proxy refreshes it transparently.

### 1. Create Google credentials

1. [Google Cloud Console](https://console.cloud.google.com) → **APIs & Services → OAuth consent screen** — configure it (External; add yourself as a test user while in "Testing" mode).
2. **APIs & Services → Credentials → Create Credentials → OAuth client ID** → application type **Web application**.
3. Add an **Authorized redirect URI** that matches **exactly**: `http://localhost:8000/auth/callback`.
4. **APIs & Services → Library** → enable **Google Calendar API**.
5. Copy the **Client ID** (`...apps.googleusercontent.com`) and **Client Secret** (`GOCSPX-...`).

### 2. Configure and run

```bash
cp .env.example .env       # then fill in the two Google values
set -a; source .env; set +a
just run-google            # or: uv run mcp101-google
```

Connect with the Inspector (HTTP transport, URL `http://localhost:8000/mcp`). It
will bounce you through Google's consent screen, then `whoami` and `list_calendars`
will work.

### Scopes

Requested in `google_server.py`:

- `openid`, `.../auth/userinfo.email` — identity
- `.../auth/calendar.readonly` — read the user's calendars

Prefer `.readonly` scopes unless you need write access.

### Gotchas

- **Restricted scopes** (Gmail, Drive contents) need Google's app-verification review for non-test users. In "Testing" mode you are limited to explicitly added test users.
- **Tokens are per-user and in-memory** in this demo. For production, configure persistent encrypted token storage and stable JWT signing keys, or tokens die on restart and everyone re-consents.
- The **redirect URI must match exactly** between Google and `BASE_URL` — a trailing slash mismatch will fail the flow.

## GitHub OAuth demo

The same OAuthProxy mechanics as the Google demo, but a GitHub OAuth App takes a
minute to create and needs **no verification review** — so this is the fastest way
to see real on-behalf-of-user API calls.

### 1. Create a GitHub OAuth App

1. GitHub → **Settings → Developer settings → OAuth Apps → New OAuth App**.
2. **Homepage URL**: `http://localhost:8000`.
3. **Authorization callback URL** (must match exactly): `http://localhost:8000/auth/callback`.
4. Register, then **Generate a new client secret**. Copy the **Client ID** (`Iv1...`) and secret.

### 2. Configure and run

```bash
cp .env.example .env       # fill in the two GITHUB_ values
set -a; source .env; set +a
just run-github            # or: uv run mcp101-github
```

Connect with the Inspector (Streamable HTTP, URL `http://localhost:8000/mcp`). After
the GitHub consent screen, `whoami` returns your profile and `list_repos` lists your
repositories.

Requested scopes (in `github_server.py`): `read:user`, `user:email`, `public_repo`.

## Docker

The image defaults to the basic server over streamable HTTP — sensible for a
container / Kubernetes deployment (stdio only makes sense for local desktop clients).

```bash
docker build -t mcp101 .
docker run --rm -p 8000:8000 mcp101            # basic server on :8000/mcp
```

Run the Google demo instead by overriding the command and passing credentials:

```bash
docker run --rm -p 8000:8000 \
    -e GOOGLE_CLIENT_ID=...apps.googleusercontent.com \
    -e GOOGLE_CLIENT_SECRET=GOCSPX-... \
    -e BASE_URL=https://your-public-host \
    mcp101 mcp101-google
```

For Kubernetes, expose port `8000`, set `BASE_URL` to the externally reachable URL,
and remember the Google **Authorized redirect URI** must be `<BASE_URL>/auth/callback`.
For anything beyond a demo, also configure persistent encrypted token storage and
stable JWT signing keys (see the gotchas above).

## CI

`.github/workflows/ci.yaml` runs on every push/PR to `main`:

- `ruff format --check` and `ruff check` (lint)
- an import smoke test for both servers
- a `docker build` to validate the image

## License

MIT

TDQS

A3.6/5.0

Scored across 2 tools

Disambiguation5/5

The two tools serve entirely different purposes—arithmetic and greeting—so there is no risk of confusion or overlap.

Naming Consistency5/5

Both tool names are single verbs in imperative form, following a consistent and predictable pattern.

Tool Count3/5

With only 2 tools, the server is on the lower end of the appropriate range; it feels thin but not unreasonable for a small utility server.

Completeness2/5

The tools cover two unrelated capabilities with no clear domain, leaving significant gaps for any coherent purpose beyond these trivial operations.

Maintenance

ActivityMaintained
ResponsivenessSyncing