@mcp-layer/attach
v1.1.2
Published
Attach to an in-process MCP server instance and return a Session.
Downloads
199
Readme
@mcp-layer/attach
@mcp-layer/attach attaches to an in-process MCP server instance and returns a shared Session. This is the way to layer REST/GraphQL/UI functionality on top of an existing server without spawning a new process.
Table of Contents
- Installation
- Usage
- Platformatic MCP (Fastify)
- When to use attach vs connect
- API (authoritative)
- Behavior and constraints
- Testing
- Runtime Error Reference
Installation
pnpm add @mcp-layer/attach
# or
npm install @mcp-layer/attach
# or
yarn add @mcp-layer/attachUsage
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { attach } from '@mcp-layer/attach';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// Register tools/resources/prompts on the server before attaching.
// server.registerTool(...)
// server.registerResource(...)
const session = await attach(server, 'my-server');
await session.client.ping();
await session.close();Platformatic MCP (Fastify)
If you're using the Platformatic MCP Fastify plugin, you can pass the Fastify instance directly to attach and the adapter will talk to the MCP endpoint in-process.
import fastify from 'fastify';
import mcp from '@platformatic/mcp';
import { attach } from '@mcp-layer/attach';
const app = fastify();
await app.register(mcp, {
serverInfo: { name: 'platformatic-demo', version: '1.0.0' }
});
// Register tools/resources/prompts using app.mcpAddTool/app.mcpAddResource/app.mcpAddPrompt.
const session = await attach(app, 'platformatic-demo', { path: '/mcp' });
await session.client.ping();
await session.close();
await app.close();Notes:
- This adapter uses Fastify
inject()against the MCP HTTP endpoint (default/mcp). - Pass
pathwhen your MCP endpoint is mounted elsewhere.
When to use attach vs connect
- Use
attachwhen you already have an in-processMcpServerinstance and want to layer new behavior (REST/GraphQL/UI) without spawning another process. - Use
@mcp-layer/connectwhen you only have a configuration entry and need to spawn a stdio process.
API (authoritative)
attach(server, name, options?)
server: anMcpServerorServerinstance from the MCP SDKname: logical server name for the Sessionoptions:info: overrides for client identity (name,version)source: override the Session source string (defaults toin-memory)
Returns a Session (from @mcp-layer/session) with:
client,transport,info,name,source,entry
Behavior and constraints
- In-memory transport: attach uses the SDK’s in-memory transport to connect client and server inside one process.
- Single transport: the target server must not already be connected. If it is,
attachthrows. - Lifecycle:
session.close()closes the client and the in-memory transport. The server instance remains yours to manage. - Errors: if the server fails during handshake, the attach call will throw; you are still responsible for server cleanup.
Testing
pnpm test --filter @mcp-layer/attachRuntime Error Reference
This section is written for high-pressure debugging moments. Each entry maps directly to the @mcp-layer/attach call path and transport lifecycle.
Expected server name to be a non-empty string.
Thrown from: attach
This happens when attach(instance, name, opts) receives a missing or blank name. The value is used as Session.name, so attach fails before opening transports.
Step-by-step resolution:
- Inspect the
attach(...)call site and confirm the second argument is a non-empty string. - Check for argument shifting bugs (for example passing
optsas the second argument by mistake). - Normalize CLI/env inputs (
trim) before passing the session name. - Add a test that rejects empty names and accepts a valid one.
const name = typeof input.name === 'string' ? input.name.trim() : '';
if (!name)
throw new Error('attach() requires a non-empty session name.');
const session = await attach(server, name, { source: 'in-memory' });Server is already connected to a transport; attach requires an unconnected server.
Thrown from: attach
This happens when the SDK server instance already has an attached transport (server.transport). attach intentionally refuses to stack a second transport onto the same server instance.
Step-by-step resolution:
- Find where the same server object is reused across multiple
attachcalls. - Confirm whether another code path already called
server.connect(...). - Use one attach per server instance, or construct a fresh server for each independent session.
- Add lifecycle tests that fail on duplicate attach and pass on fresh instances.
const serverA = createMcpServer();
const sessionA = await attach(serverA, 'a');
const serverB = createMcpServer();
const sessionB = await attach(serverB, 'b');
await Promise.all([sessionA.close(), sessionB.close()]);Unknown attach provider "{provider}".
Thrown from: attachWithProvider
This happens when provider resolution returns a name that attachWithProvider does not implement. Right now the package only supports the platformatic provider branch.
Step-by-step resolution:
- Confirm the incoming instance is actually a Platformatic Fastify MCP instance (
mcpAddTool+inject). - If the instance is wrapped, pass the underlying MCP server object (
wrapper.server) toattach. - If you are adding a new runtime integration, implement a provider branch before calling with that provider id.
- Add a test for provider detection and branch coverage.
const target = app.server ?? app;
const session = await attach(target, 'api');
await session.close();Transport is not started.
Thrown from: FastifyInjectTransport.send
This happens when FastifyInjectTransport.send(...) is invoked before the transport has been started. In normal flows, client.connect(transport) starts it; direct low-level send calls can bypass that.
Step-by-step resolution:
- Check whether code is calling transport internals directly instead of going through
Client/Session. - Ensure connection initialization (
client.connect(...)orattach(...)) happens before any request send. - Remove direct transport calls from tests unless you explicitly start the transport.
- Add a regression test for aborted pre-start sends if you maintain custom transport code.
const session = await attach(app, 'fastify-dev', { path: '/mcp' });
const tools = await session.client.listTools();
console.log(tools.tools.length);
await session.close();Expected an MCP server instance.
Thrown from: resolveServer
This happens when attach receives a value that is not an MCP SDK server instance, not a wrapper exposing .server, and not a supported provider instance.
Step-by-step resolution:
- Log the exact object passed to
attachand verify its shape. - Pass an actual MCP server instance (
McpServer/SDKServer) or supported wrapper. - If you are integrating a framework wrapper, pass the underlying
.serverobject. - Add a type guard before
attachin your integration boundary.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({ name: 'demo', version: '1.0.0' });
const session = await attach(server, 'demo');
await session.close();License
MIT
