MCP, Spaces, and Spark

Extend Copilot with external tools through MCP, curate the exact context it answers from with Spaces, and prototype an idea with Spark, then verify every returned fact instead of trusting polished output.


What you'll learn

  • Explain what MCP connects and the two trust decisions it creates
  • Prove an MCP tool actually ran and trace each answer field to a source
  • Keep a Copilot Space honest with a claim-to-evidence audit
  • Test a Spark prototype before refining one behavior at a time
On this page

This lesson covers three ways to give Copilot more to work with. MCP connects it to external systems. Spaces collect the sources for a recurring question. Spark builds a working prototype from an application brief.

More capability can make an answer or prototype look more authoritative than it is. Each tool therefore needs a matching check. Watch the external tool run, trace Space claims back to their files, and test Spark behavior before asking for refinements.

MCP connects Copilot to external systems

MCP (the Model Context Protocol) is a standard way for an AI application to connect to external tools and data. Here, VS Code and Copilot are the host. An MCP server exposes capabilities the host can discover and use, such as searching issues, reading documentation, or running a controlled operation. The server is not a language model and doesn't replace Copilot. It's a controlled interface to another system. A typical exchange: you ask for unresolved release blockers, Copilot picks a server tool like list_blockers, sends it structured input, the server returns structured data, and Copilot formats an answer from that evidence.

Two things about that server deserve constant attention. First, classify every tool by effect: a read-only tool returns information without changing anything, while a mutating tool creates, updates, or deletes data. A server that's safe to connect is not automatically safe to use for every operation. That split gives you two separate trust decisions: connection trust (should VS Code start or contact this server?) and operation trust (should this specific tool call run with this input?). Second, note the transport: a local process runs on your machine via a command and arguments, while a remote server is reached through an HTTP endpoint its owner supplies.

Configuration lives in layers. A user configuration is private to your install. A repository configuration commonly sits in .vscode/mcp.json so contributors share the same server definition. Organization or enterprise governance controls whether centrally managed users may use servers at all. That last point matters: committing an endpoint to .vscode/mcp.json shares a connection, but it does not approve the server or authorize everyone to use it. Approval is what a registry (a catalog of reviewed servers) and an allowlist (enforced policy on which servers are allowed) provide, and no committed config ever contains a secret.

Never commit a secret to MCP config

A repository's mcp.json holds non-secret connection metadata only: a server name, a transport, an approved endpoint or command. Bearer tokens, API keys, client secrets, and connection strings belong in the supported secret storage, never in committed configuration. If a credential lands in the file, treat it as exposed and rotate it. Editing the file doesn't erase it from history.

Query one MCP server, read-only· github-copilot
Bad example

Use the release-triage MCP server to list the blockers for version 2.4.

Good example

Use only the release-triage MCP server. List open release blockers for version 2.4, and for each return issue ID, title, owner, severity, and last update. Use read-only tools only. Do not modify issues, files, branches, or deployments. Before summarizing, show the server and tool you selected. If the server doesn't provide a field, write "Not returned by server."

Why this works: A blocker table drawn only from the named server, with any absent field flagged rather than guessed, and a visible record of which tool ran.

Tool activity proves an MCP run

A fluent blocker report can appear to come from your issue tracker even when Copilot never called the MCP tool. Look for observed server and tool activity. Ask the response to name the selection, watch the call or approval request, and treat silence as a discovery problem. The failure may sit in configuration, process startup, tool discovery, policy, or authentication.

After the call, audit the result one field at a time. Every displayed value needs a matching property in the server response. If there isn't one, label the field "Not returned by server." An empty blocker list can still prove discovery when the expected tool ran. A polished table without a call proves nothing about its source.

Server governance continues after approval. Record its capabilities and owner, decide who may connect, approve runtime calls, test allowed and denied cases, and review it again when it changes. A server can keep its name while adding a tool such as close_issue. That new mutating capability invalidates the earlier read-only classification, so access and enforcement need another pass.

Trace every field to a returned value

After an MCP query, ask Copilot to name the returned property behind each cell of its answer, and mark any cell with no matching property as unsupported. A fluent result with no corresponding tool call is unverified. The answer might be right, but nothing observed proves the server produced it.

Trace every answer field to its source· github-copilot
Bad example

Show where the values in the blocker table came from.

Good example

For each cell in the blocker table you just produced, name the exact property in the returned MCP result it came from. For any cell with no returned property, write "Not supported by returned fields." Do not call another source or infer a value.

Why this works: A cell-by-cell audit that either grounds each value in a returned field or exposes it as an inference you shouldn't trust.

Spaces curate the working context

A Copilot Space is a reusable place to organize the context Copilot should use for a recurring question: a named evidence package, not a new model or a coding agent. The value isn't the container. It's the discipline of choosing sources that can answer one question and stating how Copilot must use them. A curated request ("use the architecture decision, the local-dev guide, and current issues to explain how a contributor runs the service and flag missing setup") beats "tell me everything about this project," because it draws an evidence boundary that makes the answer inspectable and its gaps visible.

Keep a Space honest with a claim-to-evidence habit. Read every important response as a set of claims and carry each through four stages: which supplied artifact could support it, what guidance told Copilot to use that artifact, what exact claim Copilot made, and whether the artifact confirms, contradicts, or omits it. Classify the result as confirmed, conflicting, or missing, and never upgrade a likely value into a confirmed fact without saying so. If the source says port 4000 and the answer says 3000, that's a conflict, not a rounding error. If no source states a port, that's missing, and "the source doesn't say" is not the same as "there is no port."

