DEV Community

Cover image for One Skill, Three Runtimes: What Cross-Platform Agent Tool Development Reveals About Capability Portability
mech.app
mech.app

Posted on Originally published at mech.app

One Skill, Three Runtimes: What Cross-Platform Agent Tool Development Reveals About Capability Portability

Every major AI assistant now supports custom skills. GitHub Copilot calls them Extensions. Claude calls them Tools (and MCP servers). LangChain calls them Tools. Spring AI calls them Functions. The terminology is a mess, but the concept is identical: give an AI a callable capability with a defined interface, and it will use that capability when the task requires it.

The interesting part is not that you can build a skill. The interesting part is what happens when you try to build the same skill for three different runtimes. The authentication boundaries, execution context, and state persistence patterns diverge immediately. This article builds one concrete skill (a deployment status checker) across GitHub Copilot Extensions, Claude Tools, and a standalone agent runtime. The goal is to expose where portability breaks down and what trade-offs each platform forces.

The Skill Anatomy

An AI skill has three parts:

  1. Schema: What the skill does, what inputs it accepts, what it returns
  2. Implementation: The actual code that executes when called
  3. Registration: How the AI model discovers the skill exists

The LLM reads the schema at inference time, decides whether the skill is relevant to the user's request, calls it with structured arguments extracted from natural language, gets the result, and incorporates it into the response. The human never directly triggers the skill. The LLM does, when it judges the skill will help.

Platform Comparison: Schema and Registration

Platform Schema Format Registration Invocation
GitHub Copilot Extension Markdown description + REST endpoint GitHub App manifest @your-extension in Copilot Chat
Claude Tools (MCP) JSON Schema function definition MCP server connection config Automatic when MCP server is active
Standalone Agent Python/TypeScript function signature + docstring Direct function reference in agent config Agent loop decides based on tool list

The schema format difference is not cosmetic. GitHub Copilot Extensions use natural language descriptions because the extension is a black-box HTTP endpoint. The LLM cannot inspect the implementation. Claude Tools use JSON Schema because the tool runs in the same process as the MCP server, so the schema can be machine-validated. Standalone agents use whatever the orchestration framework expects (LangChain uses Pydantic models, LlamaIndex uses function signatures).

Authentication Boundaries

This is where portability dies.

GitHub Copilot Extensions run as GitHub Apps. Authentication happens at the GitHub App level. The extension receives a user token scoped to the repositories the user has access to. The extension cannot access anything outside GitHub's OAuth boundary. If your skill needs to call an external API (AWS, Stripe, your own backend), you must implement a separate OAuth flow or API key management system inside the extension.

Claude Tools (MCP) run locally on the user's machine or in a server the user controls. Authentication is ambient. If the MCP server needs to call AWS, it uses the AWS credentials already configured on the machine. If it needs to call an internal API, it uses the network access the machine already has. There is no OAuth handshake because there is no security boundary between the tool and the user's environment.

Standalone agents inherit the authentication context of the runtime. If the agent runs in a Lambda function, it uses the Lambda execution role. If it runs in a Docker container, it uses whatever credentials are mounted or injected. The agent does not manage authentication. The deployment environment does.

This means you cannot write a single authentication layer and reuse it. Each platform requires a different strategy:

  • GitHub Copilot: OAuth + token storage in your backend
  • Claude MCP: Ambient credentials + local config files
  • Standalone: IAM roles, service accounts, or injected secrets

Execution Context and State Persistence

GitHub Copilot Extensions are stateless HTTP endpoints. Each invocation is independent. If your skill needs to maintain state across calls (e.g., tracking a multi-step workflow), you must persist state in a database or cache and pass a session ID in each request. The extension does not have access to the conversation history. It only sees the current invocation.

Claude Tools (MCP) run in a persistent process. The MCP server can maintain in-memory state across tool calls within the same session. If the user asks a follow-up question, the tool can reference previous results without re-fetching data. However, when the MCP server restarts, all state is lost unless explicitly persisted.

Standalone agents have whatever state management the orchestration framework provides. LangChain has memory modules. LlamaIndex has context managers. You can implement custom state persistence using Redis, Postgres, or a vector store. The agent controls the state lifecycle.

State persistence is not portable. If you build a skill that relies on session state for Claude MCP, you cannot port it to GitHub Copilot without adding a database layer. If you build a skill that relies on LangChain's memory module, you cannot port it to Claude MCP without rewriting the state logic.

