@pdpp/mcp-server 0.0.0 → 0.1.0

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/src/server.js ADDED
@@ -0,0 +1,162 @@
1
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
4
+
5
+ import {
6
+ buildStreamResourceTemplate,
7
+ buildTools,
8
+ PDPP_MCP_TOOL_NAMES,
9
+ } from './tools.js';
10
+ import { RsClient } from './rs-client.js';
11
+
12
+ export const DEFAULT_SERVER_NAME = 'pdpp-mcp-server';
13
+ export const DEFAULT_SERVER_VERSION = '0.0.0';
14
+
15
+ // Shared MCP server instructions. The first 512 characters must be
16
+ // self-contained for ChatGPT and Codex (OpenAI Apps SDK guidance).
17
+ // Cross-tool details that would otherwise repeat across tool descriptions live
18
+ // here; tool descriptions stay concise and routing-specific.
19
+ export const PDPP_MCP_INSTRUCTIONS =
20
+ 'PDPP tools are grant-scoped. Start with `schema`, then call `schema(stream)` after choosing a stream; add `connection_id` when a stream name appears under multiple sources or before full schema. Use `connection_id` from schema results or `available_connections` errors to disambiguate sources. Filters must be typed objects, not bracket strings. Page and narrow with `limit`, `cursor`, and `fields`; prefer `aggregate` or lexical `search` for exact terms. ' +
21
+ 'The configured bearer limits every result; do not use owner or control-plane tokens for normal MCP access. Schema advertises valid fields, filter operators, expand relations, sort/count support, connection identities, and connector keys. Persist `connection_id`, not `grant_id`, across reconnects. ' +
22
+ '`content[]` is the reliable model-visible guide and includes next cursors/bookmarks when present; `structuredContent` is a host-dependent machine envelope, not the only place to find next-step handles.';
23
+
24
+ /**
25
+ * Build an MCP server wired to a PDPP resource server through the supplied scoped token.
26
+ *
27
+ * The server registers the profile-free normal PDPP read surface plus one resource
28
+ * template. It does not auto-connect to a transport — callers pass the transport
29
+ * explicitly so tests can use the in-memory pair and CLI use can pass
30
+ * StdioServerTransport.
31
+ */
32
+ export function createPdppMcpServer({
33
+ providerUrl,
34
+ accessToken,
35
+ rsClient,
36
+ fetch = globalThis.fetch,
37
+ serverName = DEFAULT_SERVER_NAME,
38
+ serverVersion = DEFAULT_SERVER_VERSION,
39
+ serverIcons,
40
+ }) {
41
+ // Callers may inject a custom RsClient-compatible adapter (e.g. the hosted
42
+ // adapter's PackageRsClient fan-out). Otherwise we build a single-bearer
43
+ // RsClient from the supplied accessToken.
44
+ const rs = rsClient ?? new RsClient({ providerUrl, accessToken, fetch });
45
+ const serverInfo = { name: serverName, version: serverVersion };
46
+ if (Array.isArray(serverIcons) && serverIcons.length > 0) {
47
+ serverInfo.icons = serverIcons;
48
+ }
49
+ const server = new McpServer(serverInfo, {
50
+ instructions: PDPP_MCP_INSTRUCTIONS,
51
+ });
52
+
53
+ const tools = buildTools({ rs, providerUrl });
54
+ for (const tool of tools) {
55
+ const config = {
56
+ title: tool.title,
57
+ description: tool.description,
58
+ annotations: tool.annotations,
59
+ inputSchema: tool.inputSchema,
60
+ };
61
+ if (tool.outputSchema) {
62
+ config.outputSchema = tool.outputSchema;
63
+ }
64
+ server.registerTool(
65
+ tool.name,
66
+ config,
67
+ async (args) => {
68
+ try {
69
+ return await tool.handler(args ?? {});
70
+ } catch (error) {
71
+ return toolHandlerError(error);
72
+ }
73
+ }
74
+ );
75
+ }
76
+
77
+ const streamTemplate = buildStreamResourceTemplate({ rs, providerUrl });
78
+ server.registerResource(
79
+ streamTemplate.name,
80
+ new ResourceTemplate(streamTemplate.uriTemplate, { list: undefined }),
81
+ {
82
+ title: streamTemplate.title,
83
+ description: streamTemplate.description,
84
+ mimeType: streamTemplate.mimeType,
85
+ },
86
+ async (uri, variables) => {
87
+ return await streamTemplate.read(uri.href ?? String(uri), variables);
88
+ }
89
+ );
90
+
91
+ return { server, rs };
92
+ }
93
+
94
+ export async function startStdioServer(options) {
95
+ const { server } = createPdppMcpServer(options);
96
+ const transport = new StdioServerTransport();
97
+ const closed = new Promise((resolve) => {
98
+ const prior = transport.onclose;
99
+ transport.onclose = () => {
100
+ try {
101
+ prior?.();
102
+ } finally {
103
+ resolve();
104
+ }
105
+ };
106
+ });
107
+ await server.connect(transport);
108
+ return { server, transport, closed };
109
+ }
110
+
111
+ /**
112
+ * Handle one hosted MCP Streamable HTTP request in stateless mode.
113
+ *
114
+ * The caller owns authentication and should pass an already-authorized scoped client
115
+ * bearer as accessToken. A fresh MCP server and transport are created per request so
116
+ * authorization state is never cached in an MCP session.
117
+ */
118
+ export async function handleStreamableHttpRequest(request, options) {
119
+ const { server } = createPdppMcpServer(options);
120
+ const transport = new WebStandardStreamableHTTPServerTransport({
121
+ sessionIdGenerator: undefined,
122
+ enableJsonResponse: true,
123
+ });
124
+
125
+ try {
126
+ await server.connect(transport);
127
+ return await transport.handleRequest(request);
128
+ } finally {
129
+ await Promise.allSettled([transport.close(), server.close()]);
130
+ }
131
+ }
132
+
133
+ function toolHandlerError(error) {
134
+ return {
135
+ isError: true,
136
+ content: [
137
+ {
138
+ type: 'text',
139
+ text: JSON.stringify(
140
+ {
141
+ type: 'adapter_error',
142
+ code: error?.code ?? 'tool_handler_error',
143
+ message: error?.message ?? 'Tool handler threw an error',
144
+ },
145
+ null,
146
+ 2
147
+ ),
148
+ },
149
+ ],
150
+ structuredContent: {
151
+ error: {
152
+ type: 'adapter_error',
153
+ code: error?.code ?? 'tool_handler_error',
154
+ message: error?.message ?? 'Tool handler threw an error',
155
+ },
156
+ },
157
+ };
158
+ }
159
+
160
+ export {
161
+ PDPP_MCP_TOOL_NAMES,
162
+ } from './tools.js';