Guides

Beginner demo lab#

A copy-paste lab for first-time MCP and agent-connector developers. You will create one local MCP server, run a smoke test without any host UI, open it in Inspector, add a connector config, customize the demo data, and compare your result against simple screenshot-style frames.

What you will have at the end

A working schema_summary MCP tool, a repeatable npm run demo script, an Inspector check, a connector config with a HUD/statusline and action, and a small set of visual checkpoints you can use when writing docs or release notes for your own package.

0. The whole path#

The lab is intentionally linear. Finish each checkpoint before moving to the next one; that keeps protocol problems, host problems, and connector problems separate.

StepYou doDone when
ServerCreate files and one read-only tool.npm run demo prints the tool result.
InspectorOpen a protocol-aware UI.Inspector lists and calls schema_summary.
CustomizeChange the demo data and schema.Bad table names fail; valid table names return new data.
ConnectorAdd statusline and action surfaces.Audit and dry-run install show concrete host output.

1. Create the files#

Start with this folder shape. The server is neutral MCP. The connector config is the agent-connector layer you add after the server works.

project tree
text
acme-db-mcp/
  package.json
  my-mcp-server.mjs
  agent-connector.config.mjs
  scripts/
    demo-smoke.mjs
package.json
json
{
  "name": "@acme/acme-db-mcp",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "demo": "node scripts/demo-smoke.mjs",
    "inspect": "npx -y @modelcontextprotocol/inspector node ./my-mcp-server.mjs"
  },
  "dependencies": {
    "@ken-jo/agent-connector": "^0.4.98",
    "@modelcontextprotocol/sdk": "^1.29.0",
    "zod": "^3.25.0"
  }
}

2. Paste the demo MCP server#

This server has one safe read-only tool. The table list is deliberately fake so beginners can edit it without touching a real database.

my-mcp-server.mjs
ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const tables = {
  users: ["id", "email", "created_at", "plan"],
  orders: ["id", "user_id", "total_usd", "status"],
  invoices: ["id", "customer_id", "due_date", "paid"],
};

const server = new McpServer({
  name: "acme-db-demo",
  version: "0.1.0",
});

server.registerTool(
  "schema_summary",
  {
    title: "Schema summary",
    description:
      "Return a short read-only schema summary for the demo database. Use this before writing SQL.",
    inputSchema: {
      table: z.enum(["users", "orders", "invoices"]).optional(),
      includeColumns: z.boolean().default(false),
    },
    outputSchema: {
      table: z.string().optional(),
      summary: z.string(),
      columns: z.array(z.string()).optional(),
    },
  },
  async ({ table, includeColumns }) => {
    const tableNames = Object.keys(tables);
    const selected = table ? tables[table] : undefined;
    const structuredContent = {
      table,
      summary: table
        ? `${table} has ${selected.length} demo columns.`
        : `Demo database has ${tableNames.length} tables: ${tableNames.join(", ")}.`,
      columns: includeColumns ? selected : undefined,
    };

    return {
      structuredContent,
      content: [{ type: "text", text: structuredContent.summary }],
    };
  },
);

await server.connect(new StdioServerTransport());

Beginner checkpoint

You should be able to explain this file in one sentence: the server exposes a tool named schema_summary that validates a table name and returns text plus structured JSON.

3. Add the smoke-test script#

A smoke script is easier than opening a host while you are still learning. It starts your stdio server as a client would, lists tools, calls one tool, prints the result, and closes the connection.

scripts/demo-smoke.mjs
ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["./my-mcp-server.mjs"],
});

const client = new Client({
  name: "acme-db-demo-smoke",
  version: "0.1.0",
});

await client.connect(transport);

const tools = await client.listTools();
console.log("tools:", tools.tools.map((tool) => tool.name).join(", "));

const result = await client.callTool({
  name: "schema_summary",
  arguments: { table: "users", includeColumns: true },
});

console.log("text:", result.content?.[0]?.text);
console.log("structured:", JSON.stringify(result.structuredContent, null, 2));

