[WIP] Refactor everything server to be more modular and use recommended APIs.
[WIP] Adding Gzip File as Resource tool
* Updated architecture.md
* Added gzip-file-as-resource.ts
- imports getSessionResourceURI and registerSessionResource from session.ts
- exports registerGZipFileAsResourceTool
- the registered tool
- validates the input URI
- fetches the file safely
- compresses it
- creates and registers the resource
- returns resource or resource link
* In tools/index.ts
- import registerGZipFileAsResourceTool
- in registerTools,
- call registerGZipFileAsResourceTool passing server
* Added resources/session.ts
- getSessionResourceURI gets a uri to the specified name
- registerSessionResource registers the session-scoped resource and returns a resource link
This commit is contained in:
parent
3d003abacb
commit
2afc618ccd
4 changed files with 315 additions and 1 deletions
|
|
@ -31,6 +31,7 @@ src/everything
|
||||||
├── resources
|
├── resources
|
||||||
│ ├── index.ts
|
│ ├── index.ts
|
||||||
│ ├── files.ts
|
│ ├── files.ts
|
||||||
|
│ ├── session.ts
|
||||||
│ ├── subscriptions.ts
|
│ ├── subscriptions.ts
|
||||||
│ └── templates.ts
|
│ └── templates.ts
|
||||||
├── server
|
├── server
|
||||||
|
|
@ -51,6 +52,7 @@ src/everything
|
||||||
│ ├── get-resource-reference.ts
|
│ ├── get-resource-reference.ts
|
||||||
│ ├── get-structured-content.ts
|
│ ├── get-structured-content.ts
|
||||||
│ ├── get-sum.ts
|
│ ├── get-sum.ts
|
||||||
|
│ ├── gzip-file-as-resource.ts
|
||||||
│ ├── long-running-operation.ts
|
│ ├── long-running-operation.ts
|
||||||
│ ├── get-sampling-request.ts
|
│ ├── get-sampling-request.ts
|
||||||
│ ├── toggle-logging.ts
|
│ ├── toggle-logging.ts
|
||||||
|
|
@ -104,6 +106,15 @@ At `src/everything`:
|
||||||
- Registers a `get-resource-links` tool that returns an intro `text` block followed by multiple `resource_link` items.
|
- Registers a `get-resource-links` tool that returns an intro `text` block followed by multiple `resource_link` items.
|
||||||
- get-resource-reference.ts
|
- get-resource-reference.ts
|
||||||
- Registers a `get-resource-reference` tool that returns a reference for a selected dynamic resource.
|
- Registers a `get-resource-reference` tool that returns a reference for a selected dynamic resource.
|
||||||
|
- gzip-file-as-resource.ts
|
||||||
|
- Registers a `gzip-file-as-resource` tool that fetches content from a URL or data URI, compresses it, and then either:
|
||||||
|
- returns a `resource_link` to a session-scoped resource (default), or
|
||||||
|
- returns an inline `resource` with the gzipped data. The resource will be still discoverable for the duration of the session via `resources/list`.
|
||||||
|
- Uses `resources/session.ts` to register the gzipped blob as a per-session resource at a URI like `demo://resource/session/<name>` with `mimeType: application/gzip`.
|
||||||
|
- Environment controls:
|
||||||
|
- `GZIP_MAX_FETCH_SIZE` (bytes, default 10 MiB)
|
||||||
|
- `GZIP_MAX_FETCH_TIME_MILLIS` (ms, default 30000)
|
||||||
|
- `GZIP_ALLOWED_DOMAINS` (comma-separated allowlist; empty means all domains allowed)
|
||||||
- get-sampling-request.ts
|
- get-sampling-request.ts
|
||||||
- Registers a `sampling-request` tool that sends a `sampling/createMessage` request to the client/LLM and returns the sampling result.
|
- Registers a `sampling-request` tool that sends a `sampling/createMessage` request to the client/LLM and returns the sampling result.
|
||||||
- get-structured-content.ts
|
- get-structured-content.ts
|
||||||
|
|
@ -198,6 +209,7 @@ At `src/everything`:
|
||||||
- `get-env` (tools/get-env.ts): Returns all environment variables from the running process as pretty-printed JSON text.
|
- `get-env` (tools/get-env.ts): Returns all environment variables from the running process as pretty-printed JSON text.
|
||||||
- `get-resource-links` (tools/get-resource-links.ts): Returns an intro `text` block followed by multiple `resource_link` items. For a requested `count` (1–10), alternates between dynamic Text and Blob resources using URIs from `resources/templates.ts`.
|
- `get-resource-links` (tools/get-resource-links.ts): Returns an intro `text` block followed by multiple `resource_link` items. For a requested `count` (1–10), alternates between dynamic Text and Blob resources using URIs from `resources/templates.ts`.
|
||||||
- `get-resource-reference` (tools/get-resource-reference.ts): Accepts `resourceType` (`text` or `blob`) and `resourceId` (positive integer). Returns a concrete `resource` content block (with its `uri`, `mimeType`, and data) with surrounding explanatory `text`.
|
- `get-resource-reference` (tools/get-resource-reference.ts): Accepts `resourceType` (`text` or `blob`) and `resourceId` (positive integer). Returns a concrete `resource` content block (with its `uri`, `mimeType`, and data) with surrounding explanatory `text`.
|
||||||
|
- `gzip-file-as-resource` (tools/gzip-file-as-resource.ts): Accepts a `name` and `data` (URL or data URI), fetches the data subject to size/time/domain constraints, compresses it, registers it as a session resource at `demo://resource/session/<name>` with `mimeType: application/gzip`, and returns either a `resource_link` (default) or an inline `resource` depending on `outputType`.
|
||||||
- `get-sampling-request` (tools/get-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM’s response payload.
|
- `get-sampling-request` (tools/get-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM’s response payload.
|
||||||
- `get-structured-content` (tools/get-structured-content.ts): Demonstrates structured responses. Accepts `location` input and returns both backward‑compatible `content` (a `text` block containing JSON) and `structuredContent` validated by an `outputSchema` (temperature, conditions, humidity).
|
- `get-structured-content` (tools/get-structured-content.ts): Demonstrates structured responses. Accepts `location` input and returns both backward‑compatible `content` (a `text` block containing JSON) and `structuredContent` validated by an `outputSchema` (temperature, conditions, humidity).
|
||||||
- `get-sum` (tools/get-sum.ts): For two numbers `a` and `b` calculates and returns their sum. Uses Zod to validate inputs.
|
- `get-sum` (tools/get-sum.ts): For two numbers `a` and `b` calculates and returns their sum. Uses Zod to validate inputs.
|
||||||
|
|
@ -217,7 +229,8 @@ At `src/everything`:
|
||||||
|
|
||||||
- Dynamic Text: `demo://resource/dynamic/text/{index}` (content generated on the fly)
|
- Dynamic Text: `demo://resource/dynamic/text/{index}` (content generated on the fly)
|
||||||
- Dynamic Blob: `demo://resource/dynamic/blob/{index}` (base64 payload generated on the fly)
|
- Dynamic Blob: `demo://resource/dynamic/blob/{index}` (base64 payload generated on the fly)
|
||||||
- Static Docs: `demo://resource/static/document/<filename>` (serves files from `src/everything/docs/` as static file-based resources)
|
- Static Documents: `demo://resource/static/document/<filename>` (serves files from `src/everything/docs/` as static file-based resources)
|
||||||
|
- Session Scoped: `demo://resource/session/<name>` (per-session resources registered dynamically; available only for the lifetime of the session)
|
||||||
|
|
||||||
- Resource Subscriptions and Notifications
|
- Resource Subscriptions and Notifications
|
||||||
|
|
||||||
|
|
@ -256,6 +269,14 @@ At `src/everything`:
|
||||||
|
|
||||||
- Design note: Each client session has its own `McpServer` instance; periodic checks run per session and invoke `server.notification(...)` on that instance, so messages are delivered only to the intended client.
|
- Design note: Each client session has its own `McpServer` instance; periodic checks run per session and invoke `server.notification(...)` on that instance, so messages are delivered only to the intended client.
|
||||||
|
|
||||||
|
## Session‑scoped Resources – How It Works
|
||||||
|
|
||||||
|
- Module: `resources/session.ts`
|
||||||
|
|
||||||
|
- `getSessionResourceURI(name: string)`: Builds a session resource URI: `demo://resource/session/<name>`.
|
||||||
|
- `registerSessionResource(server, resource, type, payload)`: Registers a resource with the given `uri`, `name`, and `mimeType`, returning a `resource_link`. The content is served from memory for the life of the session only. Supports `type: "text" | "blob"` and returns data in the corresponding field.
|
||||||
|
- Intended usage: tools can create and expose per-session artifacts without persisting them. For example, `tools/gzip-file-as-resource.ts` gzips fetched content, registers it as a session resource with `mimeType: application/gzip`, and returns either a `resource_link` or an inline `resource` based on `outputType`.
|
||||||
|
|
||||||
## Simulated Logging – How It Works
|
## Simulated Logging – How It Works
|
||||||
|
|
||||||
- Module: `server/logging.ts`
|
- Module: `server/logging.ts`
|
||||||
|
|
|
||||||
63
src/everything/resources/session.ts
Normal file
63
src/everything/resources/session.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { Resource, ResourceLink } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a session-scoped resource URI string based on the provided resource name.
|
||||||
|
*
|
||||||
|
* @param {string} name - The name of the resource to create a URI for.
|
||||||
|
* @returns {string} The formatted session resource URI.
|
||||||
|
*/
|
||||||
|
export const getSessionResourceURI = (name: string): string => {
|
||||||
|
return `demo://resource/session/${name}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a session-scoped resource with the provided server and returns a resource link.
|
||||||
|
*
|
||||||
|
* The registered resource is available during the life of the session only; it is not otherwise persisted.
|
||||||
|
*
|
||||||
|
* @param {McpServer} server - The server instance responsible for handling the resource registration.
|
||||||
|
* @param {Resource} resource - The resource object containing metadata such as URI, name, description, and mimeType.
|
||||||
|
* @param {"text"|"blob"} type
|
||||||
|
* @param payload
|
||||||
|
* @returns {ResourceLink} An object representing the resource link, with associated metadata.
|
||||||
|
*/
|
||||||
|
export const registerSessionResource = (
|
||||||
|
server: McpServer,
|
||||||
|
resource: Resource,
|
||||||
|
type: "text" | "blob",
|
||||||
|
payload: string
|
||||||
|
): ResourceLink => {
|
||||||
|
// Destructure resource
|
||||||
|
const { uri, name, mimeType, description, title, annotations, icons, _meta } =
|
||||||
|
resource;
|
||||||
|
|
||||||
|
// Prepare the resource content to return
|
||||||
|
// See https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-contents
|
||||||
|
const resourceContent =
|
||||||
|
type === "text"
|
||||||
|
? {
|
||||||
|
uri: uri.toString(),
|
||||||
|
mimeType,
|
||||||
|
text: payload,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
uri: uri.toString(),
|
||||||
|
mimeType,
|
||||||
|
blob: payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register file resource
|
||||||
|
server.registerResource(
|
||||||
|
name,
|
||||||
|
uri,
|
||||||
|
{ mimeType, description, title, annotations, icons, _meta },
|
||||||
|
async (uri) => {
|
||||||
|
return {
|
||||||
|
contents: [resourceContent],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { type: "resource_link", ...resource };
|
||||||
|
};
|
||||||
228
src/everything/tools/gzip-file-as-resource.ts
Normal file
228
src/everything/tools/gzip-file-as-resource.ts
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { CallToolResult, Resource } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { gzipSync } from "node:zlib";
|
||||||
|
import {
|
||||||
|
getSessionResourceURI,
|
||||||
|
registerSessionResource,
|
||||||
|
} from "../resources/session.js";
|
||||||
|
|
||||||
|
// Maximum input file size - 10 MB default
|
||||||
|
const GZIP_MAX_FETCH_SIZE = Number(
|
||||||
|
process.env.GZIP_MAX_FETCH_SIZE ?? String(10 * 1024 * 1024)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maximum fetch time - 30 seconds default.
|
||||||
|
const GZIP_MAX_FETCH_TIME_MILLIS = Number(
|
||||||
|
process.env.GZIP_MAX_FETCH_TIME_MILLIS ?? String(30 * 1000)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Comma-separated list of allowed domains. Empty means all domains are allowed.
|
||||||
|
const GZIP_ALLOWED_DOMAINS = (process.env.GZIP_ALLOWED_DOMAINS ?? "")
|
||||||
|
.split(",")
|
||||||
|
.map((d) => d.trim().toLowerCase())
|
||||||
|
.filter((d) => d.length > 0);
|
||||||
|
|
||||||
|
// Tool input schema
|
||||||
|
const GZipFileAsResourceSchema = z.object({
|
||||||
|
name: z.string().describe("Name of the output file").default("README.md.gz"),
|
||||||
|
data: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.describe("URL or data URI of the file content to compress")
|
||||||
|
.default(
|
||||||
|
"https://raw.githubusercontent.com/modelcontextprotocol/servers/refs/heads/main/README.md"
|
||||||
|
),
|
||||||
|
outputType: z
|
||||||
|
.enum(["resourceLink", "resource"])
|
||||||
|
.default("resourceLink")
|
||||||
|
.describe(
|
||||||
|
"How the resulting gzipped file should be returned. 'resourceLink' returns a link to a resource that can be read later, 'resource' returns a full resource object."
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tool configuration
|
||||||
|
const name = "gzip-file-as-resource";
|
||||||
|
const config = {
|
||||||
|
title: "GZip File as Resource Tool",
|
||||||
|
description:
|
||||||
|
"Compresses a single file using gzip compression. Depending upon the selected output type, returns either the compressed data as a gzipped resource or a resource link, allowing it to be downloaded in a subsequent request during the current session.",
|
||||||
|
inputSchema: GZipFileAsResourceSchema,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerGZipFileAsResourceTool = (server: McpServer) => {
|
||||||
|
server.registerTool(name, config, async (args): Promise<CallToolResult> => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
data: dataUri,
|
||||||
|
outputType,
|
||||||
|
} = GZipFileAsResourceSchema.parse(args);
|
||||||
|
|
||||||
|
// Validate data uri
|
||||||
|
const url = validateDataURI(dataUri);
|
||||||
|
|
||||||
|
// Fetch the data
|
||||||
|
const response = await fetchSafely(url, {
|
||||||
|
maxBytes: GZIP_MAX_FETCH_SIZE,
|
||||||
|
timeoutMillis: GZIP_MAX_FETCH_TIME_MILLIS,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compress the data using gzip
|
||||||
|
const inputBuffer = Buffer.from(response);
|
||||||
|
const compressedBuffer = gzipSync(inputBuffer);
|
||||||
|
|
||||||
|
// Create resource
|
||||||
|
const uri = getSessionResourceURI(name);
|
||||||
|
const blob = compressedBuffer.toString("base64");
|
||||||
|
const mimeType = "application/gzip";
|
||||||
|
const resource = <Resource>{ uri, name, mimeType };
|
||||||
|
|
||||||
|
// Register resource, get resource link in return
|
||||||
|
const resourceLink = registerSessionResource(
|
||||||
|
server,
|
||||||
|
resource,
|
||||||
|
"blob",
|
||||||
|
blob
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return the resource or a resource link that can be used to access this resource later
|
||||||
|
if (outputType === "resource") {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "resource",
|
||||||
|
resource: { uri, mimeType, blob },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else if (outputType === "resourceLink") {
|
||||||
|
return {
|
||||||
|
content: [resourceLink],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown outputType: ${outputType}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a given data URI to ensure it follows the appropriate protocols and rules.
|
||||||
|
*
|
||||||
|
* @param {string} dataUri - The data URI to validate. Must be an HTTP, HTTPS, or data protocol URL. If a domain is provided, it must match the allowed domains list if applicable.
|
||||||
|
* @return {URL} The validated and parsed URL object.
|
||||||
|
* @throws {Error} If the data URI does not use a supported protocol or does not meet allowed domains criteria.
|
||||||
|
*/
|
||||||
|
function validateDataURI(dataUri: string): URL {
|
||||||
|
// Validate Inputs
|
||||||
|
const url = new URL(dataUri);
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
url.protocol !== "http:" &&
|
||||||
|
url.protocol !== "https:" &&
|
||||||
|
url.protocol !== "data:"
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported URL protocol for ${dataUri}. Only http, https, and data URLs are supported.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
GZIP_ALLOWED_DOMAINS.length > 0 &&
|
||||||
|
(url.protocol === "http:" || url.protocol === "https:")
|
||||||
|
) {
|
||||||
|
const domain = url.hostname;
|
||||||
|
const domainAllowed = GZIP_ALLOWED_DOMAINS.some((allowedDomain) => {
|
||||||
|
return domain === allowedDomain || domain.endsWith(`.${allowedDomain}`);
|
||||||
|
});
|
||||||
|
if (!domainAllowed) {
|
||||||
|
throw new Error(`Domain ${domain} is not in the allowed domains list.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Error processing file ${dataUri}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches data safely from a given URL while ensuring constraints on maximum byte size and timeout duration.
|
||||||
|
*
|
||||||
|
* @param {URL} url The URL to fetch data from.
|
||||||
|
* @param {Object} options An object containing options for the fetch operation.
|
||||||
|
* @param {number} options.maxBytes The maximum allowed size (in bytes) of the response. If the response exceeds this size, the operation will be aborted.
|
||||||
|
* @param {number} options.timeoutMillis The timeout duration (in milliseconds) for the fetch operation. If the fetch takes longer, it will be aborted.
|
||||||
|
* @return {Promise<ArrayBuffer>} A promise that resolves with the response as an ArrayBuffer if successful.
|
||||||
|
* @throws {Error} Throws an error if the response size exceeds the defined limit, the fetch times out, or the response is otherwise invalid.
|
||||||
|
*/
|
||||||
|
async function fetchSafely(
|
||||||
|
url: URL,
|
||||||
|
{ maxBytes, timeoutMillis }: { maxBytes: number; timeoutMillis: number }
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(
|
||||||
|
() =>
|
||||||
|
controller.abort(
|
||||||
|
`Fetching ${url} took more than ${timeoutMillis} ms and was aborted.`
|
||||||
|
),
|
||||||
|
timeoutMillis
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch the data
|
||||||
|
const response = await fetch(url, { signal: controller.signal });
|
||||||
|
if (!response.body) {
|
||||||
|
throw new Error("No response body");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we can't trust the Content-Length header: a malicious or clumsy server could return much more data than advertised.
|
||||||
|
// We check it here for early bail-out, but we still need to monitor actual bytes read below.
|
||||||
|
const contentLengthHeader = response.headers.get("content-length");
|
||||||
|
if (contentLengthHeader != null) {
|
||||||
|
const contentLength = parseInt(contentLengthHeader, 10);
|
||||||
|
if (contentLength > maxBytes) {
|
||||||
|
throw new Error(
|
||||||
|
`Content-Length for ${url} exceeds max of ${maxBytes}: ${contentLength}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the fetched data from the response body
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const chunks = [];
|
||||||
|
let totalSize = 0;
|
||||||
|
|
||||||
|
// Read chunks until done
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
totalSize += value.length;
|
||||||
|
|
||||||
|
if (totalSize > maxBytes) {
|
||||||
|
reader.cancel();
|
||||||
|
throw new Error(`Response from ${url} exceeds ${maxBytes} bytes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push(value);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine chunks into a single buffer
|
||||||
|
const buffer = new Uint8Array(totalSize);
|
||||||
|
let offset = 0;
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
buffer.set(chunk, offset);
|
||||||
|
offset += chunk.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.buffer;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { registerGetSamplingRequestTool } from "./get-sampling-request.js";
|
||||||
import { registerGetStructuredContentTool } from "./get-structured-content.js";
|
import { registerGetStructuredContentTool } from "./get-structured-content.js";
|
||||||
import { registerGetSumTool } from "./get-sum.js";
|
import { registerGetSumTool } from "./get-sum.js";
|
||||||
import { registerGetTinyImageTool } from "./get-tiny-image.js";
|
import { registerGetTinyImageTool } from "./get-tiny-image.js";
|
||||||
|
import { registerGZipFileAsResourceTool } from "./gzip-file-as-resource.js";
|
||||||
import { registerLongRunningOperationTool } from "./long-running-operation.js";
|
import { registerLongRunningOperationTool } from "./long-running-operation.js";
|
||||||
import { registerToggleLoggingTool } from "./toggle-logging.js";
|
import { registerToggleLoggingTool } from "./toggle-logging.js";
|
||||||
import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates.js";
|
import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates.js";
|
||||||
|
|
@ -26,6 +27,7 @@ export const registerTools = (server: McpServer) => {
|
||||||
registerGetStructuredContentTool(server);
|
registerGetStructuredContentTool(server);
|
||||||
registerGetSumTool(server);
|
registerGetSumTool(server);
|
||||||
registerGetTinyImageTool(server);
|
registerGetTinyImageTool(server);
|
||||||
|
registerGZipFileAsResourceTool(server);
|
||||||
registerLongRunningOperationTool(server);
|
registerLongRunningOperationTool(server);
|
||||||
registerToggleLoggingTool(server);
|
registerToggleLoggingTool(server);
|
||||||
registerToggleSubscriberUpdatesTool(server);
|
registerToggleSubscriberUpdatesTool(server);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue