@surph_ai/sdk 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/auth/index.js +147 -0
- package/base/vinely-base/.nvmrc +1 -0
- package/base/vinely-base/README.md +30 -0
- package/base/vinely-base/app.js +5 -0
- package/base/vinely-base/declarations.d.ts +1 -0
- package/base/vinely-base/package.json +103 -0
- package/base/vinely-base/public/css/globals-2.css +3699 -0
- package/base/vinely-base/public/css/globals.css +3695 -0
- package/base/vinely-base/public/js/app.js +1 -0
- package/base/vinely-base/public/js/jquery.js +2 -0
- package/base/vinely-base/scratch.js +21 -0
- package/base/vinely-base/src/client/components/app-sidebar.tsx +59 -0
- package/base/vinely-base/src/client/components/assistant-ui/markdown-text.tsx +153 -0
- package/base/vinely-base/src/client/components/assistant-ui/thread-list.tsx +67 -0
- package/base/vinely-base/src/client/components/assistant-ui/thread.tsx +666 -0
- package/base/vinely-base/src/client/components/assistant-ui/tool-fallback.tsx +44 -0
- package/base/vinely-base/src/client/components/assistant-ui/tooltip-icon-button.tsx +44 -0
- package/base/vinely-base/src/client/components/ui/breadcrumb.tsx +109 -0
- package/base/vinely-base/src/client/components/ui/button.tsx +59 -0
- package/base/vinely-base/src/client/components/ui/input.tsx +21 -0
- package/base/vinely-base/src/client/components/ui/separator.tsx +28 -0
- package/base/vinely-base/src/client/components/ui/sheet.tsx +139 -0
- package/base/vinely-base/src/client/components/ui/sidebar.tsx +726 -0
- package/base/vinely-base/src/client/components/ui/skeleton.tsx +14 -0
- package/base/vinely-base/src/client/components/ui/tooltip.tsx +61 -0
- package/base/vinely-base/src/client/components/vines-sidebar.tsx +182 -0
- package/base/vinely-base/src/client/contexts/SessionContext.tsx +28 -0
- package/base/vinely-base/src/client/contexts/SessionReducer.tsx +32 -0
- package/base/vinely-base/src/client/hooks/use-mobile.ts +19 -0
- package/base/vinely-base/src/client/index.tsx +154 -0
- package/base/vinely-base/src/client/lib/API.ts +155 -0
- package/base/vinely-base/src/client/lib/RPC.ts +49 -0
- package/base/vinely-base/src/client/lib/utils.ts +96 -0
- package/base/vinely-base/src/server/index.ts +63 -0
- package/base/vinely-base/src/server/routes/api.ts +38 -0
- package/base/vinely-base/src/server/routes/chat.ts +133 -0
- package/base/vinely-base/src/server/routes/main.ts +36 -0
- package/base/vinely-base/src/server/routes/mcp.ts +52 -0
- package/base/vinely-base/src/server/routes/rpc.ts +64 -0
- package/base/vinely-base/src/server/routes/thread.ts +44 -0
- package/base/vinely-base/src/server/utils/APIMgr.ts +156 -0
- package/base/vinely-base/src/server/utils/Auth.ts +235 -0
- package/base/vinely-base/src/server/utils/fetchManifest.ts +15 -0
- package/base/vinely-base/src/server/utils/mcp-auth.ts +251 -0
- package/base/vinely-base/tsconfig.json +16 -0
- package/base/vinely-base/views/landing.html +187 -0
- package/base/vinely-base/views/partials/imports.dev.html +3 -0
- package/base/vinely-base/webpack.config.js +112 -0
- package/deploy/Dockerfile +9 -0
- package/deploy/index.js +244 -0
- package/index.js +266 -0
- package/package.json +28 -0
- package/scaffold/app/gulpfile.js +12 -0
- package/scaffold/app/package.json +24 -0
- package/scaffold/app/src/index.js +14 -0
- package/scaffold/index.js +171 -0
- package/scaffold/project/README.md +28 -0
- package/scaffold/project/app.js +20 -0
- package/scaffold/project/apps/README.md +1 -0
- package/scaffold/project/env.txt +4 -0
- package/scaffold/project/package-lock.json +2459 -0
- package/scaffold/project/package.json +21 -0
- package/scaffold/project/public/css/bootstrap.css +5926 -0
- package/scaffold/project/public/js/app.js +1 -0
- package/scaffold/project/public/js/jquery.js +2 -0
- package/scaffold/project/routes/index.js +8 -0
- package/scaffold/project/templates/index.mustache +20 -0
- package/utils/Realm.js +60 -0
- package/utils/http.js +22 -0
- package/utils/index.js +276 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import axios from "axios"
|
|
2
|
+
import { clsx, type ClassValue } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import RPC from "./RPC"
|
|
5
|
+
import API from "./API"
|
|
6
|
+
|
|
7
|
+
export function cn(...inputs: ClassValue[]) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UploadFileParams {
|
|
12
|
+
folder: string;
|
|
13
|
+
filedata: any;
|
|
14
|
+
filename: string;
|
|
15
|
+
filetype: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const uploadFile = async ({
|
|
19
|
+
folder,
|
|
20
|
+
filedata,
|
|
21
|
+
filename,
|
|
22
|
+
filetype
|
|
23
|
+
}: UploadFileParams) => {
|
|
24
|
+
try {
|
|
25
|
+
const { payload } = await RPC.call('s3', 'upload_url', {
|
|
26
|
+
folder,
|
|
27
|
+
filename,
|
|
28
|
+
filetype,
|
|
29
|
+
toStorage: 'false'
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const { response } = payload
|
|
33
|
+
const upload = response.uploadUrl
|
|
34
|
+
// console.log('UPLOAD URL: ' + upload)
|
|
35
|
+
|
|
36
|
+
// this returns an empty string ("")
|
|
37
|
+
const { data } = await axios({
|
|
38
|
+
url: upload,
|
|
39
|
+
method: 'PUT',
|
|
40
|
+
data: filedata,
|
|
41
|
+
headers: { 'Content-Type': filetype }
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return data
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw error
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const updateSpace = async (space: any, updates: Record<string, any>) => {
|
|
51
|
+
try {
|
|
52
|
+
const { payload } = await API.put(
|
|
53
|
+
`/space/${space._id}`,
|
|
54
|
+
updates,
|
|
55
|
+
null,
|
|
56
|
+
false
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return payload
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const fetchSpace = async (params: any) => {
|
|
66
|
+
try {
|
|
67
|
+
const url = params._id ? `/space/${params._id}` : '/space'
|
|
68
|
+
const { payload } = await API.get(url, params)
|
|
69
|
+
|
|
70
|
+
// console.log('FETCH SPACE: ' + JSON.stringify(payload))
|
|
71
|
+
|
|
72
|
+
return payload
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const fetchMessages = async (site: string, threadId: string) => {
|
|
79
|
+
try {
|
|
80
|
+
const url = `/thread/${site}/${threadId}`
|
|
81
|
+
const { data } = await axios({
|
|
82
|
+
url,
|
|
83
|
+
method: 'GET',
|
|
84
|
+
headers: { 'Content-Type': 'application/json' }
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// console.log('MESSAGES: ' + JSON.stringify(data))
|
|
88
|
+
if (data.error) {
|
|
89
|
+
throw new Error(data.error)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return data.payload
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw error
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import express, { Request, Response, NextFunction} from 'express'
|
|
2
|
+
import turbo360 from '@turbo360/turbo-sdk'
|
|
3
|
+
import main from './routes/main.js'
|
|
4
|
+
import chat from './routes/chat.js'
|
|
5
|
+
import mcp from './routes/mcp.js'
|
|
6
|
+
import thread from './routes/thread.js'
|
|
7
|
+
import rpc from './routes/rpc.js'
|
|
8
|
+
import api from './routes/api.js'
|
|
9
|
+
import fetchManifest from './utils/fetchManifest.js'
|
|
10
|
+
// import { sessionMiddleware, setupPassport } from './utils/Auth.js'
|
|
11
|
+
|
|
12
|
+
const ONE_DAY = 24 * 60 * 60 * 1000
|
|
13
|
+
|
|
14
|
+
const app = express()
|
|
15
|
+
|
|
16
|
+
turbo360.configureApp(app, {
|
|
17
|
+
views: 'views',
|
|
18
|
+
static: 'public',
|
|
19
|
+
viewEngine: 'mustache' // ejs or mustache
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// setupPassport(app)
|
|
23
|
+
// app.use(sessionMiddleware)
|
|
24
|
+
|
|
25
|
+
app.use(async (req: any, res: Response, next: NextFunction) => {
|
|
26
|
+
try {
|
|
27
|
+
req._vineyard = await fetchManifest()
|
|
28
|
+
// console.log('MANIFEST: ' + JSON.stringify(req._vineyard))
|
|
29
|
+
} catch (error) {
|
|
30
|
+
|
|
31
|
+
} finally {
|
|
32
|
+
next()
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// set routes
|
|
37
|
+
app.use('/api/chat', chat)
|
|
38
|
+
app.use('/api/mcp', mcp)
|
|
39
|
+
app.use('/api', api)
|
|
40
|
+
app.use('/thread', thread)
|
|
41
|
+
app.post('/context', (req, res, next) => {
|
|
42
|
+
if (!req.body.context) {
|
|
43
|
+
return next()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const domain = (process.env.TURBO_ENV === 'local') ? 'localhost' : '.vinely.ai'
|
|
47
|
+
const token = process.env.VLY_CONTEXT as string
|
|
48
|
+
const cookie = req.body.context
|
|
49
|
+
|
|
50
|
+
res.cookie(token, JSON.stringify(cookie), {
|
|
51
|
+
maxAge: 14 * ONE_DAY,
|
|
52
|
+
httpOnly: true,
|
|
53
|
+
domain,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
res.json({
|
|
57
|
+
data: 'context set'
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
app.use('/rpc', rpc)
|
|
61
|
+
app.use('/', main)
|
|
62
|
+
|
|
63
|
+
export default app
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import { Router } from 'express'
|
|
3
|
+
import APIMgr from '../utils/APIMgr.js'
|
|
4
|
+
|
|
5
|
+
interface ReqParams {
|
|
6
|
+
url: string;
|
|
7
|
+
method: string;
|
|
8
|
+
headers: any;
|
|
9
|
+
data?: any
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const router = Router()
|
|
13
|
+
|
|
14
|
+
router.get('/:resource', async (req, res, next) => {
|
|
15
|
+
const path = `${req.params.resource}`
|
|
16
|
+
const { payload } = await APIMgr.get(path, req.query, null)
|
|
17
|
+
res.json({ payload })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
router.get('/:resource/:id', async (req, res, next) => {
|
|
21
|
+
const path = `${req.params.resource}/${req.params.id}`
|
|
22
|
+
const { payload } = await APIMgr.get(path, null, null)
|
|
23
|
+
res.json({ payload })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
router.post('/:resource', async (req, res, next) => {
|
|
27
|
+
const path = `${req.params.resource}`
|
|
28
|
+
const { payload } = await APIMgr.post(path, req.body, null)
|
|
29
|
+
res.json({ payload })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
router.put('/:resource/:id', async (req, res, next) => {
|
|
33
|
+
const path = `${req.params.resource}/${req.params.id}`
|
|
34
|
+
const { payload } = await APIMgr.put(path, req.body, null)
|
|
35
|
+
res.json({ payload })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export default router
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Router, Response, NextFunction } from "express"
|
|
2
|
+
import { openai } from "@ai-sdk/openai"
|
|
3
|
+
// import { anthropic } from "@ai-sdk/anthropic"
|
|
4
|
+
import { StreamableHTTPClientTransport, StreamableHTTPClientTransportOptions } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
|
5
|
+
import {
|
|
6
|
+
streamText,
|
|
7
|
+
UIMessage,
|
|
8
|
+
stepCountIs,
|
|
9
|
+
convertToModelMessages,
|
|
10
|
+
experimental_createMCPClient as createMCPClient, // https://github.com/vercel/ai/discussions/5257
|
|
11
|
+
experimental_MCPClient as MCPClient
|
|
12
|
+
} from "ai"
|
|
13
|
+
import { hasAuthProvider, getAuthProvider } from "../utils/mcp-auth.js";
|
|
14
|
+
|
|
15
|
+
interface ChatMessages {
|
|
16
|
+
messages: UIMessage[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
const MCP_SERVERS: Record<string, string> = {
|
|
21
|
+
sports: 'https://mcp-sports-server-pbiesp.turbo360-staging.com/mcp',
|
|
22
|
+
population: 'https://mcp-population-server-jz3gg2.turbo360-staging.com/mcp',
|
|
23
|
+
domain: 'https://api.findadomain.dev/mcp',
|
|
24
|
+
notion: 'https://mcp.notion.com/mcp',
|
|
25
|
+
// notion: 'https://api.githubcopilot.com/mcp/', // did not work
|
|
26
|
+
// notion: 'https://server.smithery.ai/@rashidazarang/airtable-mcp/mcp' // did not work
|
|
27
|
+
// notion: 'https://server.smithery.ai/@VeyraX/veyrax-mcp/mcp', // worked
|
|
28
|
+
airtable: 'https://server.smithery.ai/airtable-server/mcp', // worked
|
|
29
|
+
rube: 'https://rube.app/mcp',
|
|
30
|
+
google: 'https://mcp.composio.dev/composio/server/fdb469c6-c700-4014-9827-e3547a2c0240/mcp?include_composio_helper_actions=true',
|
|
31
|
+
// mola: 'http://localhost:3000/mcp',
|
|
32
|
+
mola: 'https://mola-mcp-mdmvrp.turbo360-staging.com/mcp',
|
|
33
|
+
square: 'https://mcp.squareup.com/sse',
|
|
34
|
+
stripe: 'https://mcp.stripe.com',
|
|
35
|
+
// dats2101: 'http://localhost:3000/mcp',
|
|
36
|
+
dats2101: 'https://dats2101-mcp-vw2biy.turbo360-staging.com/mcp',
|
|
37
|
+
econ1012: 'https://hayden-mcp-prsjjh.turbo360-staging.com/mcp',
|
|
38
|
+
levy: 'https://levy-mcp-i5tnpu.turbo360-staging.com/mcp',
|
|
39
|
+
splitLoyalty: 'https://split-loyalty-mcp-qcqbll.turbo360-staging.com/mcp',
|
|
40
|
+
// 'banter-egcda42e': 'http://localhost:3000/mcp',
|
|
41
|
+
'banter-egcda42e': 'https://banter-mcp-pxmukt.turbo360-staging.com/mcp',
|
|
42
|
+
}
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const router = Router()
|
|
46
|
+
|
|
47
|
+
router.post('/', async (req: any, res: Response, next: NextFunction) => {
|
|
48
|
+
// console.log('\nMANIFEST: ' + JSON.stringify(req._vineyard))
|
|
49
|
+
const { vly_context } = req.cookies // check if undefined
|
|
50
|
+
// console.log('\nVLY_CONTEXT: ' + vly_context)
|
|
51
|
+
const ctx = JSON.parse(vly_context)
|
|
52
|
+
|
|
53
|
+
const { messages }: ChatMessages = req.body
|
|
54
|
+
console.log('LOG: ' + JSON.stringify(messages))
|
|
55
|
+
|
|
56
|
+
const datasource: Record<string, any> = req._vineyard.datasources[ctx.vine]
|
|
57
|
+
console.log('DATASOURCE: ' + JSON.stringify(datasource))
|
|
58
|
+
|
|
59
|
+
const headers: Record<string, string> = { 'vinely-args': JSON.stringify(datasource) }
|
|
60
|
+
// console.log('HEADERS: ' + JSON.stringify(headers))
|
|
61
|
+
|
|
62
|
+
// if not specified, default to banter:
|
|
63
|
+
const mcps = datasource.mcps || [
|
|
64
|
+
{
|
|
65
|
+
name: 'banter-egcda42e',
|
|
66
|
+
url: 'https://banter-mcp-pxmukt.turbo360-staging.com/mcp'
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const mcpClients = []
|
|
72
|
+
for (const mcp of mcps) {
|
|
73
|
+
let transport;
|
|
74
|
+
const url = new URL(mcp.url)
|
|
75
|
+
const opts: StreamableHTTPClientTransportOptions = {
|
|
76
|
+
requestInit: { headers }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (hasAuthProvider(url)) {
|
|
80
|
+
transport = new StreamableHTTPClientTransport(
|
|
81
|
+
url,
|
|
82
|
+
{
|
|
83
|
+
...opts,
|
|
84
|
+
authProvider: getAuthProvider(url),
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
} else {
|
|
88
|
+
transport = new StreamableHTTPClientTransport(url, opts)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const mcpClient = await createMCPClient({ transport, name: process.env.VLY_APP })
|
|
92
|
+
mcpClients.push(mcpClient)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const allTools: Record<string, any> = {}
|
|
96
|
+
for (const mcpClient of mcpClients) {
|
|
97
|
+
const tools = await mcpClient.tools() // this is an dictionary NOT an array
|
|
98
|
+
const toolNames: string[] = Object.keys(tools)
|
|
99
|
+
|
|
100
|
+
// TODO: prevent collision of tool names:
|
|
101
|
+
toolNames.forEach((toolName: string) => {
|
|
102
|
+
allTools[toolName] = tools[toolName]
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const result = streamText({
|
|
107
|
+
// this text will come from the vineyard level (when creating a vineyard):
|
|
108
|
+
system: `
|
|
109
|
+
You are a helpful assistant helping college-level studies review course materials. You have access to tools that allow you to get information from their uploaded content. Use these tools as a primary source when answering questions rather than your inherent knowledge. When answering, always relate your answer to the materials the user uploaded.
|
|
110
|
+
|
|
111
|
+
When appropriate, suggest additional questions the user may ask you to help further their understanding of the subject content. When you have presented a lot of material, offer to ask them a question to confirm their understanding.
|
|
112
|
+
`,
|
|
113
|
+
model: openai("gpt-4o"),
|
|
114
|
+
messages: convertToModelMessages(messages),
|
|
115
|
+
stopWhen: stepCountIs(5), // stop after 5 steps if tools were called
|
|
116
|
+
tools: allTools
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
console.log('RESULT: ' + JSON.stringify(result) + '\n')
|
|
120
|
+
|
|
121
|
+
res.set({
|
|
122
|
+
'connection': 'keep-alive',
|
|
123
|
+
'keep-alive': 'timeout=5',
|
|
124
|
+
'transfer-encoding': 'chunked'
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
result.pipeUIMessageStreamToResponse(res)
|
|
128
|
+
} catch (error: any) {
|
|
129
|
+
console.log('ERR: ' + error)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
export default router
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Router, Response, NextFunction } from 'express'
|
|
2
|
+
|
|
3
|
+
const router = Router()
|
|
4
|
+
|
|
5
|
+
const isLocal = process.env.TURBO_ENV === 'local'
|
|
6
|
+
|
|
7
|
+
const partials = {
|
|
8
|
+
imports: `partials/imports.${isLocal ? 'dev' : 'prod'}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
router.get('/', (req: any, res: Response, next: NextFunction) => {
|
|
12
|
+
res.render('landing', {
|
|
13
|
+
isLocal,
|
|
14
|
+
partials,
|
|
15
|
+
CDN: process.env.CDN,
|
|
16
|
+
context: JSON.stringify({
|
|
17
|
+
vineyard: req._vineyard
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
router.get('/:mcp', (req, res, next) => {
|
|
23
|
+
const mcp = req.params.mcp
|
|
24
|
+
const ONE_DAY = 24 * 60 * 60 * 1000
|
|
25
|
+
const domain = isLocal ? 'localhost' : '.vinely.ai'
|
|
26
|
+
|
|
27
|
+
res.cookie('vly_mcp', mcp, {
|
|
28
|
+
maxAge: 14 * ONE_DAY,
|
|
29
|
+
httpOnly: true,
|
|
30
|
+
domain,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
res.redirect('/')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export default router
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Router, Response, NextFunction } from "express"
|
|
2
|
+
import { handleMCPAuthCallback, setupAuthProvider } from "../utils/mcp-auth.js"
|
|
3
|
+
|
|
4
|
+
const MCP_CLIENT_NAME = "Test MCP Client"
|
|
5
|
+
|
|
6
|
+
const router = Router()
|
|
7
|
+
|
|
8
|
+
router.get('/setup', async (req: any, res: Response, next: NextFunction) => {
|
|
9
|
+
const { mcpUrl } = req.query
|
|
10
|
+
if (!mcpUrl) {
|
|
11
|
+
res.status(400).json({
|
|
12
|
+
data: 'Malformed request: mcpUrl required.'
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(decodeURIComponent(mcpUrl))
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// return new Response("Malformed request.", { status: 400 });
|
|
24
|
+
res.status(400).json({
|
|
25
|
+
data: 'Malformed request.'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const redirectUrl = await setupAuthProvider(MCP_CLIENT_NAME, new URL(url))
|
|
33
|
+
if (redirectUrl) {
|
|
34
|
+
res.redirect(redirectUrl.toString())
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// res.send('Authentication initiated')
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error("Error initiating MCP authentication: ", error)
|
|
42
|
+
res.status(500).json({
|
|
43
|
+
data: 'Failed to initiate MCP authentication.'
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
router.get('/auth', async (req: any, res: Response, next: NextFunction) => {
|
|
49
|
+
return await handleMCPAuthCallback(req, res)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export default router
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import { Router } from 'express'
|
|
3
|
+
|
|
4
|
+
const router = Router()
|
|
5
|
+
|
|
6
|
+
router.get('/:service/:action', (req, res, next) => {
|
|
7
|
+
res.json({
|
|
8
|
+
data: `RPC call ${req.params.service}:${req.params.action} must be POST request.`
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
router.post('/:action', async (req, res, next) => {
|
|
13
|
+
try {
|
|
14
|
+
const url = `${process.env.VINELY_RPC}/${req.params.action}`
|
|
15
|
+
const { data } = await axios({
|
|
16
|
+
url,
|
|
17
|
+
method: 'post',
|
|
18
|
+
data: req.body,
|
|
19
|
+
headers: {
|
|
20
|
+
'Accept': 'application/json',
|
|
21
|
+
'vinely-client': 'dashboard'
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (data.error) {
|
|
26
|
+
throw new Error(data.error)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
res.json({ payload: data })
|
|
30
|
+
} catch (error: any) {
|
|
31
|
+
res.json({
|
|
32
|
+
error: error.message
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
router.post('/:service/:action', async (req, res, next) => {
|
|
38
|
+
try {
|
|
39
|
+
const url = `${process.env.VINELY_RPC}/${req.params.service}/${req.params.action}`
|
|
40
|
+
const { data } = await axios({
|
|
41
|
+
url,
|
|
42
|
+
method: 'post',
|
|
43
|
+
data: req.body,
|
|
44
|
+
headers: {
|
|
45
|
+
'Accept': 'application/json',
|
|
46
|
+
'vinely-client': 'dashboard'
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// console.log('RPC: ' + JSON.stringify(data))
|
|
51
|
+
if (data.error) {
|
|
52
|
+
throw new Error(data.error)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
res.json({ payload: data })
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
res.json({
|
|
59
|
+
error: error.message
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export default router
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import { Router, Response, NextFunction } from 'express'
|
|
3
|
+
|
|
4
|
+
const router = Router()
|
|
5
|
+
|
|
6
|
+
const isLocal = process.env.TURBO_ENV === 'local'
|
|
7
|
+
|
|
8
|
+
const partials = {
|
|
9
|
+
imports: `partials/imports.${isLocal ? 'dev' : 'prod'}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
router.get('/:thread_id', (req: any, res: Response, next: NextFunction) => {
|
|
13
|
+
res.render('landing', {
|
|
14
|
+
isLocal,
|
|
15
|
+
partials,
|
|
16
|
+
CDN: process.env.CDN,
|
|
17
|
+
context: JSON.stringify({
|
|
18
|
+
vineyard: req._vineyard
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
router.get('/:site/:thread_id', async (req: any, res: Response, next: NextFunction) => {
|
|
24
|
+
try {
|
|
25
|
+
const { site, thread_id } = req.params
|
|
26
|
+
const url = `https://vinely.s3.us-east-1.amazonaws.com/sites/${site}/threads/${thread_id}.json`
|
|
27
|
+
|
|
28
|
+
const { data } = await axios({
|
|
29
|
+
url,
|
|
30
|
+
method: 'get',
|
|
31
|
+
headers: {
|
|
32
|
+
Accept: 'application/json'
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
res.json({ payload: data })
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
res.json({
|
|
39
|
+
error: error.message
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export default router
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
// import moment from 'moment'
|
|
3
|
+
|
|
4
|
+
const parseError = (err: any) => {
|
|
5
|
+
if (!err.response) {
|
|
6
|
+
return err
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!err.response.data) {
|
|
10
|
+
return err
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { status, data } = err.response
|
|
14
|
+
const error = new Error(JSON.stringify({
|
|
15
|
+
...data,
|
|
16
|
+
status,
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
error.name = data.type
|
|
20
|
+
return error
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const queryParamsToString = (query: any) => {
|
|
24
|
+
const keys = Object.keys(query)
|
|
25
|
+
const lastIndex = keys.length - 1
|
|
26
|
+
let queryString = ''
|
|
27
|
+
keys.forEach((key, i) => {
|
|
28
|
+
queryString += `${key}=${query[key]}`
|
|
29
|
+
if (i !== lastIndex)
|
|
30
|
+
queryString += '&'
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return queryString
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const API_VERSION = process.env.VLY_API_BASE
|
|
37
|
+
// const API_VERSION = `http://localhost:3000/rest`
|
|
38
|
+
|
|
39
|
+
const DEFAULT_HEADERS = {
|
|
40
|
+
'Accept': 'application/json',
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
43
|
+
// 'Cache-Control': 'no-cache',
|
|
44
|
+
// 'Pragma': 'no-cache',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class APIMgr {
|
|
48
|
+
constructor() {
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
defaultHeaders(reqHeaders: any) {
|
|
53
|
+
const headers: any = {
|
|
54
|
+
...DEFAULT_HEADERS,
|
|
55
|
+
// 'Client-TzOffset': tzOffsetHeader(),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!reqHeaders)
|
|
59
|
+
return headers
|
|
60
|
+
|
|
61
|
+
Object.keys(reqHeaders).forEach((headerName: string) => {
|
|
62
|
+
headers[headerName] = reqHeaders[headerName]
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return headers
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async get(endpoint: string, params: any, reqHeaders: any) {
|
|
69
|
+
const headers = this.defaultHeaders(reqHeaders)
|
|
70
|
+
// console.log('HEADERS: ' + JSON.stringify(headers))
|
|
71
|
+
|
|
72
|
+
const url = params
|
|
73
|
+
? `${API_VERSION}/${endpoint}?${queryParamsToString(params)}`
|
|
74
|
+
: `${API_VERSION}/${endpoint}`
|
|
75
|
+
|
|
76
|
+
// console.log('API MGR GET: ' + url)
|
|
77
|
+
try {
|
|
78
|
+
const { data } = await axios({
|
|
79
|
+
// url: `${API_VERSION}/${endpoint}`,
|
|
80
|
+
url,
|
|
81
|
+
method: 'get',
|
|
82
|
+
headers,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return data
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw parseError(error)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async post(endpoint: string, body: any, reqHeaders: any) {
|
|
92
|
+
const headers = this.defaultHeaders(reqHeaders)
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const { data } = await axios({
|
|
96
|
+
url: `${API_VERSION}/${endpoint}`,
|
|
97
|
+
method: 'post',
|
|
98
|
+
data: body,
|
|
99
|
+
headers,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return data
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw parseError(error)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async put(endpoint: string, body: any, reqHeaders: any, isFormData = false) {
|
|
109
|
+
try {
|
|
110
|
+
const headers = this.defaultHeaders(reqHeaders)
|
|
111
|
+
|
|
112
|
+
if (isFormData === true) {
|
|
113
|
+
const resp = await axios.put(`${API_VERSION}/${endpoint}`, body, {
|
|
114
|
+
responseType: 'blob',
|
|
115
|
+
headers: {
|
|
116
|
+
...headers,
|
|
117
|
+
'content-type': 'multipart/form-data',
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return resp
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { data } = await axios({
|
|
125
|
+
url: `${API_VERSION}/${endpoint}`,
|
|
126
|
+
method: 'put',
|
|
127
|
+
data: body,
|
|
128
|
+
headers,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return data
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw parseError(error)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
async delete(endpoint, body, reqHeaders) {
|
|
139
|
+
const headers = this.defaultHeaders(reqHeaders)
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const { data } = await axios({
|
|
143
|
+
url: `${API_VERSION}/${endpoint}`,
|
|
144
|
+
method: 'delete',
|
|
145
|
+
headers,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return data
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw parseError(error)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
*/
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default new APIMgr()
|