MCP Series (05): Resources and Prompts Deep Dive — Dynamic Data, Parameterized URIs, and Multi-Turn Templates A developer detailed the distinction between Tools and Resources in the Model Context Protocol (MCP), explaining that Resources are read-only data sources injected by the host while Tools are actions executed by the LLM. The post demonstrated dynamic Resources that return live data on each read, parameterized URIs for handling multiple instances, and URI design principles for hierarchical organization. The split: Tools → actions the LLM executes verbs LLM decides when to call; calls may have side effects Examples: create issue, update status Resources → data the LLM reads nouns Host decides when to inject; read-only, no side effects Examples: current Sprint status, project statistics The rule: "reading a state" → Resource. "Executing an operation" → Tool. The same data can have both: get issue as a Tool LLM controls when to call it , jira://issue/PROJ-101 as a Resource Host injects automatically when relevant . A static Resource returns the same data every time like a project list . A dynamic Resource returns the current state on each read — content changes as the underlying data changes. Sprint status: every read returns live data python sprint progress pct = 65 @server.read resource async def read resource uri: str - str: if str uri == "jira://sprint/current": global sprint progress pct sprint progress pct = min 100, sprint progress pct + random.randint 0, 3 return json.dumps { "sprint name": "Sprint 42", "progress pct": sprint progress pct, ← different each time "last updated": datetime.now timezone.utc .isoformat , ← timestamp changes "days remaining": 5, "p0 open": count p0 open , ← tracks live state }, indent=2 Test output: Read 1: progress=65% last updated=...62+00:00 Read 2: progress=67% last updated=...04+00:00 → ✓ data changed between reads Hardcoding sprint progress in a Prompt means the LLM works from a stale snapshot. A Dynamic Resource gives it the current number on every read. Mark the Resource as dynamic in its description so the LLM knows to re-read when it needs fresh data: Resource uri="jira://sprint/current", description= "Live status of the active sprint: progress, issue counts. " "Read when the user asks about sprint health. " "Re-read if you need up-to-date data — content changes over time." ↑ explicit signal that this is dynamic , When one Resource type has many instances, use parameterized URIs. list resources enumerates all instances; read resource uses a single handler for all of them. One stats Resource per project: php @server.list resources async def list resources - list Resource : resources = for key, proj in PROJECTS.items : resources.append Resource uri=f"jira://project/{key}/stats", name=f"{proj 'name' } Stats", description=f"Issue statistics for {proj 'name' } {key} .", return resources @server.read resource async def read resource uri: str - str: if str uri .startswith "jira://project/" and str uri .endswith "/stats" : proj key = str uri .split "/" 3 .upper parse from jira://project/{key}/stats if proj key not in PROJECTS: raise ValueError f"Unknown project: {proj key}" proj issues = i for i in ISSUES.values if i "project" == proj key return json.dumps { "project": proj key, "total": len proj issues , "by status": count by proj issues, "status" , "by priority": count by proj issues, "priority" , }, indent=2 Test output: jira://project/PROJ/stats → total=3, by status={'Open': 2, 'In Progress': 1} jira://project/MOBILE/stats → total=1, by status={'Open': 1} jira://project/INFRA/stats → total=1, by status={'Done': 1} The LLM reads only the project it needs. The Host can also inject the right Resource based on current context — if the user is working in the MOBILE project, inject MOBILE/stats rather than dumping all projects at once. URI design principles: jira://project/{key}/stats ← hierarchical path like REST jira://sprint/current ← active instance, no ID needed jira://dashboard ← aggregate view, fixed URI Avoid: jira://stats PROJ ← flat, doesn't scale jira://data?project=PROJ ← query params, harder to parse A Prompt template doesn't have to be static text. Render different sections based on argument values so one Prompt covers multiple scenarios cleanly. Incident report: P0 includes Escalation section, P1 doesn't if name == "incident report": severity = args.get "severity", "P1" .upper workaround = args.get "workaround", "" conditional section: P0 only p0 section = "" if severity == "P0": p0 section = "\n Escalation\n" "- Engineering VP: notify within 30 minutes\n" "- SLA breach risk: may breach the 4-hour P0 SLA\n" conditional section: only when workaround provided workaround section = "" if workaround: workaround section = f"\n Workaround\n{workaround}\n" template = f"Create a formal incident report for {issue key}...\n" f" Summary\n...\n" f" Root Cause\n..." f"{p0 section}" ← conditional insert f"{workaround section}" ← conditional insert Test output: P0: escalation section=✓ workaround section=✗ P1: escalation section=✗ workaround section=✓ P0 incidents trigger escalation protocol; P1 incidents show the workaround. The LLM receives a different template and generates a structurally different report. No need for a single large template that the LLM has to interpret. Standard Prompts have one user message. Multi-turn Prompts pre-fill a conversation history to guide the LLM through specific steps before producing the final output. PR description: 3 turns, second turn is an assistant 'thinking' step if name == "pr description": return GetPromptResult messages= Turn 1: user sets context PromptMessage role="user", content=TextContent type="text", text= f"You are a senior engineer writing a PR description.\n" f"PR addresses: {issue key}\n\n" f"First, use get issue to read the Jira issue details." , Turn 2: pre-filled assistant thinking step PromptMessage role="assistant", content=TextContent type="text", text= "I'll fetch the issue details and then write a PR description " "with: title, motivation, changes summary, test plan, and links." , Turn 3: final instruction PromptMessage role="user", content=TextContent type="text", text= "Now write the complete PR description in Markdown." , Test output: Turn count: 3 Turn 1 user : You are a senior engineer writing a PR description... Turn 2 assistant : I'll fetch the issue details and then write a PR description... Turn 3 user : Now write the complete PR description in Markdown. Uses for multi-turn Prompts: When a Prompt template embeds instructions to "read this Resource" or "call this Tool," the user doesn't need to supply data — the template tells the LLM how to get it. if name == "standup update": return GetPromptResult messages= PromptMessage role="user", content=TextContent type="text", text= f"Generate a daily standup update for {team member}.\n\n" f"Steps:\n" f"1. Read jira://sprint/current to see overall sprint health\n" ← Resource ref f"2. Use search issues to find issues {team member} worked on\n" ← Tool ref f"3. Write standup: Yesterday / Today / Blockers / Sprint health" Test output: References resource: ✓ jira://sprint/current in template References tool: ✓ search issues in template The LLM receives this Prompt and automatically reads jira://sprint/current , calls search issues , then generates the standup. The user only needs to say "generate my standup" — no manual data gathering required. Resources and Tools aren't mutually exclusive. A Prompt can reference both: LLM executing standup update: 1. Read Resource jira://sprint/current → overall sprint health 2. Call Tool search issues query="closed", assignee="alice" → completed yesterday 3. Call Tool search issues query="open", assignee="alice" → planned today 4. Generate combine data into standup format Resources inject background context passive, no side effects . Tools execute queries or operations active, LLM-initiated, may have side effects . Each type handles what it's designed for. Resources Prompts Check out PrimeSkills — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works. Find more useful knowledge and interesting products on my Homepage