@mcpflo/server-everything 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/dist/createServer.js +42 -0
- package/dist/docs/architecture.md +19 -0
- package/dist/docs/extension.md +20 -0
- package/dist/docs/features.md +23 -0
- package/dist/docs/how-it-works.md +22 -0
- package/dist/docs/instructions.md +16 -0
- package/dist/docs/startup.md +20 -0
- package/dist/docs/structure.md +21 -0
- package/dist/index.js +10 -0
- package/dist/prompts/args.js +19 -0
- package/dist/prompts/completions.js +32 -0
- package/dist/prompts/index.js +19 -0
- package/dist/prompts/resource.js +35 -0
- package/dist/prompts/simple.js +16 -0
- package/dist/resources/docsDir.js +18 -0
- package/dist/resources/file-resources.js +52 -0
- package/dist/resources/index.js +24 -0
- package/dist/resources/session.js +34 -0
- package/dist/resources/subscriptions.js +38 -0
- package/dist/resources/templates.js +91 -0
- package/dist/server/logging.js +45 -0
- package/dist/server/roots.js +44 -0
- package/dist/tools/add.js +12 -0
- package/dist/tools/annotated-message.js +67 -0
- package/dist/tools/echo.js +12 -0
- package/dist/tools/get-resource-links.js +36 -0
- package/dist/tools/get-resource-reference.js +32 -0
- package/dist/tools/get-roots-list.js +51 -0
- package/dist/tools/get-structured-content.js +35 -0
- package/dist/tools/get-tiny-image.js +23 -0
- package/dist/tools/gzip-file-as-resource.js +115 -0
- package/dist/tools/index.js +63 -0
- package/dist/tools/print-env.js +10 -0
- package/dist/tools/simulate-research-query.js +173 -0
- package/dist/tools/toggle-simulated-logging.js +33 -0
- package/dist/tools/toggle-subscriber-updates.js +33 -0
- package/dist/tools/trigger-elicitation-request-async.js +136 -0
- package/dist/tools/trigger-elicitation-request.js +167 -0
- package/dist/tools/trigger-long-running-operation.js +36 -0
- package/dist/tools/trigger-sampling-request-async.js +112 -0
- package/dist/tools/trigger-sampling-request.js +39 -0
- package/dist/tools/trigger-url-elicitation.js +0 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @mcpflo/server-everything
|
|
2
|
+
|
|
3
|
+
A deterministic, stdio-only MCP test-fixture server, built for stress-testing
|
|
4
|
+
[MCPFlo](../..) — a desktop testing tool for MCP servers. It exercises the
|
|
5
|
+
full protocol surface (tools, resources, prompts, elicitation, sampling,
|
|
6
|
+
tasks, logging, subscriptions) so MCPFlo's own UI has something real to
|
|
7
|
+
render against, beyond the official
|
|
8
|
+
[`@modelcontextprotocol/server-everything`](https://github.com/modelcontextprotocol/servers/tree/main/src/everything)
|
|
9
|
+
it was originally seeded with.
|
|
10
|
+
|
|
11
|
+
Every tool's description ends with "Demo/test fixture." so a user connecting
|
|
12
|
+
to this server doesn't mistake an intentional failure, delay, or capability
|
|
13
|
+
mismatch for a bug in MCPFlo itself.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run build
|
|
19
|
+
node dist/index.js
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or point any MCP client at it directly via stdio — no configuration required,
|
|
23
|
+
every tool has sane defaults for its arguments.
|
|
24
|
+
|
|
25
|
+
## What's here
|
|
26
|
+
|
|
27
|
+
**Tools** — 19, covering every tool in the upstream reference server
|
|
28
|
+
(including capability-gated ones like `get-roots-list` and
|
|
29
|
+
`trigger-url-elicitation`), plus fixes for several real bugs found while
|
|
30
|
+
porting them (see `docs/` for specifics). Capability-gated tools are
|
|
31
|
+
registered from the SDK's `oninitialized` hook, not eagerly — they only
|
|
32
|
+
appear in `tools/list` for clients that actually declare the capability they
|
|
33
|
+
need, matching upstream's own behavior exactly.
|
|
34
|
+
|
|
35
|
+
**Prompts** — 4: a no-argument prompt, one with required/optional arguments,
|
|
36
|
+
one with dependent argument completion, and one that embeds a resource.
|
|
37
|
+
|
|
38
|
+
**Resources** — 7 static documentation files (this package's own `docs/`,
|
|
39
|
+
describing its architecture, features, and how to extend it) plus 2 dynamic
|
|
40
|
+
resource templates (`mcpflo://dynamic/text/{resourceId}`,
|
|
41
|
+
`mcpflo://dynamic/blob/{resourceId}`) that regenerate content with a live
|
|
42
|
+
timestamp on every read and are deliberately excluded from `resources/list`.
|
|
43
|
+
|
|
44
|
+
Read [`docs/architecture.md`](docs/architecture.md) and
|
|
45
|
+
[`docs/extension.md`](docs/extension.md) for the actual code layout and how
|
|
46
|
+
to add a new tool or resource — those docs are themselves served by this
|
|
47
|
+
server, so they're worth reading through a connected client too.
|
|
48
|
+
|
|
49
|
+
## Testing
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm test
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
61 tests across 26 files, one test file per tool/prompt/resource module,
|
|
56
|
+
using the MCP SDK's `InMemoryTransport` to connect a real `Client` to a real
|
|
57
|
+
server instance in-process — no spawned child processes, no mocking of
|
|
58
|
+
protocol behavior. Most of the suite runs in milliseconds; a handful of tests
|
|
59
|
+
that verify actual timing/polling behavior (`simulate-research-query`, the
|
|
60
|
+
async trigger tools) deliberately use real timers and take a few seconds
|
|
61
|
+
each.
|
|
62
|
+
|
|
63
|
+
## Design notes
|
|
64
|
+
|
|
65
|
+
- **Deterministic by default.** No Playwright, no browser automation, no
|
|
66
|
+
live network calls — with one intentional exception: `gzip-file-as-resource`
|
|
67
|
+
defaults to fetching a real URL, kept for parity with the upstream
|
|
68
|
+
reference server it was ported from.
|
|
69
|
+
- **Tools/resources are added one at a time, by hand.** There's no bulk
|
|
70
|
+
generator, even for template-shaped families (see `docs/extension.md`).
|
|
71
|
+
- **Not yet wired as MCPFlo's seeded default server.** Currently a
|
|
72
|
+
workspace-local devDependency, exercised by this package's own test suite.
|
|
73
|
+
If it's ever seeded for real end users, it should launch a `sane`/`benign`
|
|
74
|
+
preset, not the full adversarial catalog — that preset system doesn't
|
|
75
|
+
exist yet.
|
|
76
|
+
|
|
77
|
+
## Publishing
|
|
78
|
+
|
|
79
|
+
This package declares `mcpName` for eventual MCP registry publication, in
|
|
80
|
+
addition to standard npm publishing. Neither has happened yet. When it does,
|
|
81
|
+
pin the exact version anywhere this package is spawned via `npx` (e.g. in
|
|
82
|
+
MCPFlo's own seeded server config) rather than leaving it unpinned — a new
|
|
83
|
+
publish should only reach users through a reviewed MCPFlo release, not
|
|
84
|
+
silently on next connect.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createServer = createServer;
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/experimental/tasks/index.js");
|
|
6
|
+
const index_1 = require("./tools/index");
|
|
7
|
+
const session_1 = require("./resources/session");
|
|
8
|
+
const logging_1 = require("./server/logging");
|
|
9
|
+
const subscriptions_1 = require("./resources/subscriptions");
|
|
10
|
+
const index_2 = require("./resources/index");
|
|
11
|
+
const index_3 = require("./prompts/index");
|
|
12
|
+
// Builds a fully-configured server, not yet connected to any transport.
|
|
13
|
+
// Shared by index.ts (stdio, real usage) and the test harness (in-memory).
|
|
14
|
+
function createServer() {
|
|
15
|
+
const server = new mcp_js_1.McpServer({
|
|
16
|
+
name: '@mcpflo/server-everything',
|
|
17
|
+
version: '0.0.1'
|
|
18
|
+
}, {
|
|
19
|
+
taskStore: new index_js_1.InMemoryTaskStore(),
|
|
20
|
+
instructions: (0, index_2.readInstructions)(),
|
|
21
|
+
// Unlike tools/resources, task-creation support isn't auto-declared by
|
|
22
|
+
// registering a task tool — it must be advertised explicitly here. Same
|
|
23
|
+
// for resources.subscribe: registering a resource never implies it.
|
|
24
|
+
capabilities: {
|
|
25
|
+
tasks: { requests: { tools: { call: {} } } },
|
|
26
|
+
logging: {},
|
|
27
|
+
resources: { subscribe: true }
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
(0, index_1.registerTools)(server);
|
|
31
|
+
(0, session_1.initSessionResources)(server);
|
|
32
|
+
(0, logging_1.registerLoggingCapability)(server);
|
|
33
|
+
(0, subscriptions_1.registerSubscriptionsCapability)(server);
|
|
34
|
+
(0, index_2.registerResources)(server);
|
|
35
|
+
(0, index_3.registerPrompts)(server);
|
|
36
|
+
// Capability-gated tools are registered here, once the client's initialize
|
|
37
|
+
// handshake has completed and its declared capabilities are actually known.
|
|
38
|
+
server.server.oninitialized = () => {
|
|
39
|
+
(0, index_1.registerConditionalTools)(server);
|
|
40
|
+
};
|
|
41
|
+
return server;
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`@mcpflo/server-everything` is a single stdio MCP server (`src/index.ts`) built
|
|
4
|
+
from small, individually-registered pieces:
|
|
5
|
+
|
|
6
|
+
- `src/tools/` — one file per tool, each exporting a `register<Name>(server)`
|
|
7
|
+
function that calls `server.registerTool(...)` directly. `src/tools/index.ts`
|
|
8
|
+
is a barrel that aggregates them into `registerTools(server)`.
|
|
9
|
+
- `src/resources/` — resource-side infrastructure: static file resources
|
|
10
|
+
(this doc set), session-scoped dynamic resources (e.g. gzip output),
|
|
11
|
+
templated resources, and the subscribe/unsubscribe handlers.
|
|
12
|
+
- `src/server/` — server-wide capability plumbing that isn't resource- or
|
|
13
|
+
tool-specific, e.g. the simulated logging level tracking.
|
|
14
|
+
|
|
15
|
+
Tools and resources are added one at a time by hand — there is no bulk
|
|
16
|
+
generator. `index.ts` wires everything together: constructs the `McpServer`,
|
|
17
|
+
declares capabilities that aren't auto-detected by the SDK (`tasks`,
|
|
18
|
+
`logging`, `resources.subscribe`), registers all tools/resources, then
|
|
19
|
+
connects over stdio.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Extending This Server
|
|
2
|
+
|
|
3
|
+
To add a new tool:
|
|
4
|
+
|
|
5
|
+
1. Create `src/tools/my-new-tool.ts`, exporting `registerMyNewTool(server)`
|
|
6
|
+
that calls `server.registerTool(...)` directly (raw-shape zod for
|
|
7
|
+
`inputSchema`, not a wrapped `z.object(...)`, to match the rest of this
|
|
8
|
+
codebase).
|
|
9
|
+
2. Append its description text with `Demo/test fixture.` so a real user
|
|
10
|
+
connecting to this server understands intentional failures/delays aren't
|
|
11
|
+
a bug.
|
|
12
|
+
3. Import and add it to the array in `src/tools/index.ts`.
|
|
13
|
+
4. Rebuild (`npm run build`) and manually verify with a raw JSON-RPC smoke
|
|
14
|
+
test over stdio before wiring it into MCPFlo or the e2e suite.
|
|
15
|
+
|
|
16
|
+
To add a new resource, follow the same one-file-per-item pattern under
|
|
17
|
+
`src/resources/`. If it needs a capability the SDK doesn't auto-declare
|
|
18
|
+
(check by trying it first — `tasks`, `logging`, and `resources.subscribe`
|
|
19
|
+
all needed this), add it explicitly to the `capabilities` object in
|
|
20
|
+
`src/index.ts`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Features
|
|
2
|
+
|
|
3
|
+
This server exercises a broad slice of the MCP protocol, deliberately kept
|
|
4
|
+
deterministic — no real chaos, no live browser automation, one exception
|
|
5
|
+
(the gzip tool's default network fetch, kept for compatibility with the
|
|
6
|
+
upstream reference server it was ported from).
|
|
7
|
+
|
|
8
|
+
- **Tools**: primitives, structured content, resource links, binary content,
|
|
9
|
+
progress notifications, cancellation-safe long-running work.
|
|
10
|
+
- **Resources**: static file resources, session-scoped dynamic resources,
|
|
11
|
+
subscribe/unsubscribe with simulated update notifications.
|
|
12
|
+
- **Elicitation**: form-mode and URL-mode, both request-path
|
|
13
|
+
(`elicitation/create`) and error-path (`UrlElicitationRequiredError`).
|
|
14
|
+
- **Sampling**: server-initiated `sampling/createMessage`, synchronous and
|
|
15
|
+
task-based (bidirectional MCP Tasks).
|
|
16
|
+
- **Tasks (SEP-1686)**: long-running tool execution via `tasks/get` and
|
|
17
|
+
`tasks/result`, including a mid-flight elicitation pause
|
|
18
|
+
(`input_required`).
|
|
19
|
+
- **Logging**: `logging/setLevel` with real RFC 5424 severity filtering.
|
|
20
|
+
|
|
21
|
+
Every tool's description ends with a "Demo/test fixture" note so a user
|
|
22
|
+
connecting to this server doesn't mistake intentional failures or delays for
|
|
23
|
+
a bug in MCPFlo itself.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# How It Works
|
|
2
|
+
|
|
3
|
+
A single Node process talks MCP over stdio — no HTTP, no browser, no
|
|
4
|
+
external services except where a tool's whole purpose is to demonstrate one
|
|
5
|
+
(the gzip tool's optional network fetch).
|
|
6
|
+
|
|
7
|
+
Request lifecycle for a typical tool call:
|
|
8
|
+
|
|
9
|
+
1. Client sends `tools/call`.
|
|
10
|
+
2. The registered handler runs, using only what the SDK gives it
|
|
11
|
+
(`args`, `extra.sendRequest`, `extra.sessionId`, etc.) — no shared mutable
|
|
12
|
+
state between unrelated tools.
|
|
13
|
+
3. Tools that need server-initiated round trips (elicitation, sampling) call
|
|
14
|
+
`extra.sendRequest(...)` and await the client's reply on the same
|
|
15
|
+
connection.
|
|
16
|
+
4. Tools that register a resource at call time (e.g. gzip output) do so
|
|
17
|
+
through `server.registerResource(...)`, which triggers
|
|
18
|
+
`notifications/resources/list_changed` automatically.
|
|
19
|
+
|
|
20
|
+
Several capabilities are intentionally *not* auto-declared by the SDK and
|
|
21
|
+
have to be turned on explicitly at server construction: `tasks`, `logging`,
|
|
22
|
+
and `resources.subscribe`. See `architecture.md` for where that happens.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
Connect to this server the same way as any stdio MCP server: spawn
|
|
4
|
+
`node dist/index.js` (or the published `mcpflo-server-everything` bin) and
|
|
5
|
+
speak MCP over its stdin/stdout.
|
|
6
|
+
|
|
7
|
+
Nothing here requires configuration to get started — every tool has sane
|
|
8
|
+
defaults for its arguments. A few tools depend on the connecting client's
|
|
9
|
+
declared capabilities and will return a clear `isError` message instead of
|
|
10
|
+
failing silently if the client doesn't support what they need:
|
|
11
|
+
|
|
12
|
+
- `trigger-elicitation-request` / `-async` need `capabilities.elicitation`.
|
|
13
|
+
- `trigger-url-elicitation` needs `capabilities.elicitation.url`.
|
|
14
|
+
- `trigger-sampling-request` / `-async` need `capabilities.sampling`.
|
|
15
|
+
|
|
16
|
+
Everything else works with a bare-minimum client.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Startup
|
|
2
|
+
|
|
3
|
+
On launch, before the transport connects:
|
|
4
|
+
|
|
5
|
+
1. The `McpServer` is constructed with an in-memory task store and explicit
|
|
6
|
+
`tasks`/`logging`/`resources.subscribe` capabilities (none of these are
|
|
7
|
+
inferred automatically by the SDK from registering a tool or resource).
|
|
8
|
+
2. All tools register themselves via `registerTools(server)`.
|
|
9
|
+
3. A throwaway resource is registered and immediately disabled
|
|
10
|
+
(`initSessionResources`) — purely to work around an SDK quirk where the
|
|
11
|
+
very first `registerResource()` call tries to finalize the resources
|
|
12
|
+
capability, which throws if it happens after `connect()`. Warming it up
|
|
13
|
+
here means later, real dynamic resource registrations (e.g. from the
|
|
14
|
+
gzip tool) work without special-casing.
|
|
15
|
+
4. Logging (`registerLoggingCapability`) and subscription
|
|
16
|
+
(`registerSubscriptionsCapability`) request handlers are wired up.
|
|
17
|
+
5. The server connects over stdio and starts serving requests.
|
|
18
|
+
|
|
19
|
+
There is no seeding of demo data beyond this file-resource set — everything
|
|
20
|
+
else appears only when a tool creates it.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Structure
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
packages/server-everything/
|
|
5
|
+
├── docs/ this file set — copied into dist/ at build time
|
|
6
|
+
├── src/
|
|
7
|
+
│ ├── index.ts server construction, capability wiring, connect
|
|
8
|
+
│ ├── tools/ one file per tool + a barrel (index.ts)
|
|
9
|
+
│ ├── resources/
|
|
10
|
+
│ │ ├── templates.ts static text/blob resource-reference helpers
|
|
11
|
+
│ │ ├── session.ts dynamic session-scoped resource registration
|
|
12
|
+
│ │ ├── subscriptions.ts resources/subscribe + simulated update loop
|
|
13
|
+
│ │ └── file-resources.ts registers this docs/ set as static resources
|
|
14
|
+
│ └── server/
|
|
15
|
+
│ └── logging.ts logging/setLevel + simulated log messages
|
|
16
|
+
├── package.json
|
|
17
|
+
└── tsconfig.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
New tools and resources are added one file at a time, then wired into the
|
|
21
|
+
relevant barrel — never generated in bulk.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const createServer_1 = require("./createServer");
|
|
6
|
+
const server = (0, createServer_1.createServer)();
|
|
7
|
+
server.connect(new stdio_js_1.StdioServerTransport()).catch((error) => {
|
|
8
|
+
console.error(error);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerArgumentsPrompt = registerArgumentsPrompt;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
function registerArgumentsPrompt(server) {
|
|
6
|
+
server.registerPrompt('args-prompt', {
|
|
7
|
+
title: 'Arguments Prompt',
|
|
8
|
+
description: 'A prompt with two arguments, one required and one optional. Demo/test fixture.',
|
|
9
|
+
argsSchema: {
|
|
10
|
+
city: zod_1.z.string().describe('Name of the city'),
|
|
11
|
+
state: zod_1.z.string().optional().describe('Name of the state')
|
|
12
|
+
}
|
|
13
|
+
}, (args) => {
|
|
14
|
+
const location = `${args.city}${args.state ? `, ${args.state}` : ''}`;
|
|
15
|
+
return {
|
|
16
|
+
messages: [{ role: 'user', content: { type: 'text', text: `What's weather in ${location}?` } }]
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerPromptWithCompletions = registerPromptWithCompletions;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const completable_js_1 = require("@modelcontextprotocol/sdk/server/completable.js");
|
|
6
|
+
const DEPARTMENT_MEMBERS = {
|
|
7
|
+
Engineering: ['Alice', 'Bob', 'Charlie'],
|
|
8
|
+
Sales: ['David', 'Eve', 'Frank'],
|
|
9
|
+
Marketing: ['Grace', 'Henry', 'Iris'],
|
|
10
|
+
Support: ['John', 'Kim', 'Lee']
|
|
11
|
+
};
|
|
12
|
+
function registerPromptWithCompletions(server) {
|
|
13
|
+
server.registerPrompt('completable-prompt', {
|
|
14
|
+
title: 'Team Management',
|
|
15
|
+
description: 'First argument choice narrows values for second argument. Demo/test fixture.',
|
|
16
|
+
argsSchema: {
|
|
17
|
+
department: (0, completable_js_1.completable)(zod_1.z.string().describe('Choose the department.'), (value) => ['Engineering', 'Sales', 'Marketing', 'Support'].filter((d) => d.startsWith(value))),
|
|
18
|
+
name: (0, completable_js_1.completable)(zod_1.z.string().describe('Choose a team member to lead the selected department.'), (value, context) => {
|
|
19
|
+
const department = context?.arguments?.['department'];
|
|
20
|
+
const members = department ? (DEPARTMENT_MEMBERS[department] ?? []) : [];
|
|
21
|
+
return members.filter((n) => n.startsWith(value));
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}, ({ department, name }) => ({
|
|
25
|
+
messages: [
|
|
26
|
+
{
|
|
27
|
+
role: 'user',
|
|
28
|
+
content: { type: 'text', text: `Please promote ${name} to the head of the ${department} team.` }
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerPrompts = registerPrompts;
|
|
4
|
+
const simple_1 = require("./simple");
|
|
5
|
+
const args_1 = require("./args");
|
|
6
|
+
const completions_1 = require("./completions");
|
|
7
|
+
const resource_1 = require("./resource");
|
|
8
|
+
// Add one entry per prompt file here as prompts are added, one at a time.
|
|
9
|
+
const registerFns = [
|
|
10
|
+
simple_1.registerSimplePrompt,
|
|
11
|
+
args_1.registerArgumentsPrompt,
|
|
12
|
+
completions_1.registerPromptWithCompletions,
|
|
13
|
+
resource_1.registerEmbeddedResourcePrompt
|
|
14
|
+
];
|
|
15
|
+
function registerPrompts(server) {
|
|
16
|
+
for (const register of registerFns) {
|
|
17
|
+
register(server);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerEmbeddedResourcePrompt = registerEmbeddedResourcePrompt;
|
|
4
|
+
const templates_1 = require("../resources/templates");
|
|
5
|
+
function registerEmbeddedResourcePrompt(server) {
|
|
6
|
+
server.registerPrompt('resource-prompt', {
|
|
7
|
+
title: 'Resource Prompt',
|
|
8
|
+
description: 'A prompt that includes an embedded resource reference. Demo/test fixture.',
|
|
9
|
+
argsSchema: {
|
|
10
|
+
resourceType: templates_1.resourceTypeCompleter,
|
|
11
|
+
resourceId: templates_1.resourceIdForPromptCompleter
|
|
12
|
+
}
|
|
13
|
+
}, (args) => {
|
|
14
|
+
const resourceId = Number(args.resourceId);
|
|
15
|
+
if (!Number.isFinite(resourceId) || !Number.isInteger(resourceId) || resourceId < 1) {
|
|
16
|
+
throw new Error(`Invalid resourceId: ${args.resourceId}. Must be a finite positive integer.`);
|
|
17
|
+
}
|
|
18
|
+
const uri = args.resourceType === templates_1.RESOURCE_TYPE_TEXT ? (0, templates_1.textResourceUri)(resourceId) : (0, templates_1.blobResourceUri)(resourceId);
|
|
19
|
+
const resource = args.resourceType === templates_1.RESOURCE_TYPE_TEXT
|
|
20
|
+
? (0, templates_1.textResource)(uri, resourceId)
|
|
21
|
+
: (0, templates_1.blobResource)(uri, resourceId);
|
|
22
|
+
return {
|
|
23
|
+
messages: [
|
|
24
|
+
{
|
|
25
|
+
role: 'user',
|
|
26
|
+
content: {
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: `This prompt includes the ${args.resourceType} resource with id: ${resourceId}. Please analyze the following resource:`
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{ role: 'user', content: { type: 'resource', resource } }
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSimplePrompt = registerSimplePrompt;
|
|
4
|
+
function registerSimplePrompt(server) {
|
|
5
|
+
server.registerPrompt('simple-prompt', {
|
|
6
|
+
title: 'Simple Prompt',
|
|
7
|
+
description: 'A prompt with no arguments. Demo/test fixture.'
|
|
8
|
+
}, () => ({
|
|
9
|
+
messages: [
|
|
10
|
+
{
|
|
11
|
+
role: 'user',
|
|
12
|
+
content: { type: 'text', text: 'This is a simple prompt without arguments.' }
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveDocsDir = resolveDocsDir;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
// The build script copies docs/ into dist/docs so the published package is
|
|
7
|
+
// self-contained (dist/resources/../docs resolves correctly at runtime).
|
|
8
|
+
// Vitest runs directly against src/ instead of the compiled output, where
|
|
9
|
+
// that same relative path would land on a nonexistent src/docs — the real
|
|
10
|
+
// docs/ is one level further up, at the package root. Rather than
|
|
11
|
+
// duplicating docs/ into src/ just for tests, resolve whichever actually
|
|
12
|
+
// exists.
|
|
13
|
+
function resolveDocsDir(fromDir) {
|
|
14
|
+
const distRelative = (0, path_1.join)(fromDir, '..', 'docs');
|
|
15
|
+
if ((0, fs_1.existsSync)(distRelative))
|
|
16
|
+
return distRelative;
|
|
17
|
+
return (0, path_1.join)(fromDir, '..', '..', 'docs');
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerFileResources = registerFileResources;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const docsDir_1 = require("./docsDir");
|
|
7
|
+
function getMimeType(fileName) {
|
|
8
|
+
const lower = fileName.toLowerCase();
|
|
9
|
+
if (lower.endsWith('.md') || lower.endsWith('.markdown'))
|
|
10
|
+
return 'text/markdown';
|
|
11
|
+
if (lower.endsWith('.txt'))
|
|
12
|
+
return 'text/plain';
|
|
13
|
+
if (lower.endsWith('.json'))
|
|
14
|
+
return 'application/json';
|
|
15
|
+
return 'text/plain';
|
|
16
|
+
}
|
|
17
|
+
function readFileSafe(path) {
|
|
18
|
+
try {
|
|
19
|
+
return (0, fs_1.readFileSync)(path, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return `Error reading file: ${path}. ${error}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function registerFileResources(server) {
|
|
26
|
+
// __dirname is a real CommonJS module-scope binding here, no import.meta
|
|
27
|
+
// shim needed.
|
|
28
|
+
const docsDir = (0, docsDir_1.resolveDocsDir)(__dirname);
|
|
29
|
+
let entries = [];
|
|
30
|
+
try {
|
|
31
|
+
entries = (0, fs_1.readdirSync)(docsDir);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const name of entries) {
|
|
37
|
+
const fullPath = (0, path_1.join)(docsDir, name);
|
|
38
|
+
try {
|
|
39
|
+
if (!(0, fs_1.statSync)(fullPath).isFile())
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const uri = `mcpflo://static/document/${encodeURIComponent(name)}`;
|
|
46
|
+
const mimeType = getMimeType(name);
|
|
47
|
+
const description = `Static document file exposed from docs/: ${name}`;
|
|
48
|
+
server.registerResource(name, uri, { mimeType, description }, async (readUri) => ({
|
|
49
|
+
contents: [{ uri: readUri.toString(), mimeType, text: readFileSafe(fullPath) }]
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerResources = registerResources;
|
|
4
|
+
exports.readInstructions = readInstructions;
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const templates_1 = require("./templates");
|
|
8
|
+
const file_resources_1 = require("./file-resources");
|
|
9
|
+
const docsDir_1 = require("./docsDir");
|
|
10
|
+
function registerResources(server) {
|
|
11
|
+
(0, templates_1.registerResourceTemplates)(server);
|
|
12
|
+
(0, file_resources_1.registerFileResources)(server);
|
|
13
|
+
}
|
|
14
|
+
function readInstructions() {
|
|
15
|
+
// __dirname is a real CommonJS module-scope binding here, no import.meta
|
|
16
|
+
// shim needed.
|
|
17
|
+
const filePath = (0, path_1.join)((0, docsDir_1.resolveDocsDir)(__dirname), 'instructions.md');
|
|
18
|
+
try {
|
|
19
|
+
return (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return `Server instructions not loaded: ${error}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initSessionResources = initSessionResources;
|
|
4
|
+
exports.getSessionResourceURI = getSessionResourceURI;
|
|
5
|
+
exports.registerSessionResource = registerSessionResource;
|
|
6
|
+
let counter = 0;
|
|
7
|
+
// McpServer.registerResource() calls the SDK's registerCapabilities() on its
|
|
8
|
+
// very first-ever invocation, which throws once server.connect() has already
|
|
9
|
+
// run. Tools register session resources at call-time, which is always
|
|
10
|
+
// post-connect, so we register (and immediately disable) one throwaway
|
|
11
|
+
// resource before connect to trip that one-time init path early. Disabling
|
|
12
|
+
// it hides it from resources/list without undoing the warm-up.
|
|
13
|
+
function initSessionResources(server) {
|
|
14
|
+
server.registerResource('__session_warmup__', 'mcpflo://session/warmup', { mimeType: 'text/plain' }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: 'text/plain', text: '' }] })).disable();
|
|
15
|
+
}
|
|
16
|
+
function getSessionResourceURI(name) {
|
|
17
|
+
counter += 1;
|
|
18
|
+
return `mcpflo://session/resource/${counter}/${encodeURIComponent(name)}`;
|
|
19
|
+
}
|
|
20
|
+
function registerSessionResource(server, resource, kind, data) {
|
|
21
|
+
server.registerResource(resource.name, resource.uri, { mimeType: resource.mimeType }, async (uri) => ({
|
|
22
|
+
contents: [
|
|
23
|
+
kind === 'blob'
|
|
24
|
+
? { uri: uri.href, mimeType: resource.mimeType, blob: data }
|
|
25
|
+
: { uri: uri.href, mimeType: resource.mimeType, text: data }
|
|
26
|
+
]
|
|
27
|
+
}));
|
|
28
|
+
return {
|
|
29
|
+
type: 'resource_link',
|
|
30
|
+
uri: resource.uri,
|
|
31
|
+
name: resource.name,
|
|
32
|
+
mimeType: resource.mimeType
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSubscriptionsCapability = registerSubscriptionsCapability;
|
|
4
|
+
exports.beginSimulatedResourceUpdates = beginSimulatedResourceUpdates;
|
|
5
|
+
exports.stopSimulatedResourceUpdates = stopSimulatedResourceUpdates;
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const INTERVAL_MS = 5000;
|
|
8
|
+
const sessionSubscriptions = new Map();
|
|
9
|
+
const sessionTimers = new Map();
|
|
10
|
+
function registerSubscriptionsCapability(server) {
|
|
11
|
+
server.server.setRequestHandler(types_js_1.SubscribeRequestSchema, async (request, extra) => {
|
|
12
|
+
const uris = sessionSubscriptions.get(extra.sessionId) ?? new Set();
|
|
13
|
+
uris.add(request.params.uri);
|
|
14
|
+
sessionSubscriptions.set(extra.sessionId, uris);
|
|
15
|
+
return {};
|
|
16
|
+
});
|
|
17
|
+
server.server.setRequestHandler(types_js_1.UnsubscribeRequestSchema, async (request, extra) => {
|
|
18
|
+
sessionSubscriptions.get(extra.sessionId)?.delete(request.params.uri);
|
|
19
|
+
return {};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function beginSimulatedResourceUpdates(server, sessionId) {
|
|
23
|
+
const timer = setInterval(() => {
|
|
24
|
+
const uris = sessionSubscriptions.get(sessionId);
|
|
25
|
+
if (!uris || uris.size === 0)
|
|
26
|
+
return;
|
|
27
|
+
for (const uri of uris) {
|
|
28
|
+
server.server.sendResourceUpdated({ uri }).catch(() => { });
|
|
29
|
+
}
|
|
30
|
+
}, INTERVAL_MS);
|
|
31
|
+
sessionTimers.set(sessionId, timer);
|
|
32
|
+
}
|
|
33
|
+
function stopSimulatedResourceUpdates(sessionId) {
|
|
34
|
+
const timer = sessionTimers.get(sessionId);
|
|
35
|
+
if (timer)
|
|
36
|
+
clearInterval(timer);
|
|
37
|
+
sessionTimers.delete(sessionId);
|
|
38
|
+
}
|