The trap to avoid is the citation that looks like proof. A filename printed next to a claim proves nothing on its own. You still open that file and compare the actual command, value, or status. A Space rewards curation and verification. It does not do them for you.

Ask a Space and make it cite sources· github-copilot
Bad example

Use this Space to explain how a new contributor runs the service.

Good example

Using only this Space, explain how a new contributor runs and verifies the service locally: prerequisites, commands, expected results, and any missing setup information. Name the supporting artifact for every step, and classify each conclusion as confirmed, conflicting, or missing. If the material doesn't answer something, say "Not stated in this Space."

Why this works: A grounded onboarding answer where every step names its source and gaps show up as explicit "missing" instead of confident invention.

When do you reach for Spark instead?

Spaces organize evidence. GitHub Spark does something different: it turns a natural-language application idea into a web-app prototype, no code required. It changes where you start. Instead of files, frameworks, and components, you begin with a user problem and a brief. A strong brief defines the users, the tasks they complete, the data shown or collected, the actions and states, the acceptance criteria, and the deliberate exclusions. Spark was announced in public preview for Copilot Pro+ subscribers in July 2025. If it isn't available to you, you can still write the brief and test plan and not claim you generated an app.

The habit that makes Spark useful is testing before refining. A generated app can look finished and still get validation, filtering, persistence, or empty states wrong. A polished interface is not evidence that behavior is correct. So write your expected results first ("a blank submission shows validation," "clearing the filter restores the full list"), run each one against the prototype, and record concrete observations ("two rows remained visible"), not vague impressions. Then refine one failed behavior at a time, preserving what already works, and rerun the neighboring tests to make sure your fix didn't break them.

Spaces and Spark can even chain: a Space can help you produce an evidence-backed product brief, and Spark can explore an application shape from it. They share a rule with everything above: generation is a starting point, not a verdict. You test the flows and treat sharing or deployment as a separate, deliberate decision.

Refine one failed behavior at a time

Resist fixing several Spark issues in one request. Write your expected results before you touch the prototype, pick a single failed test with a clear expected outcome, ask for that one behavioral correction while preserving everything that already works, then rerun the neighboring tests. One change at a time keeps every fix inspectable.

Describe an app to Spark as testable behavior· github-copilot
Bad example

Build a Workshop Check-in app with a registration form and a coordinator view.

Good example

Build a simple web app called Workshop Check-in. Attendees enter name, organization, and session, submit, and see a confirmation. A coordinator views registrations, filters by session, and clears the filter to restore all. Required states: field validation, successful registration, no registrations yet, and no matches for a filter. Use sample data and an accessible layout. No authentication, payments, or external integrations. Success: a blank submission shows validation. A valid one appears in the coordinator view. Filtering changes the visible rows, and clearing the filter restores the full list.

Why this works: A prototype you can test against named behaviors right away, not a screenshot to admire, because the brief defined users, flows, states, and success criteria.

Try it yourself

Curate a Space and audit its answer

Build a small Space from a handful of documents, ask one grounded question, and audit every claim: about ten minutes, and you don't need to write any code.

  1. 01

    Gather three or four short project documents that answer one recurring question, such as "how does a new contributor run this service?"

  2. 02

    Create a Space, add those documents as its context, and add guidance: use only included material, name the supporting file for each claim, and say "Not stated in this Space" for gaps.

  3. 03

    Ask your recurring question and read the response as a set of claims.

  4. 04

    For each important claim, open the named file and mark it confirmed, conflicting, or missing.

    Hint: A filename next to a claim isn't proof. Compare the actual command, value, or status.

  5. 05

    If the answer invented anything, tighten the guidance or sources and ask the question again.

A reusable Space plus a claim audit that shows which facts its sources support and which were absent all along.

Key takeaways

  • MCP connects Copilot to external tools and data, creating separate connection-trust and operation-trust decisions.
  • Prove discovery through observed server and tool activity. A fluent answer with no tool call is unverified.
  • Trace every MCP answer field to a returned property and flag anything the server didn't return.
  • A Space is only as good as its curated sources and a confirmed/conflicting/missing claim audit.
  • Spark generates a prototype to test. A polished interface is not evidence that its behavior is correct.

Check your understanding

  1. 1. Copilot produces a polished blocker report, but you see no MCP tool activity, even though the prompt required the release-triage server's list_blockers tool. Is discovery proven?

  2. 2. You need to give new contributors reusable, source-bounded answers from four onboarding documents. Which product fits?

  3. 3. Your Spark app for Workshop Check-in looks polished after generation. What does that tell you about whether filtering and validation work?

  4. 4. A read-only issue MCP server later adds a close_issue tool. What does that require?

  5. 5. A Space's sources never state a deployment date. What's the right way to report that?

Frequently asked questions

Terms used in this lesson

MCP (Model Context Protocol)
A standard way for an AI application like Copilot to connect to external tools and data through a server.
read-only vs mutating tool
An MCP tool that only returns information, versus one that creates, updates, or deletes data in the source system.
discovery proof
Evidence, observed server and tool activity, that an MCP tool ran, as opposed to a prose answer that merely sounds grounded.
Copilot Space
A reusable, named package of curated context that Copilot can answer questions against.
GitHub Spark
A tool that turns a natural-language application idea into a web-app prototype you can test and refine.

Further reading