Lesson 14 of 30, Module 2: Your First Server. Your own server edits real documents in your Drive folder.
What you can do after this lesson
Give the model two things it cannot get from a tool alone: a way to see what exists (a resource) and a job you have already written down (a prompt). Each is a small function, one decorator line and one sentence, and you can add them without touching what already works.
The problem we inherited
You install the official Google Drive server and Claude reads every document and changes none of them.
The 5 steps this lesson runs
- Write the plain Python function
- Put the @mcp line above it
- Write the one-line docstring
- Restart Claude Desktop
- Ask Claude to use it
Code in this lesson
Every block the lesson shows on screen, in the order it appears.
1. Write the plain Python function
2. Put the @mcp line above it
3. Write the one-line docstring
4. Restart Claude Desktop
5. Ask Claude to use it
@mcp.resource("drafts://folder")
def folder() -> str:
"""The name of every draft in the Scribe folder."""
q = (f"'{FOLDER}' in parents and trashed = false"
" and mimeType contains 'text/'")
found = drive.files().list(q=q, fields="files(name)",
orderBy="name").execute()
return "\n".join(f["name"] for f in found["files"])
@mcp.resource("drafts://{name}")
def draft(name: str) -> str:
"""One draft from the Scribe folder, by its name."""
return _text(_find(name))
@mcp.prompt()
def tidy_draft(name: str) -> str:
"""Fix the spelling and tighten the opening of one draft."""
return (f"Read the draft called {name}. Fix every spelling"
" mistake. Make the opening line shorter and"
" stronger. Keep my voice, and change nothing else."
" Use edit_doc for each change.")
uv run what_can_it_do.py
python3 what_can_it_do.py
TOOLS
read_doc
Read one draft from the Scribe folder in Google Drive.
input: name (string, required)
edit_doc
Replace some text inside one draft in the Scribe folder.
input: name (string, required)
input: find (string, required)
input: replace (string, required)
RESOURCES
drafts://folder
The name of every draft in the Scribe folder.
drafts://{name}
One draft from the Scribe folder, by its name.
PROMPTS
tidy_draft
Fix the spelling and tighten the opening of one draft.
input: name
Which of my drafts is about the launch?
python3 prove_it.py
VERDICT
PASS both tools are offered
PASS typo was there before
PASS typo is gone after
PASS the draft changed on Drive
PASS missing words change nothing
PASS a missing draft is explained
PASS the folder lists your draft
PASS the resource matches the tool
PASS the saved job names your draft
RESULT: your server works.
Downloads
- Module 2 checkpoint (scribe-checkpoint-m2.zip): working code as the module leaves it, so a broken session costs you nothing
- The course GitHub repo: every checkpoint, the README and the full lesson list
Prefer to read?
The written version of this part of the course is on Substack: https://genaiunplugged.substack.com/p/how-to-build-an-mcp-server-and-connect.
Lesson transcript
What we build today
Today Claude stops asking you for file names, and you stop typing the same instruction every week.
Hello, and welcome to lesson 14 of the MCP Masterclass. Last lesson we proved that Scribe changes the real document. Today we give Claude the whole folder, and we save your editing job. And we start with our map, as always.
9 lit boxes. Today 2 more light up, and both of them sit right beside your server.
So let me show you the finished thing first.
On screen: in Claude Desktop, the folder list from Scribe is attached to the chat. Then the saved job tidy_draft is picked, launch-post.md is named, and Claude tidies the draft.
I hand Claude the list of my drafts. Then I pick a saved job, and I name a draft. Claude tidies that draft, and I typed almost nothing.
Now let us go back and build it.
The two problems
Scribe works, and you proved it. But it has 2 problems, and you have probably felt both.
The first problem. Scribe edits the 1 file you name. Claude cannot see what else is in the folder, so you have to remember every file name yourself.
The second problem. You type the same instruction every single time. Fix the spelling, tighten the opening, keep my voice.
MCP has an answer for each of them, and you met both in lesson 8.
A resource is nothing but something your AI can look at. And a prompt here is nothing but a saved job.
Now, there is one difference that matters, and it is about who is in charge.
A tool is picked by the model. Claude decides when to run it. A resource and a prompt are picked by you. You hand them over when you want them.
The checklist, second run
Okay. Here is our list, and it has not changed by a single word.
1. Write the plain Python function
2. Put the @mcp line above it
3. Write the one-line docstring
4. Restart Claude Desktop
5. Ask Claude to use it
Write the plain Python function. Put the @mcp line above it. Write the one line docstring. Restart Claude Desktop. Ask Claude to use it.
Last time, steps 1 to 3 needed a long and careful walk. This time you already know them, so I am going to show you all 3 at once.
Let Claude see the folder
So let us start with the folder.
Open scribe_server.py, and put this under your 2 tools.
@mcp.resource("drafts://folder")
def folder() -> str:
"""The name of every draft in the Scribe folder."""
q = (f"'{FOLDER}' in parents and trashed = false"
" and mimeType contains 'text/'")
found = drive.files().list(q=q, fields="files(name)",
orderBy="name").execute()
return "\n".join(f["name"] for f in found["files"])
There is the function, there is the @mcp line, and there is the docstring. Same 3 steps.
Now look at the @mcp line, because it has changed a little. Last time it said tool. This time it says resource, and it carries an address.
But what is an address here?
An address is nothing but a name that an app uses to ask for this exact thing. Ours says drafts, and then folder.
And now you know why step 2 says the @mcp line. It never said the tool line, because the same step covers all 3 kinds of thing.
The function itself is a search you already know, which names the folder and skips anything in the bin.
Then it does 1 new thing, which is to keep the text files and leave everything else out.
I added that after looking at my own Drive. One of my folders holds over 100 video files, and Claude does not need to read through those. Scribe edits text, so Scribe lists text.
And it hands back the names, with 1 name on each line.
Open one draft
Next, a single draft.
@mcp.resource("drafts://{name}")
def draft(name: str) -> str:
"""One draft from the Scribe folder, by its name."""
return _text(_find(name))
Look at the address again. This one has a blank in it, and the curly brackets mark the blank.
The app fills that blank with a file name. Your function receives the name, and it hands back the words.
And the real work is 1 line, because your 2 helpers already exist. You wrote them once, and now a tool and a resource both lean on them.
That was steps 1, 2 and 3, twice over. Did you notice how little I had to explain?
Save your editing job
Now the saved job.
@mcp.prompt()
def tidy_draft(name: str) -> str:
"""Fix the spelling and tighten the opening of one draft."""
return (f"Read the draft called {name}. Fix every spelling"
" mistake. Make the opening line shorter and"
" stronger. Keep my voice, and change nothing else."
" Use edit_doc for each change.")
Same 3 steps again. The function, the @mcp line, which says prompt this time, and the docstring.
And look at what this function does. It does no work at all, because it only hands back words.
Those words are the instruction you would have typed by hand. You give it a draft name, and it gives you the full instruction with that name inside.
The words on screen are mine. Change them to yours, because this is your editing job and you know how you like it done.
Check what the server offers
So let us ask the server what it offers now.
uv run what_can_it_do.py
Or with pip.
python3 what_can_it_do.py
And here is the new list.
TOOLS
read_doc
Read one draft from the Scribe folder in Google Drive.
input: name (string, required)
edit_doc
Replace some text inside one draft in the Scribe folder.
input: name (string, required)
input: find (string, required)
input: replace (string, required)
RESOURCES
drafts://folder
The name of every draft in the Scribe folder.
drafts://{name}
One draft from the Scribe folder, by its name.
PROMPTS
tidy_draft
Fix the spelling and tighten the opening of one draft.
input: name
2 tools, 2 resources, and 1 prompt. Every one of them describes itself, and you wrote no description twice.
And notice what did not change. We never touched read_doc or edit_doc. We added 3 things beside them, and the old ones carried on working.
Restart and ask Claude
Step 4. Restart Claude Desktop.
Your settings entry stays exactly as it is, because it is the same file in the same place. So quit Claude Desktop properly, and open it again.
Step 5. Ask Claude to use it.
Now remember who is in charge. You pick resources and prompts. So this time you start with a menu.
Open the menu beside the chat box, and look for the name scribe.
On screen: the menu beside the chat box in Claude Desktop, with scribe in it. Under scribe, the folder resource and the tidy_draft saved job.
Every app puts this in a slightly different place. So do not hunt for a button. Look for the name of your server.
Pick the folder first.
On screen: the folder resource attached to the chat. It holds 4 names: draft.md, launch-post.md, weekly-notes.txt and welcome-email.md.
Claude now holds the list of your drafts. Those are the 3 samples you uploaded in lesson 11, and the draft from lesson 4.
And that list is external intelligence. Claude was never trained on your folder, and now it can see what is in it.
So ask it something that needs the list.
Which of my drafts is about the launch?
Claude answers with the file name. You did not have to remember it.
Now pick the saved job, and give it that name.
On screen: Tidy draft picked from the scribe menu. A small window asks for the draft name, and launch-post.md is typed in. The instruction attaches to the chat.
Your instruction is attached to the chat, with the draft name already inside it. Send it.
On screen: Claude reads launch-post.md, then makes 3 edits. It shortens the opening line and fixes 2 spelling mistakes. Then launch-post.md open in Drive, changed.
Claude reads the draft. If it stops and asks you first, say yes. And then it fixes the draft in your Drive.
Run the proof
And one last check, because we never take the server at its word. Run the proof again.
python3 prove_it.py
It now tests the new parts too, and this is how it ends.
VERDICT
PASS both tools are offered
PASS typo was there before
PASS typo is gone after
PASS the draft changed on Drive
PASS missing words change nothing
PASS a missing draft is explained
PASS the folder lists your draft
PASS the resource matches the tool
PASS the saved job names your draft
RESULT: your server works.
9 checks, and 9 passes.
What this lesson cost
Now stop for a moment, because I want you to see what this lesson cost you.
You added 3 new things to your server. Each one was a function, 1 line above it, and 1 sentence inside it.
Steps 1 to 3 were slow in lesson 12, and today they were quick. And steps 4 and 5 were the same as they always are.
That is the second time through the list. Keep an eye on it, because it gets cheaper again in module 3.
And what did you get for it? Claude can see your whole folder, so you stop remembering file names. And your editing job is saved, so you stop typing it.
The map now
So where does our map stand at the end of this lesson?
2 new boxes are lit. One is the folder Claude can browse, and the other is your saved jobs. That makes 11 lit boxes.
Next lesson is a quick recap of everything we built in module 2.
Bye now, and I will see you in the next lesson.