Turning a Simple JS Function into a Cross‑Platform AI Tool with MCP
This article details how we built an AI‑tool ecosystem by evolving a basic online JS cloud‑function platform into a unified, reusable capability layer that integrates with Flowise, LangChain StructuredTool, and the Model Context Protocol (MCP) to provide secure, cross‑platform tool calls for agents.
Introduction
Over the past year we experimented with building an AI‑tool ecosystem, gradually shaping a clear "capability production line" that turns a plain JavaScript function into a tool usable by any AI agent.
Why an AI Tool Capability Layer?
When developing internal AI applications (customer‑service bots, live‑stream assistants, etc.) we repeatedly faced four pain points:
Each new AI integration required rewriting the same tool logic.
AI needs a "capability" rather than a raw HTTP interface.
Raw JSON responses are hard for LLMs to consume reliably.
Tools were tightly coupled to specific projects and could not be shared as assets.
The core problem was the lack of a unified layer for writing, managing, publishing, and reusing tools.
First Version – StructuredTool (Flowise)
We started by creating a StructuredTool inside Flowise, an open‑source visual AI workflow platform built on LangChain’s TypeScript version. The tool looked like this:
export class QueryLiveRoomTool extends StructuredTool {
name = 'QueryLiveRoomTool';
description = '根据 uid 查询主播信息';
schema = z.object({
roomid: z.string().describe('直播间id')
});
async _call({ roomid }) {
const res = await axios.post(...);
return formatForAI(res);
}
}Advantages:
Callable by agents.
Typed schema for parameters.
Some modularity.
Limitations:
Lifecycle tied to the Flowise project.
Requires TypeScript and a Node project, hurting developer efficiency.
Not cross‑platform.
JSON handling still manual.
Cannot inject common internal capabilities (e.g., internal SDKs).
These drawbacks motivated a platform‑level solution.
Second Version – Online JS Cloud‑Function Platform (NodeVM Runtime)
We built a sandboxed online JS cloud‑function platform where developers write a plain JS function without needing to know Flowise, LangChain, or Node project setup.
NodeVM Sandbox
Using vm2 's NodeVM, we created a controlled execution environment that injects a set of built‑in capabilities:
File‑system access disabled.
Restricted require to a whitelist.
Injected $yuumi for internal gRPC/HTTP calls.
Injected $json2MarkdownTable to convert JSON to AI‑friendly Markdown tables.
Injected session context variables such as $cookie and $flow.
import { NodeVM } from 'vm2';
import { StructuredTool } from '@langchain/core/tools';
class DynamicTool extends StructuredTool {
constructor({ name, description, schema, code }) {
super({ name, description, schema });
this.code = code; // user‑written JS
}
async _call(args, runManager, flowContext) {
const sandbox = {
...Object.fromEntries(Object.entries(args).map(([k, v]) => [`$${k}`, v])),
$flow: flowContext,
$cookie: flowContext.cookie,
$yuumi,
$json2MarkdownTable,
$biliLLM: biliLLMClient
};
const vm = new NodeVM({ sandbox, console: 'inherit', require: { builtin: allowedBuiltinDeps, external: allowedExternalDeps } });
return await vm.run(`module.exports = async () => { ${this.code} }()`, __dirname);
}
}Online Debugging with Monaco‑Editor
The editor provides syntax highlighting, basic IntelliSense, and a mock panel where developers can supply JSON arguments and see console logs, return values, and errors directly in the sandbox.
Tool Arguments Configuration
We added a visual UI that converts a JSON schema into both a LangChain zod schema and an MCP Tool Arguments schema, generating:
MCP JSON schema.
StructuredTool Zod schema.
NodeVM variable bindings (e.g., $roomid).
This configuration is reusable across the entire platform.
Tool Marketplace
Internal teams can publish their tools to a shared marketplace; other teams can discover and reuse them, turning project‑specific scripts into enterprise‑wide capabilities.
Third Version – MCP Integration (Cross‑Platform Tool Calls)
The Model Context Protocol (MCP) acts as a unified plug‑in board for "large model + tool" interactions. By exposing our tools via MCP we achieve:
Uniform, describable, streamable invocation.
Support for tools running on any host (local, remote, or other teams).
Common Misconception
Simply wrapping an existing HTTP endpoint as an MCP tool is insufficient because:
Natural‑language queries must be mapped to concrete parameters (requires prompts, few‑shot examples, or classification models).
Raw JSON responses are not AI‑friendly; we must convert them to readable text/Markdown.
Proxy Layer and Session Management
We implemented a thin MCP gateway on top of Express:
export function createMCPServer({ app, AppDataSource }) {
// Single function → MCP‑StreamableHTTP
app.post('/api/mcp/function-tool/:toolId', singleToolCreateStreamableHTTPServer);
// Multiple functions → MCP‑StreamableHTTP
app.post('/api/mcp/:mcpId', multipleToolCreateStreamableHTTPServer);
// Session reuse
app.get('/api/mcp/function-tool/:id', handleSessionRequest);
app.delete('/api/mcp/function-tool/:id', handleSessionRequest);
app.get('/api/mcp/:mcpId', handleSessionRequest);
app.delete('/api/mcp/:mcpId', handleSessionRequest);
}Each request creates or reuses a StreamableHTTPServerTransport identified by a mcp-session-id. The transport pools are cleaned up when the connection closes.
async function createStreamableHTTP({ req, res, username }) {
const sessionId = req.headers['mcp-session-id'];
let transport;
if (sessionId && transports.streamable[sessionId]) {
transport = transports.streamable[sessionId]; // reuse
} else if (!sessionId && isInitializeRequest(req.body)) {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => { transports.streamable[sessionId] = transport; }
});
transport.onclose = () => { delete transports.streamable[transport.sessionId]; };
const server = buildMcpServer({ ...config, transport: 'streamable-http' });
await server.connect(transport);
} else {
res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided' }, id: null });
return;
}
await transport.handleRequest(req, res, req.body);
}Registering Tools with MCP
We convert stored tool metadata (name, description, JSON schema, JS code) into a dynamic StructuredTool, then register it with the official MCP server:
function buildMcpServer({ name, tools, cookie, env, id, type, username, transport }) {
const server = new McpServer({ name, version: '1.0.0' });
for (const tool of tools) {
const obj = {
name: tool.name,
description: tool.description,
schema: z.object(convertSchemaToZod(tool.schema)),
code: tool.func
};
const dynamicTool = new DynamicStructuredTool(obj);
dynamicTool.setFlowObject({ cookie, env: typeof env === 'object' && Object.keys(env).length ? env : {} });
const paramsSchema = parseToolArgumentsSchema(tool.schema);
server.tool(tool.name, tool.description, paramsSchema, async (args, _extra) => {
const result = await dynamicTool.call(args);
if (process.env.DEPLOY_ENV === 'prod') reportMcpUse({ id, type, name, username, transport });
return { content: [{ type: 'text', text: result }] };
});
}
return server;
}The full execution chain becomes:
MCP → McpServer.tool → DynamicStructuredTool → NodeVM → internal services.
Security and Identity Verification
MCP is exposed as a cross‑platform service entry point, so we enforce:
Registration requires a signed sign parameter generated according to internal standards.
Proxy layer validates the signature, injects the appropriate $cookie and session context, then forwards the request to internal business APIs.
This design keeps the external interface uniform while keeping the actual business logic safely inside the internal network.
Conclusion
By progressing from Flowise StructuredTool → online JS cloud‑function platform → MCP integration, we achieved a unified tool definition, execution runtime, call protocol, marketplace, and security model. Developers now only need to write a simple JavaScript function to give an AI a new capability, eliminating repetitive integration work and paving the way for further performance and correctness improvements.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