Implementation: Deployment Status Checker

Here is the same skill implemented for each platform.

GitHub Copilot Extension

// GitHub App endpoint
app.post('/api/copilot/deployment-status', async (req, res) => {
  const { environment, service } = req.body;
  const token = req.headers['x-github-token'];

  // Must validate token and check user permissions
  const user = await validateGitHubToken(token);
  if (!user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Call external API with stored credentials
  const apiKey = await getApiKeyForUser(user.id);
  const status = await fetch(`https://deploy-api.example.com/status`, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  res.json({ status: status.data });
});
Enter fullscreen mode Exit fullscreen mode

The extension must handle token validation, user-scoped credential retrieval, and HTTP response formatting. It cannot assume any ambient authentication.

Claude Tool (MCP)

// MCP server tool definition
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: "check_deployment_status",
    description: "Check the deployment status of a service in an environment",
    inputSchema: {
      type: "object",
      properties: {
        environment: { type: "string" },
        service: { type: "string" }
      },
      required: ["environment", "service"]
    }
  }]
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "check_deployment_status") {
    const { environment, service } = request.params.arguments;

    // Uses ambient credentials from local config
    const status = await fetch(`https://deploy-api.example.com/status`, {
      headers: { 'Authorization': `Bearer ${process.env.DEPLOY_API_KEY}` }
    });

    return { content: [{ type: "text", text: JSON.stringify(status.data) }] };
  }
});
Enter fullscreen mode Exit fullscreen mode

The MCP tool reads credentials from the environment. No token validation, no user-scoped storage. It assumes the user has already configured access.

Standalone Agent (LangChain)

from langchain.tools import tool

@tool
def check_deployment_status(environment: str, service: str) -> dict:
    """Check the deployment status of a service in an environment.

    Args:
        environment: The environment name (staging, production)
        service: The service name
    """
    import os
    import requests

    # Uses injected credentials from agent runtime
    api_key = os.environ['DEPLOY_API_KEY']
    response = requests.get(
        'https://deploy-api.example.com/status',
        headers={'Authorization': f'Bearer {api_key}'}
    )
    return response.json()
Enter fullscreen mode Exit fullscreen mode

The standalone tool is a plain function. The orchestration framework handles registration and invocation. Authentication is ambient, like MCP, but the deployment environment controls credential injection.

Where Portability Breaks Down

You cannot write a single implementation and deploy it everywhere. The differences are structural:

  1. Authentication model: OAuth + user-scoped storage vs. ambient credentials vs. injected secrets
  2. Execution context: Stateless HTTP vs. persistent process vs. orchestration-managed lifecycle
  3. Registration mechanism: GitHub App manifest vs. MCP server config vs. function reference
  4. Error handling: HTTP status codes vs. MCP error responses vs. framework-specific exceptions
  5. Observability: GitHub App logs vs. local stdout vs. agent framework telemetry

If you want to build a reusable skill, you must abstract the platform-specific parts into adapters. The core logic (calling the deployment API, parsing the response) can be shared. The authentication, state management, and registration layers cannot.

Decision Framework

Use Case Best Platform Why
Internal tool for your team, needs access to private APIs Claude MCP Ambient credentials, no OAuth overhead, runs locally
Public-facing skill for GitHub users GitHub Copilot Extension Built-in distribution, GitHub OAuth, sandboxed execution
Complex multi-step workflow with state Standalone Agent Full control over state persistence, orchestration, and error recovery
Prototype or one-off automation Claude MCP Fastest to build, no deployment infrastructure
Enterprise deployment with compliance requirements Standalone Agent Full control over security boundaries, audit logs, and credential management

Technical Verdict

Use GitHub Copilot Extensions when you want to distribute a skill to GitHub users and the skill only needs access to GitHub APIs or public endpoints. The OAuth model and stateless execution are constraints, not bugs. They make the extension safe to run in a multi-tenant environment.

Use Claude Tools (MCP) when you are building for yourself or a small team and the skill needs access to local resources or private APIs. The ambient authentication and persistent process model make development fast, but they are not suitable for untrusted users.

Use standalone agents when you need full control over the execution environment, state persistence, and security boundaries. The trade-off is deployment complexity. You must manage the runtime, credentials, and observability yourself.

Avoid trying to build a single implementation that works everywhere. The platforms have fundamentally different execution models. Build adapters instead. Share the core logic, isolate the platform-specific parts, and accept that portability is limited.

Source Links

Top comments (0)