await client.close();
terminal
bash
npm install
npm run demo
npm run inspect

# After the server works:
npx @ken-jo/agent-connector audit --connector ./agent-connector.config.mjs
npx @ken-jo/agent-connector install --connector ./agent-connector.config.mjs --targets claude-code --dry-run
npx @ken-jo/agent-connector action claude-code show-demo-tables --connector acme-db-demo

4. Open Inspector and capture the first demo frame#

Inspector is the best first screenshot because it proves the MCP layer works before any host-specific install is involved. Capture the tool list and one successful schema_summary call.

Terminal
npm run demo
$ npm run demo
tools: schema_summary
text: users has 4 demo columns.
structured: { "table": "users", "columns": [...]}
Inspector
schema_summary call
Tool
schema_summary
Arguments
{ "table": "users", "includeColumns": true }
users has 4 demo columns.

5. Customize one thing on purpose#

The first customization should change data without changing the mental model. Replace the fake table catalog, update the enum, and rerun the same script. This teaches the right habit: schema and implementation move together.

customize table catalog
ts
// Change the catalog first.
const tables = {
  products: ["id", "sku", "name", "price_usd"],
  inventory: ["id", "product_id", "warehouse", "quantity"],
};

// Then update the enum so invalid table names still fail early.
table: z.enum(["products", "inventory"]).optional()
TryWhat you learn
Rename the tablesTool descriptions and schemas should match the actual domain.
Add one boolean optionOptional arguments should have a clear default and visible result.
Call an invalid tableZod validation should fail before your handler touches app logic.

6. Add agent-connector surfaces#

After the MCP tool works, add the connector layer. This example keeps it small: one server declaration, one HUD/statusline string, and one user-invoked action. Unsupported hosts skip-warn; supported hosts emit native affordances.

agent-connector.config.mjs
ts
import {
  defineAction,
  defineConnector,
  defineStatusline,
} from "@ken-jo/agent-connector/sdk";
import { fileURLToPath } from "node:url";

const serverPath = fileURLToPath(new URL("./my-mcp-server.mjs", import.meta.url));

const statusline = defineStatusline({
  description: "Show the demo connector state.",
  options: { refreshInterval: 5, maxLines: 1 },
  render(ctx) {
    const calls = ctx.usage?.calls ?? 0;
    const host = ctx.host === "unknown" ? "host" : ctx.host;
    return `acme-db demo · ${host} · ${calls} calls`;
  },
});

const showTables = defineAction({
  id: "show-demo-tables",
  label: "Show demo tables",
  description: "Print the demo table list.",
  placement: "command-palette",
  run(ctx) {
    return {
      message: `Demo tables for ${ctx.host}: users, orders, invoices`,
    };
  },
});

export default defineConnector({
  displayName: "Acme DB Demo",
  server: {
    transport: "stdio",
    command: "node",
    args: [serverPath],
  },
  statusline,
  actions: [showTables],
});
Host chat prompt
Ask a useful first question
You have an MCP tool named schema_summary.

Please inspect the users table, explain what columns exist, and suggest one safe
read-only query a beginner could try next.
HUD + action preview
What the user should see
acme-db demo · claude-code · 1 calls
Action output
Demo tables for claude-code: users, orders, invoices

7. Capture docs-ready demo screenshots#

For a beginner-facing README or release note, capture only the screens that prove a boundary. More screenshots are not better; they become noise unless each one answers a different question.

ScreenshotProvesKeep visible
Terminal smoke testThe server can be called without a host UI.Tool name, text result, structured JSON.
Inspector callThe MCP protocol layer works.Arguments and successful result, not unrelated browser chrome.
Host chat answerA real host can use the tool.The prompt, the host's answer, and the tool result summary.
HUD/action previewagent-connector surfaces are wired.Short HUD string and one explicit action result.

Next pages

Continue with Build your first MCP server for the protocol walkthrough, then Connect your first host and Add connector surfaces.