A minimal MCP (Model Context Protocol) server library for Bun with auth forwarding support.
bun add git+ssh://github.com/futuritywork/plugins.git#v0.0.1import { z } from "zod";
import { mcp } from "@futuritywork/plugins";
const app = mcp({
name: "my-server",
version: "1.0.0",
});
app.tool("greet", {
description: "Greet a user",
input: z.object({
name: z.string(),
}),
handler: async ({ name }) => {
return { message: `Hello, ${name}!` };
},
});
app.listen(3000);bun run server.ts
# MCP server listening on http://localhost:3000/mcpCreate an MCP application.
const app = mcp({
name: "my-server", // required
version: "1.0.0", // required
path: "/mcp", // default: "/mcp"
instructions: "...", // optional system instructions
capabilities: { ... }, // optional MCP capabilities
pluginManifest: { ... }, // optional auth forwarding manifest
});Register a tool.
app.tool("add", {
description: "Add two numbers",
input: z.object({
a: z.number(),
b: z.number(),
}),
handler: async ({ a, b }) => {
return { sum: a + b };
},
});Tools without input:
app.tool("ping", {
description: "Health check",
handler: async () => {
return { status: "ok" };
},
});Register a resource.
app.resource("config://settings", {
description: "Application settings",
fetch: async () => {
return { theme: "dark", language: "en" };
},
});Apply a plugin.
import { cors } from "@futuritywork/plugins";
app.use(
cors({
allowOrigin: "*",
allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
})
);Add middleware directly.
app.middleware(async (req, next) => {
console.log(`${req.method} ${req.url}`);
return next(req);
});Start the server.
// HTTP (default)
await app.listen(3000);
// WebSocket
await app.listen(3000, "websocket");import { mcp, cors } from "@futuritywork/plugins";
const app = mcp({ name: "server", version: "1.0.0" });
app.use(
cors({
allowOrigin: "*",
allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization", "Accept", "Mcp-Session-Id"],
exposeHeaders: ["Mcp-Session-Id"],
maxAge: 86400,
credentials: false,
})
);
app.listen(3000);Configure OAuth metadata for /.well-known/oauth-authorization-server:
const app = mcp({
name: "server",
version: "1.0.0",
oauth: {
issuer: "https://auth.example.com",
authorizationEndpoint: "https://auth.example.com/oauth/authorize",
tokenEndpoint: "https://auth.example.com/oauth/token",
jwksUri: "https://auth.example.com/.well-known/jwks.json",
scopesSupported: ["openid", "profile"],
},
});There are two auth modes for Futurity plugins:
| Mode | Description | Use Case |
|---|---|---|
| Auth Forwarding (v1) | Platform manages OAuth tokens | Simple integrations |
| Chained Auth (v2) | Plugin manages sessions & tokens | Complex integrations, multi-service |
Auth forwarding lets the Futurity platform manage OAuth tokens on behalf of your plugin. Instead of implementing OAuth end-to-end, you declare your auth requirements in a signed manifest.
bun run keygenThis prints an Ed25519 keypair. Keep the private key secret; register the public key with the Futurity API.
const app = mcp({
name: "my-plugin",
version: "1.0.0",
pluginManifest: {
specVersion: 2,
pluginId: "my-plugin",
name: "My Plugin",
version: "1.0.0",
signingKey: process.env.FUTURITY_SIGNING_KEY!,
auth: {
type: "forwarding",
tokenEndpoint: "https://auth.example.com/oauth2/token",
authorizationEndpoint: "https://auth.example.com/oauth2/authorize",
requiredScopes: ["read", "write"],
deliveryMethod: "header", // "header" (default) or "query"
maxTokenTtl: 3600, // optional, seconds
},
mcpUrl: "https://my-plugin.example.com/mcp",
},
});The signed manifest is automatically served at:
GET /.well-known/futurity/plugin
The response includes an X-Futurity-Signature header with an Ed25519 JWS signature.
For plugins that need to manage their own sessions and store third-party tokens, use chained auth. The plugin controls the OAuth flow and issues its own tokens.
const app = mcp({
name: "my-plugin",
version: "1.0.0",
pluginManifest: {
specVersion: 2,
pluginId: "my-plugin",
name: "My Plugin",
version: "1.0.0",
signingKey: process.env.PLUGIN_SIGNING_KEY!,
auth: {
type: "chained",
authorizationEndpoint: "https://plugin.example.com/auth/authorize",
callbackEndpoint: "https://plugin.example.com/auth/callback",
tokenEndpoint: "https://plugin.example.com/auth/token",
requiredUserContext: ["user_id", "email"],
},
mcpUrl: "https://plugin.example.com/mcp",
},
chainedAuth: {
sessionStore: mySessionStore,
platformJwksUrl: "https://platform.example.com/.well-known/jwks.json",
pluginSigningKey: process.env.PLUGIN_SIGNING_KEY!,
handlers: {
onAuthorize: async (userContext, platformState, platformCallback) => { ... },
onCallback: async (req) => { ... },
onToken: async (req) => { ... },
},
},
});See docs/chained-auth.md for full documentation including:
- Complete implementation guide
- Request binding for anti-replay protection
- Session management
- Platform integration guide
import { generateKeyPair, signPayload, verifyPayload } from "@futuritywork/plugins";
const { privateKey, publicKey } = generateKeyPair();
const jws = signPayload('{"hello":"world"}', privateKey);
const valid = verifyPayload('{"hello":"world"}', jws, publicKey); // trueCustom auth middleware:
const app = mcp({
name: "server",
version: "1.0.0",
auth: async (req) => {
const token = req.headers.get("authorization")?.replace("Bearer ", "");
if (!token) return false;
return await validateToken(token);
},
});const state = {
counter: 0,
items: new Map<string, string>(),
};
app.tool("increment", {
handler: async () => {
state.counter++;
return { counter: state.counter };
},
});
app.tool("set", {
input: z.object({ key: z.string(), value: z.string() }),
handler: async ({ key, value }) => {
state.items.set(key, value);
return { key, value };
},
});The HTTP transport supports multiple concurrent client sessions.
const app = mcp({ name: "server", version: "1.0.0" });
app.tool("example", { handler: async () => ({ ok: true }) });
await app.listen(3000);
console.log(app.activeSessions);
await app.stop();import { StreamableHttpServer, StreamableHttpTransport } from "@futuritywork/plugins";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new StreamableHttpServer({
port: 3000,
path: "/mcp",
onSession: async (transport: StreamableHttpTransport) => {
const mcp = new McpServer({ name: "server", version: "1.0.0" });
await mcp.connect(transport);
},
});
await server.start();Run examples from the examples/ directory:
bun examples/calculator.ts # Math operations and unit conversion
bun examples/cors.ts # CORS configuration
bun examples/database.ts # Document database
bun examples/filesystem.ts # Virtual filesystem
bun examples/oauth.ts # OAuth metadata
bun examples/stateful.ts # Counter, notes, key-value store
bun examples/todo-app.ts # Todo list with CRUD
bun examples/weather-api.ts # Weather data API
bun examples/monday/index.ts # monday.com integration with auth forwardingA complete monday.com MCP server is included demonstrating auth forwarding:
# Set environment variables
export FUTURITY_SIGNING_KEY="your-private-key-base64"
# Run the server
bun examples/monday/index.tsFeatures:
- Boards: List and get board details
- Items: Full CRUD operations
- Updates: Read and create comments
- Groups: Create new groups
import type {
// App
McpApp,
McpAppOptions,
ToolOptions,
ResourceOptions,
Middleware,
AuthMiddleware,
WellKnownEntry,
// Plugin Manifest
PluginManifest,
PluginManifestOptions,
Auth,
AuthForwarding,
ChainedAuth,
// Chained Auth
Session,
PendingSession,
SessionStore,
UserContext,
ChainedAuthConfig,
ChainedAuthHandlers,
PlatformAssertionClaims,
VerifiedPlatformAssertion,
// Transports
StreamableHttpServer,
StreamableHttpServerOptions,
StreamableHttpTransport,
WebSocketTransport,
WebSocketTransportOptions,
} from "@futuritywork/plugins";import {
// Session management
createPendingSession,
activateSession,
expireSession,
revokeSession,
isSessionValid,
InMemorySessionStore,
// Token generation
generatePluginToken,
validatePluginToken,
generateRefreshToken,
// State management
generateChainedState,
parseChainedState,
// Request binding
hashPluginToken,
hashRequest,
validatePlatformAssertion,
validateAuthenticatedRequest,
// JWT validation
validateUserContextJwt,
clearJwksCache,
} from "@futuritywork/plugins";Copyright © 2025 Futurity Technologies Pte Ltd
This software is licensed under the Business Source License 1.1 (BSL). You may use, copy, modify, and redistribute for non-production purposes. Production use requires a commercial license until the Change Date (2030-01-01), after which the software becomes available under the GNU General Public License v3.0 or later.
See LICENSE for full terms.