diff --git a/src/components/gitAgent/GitAgentMCP.tsx b/src/components/gitAgent/GitAgentMCP.tsx new file mode 100644 index 0000000..f7a4f1c --- /dev/null +++ b/src/components/gitAgent/GitAgentMCP.tsx @@ -0,0 +1,259 @@ +import { motion } from "framer-motion"; +import { CodeBlock } from "@/components/gitAgent/CodeBlock"; + +const agentYaml = `# agent.yaml +mcp_servers: + filesystem: # local server, launched over stdio (default) + command: npx + args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/data"] + env: + LOG_LEVEL: "\${MCP_LOG_LEVEL}" # \${VAR} is interpolated from your shell env + timeoutMs: 30000 # connect + list-tools timeout, default 30000ms + + analytics: # remote server over Streamable HTTP + type: http + url: "https://mcp.example.com/mcp" + headers: + Authorization: "Bearer \${ANALYTICS_TOKEN}" + + legacy: # legacy SSE transport (deprecated upstream) + type: sse + url: "https://old.example.com/sse"`; + +const sdkCode = `import { query } from "@open-gitagent/gitagent"; + +for await (const msg of query({ + prompt: "Summarize last week's signups from the database", + mcpServers: { + postgres: { + command: "npx", + args: ["-y", "@modelcontextprotocol/server-postgres", process.env.DB_URL!], + }, + }, +})) { + if (msg.type === "tool_use") console.log(\`calling \${msg.toolName}\`); +}`; + +const configFields = [ + { field: "command / args / env / cwd", applies: "stdio", notes: "How to launch a local server as a child process" }, + { field: "type: http | sse + url + headers", applies: "remote", notes: "Connect to a server over the network" }, + { field: "timeoutMs", applies: "both", notes: "Connect + listTools() timeout, default 30000ms" }, +]; + +const runtimeSteps = [ + { step: 1, text: "On startup, GitAgent connects to every configured server in parallel." }, + { step: 2, text: "It calls listTools() on each (following pagination cursors if the server paginates)." }, + { step: 3, text: "Every discovered tool is registered as __, sanitized to match provider naming rules (^[a-zA-Z0-9_-]{1,64}$) — e.g. filesystem__read_file, postgres__query." }, + { step: 4, text: "When the agent calls one of these tools, GitAgent forwards the call to the real MCP server and flattens the result (text/image/audio/resource blocks) into a string the model can read. Binary content is summarized, not inlined, to protect the token budget." }, + { step: 5, text: "On session end, every server connection is closed automatically (idempotent — safe even if called twice)." }, +]; + +const behaviors = [ + { label: "Fail-soft by design", desc: "If one server fails to connect, times out, or errors while listing tools, GitAgent logs a warning and skips just that server — everything else (other MCP servers, built-in tools) keeps working." }, + { label: "Lazy", desc: "If you configure zero MCP servers, the @modelcontextprotocol/sdk package is never even imported — no cost for agents that don't use it." }, +]; + +const tryFsYaml = `# agent.yaml +mcp_servers: + fs: + command: npx + args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/your/agent"]`; + +const tryRunCmd = `gitagent -d /path/to/your/agent \\ + --prompt "List every tool you have available, including any starting with fs__"`; + +const tryUseCmd = `gitagent -d /path/to/your/agent \\ + --prompt "Use the fs tool to list the contents of the skills directory"`; + +const brokenYaml = `# agent.yaml +mcp_servers: + broken: + command: definitely-not-a-real-binary-xyz`; + +const troubleshooting = [ + { q: "Tool not showing up?", a: "Check stderr for a [mcp:] failed to connect or failed to list tools warning — the connection and listTools() call are fail-soft, so problems are logged, not thrown." }, + { q: "${VAR} came through empty?", a: "GitAgent warns [mcp] env var VAR is not set; substituting empty string if the referenced env var isn't set in your shell — make sure it's exported before running." }, + { q: "Tool name collision?", a: "If an MCP tool's sanitized name collides with an existing tool (built-in or another server's), GitAgent logs a warning and skips the colliding one rather than silently overwriting it." }, + { q: "Timeout too short for a slow server?", a: "Raise timeoutMs in that server's config block (default is 30000ms for both connect and listTools())." }, +]; + +export function GitAgentMCP() { + return ( +
+
+ {/* Section heading */} + +

+ MCP Client +

+

+ GitAgent is an MCP client: point it at any{" "} + MCP server{" "} + (filesystem, GitHub, Postgres, Slack, fetch, and more) and that server's tools are automatically discovered and handed to your agent — no integration code needed. +

+

+ Current limitation: only MCP tools are supported today. Resources and prompts (other MCP primitives) are not yet exposed. +

+
+ + {/* A. Configure in agent.yaml */} + +

+ Configure in agent.yaml — persistent, per-agent +

+ +
+ {configFields.map((f, i) => ( + +
+ {f.field} + {f.applies} +

{f.notes}

+
+
+ ))} +
+
+ + {/* B. Configure via SDK */} + +

+ Configure via the SDK — per-call, programmatic +

+ +

+ SDK mcpServers are merged with any agent.yaml mcp_servers — if the same key exists in both, the SDK value wins. +

+
+ + {/* C. What happens at runtime */} + +

+ What happens at runtime +

+
+ {runtimeSteps.map((s, i) => ( + + + {s.step} + +

{s.text}

+
+ ))} +
+
+ {behaviors.map((b, i) => ( + +

{b.label}

+

{b.desc}

+
+ ))} +
+
+ + {/* D. Try it yourself */} + +

+ Try it yourself — a real, no-setup server +

+

+ The easiest server to test against is @modelcontextprotocol/server-filesystem (official, no API keys needed). +

+ +

Step 1 — point a server at an agent directory in its agent.yaml:

+ + +

Step 2 — run it and watch the tool discovery:

+ +

+ You should see fs__read_file, fs__list_directory, fs__search_files, etc. in the tool list GitAgent prints on startup — confirming the server connected and its tools were namespaced correctly. +

+ +

Step 3 — actually use one:

+ +

+ Watch for a tool_use event calling fs__list_directory (or similar) in the output — that's the MCP round-trip actually happening, not the model just describing what it would do. +

+ +

Step 4 — test the fail-soft behavior (optional): point a server at a command that doesn't exist and confirm GitAgent still starts fine:

+ +

+ Run the same command again — you'll see a [mcp:broken] failed to connect: … — skipping warning in stderr, but the agent still starts and the fs server (if still configured) still works. +

+
+ + {/* E. Troubleshooting */} + +

+ Troubleshooting checklist +

+
+ {troubleshooting.map((t, i) => ( + +

{t.q}

+

{t.a}

+
+ ))} +
+
+
+
+ ); +} diff --git a/src/components/gitAgent/GitAgentSidebar.tsx b/src/components/gitAgent/GitAgentSidebar.tsx index 71ac967..9087205 100644 --- a/src/components/gitAgent/GitAgentSidebar.tsx +++ b/src/components/gitAgent/GitAgentSidebar.tsx @@ -48,6 +48,7 @@ export const sidebarGroups = [ slug: "capabilities", items: [ { id: "tools", label: "Tools" }, + { id: "mcp", label: "MCP Client" }, { id: "skills", label: "Skills" }, { id: "workflows", label: "Workflows" }, { id: "hooks", label: "Hooks" }, diff --git a/src/pages/GitAgentDocsPage.tsx b/src/pages/GitAgentDocsPage.tsx index 4ddb76d..8024f83 100644 --- a/src/pages/GitAgentDocsPage.tsx +++ b/src/pages/GitAgentDocsPage.tsx @@ -20,6 +20,7 @@ import { GitAgentCLI } from "@/components/gitAgent/GitAgentCLI"; import { GitAgentModels } from "@/components/gitAgent/GitAgentModels"; import { GitAgentWebUI } from "@/components/gitAgent/GitAgentWebUI"; import { GitAgentTools } from "@/components/gitAgent/GitAgentTools"; +import { GitAgentMCP } from "@/components/gitAgent/GitAgentMCP"; import { GitAgentSkills } from "@/components/gitAgent/GitAgentSkills"; import { GitAgentWorkflows } from "@/components/gitAgent/GitAgentWorkflows"; import { GitAgentHooks } from "@/components/gitAgent/GitAgentHooks"; @@ -47,6 +48,7 @@ const SECTION_COMPONENTS: Record = { webui: GitAgentWebUI, messaging: GitAgentMessaging, tools: GitAgentTools, + mcp: GitAgentMCP, skills: GitAgentSkills, workflows: GitAgentWorkflows, hooks: GitAgentHooks,