title: "Cross-Platform AI Skill Development: Write Once, Run Everywhere" slug: "cross-platform-ai-skill-development" description: "The complete guide to building AI skills that work across MCP, A2A, REST, and multiple agent frameworks β architecture patterns, tooling, and deployment strategies." category: "Technical" publishedAt: "2026-07-22"
Cross-Platform AI Skill Development: Write Once, Run Everywhere
AI agents are fragmented across dozens of frameworks, protocols, and platforms. LangChain, CrewAI, AutoGen, Semantic Kernel β each has its own tool format. MCP, A2A, and REST APIs each expect different communication patterns. For skill creators, this fragmentation means building the same skill 3-5 times to reach all platforms. This guide shows you how to build once and deploy everywhere.
The Fragmentation Problem
Current Landscape
| Platform/Framework | Tool Format | Protocol | Language |
|---|---|---|---|
| Claude (Anthropic) | MCP | JSON-RPC | TypeScript/Python |
| ChatGPT (OpenAI) | Function Calling | HTTP REST | Any |
| LangChain | Tools API | Python/JS | Python/TypeScript |
| CrewAI | Tools | Python | Python |
| AutoGen | Function Calling | Python | Python |
| Semantic Kernel | Plugins | HTTP/gRPC | C#/Python |
| SkillExchange | MCP Skills | Streamable HTTP | Any |
| Smithery | MCP | HTTP | Any |
Building a skill for all of these means maintaining 5+ codebases. That's unsustainable for individual developers.
The Solution: Protocol-First Skill Architecture
The key insight: separate your skill's logic from its delivery mechanism. Build the core skill once, then expose it through multiple protocol adapters.
βββββββββββββββββββββββββββ
β Skill Core (Logic) β
β β
β β’ Business logic β
β β’ Input processing β
β β’ Output formatting β
β β’ Error handling β
β β’ Validation β
β β
βββββββββββββ¬ββββββββββββββ
β
βββββββββββββ΄ββββββββββββββ
β Protocol Adapter Layer β
β β
βββββββββββΌββββββββββ¬ββββββββββ¬ββββββ΄βββββββββββ
β β β β β
βββββββ΄βββ βββββ΄ββββ βββββ΄ββββ βββββ΄βββββ ββββββββββ΄βββββ
β MCP β β A2A β β REST β β Lang β β Native β
βAdapter β βAdapterβ βAdapterβ β Chain β β SDK β
ββββββββββ βββββββββ βββββββββ ββββββββββ βββββββββββββββ
Building the Skill Core
Your skill core should be a pure function that takes input and returns output:
// skill-core.ts β The single source of truth
export interface SkillInput {
document: string;
options?: {
language?: string;
detail_level?: "summary" | "detailed" | "comprehensive";
format?: "text" | "json" | "markdown";
};
}
export interface SkillOutput {
result: string;
metadata: {
processing_time_ms: number;
confidence: number;
model_used: string;
tokens_consumed: number;
};
}
export class DocumentAnalyzerSkill {
readonly name = "document-analyzer";
readonly version = "1.2.0";
readonly description = "Analyzes documents and extracts key insights, summaries, and actionable items.";
// Zod schema for validation (used by all adapters)
readonly inputSchema = z.object({
document: z.string().min(1).max(500000),
options: z.object({
language: z.string().default("en"),
detail_level: z.enum(["summary", "detailed", "comprehensive"]).default("detailed"),
format: z.enum(["text", "json", "markdown"]).default("markdown")
}).optional().default({})
});
async execute(input: SkillInput): Promise<SkillOutput> {
const start = Date.now();
// 1. Validate input
const validated = this.inputSchema.parse(input);
// 2. Process based on detail level
const model = this.selectModel(validated.options.detail_level);
const analysis = await this.analyze(validated, model);
// 3. Format output
const formatted = this.formatOutput(analysis, validated.options.format);
return {
result: formatted,
metadata: {
processing_time_ms: Date.now() - start,
confidence: analysis.confidence,
model_used: model,
tokens_consumed: analysis.tokens
}
};
}
private selectModel(detailLevel: string): string {
const modelMap = {
"summary": "glm-4.7-flash", // Fast, cheap
"detailed": "glm-5.0", // Balanced
"comprehensive": "glm-5.1" // Best quality
};
return modelMap[detailLevel] || "glm-5.0";
}
private async analyze(input: SkillInput, model: string) {
// Core analysis logic β model-agnostic
const prompt = this.buildPrompt(input);
const response = await llmClient.complete(model, prompt);
return this.parseAnalysis(response);
}
}
Protocol Adapters
MCP Adapter
// adapters/mcp.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { DocumentAnalyzerSkill } from "./skill-core";
const skill = new DocumentAnalyzerSkill();
const server = new McpServer({
name: skill.name,
version: skill.version
});
// Register as MCP tool
server.tool(
skill.name,
skill.description,
skill.inputSchema,
async (params) => {
const result = await skill.execute(params);
return {
content: [{
type: "text",
text: result.result
}],
metadata: result.metadata
};
}
);
// Start MCP server
const transport = new StreamableHTTPServerTransport({});
await server.connect(transport);
const app = express();
app.post("/mcp", transport.handleRequest);
app.listen(3000);
REST API Adapter
// adapters/rest.ts
import express from "express";
import { DocumentAnalyzerSkill } from "./skill-core";
const skill = new DocumentAnalyzerSkill();
const app = express();
app.post("/analyze", express.json({ limit: "50mb" }), async (req, res) => {
try {
const result = await skill.execute(req.body);
res.json({
result: result.result,
metadata: result.metadata
});
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: "Invalid input", details: error.errors });
} else {
res.status(500).json({ error: "Analysis failed", message: error.message });
}
}
});
// Health check
app.get("/health", (req, res) => res.json({ status: "ok", version: skill.version }));
// OpenAPI spec (auto-generated)
app.get("/openapi.json", (req, res) => {
res.json({
openapi: "3.1.0",
info: { title: skill.name, version: skill.version },
paths: {
"/analyze": {
post: {
summary: skill.description,
requestBody: { required: true, content: { "application/json": { schema: skill.inputSchema } } },
responses: { "200": { description: "Success" } }
}
}
}
});
});
app.listen(8080);
LangChain Tool Adapter
// adapters/langchain.ts
import { DynamicTool } from "@langchain/core/tools";
import { DocumentAnalyzerSkill } from "./skill-core";
const skill = new DocumentAnalyzerSkill();
export const documentAnalyzerTool = new DynamicTool({
name: skill.name,
description: skill.description,
func: async (input: string) => {
// LangChain passes input as a string β parse it
const params = typeof input === "string" ? JSON.parse(input) : input;
const result = await skill.execute(params);
return result.result;
}
});
// For LangChain.js structured tools
import { StructuredTool } from "@langchain/core/tools";
import { z } from "zod";
export class DocumentAnalyzerStructuredTool extends StructuredTool {
name = skill.name;
description = skill.description;
schema = skill.inputSchema;
async _call(input: z.infer<typeof skill.inputSchema>): Promise<string> {
const result = await skill.execute(input);
return result.result;
}
}
CrewAI Tool Adapter
# adapters/crewai.py
from crewai_tools import BaseTool
from pydantic import BaseModel, Field
import httpx
class DocumentAnalyzerInput(BaseModel):
document: str = Field(..., description="The document text to analyze")
detail_level: str = Field("detailed", description="Level of detail: summary, detailed, comprehensive")
class DocumentAnalyzerTool(BaseTool):
name: str = "document_analyzer"
description: str = "Analyzes documents and extracts key insights, summaries, and actionable items."
args_schema: type[BaseModel] = DocumentAnalyzerInput
def _run(self, **kwargs) -> str:
# Call the skill core via REST API
response = httpx.post(
"http://skill-core:8080/analyze",
json=kwargs,
timeout=30.0
)
response.raise_for_status()
return response.json()["result"]
OpenAI Function Calling Adapter
// adapters/openai.ts
import { DocumentAnalyzerSkill } from "./skill-core";
const skill = new DocumentAnalyzerSkill();
// Export as OpenAI function definition
export const functionDefinition = {
type: "function",
function: {
name: skill.name,
description: skill.description,
parameters: {
type: "object",
properties: {
document: { type: "string", description: "The document text to analyze" },
options: {
type: "object",
properties: {
language: { type: "string", default: "en" },
detail_level: { type: "string", enum: ["summary", "detailed", "comprehensive"] },
format: { type: "string", enum: ["text", "json", "markdown"] }
}
}
},
required: ["document"]
}
}
};
// Handler for OpenAI function calls
export async function handleFunctionCall(args: string): Promise<string> {
const input = JSON.parse(args);
const result = await skill.execute(input);
return result.result;
}
Build and Deployment Strategy
Build Configuration
// package.json
{
"name": "document-analyzer-skill",
"scripts": {
"build:all": "npm run build:mcp && npm run build:rest && npm run build:langchain",
"build:mcp": "esbuild src/adapters/mcp.ts --bundle --platform=node --outfile=dist/mcp.js",
"build:rest": "esbuild src/adapters/rest.ts --bundle --platform=node --outfile=dist/rest.js",
"build:langchain": "tsc --project tsconfig.langchain.json",
"build:crewai": "echo 'Python adapter β build separately'",
"package": "node scripts/package-all.js",
"publish:skillexchange": "node scripts/publish-to-skillexchange.js",
"publish:npm": "npm publish",
"publish:pypi": "cd adapters/crewai && python -m build && twine upload dist/*"
}
}
Docker Setup for Multi-Protocol Deployment
FROM node:22-slim AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
# MCP Server image
FROM base AS mcp-server
EXPOSE 3000
CMD ["node", "dist/mcp.js"]
# REST API image
FROM base AS rest-api
EXPOSE 8080
CMD ["node", "dist/rest.js"]
Docker Compose for All Adapters
version: "3.9"
services:
mcp-server:
build:
context: .
target: mcp-server
ports: ["3000:3000"]
rest-api:
build:
context: .
target: rest-api
ports: ["8080:8080"]
# The REST API is also accessible for CrewAI, AutoGen, etc.
SDK Generation
Auto-generate SDKs for popular languages from your input schema:
// scripts/generate-sdks.ts
import { zodToTypeScript, zodToPython } from "@skill/sdk-generator";
const skill = new DocumentAnalyzerSkill();
// Generate TypeScript SDK
const tsSDK = generateTypeScriptSDK({
name: skill.name,
schema: skill.inputSchema,
endpoint: "https://my-skill.com/api"
});
// Generate Python SDK
const pySDK = generatePythonSDK({
name: skill.name,
schema: skill.inputSchema,
endpoint: "https://my-skill.com/api"
});
// Generate OpenAPI spec (for auto-client generation in any language)
const openApiSpec = generateOpenAPI({
name: skill.name,
description: skill.description,
schema: skill.inputSchema
});
Publishing to Multiple Marketplaces
// scripts/publish-all.ts
import { SkillExchangeClient, SmitheryClient, NPMClient } from "@skill/publishers";
const skill = new DocumentAnalyzerSkill();
// Publish to SkillExchange
await new SkillExchangeClient().publish({
name: skill.name,
description: skill.description,
endpoint: "https://my-skill.com/mcp",
protocol: "MCP",
pricing: { type: "per_invocation", price: 0.03, currency: "EUR" }
});
// Publish to Smithery
await new SmitheryClient().publish({
name: skill.name,
sourceUrl: "https://github.com/me/document-analyzer",
mcpConfig: "./mcp.json"
});
// Publish to NPM (for LangChain/Direct usage)
await new NPMClient().publish({
name: `@me/${skill.name}`,
version: skill.version,
entryPoint: "./dist/langchain.js"
});
Testing Across Platforms
// tests/cross-platform.test.ts
import { DocumentAnalyzerSkill } from "../src/skill-core";
const TEST_INPUTS = [
{ document: "Short text", options: { detail_level: "summary" } },
{ document: "Medium length document with more context...", options: { detail_level: "detailed" } },
{ document: "Very long document...", options: { detail_level: "comprehensive" } },
];
describe("Cross-platform consistency", () => {
const skill = new DocumentAnalyzerSkill();
for (const input of TEST_INPUTS) {
test(`produces consistent output for: ${input.document.slice(0, 20)}...`, async () => {
// Test through skill core
const coreResult = await skill.execute(input);
// Test through REST API
const restResult = await fetch("http://localhost:8080/analyze", {
method: "POST",
body: JSON.stringify(input)
}).then(r => r.json());
// Test through MCP
const mcpResult = await mcpClient.callTool(skill.name, input);
// Outputs should be equivalent (not identical due to model non-determinism)
expect(restResult.result).toBeTruthy();
expect(mcpResult.content[0].text).toBeTruthy();
// Quality should be similar
const coreScore = await qualityScorer.score(coreResult.result, input);
const restScore = await qualityScorer.score(restResult.result, input);
expect(Math.abs(coreScore - restScore)).toBeLessThan(0.1);
});
}
});
Conclusion
Cross-platform AI skill development isn't about writing everything once β it's about separating concerns correctly. Build your skill core as a pure, protocol-agnostic module. Then add thin adapters for each platform. This approach reduces maintenance by 70%, ensures consistency across platforms, and lets you reach every agent ecosystem from a single codebase.
The future of AI skills is protocol-agnostic β build for that future today. Your skill core should outlast every framework, protocol, and platform that currently exists.